下面手动实现一个简单的 Webpack Loader。
搭建项目#
先创建一个 Webpack 项目
mkdir make-loader
cd make-loader
yarn init -y
yarn add webpack webpack-cli -D在根目录中创建 src 文件夹,并在里面中创建一个index.js文件,编写下面代码
console.log("Hello Webpack");创建 Webpack 配置文件webpack.config.js
const path = require("path");
module.exports = {
mode: "development",
entry: path.resolve(__dirname, "src/index"),
output: {
path: path.resolve(__dirname, "dist"),
filename: "[name].js",
},
};在package.json中编写打包脚本命令
"scripts": {
"build": "webpack"
}尝试进行编译打包
yarn build查看根目录下 dist 文件夹里面的main.js文件,这是打包生成的文件,在文件倒数几行里发现下面的代码,编译成功。
eval(
"console.log('Hello Webpack')\n\n//# sourceURL=webpack:///./src/index.js?"
);编写 Loader#
简单实现#
下面开始编写 Loader,其实 Loader 就是一个普通函数。
在根目录下创建loaders文件夹,然后创建一个replaceLoader.js文件,编写下面代码
module.export = function (source) {
return source.replace("Webpack", "Hale");
};这个 Loader 函数非常简单,接受源文件source作为参数,然后把源文件中的Webpack替换成Hale,并且返回替换后的值。
这里有两点需要注意。
- Loader 文件导出的模块只能是一个普通的函数,不能是箭头函数,因为后面还需要在普通函数中使用
this对象,箭头函数的this指向的函数外面的作用域。 - Loader 函数必须返回一个字符串类型或者
Buffer类型的值。
下面把我们编写的 Loader 引入到配置文件中
module.exports = {
// ...
module: {
rules: [
{
test: /\.js$/,
use: path.resolve(__dirname, "./loaders/replaceLoader.js"),
},
],
},
};重新进行编译打包
yarn build查看打包后的文件,在文件倒数几行里发现下面的代码,说明我们编写的 Loader 解析文件成功。
eval("console.log('Hello Hale')\n\n//# sourceURL=webpack:///./src/index.js?");选项设置#
我们在使用 Webpack 的 Loader 时,有时候会传入options的参数来定制一些操作。比如
module.exports = {
module: {
rules: [
{
test: /\.js$/,
use: {
loader: path.resolve(__dirname, "./loaders/replaceLoader.js"),
options: {
name: "Hale",
},
},
},
],
},
};那么 Loader 是如何接受options传入的参数的呢?是给 Loader 函数传入第二个参数options吗?
答案是否定的,是通过this.query这样属性来获取参数的。
下面修改下 Loader 函数,通过this.query获取到name的值
module.export = function (source) {
// return source.replace('Webpack', 'Hale')
return source.replace("Webpack", this.query.name);
};然后重新进行编译打包
yarn build查看打包后的文件,在文件倒数几行里发现下面的代码,说明修改后的 Loader 解析文件成功。
eval("console.log('Hello Hale')\n\n//# sourceURL=webpack:///./src/index.js?");callback 接口#
Webpack 给 Loader 函数提供了很多的 API,大部分都是通过this.xxx来调用的。
下面介绍下this.callback这个 API,它接受四个参数,分别是err、content、sourceMap和meta,使用this.callback可以抛出错误或者查看sourceMap。
this.callback(
err: Error | null, // 可能出现的错误
content: string | Buffer, // 输出的内容
sourceMap?: SourceMap, // sourceMap
meta?: any // 其他配置
);下面使用下这个 API
module.export = function (source) {
const result = source.replace("Webpack", this.query.name);
this.callback(null, result);
};重新进行编译打包,编译成功。
异步函数#
有时候在 Loader 中可能需要编写异步代码,比如使用定时器setTimeout。先尝试使用编写下面代码
module.export = function (source) {
const result = source.replace("Webpack", this.query.name);
setTimeout(() => {
this.callback(null, result);
}, 3000);
};重新进行编译打包,发现编译失败。这时候需要借助另外一个 API this.async来使用异步函数。
module.export = function (source) {
const result = source.replace("Webpack", this.query.name);
const callback = this.async();
setTimeout(() => {
callback(null, result);
}, 3000);
};重新进行编译打包,编译成功。
多个 Loader#
我们知道对于同一个类型的文件,可以使用多个 Loader 进行编译解析,而且 Loader 的调用顺序是从后到前、从右到左的,比如某个文件解析需要两个 Loader,它们的排序是[aLoader, bLoader],那么 Webpack 会先调用b Loader解析文件,然后再调用aLoader继续解析文件。
把现在带异步函数的 Loader 文件重新命名为replaceLoaderAsync.js,然后重新创建一个replaceLoader.js文件,编写下面代码
module.exports = function (source) {
return source.replace("Hale", "World");
};现在loaders文件夹下面就有两个自定义的 Loader 了,把它们都引入到 webpack 配置文件中
module.exports = {
// ...
module: {
rules: [
{
test: /\.js$/,
use: [
{
loader: path.resolve(__dirname, "./loaders/replaceLoader.js"),
},
{
loader: path.resolve(__dirname, "./loaders/replaceLoaderAsync.js"),
options: {
name: "Hale",
},
},
],
},
],
},
};然后重新进行编译打包
yarn build查看打包后的文件,在文件倒数几行里发现下面的代码,说明我们编写的两个 Loader 都生效了。
eval("console.log('Hello World')\n\n//# sourceURL=webpack:///./src/index.js?");分析:replaceLoaderAsyncLoader 排在后面,Webpack 会先调用它,把字符串Webpack替换成Hale,然后再调用replaceLoaderLoader,把字符串Hale替换成World。
Loader 配置#
我们在配置第三方的 Loader 的时候,一般都不会导入一段的很长的路径,而是只填写 Loader 的名称即可。
自定义的 Loader 也能这样配置 Loader 吗?答案是肯定的,需要借助resolveLoader选项。
module.exports = {
// ...
resolveLoader: {
modules: ["node_modules", "./loaders"],
},
module: {
rules: [
{
test: /\.js$/,
use: [
{
loader: "replaceLoader",
},
{
loader: "replaceLoaderAsync",
options: {
name: "Deng",
},
},
],
},
],
},
};我们resolveLoader选项,在modules选项中填入node_modules和./loaders,这样 Webpack 就会先在node_modules中查找 Loader,如果找不到,再去./loaders文件夹中查找 Loader。
现在,重新进行编译打包,编译成功。
小结#
上面编写的 Loader 只是实现最简单的功能,让大家对 Loader 有一个简单的认识,其实 Loader 远不止这些功能,可以查看它的相关文档进行详细了解。