Scoped CSS style

The first thing we need is to find a way to parse/change css. The only lib I could find was this one https://github.com/acammack-r7/erlang-css, but I'm afraid it hasn't been maintained for a while. So we either find an alternative or we need to roll our own parser. Maybe using nimble_parsec again?

Read the scoped attribute from , if it's set, add a unique component hash id to selectors and translate the code accordingly. Example:

  ...

becomes:

  ...

Keep track of styles definitions in order to render only one definition for each type of component. Currently, if we render more than one instance of a component, the style is also rendered, causing unnecessary duplication.

Collecting the styles linked to the components is easily done using an Agent or by setting the styles as an attribute on the Component. The work (as far as I can tell) lies in injecting the relevant styles where they are needed. It would be necessary to collect a full tree of components and have the top-most component retrieve all the relevant styles of its children.
Why do you see the need to parse the CSS, could this not be implemented in an agnostic manner? The de-duplication of styles would not need any parsing of the CSS, as far as I can tell.
At the moment, the the scoped parameter is deprecated, although people are trying to start it back up again. Would it not be better to advise people to keep their styles out of the components? Seems a bit cleaner to me, but I guess people have use cases for this.
Please let me know if you have any suggestions on how to tackle this, would love to help.

Hey @Zurga!

Collecting the styles linked to the components is easily done using an Agent or by setting the styles as an attribute on the Component. The work (as far as I can tell) lies in injecting the relevant styles where they are needed. It would be necessary to collect a full tree of components and have the top-most component retrieve all the relevant styles of its children.

Yeah, that's what I have in mind so far.

Why do you see the need to parse the CSS, could this not be implemented in an agnostic manner? The de-duplication of styles would not need any parsing of the CSS, as far as I can tell.

The parsing is needed in order to change the styles by adding a unique hash to each of the selectors as I tried to show above. That way, the user can create something like "private" css classes for a particular component without leaking those styles to any of the child components. You can see the same approach being used by Vue.js here https://vue-loader.vuejs.org/guide/scoped-css.html.

At the moment, the the scoped parameter is deprecated, although people are trying to start it back up again.

Since we have our own parser, we don't need to worry much about that. Surface is a higher-level abstraction on top of HTML. We can certainly have our own semantics for the scoped attribute or even use another attribute (or directive) to avoid confusion. Personally, I don't like much the idea of injecting css styles directly in the component's file. I would prefer to add a collocated .css file that would be automatically "scoped"and  injected in the generated code as described here https://github.com/msaraiva/surface/issues/22

Would it not be better to advise people to keep their styles out of the components? Seems a bit cleaner to me, but I guess people have use cases for this.

Yeah, that's exactly why this issue was opened. I want to get as much feedback as possible about the pros and cons of having this feature in Surface. So far, I'm not convinced that it will make a big difference, that's why it's not even planned to be implemented for the first stable version. However, as far as I know, people use this a lot in Vue and Svelte and they love it, so let's wait and see if we get convinced that we should do this.
Thanks for your inputs!

I am very much so excited about this.
Another take on styling could be following the approach of what Styled Components is for React. We could have this as a new library or as part of Surface.
One of the main benefits of styled-components is being able to have dynamic CSS, which can take component props and update the CSS accordingly, and allows you to run Elixir code to calculate CSS. This also opens up the style system to allow helper libraries from the community: These may look similar to https://polished.js.org/, and https://github.com/styled-system/styled-system
StyledSurface (or whatever you would want to call it 🙂) could learn from the internals of StyledComponents on when and how to append to the DOM with updated styles. We would then, if I am not mistaken could leverage LiveView to broadcast any style updates to a style tag at the bottom of the HTML head tag. The auto-generated classes would work the same as with the original scoped styles idea above.
What are your thoughts on this idea @msaraiva? I would be willing to work on a prototype to get some ideas flowing, with your guidance.

Hi @garretttaco!
It looks like there are so many different approaches to deal with CSS nowadays that it makes almost impossible to define a specific built-in strategy for Surface without the high risk of becoming obsolete in a couple of months. I believe the best approach would be to start with separate libs for that and if eventually, those libs require any change in Surface, we can define new extension points to make them work.

What are your thoughts on this idea @msaraiva? I would be willing to work on a prototype to get some ideas flowing, with your guidance.

That's awesome! I have no idea right now which approach would be better for Surface so feel free to try any of them, following your judgment. You can try the "separate lib" strategy and let me know if you need any assistance. I'll be glad to help!

I saw the reference to this issue from the CSSWG thread on "bringing back scoped styles"; I just want to let you know there's a scoped attribute polyfill which might be of interest to you (created and maintained by a Googler): https://github.com/samthor/scoped

That looks Interesting. Thanks for the link, @Malvoz.

I am very much so excited about this.
Another take on styling could be following the approach of what Styled Components is for React. We could have this as a new library or as part of Surface.
One of the main benefits of styled-components is being able to have dynamic CSS, which can take component props and update the CSS accordingly, and allows you to run Elixir code to calculate CSS. This also opens up the style system to allow helper libraries from the community: These may look similar to https://polished.js.org/, and https://github.com/styled-system/styled-system
StyledSurface (or whatever you would want to call it 🙂) could learn from the internals of StyledComponents on when and how to append to the DOM with updated styles. We would then, if I am not mistaken could leverage LiveView to broadcast any style updates to a style tag at the bottom of the HTML head tag. The auto-generated classes would work the same as with the original scoped styles idea above.
What are your thoughts on this idea @msaraiva? I would be willing to work on a prototype to get some ideas flowing, with your guidance.

Hello, indeed this is most interesting ! I've been using Styled Component and React for some time and the  first thought I had when Surface was created is "If we only we could style Surface components in the same way !" ;-)
Have you started anything about this  ?
Thanks :-)

