Skip to content
This repository was archived by the owner on May 29, 2019. It is now read-only.

feat: extract && emit a CSS file per split point (import()/require.ensure) #455

Closed
osdevisnot opened this issue Mar 7, 2017 · 27 comments
Closed

Comments

@osdevisnot
Copy link

While trying to implement page optimizations in our app, we want to have ability to generate separate CSS files per webpack chunk to improve page rendering performance for first page in our application. To achieve that, we have been trying to use extract-text-plugin in combination with require.ensure like so:

const $ = require('load-webpack-plugins')();

module.exports = {
  entry: { 'app': './src/app.js' },
  output: {
    path: 'dist',
    filename: '[name].js'
  },
  devtool: 'source-map',
  module: {
    loaders: [
      { test: /\.js$/, use: [{ loader: 'babel-loader' }] },
      {
        test: /\.css$/,
        use: $.ExtractTextPlugin.extract({
          fallback: 'style-loader',
          use: [{ loader: 'css-loader' }]
        })
      }
    ]
  },
  plugins: [
    new $.ExtractTextPlugin({ filename: '[name].[contenthash].css', allChunks: true }),
    new $.NamedModulesPlugin(),
  ]
}

with app.js being:

console.log('this is app.js');

require.ensure([], require => {
  require('./a.css');
}, 'base');

require.ensure([], require => {
  require('./b.css');
}, 'first');

require.ensure([], require => {
  require('./c.css');
}, 'second');

and a.css being:

.a {
  color: red;
}

and b.css being:

.b {
  color: red;
}

and c.ss being:

.c {
  color: red
}

Problem is we are getting only one CSS file dist/app.e2e9da337e9ab8b0ca431717de3bea22.css with content of all the 3 chunks:

.a {
  color: red;
}

.b {
  color: red;
}

.c {
  color: red
}


/*# sourceMappingURL=app.e2e9da337e9ab8b0ca431717de3bea22.css.map*/

how do we go about extracting one css file per webpack chunk (require.ensure) in this case? should this even be supported by extract-text-plugin.

PS: Here is an example repository demonstrating this issue -- https://github.com/osdevisnot/extract-text-demo

@osdevisnot
Copy link
Author

osdevisnot commented Mar 8, 2017

cross posted on stackoverflow

@jeffrey-l-turner
Copy link

jeffrey-l-turner commented Mar 28, 2017

Have you tried?:

    const ExtractTextPlugin = require('extract-text-webpack-plugin');
    const Extract_A_CSS = new ExtractTextPlugin({filename: './a.css', allChunks: true,});
    const Extract_B_CSS = new ExtractTextPlugin({filename: './b.css', allChunks: true,});
    const Extract_C_CSS = new ExtractTextPlugin({filename: './c.ss', allChunks: true,});

