Loader 原理
上文我们写了一个打包器,但是只能加载 JS 文件,现在我们尝试让他可以加载 CSS
如何加载 CSS
思路
- 我们的 bundle 只能加载 JS
- 我们想要加载 CSS
- 如果我们能把 CSS 变成 JS。那么就可以加载 CSS 了
1
2
3
4
5
6
7
8// 获取文件内容,将内容放至 depRelation
let code = readFileSync(filepath).toString()
if (/\.css$/.test(filepath)) {
code = `
const code = ${JSON.stringify(code)};
export default code;
`
}如此一来,我们的 CSS 文件就变成了 js文件,但是目前并没有用,CSS 并不会生效。
再加一个骚操作即可让 CSS 生效
1 | // 获取文件内容,将内容放至 depRelation |
完整代码
1 | import { parse } from "@babel/parser" |
让我们搞个页面试试代码就知道了
1 | // index.js |
1 | // index.css |
运行 node -r ts-node/register bundler_css.ts ,然后新建一个页面引入即可
1 |
|
到此我们已经成功加载一个 CSS 文件,但是我们没有使用 loader,我们目前是写死在打包器的。
创建 CSS loader
其实很简单
只需要创建文件 css-loader.js,并把代码复制过去即可
1 | // css-loader.js |
之前的代码变成引入的文件
1 | if (/\.css$/.test(filepath)) { |
为什么要用 require,因为很多 loader 的名字都是从配置文件中读取的,主要是为了方便动态加载
loader 长什么样子
- 一个loader 可以是普通函数
function transform(code){ const code2 = doSomething(code) return code } modules.exports = transform
1
2
3
4
5
6
7- 一个 loader 也可以是一个异步函数
- ```js
async function transform(code){
const code2 = await doSomething(code)
return code
}
modules.exports = transform
简单的 loader 搞定,开始优化
单一职责原则
- webpack 里每个 loader 只做一件事
- 目前我们的 css-loader 做了两件事
- 一是把 CSS 变为 JS 字符串
- 二是把 JS 字符串放到 style 标签里
不浮于表面,是 P6 的觉悟
如果你知道的东西跟别人差不多,很难进大公司
很显然我们只要把我们的 loader 拆成两个 loader 就可以了
1 | // css-loader |
1 | // style-loader |
1 | // bundle_css_loader_1 |
运行发现检查代码发现这是行不通的
经过 style-loader 转换过的代码,并不是我们想要的结果,说明我们的思路存在一些问题
分析
我的代码错在哪儿呢?
- style-loader 不是转译
- sass-loader、less-loader 这些 loader 是把代码从一种语言转译为另一种语言
- 因此将这样的 loader 连接起来不会出问题
- 但 style-loader 是在插入代码,不是转译,所以需要寻找插入时机和插入位置
- 插入代码的时机应该是在获取到 css-loader 的结果之后
- 插入代码的位置应该是在就代码的下面
目前缺乏一种机制可以让我们随意插入代码,而 webpack 是可以的,所以目前来说我们做不到–写不出 style-loader
- Webpack 官方 style-loader 的思路
- style-loader 在 pitch 钩子里通过 css-loader 来 require 文件内容
- 然后在文件内容后面添加 injectStyleIntoStyleTag(content, …) 代码
- 接下来看看 webpack 的核心代码在哪儿
- 并分析
阅读 style-loader 源码理解 webpack
- 不推荐这么做
- 直接看源码
- 应该这么做
- 不看源码,大胆假设
- 遇到问题,小心求证
- 带着问题看源码唯一正确的方式(我认为)
- 一定要自己先想一次
- 当你的思路无法满足需求的时候,去看别人的实现
- 看懂了,就悟了
全部折叠以后,代码结构十分清晰,首先他声明了一个 loaderApi 函数,然后添加了一个 pitch 函数(非常重要)
这个 style-loader 非常奇怪,本身竟然是一个空函数,所有的逻辑都在 pitch 函数里面。
由于我折叠了所有代码,逻辑结构得长清楚,首先获取所有的选项,然后验证这些选项,之后声明一些变量,最终会在一个 switch case 负责主要的逻辑
一般来说我们会把代码插入到 style 标签里面
返回的东西中,会判断是否是最新的模块系统,是的话就es6的,否则就用 nodeJS 的模块,所以我们只展开最新的
首先引入一个 api 函数,直接看后面的英文(’runtime/injectStylesIntoStyleTag.js’),我们可以很容易发现这个 api 函数就是一个把 style 插入 styleTag 的函数
其次是引入 content,很显然这就是要插入的 style 内容,
我们有了一个插入函数和插入内容,有了这两样东西,那就逻辑上来说就很简单了,直接一结合就是想要的结果
果然,把 content 和 options 传给 api 这个函数,这样之后页面就有想要的样式了,所以这个style-loader 核心代码就三块。
其他代码基本就是在做各种兼容
这个架构看起来好像和我们写的 style-loader 一样的,但是为什么我们就很难实现了,关键在于 webpack 的 style-loader 可以去加载这个request,但是我们没有这个 request 对象,
再来看看我们的问题
我们这个地方能写啥呢?我们拿不到内容,而webpack 比我们多传入了一个 request 对象,它可以拿到代码之外的东西。
webpack 到底有多少个 loader