Hi @fpalluel!
Unfortunately, I wasn't able to start working on this yet as I'm still working on the core features for the first version. If you have already thoughts about the direction we should take regarding the API, feel free to share them. As I said, I'm not sure yet which direction we should take due to the uncountable number of different ways to work with CSS.

Hello @msaraiva :-)
Given the following styled-componentsJS code :
export const MyButton = styled.button`
      background-color:  ${props => (props.is_loading ? props.theme.background_color.loading : props.theme.background_color.primary)};
      border: none;
      color: ${props => props.theme.color.primary};
      padding: 15px 32px;
      text-align: center;
      text-decoration: none;
      display: inline-block;
      font-size: 34px;
`;

export const MySpecialButton = styled.MyButton`
      font-size: 16px;
`;

Note: theme gets injected from parent component via the React's Context API (same name, same purpose ;-) )
In Surface this could be (naively) :
defmodule SurfaceTestWeb.MyButton do
  use Surface.Component

  @doc "The loading state"
  property is_loading, :boolean, required: true

  @doc "The main content"
  slot default, required: true

  context get theme, from: Main

  def style do
    css """
      background-color: {{ if @is_loading, do: @theme.background_color.loading, else: @theme.background_color.primary }} ;
      border: none;
      color<EMAIL_ADDRESS>      padding: 15px 32px;
      text-align: center;
      text-decoration: none;
      display: inline-block;
      font-size: 16px;
    """
  end

  def render(assigns) do
    ~H"""
      <button>
        <slot />
      </button>
    """
  end
end

and
defmodule SurfaceTestWeb.MySpecialButton do
  use Surface.Component
  alias SurfaceTestWeb.MyButton

  @doc "The loading state"
  property is_loading, :boolean, required: true

  @doc "The main content"
  slot default, required: true

  def style do
    css """
      font-size: 34px;
    """
  end

  def render(assigns) do
    ~H"""
      <MyButton is_loading={{ @is_loading }}>
        <slot />
      </MyButton>
    """
  end
end

'style' being a optional callback and css a preprocessing macro.
Under the hood, for instance for MySpecialButton, the css would be parsed, the properties would be merged with MyButton css properties,  variables would be interpolated.
The final css is a unique class, which gets injected in a style tag, within the header, as @garretttaco suggested.
The whole styled-components process is well described in this article.
Whichever css integration method is chosen for Surface, we will need a CSS parser :-(
Perhaps porting stylis to elixir, using nimble_parsec ?

Hi @fpalluel!
Thanks a lot for your suggestion. I'll take a closer look at styled-components. There are still a few things that need to understand better, e.g. how does it work with existing classes, how to style nested nodes, etc.

Hi folks!
I've been doing some experiments regarding scoped styles in the last few days and I'd like to share some of the results and finally have a first official proposal. The solution is basically what has been proposed by @fpalluel (thanks again, BTW).
Let's start with the requirements.
Requirements
What do we need/want:

Define CSS styles that are scoped by component, i.e. styles defined for one component should not leak to other components. 1. Support updating styles dynamically based on runtime assigns' changes.
Extend the API to allow defining styles in the same file of the component
Allow defining the styles in a separated collocated file.
Create a default processor that can compile the styles into CSS and split the styles into:

Static styles (applies to all instances of a component)
Dynamic styles (applies only to an instance).

Support custom processors by implementing the StyleProcessor behaviour.

The proposal
We propose the following additions to in the API.
The style macro
Defines a style for a component. Either by defining it inline or in a collocated file.
Example (inline):

Dynamic styles
This is probably the only significant change we have compared with the original proposal from @fpalluel. Instead of allowing arbitrary changes in the style using interpolation with {{ }}, we restrict the scope of changes by injecting CSS variables with the dynamic values.
Example:

Regarding the mapping syntax, we can make it more flexible allowing accepting explicit keys and functions as values. Something like:
style vars: [^count, "--my-color": &calculated_value/1] do
  ...
end

And calculated_value could be:
def calculated_color(assigns) do
  colors = ["red", "green", "black"]
  index = rem(assigns.count, length(colors))
  Enum.at(colors, index)
end

Why not allowing {{ }} directly?
Because:

It's more complex.  We end up introducing a "new" language which is basically CSS or SCSS with {{...}} interpolation. The problem of introducing a new language to solve a problem is that the new language comes with its own set of problems, e.g. efficiently parsing the new rules related to interpolation, handling conflicting grammar between both languages (elixir and css/scss) and so on.
We have the concept of dynamic CSS Variables in CSS. We also have static variables in SCSS using the $ prefix and interpolations using #{}. Why introduce another concept on top of all that? I believe mapping values from one language to the other is a safer/better approach.
Tooling support. By not creating a new language, we can reuse existing tools like editor support for auto-complete without breaking them. In the example above the syntax-highlighting works flawless, after all, it's valid scss.

External files
By default, we could accept a collocated .scss file that would be automatically processed. Adding an explicit style would only be necessary if you need to pass any option, e.g. mapping dynamic values.
style vars: ["--my-color": ^color]

We could also accept defining the file explicitly:
style src: "./other_file.scss"

Custom processors
As I said on other occasions, the CSS world moves extremely fast. My plan is to provide a default style processor for scss. However, other devs will be able to roll their own solution by implementing a Surface.StyleProcessor.
Initial implementation
As an experiment, I've been working on an initial implementation for this proposal.
How does it work?
Given the following Counter component:
defmodule Counter do
  use Surface.LiveComponent

  data count, :integer, default: 32
  data color, :string, default: "black"

  style vars: ["--count": ^count, "--color": ^color] do
    scss """
    & {
      border: solid 2px var(--color);
      border-radius: 10px;
      padding: 10px;

      .title {
        font-size: calc(var(--count) * 1px);
      }

      > .subtitle {
        color: var(--color);
        font-weight: bold;
        font-style: italic;
        &:hover {
          color: orange;
        }
      }
    }
    """
  end

  def render(assigns) do
    ~H"""

        <button class="button is-info" :on-phx-click="dec">
          -
        </button>
        <button class="button is-info" :on-phx-click="inc">
          +
        </button>

font-size: {{ @count }}

color: {{ @color }}

    """
  end

  def handle_event("inc", _, socket) do
    count = socket.assigns.count + 1
    {:noreply, assign(socket, count: count, color: get_color(count))}
  end

  def handle_event("dec", _, socket) do
    count = socket.assigns.count - 1
    {:noreply, assign(socket, count: count, color: get_color(count))}
  end

  def get_color(count) do
    colors = ["red", "green", "black"]
    index = rem(count, length(colors))
    Enum.at(colors, index)
  end
end

And adding a couple of instances to a page:

The actions taken will be:

The scss code is compiled by LibSass using sass.ex.
The resulting CSS is split into two parts, one for the static css and the other for the dynamic part.
All static parts from all components are merged into a components.css file currently located in priv/static/css/.
Each dynamic part is rendered along with its related component instance.

Here's the static file generated for the Counter component:
/* This file was generated by the Surface compiler */

[data-sface-module="Counter"] {
  --count: 32;
  --color: black;
}

[data-sface-module="Counter"] {
  border: solid 2px var(--color);
  border-radius: 10px;
  padding: 10px;
}

[data-sface-module="Counter"] .title {
  font-size: calc(var(--count) * 1px);
}

[data-sface-module="Counter"] > .subtitle {
  color: var(--color);
  font-weight: bold;
  font-style: italic;
}

[data-sface-module="Counter"] > .subtitle:hover {
  color: orange;
}

Besides including all static properties into the components.css, we also initialize all dynamic properties with their default values, if any.
Here's the dynamic part:

Limitations

Although all modern browsers already have support for CSS Variables, there's no built-in support for IE.
sass.ex hasn't been updated for a couple of years. We'll need to either try to update it or choose another lib.
More? Tell me, please :)

