Skip to content

Commit 849ad54

Browse files
committed
create css files per chunk + HMR
1 parent 360c155 commit 849ad54

File tree

6 files changed

+171
-247
lines changed

6 files changed

+171
-247
lines changed

README.md

+34-190
Original file line numberDiff line numberDiff line change
@@ -1,227 +1,71 @@
11
[![npm][npm]][npm-url]
2-
[![node][node]][node-url]
3-
[![deps][deps]][deps-url]
4-
[![tests][tests]][tests-url]
5-
[![coverage][cover]][cover-url]
62
[![chat][chat]][chat-url]
73

8-
<div align="center">
9-
<img width="200" height="200"
10-
src="https://cdn.rawgit.com/webpack-contrib/extract-text-webpack-plugin/574e3200/logo.svg">
11-
<a href="https://github.com/webpack/webpack">
12-
<img width="200" height="200"
13-
src="https://webpack.js.org/assets/icon-square-big.svg">
14-
</a>
15-
<h1>Extract Text Plugin</h1>
16-
</div>
4+
# Extract CSS Chunk
175

18-
<h2 align="center">Install</h2>
6+
Like the Extract Text Webpack Plugin, but creates multiple css files (one per chunk). Then, as part of server side rendering, you can deliver just the css chunks needed by the current request.
197

20-
```bash
21-
# for webpack 2
22-
npm install --save-dev extract-text-webpack-plugin
23-
# for webpack 1
24-
npm install --save-dev extract-text-webpack-plugin@1.0.1
25-
```
8+
In addition, for each javascript chunk created another js chunk is created with the styles injected via style-loader
9+
so that when the client asynchronously loads more chunks the styles will be available as well. This is as opposed to to the chunks you
10+
initially serve embedded in the page, which are post-fixed with `no_css.js`, which do not inject styles since it's expected that
11+
the corresponding extracted CSS files are embedded in the page as well. This obviously serves the purpose of reducing the size of your initially
12+
served javascript chunks. As much as possible has been thought of to make this a complete solution.
13+
14+
BONUS: It also has first-class support for Hot Module Replacement across those css files/chunks.
2615