...
then:

     {
         test: /a\.css$/,
         use: Extract_A_CSS.extract({
             //fallback: 'style-loader', // fallback will not be used if allChunks: true
             use: ['css-loader',],
         }),
     },
     {... // etc.

I'm using this approach with an import 'file.css'; to get all of the dependencies resolved and then using standard script/link tags in the html to get all of the css generated.

I'm not quite sure how that plays with a require.ensure using commonJS...

@faceyspacey
Copy link

faceyspacey commented Mar 29, 2017

This is a very important feature with code splitting becoming easier and easier. You need to be able to get the css from code split chunks, not just specified entry chunks.

Basically there needs to be a new option like splitChunks: true or something like this, or even being able to provide fallback: 'extract-text' lol. Perhaps that could be an easy way for a 3rd party to implement this.

Ultimately we just need to dig in their code and provide the PR. This is a must-have now.

@osdevisnot
Copy link
Author

@jeffrey-l-turner we already do the new plugin instance for known entry chunks. But it's hard to know dynamic chunks in advance in every app. Also, it would require changing build configuration per app. It would be great if we consider this as an option in plugin configuration for this plugin like @faceyspacey mentioned.

@faceyspacey
Copy link

Id be interested in producing the PR. Any help to point me in the right direction would be appreciated.

Are you saying that the option might require we specify an array of dynamic chunk names. I hope that's not the case. The stats contains the names of all chunks in the stat object for each module. It maintains an array of chunks it corresponds to. We should be able to get the css statically.

@osdevisnot
Copy link
Author

You are right @faceyspacey about figuring out chunk names from stats. The intention here is to have one build configuration for multiple apps that figures out the chunks and extracts the files based on dynamic require.ensure chunks.

@michael-ciniawsky michael-ciniawsky changed the title extracting one css file per webpack chunk (require.ensure) extract && emit a CSS file per split point (import()/require.ensure) Apr 22, 2017
@michael-ciniawsky michael-ciniawsky changed the title extract && emit a CSS file per split point (import()/require.ensure) feat: extract && emit a CSS file per split point (import()/require.ensure) Apr 22, 2017
@aseem2625
Copy link

Hey @osdevisnot my use case is the same. However, not able to find any solution uptil now. Were you able to resolve the issue?
My all dependencies are up to date.

@aseem2625
Copy link

@faceyspacey
As of now, I'm trying extract-css-chunks-webpack-plugin. Will check out magic comment feature as well

Thanks 😄

@faceyspacey
Copy link

@aseem2625 here's a private/unpublished launch article im releasing in a few days:

https://medium.com/@faceyspacey/extract-css-chunks-webpack-plugin-chunkify-your-css-a-few-things-you-need-45cb474cc332

it should also help you.

feedback on anything u find confusing or that i could write better is much appreciated.

@aseem2625
Copy link

@faceyspacey Thanks.. I'm implementing these stuffs right now only. So, I'll have a look and get back with feedbacks. 😄

@aseem2625
Copy link

aseem2625 commented Jun 17, 2017

@faceyspacey Also, i just wanted to point out that using

options: {
	modules: true,
	localIdentName: '[name]__[local]--[hash:base64:5]'
}

throws the following error.

Invalid configuration object. Webpack has been initialised using a configuration object that does not match the API schema.
 - configuration.module.rules[2].loader should be one of these:
   non-empty string | non-empty string | function | object { loader?, options?, query? } | function | [non-empty string | function | object { loader?, options?, query? }]
   Details:
    * configuration.module.rules[2].loader should be a string.
    * configuration.module.rules[2].loader should be a string.
    * configuration.module.rules[2].loader should be an instance of function.
    * configuration.module.rules[2].loader should be an object.
    * configuration.module.rules[2].loader should be one of these:
      non-empty string | function | object { loader?, options?, query? }
    * configuration.module.rules[2].loader should be an instance of function.
    * configuration.module.rules[2].loader[1] should be a string.
    * configuration.module.rules[2].loader[1] should be an instance of function.
    * configuration.module.rules[2].loader[1] has an unknown property 'use'. These properties are valid:
      object { loader?, options?, query? }
    * configuration.module.rules[2].loader[1] should be one of these:
      non-empty string | function | object { loader?, options?, query? }
    * configuration.module.rules[2].loader should be one of these:
      non-empty string | function | object { loader?, options?, query? } | function | [non-empty string | function | object { loader?, options?, query? }]

Not using, options works fine. However, why would it output no_css.js ? I'm using this plugin only to have chunks with no css, so why not just have 1 .js for each chunk in /dist folder!
Otherwise, it's a tedious job to now write a functionality to download no_css.js for the requested chunk instead of .js

@faceyspacey
Copy link

that shouldnt be the case. ...my recommendation is to try one of the boilerplates here:

https://github.com/faceyspacey/webpack-flush-chunks#boilerplates

that best matches your setup. get it working, get comfortable with it. then copy/paste the code.

im signing off for the day. post any issues on the ECCWP Issue tracker if it's a real issue. there is likely a minor mistake in how ur structuring the config options.

@aseem2625
Copy link

aseem2625 commented Jun 17, 2017

Hi @faceyspacey
I'm current working on SPA boilerplate with best practices with minimal footprint (also not using react but infernojs). I saw your boilerplates which you suggested and as your article suggests that if my application is SSR then pages rendered from server must have separate css file so that browser capability of loading in parallel can be utilised while loading js with css injected while navigating and requesting modules async. This strategy is absolutely right for SSR and I've seen nice websites doing like this. (Eg- www.housing.com if you've come across)

However, in my case, it's SPA and it's having chunks for each route using require.ensure but currently having corresponding css injected in their css and common js as bundle.[some_chunkhash].css But I just want corresponding .js file without css injected and it's individual css. While the plugin you created makes my /dist having 3 files for each chunk (2 js and 1 css). Is there a way to avoid that?

Also, I feel that boilerplates should be renamed to the following

Universal Webpack Boilerplate [Same]
Universal Webpack Boilerplate (using chunkNames + magic comments) [Same]
Universal Webpack Boilerplate (Babel way)
Universal Webpack Boilerplate (Babel way using chunkNames + magic comments)

I would be using your solution when I would be moving my boilerplate to SSR, later this month.

😄

@faceyspacey
Copy link

...cool i'll update the github caption at the top to those.

...we're adding more configuration options in the next couple of days to toggle off those things which you don't seem to need:

faceyspacey/extract-css-chunks-webpack-plugin#14

Maybe that will solve your problem. Please comment on that issue if there is anything we're missing.

@faceyspacey
Copy link

add those 2 additional loaders to a boilerplate and see if it works. then we'll know :)

...u dont have to specify a fallback.

@osdevisnot
Copy link
Author

osdevisnot commented Jul 13, 2017

@faceyspacey just wondering, is extract-css-chunks a go to plugin for this use case?

@aseem2625
Copy link

@osdevisnot Not answering your ques. But check this article by @faceyspacey

@faceyspacey
Copy link

It's been a while and I'm on my phone, but based on the Issue title, yes. "Emit CSS file per split point." Yes indeed.

@aseem2625
Copy link

aseem2625 commented Jul 13, 2017

@osdevisnot To answer your question, this plugin generates 3 files for that given js chunk..
1) your_js.js (without css)
2) your_css.css( to be used with above js file)
3) your_js.js (with css in js)
What @faceyspacey wanted to suggest with this plugin is that for SSR you should be ideally using your_js.js and your_css.css bcoz in order to fasten up the retrieval of code. But on client side navigation, your_js.js(css in js)` is better bcoz u have less files to fetch this time and splitting css and js doesn't make sense.

@faceyspacey I hope I am correct.

I'll give try to this plugin and v2 with my SSR repo and see what's the best thing.

@osdevisnot
Copy link
Author

@faceyspacey @aseem2625 @jeffrey-l-turner At this point, I am successfully able to extract dynamic CSS chunks using babel-plugin-dual-import + import(). However, I am having hard time figuring out how to load these chunks in browser on app bootstrap. Did you guys end up resolving this using a method/approach?

The latest demo of my work is here: https://github.com/osdevisnot/extract-text-demo

@aseem2625
Copy link

@osdevisnot
You're saying that your babel-plugin-dual-import converts your `import 'some.scss' into separate 'some.css' at build time for every dynamic chunk?