In case you have any question or suggestion, please, let me know.
Cheers.

This is interesting, I was thinking about using something like structs for all possible values and parse it using those macros (css, scss, sass) and use a validator (maybe the same approach from Ecto) and returns an error in compile-time if some property has the wrong value.
This idea came from repo (https://github.com/davesnx/styled-ppx), which my friend constributed with a parser from CSS specs (https://github.com/EduardoRFS/reason-css-parser) for ReasonML and was implemented inside the StyledPPX library.
So I though about "Why not made it work with Surface, bringing a new experience when developing frontend with Phoenix?"
I'll let you know when I start to work with this project to improve :)

Hey @aleDsz!
Although the default built-in style processor will probably only handle CSS and SCSS code, I think the API should be flexible enough to allow something like:
style :my_custom_style do
  # Any code allowed by the custom processor
  %MyStruct{
    ...
  }
end

The block content (along with any option defined) would be forwarded to the custom processor that is registered as :my_custom_style. This way the code can be validated and parsed by the processor at compile-time.
The StyleProcessor behaviour will be something like:

In case you already have an idea of how the styles would look like using your elixir structs, you can send a couple of snippets so we can see if the proposed model will be a good fit.

In case anyone wants to give it a try, a PoC project can be found at https://github.com/msaraiva/surface_app/tree/style-experiments.

Hey @msaraiva!
My idea is not only to use structs to represent the types of properties, but also the values. However, instead of writing the style with structs, you would use the standard CSS parser for a typed CSS and do the validations in compile-time.
Like this example with ReasonML:

You can write pure CSS, but the validation will occur when your are writing code, and if something it's wrong, we can make it shows an error in the editor.

I was thinking re-use the idea from Ecto validations, to check if property support some value and create a message when the CSS have unsupported values.
Next week I'm on vacation from job, so I'll have more time to think about this idea and start code to see how it should work

Hi @aleDsz!

You can write pure CSS, but the validation will occur when your are writing code, and if something it's wrong, we can make it shows an error in the editor.

Since all translation happens at compile-time, we already show any error found by the Sass compiler. Here's an example:

I was thinking re-use the idea from Ecto validations, to check if property support some value and create a message when the CSS have unsupported values.

Such validation can certainly be provided as soon as we have a parser or if libsass provides an API to access the parsed nodes. As far I as I know it doesn't, we just have access to the final generated CSS. Having our own parser would solve that.

Next week I'm on vacation from job, so I'll have more time to think about this idea and start code to see how it should work

Awesome! Let me know if you have any question or need any assistance.

Yeah, sure.
Interesting this SASS validation, but as you said, if we create our own parser, we can't catch the error before generating the final CSS.
I have some ideas how to implement it, but first we need to create a parser from CSS specs, and after that, create the structs to implement the validator and such things.

Good day! What is status with task and perhaps I can help anyhow with my limited knowledge, if task is well defined. :)

Hi @mxrguspxrt!
We're currently working on a few modifications in the context API in order to make it more flexible and more importantly, make it work nicely with LV's change tracking mechanism. We realized change tracking was being disabled whenever using contexts or slots.
Since it involves changes in the API, we decided to address those issues before releasing our first stable version. The work is almost done and as soon as we release v0.1.0, we'll be back to the issue tracker and review the priorities.
We might include this feature for v0.2.0 if we feel we have enough information that can give us a clear picture of what should be done. I don't feel we're quite there yet. We didn't get much feedback regarding the proposed solution. I'd love to hear more about the pros and cons, as well as alternative solutions before adding this feature to the core lib.
Is the proposed solution using LibSass + CSS variables for dynamic styles a good fit for you?

