Webpack Plugin: How to add dynamically a Module to the main Chunk?
Asked Answered
C

2

7

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;
Classic answered 3/3, 2018 at 19:15 Comment(6)
Would you share some of the configuration that you already have done ?Nightlong
Added my current code, currently it creates a new chunk with addStylesTemplate, what I want to achieve it that the result.extracted (this style should be injected into an inline style) will become a webpack module inside the original Chunk.Classic
You want to put part of style into extract-text-plugin, and next part into style-loader (which will render it to HTML head). Correct?Ferine
Yes, it can be passed to style-loader, but it is not must, the must is that the css will be in the main chunk.Classic
@felixmosh, what version webpack target?Panettone
It doesn't matter, currently I'm working on version 3...Classic
C
2

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.

Classic answered 12/8, 2018 at 19:25 Comment(0)
S
-1

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)
      }
    )
  }
}
Standard answered 24/3, 2019 at 14:6 Comment(2)
I'm familiar with that approach, pay attention that the content that I need to inject back to the bundle is a result of the compilation.Classic
The simplest approach to do what you have achieved is just using loader :]Classic

© 2022 - 2024 — McMap. All rights reserved.