包看包会的《webpack-loader该怎么写》
前言
webpack5.x 更新已经一周年,到现在对于 webpack 的讨论总结起来依然就四个字 yyds,如果你还不知道写么开发一个loader,这篇文章值得你看完。文章对新手比较友好哦~ 其中各种描述很保姆化请谅解(笔者有一颗保姆心),有webpack配置基础的直接从 【loader怎么实现】开始看,
奠定webpack强大的能力依靠的是各种 loader 和 plugin。
什么是 loader
我们先用一个基础示例来解释它
-
创建一个 write-loader 文件夹
-
npm init -y 初始化
-
npm install webpack webpack-cli
创建如图的目录结构
在 src > index.js 中我们写上一小段代码
function add(a, b) {
return a + b
}
add(1, 2)
假设这段代码就是我们全部的项目代码,接下来就是把这份js引入到html文件了,但是项目上线考虑到节约服务器资源,我们希望上线之前将项目打包压缩,那么webpack的能力就发挥了
const path = require('path')
module.exports = {
mode: 'development',
entry: {
main: './src/index.js'
},
output: {
path: path.resolve(__dirname, 'dist'),
filename: 'main.js',
}
}
...(省略部分代码)
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1",
"dev": "webpack --config webpack.config.js"
},
最后执行 npm run dev 你会看到打包成功的,根目录下的dist文件夹安静的躺着 main.js。接下来直接把mian.js引入到index.html中即可看到打印
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
// 做好引入操作
<script src="./dist/main.js"></script>
</head>
<body>
<div id="app">hello world</div>
</body>
</html>
以上都是最基础的webpack的配置,本部应该出现在这个文章中(奈何笔者保姆心态改不掉)
回归正题什么是loader? 现在当我想要修改html中的 hello world 文本颜色时,在单页应用火热的如今,我们是这么写的css:
// src > style.css
#app{
font-size: 30px;
font-weight: bold;
color: red;
}
样式写好我们引入到 index.js 中:
// src > index.js
import './style.css' // 引入样式
function add(a, b) {
return a + b
}
add(1, 2)
然后再去执行打包操作,你会看到什么呢?
报错!!
解释一下,我们要知道 webpack 只能识别 js 语法,它是无法识别 css 语法的,所以 主角loader该现身了,我们需要一个 css-loader 用于帮助webpack解析css语法,style-loader 用于把解析后的css代码以style标签加载到html文件中。 执行 npm i css-loader style-loader
修改webpack的配置文件:
const path = require('path')
module.exports = {
mode: 'development',
entry: {
main: './src/index.js'
},
module: {
rules: [
{
test: /\.css$/, // +++正则匹配.css后缀的文件
use: ['style-loader', 'css-loader'] // 注意loader的执行顺序是从右往左的所以先解析再加入
}
]
},
output: {
path: path.resolve(__dirname, 'dist'),
filename: 'main.js',
}
}
再执行 npm run dev, 舒服了,你可以在打包出来的main.js中19行的位置看到我们的css
也即是说我们的css被打包到了js代码一起,那还能用嘛,去页面上看效果:
看到这了,之前不明白webpack loader是什么的同学应该已经明白了,loader就是帮助 webpack 去解析或者说执行 webpack自己做不到的文件
loader怎么实现
你已经知道loader是个什么角色了,还不够,我们还需要再深层次的了解 loader 的底层原理
1.loader的原理
webpack官方文档告诉我们以下几点:
1. loader 是一个函数
2. loader 函数必须要返回值string或者buffer
3. loader 的参数是当前匹配到的内容*
且 webpack loader的设计原则应该遵从:
1. 单一职责(表现为低耦合,loader复用性也更高)
2. 链式组合 (一个loader操作完的文件可以被另一个loader继续操作)
3. 模块化 (每一个loader都应该写成一个模块)
4. 无状态 (loader不应该具有自己的状态,想想react的无状态组件你会通透很多)
5. 结合loader-utils (后面解释)
6. loader 集成依赖
7. 代码公用
我们优先解释一下第六点 loader-utils 这对于写loader很重要,代码是最好的解释,在根目录下创建 loader-util.js 文件
function loader(content) {
console.log('进入到了loader内部');
return 'hello'
}
module.exports = loader
把它看做是我们写好的一个 loader, 用上它:
// webpack.config.js 添加代码
module: {
rules: [
(...省略部分代码)
{
test: /\.js$/,
use: [
{
loader: path.resolve('./loader-util.js'),
options: {
name: 'wn'
}
}
]
},
]
}
当webpack编译过程中匹配到js文件就会调用loader-util.js里面的函数loader,测试一下 npm run dev
果然没有问题,另外再添加一行打印:
function loader(content) {
console.log('进入到了loader内部');
console.log(content); // +++
return 'hello'
}
执行编译你会看到:
这可不就是我们在js文件中写的代码嘛!原来 3. loader 的参数是当前匹配到的内容 是这个意思!
但是我们在使用 loader 这个函数的时候配置了 options,这是webpack允许的写法,意在给 loader 函数传递参数,那么如何拿到调用loader时传递的参数,就要用到 loader-utils了,在 loader-util.js 中添加代码如下:
const loaderUtils = require('loader-utils') // +++
function loader(content) {
console.log('进入到了loader内部');
console.log(content);
console.log(loaderUtils.getOptions(this)); // +++
return 'hello'
}
module.exports = loader
getOptions是loader-utils提供的一个回去options参数的方法,你放佛明白了什么并迫不及待的执行了一遍打包:
好了你也知道loader-utils是什么样子的存在了, 其实它还提供了很多很实用的方法,感兴趣的小伙伴可以看这里 官方文档 loader-utils
你对loader的原理也就有一个大体的认知了,接下来就来实现一个laoder吧
2. loader的实现
那咱们拟定一个需求,比如说:我有这么一个需求,要实现 html文件打包,按照单一职责这个设计原则,这个需求应该包含两个功能
- html语法的解析
- html文件的压缩
也就是说这样一个需求应该设计成两个 loader 来完成,考虑到第一个功能解析html的语法loader实现过于复杂,我们就用一个node库已有的 html-loader 来完成,第二个功能我们来自己手动实现。
根目录下创建一个 html-minify-loader.js
// html-minify-loader.js
module.exports = function(source) {
console.log(source);
}
参数source将会编程匹配到的html代码,这一点大家都知道了吧。先用上这个loader吧,在src下新增 app.js, example.html两个文件
// example.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
example
</body>
</html>
// app.js
var html = require('./example.html')
console.log(html);
现在我们去修改配置文件
// webpack.config.js
const path = require('path')
module.exports = {
mode: 'development',
entry: {
main: './src/app.js' // 入口改成app.js
},
module: {
rules: [
{
test: /\.html$/,
use: [
'html-loader', // 这个需要 npm i html-loader 安装
{
loader: path.resolve('./html-minify-loader.js'), // 引入我们自己写的loader
options: {
comments: false // html-loader 中的不打包注释的配置
}
}
]
}
]
},
output: {
path: path.resolve(__dirname, 'dist'),
filename: 'main.js',
}
}
万事俱备,只欠我们的 html-minify-loader, 要干成压缩html这件事,原理就是合理的把html代码中的空格和换行干掉,这一点可以靠自己写正则完成,也可以懒一点,直接用个 minimize 轮子,哈哈哈哈,被你发现了,我们真的只是聊loader的实现原理,这个需求中遇到的其他难点都被我们用轮子化解了, npm i minimize 后
// html-minify-loader.js
var Minimize = require('minimize')
var loaderUtils = require('loader-utils')
module.exports = function(source) {
// console.log(source);
// 拿到配置的options参数
var options = loaderUtils.getOptions(this) || {}
// 把参数交给 minimize
var minimize = new Minimize(options);
// 返回minimize压缩之后的html代码
return minimize.parse(source);
}
就是这么简单,执行打包,在dist > main.js 中能找到example的html代码,并且是压缩过的
你又懂了一个大厂面试官常考问题,下次写项目碰到,可以自己试试写个laoder了!
参考
转载自:https://juejin.cn/post/7013350090945331237