I have minimal exposure to Phoenix and Elixir however I’m an experienced frontend dev I’d love to use this feature in an ongoing project I’m having developed. Some general thoughts:

It would be nice if css can be autoprefixed out of the box as it’s a common thing done.
I think SCSS support is generally more than sufficient as a MVP, though later it might be nice to provide easy configurable support for more bleeding edge spec stuff (similar to how babel does) without making the user entirely roll their own.
Given that CSS is scoped, is there any plans to add a precompile step to generate a separate CSS file? If you have a site that renders 30 buttons or so, repeating the style block for each one, vs generating a stylesheet that can be cached across renders could be much more performing.
I have experience in production with styled components (otherwise known as css-in-is) and would really appreciate an implementation that doesn’t use it as it suffers from performance issues and is generally built for declarative frontend frameworks (and using things like polished and these other javascript frameworks in this framework don’t really make sense to me. the whole deal with phoenix live view is minimalism in how much javascript you need, so to take these javascript libraries for customizing saturation just seem a bit silly to me vs doing it in phoenix / elixir and passing the data from the controller or wherever onto these specific components.
Assuming you have a single cached stylesheet, you may need to re-think how you do the dynamic values? It could be the case that css variables are scoped to some element but I’m not entirely sure that plays well with a global stylesheet? I don’t see the dynamic values being that big of an issue though (at least for version 1).

Hi @reywright!
Thanks for sharing your thoughts.

Given that CSS is scoped, is there any plans to add a precompile step to generate a separate CSS file?

The PoC we have does exactly that. It generates a file containing all the styles scoped by each component/module. Only the dynamic parts are duplicated for each instance of a component.

I'm not (yet) using Phoenix developer, just curious about this project, so this question might be very basic. How would a component like <SomeItem selected={{ @foo }} /> (where the selected prop toggles the item color) be implemented?

@msaraiva
I hope you are good! :)
What are the future plans with https://github.com/msaraiva/surface/compare/scoped-styles  and when it might become part of master?

Another idea if you dont take a scss route that i was discussing with coworkers is potentially making styles composable for example
defmodule Button do
  use Surface.Component

  def render(assign) do
    ~H"""
    <button>{{ @name }}</button>
    """
  end

end

defmodule Form do
  use Surface.Component

  @CancelButton styled Button, """
    color: red;
  """

  @SubmitButton styled Button, """
    color: blue;
  """

  def render(assigns) do
    ~H"""

      <@CancelButton name="Cancel" />
      <@SubmitButton name="Cancel" />

    """
  end
end

There may be a better way to do something like that - or i'm blind and glossed over it :)
Just some feedback! Great work so far!

Hi @Eein!
Thanks for the feedback.
I like the idea. But what if we had a more generic way to create this kind of private lightweight stateless components (we can call them templates for now). The difference is that it would be able to set not only styles but any defined property. Something like:
defmodule Form do
  use Surface.Component

  template CancelButton :: Button,
    label: "Cancel",
    style: """
    color: red;
    """

  def render(assigns) do
    ~H"""

      ...
      <CancelButton/>

    """
  end
end

We could even use this very same model to extend HTML tags directly. Like:
defmodule Form do
  use Surface.Component

  template CancelButton :: button,
    class: "btn rounded",
    style: "color: red",
    do: ~H(Cancel)

  def render(assigns) do
    ~H"""

      ...
      <CancelButton/>

    """
  end
end

One great benefit of this approach is that, in both cases, we don't have to create real modules for them. They are just templates, which means that <CancelButton/> would be replaced by <button class="btn rounded" ...>Cancel</button> at compile-time.
WDYT?

Curious ... given the original PoC relied on the now defunct translator to process styles, how would this work with the move to the compiler?

Hi @lewisf!
I'll move all code from the PoC to a new separate project and update it accordingly.

WDYT?

looks great!
:+1:

hi @msaraiva!
looking to see if i can help out with anything over the holidays / next couple of weeks. i'm REALLY excited to have style definitions co-located in Surface components -- there's something really nice about defining behavior and look and feel in the same place.
I was wondering if it's worth having the style definitions respond to changes in props/state. styled-components from the React ecosystem allows folks to do :
const CancelButton = styled("button")`
  color: red,
  ${(props) => {
     // return a styles object
  }
`

With this ability to "extend" the css of any component, there isn't really a need for class anymore, which is kind of nice.

Very excited about this thread! One question I can't tell so far, would the generated CSS classes be globally unique? I think that's a huge benefit of Styled Components for example.

The proposed style approach, that can optionally map properties from the component to CSS variables, seems like a sound one.
A styled-component-like library can be easily built on top of that foundation, but it doesn't seem core to the project.

The proposed style approach, that can optionally map properties from the component to CSS variables, seems like a sound one.
A styled-component-like library can be easily built on top of that foundation, but it doesn't seem core to the project.

looking to see if i can help out with anything over the holidays / next couple of weeks. i'm REALLY excited to have style definitions co-located in Surface components -- there's something really nice about defining behavior and look and feel in the same place.

Hi @lewisf!
Sorry for the late response. As I mentioned before, anything regarding styles will probably be done in a separate project anyway so feel free to try out and bring for discussion any experiment you might have using whatever ideas you think are appropriate (although I believe your couple of weeks are gone 🙂).
I still need to update the PoC we have to make it work with the latest changes. I'll do that after releasing v0.1.2.

Very excited about this thread! One question I can't tell so far, would the generated CSS classes be globally unique? I think that's a huge benefit of Styled Components for example.

@Adzz yes. That's the idea.

looking to see if i can help out with anything over the holidays / next couple of weeks. i'm REALLY excited to have style definitions co-located in Surface components -- there's something really nice about defining behavior and look and feel in the same place.

