Reputation: 35603
I'm working on a Webpack plugin that basically looks for a css
assets inside the chunks, when it founds such asset, applies on it some postCSS
plugin that returns 2 outputs, the one should continue to be extracted using Extract-Text-Plugin
and the other output should become a new module inside the chunk that injects it to the head on runtime.
The only part that I'm not managed to implement is the part that creates a new module inside existing Chunk. There is some pointers / ideas?
I managed to create a new chunk out of it but without webpack wrappers, that means that I'm not able to support HMR for that piece of css and load it lazily.
class ExtractTPAStylePlugin {
constructor(options) {
this._options = Object.assign({
pattern: [
/"\w+\([^\)]+\)"/
]
}, options);
}
extract(compilation, chunks) {
const promises = [];
chunks.forEach((chunk) => {
promises.push(
chunk.files
.filter(fileName => fileName.endsWith('.css'))
.map(file => postcss([extractStyles(this._options)])
.process(compilation.assets[file].source(), {from: file, to: file})
.then((result) => {
compilation.assets[file] = new RawSource(result.css);
const filename = file.replace('.css', fileSuffix);
const newChunk = new Chunk(filename);
newChunk.files = [filename];
newChunk.ids = [];
compilation.chunks.push(newChunk);
const extractedStyles = `(${addStylesTemplate})()`
.replace('__CSS__', JSON.stringify(result.extracted))
.replace('__ID__', file);
compilation.assets[filename] = new OriginalSource(extractedStyles);
}))
);
});
return Promise.all(promises);
}
apply(compiler) {
compiler.plugin('compilation', (compilation) => {
compilation.plugin('optimize-chunk-assets', (chunks, callback) => {
this.extract(compilation, chunks)
.then(() => callback())
.catch(callback);
});
});
}
}
module.exports = ExtractTPAStylePlugin;
Upvotes: 7
Views: 2787
Reputation: 33700
There are perhaps more ways to do this.
One convenient way I found was creating a custom NormalModuleFactory
plugin and hooking it with the compiler.
The plugin receives a module request and context (what is being imported form where). With that you can match the request and return your module source. Simplified, it looks something like:
class CustomModuleFactoryPlugin {
apply (normalModuleFactory) {
// Tap in the factory hook.
normalModuleFactory.hooks.factory.tap(
'CustomModuleFactoryPlugin',
factory => (data, cb) => {
const { context } = data
const { request } = data.dependencies[0]
if (path.join(context, request).contains('/something/you/expect') {
return cb(null, new RawSource('Success!'))
}
return factory(data, cb)
}
)
}
}
Upvotes: -1
Reputation: 35603
OK, So I've managed to collect some pieces of code from several plugins, and the winning solution is to inject a loader to some fake import file, in that loader load the entire js code to the main bundle, and put some placeholder for the results from optimize-chunk-assets
phase.
After that, in optimize-chunk-assets
, you can find the relevant chunk, and use ReplaceSource
to find & replace the placeholder.
For inspiration, you can checkout that plugin.
Upvotes: 2