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:
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
Loaders are transformations that are applied to the source code of a module. They allow you to pre-process files as you import or “load” them. Thus, loaders are kind of like “tasks” in other build tools and provide a powerful way to handle front-end build steps.
There are four basic concepts in webpack: entry , output , modules and plug-ins . These configurations are added in webpack.
Loaders work at the individual file level during or before the bundle is generated. Plugins: Plugins work at bundle or chunk level and usually work at the end of the bundle generation process. Plugins can also modify how the bundles themselves are created.
When webpack processes your application, it internally builds a dependency graph from one or more entry points and then combines every module your project needs into one or more bundles, which are static assets to serve your content from.
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:
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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With