Skip to content

Commit 5896841

Browse files
Merge pull request bootstarted#21 from metalabdesign/update-listen-for-webpack-emit
Allow splitting on `emit` event.
2 parents 0e6f125 + 0d79377 commit 5896841

File tree

3 files changed

+90
-44
lines changed

3 files changed

+90
-44
lines changed

README.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,8 @@ The following configuration options are available:
5555

5656
**preserve**: `default: false`. Keep the original unsplit file as well. Sometimes this is desirable if you want to target a specific browser (IE) with the split files and then serve the unsplit ones to everyone else.
5757

58+
**defer**: `default: 'false'`. You can pass `true` here to cause this plugin to split the CSS on the `emit` phase. Sometimes this is needed if you have other plugins that operate on the CSS also in the emit phase. Unfortunately by doing this you potentially lose chunk linking and source maps. Use only when necessary.
59+
5860
[webpack]: http://webpack.github.io/
5961
[herp]: https://github.com/ONE001/css-file-rules-webpack-separator
6062
[postcss]: https://github.com/postcss/postcss

src/index.js

Lines changed: 61 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -70,19 +70,24 @@ export default class CSSSplitWebpackPlugin {
7070
* @param {Boolean|String} imports Truish to generate an additional import
7171
* asset. When a boolean use the default name for the asset.
7272
* @param {String} filename Control the generated split file name.
73+
* @param {Boolean} defer Defer splitting until the `emit` phase. Normally
74+
* only needed if something else in your pipeline is mangling things at
75+
* the emit phase too.
7376
* @param {Boolean} preserve True to keep the original unsplit file.
7477
*/
7578
constructor({
7679
size = 4000,
7780
imports = false,
7881
filename = '[name]-[part].[ext]',
7982
preserve,
83+
defer = false,
8084
}) {
8185
this.options = {
8286
size,
8387
imports: normalizeImports(imports, preserve),
8488
filename: nameInterpolator(filename),
8589
preserve,
90+
defer,
8691
};
8792
}
8893

@@ -121,6 +126,48 @@ export default class CSSSplitWebpackPlugin {
121126
});
122127
}
123128

