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

ExtractTextPlugin extracts from asynchronous split point chunks #120

Closed
ghost opened this issue Oct 26, 2015 · 38 comments
Closed

ExtractTextPlugin extracts from asynchronous split point chunks #120

ghost opened this issue Oct 26, 2015 · 38 comments

Comments

@ghost
Copy link

ghost commented Oct 26, 2015

I have a webpack.config.js like:

module.exports = {
  entry: {
    index: ['./app/routes/ClientRouter', './app/pages/Index'],
    create:  ['./app/routes/ClientRouter', './app/pages/Create'],
    play:  ['./app/routes/ClientRouter', './app/pages/Play'],
    vendor: ['react', 'react-dom', 'flux']
  },
  output: {
    path: __dirname + '/public/build',
    publicPath: '/build/',
    filename: '[name].js',
    chunkFileName: '[id].chunk.js'
  },
  resolve: {
    alias: alias
  },
  module: {
    noParse: noParse,
    loaders: [
      { test: /.js$/, loader: 'babel-loader', exclude: /node_modules/ },
      { test: /.css$/, loader: ExtractTextPlugin.extract('style-loader', 'css-loader'), exclude: /node_modules/ }
    ]
  },
  plugins: [
    new webpack.optimize.CommonsChunkPlugin({ name: 'vendor', filename: 'vendor.js', minChunks: Infinity }),
    new ExtractTextPlugin('[name].css', {
      allChunks: false
    })
  ]
};

And a client router like:

function route() {
  var location = window.location.pathname,
      appContainer = document.getElementById('app-container'),
      routeMatches;

  if(!location || location == '/') {
    require.ensure(['../pages/Index'], function() {
      var Page = require('../pages/Index');
      ReactDOM.render(<Page />, appContainer);
    });
  } else if(routeMatches = location.match(/^\/create$/)) {
    require.ensure(['../pages/Create'], function() {
      var Page = require('../pages/Create');
      ReactDOM.render(<Page />, appContainer);
    });
  } else if(routeMatches = location.match(/^\/play\/([01]+)$/)) {
    require.ensure(['../pages/Play'], function() {
      var Page = require('../pages/Play');
      ReactDOM.render(<Page mazeData={routeMatches[1]}/>, appContainer);
    });
  }
}

The ExtractTextPlugin creates index.js/index.css, create.js/create.css, etc, with the extracted CSS for each containing only what is directly required from each of those entry points. It also generates 1.1.js, 2.2.js, and 3.3.js for the split points to load pages asynchronously, but the CSS is removed from them as well. I.e. (from 1.1.js):

/***/ 5:
/***/ function(module, exports) {

    // removed by extract-text-webpack-plugin

/***/ }

This means as you navigate between pages the CSS for the asynchronously-loaded splits do not get added to the page, only their JS. You only have the CSS from the initial entry point.

It seems like 1.1.js, etc should still contain their inline CSS and inject it, like when you aren't using the ExtractTextPlugin. That way you can get the benefits of the extracted entry point CSS for the initial pageload and avoid an FOUC, but after that everything works as a single page app.

I thought the plugin would work that way from the documentation, which says "by default it extracts only from the initial chunk(s)." And I tried explicitly setting 'allChunks: false'.

@ghost ghost changed the title ExtractTextPlugin extracting from asynchronous split point chunks ExtractTextPlugin extracts from asynchronous split point chunks Oct 26, 2015
@noomorph
Copy link

+1, the same problem here

probably, CSS should not be extracted from async bundles

@ghost
Copy link
Author

ghost commented Oct 29, 2015

@sokra should I try to produce a pull request to do this?

@Duan112358
Copy link

@chrispesto I use bundle-loader to extract async modules,
and ExtractTextWebpackPlugin to extract common used css, the other module based css just bundled within js as allChunks: false specified. And, it work correctly.

@Duan112358
Copy link

you have three entries however

@KJTsanaktsidis
Copy link

+1 this is a real problem. In our case we have a tests chunk which has all of the tests and the asynchronous chunks baked into it, whilst in the actual app they are only loaded asynchronously. So all of the CSS from these chunks ends up extracted into tests.css instead of staying in the chunk.

@KJTsanaktsidis
Copy link

