Good question. I've done quite some homework in order to properly answer it. Here's what I found.
Normal Loader
The common understanding of webpack loaders, is that they are units chained-up to form a pipeline. Each loader processes the input source code, transforms it, then passes the result down to the next unit in the pipeline. And this process repeats until the last unit done its job.
But above is only part of the whole picture, only true for normal loaders. style-loader
is not a normal loader, because it also has a pitch method.
The pitch
Method of Loader
Note, there's no such thing as pitch loader, cus every loader can have a "normal side" and "pitch side".
Here's the not-very-helpful webpack doc on pitching loader. The most useful info is the concept of "pitch phase" and "normal phase" and their execution order.
|- a-loader `pitch`
|- b-loader `pitch`
|- c-loader `pitch`
|- requested module is picked up as a dependency
|- c-loader normal execution
|- b-loader normal execution
|- a-loader normal execution
You've seen style-loader
's source code, the export looks like:
module.exports = {}
module.exports.pitch = function loader(request) {
/* ... */
return [/* some string */].join('\n')
}
The only related part in the doc to above source code:
if a loader delivers a result in the pitch method the process turns around and skips the remaining loaders.
It's still quite unclear on how exactly this pitch thing works.
Digging Deeper
I finally came across this blog post (written in chinese tho) talks about the detail. Specifically, it analyses the exact case like in style-loader
where the pitch
method returns something.
As per the blog, the pitch
method is mainly used to access and modify metadata early in the loader process. Returning from pitch
method is indeed rare, and poorly documented. But when it does return sth other than undefined
, here's what happens:
# Normal execution order is disrupted.
|- style-loader `pitch` # <-- because this guy returns string early
# below steps are all canceled out
|- css-loader `pitch`
|- requested module is picked up as a dependency
|- css-loader normal execution
|- style-loader normal execution
Then the return value from styleLoader.pitch
just becomes a new in-memory file entry. This file is then loaded like a normal file and transformed using a brand new load process.
If you check, the content of this on-the-fly generated file from styleLoader.pitch
looks something like
var content = require("!!./node_modules/css-loader/dist/cjs.js??ref--8-3!./index.css");
You'll notice every require
request is fully configured using inline query. Thus these request won't go through any test
in webpackConfig.module.rules
.
Conclusion
Basically, this is what style-loader
does:
- it captures a request early by exposing a
pitch
method.
- it then understands what this request is about, read the config of all following-up loaders, transforms all config to inline-queried
require(...)
- it then issues a new file on-the-fly, and by doing this, the original request is effectively canceled then replaced by a new request to this in-memory file.
I don't know any better, all truth is held in the source code of loader-runner
module. If anyone has better ref sources or understanding, please comment, post an answer or edit mine.