129+
chunksMapping(compilation, chunks, done) {
130+
const assets = compilation.assets;
131+
const publicPath = strip(compilation.options.output.publicPath || './');
132+
const promises = chunks.map((chunk) => {
133+
const input = chunk.files.filter(isCSS);
134+
const items = input.map((name) => this.file(name, assets[name]));
135+
return Promise.all(items).then((entries) => {
136+
entries.forEach((entry) => {
137+
// Skip the splitting operation for files that result in no
138+
// split occuring.
139+
if (entry.chunks.length === 1) {
140+
return;
141+
}
142+
// Inject the new files into the chunk.
143+
entry.chunks.forEach((file) => {
144+
assets[file._name] = file;
145+
chunk.files.push(file._name);
146+
});
147+
const content = entry.chunks.map((file) => {
148+
return `@import "${publicPath}/${file._name}";`;
149+
}).join('\n');
150+
const imports = this.options.imports({
151+
...entry,
152+
content,
153+
});
154+
if (!this.options.preserve) {
155+
chunk.files.splice(chunk.files.indexOf(entry.file), 1);
156+
delete assets[entry.file];
157+
}
158+
if (imports) {
159+
assets[imports] = new RawSource(content);
160+
chunk.files.push(imports);
161+
}
162+
});
163+
return Promise.resolve();
164+
});
165+
});
166+
Promise.all(promises).then(() => {
167+
done();
168+
}, done);
169+
}
170+
124171
/**
125172
* Run the plugin against a webpack compiler instance. Roughly it walks all
126173
* the chunks searching for CSS files and when it finds one that needs to be
@@ -132,50 +179,21 @@ export default class CSSSplitWebpackPlugin {
132179
* @returns {void}
133180
*/
134181
apply(compiler : Object) {
135-
// Only run on `this-compilation` to avoid injecting the plugin into
136-
// sub-compilers as happens when using the `extract-text-webpack-plugin`.
137-
compiler.plugin('this-compilation', (compilation) => {
138-
const assets = compilation.assets;
139-
const publicPath = strip(compilation.options.output.publicPath || './');
140-
compilation.plugin('optimize-chunk-assets', (chunks, done) => {
141-
const promises = chunks.map((chunk) => {
142-
const input = chunk.files.filter(isCSS);
143-
const items = input.map((name) => this.file(name, assets[name]));
144-
return Promise.all(items).then((entries) => {
145-
entries.forEach((entry) => {
146-
// Skip the splitting operation for files that result in no
147-
// split occuring.
148-
if (entry.chunks.length === 1) {
149-
return;
150-
}
151-
// Inject the new files into the chunk.
152-
entry.chunks.forEach((file) => {
153-
assets[file._name] = file;
154-
chunk.files.push(file._name);
155-
});
156-
const content = entry.chunks.map((file) => {
157-
return `@import "${publicPath}/${file._name}";`;
158-
}).join('\n');
159-
const imports = this.options.imports({
160-
...entry,
161-
content,
162-
});
163-
if (!this.options.preserve) {
164-
chunk.files.splice(chunk.files.indexOf(entry.file), 1);
165-
delete assets[entry.file];
166-
}
167-
if (imports) {
168-
assets[imports] = new RawSource(content);
169-
chunk.files.push(imports);
170-
}
171-
});
172-
return Promise.resolve();
173-
});
182+
if (this.options.defer) {
183+
// Run on `emit` when user specifies the compiler phase
184+
// Due to the incorrect css split + optimization behavior
185+
// Expected: css split should happen after optimization
186+
compiler.plugin('emit', (compilation, done) => {
187+
return this.chunksMapping(compilation, compilation.chunks, done);
188+
});
189+
} else {
190+
// Only run on `this-compilation` to avoid injecting the plugin into
191+
// sub-compilers as happens when using the `extract-text-webpack-plugin`.
192+
compiler.plugin('this-compilation', (compilation) => {
193+
compilation.plugin('optimize-chunk-assets', (chunks, done) => {
194+
return this.chunksMapping(compilation, chunks, done);
174195
});
175-
Promise.all(promises).then(() => {
176-
done();
177-
}, done);
178196
});
179-
});
197+
}
180198
}
181199
}

test/spec/index.spec.js

Lines changed: 27 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -133,10 +133,36 @@ describe('CSSSplitWebpackPlugin', () => {
133133
expect(files).to.not.have.property('styles-1.css.map');
134134
})
135135
);
136-
137136
it('should fail with bad imports', () => {
138137
expect(() =>
139138
new CSSSplitWebpackPlugin({imports: () => {}})
140139
).to.throw(TypeError);
141140
});
141+
describe('deferred emit', () => {
142+
it('should split css files when necessary', (done) => {
143+
webpack({size: 3, defer: true}).then(({stats, files}) => {
144+
expect(stats.assetsByChunkName)
145+
.to.have.property('main')
146+
.to.contain('styles-1.css')
147+
.to.contain('styles-2.css');
148+
expect(files).to.have.property('styles-1.css');
149+
expect(files).to.have.property('styles-2.css');
150+
expect(files).to.have.property('styles.css.map');
151+
done();
152+
});
153+
});
154+
it('should ignore files that do not need splitting', (done) => {
155+
webpack({size: 10, defer: true}).then(({stats, files}) => {
156+
expect(stats.assetsByChunkName)
157+
.to.have.property('main')
158+
.to.contain('styles.css')
159+
.to.not.contain('styles-1.css')
160+
.to.not.contain('styles-2.css');
161+
expect(files).to.have.property('styles.css');
162+
expect(files).to.not.have.property('styles-1.css');
163+
expect(files).to.not.have.property('styles-2.css');
164+
done();
165+
});
166+
});
167+
});
142168
});

0 commit comments

Comments
 (0)