前言
loader几乎是webpack配置必备,但是我们用它的时候有没有想过它的原理到底是怎么回事呢?今天就让我们来探究一下它的原理吧。
loader的作用
在探究原理之前,我们先来回顾一下常用的loader与及相关的作用。
如处理css文件的
style-loader, css-loader, sass-loader
module:{
rules: [
{
test: /\.css$/,
use: ['style-loader', 'css-loader', 'sass-loader']
}
]
}
loade执行顺序是从右往左执行的。至于为什么是从右到左执行而不是从左到右?因为webpack选择了compose这样的函数式编程方式,而从左到右则是pipe式编程。
所以上面例子的执行顺序是遇到.scss的文件就先经过sass-loader 处理再到 css-loader 再到style-loader,我们可以猜测到写的scss文件经过处理得到css字符串,再给css-loader序列化处理,最后给style-loader处理创建style标签插入到html
所以我们可以从这些现象就可以猜出来loader其实就像一个函数,有参数输入,经过处理,然后将处理好的结果输出。
可以打开webpack官网loader部分https://webpack.js.org/api/loaders/,这里详细指出了如何写一个loader。
如写一个 sync-loader.js
module.exports = function(content, map, meta) {
return someSyncOperation(content);
};
最后导出一个函数,这个函数有几个参数,其中主要用到的就是content这个参数,它表示读取到的对应的文件的源码内容,类型可以是string 也可以是buffer。我们就可以在这个方法里面对这些源码进行操作了。
laoder实战
弄清楚原理之后,我们自己手写3个loader,创建src/myLoaders目录
创建 my-sass-loader.js、 my-css-loader.js、 my-style-loader.js,
再配置好webpack.config.js如下
const path = require('path');
const HtmlWebpackPlugin = require('html-webpack-plugin')
const { CleanWebpackPlugin } = require('clean-webpack-plugin')
module.exports = {
mode: 'development',
entry: {
index: './src/index.js'
},
output: {
path: path.resolve(__dirname, 'dist'),
filename: '[name].js'
},
resolveLoader: {
modules: ['node_modules', './src/myLoaders'],
},
module: {
rules: [
{
test: /\.css$/,
use: ['style-loader', 'css-loader']
},
{
test: /\.scss$/,
use: ['my-style-loader', 'my-css-loader', 'my-sass-loader']
}
]
},
plugins: [
new HtmlWebpackPlugin(), // 输出html模板
new CleanWebpackPlugin(), // 清除dist目录
]
}
注意配置中配置了resolveLoader来自定义loader的存放目录的
resolveLoader: {
modules: ['node_modules', './src/myLoaders'],
}
和scss 的loader配置改为了我们自己写的loader
{
test: /\.scss$/,
use: ['my-style-loader', 'my-css-loader', 'my-sass-loader']
}
配置好之后,分别写这三个loader要做的事情,
首先是my-sass-loader, 利用sass的api对scss文件内容进行编译
const sass = require('sass')
module.exports = function(content) {
const result = sass.renderSync({
data: content
})
return result.css
}
其次是my-css-loader, 对编译后的文件进行序列化
module.exports = function(content) {
return JSON.stringify(content)
}
最后是my-style-loader, 对css字符串进行创建style标签并插入到head中
module.exports = function(content) {
return `
const style = document.createElement('style');
style.setAttribute('id', 'my-style-loader');
style.innerHTML = ${content};
document.head.appendChild(style);
`
}
为了查看效果,我额外加了一句 style.setAttribute('id', 'my-style-loader');
index.scss代码:
body {
background: red;
div {
background: blue;
color: #fff;
}
}
写完了,npm run dev
打开dist/index.html查看效果
ok, 没问题,自己实现了三个loader。有人问为什么要三个?一个不行吗?其实一个也可以的,但是这样子的写法不符合函数式编程的规范,不符合官方提倡的写法。官方要求一个loader只做一件事。这样子函数会更加纯粹,纯粹的函数对于可维护性、可扩展性、可重构性大大有利,这才是体现一个程序员的内涵的地方。否则你养成了经常写一坨偶合代码的习惯将会影响日后的成长。
如果要获取参数怎么办?
在loader函数内通过this.query获取;而且因为要使用this ,所以,导出的函数必须是module.exports = function(){}, 不能是箭头函数。
console.log(this.query)
如果要返回多个结果怎么办?
可以用 this.callback
this.callback(
err: Error | null,
content: string | Buffer,
sourceMap?: SourceMap,
meta?: any
);
如果要返回异步结果呢?
以上都是同步执行返回loader的处理的结果,同理,业务中可能会有异步处理的结果,就用this.async,如下
module.exports = function(content) {
const callback = this.async();
setTimeout(() => {
callback(content)
}, 1000)
}
总结
- loader就是一个必须有返回值(String | Buffer)的函数
- loader函数对源文件进行增删改处理,将处理好的结果返回给webpack。
- loader不能是箭头函数,因为内部可以采用this
- loader支持同步异步、返回结果
- loader的参数通过this.query获取
- resolveLoaders可以配置存放loader的路径
以上是我看法,如有不对请留言讨论,谢谢。