Skip to content
This repository was archived by the owner on Oct 9, 2020. It is now read-only.

A modular CSS proposal #30

Closed
guybedford opened this issue Apr 22, 2015 · 19 comments
Closed

A modular CSS proposal #30

guybedford opened this issue Apr 22, 2015 · 19 comments

Comments

@guybedford
Copy link
Member

An idea..

component-style.mcss (modular css)

/* $ allows us to define "CSS class name placeholders" */
${base} {
  font-size: 16px;
  color: #444;
}
${base} p {
  font-size: 12px;
}

component.js

import {base} from './component-style.mcss!';

// etc for whatever framework we're in
export function render(opts) {
  return `
    <div class="${base}">
      <p>stuff</p>
    </div>
  `;
}

That is, the className of base is a dynamic name that is shared as an export of the CSS module, which itself then generates the CSS class names for us.

Say in development, the className could be generated as component-style-base-0, with uniqueness managed, and then in production it could be reduced to z2d on build / bundle.

In this way we get around the biggest modularity hurdle in CSS - the global namespacing of classes.

The syntax may need revising, but it would be great to see something like this if anyone is keen on working on it please let me know!

The other cool thing is it could be done in a way that is backwards compatible with the CSS plugin here, in which case we could ship it in this default CSS plugin with jspm.

@guybedford
Copy link
Member Author

@geelen while we're on the topic of CSS, I'd be really interested to hear what you think of this.

@geelen
Copy link

geelen commented Apr 25, 2015

Very interesting! This doesn't seem particularly difficult to be honest. I really like the syntax it gives you for including the CSS into the file. Personally, I'd borrow something from web components, the :host selector in place of the ${base} one, but that would only let you export one class. That works for me, though, since I usually only define a single class then use the direct descendent selector > to target each tag within the object.

So, if this worked like my postcss plugin, then each file could simply map :host to .component-style-base-X and keep track of all the classes currently being used. Then instead of returning a noop from the fetch, it would just return a JS snippet that exports all the variables. Actually, this would be pretty damn simple to build on top of plugin-postcss, but because the export behaviour is different, it wouldn't necessarily be something we could add to plugin-postcss.

But the <link> injecting and live-reloading from plugin-postcss could definitely be shared. I like this a lot :)

@guybedford
Copy link
Member Author

Thanks so much for the feedback. The idea was pretty much entirely from https://medium.com/@jviereck/modularise-css-the-react-way-1e817b317b04.

One useful thing of allowing many names is that you don't run the risk of a selector like:

:host p .class {} 

Spilling over a child boundary and into a child component, as all the class names that might cause issues get to be unique. > is perfect for this, but users will make mistakes with it.

So I do think many names is a useful feature. Suggestions for better syntax though are very much appreciated. :${customName} perhaps?

Yes - it's very simple and can just be an output of the instantiate hook. If we could converge on a single CSS plugin, I'd love to try and have something like this in there.

@guybedford
Copy link
Member Author

We should even try and think of something that we could forseeably imagine becoming a CSS syntax in future. No reason not to shoot for the long shot.

@geelen
Copy link

geelen commented Apr 25, 2015

Css has a custom selector spec, I'll dig out a link when I get home but
it's part of the nextcss pack of processors. Let's use that syntax.
On Sat, 25 Apr 2015 at 2:44 pm Guy Bedford notifications@github.com wrote:

We should even try and think of something that we could forseeably imagine
becoming a CSS syntax in future. No reason not to shoot for the long shot.


Reply to this email directly or view it on GitHub
#30 (comment).

@guybedford
Copy link
Member Author

And what do you know! https://github.com/webpack/css-loader#local-scope

@guybedford
Copy link
Member Author

Independently coming up with the same idea is always a good sign... I'd be more than happy to converge on Webpack's syntax here.

@danieldunderfelt
Copy link

While both this and Webpacks efforts for local CSS are commendable, I just can't get over the special CSS syntax. Why do you need a new way to declare styles? I don't think we need to write CSS is a different way than we do now.

Say we have a style declaration like this:

// style.css
.container .element { ... }

And we import it with a module loader:

// MyModule.jsx
import Styles from '../css/style.css!'

Why can't we simply extract a map of the imported classes from the stylesheet and do this:

// MyModule.jsx

render() {
  return <div style={Styles.container}>
    <section style={Styles.element}></section>
  </div>
}

And then the bundled output would be transformed like in current local CSS proposals:

.ghlköfgh98fghjlk .dfg8dfgh89jkldf { ... }

Or are there some headaches here I've overlooked?

@guybedford
Copy link
Member Author

This would be like building a module system by declaring local variables in scripts to be exports, even though previously they wrote to the global. The biggest argument against would be a loss of full backwards compat with normal CSS.

@danieldunderfelt
Copy link

Hmm, my example was probably crappy... the local CSS efforts are pretty new to me. My main fear is that every library or tool that comes out with a local CSS scheme has it's own way of doing it, like every framework had their own way of creating classes before ES6 came along. Converging on Webpack's syntax is the right thing to do. Hopefully, once local CSS goes big, other libraries will follow and not invent their own.

