使用webpack来开发一些自定义的loader,可以让我们更加深刻的认识和了解webpack的工作机制是怎么样的。
每一个loader都是一个函数,该函数在loader转换资源的时候调用。在该函数中可以使用this来访问上下文环境并可以使用一些 loader API, 比如 this.version表loader api的版本号、 this.context表模块所在目录、this.request表被解析出来的erquest字符串、this.query获取配置option字段…等等
一般loader开发都会遵循一些原则,比如单一性即每个loader只做单一功能的事情,对于复杂功能应该使用多个loader相互组合的方式来实现。比如我们在开发过程中一般使用 less or scss来写页面的样式。但最终渲染到浏览器中的是css,从less/scss到css至少需要less-load将less编译为css,css-loader将css中的 @import 和 url(’./././)中的相对路径改为 require()的格式, style-loader: 将最后的样式使用style标签插入到页面head中
经过三个loader的配合使用最终将我们less/scss转化了浏览器可识别的css. 这样做的好处就是将功能隔离,代码解耦,可以按照所需功能来自由使用所需loader。一般开发loader需要遵循下面这些主要的原则:
我们可以在配置文件中的 module字段之下的rule中添加匹配文件规则,然后对匹配正确的文件使用那些loader,这个套路和我们正常使用loader基本类似,只不过如果在开发阶段loader的路径需要设置为本地目录一般使用 path.resolve,一般长这个样子:
module.exports = {
//...
module: {
rules: [
{
test: /\.css$/,
use: [
{
loader: path.resolve('path/to/loader.js'),
options: {/* ... */}
}
]
}
]
}
};
如果需要执行多个loader那可以在use数组中追加其他loader即可,需要注意配置的loader执行的顺序也是从右到左,从下到上。
按照同异步可以将loader区分为同步loader和异步loader。如果是单个处理结果,可以直接使用同步模式处理直接返回,但如果需要多个结果处理,就必须在异步loader中使用 this.async()告诉当前的上下文context,这是一个异步的loader,需要loader runner 等待异步处理的结果,在异步处理完之后再调用this.callback()传递给下一个loader执行。
这里实现一个小功能,即自己写一个less-laoder和style-loader。将编写的less经过这两个loader处理之后使用style标签插入在页面的head标签内。并在style标签中自定义一个data-origin属性,用来标记这个样式是来自于哪个文件的,以往我做过一些项目都是将很多文件的样式插入在head标签中,可是有的时候需要调试样式就不知道这个样式来自于哪个文件,这里我们在处理的时候会把文件的来源标记在style标签中。
这里新建一个webpack 4.0以上的工程,配置完基本的出入口字段之后,新建一个或者多个less文件,并被入口文件引入。
比如这里在工程根目录src文件夹下新建了一个index.less的文件写入简单样式如下:
@red:red;
@yellow:yellow;
@baseSize:20px;
body{
background-color: @red;
color:@yellow;
font-size: @baseSize;
}
上面是一写简单的定义body标签的样式。
在本地新建一个loader文件夹用于存放我们自定义loader文件。
按照上面介绍过的在开发阶段可以在webpack.config.js文件中按照下面的方式配置来测试我们写的loader。
module.exports = {
mode: 'development',
entry: './src/index.js',
output: {
filename: "bundle.js",
path: path.resolve(__dirname, 'dist')
},
module:{
rules:[
{
test: /\.less$/,
use:[
{
loader:path.resolve('./loader/style-loader.js'),
},
{
loader:path.resolve('./loader/less-loader.js'),
}
]
},
]
};
异步loader需要有几个注意的2点如下:
下面看我们自己实现的一个简易版本的less-loader。用于将编写的less编译为css并传递给下一个style-loader。
在该文件夹下新建一个less-loader.js文件,用于处理less文件。代码如下:
let less = require('less');
function loader(source) {
const callback = this.async();
less.render(source)
.then((output)=>{
callback(null, output.css);
}, (err)=>{
// handler err
})
}
module.exports = loader;
这里处理的业务很简单,就是拿到原始的less文件内容,将less通过less.redner编译为css并传递给下一个loader即可。
关键的几句代码如下:
遵循简单原则,即一个loader只做一个事情,上面进过less-loader将编写的less编译为css并传递过来之后,我们在这个loader中做的事情就是将接受到的css文件使用style标签包裹起来并加入一个自定义属性data-origin标记这个文件的来源插入入口文件的head内。
在loader文件夹下新建style-loader.js 代码如下:
// webpack 自定义loader
function loader(source) {
source = JSON.stringify(source);
const root = process.cwd();
const resourcePath = this.resource;
const origin = resourcePath.replace(root, '');
let style = `
let style = document.createElement('style');
style.innerHTML = ${source};
style.setAttribute('data-origin', '${origin}');
document.head.appendChild(style);
`;
return style
}
关键代码如下:
第五点是因为使用webpack打包之后其实是会将文件结果处理为一段可执行的js字符串再被eval包裹,最后入口页面会引入这个bundle.js的时候会执行这段可执行的js字符串。
大概长得像这个样子:
编写完逻辑之后,即可在控制台执行
npx webpack
webpack会根据webpack.config.js的配置文件来对工程进行打包。
我们编写的less文件也会经过我们自定义编写的less-laoder 和 style-loader处理完之后插入在入口文件中,效果如下:
我们可以看到打包之后的入口文件中插入了我们写的less样式,并且在每个style标签中都有一个data-origin自定义属性来标记原文件在工程中的相对地址。
查看在线代码