theme: awesome-green
webpack 核心之 loader原理
webpack 只能理解 JavaScript 和 JSON 文件,这是 webpack 开箱可用的自带能力。loader 让 webpack 能够去处理其他类型的文件,并将它们转换为有效 模块,以供应用程序使用,以及被添加到依赖图中。
手写一个loader
手写一个loader之前需要知道,loader 是导出为一个函数的 node 模块。该函数在 loader 转换资源的时候调用。
loader可以是一个同步函数,也可以是一个异步函数
// css-loader.js
const transform = code => `
const str = ${JSON.stringify(code)}
if(document){
const style = document.createElement('style')
style.innerHTML = str
document.head.appendChild(style)
}
export default str
`
module.exports = transform
然后在打包器代码中引入,这里有打包器的具体实现
// 核心部分
// 获取文件内容,将内容放至 depRelation
let code = readFileSync(filepath).toString()
if(/\.css$/.test(filepath)){ // 如果文件路径以 .css 结尾
code = require('./loaders/css-loader.js')(code)
}
const { code: es5Code } = babel.transform(code, {
presets: ['@babel/preset-env']
})
这样有点违背webpack loader单一职责原则,目前手写的 css-loader 做了两件事
- 一是把 CSS 变为 JS 字符串
- 二是把 JS 字符串放到 style 标签里
如何解决呢?
新建两个文件不久能解决了吗?
// css-loader
const transform = code => `
const str = ${JSON.stringify(code)}
export default str
`
module.exports = transform
// style-loader
const transform = code => `
if(document){
const style = document.createElement('style')
style.innerHTML = ${JSON.stringify(code)}
document.head.appendChild(style)
}
`
module.exports = transform
// 打包器核心代码
if(/\.css$/.test(filepath)){ // 如果文件路径以 .css 结尾
code = require('./loaders/css-loader.js')(code)
code = require('./loaders/style-loader.js')(code)
}
打包部分代码讲解
exports["default"] = _default;
}
},{
key: "style.css",
deps: [],
code: function(require, module, exports){
"use strict";
if (document) {
var style = document.createElement('style');
style.innerHTML = "\nconst str = \"body {\n color: red;\n}\n\"\nexport default str\n";
document.head.appendChild(style);
}
}
}];
现在发现了一些问题,打包出来的代码出现了多余代码\\n\"\nexport default str\n
, 也可以推断出我上面手写loader
的代码时有问题的
于是我决定去分析 webpack 的 loader
webpack的style-loader
是这样实现
style-loader 在 pitch 钩子里通过 css-loader 来 require 文件内容
然后在文件内容后面添加 injectStylesIntoStyleTag(content, …) 代码
style-loader
源码阅读
这是我经过vscode
折叠代码之后的样子,发现就是通过pitch
钩子实现下面一系列操作
这个styleTag
的case是我需要看的,其核心就是最后的API方法,把你要插入的内容传入,它会帮你实现各种插入兼容更多功能的代码
在 webpack 5 之前,通常使用:
raw-loader
将文件导入为字符串url-loader
将文件作为 data URI 内联到 bundle 中file-loader
将文件发送到输出目录
raw-loader源码
第一,第二行可以通过名字看出作用;现在需要看一下第三行代码
这个就是第三行代码引入的内容, 解释
"additionalProperties": false, // 不能随意给loader加上随意的属性
"properties": {
"esModule": { // 可以通过这个选项切换是否支持esModule的语法
"type": "boolean"
}
},
"type": "object" // 无需搞懂
对于上面源码还有两行是比较特别的
const json = JSON.stringify(source)
.replace(/\u2028/g, '\u2028')
.replace(/\u2029/g, '\u2029');
这是JSON.stringif()的一个bug
如何想深入了解\u2028和\u2029
对上面的源码进行总结
- webpack 提供 loader-utils 和 schema-utils 作为辅助工具
- webpack 通过 this 来传递上下文
- getOptions(this) 可以获取 options
- validate 可以验证 options 是否合法
css-loader
源码解析
最终是通过callback处理,而不是通过return,这也说明css-loader
是异步的,也验证了开头所说的 loader可以是一个同步函数,也可以是一个异步函数
里面一大堆是不需要看的,核心代码如下
getModuleCode 如下:
感觉上面箭头的代码(核心代码)也无需解释了,一看就懂了。
暂无评论内容