应用场景:
vue-cli3.0移动端app项目,利用ios跟Android的桥能力,可以下载文件到App上,实现了热更新功能,可局部替换用户App上的js代码;由于浏览器缓存机制,当页面访问过aaa.js后,再次访问时浏览器只会拿取缓存,并不会重新加载aaa.js,意味着替换后的代码不生效。
一. 思路:给项目打包后的index.html文件中,将<script src=static/js/app.js></script>改成<script src=static/js/app.js?2cf0c6eb9ccbcc63644f></script>,拼上哈希值的方式来清除浏览器缓存。该app.js为主文件,利用vue-cli内置的配置即可解决。
在vue.config.js中配置chainWebpack属性:
module.exports = {
css: {
sourceMap: true,
extract: false
},
chainWebpack: config => {
config
.plugin('html')
.tap(options => {
options[0].hash = true//打包后的index.html中app.js的script标签的src路径后面会拼上hash,但link标签中的src路径没有拼上
return options
})
}
}
//其他配置参考https://cli.vuejs.org/zh/config/#chainwebpack
//https://www.webpackjs.com/plugins/html-webpack-plugin/
二 . 但是上面步骤只能清除app.js文件的缓存,其他chuck文件的,在这里用过自己编写插件解决。
1. 先配置runtimeChunk属性,将各个模块之间的引用和加载的逻辑的文件抽离出来,例如runtimeChunk_app.js
module.exports = {
css: {
sourceMap: true,
extract: false
},
chainWebpack: config => {
config
.plugin('html')
.tap(options => {
options[0].hash = true//打包后的index.html中app.js的script标签的src路径后面会拼上hash,但link标签中的src路径没有拼上
return options
})
},
configureWebpack:(config) => {
config.optimization = {
// runtimeChunk : 默认为false,runtime相关的代码(各个模块之间的引用和加载的逻辑)内嵌入每个entry。(重点:各个模块之间的引用和加载的逻辑)
// runtime 指的是 webpack 的运行环境(具体作用就是模块解析, 加载) 和 模块信息清单
// true:对于每个entry会生成runtime~${entrypoint.name}的文件。我们项目的入口文件为app.js,打包出来文件为:runtimechunk~app.js
runtimeChunk:{
name: entrypoint => `runtimechunk_${entrypoint.name}`
}
}
}
}
//其他配置参考https://cli.vuejs.org/zh/config/#configurewebpack
//https://www.webpackjs.com/plugins/split-chunks-plugin/
build之后的压缩过runtimeChunk_app.js:
2. 自己编写plugin,在项目src文件同级创建plugins文件夹,定义一个存放解决该需求的插件文件appjsGuideAfterAddHash,创建index.js。
class AppjsGuideAfterAddHash {
/**
* compiler是webpack的一个实例,这个实例存储了webpack各种信息,所有打包信息
* https://webpack.js.org/api/compiler-hooks
* 官网里面介绍了compiler里面有个hooks这样的概念
* hooks是钩子的意思,里面定义了时刻值
*/
constructor(options) {
// newPath可以设置文件名,aaa 或者 aaa.js ,打包后目录则为static/aaa.js;也可以设置为文件路径AAA/BBB.js,打包后目录则为static/AAA/BBB.js
this.newPath = ''
this.isError = ''
if (options.hasOwnProperty('newPath')) {
if (options.newPath === '') {
throw "注意:您输入了一个空字符串或者空路径!!!";//抛出异常终止打包
}
if (/\.(js)$/.test(options.newPath)) {
this.newPath = options.newPath
} else {
// 用户配置时候没有添加.js后缀
this.newPath = `${options.newPath}.js`
}
} else {
this.newPath = 'runtimechunk_app.js'
console.log("调用插件未配置newPath,则设置默认的一个文件路径,基于跟项目static同级,--> 'runtimechunk_app.js'")
}
this.customHash = options.hasOwnProperty('customHash') ? options.customHash : '';//用户自定义的字段之类的
// 为true自定义的customHash则不使用,使用默认的setDefaultHash;为false时customHash生效。
this.isDefaultHash = options.hasOwnProperty('isDefaultHash') ? options.isDefaultHash : false;
// 存储该文件的内容
this.temp_runtimechunk_app = '';
// 内容长度大小
this.temp_runtimechunk_app_length = 0;
}
// 用户设置isDefaultHash为true,则使用默认hash ,20位
setDefaultHash(randomFlag, min, max) {
// randomFlag ,true:hash值固定位数,只需传入min , false:hash范围在min到max之间
let str = ""
let range = min
let arr = ['0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z', 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z'];
// 随机产生
if(randomFlag){
range = Math.round(Math.random() * (max-min)) + min;
}
let pos = ''
for(var i=0; i<range; i++){
pos = Math.round(Math.random() * (arr.length-1));
str += arr[pos];
}
return str;
}
/**
* 用到了hooks,emit这个时刻,在输出资源之前,这里官网告诉我们是一个异步函数
* compilation是这一次的打包信息,所以跟compiler是不一样的
* tapAsync接受两个参数,第一个是名字,
*/
apply(compiler) {
compiler.hooks.emit.tapAsync('AppjsGuideAfterAddHash', (compilation, callback) => {
// 针对项目的指引关系js文件进行分析
for (var filename in compilation.assets) {
// Get the asset source for each file generated by the chunk:
if (filename.indexOf('static/js/runtimechunk_app.js') != -1) {
this.temp_runtimechunk_app = compilation.assets[filename].source();
this.temp_runtimechunk_app_length = this.temp_runtimechunk_app.length;
}
}
let random_20 = this.setDefaultHash(false,20)//
this.isDefaultHash?console.log( random_20, "<<<--------------- isDefaultHash为true时拼接到js后的默认hash值 "):console.log(this.customHash , "<<<--------------- isDefaultHash为false时自定义字符串 ")
let new_source = this.temp_runtimechunk_app.replace('+".js"',`+".js?${this.isDefaultHash?random_20:this.customHash}"`)
// 将这个列表作为一个新的文件资源,插入到 webpack 构建中:
compilation.assets[this.newPath] = {
source: function(){
return new_source//新的文件资源
},
size: function() {
return this.temp_runtimechunk_app_length//设置长度大小
}
};
callback();
})
}
}
module.exports = AppjsGuideAfterAddHash
//webpack自定义plugin官网链接:https://www.webpackjs.com/api/plugins/
vue.config.js文件中:
let AppjsGuideAfterAddHash = require('./plugins/appjsGuideAfterAddHash/index.js');//引入自定义插件。
let ZipPlugin = require('zip-webpack-plugin');
module.exports = {},
configureWebpack:(config) => {
config.plugins.push(
new AppjsGuideAfterAddHash({
newPath: 'static/js/runtimechunk_app.js',//配置不可以变动,如后续需要变动,需要相应修改该插件。
customHash: `${process.env.VUE_APP_VERSION}`,//H5版本号
}),//需要考虑插件之间的执行顺序。例如这里需要先执行了AppjsGuideAfterAddHash之后才能执行ZipPlugin。
new ZipPlugin({}),
);
}
}