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加上资源路径,根据!分割,连接而成的内联loaderprecedingRequest:当前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___;