krasimir / cssx

CSS in JavaScript
http://krasimir.github.io/cssx/
MIT License
1.01k stars 48 forks source link

cssx syntax compiled to jss json dsl #2

Closed kof closed 8 years ago

kof commented 8 years ago

I am wondering if you could imagine to compile cssx syntax to json dsl of jss and then use jss lib for rendering at runtime.

krasimir commented 8 years ago

Interesting. Give me few days and will I'll see how it goes.

kof commented 8 years ago

I think the negative feedback I have got so far to jss was from people who are not ready to write CSS in JSON. They want CSS syntax. This is also the reason for popularity of css-modules. At the same time the amount of advantages of runtime-rendered CSS is so huge, I don't even want to go ever back to the server rendered CSS, exception are some optimizations for e.g. bootstrapping application.

I have tried to put it on the list here https://github.com/jsstyles/jss/blob/master/docs/benefits.md and also did this presentation recently https://slides.com/kof/javascript-style-sheets/

ai commented 8 years ago

With JSS we will have many already written plugins. So I really want CSSX + JSS solution :D.

tunnckoCore commented 8 years ago

I also will follow this one, because I'm working on https://github.com/postjson/postjson idea, which also could be useful and interesting in some way. :)

kof commented 8 years ago

@tunnckoCore postjson is very interesting, sounds like I could use postjson in jss as a lower level lib.

tunnckoCore commented 8 years ago

@kof thanks! Yea, maybe. I'm still thinking what would be the best approach and API for the plugins - there are two ways for the moment haha. I'm open for ideas (feel free to open issues there) and would be happy to hear what you need.

krasimir commented 8 years ago

@kof @ai @tunnckoCore Yesterday I did an update of CSSX allowing the generation of plain JavaScript literal. I.e. it's not CSSX lib specific. Here is an example:

var styles = function () {
 return <style>
   .container {
     width: 100%;
   }
   @media (max-width: 600px) {
     .container {
       width: 50%;
     }
   }
 </style>;
}

It is transpiled to:

var styles = function () {
  return (function () {
    var _4 = {};
    _4['width'] = '50%';
    var _3 = {};
    _3['width'] = '100%';
    var _2 = [];

    _2.push(['.container', _3]);

    var _5 = {},
        _6 = [];
    _5['@media (max-width: 600px)'] = _6;

    _6.push(['.container', _4]);

    _2.push(_5);

    return _2;
  }.apply(this));
};

Which is the following JSON:

[
  [
    ".container",
    {
      "width": "100%"
    }
  ],
  {
    "@media (max-width: 600px)": [
      [
        ".container",
        {
          "width": "50%"
        }
      ]
    ]
  }

So it is definitely possible writing a .toJSON method. @kof can you please point me to a place where I could see the jss dsl specification (or at least an example containing all the different types of nodes).

kof commented 8 years ago

Let me know if it good enough json-dsl

kof commented 8 years ago

I saw so much similar code in the cssx client to what jss and it's plugins do, it really makes sense to use jss for client side rendering and combine our efforts in making it better.

trusktr commented 8 years ago

I like JSS's Object-literal syntax better. Also, why not just compile to the literal form rather than to the pieces and putting them together? Suppose for example we have this:

var stylesheet = <style>
   .container {
     width: 100%;
   }
   @media (max-width: 600px) {
     .container {
       width: 50%;
     }
   }
</style>

stylesheet.attach() // <-- better leaving it up to the user to decide when to attach/detach

then it'd compile to this:

let stylesheet = jss.createStyleSheet({
    '.container': {
        width: '100%'
    },
    '@media (max-width: 600px)': {
        '.container': {
            width: '50%'
        }
    }
})

stylesheet.attach() // <-- better leaving it up to the user to decide when to attach/detach

Maybe you guys can agree on a common object-literal format, then end users can choose which runtime they prefer, as either would be compatible with the compiled output. In that case, then we'd want to compile to this:

let styles = {
    '.container': {
        width: '100%'
    },
    '@media (max-width: 600px)': {
        '.container': {
            width: '50%'
        }
    }
}

and we'd let the user choose which runtime to use:

let stylesheet = cssx('my-styles', styles)
// or
let stylesheet = jss.createStyleSheet(styles)
trusktr commented 8 years ago

Example where we take advantage of JSS' collision-free class names:

var style = <style>
   container {
     width: 100%;
   }
</style>
jss.createStyleSheet(style)

would compile to

var stylesheet = {
   container: {
     width: '100%'
   }
}
jss.createStyleSheet(style)

which generates at runtime:

<style>
  .container-jss-0 {
    width: 100%;
  }
</style>
krasimir commented 8 years ago

@trusktr That's exactly what I'm working on right now. I'm changing the transpiler of CSSX to output non-CSSX specific code.

krasimir commented 8 years ago

@kof @ai @tunnckoCore @trusktr

Finally I managed to release a jss compatible version - 5.0.0. I bumped the major version twice because I had to make changes everywhere :)

