有一些场景下需要这么个需求,在webpack编译运行时自动插入js或css代码到html模板中。例如要插入一段埋点插件或监控插件的js代码,当然你可以直接写在html模板中,但是对于一个项目中存在多个html模板的情况就不适合一个个手写添加了;
本文讨论通过自定义封装一个webpack plugin的形式来实现自动插入js或css的功能。
- 你可以直接使用我封装好的npm包:insert-html-webpack-plugin(欢迎star)
- 也可以按以下教程自己手写一个。
一、分析
- 本身webpack在运行时就会自动处理html模板,把项目引用的代码经过编译、打包、压缩等一系列处理后通过js和css插入到html相应的位置,而这个功能是通过html-webpack-plugin插件完成的。
- 该插件在webpack编译时会有几个events事件,以v3.2.0版本为例,会有以下几个异步events:
html-webpack-plugin-before-html-generation
html-webpack-plugin-before-html-processing
html-webpack-plugin-alter-asset-tags
html-webpack-plugin-after-html-processing
html-webpack-plugin-after-emit
----以上events分别对应不同的编译时期。 - 而我们要插入自定义的js和css,为了能精确控制插入位置,我们最好是在html-webpack-plugin插件插入js和css之后再做我们的插入动作,顾名思义,也就是在 html-webpack-plugin-after-html-processing 事件里来处理。
二、实现
基本思路:在webpack hook的编译阶段,拿到html页面字符串,根据标签字符串分割,然后插入我们自己的js或css代码,再拼接起来。
1、webpack plugin
- webpack plugin的主要作用就是在webpack某些钩子时期做一些处理来实现想要的功能,每一个plugin插件使用时都是通过new的方式创建一个实例,webpack在内部接收到该实例后会调用实例的apply方法来执行plugin的逻辑。
- 所以,自定义编写plugin时有两种方式:
一是编写一个构造函数,在原型上添加一个apply方法;
二是编写一个class类,类里添加一个apply方法。
2、插入js示例代码
这里就以class类的形式编写,实现插入自定义js,
- 以script标签外联方式引入js文件:
class MyPlugin {
constructor (options) {
this.options = options || ''
}
apply (compiler) {
// 获取要插入的js的地址
const path = this.options.path
// 定义要插入的script字符串
const scriptCode = `<script src="${path}"></script>`
// 编译时注入
compiler.hooks.compilation.tap(
'compilation',
compilation => {
compilation.hooks.htmlWebpackPluginAfterHtmlProcessing.tap(
'htmlWebpackPluginAfterHtmlProcessing',
htmlPluginData => {
// 获取html文件字符串
const htmlStr = htmlPluginData.html.toString()
// 字符串替换,在<body>字符串后追加script
htmlPluginData.html = htmlStr.replace(/<body>/, '<body>' + scriptCode)
})
})
}
}
module.exports = MyPlugin
3、插入css示例代码
和js类似,可以在</title>之后插入:
const linkCode = `<link rel="stylesheet" href="${path}"/>`
htmlPluginData.html = htmlStr.replace(/</title>/, '</title>' + linkCode)
4、使用方式
以插入js为例:
const MyPlugin = require('./MyPlugin')
new MyPlugin({
path: 'https://www.a.com/bb.js'
})
5、内联方式
如果要以内联方式引用代码插入,可以考虑使用node的fs模块读取js或css文件字符串后拼接,
以插入js为例:
const fs = require('fs')
const path = require('path')
// 获取要插入的js的路径,这里的路径是相对于webpack运行命令目录(一般是项目根目录)的路径
const path = this.options.path
// 定义要插入的script字符串
let scriptCode = ''
// 读取js内容
let str = ''
try {
str = fs.readFileSync(path.join(process.cwd(), path), 'utf-8').toString()
} catch (err) {
console.log('js readFileSync error:', err)
}
scriptCode = `<script>${str}</script>`
- 如果是插入css,只需要把上述代码的script标签换成style标签即可。
三、注意点
- 插入的js或css不会再经过webpack的任何处理,所以在插入之前请检查待插入的代码兼容性。