Of course, the question is, what should the behaviour be if a file is referenced synchronously from one entry chunk and asynchronously from another? Should the text be included in both the bundle and in the extracted CSS from the synchronously-requiring chunk, or not be extracted at all?

@scabbiaza
Copy link

I agree too, that it's a problem.
In my opinion it should not be any different in behaviour between css and js files, and even images, texts etc.
If files are in the the asynchronously specify dependencies, they should load asynchronously.

@stephenjwatkins
Copy link

+1

@sokra Is there any way to keep the styles in the chunks that are split via require.ensure?

@chrispesto did you figure this out?

@jarvanxing
Copy link

+1 Wondering if it's possible for ExtractTextWebpackPlugin to generate CSS files for each of the split points.

I really need this.

@andreechristian
Copy link

+1 is there any updates on this? I cannot get the splitted CSS using require.ensure

@jackyon
Copy link

jackyon commented May 12, 2016

+1

2 similar comments
@aaronwng
Copy link

+1

@itsmepetrov
Copy link

+1

@aaronwng
Copy link

  {
      test: /\.css$/,
      loaders: ["style-loader/url","file?name=app/[name].[hash].css!extract","css-loader","postcss-loader"]
  },

i finnally found use file-loader and extract-loader combined could achieve this,

@aaronwng
Copy link

just require the css in your async loaded source file

@aaronwng
Copy link

the most important is the style-loader with ''/url" option

@yxwu25
Copy link

yxwu25 commented Aug 9, 2016

+1

@strayiker
Copy link

+1, have the same problem

@george3447
Copy link

+1

1 similar comment
@denich
Copy link

denich commented Oct 25, 2016

+1

@poluyanov
Copy link

poluyanov commented Nov 22, 2016

+1
was fixed with CommonsChunkPlugin removing

@satazor
Copy link

satazor commented Dec 4, 2016

@biblesyme I've tried your suggestion but it's not working. Do you have a example somewhere? Thanks in advance.

@aaronwng
Copy link

aaronwng commented Dec 4, 2016

@satazor this is my implementation based on React, hope it help U

App.js

process.env.BROWSER && require('./app.sass')
import React from 'react'
import { Link } from 'react-router'
import {connect} from 'react-redux'