What I tried to question with my example above was why something like :local(.class) is needed. I assume, as this is something I'd like to see, that each component has its styles and the js, as @geelen's Typeslab project illustrates here: https://github.com/geelen/typeslab/tree/master/src/lib/components, in the same folder.

If that's the way this is going, I see no need for :local() as the act of importing the CSS into a component would encapsulate it and MAKE it local, through whatever means. If you'd include the same CSS in HTML with <link>, the styles would be global.

What I'm trying to say is, I don't want to see style syntax coupled with the tool. Sass gets away with it because Sass is huge. If local CSS tooling follow the same explosive growth as JS frameworks, then we're in for trouble if no consensus is reached.

That said, this conversation shows that CSS plans for System.js is moving in a good direction by following an existing implementation.

@guybedford
Copy link
Member Author

@danieldunderfelt it's completely natural in the evolutionary process to have different systems trying to do the same thing. Trying to standardize too soon can actually be detrimental here, as one loses out on the ecosystem benefits of having properly explored the space.

My opinion is that we should let these syntaxes go wild, then the time will come for standards later.

As a developer what this means is that modular CSS will be a unique system you have to buy into. And you will have to rewrite your modular CSS system moving between systems of when standards finally come. jspm itself will aim to advocate a single system, although it is not yet clear what this will look like.

When you look at the various costs of upgrade paths I really don't think that's too bad at all on the scale of things.

That said if there was a killer standards-compatible modular CSS proposal that we all agree on today and are 100% certain provides what we all want, certainly we should go with it, but in the initial discussions we've had it's been incredibly apparent that there is still a lot of differing opinion in the space that needs to be tested.

@danieldunderfelt
Copy link

Okay, I can totally see the value in a last-syntax-standing evolutionary fight to the death! Is there a place for discussing this where I can listen in, form my own opinions and contribute?

@guybedford
Copy link
Member Author

@geelen's gist at https://gist.github.com/geelen/3255bf7b48abad32c68d has some interesting ideas.

@danieldunderfelt
Copy link

Thanks! That looks sweet.

Also, I just noticed Webpack's started to explore local-by-default styles. Here's an example: https://github.com/markdalgleish/css-modules-example

@geelen
Copy link

geelen commented May 26, 2015

Yeah @markdalgleish and I spent all weekend at CampJS talking about this. Webpack loader now has a module-mode. I'm converting my postcss loader to do exactly the same thing.

Both Webpack and JSPM are in a special position, where we're trying to figure out how to deal with a new piece of functionality: the ability for CSS to export information to JS. Mark mentioned the idea of needing a CommonJS of CSS — a standard way of introducing module-building components into a language that's been designed to only use 1 file & global variables.

css-loader currently has an import syntax for classes now, but I want something lower-level. My current thoughts are here, though they're a little lacking in context unless you've been talking about this all weekend :) https://gist.github.com/geelen/1667acc69a5e9ea65438

@guybedford
Copy link
Member Author

@geelen this is looking great! Awesome to hear you're making progress.

It was really nice to see compatibility with CSS variables, which made me wonder if export and import might work more easily with a special scope declaration. The other thing is that if creating a new entry point into CSS as "module", then we CAN allow the class names to be locally-scoped. So to stir the pot a bit with some random ideas which may or may not make sense:

@import url(‘test-landscape.css’) (orientation:landscape) {
  --variable-name: var(—-export-name);
  .local-class: .export-class;
}
@import url(‘test-portrait.css’) (orientation:portrait) {
  --variable-name: var(—-export-name);
  .local-class: .export-class;
}
.local-class {
  /* local-class only makes sense in a local context - does not reference global CSS at all?? */
}
:export {
  --custom-export-name: blue;
  --local-var: "value";
}

Effectively, the export values in a real spec could be JS symbols and not even string values, so that the idea of there being a mangled name behind it can disappear. Of course this doesn't make sense for templating though.

Anyway, just thought I'd share random thoughts - I'm sure you'll come up with a great system for us to use.

@guybedford
Copy link
Member Author

That said, the above sense of local does make me see the value in :local. What's the current status of that?

@geelen
Copy link

geelen commented May 26, 2015

Ha! I have something quite similar now here: webpack-contrib/css-loader#60

With the idea of 'module mode', it makes sense for all .classNames to be exports. If you want global classes, don't use module mode. And given that, I'm on board with @markdalgleish's idea to use . instead of : to mean "export this".

Getting these stepped wrapped up in postcss transforms is the next step, and hopefully @sokra will move css-loader to use them as well. I feel like we're actually getting closer to the concept of var x = require('y') and module.exports.z = w 😎

@guybedford
Copy link
Member Author

Well this has now happened.... geelen/jspm-loader-css#3 (comment). Plan is to transition that project into becoming the primary css loader here as soon as it can fulfill all the requirements of this one.

Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
None yet
Projects
None yet
Development

No branches or pull requests

3 participants