Loader 原理

针对上一篇文章我们的 bundler 只能加载 js,那么我们如何让它支持 css 那?

思路:
1). 我们的 bundler 只能加载 js
2). 我们想要加载 css
如果我们能把 css 变成 js, 那么就可以加载 css 了

1. 把 css 变成 js

复制 bundler_1.ts 为 bundler_css.ts

let code = readFileSync(filepath).toString()
if (/\.css$/.test(filepath)) {
    code = `
      const str = ${JSON.stringify(code)}
      export default str
    `
  }

问题:上面的代码把 body {color: red} 变成
const str = "body {color: red}"; export default str
有什么 X 用?body 里的文字并不会变红啊

1.1 改进

if (/\.css$/.test(filepath)) { // 如果文件路径以 .css 结尾
    code = `
      const str = ${JSON.stringify(code)}
+      if (document) {
+        const style = document.createElement('style')
+        style.innerHTML = str
+        document.head.appendChild(style)
+      }
      export default str
    `
  }

测试复制 project_1 为 project_css
新建一个style.css 文件,然后在index.js 里引入

  • style.css
body {
    color: red;
}

运行 node -r ts-node/register bundler_css.ts
然后直接在 dist 目录下创建 index.html 引入打包生成的文件

解决过程

  1. project_1 目录复制为 project_css
  2. bungler_1.ts 复制为 bundler_css.ts
  3. 改一下 bundler_css.ts
  4. 改一下 project_css 让 index.js 加载一个 css 文件
  5. 创建 index.html 测试 css 是否生效

2. 创建 css-loader

将上面的 code 代码抽离为单独的 loader

  1. 新建一个 loaders/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
  1. 复制一个 bundler_css.ts 并命名为 bundler_css_loader.ts
    将代码改为
if (/\.css$/.test(filepath)) {
    code = require('./loaders/css-loader.js')(code)
  }

2.1. 什么是 loader

1). loader 可以是一个普通函数

function transform(code) {
  const code2 = doSomething(code)
  return code2
}
module.exports = transform

2). loader 也可以是一个异步函数

async function transform(code) {
  const code2 = await doSomething(code)
  return code2
}
module.exports = transform

核心代码


为什么用 require 不用 import

  • 主要是为了方便动态加载

3. 优化 loader

单一职责原则
webpack 里每个 loader 只做一件事
而我们的 css-loader 做了两件事:
1). 把 css 变为 js 字符串
2). 把 js 字符串放到 style 标签里

将我们的 loader 拆成 2 个
新建一个 style-loader

  • loaders_2/style-loader.js
const transform = (code) => `
  if (document) {
    const style = document.createElement('style')
    style.innerHTML = ${JSON.stringify(code)}
    document.head.appendChild(style)
  }
  export default str
`
module.exports = transform
  • loaders_2/css-loader.js
const transform = (code) => `
  const str = ${JSON.stringify(code)}
  export default str
`
module.exports = transform
  • bundler_css_loader2.ts
if (/\.css$/.test(filepath)) {
    code = require('./loaders_2/css-loader.js')(code)
    code = require('./loaders_2/style-loader.js')(code)
  }

运行 node -r ts-node/register bundler_css_loader2.ts
打包编译的代码如下

style.innerHTML 里多了很多我们不需要的代码,实际上我们只想要 style.innerHTML = "{color: red}"

3.1. 问题分析

  • style-loader 不是转译
    sass-loader、less-loader 这些 loader 是把代码从一种语言转译为另一种
    因此将这样的 loader 连接起来不会出问题
    但 style-loader 是在插入代码,不是转译,所以需要寻找插入时机和插入位置
    插入代码的时机应该是在获取到 css-loader 的结果之后
    插入代码的位置应该是在就代码的下面
  • Webpack 官方 style-loader 的思路
    style-loader 在 pitch 钩子里通过 css-loader 来 require 文件内容
    然后在文件内容后面添加 injectStylesIntoStyleTag(content, ...) 代码

你可能感兴趣的:(Loader 原理)