var styles = <style>
  button {
    font-size: 12;
    &:hover {
      background: blue;
    }
  }
  ctaButton {
    extend: button;
    &:hover {
      background: red;
    }
  }
  @media (min-width: 1024px) {
    button {
      minWidth: 200;
    }
  }
</style>;

is transpiled to:

var styles = (function () {
  var _1 = {},
      _2 = {},
      _3 = {},
      _4 = {},
      _5 = {},
      _6 = {},
      _7 = {};
  _2['background'] = 'blue';
  _3['&:hover'] = _2;
  _3['font-size'] = '12';
  _4['background'] = 'red';
  _5['&:hover'] = _4;
  _5['extend'] = 'button';
  _6['minWidth'] = '200';
  _7['button'] = _6;
  _1['button'] = _3;
  _1['ctaButton'] = _5;
  _1['@media (min-width: 1024px)'] = _7;
  return _1;
}.apply(this));

which if we execute outputs:

{
  "button": {
    "&:hover": {
      "background": "blue"
    },
    "font-size": "12"
  },
  "ctaButton": {
    "&:hover": {
      "background": "red"
    },
    "extend": "button"
  },
  "@media (min-width: 1024px)": {
    "button": {
      "minWidth": "200"
    }
  }
}

There is a working example that uses jss here (the source code is here).

I'll be happy if someone tests a more complex cases so we see if it works with production-ish ready code.

kof commented 8 years ago

Nice, will try it out soon. Do you think it makes for cssx to focus on cssx as a language and continue providing all transpiling tools, but remove the cssx client and fully rely on jss?

Also I think it would be nice to add cssx as an option on the jss repl http://jsstyles.github.io/repl/ Thinking of a select box which allows to switch between them.

kof commented 8 years ago

Regarding json output from cssx: it would be nice to get number values from those which have no unit. This allows jss-default-unit plugin to set default unit automatically.

krasimir commented 8 years ago

Regarding json output from cssx: it would be nice to get number values from those which have no unit. This allows jss-default-unit plugin to set default unit automatically.

I just filed an issue about that https://github.com/krasimir/cssx/issues/6

Do you think it makes for cssx to focus on cssx as a language and continue providing all transpiling tools, but remove the cssx client and fully rely on jss?

I already started thinking in this direction. I removed 3 features of the CSSX client-side library and it is really simple now. I'm not planning to extend it soon. Just updated the docs so cssx lib is more like an alternative of jss.

kof commented 8 years ago

If you need something for cssx jss doesn't allow to do, just create an issue on jss repository.

tunnckoCore commented 8 years ago

Hey! This going very interesting! Soon is my turn to review the jss core more deeply (@kof help will be appreciated), meaning what it does actually, what are the processes/steps. And you can help with PostJSON - it can be third direction.

Btw I'm going deeper and deeper, today pushed https://github.com/limonjs/limon (pluggable lexer) and going to push "pluggable" parser. When finish them both, I'll start playing with few things - json lexer, parser and stringifier; semver lexer, parser and stringifier; and porting PostCSS's tokenizer (because it is awesome).

@krasimir, междудругото парсъра мисля да го кръстя лиман хахаха - лимон и лиман. За жалост лагуна е заето, lemon също, та затова лимон и лиман - само това, че разликата е само в една буква ме притеснява и дразни. Чудя се. Anyway.

kof commented 8 years ago

@tunnckoCore interesting things yes, ping me on gitter if you have any questions, I still didn't have time to think how we could extract the part of jss which is about the ast and it's manipulations by plugins into something generic like postjson. But I think it's definitely possible, it's just a load of work.

tunnckoCore commented 8 years ago

Yea, done. :)

trusktr commented 8 years ago

