webpack构建/编译过程中有一些生命周期,webpack插件开发就是你可以调用这些生命周期的钩子,在某些生命周期去执行你需要的操作,例如生成文件,修改文件内容,删除文件。
生命周期的类型有tap,tapAsync 和 tapPromise,同步、异步钩子,具体的看官方文档。
插件开发的步骤官方文档也写了比较清楚的解释,现在我这里做一个案例,在umi脚手架中去实现自己的webpack插件。
插件功能:把某个文件夹内的文件路径,转换成json格式文件。
- 新建plugins文件夹,里面新建source-path-plugin.js文件
- source-path-plugin.js文件里面写一个SourcePathPlugin类
source-path-plugin.js
class SourcePathPlugin {
constructor({ sourcePath = 'src', }) {
this.sourcePath = sourcePath; //插件接收的参数
}
}
module.exports = SourcePathPlugin;
- 根据官方文档,定义一个
apply方法,以 compiler 为参数。
source-path-plugin.js
class SourcePathPlugin {
constructor({ sourcePath = 'src', }) {
this.sourcePath = sourcePath; //插件接收的参数
}
apply(compiler) {
// 指定一个挂载到 webpack 自身的事件钩子
compiler.hooks.environment.tap('SourcePathPlugin', () => {
//这里实现生成src路径json文件
});
}
}
module.exports = SourcePathPlugin;
- 接下用fs来读取文件和写入文件
source-path-plugin.js
class SourcePathPlugin {
constructor({ sourcePath = 'src', }) {
this.sourcePath = sourcePath; //插件接收的参数
}
// 递归获取文件路径树数据
static getFileTreeData(path, obj) {
const files = fs.readdirSync(path, { withFileTypes: true });
for (let i = 0; i < files.length; i++) {
const file = files[i];
if (file.name[0] == '.') {
continue;
}
const filePath = `${path}/${file.name}`;
if (file.isFile()) {
obj[file.name] = filePath;
}
if (file.isDirectory()) {
let childObj = {};
SourcePathPlugin.getFileTreeData(filePath, childObj);
obj[file.name] = childObj;
}
}
}
apply(compiler) {
// 指定一个挂载到 webpack 自身的事件钩子
compiler.hooks.environment.tap('SourcePathPlugin', () => {
const fileTree = {};
//获取src文件夹路径下的文件路径json,结果放在fileTree
SourcePathPlugin.getFileTreeData(this.sourcePath, fileTree);
// 当public文件夹中没有code文件夹时,创建code文件夹
if (!fs.existsSync('public/code')) {
fs.mkdirSync('public/code', { recursive: true });
}
// 在code文件夹中写入path.json文件
const json = JSON.stringify(fileTree, null, 2);
fs.writeFileSync('public/code/path.json', json);
});
}
}
module.exports = SourcePathPlugin;
- 插件已写好,接下来在webpack配置中使用插件。
webpack.config.js
const SourcePathPlugin = require('./plugins/source-path-plugin');//引入插件
memo.plugin('source-path-plugin').use(SourcePathPlugin, [
{
sourcePath: 'src',
},
]);
到这里一个插件的开发和使用就完成了。
- 扩展插件的功能,在编译完成后对文件夹进行复制或删除。以下是完整代码
source-path-plugin.js
// 自定义 webpack plugin.
const fs = require('fs');
const path = require('path');
class SourcePathPlugin {
constructor({ sourcePath = 'src', deletePath, copy }) {
this.sourcePath = sourcePath;
this.copy = copy;
this.deletePath = deletePath;
}
// 获取文件路径树数据
static getFileTreeData(path, obj) {
const files = fs.readdirSync(path, { withFileTypes: true });
for (let i = 0; i < files.length; i++) {
const file = files[i];
if (file.name[0] == '.') {
continue;
}
const filePath = `${path}/${file.name}`;
if (file.isFile()) {
obj[file.name] = filePath;
}
if (file.isDirectory()) {
let childObj = {};
SourcePathPlugin.getFileTreeData(filePath, childObj);
obj[file.name] = childObj;
}
}
}
// 复制文件夹内的文件
static copyFolderSync(source, target) {
// 检查目标文件夹是否存在,如果不存在,则创建
if (!fs.existsSync(target)) {
fs.mkdirSync(target);
}
// 读取源文件夹下的所有文件/文件夹
const files = fs.readdirSync(source);
for (let file of files) {
let sourcePath = path.join(source, file);
let targetPath = path.join(target, file);
// 检查是文件还是文件夹
if (fs.lstatSync(sourcePath).isDirectory()) {
// 如果是文件夹,则递归复制
copyFolderSync(sourcePath, targetPath);
} else {
// 如果是文件,则直接复制
fs.copyFileSync(sourcePath, targetPath);
}
}
}
// 删除文件或文件夹
static deleteFolderRecursive(directory) {
if (fs.existsSync(directory)) {
// Node.js v14.0.0 ,recursive选项可删除文件夹内容
fs.rmdirSync(directory, { recursive: true });
} else {
// 删除文件
fs.unlinkSync(directory);
}
}
apply(compiler) {
// 生成src路径json文件
compiler.hooks.environment.tap('SourcePathPlugin', () => {
const fileTree = {};
SourcePathPlugin.getFileTreeData(this.sourcePath, fileTree);
if (!fs.existsSync('public/code')) {
fs.mkdirSync('public/code', { recursive: true });
}
const json = JSON.stringify(fileTree, null, 2);
fs.writeFileSync('public/code/path.json', json);
});
// 构建完后复制文件和删除文件
compiler.hooks.done.tap('SourcePathPlugin', stats => {
const environment = process.env.NODE_ENV;
if (environment == 'production') {
let arr = [];
this.copy.map(item => {
arr.push(SourcePathPlugin.copyFolderSync(item.from, item.to));
});
this.deletePath.map(item => {
arr.push(SourcePathPlugin.deleteFolderRecursive(item));
});
Promise.all(arr);
}
});
}
}
module.exports = SourcePathPlugin;
webpack.config.js
const SourcePathPlugin = require('./plugins/source-path-plugin');
memo.plugin('source-path-plugin').use(SourcePathPlugin, [
{
sourcePath: 'src',
deletePath: ['dist/src/.umi', 'dist/src/.umi-production'],
},
]);