Hi @lewisf!
Sorry for the late response. As I mentioned before, anything regarding styles will probably be done in a separate project anyway so feel free to try out and bring for discussion any experiment you might have using whatever ideas you think are appropriate (although I believe your couple of weeks are gone 🙂).
I still need to update the PoC we have to make it work with the latest changes. I'll do that after releasing v0.1.2.

Very excited about this thread! One question I can't tell so far, would the generated CSS classes be globally unique? I think that's a huge benefit of Styled Components for example.

@Adzz yes. That's the idea.

Scoped CSS style component approach basically addresses issue of hard to maintain CSS and conflicting CSS in big projects.
Same result, with different approach, could be achieved by using:
https://adamwathan.me/css-utility-classes-and-separation-of-concerns/
http://tailwindcss.com/
TailwindCSS configuration simply enables custom configuration, if needed, but defaults are also very good.

Scoped CSS style component approach basically addresses issue of hard to maintain CSS and conflicting CSS in big projects.
Same result, with different approach, could be achieved by using:
https://adamwathan.me/css-utility-classes-and-separation-of-concerns/
http://tailwindcss.com/
TailwindCSS configuration simply enables custom configuration, if needed, but defaults are also very good.

Hi @msaraiva!
I want to say I really like your suggestion as a first step to bring scoped CSS into Surface. Leveraging SASS and CSS variables is a great idea and will help get started much quicker. That said I see a few issues I think are worth mentioning.
Disclaimer: I'm a full-time React developer and a big fan of styled-components. As a whole, my suggestions basically boils down to "please re-implement styled-components in Elixir", but it think they still have value when taken individually :slightly_smiling_face:
First, while CSS variables are great, they're not as versatile as a full-blown templating solution. Their main drawback is that they can't be used outside of property values. Most of the time this is fine, but it can be an issue, namely in media queries. For example, with React and styled-components, I could do the following:
const Wrapper = styled.div`
  @media (max-width: ${props => props.mobileBreakpoint}) {
    ...
  }
`