@krasimir Idea: maybe let the user choose the runtime:

// User chooses CSSX:

let style = <style runtime="cssx">
  .foo {
    height: 100%;
  }
</style>

// or, maybe instead of <style>, template tag syntax:
let style = cssx`
  .foo {
    height: 100%;
  }
// User chooses CSSX:

let style = <style runtime="jss" named="false">
  .foo {
    height: 100%;
  }
</style>

let style2 = <style runtime="jss">
  foo {
    height: 100%;
  }
</style>

// or, maybe instead of <style>, template tag syntax:
let style = jss`
  foo {
    height: 100%;
  }
`

But I'm not sure how to enable named:false in the template tag form of the jss example, so maybe the <style> tag is more useful.

Also, what happens if a user places a <style> tag in a React component, which is bound to happen if CSSX gained wide adoption:

render() {
  return <div>
    <h1>My React App</h1>
    <style runtime="jss">
      .foo {
        height: 100%;
      }
    </style>
  </div>
}

Maybe a compile-timeerror would be thrown in that case, with an explanation pointing to some docs on how to use CSSX?

Or maybe you can somehow scope that style to the element where the <style> tag is located? Maybe it'd require matching rules. F.e.:

render() {
  return <div>
    <h1 className="foo">My React App</h1>
    <style runtime="jss">
      .foo {
        color: red;
      }
    </style>
  </div>
}

That's just an outer-level idea. I'm not sure what JS that'd actually translate to. Maybe it'd remove the stylesheet entirely from the JSX, before JSX handles it, so it'd look like the following before JSX transforms the code:

var _cssx_classes1 = (function () {
  ...
}.apply(this));

render() {
  return <div>
    <h1 classname="{_cssx_classes1.foo}">my react app</h1>
  </div>
}
krasimir commented 8 years ago

@trusktr thank you for commenting here. Here is my feedback:

Regarding the runtime attribute

CSSX is translated to JavaScript similarly to JSX. The difference is that CSSX result is not doing anything. It's not using any library. It's just a creation of object literal. What happen with this next depends on the developer. For example:

let style = <style>
  .foo {
    height: 100%;
  }
</style>

Is transformed to:

let style = (function () {
  var _1 = {},
      _2 = {};
  _2['height'] = '100%';
  _1['.foo'] = _2;
  return _1;
}.apply(this));

which is the same saying:

let style = {
  ".foo": {
    "height": "100%"
  }
}

How the style variable is used is completely up to the developer. So, if we add a runtime attribute we'll make a big assumption which I want to escape from.

Also, what happens if a user places a <style> tag in a React component.

We'll indeed get an error. If we have to provide a message that points the developer to CSSX docs then we have to contribute to JSX transpilation which will assume that by adding <style> you mean CSSX which is not quite right. You may have a React class component which name is style.

Or maybe you can somehow scope that style to the element where the <style> tag is located?

There is react-cssx project where we have a CSSX component and we do have scoping of CSSX styles. However, I'm thinking that the project here should be really about transpiling. Even though we have a consumer of the transpiled code will be nice to use jss or css-modules instead because they did a good progress and have some sort of adoption already.

Maybe it'd remove the stylesheet entirely from the JSX, before JSX handles ...

This requires changes in how JSX is transpiled which is part of babylon project. If I have to submit a PR with this I'm 99% sure that it will be rejected because (a) it's a custom thing and (b) will decrease the performance of the transpiler.

trusktr commented 8 years ago

@krasimir

It's just a creation of object literal. What happen with this next depends on the developer.

Maybe "format" is better than "runtime". So that way a developer can choose between CSSX or JSS object formats, depending on what the developer prefers, then do as needed with the object.

About the JSX stuff, for sure. Just throwing some ideas out there. I myself am fine just having the object transpiled from the CSS syntax, f.e.

jss.createStyleSheet(<style format="jss">
  ...
</style>)
krasimir commented 8 years ago

So that way a developer can choose between CSSX or JSS

I adopted the JSS format. So now CSSX and JSS use same thing. (I mean in the released version 5).

trusktr commented 8 years ago

adopted the JSS format

Oh okay, gotcha. That works in my case since I'm using JSS. :]

Can't wait to try this soon!

krasimir commented 8 years ago

Great. You may be interested in this example.

trusktr commented 8 years ago

Just got to try this for the first time finally (cssx-loader + jss). So awesome!