现代前端在开发过程中时常都会使用到webpack,用来对代码进行模块化打包。通常情况下,我们都是直接使用webpack的配置和别人写好的loader。如果自己要实现一个loader,需要怎么做?
1. 什么是loader?
webpack是基于node的模块化打包工具,它本身也只能处理JS和JSON文件,没有处理CSS、图片等其他格式文件的能力。loader就相当于是一个翻译机,将这些文件翻译成webpack能处理的格式。换句话说,loader赋予了webpack处理其他文件的能力。
2. loader的使用
module.exports = {
//...
module: {
rules: [
{
test: /\.scss$/,
use: ['style-loader', 'css-loader', {
loader: 'sass-loader',
options: {
//...
}
}]
}
]
}
};
- 职责单一: 每个loader只完成一种转换,如sass-loader只讲sass转换为css。
- 链式调用: 第一个loader接收到的是资源文件的内容,后续loader都是接收到的上一个loader返回的处理结果。
- 调用顺序: 在loader中存在一个pitch属性,调用顺序是
style-loader(pitch)、css-loader(pitch)、sass-loader(pitch)、sass-loader、css-loader、style-loader
。如果其中一个pitch返回了值,则停止后续调用。简单点说就是先从左往右执行pitch,然后从右往左执行loader。
3. 一个基础的loader
上面写了一堆乱七糟八的东西,看起来着实枯燥。现在就写一个一讲自定义loader就会写的例子,就和写代码必先写hello world一样的东西。
一个用于替换的loader。替换原来JS代码中的NAME。
webpack.config.js
const path = require('path');
// 先看看怎么用
module.exports = {
//...
module: {
rules: [
{
test: /\.js$/,
use: [{
// 本地引用loader
loader: path.resolve('./replace-loader'),
options: {
// 通过配置传入words来替换NAME为wei
words: 'wei'
}
}]
}
]
}
};
replace-loader.js
// loader-utils是专门用于自定义loader时的一些工具函数
const { getOptions } = require('loader-utils');
module.exports = function(source) {
const options = getOptions(this); // getOptions用于获取配置
return source.replace(/NAME/g, options.words);
}
如上,一个简单的loader就完成了。
3. mini版的style-loader
只写一个replace-loader好像啥也没有写一样,不写个todo list怎么能体现出我能自己写loader了。
const { stringifyRequest, getOptions } = require('loader-utils');
function loader(source) {}
// 这里使用pitch是因为按照正常顺序在css-loader调用之后调用style-loader的话,style-loader接收到的就是一堆代码字符串
// 为了避免这种问题,所以需要在css-loader执行之前执行style-loader
loader.pitch = function(remainingRequest, precedingRequest, data) {
const options = getOptions(this);
// 使用JSON字符串化属性
// 作为变量来拼接字符串
const attributes = JSON.stringify(options.attributes || {});
const insert = options.insert === undefined
? '"head"'
: typeof options.insert === 'string' ? JSON.stringify(options.insert) : options.insert.toString();
// 标准化路径
const request = stringifyRequest(this, '!!' + remainingRequest);
return `
var style = document.createElement('style');
var content = require(${ request }); // 相当于是require(css-loader!resource),返回的就是css-loader处理之后的内容
var attributes = ${ attributes };
var insert = ${ insert };
// 遍历设置属性
for (var key in attributes) {
style.setAttribute(key, attributes[key]);
}
content = content.__esModule ? content.default : content;
style.innerHTML = content;
var insertElement;
if (typeof insert === 'string') {
var insertElement = document.querySelector(insert);
insertElement && insertElement.appendChild(style);
} else {
insert(style);
}
`;
}
module.exports = loader;
4. mini版的sass-loader
const sass = require('node-sass');
const { getOptions } = require('loader-utils');
module.exports = function(source) {
const options = getOptions(this);
const sassOptions = options.sassOptions || {};
const result = sass.renderSync({
data: source,
...sassOptions
});
return result.css;
};
loader相关的知识还有很多,例如异步(this.async())
、处理二进制数据(loader.raw = true)
、禁用缓存(this.cacheable(false))
等等。在实际开发的时候可以再查阅相关的文档。
更多:
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。