class App extends React.Component {
// business logic.....

route.js

module.exports= [
{
    path:'/user'
    , getComponent(nextState, cb){
        require.ensure([], (require)=> {
            //  async binding
            cb(null, require('./components/App.js').Container)
        })
    }
    ,childRoutes:[
      require('./routes/Register'),
      require('./routes/ChangePassword'),
      require('./routes/Login')
    ]
}
,require('./routes/PageNotFound')
]

webpack.config

module:{
  loaders:[
      {
          test: /\.sass$/,
          loaders: ["style/url", "file?name=_build_/[name].[hash].css!extract","css-loader","postcss-loader","sass?indentedSyntax=true"]
      }
  ]
}
      

@shubhamarora
Copy link

shubhamarora commented Dec 23, 2016

I want to create a dynamic split point. so I have changed my route to this:

<Route name="NAME" path='PATH' component={COMPONENT} >
    <IndexRoute name="NAME " getComponent={(location, callback)=>{
        require.ensure([], function() {
            callback(null, require('./containers/cont/view.js'));
        })
    }}/>
</Route>

After introducing require.ensure. Webpack has started throwing below error:

[webpack-isomorphic-tools] [error] asset not found: ./src/styles/core.scss
[1] ----
[1] ==> 💻  Open http://localhost:8084 in a browser to view the app.
[0] ./src/components/__tests__/InfoBar-test.js
[0] Module not found: Error: Cannot resolve module 'components/InfoBar/InfoBar.scss' in /src/components/__tests__
[0] resolve module components/InfoBar/InfoBar.scss in /src/components/__tests__
[0]   looking for modules in /src
[0]     resolve 'file' or 'directory' InfoBar/InfoBar.scss in /src/components
[0]       resolve file
[0]         /src/components/InfoBar/InfoBar.scss doesn't exist
[0]         /src/components/InfoBar/InfoBar.scss.json doesn't exist
[0]         /src/components/InfoBar/InfoBar.scss.js doesn't exist
[0]         /src/components/InfoBar/InfoBar.scss.jsx doesn't exist
[0]       resolve directory
[0]         /src/components/InfoBar/InfoBar.scss doesn't exist (directory default file)
[0]         /src/components/InfoBar/InfoBar.scss/package.json doesn't exist (directory description file)
[0]   looking for modules in /node_modules
[0]     /node_modules/components doesn't exist (module as directory)
[0] [/src/components/InfoBar/InfoBar.scss]
[0] [/src/components/InfoBar/InfoBar.scss.json]
[0] [/src/components/InfoBar/InfoBar.scss.js]
[0] [/src/components/InfoBar/InfoBar.scss.jsx]
[0] [/node_modules/components]
[0]  @ ./src/components/__tests__/InfoBar-test.js 68:17-59
[0] ./~/css-loader?modules&importLoaders=2&sourceMap&localIdentName=[local]___[hash:base64:5]!./~/postcss-loader?browsers=last 2 version!./~/sass-loader?outputStyle=expanded&sourceMap!./src/styles/sa_components.scss
[0] Module build failed:
[0]     color: $subtitle-color;
[0]           ^
[0]       Undefined variable: "$subtitle-color".
[0]       in /src/styles/sa_components.scss (line 10, column 12)
[0] Error:
[0]     color: $subtitle-color;
[0]           ^
[0]       Undefined variable: "$subtitle-color".
[0]       in /src/styles/sa_components.scss (line 10, column 12)
[0]     at options.error (/node_modules/node-sass/lib/index.js:292:26)
[0]  @ ./src/styles/sa_components.scss 4:14-305 13:2-17:4 14:20-311
[0] ./~/css-loader?modules&importLoaders=2&sourceMap&localIdentName=[local]___[hash:base64:5]!./~/postcss-loader?browsers=last 2 version!./~/sass-loader?outputStyle=expanded&sourceMap!./src/styles/post_bootstrap_overrides.scss
[0] Module build failed:
[0]       font-size: $paragraph-font;
[0]                 ^
[0]       Undefined variable: "$paragraph-font".
[0]       in /src/styles/post_bootstrap_overrides.scss (line 109, column 18)
[0] Error:
[0]       font-size: $paragraph-font;
[0]                 ^
[0]       Undefined variable: "$paragraph-font".
[0]       in /src/styles/post_bootstrap_overrides.scss (line 109, column 18)
[0]     at options.error (/node_modules/node-sass/lib/index.js:292:26)
[0]  @ ./src/styles/post_bootstrap_overrides.scss 4:14-316 13:2-17:4 14:20-322
[0] [webpack-isomorphic-tools/plugin] [error] Module "./~/css-loader?modules&importLoaders=2&sourceMap&localIdentName=[local]___[hash:base64:5]!./~/postcss-loader?browsers=last 2 version!./~/sass-loader?outputStyle=expanded&sourceMap!./src/styles/post_bootstrap_overrides.scss" has no source. Maybe Webpack compilation of this module failed. Skipping this asset.
[0] [webpack-isomorphic-tools/plugin] [error] Module "./~/css-loader?modules&importLoaders=2&sourceMap&localIdentName=[local]___[hash:base64:5]!./~/postcss-loader?browsers=last 2 version!./~/sass-loader?outputStyle=expanded&sourceMap!./src/styles/sa_components.scss" has no source. Maybe Webpack compilation of this module failed. Skipping this asset.

CSS Loader configuration in webpack:

{ test: /\.scss$/, loader: 'style!css?modules&importLoaders=2&sourceMap&localIdentName=[local]___[hash:base64:5]!postcss-loader?browsers=last 2 version!sass?outputStyle=expanded&sourceMap' },

Any idea on why the module build started failing and it is unable to resolve CSS variables?
@biblesyme @chrispesto @sokra

@mrdulin
Copy link

mrdulin commented Dec 26, 2016

@biblesyme thanks. it works. But is there a way to set the css filename same with my async chunks filename?

@michael8090
Copy link

I solve the problem by setting allChunks to true, now the delayed css content is in the entry css file.

@dalimian
Copy link

+1 having this issue

@sokra, lot of interest in this issue from many people, none of the work arounds are acceptable cause they all give something up, your input would be very valuable, at least do u think of this as bug, as designed, or enhancement. We are migrating a huge site to react/react-router/webpack, if we have your input we could maybe work on a patch.

to summarize the original issue in my own words

