Byeongin Yoon
Byeongin Yoon

Reputation: 4077

How style-loader works with css-loader?

I know that style-loader adds CSS to a dom by injecting a tag. And css-loader gets css as string when it meets require('./style.css');.

But, How style-loader plays with css-loader??

I'm reading style-loader source code and css-loader source code. But I cannot understand how they play together.

How the css string which css-loader gets from style.css is passed to style-loader?

Upvotes: 7

Views: 1368

Answers (1)

hackape
hackape

Reputation: 19997

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:

  1. it captures a request early by exposing a pitch method.
  2. it then understands what this request is about, read the config of all following-up loaders, transforms all config to inline-queried require(...)
  3. 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.

Upvotes: 13

Related Questions