【javascript】手写一个webpack loder
手写一个loader
为什么需要loader?
webpack 实际上只能处理js文件,那么对于除了js文件的其他类型的文件 比如 css sass 等。。我们不能直接用webpack来处理。
我们需要一个翻译员(loader)来帮我们的文件处理一下。有时候我们不只需要一个翻译员来工作,比如要把文言文翻译成外语,首先要转换成白话文,然后转换为外语。
Loader就像一个翻译员,能将源文件经过转化后输出新的结果,并且一个文件还可以链式的经过多个翻译员翻译。
以scss文件为例:
先将scss源代码交给sass-loader,将scss转换成css;
将sass-loader输出的css提交给css-loader处理,找出css中依赖的资源,压缩css;
将css-loader输出的css提交给style-loader处理,转换成通过脚本加载的javascript代码。
最终的结果一定是javascript代码。
编写loader的原则
职责单一: 一个loader只做一件事情。优点:容易维护且能够链式调用
模块化:
保证输出模块化,loader生成的模块与普通块遵循的相同的设计原则
无状态:
确保loader在不同模块转换之间,不保存状态。每次运行都应该独立于其他编译模块以及相同模块之前的编译结果。
动手写一个基础loader
webpack运行在Node上,一个loader其实就是一个node模块,这个模块需要导出一个函数。这个导出的函数输入的就是处理前的原内容,对内容处理之后 然后输出被处理过的内容。
我们现在写一个简单的javascript loader,这个loader的作用就是将js文件内容里面的字符串 like 转换成 ❤ 。
直接上代码,我们将这个loader命名为k-loader.js
module.exports = function (source) {
const regExp = new RegExp("like", "ig")
const result = source.replace(regExp, "❤")
return result
}
怎么调试我们的loader,我们使用官方推荐的一种:
module.exports = {
module: {
rules: [
{
test: /\.js$/,
use: [
{
loader: path.resolve('path/to/loader.js'),
options: {/* ... */}
}
]
}
]
}
};
定义好loader然后找到该loader相对于webpack配置文件的路径即可。
我们看k-loader.js 做了什么,获取到source,source就是加载的js文件的文件内容,然后做了一次数据清洗,将like全部转换成❤,然后返回字符串。 完事儿!这就ok了,你可以做一个测试,你会发现js文件里面的like全部被替换成了❤,是不是很简单。。是的。
基础loader上增加一些配置
我们一般的loader是有option配置的,就是说用户需要自定义一些功能。
我们把这个like 和 ❤,配置在option里面,这样的话,就可以灵活的使用了,我们只需要改like 和 ❤的值即可。我们使用到了webpack提供的辅助模块loder-util
,我们通过getOPtions(this)方法可以获取到webpack配置loader的时候传递的option值。
代码长这样:
const loaderUtils = require("loader-utils")
module.exports = function (source) {
const options = loaderUtils.getOptions(this) || {}
// 假定option只有一个key value
// 这里应该对option做一次验证。
const key = Object.keys(options)[0]
const value = options[key]
const regExp = new RegExp(key, "ig")
const result = source.replace(regExp, value)
return result
}
这样我们将options的值配置成{ like: "❤" }
就会和我们上面的hard code 达到一样的效果,我们将options改成{like: "love"}
这样就会把所有js文件里面的like替换成love。
到这一步,我们就已经可以自己编写一个loader了。
返回多个值
我们上面写的loader只是返回了一个值,在一些特殊的场景我们需要用this.callback()返回多个值。
以用babel-loader转换ES6代码为例,它需要输出转换后的ES5代码对应的SourceMap 以方便调试源码。 为了将Source Map也一起随着ES5代码返回给Webpack,还可以这样写:
module.exports = function(source) {
this.callback(null, source, sourceMaps)
return; // 当调用 callback() 时总是返回 undefined
}
this.callback API长下面的样子:
this.callback(
err: Error | null,
content: string | Buffer,
sourceMap?: SourceMap,
meta?: any
);
同步或异步
loader有同步和异步之分,这时候我们需要使用this.async()
来获取callback
函数。
module.exports = function(content, map, meta) {
var callback = this.async();
someAsyncOperation(content, function(err, result) {
if (err) return callback(err);
callback(null, result, map, meta);
});
};
参考:
完整demo 见这里:
https://github.com/aeolusheath/webpack-certain-features/tree/master/write-loader-diy