Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Full static extraction to CSS file while keeping all dynamic parts intact. #579

Open
17 of 26 tasks
kof opened this issue Sep 7, 2017 · 12 comments
Open
17 of 26 tasks
Labels
complexity:high Best brains need to talk about it. feature request This will safe many lifes! important The thing you do when you wake up! perf

Comments

@kof
Copy link
Member

kof commented Sep 7, 2017

The idea is to optimize it in a way that shifts preprocessing runtime overhead to the build stage, while keeping all dynamic parts working. It includes 2 stages: one is babel plugin, another is webpack plugin.

With full extraction,:

  • There will be no static styles at runtime that need to be parsed and injected (only dynamic)
  • No double loading of static styles in JS build + CSS build.

With babel plugin only there will be no runtime processing of static styles, only dynamic. Current state is that jss core without plugins with styles object vs preprocessed version of the same object results in 50% performance boost.

Exmple

// source js
const styles = {
  static: {
    color: 'green'
  },
  mixed: {
    color: 'red',
    margin: (props) => props.spacing
  }
}

createStyleSheet(styles).attach()

// generated js
const styles = {
  '@raw': `
    .static-0-0-1 {
      color: green; 
    }
    .mixed-0-0-2 {
      color: red;
    }
  `,
  mixed: {
    margin: (props) => props.spacing
  }
}

const {classes} = createStyleSheet(styles, {
  classes: {
    static: 'static-0-0-1', 
    mixed: 'mixed-0-0-2'
  }
}).attach()

Todo babel plugin

  • Identify injectSheet(styles, options), createStyleSheet(styles, options) calls, make it customizable for different function names.
  • allow to pass jss config to the babel plugin
  • extract static styles object literal
  • extract static styles from the reference
  • extract any value from reference
  • extract sheet options if provided
  • extract sheet options from reference
  • extract sheet options nested properties from reference
  • create jss with plugins => createStyleSheet(styles, options).toString()
  • insert static css as a @raw rule into the styles declaration
  • remove original static styles
  • pass classes map from static sheet as options
  • math expressions
  • function call results
  • css preprocessing pipeline (with postcss)
  • autoprefixer
  • babel like theming configuration over file system in any directory????

Todo core

  • implement option classes to createStyleSheet
  • implement@raw plugin, add it to default preset
  • make a bench comparing @raw with equivalent style objects
  • docs
  • blogpost?

Todo webpack plugin

  • identify @raw rule
  • extract css
  • remove the rule
  • provide the css to webpack so that other loaders can use it (tbd how)

Future enhancements

  • Think of potential solution to the problem: {padding: (props) => props.spacing, paddingLeft: 10} after compilation paddingLeft will be overwritten by padding since it will have higher source order specificity
  • If static CSS is used without critical CSS over SSR, dynamic styles are not part of the static bundle and styling is incomplete. Think of a strategy to warn/require default values for dynamic styles.
  • a demo app with webpack/postcss/css-modules/autoprefixer
  • When there are no dynamic styles, remove all jss runtime code from the module
  • When function values/rules are not using props and can be statically extracted (see linaria)
  • Separate entry point jss/static for a reduced version of jss which does not include any plugins etc logic, since it is all preprocessed (unless we can treeshake it???)
  • Remove unused styles or warn when any detected, see this and the article for e.g.

Some inspiration can be taken from

https://github.com/4Catalyzer/css-literal-loader
https://github.com/callstack-io/linaria
https://www.npmjs.com/package/extract-jss-webpack-plugin

@kof kof added complexity:high Best brains need to talk about it. feature request This will safe many lifes! help wanted idea What if we turn JSS into styled-components? labels Sep 7, 2017
@StephanBijzitter
Copy link

Perhaps inspiration can be gotten from babel-plugin-css-in-js.

The only significant downside with this plugin is that usage with webpack-dev-server (and hot module reloading enabled) does reload the page, as compilation is done in two steps.

  1. user edits styles in a js file
  2. webpack detects changes to .js and hard-reloads page (new .css is now created)
  3. webpack detects changes to .css and hot-reloads page

It's definitely a good improvement, but it can also be made better.

@kof
Copy link
Member Author

kof commented Apr 2, 2018

