Search code examples
webpackwebpack-2

Webpack loaders order: what are webpack pre and post loaders and how they differ from a chain of loaders


I get that in the newest Webpack we can specify the module.rules option enforce: 'pre' to make a certain loader run as a "pre-loader" as specified in the docs.

But I couldn't find any proper explanation of what pre-loader and post-loader means. Off course we can logically think that "pre" runs before "post" but I don't get what EXACTLY happens (and why is not documented?).

This is also considering that there is already a way to specify the loaders order looking at the property Rule.use in the docs which says Loaders can be chained by passing multiple loaders, which will be applied from right to left (last to first configured)

So two connected questions:

  • what is the difference between chaining and pre and post ?
  • is there a way to have a more verbose webpack log on the sequence of this chain to understand what runs first and what second?

PS 1: I know there are similar questions on SO but none that I found is linking to a piece of documentation that actually explains the loading order in details

PS 2: a brief scenario on why this seems important to me is that I run typescript, tslint and babel and I would like to understand the correct chaining process and what is actually going on in the various steps


Solution

  • To discover the answer I wrote my own loaders a-loader.js through h-loader.js that take in content, print a log, and then return the content. Each loader file has a normal phase and a pitching phase for completeness. You can read about pitching loaders here https://webpack.js.org/api/loaders/#pitching-loader.

    a-loader.js:

    module.exports = function(content) {
      console.log('Loader A, normal phase.');
      return content;
    };
    
    module.exports.pitch = function(remainingRequest, precedingRequest, data) {
      console.log('Loader A, pitching phase.');
    }
    

    All the loaders have identical code except I changed the logging statement to log which loader it is.

    My webpack-config.js looked like this:

      module: {
        rules: [
          {
            test: /\.js$/,
            use: [{loader: path.resolve('loaders/a-loader.js')}],
          },
          {
            test: /\.js$/,
            use: [{loader: path.resolve('loaders/b-loader.js')}]
          },
          {
            test: /\.js$/,
            use: [{loader: path.resolve('loaders/c-loader.js')}]
          },
          {
            test: /\.js$/,
            use: [{loader: path.resolve('loaders/d-loader.js')}]
          },
          {
            test: /\.js$/,
            use: [{loader: path.resolve('loaders/e-loader.js')}],
          },
          {
            test: /\.js$/,
            use: [{loader: path.resolve('loaders/f-loader.js')}]
          },
          {
            test: /\.js$/,
            use: [{loader: path.resolve('loaders/g-loader.js')}]
          },
          {
            test: /\.js$/,
            use: [{loader: path.resolve('loaders/h-loader.js')}]
          },
        ]
      }
    

    Output:

    Loader A, pitching phase.
    Loader B, pitching phase.
    Loader C, pitching phase.
    Loader D, pitching phase.
    Loader E, pitching phase.
    Loader F, pitching phase.
    Loader G, pitching phase.
    Loader H, pitching phase.
    Loader H, normal phase.
    Loader G, normal phase.
    Loader F, normal phase.
    Loader E, normal phase.
    Loader D, normal phase.
    Loader C, normal phase.
    Loader B, normal phase.
    Loader A, normal phase.
    

    No surprise here. The pitching phases run first, and then the normal phases run. As you pointed out, normal phase loaders are applied right-to-left. h is first in the normal phase because it is the furthest right in the array (chain). I have a helpful way to remember the pitching order. Just think about the normal phase order and imagine a mirror image projected above the normal order. The mirror image is the pitching order.

    Next, I adjusted the webpack.config.js to be the following:

    • pre: a, h
    • post: d, f
      module: {
        rules: [
          {
            test: /\.js$/,
            use: [{loader: path.resolve('loaders/a-loader.js')}],
            enforce: "pre"
          },
          {
            test: /\.js$/,
            use: [{loader: path.resolve('loaders/b-loader.js')}]
          },
          {
            test: /\.js$/,
            use: [{loader: path.resolve('loaders/c-loader.js')}]
          },
          {
            test: /\.js$/,
            use: [{loader: path.resolve('loaders/d-loader.js')}],
            enforce: "post"
          },
          {
            test: /\.js$/,
            use: [{loader: path.resolve('loaders/e-loader.js')}],
          },
          {
            test: /\.js$/,
            use: [{loader: path.resolve('loaders/f-loader.js')}],
            enforce: "post"
          },
          {
            test: /\.js$/,
            use: [{loader: path.resolve('loaders/g-loader.js')}]
          },
          {
            test: /\.js$/,
            use: [{loader: path.resolve('loaders/h-loader.js')}],
            enforce: "pre"
          },
        ]
      }
    

    Ouput:

    Loader D, pitching phase.
    Loader F, pitching phase.
    Loader B, pitching phase.
    Loader C, pitching phase.
    Loader E, pitching phase.
    Loader G, pitching phase.
    Loader A, pitching phase.
    Loader H, pitching phase.
    Loader H, normal phase.
    Loader A, normal phase.
    Loader G, normal phase.
    Loader E, normal phase.
    Loader C, normal phase.
    Loader B, normal phase.
    Loader F, normal phase.
    Loader D, normal phase.
    

    Ignore the pitching phase for a moment, because remember they are just a mirror reflection of the normal phase. Think of enforce: pre and post like groupings. The "pre" are the first group, then comes the unlabeled "normal" group, and finally the "post" group. In normal phase, the first loader is h because it is in the "pre" group and is furthest right in the array. Next is a because it is the only other one in the "pre" group. Next comes the "ungrouped" g, e, c, b from right-to-left. And finally the "post" group, f and d, runs in right-to-left order.

    I don't know why this isn't documented better on the webpack site.