There is no way to reproduce this with CSS variables. Again, this is still a great way to get started quickly, but I think in the long run full templating support will be a really nice feature to have, if not a must. Templating also has the advantage that if a variable needs to be calculated, you can keep the calculation inside the CSS (assuming it's fairly short), thus maintaining a better separation of concerns.
Second, I think having the ability to compose styles is very important (again, more of a long term goal), as @Eein suggested. I'm not sure if this is just an implementation detail or if you had a specific reason for it, but the data-sface-module makes that kinda hard I think. The way styled-components works is that it simply generates and injects classes into components. This means that composition is simply a matter of adding a new class on top of the already existing ones. Just something to consider.
Second-and-a-half, I'm not a big fan of having a single style macro that has to be applied to the whole module. I would personally much prefer to have the lib generate components for me that I can then use in my markup as I see fit. This has the advantage of making the markup/styling clearer because you don't have to add class all over the place. It also makes it easier to figure out which element uses which style because you don't have to look for the class attributes which can get a little lost if your markup uses many props. (I marked this as "second-and-a-half" because this point is kind of a prerequisite to the composition solution as I see it)
Third, regarding the complexity of the solution I'm proposing, losing compile time validation of CSS is really not a big deal to me. Unlike buisness logic, which has many corners and edge cases that compilation errors can greatly help to deal with, CSS is imo more "one-time-deal" type of thing. What I mean is that when you're writing CSS, you usually have the browser open on the side and are constantly checking to see if what you've written does what you want. Actually, a great way to write CSS is to actually write it in the dev tools and only copy-paste it into your project once it's done. Besides, browser dev tools are already really good at telling if your CSS is valid or not.
Finally, I want to mention that the big drawback with what I'm proposing is that collocated stylesheets are kind of out of the question. This isn't a problem for me as I honestly hate the idea of having to navigate between three files to access my markup, logic and style, but I do realize that many people in the Phoenix community seem to really like it, so it's definitely something to consider.

Hi @Blond11516!
Thanks for your input. Those are very good points to consider and shows exactly how hard is to pick a specific strategy to implement as a built-in feature in Surface. There are too many ways to use CSS in a component-based project today, each one of them providing different tradeoffs. I'm still not convinced that we should bring all that mess to the core.

Agreed, an external library is a much better fit for this. Besides that leaves the door open for others to do their own thing, so I can have my styled-components clone while the rest of the community uses collocated styles! :D

Just my 2 cents.
Since this will not be in the core due to the different approaches possible.
Maybe something can be configured using the Javascript Hooks to apply styles
maybe using https://github.com/indooorsman/esbuild-css-modules-plugin
or https://cssinjs.org/?v=v10.9.0

We have a new implementation for this feature based on Vue's approach. See https://github.com/surface-ui/surface/pull/621 for details.

Implemented in #621.


License

Thank you for providing the source code for this amazing project! In order to allow people to also use your code, can you please add a software license such as the MIT license? Otherwise, no one is legally allowed to work with your code (https://choosealicense.com/no-permission/). You can find further information provided by GitHub here: https://docs.github.com/en/repositories/managing-your-repositorys-settings-and-features/customizing-your-repository/licensing-a-repository

Thank you for the kind reminder. I have added an MIT license.

Thank you!


Created a 'write_audio_to_buffer' function.

This function accepts a USoundWaveProcedural and a Python buffer object. The 'QueueAudio' function on the USoundWaveProcedural is called, passing along the binary data within the buffer. This audio can then be played via normal Unreal methods (adding to an AudioComponent, playing sound at a location, etc.).
I'm not entirely sure if this fits in with the rest of the project, given it's a function that applies just for one class (USoundWaveProcedural). Even so, I don't think there is a way to do it entirely within Python, since you'd need to pass in a raw uint8* array to the function.

Hi, thanks for the patch, i think it should not take 2 arguments, but only the buffer as SoundWaveProcedural should be 'self' and maybe i would rename it to 'queue_audio':
from unreal_engine.classes import SoundWaveProcedural

sound_wave = SoundWaveProcedural()
sound_wave.queue_audio(data)

world.play_sound_at_location(sound_wave, ...)

I've implemented your requested changes in the most recent commit (#85de0dd).

Thanks, refactored a bit: https://github.com/20tab/UnrealEnginePython/commit/1c8fbc0ada6b7f3eee22a52d48317a26ab0154d0


Add 'Vacated' and 'Occupied' events

We need these events to properly send webhook and console events. Right now the implementation may not warn if the channel is actually vacated or not.

Closed by https://github.com/edgurgel/poxa/pull/70


Error calling pool.connect with callback

Summary
Calling pool.connect on a new ConnectionPool errors and calls callback with null error. This is not experienced when using a Promise.
Environment

Macbook Pro
Node v9.4.0

Example Code
const sql = require('mssql');
const config = require('./config');

const pool = new sql.ConnectionPool(options)
pool.connect(err => {
  console.error('Error connecting');
  console.error(err);
  process.exit(-1);
});

/* Query stuff */

Result
Error connecting
null

Expected
{
  recordsets: [
...

Working code
const sql = require('mssql');
const config = require('./config');

const pool = new sql.ConnectionPool(options)
pool.connect()
  .catch(err => {
    console.error('Error connecting');
    console.error(err);
    process.exit(-1);
  })

/* Query stuff */

Additionally, removing the process.exit call lets the code run. It appears that the callback is being executed regardless of error. I was able to return results after connecting even though the callback was executed.

My mistake. I misinterpreted the documentation on sql.ConnectionPool. I had assumed it executed the callback when there was an error rather than the traditional Node style.
Sorry.


DataURL images also send network request to public path

Operating System: Mac OSX
Node Version: 12.16.2
NPM Version: 6.14.4
webpack Version: 4.42.1
url-loader Version: 4.1.0

Expected Behavior
Images under 1000 bytes are inlined as DataURL and no network request are done for those images.
Actual Behavior
Images under 1000 bytes are inlined as DataURL but a network request is done for each images (and failing with 404 error code because path doesn't exists).
Here a screenshot in local mode :

Code
My webpack images loader settings
{
            test: [/\.bmp$/, /\.gif$/, /\.jpe?g$/, /\.png$/, /\.svg$/],
            use: [
              {
                loader: require.resolve('url-loader'),
                options: {
                  limit: 10000,
                  name: 'static/media/[name].[hash:8].[ext]',
                }
              },
              {
                loader: require.resolve('image-webpack-loader'),
                options: {
                  optipng: {
                    enabled: false,
                  },
                  svgo: {
                    enabled: false
                  },
                  webp: {
                    quality: 85
                  }
                }
              }
            ]
}

The way I import images in my views
import CloseIcon from '../../../assets/images/icons/close.svg';

<img src={CloseIcon} alt='close' />

How Do We Reproduce?
I think the best way is creating a CRA app and eject (what I did 2 years ago), and putting my webpack config file
I have this problem in production and to reproduce it in local you have to run
yarn build
yarn serve

Because some images is more than limit: 10000

From docs:
If the file size is equal or greater than the limit file-loader will be used (by default) and all query parameters are passed to it.

Images under 1000 bytes are inlined as DataURL but a network request is done for each images (and failing with 404 error code because path doesn't exists).

We don't inject network request, you can look at our code :smile:

Because some images is more than limit: 10000

From docs:
If the file size is equal or greater than the limit file-loader will be used (by default) and all query parameters are passed to it.

Yes I know and it's working well, images size greater than limit are loaded via path, but images under limit are inlined as DataURL but still do a network request...I know you don't inject network request according to the doc but I don't understand why I still have one...
I thought it could be some cache but I have nothing about those images in asset-manifest, or maybe the way I import images ? Or something that I do wrong in the webpack configuration...?
Thanks for your time 🙏

You can see here screenshots from my production app :
A first call with path 404 (type is text/html)

And then correct call  as DataURL (type svg+xml)

@marinav Can you create reproducible test repo?

@evilebottnawi ok I'm sorry it was due to an SSR setting in my project, I haven't notice before...I close the issue sorry for the waste of time.
Thank you 🙏


TransformSequence.Loop() is broken for instantaneous transforms

Currently, instantaneous transforms are updated instantly when they're added to the target Drawable. This occurs before IsLooping is set, and results in the transform being immediately removed from the target.
This is not intended for some transforms, e.g. Spin() which rely on the instantaneous transform being looped.
The following is an easily replicable case:
box.MoveToX(100)
      .Then().Delay(1000).MoveToX(0)
      .Then().MoveToX(200, 1000).Loop();

Which result in the following:

Time
Expected-X
Actual-X
Comment

0
100
100

999
100
100

1000
0
0

1999
~200
~200

2000
100
200
New loop starts

2999
100
200

3000
0
0

3999
~200
~200

4000
100
200
New loop starts

expected vs actual
Haven't been able to completely resolve this issue yet.

Want to back this issue? Post a bounty on it! We accept bounties via Bountysource.

It indeed was!


Initializing TorchBackend in DeepSpeed with backend nccl exits with return code = -11

Hello,
I am experiencing an issue when trying to initialize TorchBackend with DeepSpeed using the backend nccl. The operation fails with return code = -11.
Here are the steps to reproduce the issue:

bash /home/sds/ustcllm/jellow/DeepSpeedExamples/applications/DeepSpeed-Chat/training/step1_supervised_finetuning/training_scripts/single_node/run_13b.sh

I have successfully run this on other machines without any issues, but for some reason, it's not working in my current environment.
Environment details:

OS: Linux gnode03 5.15.0-60-generic 66-Ubuntu SMP Fri Jan 20 14:29:49 UTC 2023 x86_64 x86_64 x86_64 GNU/Linux
Python version: 3.10
DeepSpeed version:  0.9.2
PyTorch version: 2.0.1
CUDA/cuDNN version: 12.0.140
GPU model and memory: A100*8 80GB

Here are the relevant parts of the log:
[2023-05-22 13:46:52,710] [WARNING] [runner.py:191:fetch_hostfile] Unable to find hostfile, will proceed with training with local resources only.
Detected CUDA_VISIBLE_DEVICES=0,1,2,3: setting --include=localhost:0,1,2,3
[2023-05-22 13:46:52,744] [INFO] [runner.py:541:main] cmd = /home/sds/ustcllm/jellow/venv/bin/python -u -m deepspeed.launcher.launch --world_info=eyJsb2NhbGhvc3QiOiBbMCwgMSwgMiwgM119 --master_addr=<IP_ADDRESS> --master_port=29500 --enable_each_rank_log=None /home/sds/ustcllm/jellow/DeepSpeedExamples/applications/DeepSpeed-Chat/training/step1_supervised_finetuning/main.py --data_path Dahoas/rm-static Dahoas/full-hh-rlhf Dahoas/synthetic-instruct-gptj-pairwise yitingxie/rlhf-reward-datasets --data_split 2,4,4 --model_name_or_path facebook/opt-13b --per_device_train_batch_size 4 --per_device_eval_batch_size 4 --max_seq_len 512 --learning_rate 1e-4 --weight_decay 0. --num_train_epochs 16 --gradient_accumulation_steps 1 --lr_scheduler_type cosine --num_warmup_steps 0 --seed 1234 --gradient_checkpointing --zero_stage 3 --lora_dim 128 --lora_module_name decoder.layers. --deepspeed --output_dir /home/sds/ustcllm/jellow/DeepSpeedExamples/applications/DeepSpeed-Chat/run/13B/step1
[2023-05-22 13:46:55,272] [INFO] [launch.py:229:main] WORLD INFO DICT: {'localhost': [0, 1, 2, 3]}
[2023-05-22 13:46:55,272] [INFO] [launch.py:235:main] nnodes=1, num_local_procs=4, node_rank=0
[2023-05-22 13:46:55,272] [INFO] [launch.py:246:main] global_rank_mapping=defaultdict(<class 'list'>, {'localhost': [0, 1, 2, 3]})
[2023-05-22 13:46:55,272] [INFO] [launch.py:247:main] dist_world_size=4
[2023-05-22 13:46:55,272] [INFO] [launch.py:249:main] Setting CUDA_VISIBLE_DEVICES=0,1,2,3
[2023-05-22 13:46:59,328] [INFO] [comm.py:622:init_distributed] Initializing TorchBackend in DeepSpeed with backend nccl
[2023-05-22 13:47:02,284] [INFO] [launch.py:428:sigkill_handler] Killing subprocess 2430401
[2023-05-22 13:47:02,300] [INFO] [launch.py:428:sigkill_handler] Killing subprocess 2430402
[2023-05-22 13:47:02,301] [INFO] [launch.py:428:sigkill_handler] Killing subprocess 2430403
[2023-05-22 13:47:02,406] [INFO] [launch.py:428:sigkill_handler] Killing subprocess 2430404
[2023-05-22 13:47:02,513] [ERROR] [launch.py:434:sigkill_handler] ['/home/sds/ustcllm/jellow/venv/bin/python', '-u', '/home/sds/ustcllm/jellow/DeepSpeedExamples/applications/DeepSpeed-Chat/training/step1_supervised_finetuning/main.py', '--local_rank=3', '--data_path', 'Dahoas/rm-static', 'Dahoas/full-hh-rlhf', 'Dahoas/synthetic-instruct-gptj-pairwise', 'yitingxie/rlhf-reward-datasets', '--data_split', '2,4,4', '--model_name_or_path', 'facebook/opt-13b', '--per_device_train_batch_size', '4', '--per_device_eval_batch_size', '4', '--max_seq_len', '512', '--learning_rate', '1e-4', '--weight_decay', '0.', '--num_train_epochs', '16', '--gradient_accumulation_steps', '1', '--lr_scheduler_type', 'cosine', '--num_warmup_steps', '0', '--seed', '1234', '--gradient_checkpointing', '--zero_stage', '3', '--lora_dim', '128', '--lora_module_name', 'decoder.layers.', '--deepspeed', '--output_dir', '/home/sds/ustcllm/jellow/DeepSpeedExamples/applications/DeepSpeed-Chat/run/13B/step1'] exits with return code = -11

Hi, I encounter the same issue as you when i use ustc-scc. My environment is very similar to yours.
python==3.11.3
pytorch==2.0.1 (py3.11_cuda11.7_cudnn8.5.0_0)
transformers==4.29.1
cuda==11.7

Try setting the following two environment variables
1、export PATH=/usr/local/cuda-12.0/bin:$PATH
2、export LD_LIBRARY_PATH=/usr/local/cuda-12.0/lib64:$LD_LIBRARY_PATH

I'm also hitting the same exact error. Did anyone find a solution to this?

I tried to set it up, but it still gives an error.

Try setting the following two environment variables 1、export PATH=/usr/local/cuda-12.0/bin:$PATH 2、export LD_LIBRARY_PATH=/usr/local/cuda-12.0/lib64:$LD_LIBRARY_PATH

cuda installation path is /opt/cuda, I check the PATH and LD_LIBRARY_PATH:
>echo $PATH
/opt/cuda/11.7.1_515.65.01/bin:/opt/cuda/11.7.1_515.65.01/nvvm:/home/user/.conda/envs/user/bin:/opt/Anaconda3/2022.05/condabin:/usr/lpp/mmfs/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/snap/bin
>echo $LD_LIBRARY_PATH:
/opt/cuda/11.7.1_515.65.01/lib64/stubs:/opt/cuda/11.7.1_515.65.01/lib64
>ls /opt/cuda/11.7.1_515.65.01/lib64/stubs|grep libnvidia
libnvidia-ml.so
libnvidia-ml.so.1

I noticed that there are 2 warnings may related to the error:
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
WARNING:

You should always run with libnvidia-ml.so that is installed with your
NVIDIA Display Driver. By default it's installed in /usr/lib and /usr/lib64.
libnvidia-ml.so in GDK package is a stub library that is attached only for
build purposes (e.g. machine that you build your application doesn't have
to have Display Driver installed).
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!

/home/user/.conda/envs/user/lib/python3.11/site-packages/torch/cuda/__init__.py:546: UserWarning: Can't initialize NVML
  warnings.warn("Can't initialize NVML")

Try setting the following two environment variables 1、export PATH=/usr/local/cuda-12.0/bin:$PATH 2、export LD_LIBRARY_PATH=/usr/local/cuda-12.0/lib64:$LD_LIBRARY_PATH

I tried to set it up, but it still gives an error.

If the problem of exits code with -11 still occurs, then I am not sure why it occurs.
But if the error "RuntimeError: CUDA error: OS call failed or operation not supported on this OS" appears after setting, it is likely that the RAM is insufficient, try setting "offload_optimizer_device" to none

Hi everyone,
I have managed to resolve the issue I was experiencing when trying to initialize TorchBackend with DeepSpeed using the backend nccl. The operation was failing with return code = -11.
The solution was to disable the InfiniBand (IB) transport in NCCL by setting the NCCL_IB_DISABLE environment variable to 1. To do this, simply run the following command before executing your script:
export NCCL_IB_DISABLE=1

This command tells NCCL not to use InfiniBand as the transport method for communication. Disabling InfiniBand might be necessary in certain system configurations or when InfiniBand is not available.
After applying this solution, I was able to successfully run my script without any issues. If you are encountering a similar problem, I hope this solution helps you as well.
Best regards

BTW, only use export NCCL_IB_DISABLE=1 do not work in my situation (2x8v100 on cluster machine). It can not fully disable IB. So I use the following cmd:
export NCCL_IB_DISABLE=1
export NCCL_IBEXT_DISABLE=1


DiagonalPolicy in ParBilinearForm

I want to assemble a parallel matrix using the diagonal policy Matrix::DIAG_ZERO. All of the parallel elimination routines (ParBilinearForm::FormLinearSystem, ParallelEliminateEssentialBC, ParallelAssembleElim, etc.) implicitly use the policy Matrix::DIAG_ONE (regardless of what policy is actually set in the bilinear form).
Following #181, you can EliminateEssentialBC in the local matrix, and then call ParallelAssemble, but this only works for the policy DIAG_KEEP. I have not found a way to correctly assemble the parallel matrix using the DIAG_ZERO policy.
Below is an example demonstrating this issue. The hard coded policy and serial_elimination variables let you choose six possibilities, and it prints the diagonal.
int main(int argc, char * argv[])
{
   const Matrix::DiagonalPolicy policy = Matrix::DIAG_ZERO;
   const bool serial_elimination = true;

   MPI_Init(&argc, &argv);
   int rank, size;
   MPI_Comm_rank(MPI_COMM_WORLD, &rank);
   MPI_Comm_size(MPI_COMM_WORLD, &size);

   Mesh * mesh = new Mesh(2, 2, Element::QUADRILATERAL, 1);
   ParMesh pmesh(MPI_COMM_WORLD, *mesh);
   delete mesh;

   H1_FECollection fec(1, 2);
   ParFiniteElementSpace fespace_lor(&pmesh, &fec);
   ParBilinearForm a_form(&fespace_lor);

   Array<int> ess_bdr(pmesh.bdr_attributes.Max());
   ess_bdr = 1;
   Array<int> ess_tdof_list;
   fespace_lor.GetEssentialTrueDofs(ess_bdr, ess_tdof_list);

   a_form.SetDiagonalPolicy(policy);
   a_form.AddDomainIntegrator(new DiffusionIntegrator);

   HypreParMatrix * hmatrix;
   a_form.Assemble();
   if (serial_elimination)
   {
      a_form.EliminateVDofs(ess_tdof_list, policy);
      a_form.Finalize();
      hmatrix = a_form.ParallelAssemble();
   }
   else
   {
      Vector X, B;
      Vector x(fespace_lor.GetVSize()), b(fespace_lor.GetVSize());
      hmatrix = new HypreParMatrix;
      a_form.FormLinearSystem(ess_tdof_list, x, b, *hmatrix, X, B);
   }

   Vector diag;
   hmatrix->GetDiag(diag);
   for (int i = 0; i < size; ++i)
   {
      if (rank == i)
         diag.Print(std::cout, 1);
      MPI_Barrier(MPI_COMM_WORLD);
   }
   delete hmatrix;
   MPI_Finalize();
}

Example output (serial output is correct, parallel output is not):
[barker29@tux411:element-tensor(master)]$ ./issue
0
0
0
0
2.66667
0
0
0
0
[barker29@tux411:element-tensor(master)]$ mpirun -np 2 ./issue
2
0.666667
2.66667
0.666667
0
0
0
0
0
0
[barker29@tux411:element-tensor(master)]$

@barker29, the issue is that a_form.EliminateVDofs() expects a list of "vdofs" and you are giving it a list of "tdofs". Essentially, on more than 1 processors, ess_tdof_list should only be used when serial_elimination == false.
Anyway, the simplest solution is to call a_form.EliminateEssentialBC(ess_bdr, policy) instead of a_form.EliminateVDofs(ess_tdof_list, policy).

Thanks @v-dobrev , that fixes it.