Additional thoughts: if we are not allowed to use any dynamic capabilities like function values etc which can access render time variables, why not put js styles in separate files, name them something.styles.js and just write a small script to extract them into css files, it is a few lines of code and not worth creating a package/library for it.

You would need to

  1. require the styles script
  2. use jss.createStyleSheet()
  3. call sheet.toString() => css file

Update:

  • This would make for bad DX, since user would have to manually separate static and dynamic styles
  • You would be double loading static styles: once with css bundle, once with js bundle

@StephanBijzitter
Copy link

As far as I understand, having the static styles in a different file would essentially be the same as any SCSS/SASS file, except in JavaScript. The main benefit being that you don't need to set up any loaders and linters for those.

It's still a very nice improvement, but for me the first reason to use JSS is to eliminate unused styles from being present in whatever is served to the user. Dead code elimination simply doesn't work with CSS (unless I missed something) and with JavaScript it's already present (provided you don't mess it up yourself). Do you think that would still work with separate JSS style files?

@kof
Copy link
Member Author

kof commented Apr 2, 2018 via email

@kof kof changed the title Static CSS files extraction Full static extraction to CSS file while keeping all dynamic parts intact. Jul 1, 2018
@kof
Copy link
Member Author

kof commented Jul 1, 2018

Update the original description, since I see that we can actually keep both static extraction and dynamic styles accessing the runtime.

@satya164
Copy link

satya164 commented Jul 1, 2018

Think of potential solution to the problem: {padding: (props) => props.spacing, paddingLeft: 10} after compilation paddingLeft will be overwritten by padding since it will have higher source order specificity

I think there are 2 ways to handle this:

  1. The plugin can completely ignore rule-set which cannot be statically evaluated
  2. The plugin can keep a list of what's safe to re-order and what is not, and then extract the subset of the rules statically which is safe to re-order. I think CSS minifiers already do this.

I think the first solution is the most logical solution for initial implementation and eventually, the plugin can get smarter about it.

@kof
Copy link
Member Author

kof commented Jul 1, 2018

@satya164 I was thinking to not evaluate function values at all and always keep them dynamic and warn the user if his ordering is going to be broken, based on prop names and dynamic values.

@satya164
Copy link

satya164 commented Jul 1, 2018

Yeah, that's mostly what I meant by the first one :)
But no reason to get smarter in future and handle it as well :)

@kof kof added important The thing you do when you wake up! and removed idea What if we turn JSS into styled-components? help wanted labels Jul 5, 2018
@cssinjs cssinjs deleted a comment from mehrdad-shokri Jul 11, 2018
@kof
Copy link
Member Author

kof commented Jul 12, 2018

Updated again, I figured out with help from @giuseppeg that

  1. babel plugins should not emit css files, its an ugly side effect
  2. we can make babel plugin very useful by its own by preprocessing static styles and keeping them in js in the first step, even without static extraction, rendering will be much faster
  3. @raw rule will be handled by a new plugin, which will basically just take the string as it is and render it, this should be 0 processing overhead

@marmikdesai
Copy link

Hi,
Is there any timeline so we can use this in production?

@Mati365
Copy link

Mati365 commented Aug 21, 2019

@kof Isn't extracting styles during webpack compilation too overcomplicated? Maybe extracting static styles during server startup to one big sheet and adding information about 'critical' classnames to components uuids will be better? I think that creating hash with components class names improve hydration performance - client won't have to manually remove SSR stylesheet after hydration

Consider this:

const {css} = createSheet();
const {classes} = css(
  {
    base: {
      display: 'initial',
      background: 'red',
    },

    green: {
      background: 'green',
    },
  },
);

const Component = () => <div className={classes.base} />;
  
const CSS = await sheet.compileCSS();
// which compiles to something like it:
{
  __styles: `
    .c0 {
      display: initial;
      background: red;
    }
    .c1 {
      background: green;
    }
  `,

  ...
  [componentUUID]: {
    base: 'c0',
    green: 'c1',
  } 
  ...
}

runServer(CSS); 

@kof kof added the perf label Aug 22, 2021
@Jony-Y
Copy link

Jony-Y commented Jun 21, 2022

Any update on this?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
complexity:high Best brains need to talk about it. feature request This will safe many lifes! important The thing you do when you wake up! perf
Projects
None yet
Development

No branches or pull requests

6 participants