webpack-loader
基本工作流是将一个文件以字符串的形式读入,对其进行语法分析及转换,然后交由下一环节进行处理,所有载入的模块最终都会经过
moduleFactory
处理,转成JavaScript
可以识别和运行的代码,从而完成模块的集成。
loader分类与优先级
loader
可以被分为四类,分别是:后置post
,普通normal
,行内inline
,前置pre
。
四种
loader
调用先后顺序为:pre
>normal
>inline
>post
。
调用的优先级为,自下而上,自右向左。(pitch情况下,则反过来)
rules: [
{
test: /.xss$/,
use: [
{
loader: 'loader-1.js',
},
{
loader: 'loader-2.js',
},
],
},
{
test: /.xss$/,
use: [
{
loader: 'loader-3.js',
},
{
loader: 'loader-4.js',
},
],
enforce: "pre",
},
{
test: /.xss$/,
use: [
{
loader: 'loader-5.js',
},
{
loader: 'loader-6.js',
},
],
enforce: "post",
},
]
// loader-3-pitch
// loader-4-pitch
// loader-1-pitch
// loader-2-pitch
// loader-5-pitch
// loader-6-pitch
// loader-6
// loader-5
// loader-2
// loader-1
// loader-4
// loader-3
三种前缀语法:
- !:忽略
normal loader
- -!:忽略
pre loader
和normal loader
- !!:忽略所有
loader
(pre
/noraml
/post
)
以loader-1为
pre loader
,loader-2为normal loader
,loader-3为post loader
为例。
- 无前缀
import "../loader/loader-4.js!./loader.les"
loader-3-pitch
loader-4-pitch
loader-2-pitch
loader-1-pitch
loader-1
loader-2
loader-4
loader-3
- !前缀
import "!../loader/loader-4.js!./loader.les"
loader-3-pitch
loader-4-pitch
loader-1-pitch
loader-1
loader-4
loader-3
- -!前缀
import "-!../loader/loader-4.js!./loader.les"
loader-3-pitch
loader-4-pitch
loader-4
loader-3
- !!前缀
import "!!../loader/loader-4.js!./loader.les"
loader-4-pitch
loader-4
同步/异步 loader
loader
本质上是导出为函数的JavaScript
模块。loader runner
会调用此函数,然后将上一个loader
产生的结果或者资源文件传入进去。函数中的this
作为上下文会被webpack
填充,并且loader runner
中包含一些实用的方法
- 单个处理结果,可以在 同步模式 中直接返回
- 多个处理结果,须调用
this.callback()
- 异步模式 中,须调用
this.async()
/**
*
* @param {string|Buffer} content 源文件的内容
* @param {object} [map] 可以被 https://github.com/mozilla/source-map 使用的 SourceMap 数据
* @param {any} [meta] meta 数据,可以是任何内容
*/
function webpackLoader(content, map, meta) {
// 你的 webpack loader 代码
}
Pitching Loader
remainingRequest
:当前loader
右侧的所有loader
加上资源路径,根据!
分割,连接而成的内联loader
precedingRequest
:当前loader
左侧的所有loader
,根据!
分割,连接而成的内联loader
。data
:在pitch
阶段和normal
阶段之间共享的data
对象。即:pitch
阶段的参数data
和normal
阶段通过this.data
获取的data
为同一对象。
loader
总是 从右到左被调用。有些情况下,loader
只关心request
后面的 元数据(metadata
),并且忽略前一个loader
的结果。在实际(从右到左)执行loader
之前,会先 从左到右 调用loader
上的pitch
方法
为什么
loader
可以利用pitching
阶段呢?首先,传递给pitch
方法的data
,在执行阶段也会暴露在this.data
之下,并且可以用于在循环时,捕获并共享前面的信息
module.exports = function (content) {
return someSyncOperation(content, this.data.value);
};
module.exports.pitch = function (remainingRequest, precedingRequest, data) {
data.value = 42;
};
如果某个 loader 在 pitch 方法中给出一个结果,那么这个过程会回过身来,并跳过剩下的 loader。在我们上面的例子中,如果 b-loader 的 pitch 方法返回了一些东西
module.exports = function (content) {
return someSyncOperation(content);
};
module.exports.pitch = function (remainingRequest, precedingRequest, data) {
if (someCondition()) {
return (
'module.exports = require(' +
JSON.stringify('-!' + remainingRequest) +
');'
);
}
};
详解 style-loader 和 css-loader
上面讲了如何写一个简单的
loader
,那么如果我们需要加载css
,应该怎么实现呢?我们一般css
的loader
都是这么配置的:
{
test: /\.css$/,
use: [
{ loader: "style-loader" },
{ loader: "css-loader" }
]
}
即我们的CSS代码会先被
css-loader
处理一次,然后再交给style-loader
进行处理。那么这两步分别是做什么呢?
css-loader
的作用是处理css中的@import
和url
这样的外部资源style-loader
的作用是把样式插入到DOM
中,方法是在head
中插入一个style
标签,并把样式写入到这个标签的innerHTML
里
画一张示意图来表示:
上面的处理过程即
webpack
建议一个loader
只做一件事。我们对图片等资源的处理和把样式插入到DOM
分为两个任务,这样每个loader
做的事情就比较简单,而且可以通过不同的组合实现更高级的功能
style-loader 原理解析
style-loader
的主要作用就是把 CSS 代码插入DOM中,我们看看他的代码。style-loader 的主要一段代码如下所示:
`${esModule ? `import api from ${_loaderUtils.default.stringifyRequest(this, `!${_path.defa> ult.join(__dirname, 'runtime/injectStylesIntoStyleTag.js')}`)};
import content${namedExport ? ', * as locals' : ''} from ${_loaderUtils.default. stringifyRequest(this, `!!${request}`)};` : `var api = require(${_loaderUtils.default.stringifyRequest(this, `!${_path.default.join(__dirname, 'runtime/injectStylesIntoStyleTag.js')} `)});
var content = require(${_loaderUtils.default.stringifyRequest(this, `!!${request}`)});
content = content.__esModule ? content.default : content;`}
var options = ${JSON.stringify(runtimeOptions)};
options.insert = ${insert};
options.singleton = ${isSingleton};
var update = api(content, options);
${hmrCode}
${esModule ? namedExport ? `export * from ${_loaderUtils.default.stringifyRequest(this, `!! ${request}`)};` : 'export default content.locals || {};' : 'module.exp> orts = content.locals || {};'}`;
它其实就是返回了一段JS代码,这段代码主要就分为两步:
- 通过
require
来获取css文件的内容,得到是一个字符串 - 调用
applyToSingletonTag
把css内容插入到 DOM中去
css-loader原理
style.css
@import './global.css';
h1 {
color: #0080ff;
}
.icon {
width: 100px;
height: 100px;
background-image: url('./icon.jpg');
background-size: contain;
}
global.css
body {
background-color: #ff0000;
}
css-loader
会执行两次,因为有两个文件,每一个css
文件的处理,比如style.css
的处理会包括两部分:
- 把
@import
替换成require("-!../node_modules/css-loader/index.js!./global.css")
,-!`` 语法会禁用掉
preLoader和
loader,因此这一个
require`并不会递归调用自己。 - 把
background-image: url('./icon.jpg’)
替换成"background-image: url(" + require("./icon.jpg") + “)"
,图片的处理比较简单,因为如何加载图片,这是另一个loader
的事情,css-loader
不负责这部分,他只需要关心把url
转换成require
就可以了。 简单的概括,就是把这两种css
内部的依赖,替换成JS
的语法require
,这样webpack
就能正确处理了。
css-loader
转译后输出的代码:
// Imports
import ___CSS_LOADER_API_SOURCEMAP_IMPORT___ from "../node_modules/css-loader/dist/runtime/cssWithMappingToString.js";
import ___CSS_LOADER_API_IMPORT___ from "../node_modules/css-loader/dist/runtime/api.js";
var ___CSS_LOADER_EXPORT___ = ___CSS_LOADER_API_IMPORT___(___CSS_LOADER_API_SOURCEMAP_IMPORT___);
// Module
___CSS_LOADER_EXPORT___.push([module.id, ".foo-baz {\n font-weight: bold;\n}\n.bar {\n > background: #ff0000;\n}", "",{"version":3,"sources":["webpack://./src/index.css"],"names":[],"mappings":"AAAA;IACI,iBAAiB;AACrB;AACA;IACI,mBAAmB;AACvB","sourcesContent":[".foo-baz {\n font-weight: bold;\n}\n.bar {\n background: #ff0000;\n}"],"sourceRoot":""}]);
// Exports
export default ___CSS_LOADER_EXPORT___;