27-
<h2 align="center">Usage</h2>
16+
NOTE: most the code comes from the original Extract Text Webpack Plugin--the goal is to merge this functionality back into that package at some point. That might be a while. Until then I'd feel totally comfortable just using this package. It's meant specifically for use with *React Loadable*. For complete usage, see the [React Loadable Example](https://github.com/thejameskyle/react-loadable-example).
2817

29-
> :warning: For webpack v1, see [the README in the webpack-1 branch](https://github.com/webpack/extract-text-webpack-plugin/blob/webpack-1/README.md).
18+
## Install
19+
```bash
20+
yarn add --dev extract-css-chunk
21+
```
3022

23+
## Usage
3124
```js
32-
const ExtractTextPlugin = require("extract-text-webpack-plugin");
25+
const ExtractCssChunk = require("extract-css-chunk")
3326

3427
module.exports = {
3528
module: {
3629
rules: [
3730
{
3831
test: /\.css$/,
39-
use: ExtractTextPlugin.extract({
40-
fallback: "style-loader",
41-
use: "css-loader"
32+
use: ExtractCssChunk.extract({
33+
use: 'css-loader?modules&localIdentName=[name]__[local]--[hash:base64:5]',
4234
})
4335
}
4436
]
4537
},
4638
plugins: [
47-
new ExtractTextPlugin("styles.css"),
39+
new ExtractCssChunk,
4840
]
4941
}
5042
```
5143

52-
It moves all the `require("style.css")`s in entry chunks into a separate single CSS file. So your styles are no longer inlined into the JS bundle, but separate in a CSS bundle file (`styles.css`). If your total stylesheet volume is big, it will be faster because the CSS bundle is loaded in parallel to the JS bundle.
53-
54-
|Advantages|Caveats|
55-
|:---------|:------|
56-
| Fewer style tags (older IE has a limit) | Additional HTTP request |
57-
| CSS SourceMap (with `devtool: "source-map"` and `extract-text-webpack-plugin?sourceMap`) | Longer compilation time |
58-
| CSS requested in parallel | No runtime public path modification |
59-
| CSS cached separate | No Hot Module Replacement |
60-
| Faster runtime (less code and DOM operations) | ... |
61-
62-
<h2 align="center">Options</h2>
63-
64-
```js
65-
new ExtractTextPlugin(options: filename | object)
66-
```
67-
68-
|Name|Type|Description|
69-
|:--:|:--:|:----------|
70-
|**`id`**|`{String}`|Unique ident for this plugin instance. (For advanced usage only, by default automatically generated)|
71-
|**`filename`**|`{String|Function}`|Name of the result file. May contain `[name]`, `[id]` and `[contenthash]`|
72-
|**`allChunks`**|`{Boolean}`|Extract from all additional chunks too (by default it extracts only from the initial chunk(s))|
73-
|**`disable`**|`{Boolean}`|Disables the plugin|
74-
|**`ignoreOrder`**|`{Boolean}`|Disables order check (useful for CSS Modules!), `false` by default|
75-
76-
* `[name]` name of the chunk
77-
* `[id]` number of the chunk
78-
* `[contenthash]` hash of the content of the extracted file
79-
80-
> :warning: `ExtractTextPlugin` generates a file **per entry**, so you must use `[name]`, `[id]` or `[contenthash]` when using multiple entries.
81-
82-
#### `#extract`
83-
84-
```js
85-
ExtractTextPlugin.extract(options: loader | object)
86-
```
87-
88-
Creates an extracting loader from an existing loader. Supports loaders of type `{ loader: [name]-loader -> {String}, options: {} -> {Object} }`.
8944

90-
|Name|Type|Description|
91-
|:--:|:--:|:----------|
92-
|**`options.use`**|`{String}`/`{Array}`/`{Object}`|Loader(s) that should be used for converting the resource to a CSS exporting module _(required)_|
93-
|**`options.fallback`**|`{String}`/`{Array}`/`{Object}`|loader(e.g `'style-loader'`) that should be used when the CSS is not extracted (i.e. in an additional chunk when `allChunks: false`)|
94-
|**`options.publicPath`**|`{String}`|Override the `publicPath` setting for this loader|
45+
## Info
9546

47+
It moves all the `require("style.css")`s in entry chunks ***AND DYNAMIC CODE SPLIT CHUNKS*** into a separate ***multiple*** CSS files. So your styles are no longer inlined into the JS bundle, but separate in CSS bundle files (e.g named entry points: `main.12345.css` and dynamic split chunks: `0.123456.css`, `1.123456.css`, etc).
9648

97-
#### Multiple Instances
49+
If you effectively use code-splitting this can be a far better option than using emerging solutions like *StyleStron*, *StyledComponents*,and slightly older tools like *Aphrodite*, *Glamor*, etc. We don't like either rounds of tools because they all have a runtime overhead. Every time your React component is rendered with those, CSS is generated and updated within the DOM. The reason *Extract CSS Chunk* can be a better option is because *we also generate multiple sets of CSS* based on what is actually "used", but without the runtime overhead. The difference is our definition of "used" is modules determined statically (which may not in fact be rendered) vs. what is actually rendered (as is the case with the other tools).
9850

99-
There is also an `extract` function on the instance. You should use this if you have more than one instance of `ExtractTextPlugin`.
100-
101-
```js
102-
const ExtractTextPlugin = require('extract-text-webpack-plugin');
103-
104-
// Create multiple instances
105-
const extractCSS = new ExtractTextPlugin('stylesheets/[name]-one.css');
106-
const extractLESS = new ExtractTextPlugin('stylesheets/[name]-two.css');
107-
108-
module.exports = {
109-
module: {
110-
rules: [
111-
{
112-
test: /\.css$/,
113-
use: extractCSS.extract([ 'css-loader', 'postcss-loader' ])
114-
},
115-
{
116-
test: /\.less$/i,
117-
use: extractLESS.extract([ 'css-loader', 'less-loader' ])
118-
},
119-
]
120-
},
121-
plugins: [
122-
extractCSS,
123-
extractLESS
124-
]
125-
};
126-
```
51+
So yes, our CSS files may be mildly larger and include unnecessary css, but it's CSS that is likely to be used, i.e. if an "if/else" statement reaches another branch, *but not CSS from a different section of your app.*
12752

128-
### Extracting Sass or LESS
53+
In short, by putting code splitting appropriate places you have a lot of control over the css files that are created. **It's our perspective that you have achieved 80-99% of the performance gains (i.e. the creation of small relevant css files) at this static stage. Offloading this work to the runtime stage is ultimately nit-picking and results in diminishing returns.**
12954

130-
The configuration is the same, switch out `sass-loader` for `less-loader` when necessary.
55+
ADDITIONAL BENEFIT: **This also means you DO NOT need to clutter your component code with a specialized way of applying CSS!** A final cherry on top is that the way you import module-based styles is exactly how you would import styles in React Native that exist in a separate file, which allows for extremely interchangeable code between React Native and regular React. Hurray!
13156

132-
```js
133-
const ExtractTextPlugin = require('extract-text-webpack-plugin');
57+
**We love CSS modules; no less, no more.**
13458

135-
module.exports = {
136-
module: {
137-
rules: [
138-
{
139-
test: /\.scss$/,
140-
use: ExtractTextPlugin.extract({
141-
fallback: 'style-loader',
142-
//resolve-url-loader may be chained before sass-loader if necessary
143-
use: ['css-loader', 'sass-loader']
144-
})
145-
}
146-
]
147-
},
148-
plugins: [
149-
new ExtractTextPlugin('style.css')
150-
//if you want to pass in options, you can do so:
151-
//new ExtractTextPlugin({
152-
// filename: 'style.css'
153-
//})
154-
]
155-
}
156-
```
59+
## NOTE
15760

158-
### Modify filename
61+
The file name structure is currently hard-coded to be `[name].[contenthash].css`. It was first created to be used with *React Loadable* which will discover your CSS files for you (from webpack stats) no matter what you name them--so it's not something you need to worry about. If you want to fix that and therefore allow for a user-specified `filename` like in the original *Extract Text Plugin*, feel free to make a PR. The current filename structure is required for HMR so that new files are created on each change--so that is the problem you're trying to solve. For whatever reason, if the file name is the same (i.e. doesn't contain a hash) the original file is not updated. There are some notes in the code. Check the `hotModuleReplacement.js` file.
15962

160-
`filename` parameter could be `Function`. It passes `getPath` to process the format like `css/[name].css` and returns the real file name, `css/js/a.css`. You can replace `css/js` with `css` then you will get the new path `css/a.css`.
16163

64+
[npm]: https://img.shields.io/npm/v/extract-css-chunk.svg
65+
[npm-url]: https://npmjs.com/package/extract-css-chunk
16266

163-
```js
164-
entry: {
165-
'js/a': "./a"
166-
},
167-
plugins: [
168-
new ExtractTextPlugin({
169-
filename: (getPath) => {
170-
return getPath('css/[name].css').replace('css/js', 'css');
171-
},
172-
allChunks: true
173-
})
174-
]
175-
```
67+
[tests]: http://img.shields.io/travis/faceyspacey/extract-css-chunk.svg
68+
[tests-url]: https://travis-ci.org/faceyspacey/extract-css-chunk
17669

177-
<h2 align="center">Maintainers</h2>
178-
179-
<table>
180-
<tbody>
181-
<tr>
182-
<td align="center">
183-
<img width="150" height="150"
184-
src="https://avatars3.githubusercontent.com/u/166921?v=3&s=150">
185-
</br>
186-
<a href="https://github.com/bebraw">Juho Vepsäläinen</a>
187-
</td>
188-
<td align="center">
189-
<img width="150" height="150"
190-
src="https://avatars2.githubusercontent.com/u/8420490?v=3&s=150">
191-
</br>
192-
<a href="https://github.com/d3viant0ne">Joshua Wiens</a>
193-
</td>
194-
<td align="center">
195-
<img width="150" height="150"
196-
src="https://avatars3.githubusercontent.com/u/533616?v=3&s=150">
197-
</br>
198-
<a href="https://github.com/SpaceK33z">Kees Kluskens</a>
199-
</td>
200-
<td align="center">
201-
<img width="150" height="150"
202-
src="https://avatars3.githubusercontent.com/u/3408176?v=3&s=150">
203-
</br>
204-
<a href="https://github.com/TheLarkInn">Sean Larkin</a>
205-
</td>
206-
</tr>
207-
<tbody>
208-
</table>
209-
210-
211-
[npm]: https://img.shields.io/npm/v/extract-text-webpack-plugin.svg
212-
[npm-url]: https://npmjs.com/package/extract-text-webpack-plugin
213-
214-
[node]: https://img.shields.io/node/v/extract-text-webpack-plugin.svg
215-
[node-url]: https://nodejs.org
216-
217-
[deps]: https://david-dm.org/webpack-contrib/extract-text-webpack-plugin.svg
218-
[deps-url]: https://david-dm.org/webpack-contrib/extract-text-webpack-plugin
219-
220-
[tests]: http://img.shields.io/travis/webpack-contrib/extract-text-webpack-plugin.svg
221-
[tests-url]: https://travis-ci.org/webpack-contrib/extract-text-webpack-plugin
222-
223-
[cover]: https://coveralls.io/repos/github/webpack-contrib/extract-text-webpack-plugin/badge.svg
224-
[cover-url]: https://coveralls.io/github/webpack-contrib/extract-text-webpack-plugin
225-
226-
[chat]: https://badges.gitter.im/webpack/webpack.svg
227-
[chat-url]: https://gitter.im/webpack/webpack
70+
[chat]: https://badges.gitter.im/extract-css-chunk.svg
71+
[chat-url]: https://gitter.im/extract-css-chunk

hotModuleReplacement.js

+10-6
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,18 @@
1-
module.exports = function(compilationHash, publicPath, outputFilename) {
1+
module.exports = function(publicPath, outputFilename) {
22
if (document) {
33
var origin = document.location.protocol + '//' + document.location.hostname + (document.location.port ? ':' + document.location.port: '');
4+
var newHref = origin + publicPath + outputFilename
45
var styleSheets = document.getElementsByTagName('link');
6+
7+
//update the stylesheet corresponding to `outputFilename`
58
for (var i = 0; i < styleSheets.length; i++) {
69
if (styleSheets[i].href) {
7-
var hrefUrl = styleSheets[i].href.split('?');
8-
var href = hrefUrl[0];
9-
var hash = hrefUrl[1];
10-
if (hash !== compilationHash && href === origin + publicPath + outputFilename) {
11-
var url = href + '?' + compilationHash;
10+
var oldChunk = styleSheets[i].href.split('.')[0];
11+
var newChunk = newHref.split('.')[0];
12+
13+
if (oldChunk === newChunk) {
14+
// date insures sheets update when [contenthash] is not used in file names
15+
var url = newHref + '?' + (+new Date);
1216
styleSheets[i].href = url;
1317
console.log('[HMR]', 'Reload css: ', url);
1418
break;

0 commit comments

Comments
 (0)