I'm not sure how to do for SPA. But for SSR, you can created a *.js file where mapping and file name is there. And depending upon requested chunk, you can append that some.css in head. I can give more details if you want.

@osdevisnot
Copy link
Author

@aseem2625, sorry I meant $.ExtractCssChunksPlugin() gets a separate file for dynamically imported chunk.

And unfortunately, I need to solve this for SPA.

Just to add more context, only issue we have now is downloading the css chunk and injecting it into head for SPA.

@faceyspacey
Copy link

faceyspacey commented Oct 18, 2017

@osdevisnot you are right, these tools were developed with SSR in mind. However good news: several of our users have developed SPA webpack plugins for your use case. Essentially you need to embed the cssHash into the page, containing what stylesheets are available. Search the repos and you will find it. I'll paste it when I'm back on my computer.

@faceyspacey
Copy link

@osdevisnot here's one such solution:

https://github.com/mario-jerkovic/flush-css-chunks-webpack-plugin

there are 2 others i believe, but i cant find them right now.

@michael-ciniawsky
Copy link
Member

ETWP entered maintenance mode and won't receive any feature updates, please open an issue in mini-css-extract-plugin instead

The mini-css-extract-plugin emits a chunk per split point 🎉

@monochrome-yeh
Copy link

Hello guys, I have the same requirement
and there's the best solution I found,
#120 (comment)

We just need to set file-loader for it

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

6 participants