如何打造一个webpack的plugin
前言
前几天面试,面试官问打造一个webpack的plugin有思路吗,我愣住了,来写个文章记录一下。
正文
不清楚webpack是啥的可以去看看我的上一篇文章,记录的很详细,我们就直接简单铺垫下开始。
一、创建目录结构
我们创建一个WEBPACK-PLUGIN的目录,初始化后,安装webpack和webpack-cli。接着创建一些目录文件,结构如下:
二、编写index.js
我们在index.js中写一段很简单的代码,如下:
function add(a, b) {
return a + b
}
add(1, 2)
console.log('hello world');
如果这个index.js我需要打包到index.html中使用,我们就需要在根目录创建一个webpack的配置文件webpack.config.js。
三、编写webpack.config.js
基本的配置如下:
module.exports = {
mode: 'development',
entry: {
main: path.resolve(__dirname, 'src/index.js')
},
output: {
path: path.resolve(__dirname, 'dist'),
filename: 'main.js'
}
并且在package.json中配置运行命令(直接yarn build打包):
"build": "webpack --config webpack.config.js"
运行打包命令后,我们会发现根目录下多出dist文件夹,里面的main.js是对index.js的打包,我们将其引入html。
四、编写index.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<script src="./dist/main.js"></script> <!-- 引入main.js -->
</body>
</html>
我们跑一下这个html,成功打印!
五、几个问题
问题一
:
我们如果修改html中打印的东西,重新打包,那么处于dist文件夹下的文件依然还是main.js。这就会有一个问题,CDN网络分发的情况下,分布服务器感知总部服务器的变更一般是通过资源文件名的变更而变更的。
所以,该想个法子了!可不可以在每次打包后在文件名后拼接一串哈希值呢?
我们在webpack配置文件中将 filename 改成 'main.[hash:8].js'
再去打包,发现dist中新增了一个文件!成功解决这个问题。
问题二
:
如果我们想让main.js自动引入到这个html中来而不需要我们手动引入呢?
可以安装html-webpack-plugin插件解决这个问题。
配置文件代码如下:
const path = require('path')
const HtmlWebpackPlugin = require('html-webpack-plugin')
module.exports = {
mode: 'development',
entry: {
main: path.resolve(__dirname, 'src/index.js')
},
output: {
path: path.resolve(__dirname, 'dist'),
filename:'main.[hash:8].js'
},
plugins: [
new HtmlWebpackPlugin({
template: path.resolve(__dirname, 'index.html')
})
]
}
我们把之前我们手动在html中引入main.js的代码删除,重新打包,会发现dist文件夹下出现index.html,并且自动引入了main.js,运行html也能得到相应的打印!
问题三
:
我们可以瞅一眼,dist文件夹下一坨main.js混杂在一起,那么之前打包过的文件我们是不是可以删除呢?
可以安装clean-webpack-plugin插件来解决。
配置文件继续添加引入并使用:
const path = require('path')
const HtmlWebpackPlugin = require('html-webpack-plugin')
const { CleanWebpackPlugin } = require('clean-webpack-plugin')
module.exports = {
mode: 'development',
entry: {
main: path.resolve(__dirname, 'src/index.js')
},
output: {
path: path.resolve(__dirname, 'dist'),
filename: 'main.js' //'main.[hash:8].js':每次打包的文件名不一样 让分布服务器能感知到主服务器发生变化
},
plugins: [
new HtmlWebpackPlugin({
template: path.resolve(__dirname, 'index.html')
}),
new CleanWebpackPlugin(), //把历史打包文件清除掉
]
}
打包完成后,我们去dist文件夹查看,发现瞬间明朗了!
你以为问题结束了?还没有呢!
问题四
:CleanWebpackPlugin是怎么把历史的打包文件清除的呢?它怎么知道哪些是历史的包呢?
思路
:在把新的包放进dist文件夹前把历史的包清除掉不就可以了吗?
那么这就涉及到执行时间的问题了,我们去webpack官方文档发现webpack内置了一系列的钩子函数(compiler 钩子),分别会在打包过程中的各个时间节点执行。
(🚩ps:compilation 钩子是编译阶段的钩子函数)
再去看看官方文档
中对plugin的介绍
:
插件是webpack的支柱功能,插件目的在于解决loader无法实现的其他事,webpack插件是一个具有apply方法的JavaScript对象。apply方法会被webpack compiler调用,并且在整个编译生命周期都可以访问compiler对象。
好了,到这里,我们基本的铺垫才做好。
六、正题:打造一个简单插件
我们需要打造一款可以自动在main.js后拼接一个时间戳的插件,并且能自动引入到html中。
思路
:
- 拼接时间戳应该在html文件生成出来之前完成
- 我们的plugin应该在html-webpack-plugin之前生效
我们在根目录创建stamp-webpack-plugin.js来打造插件
const HtmlWebpackPlugin = require('html-webpack-plugin')
class StampPlugin {
apply(compiler) {
//compilation是在编译这件事被创建之后就会执行(可以保证我们的插件一定会在文件生成之前执行)
//注册一个名为StampWebpackPlugin的方法
compiler.hooks.compilation.tap('StampWebpackPlugin', (compilation, compilationParams) => { //tap用来触发钩子函数
//参数compilation是模块
HtmlWebpackPlugin.getHooks(compilation).beforeAssetTagGeneration.tap('StampWebpackPlugin', (data, cb) => { //使用HtmlWebpackPlugin提供的钩子beforeAssetTagGeneration(静态标签生成之前)
let jsSrc = data.assets.js[0] //拿到'main.js'
data.assets.js[0] = `${jsSrc}?${new Date().getTime()}` //拼接时间戳后替换
});
})
}
}
module.exports = StampPlugin
最后,我们只需要在webpack配置文件中引入我们打造的插件就行了:
const path = require('path')
const HtmlWebpackPlugin = require('html-webpack-plugin')
const { CleanWebpackPlugin } = require('clean-webpack-plugin')
const StampPlugin=require('./stamp-webpack-plugin.js')
module.exports = {
mode: 'development',
entry: {
main: path.resolve(__dirname, 'src/index.js')
},
output: {
path: path.resolve(__dirname, 'dist'),
filename: 'main.js'
},
plugins: [
new HtmlWebpackPlugin({
template: path.resolve(__dirname, 'index.html')
}),
new CleanWebpackPlugin(), //把历史打包文件清除掉
new StampPlugin()
]
}
再次打包,会发现html自动引入main.js,并拼接上了时间戳,再也不用手动在output的文件名中添加了!
结
这里只打造一个简单的插件,流程并不复杂,看完之后大佬们就可以自己去打造插件了!