  1. extract-text-plugin does not usually extract from async chunks (from require.ensure), - so far so good, this is the expected behavior
    so in this example

     require.ensure(['../pages/Play'], function() {
       var Page = require('../pages/Play');
     },'play-chunk-async');
    

    play-chunk-async.js will be generated that will contain inline styles - as expected

  2. however, as soon as any manual entry entry is added pointing to ../pages/Play, play-chunk-async.js no longer contains the inline styles

    so after adding this entry to webpack config

    entry: {
       play:  ['./app/pages/Play'],
    }
    

    now play.js(has no inline styles as expected) and play.css are generated as expected, but play-chunk-async.js no longer has the inline styles - unexpected

I think fix to this issue is essential if one wants to be able to render pages on both server side (and avoid flash of unstyled content) and client side. For now we have allChunks set to true but this is not going to scale long term

@faceyspacey
Copy link

faceyspacey commented Apr 5, 2017

I think around this area in the code:

https://github.com/webpack-contrib/extract-text-webpack-plugin/blob/master/index.js#L267

we should now allow for the capability to extract css per chunk into their own file, and then packages like React Loadable can discover the the css files it needs to embed in the initial page.

In fact, I'm working on a PR to complete React Loadable's capabilities so it can properly (and synchronously) embed within served html the chunks used in the current request. So this may make obsolete the need to manually make entry points on behalf of dynamic ones to generate their CSS file. However, using this feature would come at the cost of having to insure you're using something like React Loadable to get those css chunk files to the client. At the end of the day, a robust and general solution to handle the marriage of code splitting + server rendering is needed, and that's precisely what I'm working on with React Loadable.

...Anyone have any ideas what needs to be done to modify extract-text-webpack-plugin??

My first inclination is to change:

var shouldExtract = !!(options.allChunks || chunk.isInitial());

to

var shouldExtract = true; // lol

I.e. make it always extract. But obviously there's more to it. Somewhere it's specifying that the results from all chunks go to an output file corresponding to the entry chunk. It looks like this might have something to do with it: module[NS + "/extract"] = shouldExtract.

Basically it seems like we need to undo its forcing of all output into a single file. It probably requires less code to just insure an output file per chunk as the code loops through chunks and modules. This is my first time looking at the code, any tips are more than welcome.

@faceyspacey
Copy link

faceyspacey commented Apr 15, 2017

Hey everyone, the following is a fork I made that supports individual css files per chunk (including dynamic split chunks):

https://github.com/faceyspacey/extract-css-chunks-webpack-plugin

It also has HMR working. You should use it just for your client webpack config, as it has HMR built in.

This is intended for use with an upcoming PR to React Loadable, which will handle your split points in an effective way along with server side rendering. What that means is that when using React Loadable server side, split points are synchronously required on the server and synchronously required on the client if it's the initial page load. This allows you to render once on the client without the checksum match failing, while having all the chunks (including CSS chunks) available for synchronous requiring.

I'll keep you posted as to when React Loadable supports this.

BowlingX added a commit to BowlingX/extract-text-webpack-plugin that referenced this issue Jun 17, 2017
BowlingX added a commit to BowlingX/extract-text-webpack-plugin that referenced this issue Jun 20, 2017
commit e2631af
Author: David Heidrich <[email protected]>
Date:   Mon Jun 19 19:08:13 2017 +0200

    removed dead code, cleanup

commit d8943d7
Author: David Heidrich <[email protected]>
Date:   Mon Jun 19 15:48:51 2017 +0200

    restored console log

commit 0d7c94a
Author: David Heidrich <[email protected]>
Date:   Mon Jun 19 00:30:34 2017 +0200

    fixed async loading (keep original style loader in async chunks)

commit 950d7ef
Author: David Heidrich <[email protected]>
Date:   Sun Jun 18 15:52:41 2017 +0200

    fixed exporting module informations

commit 8b97ee5
Author: David Heidrich <[email protected]>
Date:   Sun Jun 18 14:27:51 2017 +0200

