多个webpack plugin 插件带你一块编写,这次一定学会!
plugin 的特征
- 是一个独立的模块。
- 模块对外暴露一个 js 函数。
- 函数的原型
(prototype)
上定义了一个注入compiler
对象的apply
方法。 apply
函数中需要有通过compiler
对象挂载的hooks
来进行更改程序
Plugin在整个编译过程中,以"恰当的时机",来进行插件的运行,常用于优化、运行服务、dist文件处理等等。那么问题来了,恰当的时机是什么时机?在接下来的内容中,你将了解,plugin是什么、plugin在打包构建过程中的原理、如何仿写一个plugin。
Compiler 对象 (负责编译)
Compiler
对象包含了当前运行Webpack
的配置,包括entry、output、loaders
等配置,这个对象在启动 Webpack 时被实例化,而且是全局唯一的。Plugin 可以通过该对象获取到 Webpack 的配置信息进行处理。
常用的compiler-hooks如下:
- run (再编辑器开始读取记录前执行)
- compile (再一个新的compliation创建之前执行)
- compilation (再一个新的compliation创建后执行)
- make(compilation 结束之前执行)
- emit(输出 asset 到 output 目录之前执行)
- afterEmit(输出 asset 到 output 目录之后执行)
- done(在 compilation 完成时执行)
- failed(在 compilation 失败时调用)
- shutdown(当编译器关闭时调用。)
实现HelloWordPulgin
插件,可以看下面代码执行时机:
class HelloWordPulgin {
constructor(options) {
console.log(options, 'options....');
}
apply(compiler) {
compiler.hooks.run.tap('myRun', () => {
console.log('开始编译...run')
});
compiler.hooks.compile.tap('myCompile', () => {
console.log('新的compliation创建...compile')
});
compiler.hooks.compilation.tap('myCompilation', () => {
console.log('compliation创建后...compilation')
});
compiler.hooks.make.tap('myMake', () => {
console.log('compliation结束之前...make')
});
compiler.hooks.emit.tap('myEmit', () => {
console.log('输出之前...emit')
});
compiler.hooks.afterEmit.tap('myAfterEmit', () => {
console.log('输出完成...afterEmit')
});
compiler.hooks.done.tap('myDone', () => {
console.log('compliation完成...done')
});
compiler.hooks.failed.tap('myFailed', () => {
console.log('compliation失败...failed')
});
compiler.hooks.shutdown.tap('myShutdown', () => {
console.log('编译器关闭时...')
});
}
}
module.exports = HelloWordPulgin;
webpack
应用:
const HelloWordPulgin = require('./pugins/hello-word-pulgin');
const path = require('path');
module.exports = {
mode: 'development',
entry: './src/index.js',
output: {
path: path.resolve(__dirname, 'dist'),
filename: '[name].bundle.js', // 打包后输出文件的文件名
},
plugins: [
new HelloWordPulgin({ title: 'hello pulgin' }) // HelloWordPulgin
]
};
bash
输出信息
npm run build
> plugins@1.0.0 build D:\works\webpack-loader-plugin\plugins
> webpack
{ title: 'hello pulgin' } options....
开始编译...run
新的compliation创建...compile
compliation创建后...compilation
compliation结束之前...make
输出之前...emit
输出完成...afterEmit
compliation完成...done
编译器关闭时...
asset main.bundle.js 1.22 KiB [emitted] (name: main)
./src/index.js 38 bytes [built] [code generated]
webpack 5.74.0 compiled successfully in 154 ms
上面输出信息都按照咱们期望的输出了(failed
除外,需要可以再代码执行错误去验证)。
Compilation 对象
Compilation
模块会被Compiler
用来创建新的compilation
对象(或新的build
对象)。compilation
实例能够访问所有的模块和它们的依赖(大部分是循环依赖)。 它会对应用程序的依赖图中所有模块, 进行字面上的编译(literal compilation)。 在编译阶段,模块会被加载(load
)、封存(seal
)、优化(optimize
)、 分块(chunk
)、哈希(hash
)和重新创建(restore
)。
和 compiler 用法相同,取决于不同的钩子类型, 所以也可以在某些钩子上访问
tapAsync
和tapPromise
。
Compiler
和 Compilation
的区别
Compiler
代表了整个Webpack
从启动到关闭的生命周期Compilation
只是代表了一次新的编译,只要文件有改动,compilation
就会被重新创建。
部分Compilation hooks如下:
- buildModule (在模块构建开始之前触发,可以用来修改模块)
- rebuildModule(在重新构建一个模块之前触发)
- failedModule(模块构建失败时执行。)
- succeedModule(模块构建成功时执行。)
- finishModules(所有模块都完成构建并且没有错误时执行。)
实现copy-webpack-plugin插件
copy-webpack-plugin
就是把部分文件copy
到目标文件,咱们选用Compiler hooks done
(compliation完成)去实现。
copy-webpack-plugin
源码:
const fs = require('fs');
class CopyWebpackPlugin {
constructor(options) {
this.formPath = options.form;
this.toPath = options.to;
console.log(options, 'options....');
}
apply(compiler) {
const { formPath, toPath } = this;
const isDir = fs.statSync(formPath).isFile() ? false : true;
compiler.hooks.done.tap('myDone', () => {
// fs.cp is not a function 新增于: v16.7.0
// node >= 16.7.0 没有大于可以用其他插件实现复制功能
fs.cp(formPath, toPath, { recursive: isDir }, (err) => {
if (err) {
throw err;
}
})
console.log('copy完成...done')
});
compiler.hooks.shutdown.tap('myShutdown', () => {
console.log('编译器关闭时...')
});
}
}
module.exports = CopyWebpackPlugin;
webpack
应用
const path = require('path');
const HelloWordPulgin = require('./pugins/hello-word-pulgin');
const CopyWebpackPlugin = require('./pugins/copy-webpack-plugin');
module.exports = {
mode: 'development',
entry: './src/index.js',
output: {
path: path.resolve(__dirname, 'dist'),
filename: '[name].bundle.js', // 打包后输出文件的文件名
},
plugins: [
new HelloWordPulgin({ title: 'hello pulgin' }), // 执行时机
new CopyWebpackPlugin({
form: path.resolve(__dirname, 'static'),
to: path.resolve(__dirname, 'dist', 'static'),
})
]
};
bash
输出信息:
$ npm run build
> plugins@1.0.0 build
> webpack
{ title: 'hello pulgin' } options....
{
form: 'D:\\works\\webpack-loader-plugin\\plugins\\static',
to: 'D:\\works\\webpack-loader-plugin\\plugins\\dist\\static'
} options....
开始编译...run
新的compliation创建...compile
compliation创建后...compilation
compliation结束之前...make
输出之前...emit
输出完成...afterEmit
compliation完成...done
copy完成...done
编译器关闭时...
编译器关闭时...
asset main.bundle.js 1.22 KiB [compared for emit] (name: main)
./src/index.js 38 bytes [built] [code generated]
webpack 5.74.0 compiled successfully in 408 ms
这个时候去看dist
文件夹发现已经有咱们复制的目录了。
clean-webpack-plugin
实现
clean-webpack-plugin
一般用在输出前用来清空之前目录,咱们选用Compiler hooks emit
去完成。
clean-webpack-plugin
源码:
const path = require('path');
const shell = require('shelljs');
class CleanWebpackPlugin {
constructor(options) {
this.targetPath = options.target;
}
apply(compiler) {
compiler.hooks.emit.tap('myEmit', () => {
shell.cd(this.targetPath);
shell.cd('..');
shell.rm('-rf', './dist'); // 用什么方案做删除都行,这边用到了shelljs
shell.pwd();
shell.exec('mkdir dist');
console.log('输出之前...emit')
});
}
}
module.exports = CleanWebpackPlugin;
webpack
应用
const path = require('path');
const HelloWordPulgin = require('./pugins/hello-word-pulgin');
const CopyWebpackPlugin = require('./pugins/copy-webpack-plugin');
const CleanWebpackPlugin = require('./pugins/clean-webpack-plugin');
console.log(path.resolve('dist'))
module.exports = {
mode: 'development',
entry: './src/index.js',
output: {
path: path.resolve(__dirname, 'dist'),
filename: '[name].bundle.js', // 打包后输出文件的文件名
},
plugins: [
new HelloWordPulgin({ title: 'hello pulgin' }), // 执行时机
new CleanWebpackPlugin({
target: path.resolve(__dirname, 'dist'),
}),
new CopyWebpackPlugin({
form: path.resolve(__dirname, 'static'),
to: path.resolve(__dirname, 'dist', 'static'),
})
]
};
bash
输出日志
$ npm run build
> plugins@1.0.0 build D:\works\webpack-loader-plugin\plugins
> webpack
D:\works\webpack-loader-plugin\plugins\dist
{ title: 'hello pulgin' } options....
{
form: 'D:\\works\\webpack-loader-plugin\\plugins\\static',
to: 'D:\\works\\webpack-loader-plugin\\plugins\\dist\\static'
} options....
开始编译...run
新的compliation创建...compile
compliation创建后...compilation
compliation结束之前...make
输出之前...emit
remove target path success
create dist file success
输出之前...emit
输出完成...afterEmit
compliation完成...done
copy完成...done
编译器关闭时...
编译器关闭时...
asset main.bundle.js 1.22 KiB [emitted] (name: main)
./src/index.js 38 bytes [built] [code generated]
webpack 5.74.0 compiled successfully in 554 ms
实现flie-count-webpack-plugin
插件
flie-count-webpack-plugin
用于统计文件数量注入到一个markdown
里面。可以再输出前同上(Compiler hooks emit)
flie-count-webpack-plugin
源码
class FileCountWebpackPlugin {
constructor(options) {
this.targetName = `${(options?.targetName || 'file-count')}.md`;
}
apply(compiler) {
compiler.hooks.emit.tap('myEmit', (compilation) => {
const webpack = compiler.webpack;
// compilation.assets 获取文件信息
const compilationAssets = compilation.assets;
const assetsKeys = Object.keys(compilationAssets);
const assetsLength = assetsKeys.length;
let content = `## count files (${assetsLength}) \n`;
assetsKeys.forEach(item => {
content += `- ${item} \n`;
});
compilation.assets[this.targetName] = {
source: function () {
return content
},
size: function () {
return content.length
},
};
console.log('输出之前...emit');
});
}
}
module.exports = FileCountWebpackPlugin;
webpack
应用
const path = require('path');
const HelloWordPulgin = require('./pugins/hello-word-pulgin');
const CopyWebpackPlugin = require('./pugins/copy-webpack-plugin');
const CleanWebpackPlugin = require('./pugins/clean-webpack-plugin');
const FileCountWebpackPlugin = require('./pugins/flie-count-webpack-plugin');
console.log(path.resolve('dist'))
module.exports = {
mode: 'development',
entry: './src/index.js',
output: {
path: path.resolve(__dirname, 'dist'),
filename: '[name].bundle.js', // 打包后输出文件的文件名
},
plugins: [
new HelloWordPulgin({ title: 'hello pulgin' }), // 执行时机
new CleanWebpackPlugin({
target: path.resolve(__dirname, 'dist'),
}),
new FileCountWebpackPlugin(),
new CopyWebpackPlugin({
form: path.resolve(__dirname, 'static'),
to: path.resolve(__dirname, 'dist', 'static'),
})
]
};
bash
输出日志
$ npm run build
> plugins@1.0.0 build D:\works\webpack-loader-plugin\plugins
> webpack
D:\works\webpack-loader-plugin\plugins\dist
{ title: 'hello pulgin' } options....
{
form: 'D:\\works\\webpack-loader-plugin\\plugins\\static',
to: 'D:\\works\\webpack-loader-plugin\\plugins\\dist\\static'
} options....
开始编译...run
新的compliation创建...compile
compliation创建后...compilation
compliation结束之前...make
输出之前...emit
remove target path success
create dist file success
输出之前...emit
输出之前...emit
(node:49132) [DEP_WEBPACK_COMPILATION_ASSETS] DeprecationWarning: Compilation.assets will be frozen in future, all modifications are deprecated.
BREAKING CHANGE: No more changes should happen to Compilation.assets after sealing the Compilation.
Do changes to assets earlier, e. g. in Compilation.hooks.processAssets.
Make sure to select an appropriate stage from Compilation.PROCESS_ASSETS_STAGE_*.
(Use `node --trace-deprecation ...` to show where the warning was created)
输出完成...afterEmit
compliation完成...done
copy完成...done
编译器关闭时...
编译器关闭时...
asset main.bundle.js 1.22 KiB [emitted] (name: main)
asset file-count.md 38 bytes [emitted]
./src/index.js 38 bytes [built] [code generated]
webpack 5.74.0 compiled successfully in 408 ms
DeprecationWarning: Compilation.assets will be frozen in future, all modifications are deprecated.
(DeprecationWarning:编译。未来资产将被冻结,所有修改不赞成) 期望值达到了,给我生成file-count.md
文件了。但是有警告信息,这个时候百度发现都是npm i -D html-webpack-plugin@next
安装最新版本的,应该是一个问题新版本html-webpack-plugin
都解决了,我来借鉴下。
参考html-webpack-plugin
v5源码更改后:
class FileCountWebpackPlugin {
constructor(options) {
this.targetName = `${(options?.targetName || 'file-count')}.md`;
}
apply(compiler) {
compiler.hooks.thisCompilation.tap('myEmit', (compilation) => {
compilation.hooks.processAssets.tapAsync({
name: 'process',
stage: webpack.Compilation.PROCESS_ASSETS_STAGE_OPTIMIZE_INLINE
}, (assets, callback) => {
let count = 0;
let content = '';
for (const asset in assets) {
count++;
content += `- ${asset} \n`;
}
const title = `## count files (${count}) \n`;
compilation.emitAsset(this.targetName, new webpack.sources.RawSource(title + content, false));
callback();
callback();
})
console.log('输出之前...thisCompilation');
});
}
}
module.exports = FileCountWebpackPlugin;
更改后bash
输入日志:
$ npm run build
> plugins@1.0.0 build D:\works\webpack-loader-plugin\plugins
> webpack
D:\works\webpack-loader-plugin\plugins\dist
{ title: 'hello pulgin' } options....
{
form: 'D:\\works\\webpack-loader-plugin\\plugins\\static',
to: 'D:\\works\\webpack-loader-plugin\\plugins\\dist\\static'
} options....
开始编译...run
新的compliation创建...compile
输出之前...emit
compliation创建后...compilation
compliation结束之前...make
输出之前...emit
remove target path success
create dist file success
输出之前...emit
输出完成...afterEmit
compliation完成...done
copy完成...done
编译器关闭时...
编译器关闭时...
asset main.bundle.js 1.22 KiB [emitted] (name: main)
asset file-count.md 38 bytes [emitted]
./src/index.js 38 bytes [built] [code generated]
webpack 5.74.0 compiled successfully in 386 ms
实现banner-webpack-plugin
插件
banner-webpack-plugin
咱们就开发基础功能再静态资源添加信息, 跟上面实现方法大概一致。
简易banner-webpack-plugin
源码:
class BannerWebpackPlugin {
constructor(options = { banner() { } }) {
this.prefixBanner = options?.banner() || '';
}
apply(compiler) {
compiler.hooks.thisCompilation.tap('myThisCompilation', (compilation) => {
const webpack = compiler.webpack;
compilation.hooks.processAssets.tapAsync({
name: 'process',
stage: webpack.Compilation.PROCESS_ASSETS_STAGE_ADDITIONS
}, (assets, callback) => {
for (const asset in assets) {
if (asset.endsWith('.js')) {
compilation.updateAsset(asset, source => {
const sourceContent = source.source();
const newContent = this.prefixBanner + sourceContent;
return new webpack.sources.RawSource(newContent, false)
});
}
}
callback();
})
})
}
}
module.exports = BannerWebpackPlugin;
webpack
应用:
const path = require('path');
const HelloWordPulgin = require('./plugins/hello-word-pulgin');
const CopyWebpackPlugin = require('./plugins/copy-webpack-plugin');
const CleanWebpackPlugin = require('./plugins/clean-webpack-plugin');
const FileCountWebpackPlugin = require('./plugins/flie-count-webpack-plugin');
const BannerWebpackPlugin = require('./plugins/banner-webpack-plugin');
console.log(path.resolve('dist'))
module.exports = {
mode: 'development',
entry: './src/index.js',
output: {
path: path.resolve(__dirname, 'dist'),
filename: '[name].bundle.js', // 打包后输出文件的文件名
},
plugins: [
new HelloWordPulgin({ title: 'hello pulgin' }), // 执行时机
new CleanWebpackPlugin({
target: path.resolve(__dirname, 'dist'),
}),
new BannerWebpackPlugin({
banner() {
return `/**
* 这是是XXX系统代码
* 我们是XXX团队
* 打包时间${new Date().toLocaleString()}
*/`
}
}),
new FileCountWebpackPlugin(),
new CopyWebpackPlugin({
form: path.resolve(__dirname, 'static'),
to: path.resolve(__dirname, 'dist', 'static'),
}),
]
};
...
输出正常,达到期望效果
remove-console-webpack-plugin
删除console插件开发
remove-console-webpack-plugin
去掉生产环境无用的console
。
remove-console-webpack-plugin
源码
class RemoveConsoleWebpackPlugin {
constructor(options) {
this.options = options;
}
apply(compiler) {
compiler.hooks.thisCompilation.tap('myThisCompilation', (compilation) => {
const webpack = compiler.webpack;
// 生产环境删除注释
if (process.env.NODE_ENV !== 'production') return;
compilation.hooks.processAssets.tapAsync({
name: 'process',
stage: webpack.Compilation.PROCESS_ASSETS_STAGE_ADDITIONS
}, (assets, callback) => {
for (const asset in assets) {
if (/(.js|.ts|.jsx|.ts|.vue)$/.test(asset)) {
compilation.updateAsset(asset, source => {
const sourceContent = source.source();
const newSource = sourceContent.replace(/console.log\(.*\);?/g, '');
return new webpack.sources.RawSource(newSource, false)
});
}
}
callback();
})
})
}
}
module.exports = RemoveConsoleWebpackPlugin;
webpack
应用:
...
new RemoveConsoleWebpackPlugin(),
...
转载自:https://juejin.cn/post/7231896581477007419