插件写得好好的,一跑就跪?
上周给项目写了个自动注入mock的Webpack插件,本地跑v5没问题,结果测试服v3直接报错。看着控制台里的TypeError: Cannot read property 'use' of undefined
,我默默打开了"模块缓存黑魔法"秘籍...
一、插件开发三大痛点
- 版本分裂:Webpack 4/5插件写法差异大
- 生命周期错位:插件钩子触发时机不对
- 实例获取困难:无法直接访问Dev Server实例
问题场景 :想在插件里修改开发服务器中间件,却发现不同版本的webpack-dev-server
实例结构完全不同!
二、插件开发三板斧
🌰 生活类比:
就像给不同品牌的咖啡机加奶泡------得先搞清楚咖啡机型号,再找到注入奶泡的正确位置。
1. 版本探测仪
javascript
// 在插件构造函数中检测版本
const { version } = require('webpack/package.json')
const isWebpack5 = Number(version.split('.')[0]) >=5
2. 钩子精准打击
javascript
class MyPlugin {
apply(compiler) {
// 开发服务器启动时触发
compiler.hooks.done.tap('MyPlugin', () => {
if (compiler.options.mode === 'development') {
this.patchDevServer(compiler)
}
})
}
patchDevServer(compiler) {
// 核心逻辑写这里
}
}
3. 实例穿透术
javascript
// 从compiler对象获取Dev Server实例
const devServer = compiler.options.devServer
if (devServer) {
// 直接修改实例属性
devServer.middleware.use(myMiddleware)
}
三、实战代码模板
场景1:直接挂载中间件(适用于v4+)
javascript
class DevServerPlugin {
apply(compiler) {
compiler.hooks.afterPlugins.tap('DevServerPlugin', () => {
const devServer = compiler.options.devServer
if (devServer) {
// 动态添加中间件
devServer.setupMiddlewares = (middlewares, server) => {
middlewares.unshift({
name: 'my-middleware',
path: '/',
middleware: myMiddleware
})
return middlewares
}
}
})
}
}
场景2:暴力破解版本差异
javascript
class UniversalPlugin {
apply(compiler) {
compiler.hooks.afterPlugins.tap('UniversalPlugin', () => {
// 核心:从缓存中获取原始模块
const devServerModule = require.cache[require.resolve('webpack-dev-server')]
if (!devServerModule) return
// 针对不同版本修改原型链
if (devServerModule.exports.WebpackDevServer) {
// v4/v5处理
const originalSetup = devServerModule.exports.WebpackDevServer.prototype.setupMiddlewares
devServerModule.exports.WebpackDevServer.prototype.setupMiddlewares = function(...args) {
originalSetup.apply(this, args)
this.middleware.use(myMiddleware)
}
} else if (devServerModule.exports.Server) {
// v3处理
const originalApply = devServerModule.exports.Server.prototype.apply
devServerModule.exports.Server.prototype.apply = function(...args) {
originalApply.apply(this, args)
this.app.use(myMiddleware)
}
}
})
}
}
四、避坑指南
-
钩子顺序:
afterPlugins
用于修改配置done
用于操作已启动的实例
-
异步处理:
javascript// 带Promise的中间件要特殊处理 compiler.hooks.done.tapPromise('MyPlugin', async () => { await someAsyncOperation() })
-
清理工作:
javascript// 避免内存泄漏 compiler.hooks.beforeExit.tap('MyPlugin', () => { delete require.cache[require.resolve('webpack-dev-server')] })
总结:插件开发的必胜心法
- 先探版本 :用
webpack/package.json
版本号做分支 - 钩子为王:选对触发时机比什么都重要
- 缓存兜底:遇到版本差异就掏出模块缓存大法
好啦,这个坑咱今天就填到这。如果还有哪里没讲明白,或者你想从我这儿挖点别的宝藏,随时跟我唠!你的每一条反馈,都会让我变得更懂你。