    fixed line length

commit 6898aa4
Author: David Heidrich <[email protected]>
Date:   Sun Jun 18 14:26:34 2017 +0200

    respect all chunks

commit bbc93ab
Author: David Heidrich <[email protected]>
Date:   Sun Jun 18 12:33:09 2017 +0200

    adjusted test to be env agnostic

commit 3eba627
Author: David Heidrich <[email protected]>
Date:   Sun Jun 18 12:25:16 2017 +0200

    adjusted tests to use css-loader, added tests for allChunks, remove module from initial chunk

commit 09ac8cb
Author: David Heidrich <[email protected]>
Date:   Sat Jun 17 22:36:09 2017 +0200

    adjusted tests, remove module from base if extracted

commit 1791796
Author: David Heidrich <[email protected]>
Date:   Sat Jun 17 21:23:58 2017 +0200

    added testcase for issue webpack-contrib#120
@alexander-akait
Copy link
Member

@faceyspacey can your provide information what exactly does not work as expected or wrong? Maybe we can fix it into extract-text-webpack-plugin, thanks!

@mschnee
Copy link

mschnee commented Oct 26, 2017

Having looked into this for a while, I feel @faceyspacey's solution is super overkill (it requires multiple loaders and plugins, it requires magic strings added to the window object, it makes a number of assumptions about server-side-rendering). It's a great solution for a specific few cases (and I use it and love it), but it doesn't work if you just want to dynamically load a css file on-demand.

I think the actual TLDR ask here is to replace:

/***/ "./path/to/your/dynamic/split/file.css":
/***/ (function(module, exports) {

// removed by extract-text-webpack-plugin

/***/ }),

with (assuming webpack.output.publicPath = '/static');

/***/ "./path/to/your/dynamic/split/file.css":
/***/ (function(module, exports) {

loadCss(`/static/0.7739c3c60ea45ec5e06ab85046aba848.css`);

/***/ }),

Where /static/0.7739c3c60ea45ec5e06ab85046aba848.css is a chunk that has been split and extracted, as opposed to being an inlined css string in Javascript (not extracted at all) or an extraction of all style strings into a single giant css file (current ExtractTextPlugin behavior)

I know there's a long-term plan in webpack to make styles (and webasm) first-class module types in addition to javascript, but in the interim is it possible to configure an extract-text-webpack-plugin instance with a means of dynamically loading a chunk at runtime?

@VincentCharpentier
Copy link

VincentCharpentier commented Jan 4, 2018

none of the solutions here seems to work for me.

I'm working on a big solution (multiple websites in one solution - I can't split the project) which brings me to code-splitting and lazy-loading.
I'm using css-modules to avoid potential conflicts with CSS class names.

Each of the websites is basically wrapped in a route component (react-router).

For now I can use allChunks:true option to put all my css in one single file but that's obvioulsy not an optimal solution.

I wish I could do just like webpack output.chunkFilename option:

output: {
  path: BUILD_DIR,
  filename: '[name].bundle.js',
  publicPath: '/',
  chunkFilename: '[id].chunk.js',
}

Is there any plan to allow this kind of behavior ?

@sheepsteak
Copy link

@VincentCharpentier there are big changes coming to CSS in Webpack 4 and I think this is one of the things that will be possible.

https://medium.com/webpack/road-to-webpack-4-week-20-21-1641d03ce06e

@VincentCharpentier
Copy link

@sheepsteak So This is gonna be standard webpack ? What a great news, thanks for the info.

I think I can just fallback to style-loader in the meantime.

@sheepsteak
Copy link

@VincentCharpentier I believe so 😄

In the first link it says:

We adjust the Chunk Templates to write two files. One for the javascript and one of the stylesheets (in a .css file).

I've been waiting a long time for this too!

@alexander-akait
Copy link
Member

It is very old issue, closing in favor #120 (new issue and have PR). Don't worry we remember about issue. Feel free to PR.

@dcalhoun
Copy link

dcalhoun commented Feb 1, 2018

@evilebottnawi this issue is #120. Did you intend to reference a different issue/PR?

@alexander-akait
Copy link
Member

@dcalhoun oh sorry, #686

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

No branches or pull requests