webpack下编写loader和plugin
【前言】
本文的目的是对loader和plugin的编写有一个宏观的概念,具体的东西之后会增加。
我们都知道loader(就是一个函数)是将不同的文件资源转换为js(将整个文件作为字符串传给loader,然后返回一个JS),插件是通过在监听webpack生命周期中的不同钩子去做一些事情,赋予webpack一些额外的能力。
【本文目录】
1. loader
2. plugin
loader
举个例子:
项目目录:
QQ截图20200428155925.png我们的webpack配置文件可能是这样的:
//webpack.config.js
const path = require('path');
const HtmlWebpackPlugin = require('html-webpack-plugin');
const MyPlugin = require('my-plugin');
module.exports = {
entry: ['./src/index.js'],
output: {
filename: 'bundle.js',
path: path.resolve(__dirname, 'dist')
},
mode:"development",
module: {
rules: [
{
test: /\.txt$/,
exclude: /node_modules/,
use: [
{
loader: 'my-loader',
options: {
name: 'my-loader'
}
}
]
}
]
},
plugins: [
new HtmlWebpackPlugin()
new MyPlugin()
]
}
然后我们可以看到我对.txt
文件使用了我自己的loader(my-loader)
然后在当前的node_modeules下新建一个my-loader
文件夹(为了能是webpack直接能找到),并通过npm init --y 初始化并新建一个index.js
//index.js
import { getOptions } from 'loader-utils';
import validateOptions from 'schema-utils';
// import path from 'path';
// import memoryfs from 'memory-fs';
// import webpack from 'webpack'
const schema = {
type: 'object',
properties: {
name: {
type: 'string'
}
}
}
export default function loader(source) {
// let callback = this.async();
// const textpath = path.resolve(__dirname, 'test.txt');
// this.addDependency(textpath);
// fs
// const options = getOptions(this);
// fs.readFile(textpath, 'utf-8', function(err, data) {
// if(err) {
// return callback(err)
// }else {
// callback(null, `${data} \n ${source}`)
// }
// })
const options = loaderUtils.getOptions(this);
validateOptions(schema, options, 'Example loader')
source = {
content: source.replace(/\[name\]/g, options.name)
}
return `export default ${JSON.stringify(source)}`
}
这里看到我参照官网用了babel,如果你没用babel,就用require好了。其中getOptions就是去拿我们平常传给loader的option的,schema-utils使用来校验option是否合法的。参数source就是我们要处理的文件整个的字符串形式。最后的return就是我们返回的JS(字符串).最后webpack会调用eval或者Function去执行这些字符串的.
再来看下项目入口文件index.js的内容。
//index.js
const b = require('./test.txt').default
console.log('b', b.content)
最后在package.json下面新建一个script:"build":"webpack"
{
"name": "webpack-test",
"version": "1.0.0",
"description": "",
"private": true,
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1",
"build": "webpack"
},
"keywords": [],
"author": "",
"license": "ISC",
"devDependencies": {
"@babel/core": "^7.9.0",
"@babel/plugin-transform-runtime": "^7.9.0",
"@babel/polyfill": "^7.8.7",
"@babel/preset-env": "^7.9.5",
"@babel/runtime": "^7.9.2",
"babel-loader": "^8.1.0",
"css-loader": "^3.5.3",
"html-webpack-plugin": "^4.2.0",
"style-loader": "^1.2.0",
"webpack": "^4.43.0"
}
}
然后运行npm run build
最后用chrome打开dist目录下的index.html:
QQ截图20200428165115.png
看到了么?我将一个.txt文件转成了js,并最终使用!
plugin
plugin是一个构造函数,我们所有的工作就是在这个构造函数的原型上去定义一个一个apply方法,webpack调用插件的时候会去调用这个apply方法的。
同样的按照loader的形式我们在项目根目录下的node_modules
下新建一个my-plugin
的文件夹,并在里面初始化npm并新建一个index.js文件。
function MyPlugin () {
}
MyPlugin.prototype.apply = function (compiler) {
compiler.plugin('done', function() {
console.log('this is my plugin')
})
compiler.plugin('compilation', function(compilation) {
compilation.plugin('optimize', function() {
console.log("Assets are being optimized.");
})
})
compiler.plugin('emit', function(compilation, callbacks) {
let fileList = 'in this build: \n'
for (let fileName in compilation.assets) {
fileList += '-' + fileName + '\n'
}
compilation.assets['filelist.md'] = {
source: function() {
return fileList
},
size: function () {
return fileList.length
}
}
callbacks();
})
}
module.exports = MyPlugin;
看到上面我调用了一系列的钩子了么?在每个钩子里我们可以拿到一些资源并做一些事情,这就是plugin的大致的原理。
比如:
我在'emit'这个钩子中生成了一个filelist.md的文件,记录了最终在经过webpack打包后所有文件的文件名列表。运行完npm run build后看下dist目录下的filelist.md文件:
in this build:
-bundle.js
-index.html
看到没,我生成的2个文件都被记录了下来。