webpack 核心之 loader原理


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的代码时有问题的

于是我决定去分析 webpackloader

webpack的style-loader是这样实现

style-loaderpitch 钩子里通过 css-loader 来 require 文件内容
然后在文件内容后面添加 injectStylesIntoStyleTag(content, …) 代码

style-loader源码阅读

5.jpg
这是我经过vscode折叠代码之后的样子,发现就是通过pitch钩子实现下面一系列操作
6.jpg
这个styleTag的case是我需要看的,其核心就是最后的API方法,把你要插入的内容传入,它会帮你实现各种插入兼容更多功能的代码

在 webpack 5 之前,通常使用:

raw-loader源码

7.jpg
第一,第二行可以通过名字看出作用;现在需要看一下第三行代码

8.jpg
这个就是第三行代码引入的内容, 解释

"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源码解析

9.jpg

10.jpg
最终是通过callback处理,而不是通过return,这也说明css-loader是异步的,也验证了开头所说的 loader可以是一个同步函数,也可以是一个异步函数

里面一大堆是不需要看的,核心代码如下
11.jpg
getModuleCode 如下:
12.jpg
感觉上面箭头的代码(核心代码)也无需解释了,一看就懂了。

© 版权声明
THE END
喜欢就支持一下吧
点赞6 分享
评论 抢沙发
头像
欢迎您留下宝贵的见解!
提交
头像

昵称

取消
昵称表情代码图片

    暂无评论内容