vue-cli3.0中基于webpack自定义plugin

3,836 阅读3分钟

应用场景:

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。

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({}),
           );
   }
}