解决大中型浏览器(Chrome)插件开发痛点:自定义热更新方案——1.原理分析及构建部署实现
背景
作为一款版本快速更新迭代的扩展程序,立刻解决重大bug、功能发布,频繁发版影响用户体验,并且,用户无科学上网没法自动更新程序。基于此背景,设计了套基于webpack构建,IndexedDB缓存的热更新方案,支持"一键"部署,解决线上问题,同时大大提升开发效率。
一、模块热更新原理
var obj = {
a:1,
init(){
console.log('我是obj')
}
}
eval(`
var obj = {
a:1,
init(){
console.log('你被替换了!')
}
}
`)
obj.init() // 你被替换了!
这是整个热更新方案的原理,对整个对象进行替换执行。
接下去介绍下基于此的实现方案,更确切的说是一种规范,例子仅介绍Content-Scripts脚本的热更新,涉及扩展程序的Popup页,background.js以及可能需要注入js资源文件的实现不做介绍,仅提供思路。
二、设计项目结构规范和模板代码
# 项目代码结构
/build // webpack构建脚本
webpack.hotfix.js // 热更新webpack脚本
webpack.base.js // 基础配置
webpack.dev.js // 开发配置
webpack.prod.js // 生产配置
/hotfix // 执行npm run hotfix 将产生此热修复代码文件
content-scripts/
moduleName.js
/app
background/ // 程序后台运行的代码文件
popup/ // 浏览器右上角展示的pop页目录
content-scripts/
moduleName/ // 功能模块代码
entity.js // 模块实体类
index.js // 模块热更新代码
....
package.json
manifest.json // chrome扩展程序的配置文件
这么设计代码结构是方便webpack打hotfix代码文件。
// entity.js
const obj = {
Init(){
//实现代码业务逻辑,包括创建模块功能视图
}
}
/** 模块对象挂载到全局,方便进行模块替换
* 注意,此处扩展程序content-script注入到页面会创建扩展程序独立的环境,跟页面是不同域的,两者绝缘
*/
if(window.CRX){
window.CRX.obj = obj
}else{
window.CRX = {}
window.CRX.obj = obj
}
export default obj
import hotFix from 'hotfix.js'
import obj from './entity.js'
//热修复方法,对obj模块进行热修复(下期介绍:基于双缓存获取热更新代码)
const moduleName = 'obj';
hotFix(moduleName).catch(err=>{
console.warn(`${moduleName}线上代码解析失败`,err)
obj.Init()
})
三、设计Webpack构建hotfix文件
// 配置文件 config.js
//配置打包目录
config.packPath = [
'./app/content-scripts/',
]
//获取命令行自定义热修复模块名参数 npm run hotfix --module=name1,name2
config.hotFixModule = (function () {
let modules = process.argv[process.argv.length - 1]
if (modules.indexOf('module=') > -1) {
modules = modules.split('=')[1].split(',')
return modules.map(function (key) {
return moduleMap[key.trim()]
})
}
})()
export default config
// webpack.hotfix.js
utils.checkEntries(entries) // 检验模块是否配置,防止误上传
const packPath = config.packPath // 配置的需要热更新文件的包路径
let hotFixEntries = {} // 配置热更新打包入口对象
packPath.forEach(function (path) {
const entryKey = path.indexOf('content-scripts') > -1 ? '**/entity.js' : '**/index.js'
glob.sync(path + entryKey).forEach(function (entry) {
//匹配出模块名content-scripts/moduleName
const matches = /.*app\/(.*)\/\b(?:entity|index)\b\.js/.exec(entry)
if (matches[1]) {
hotFixEntries[matches[1]] = entry
}
})
})
// 根据命令行入参模块名过滤出需热修复的模块
if (config.hotFixModule) {
const hotFixModules = Object.keys(hotFixEntries).filter(module => config.hotFixModule.indexOf(module) > -1)
for (let p in hotFixEntries) {
if (hotFixModules.indexOf(p) === -1) {
delete hotFixEntries[p]
}
}
}
// 合并基本webpack配置文件
module.exports = merge(baseWebpack, {
entry: hotFixEntries,
output: {
path: utils.resolvePath.base(config.dirHotFix), // 打包后的文件存放的地方
}
})
{
"scripts":{
"clean": "rimraf -rf ./package",
"hotfix": "npm run cleanHotfix && cross-env NODE_ENV=production webpack --config ./build/webpack.hotfix.js --hide-modules --mode production",
}
}
自此完成模块热更新构建指令,支持可选参数配置模块名:
npm run hotfix --module=module1,module2
执行命令,生产hotfix/文件夹,存放各热修复模块文件。
四、设计热更新代码自动部署
version: 当前程序版本号
name:模块名
type:热更新模块所属类型
content:模块代码
接口收到数据,依次创建version/type/name.js文件,并写入content。
// deploy.js
const allUploadQueue = [] // 所有要上传文件队列
// ora:一款优雅的终端旋转器
const spinner = ora('uploading files... ')
function addDirToQueue (_path, cb) {
// 递归获取hotfix下模块文件相关逻辑省略
......
// 构建队列文件信息
let obj = {
data: _path,
size: size,
name: fileName,
type,
content: fs.readFileSync(_path).toString()
}
allUploadQueue.push(obj)
}
function startUpload(){
.....
let tmp = []
// 截取5次数据
if (allUploadQueue.length >= options.perTotal) {
tmp = allUploadQueue.splice(0, options.perTotal)
} else {
tmp = allUploadQueue.splice(0, allUploadQueue.length)
}
...
//构造请求
const promise = tmp.map(...)
// 递归上传
Promise.all(promise).then(data=>{
if(allUploadQueue.length){
startUpload(options)
}
})
}
function uploadScriptFile (options = {}) {
spinner.start()
options = Object.assign({}, defaultOptions, options)
//invariant:一款开发中描述错误的插件
invariant(options.file, 'upload file is required!')
invariant(options.originUrl, 'upload originUrl is required!')
invariant(options.data, 'data object is required!')
//根据hotfix文件夹路径,递归模块信息到队列
addDirToQueue(options.file, function () {
//单次并发5次上传文件请求,去上传文件
startUpload(options)
})
}
"scripts":{
...
"deploy":"node ./build/deploy.js"
}
执行npm run deploy,完成hotfix文件上传服务器
五、总结
自此便实现了Chrome扩展程序的热更新打包部署。
//1. 构建hotfix
npm run hotfix --module=name1,name2
//2. 发布到线上
npm run deploy
--END--
作者:梁龙先森 WX:newBlob
原创作品,抄袭必究