写点什么

解决大中型浏览器 (Chrome) 插件开发痛点:自定义热更新方案——1. 原理分析及构建部署实现

用户头像
梁龙先森
关注
发布于: 2020 年 11 月 06 日
解决大中型浏览器(Chrome)插件开发痛点:自定义热更新方案——1.原理分析及构建部署实现

Chrome扩展程序序列文章:

  1. 浏览器插件:那些你需会的操作

  2. 热更新方案设计:1.原理分析及构建部署实现

  3. 热更新方案设计:2.基于双缓存更新功能模块

背景

  1. chrome扩展程序是个以crx为后缀的文件,安装在chrome内核的浏览器,运行在独立的进程。它可以操作浏览器的外观,与浏览器交互、具备扩展实现能力。

  2. 扩展程序的发布更新,需要走chrome应用市场审核。当推送更新时,Google会自动更新用户的扩展程序,但是会有时滞(几个小时),前提是国内用户需科学上网。否则,只能自己去找包下载更新。

  3. 认知的渠道里没有找到相关chrome扩展程序脚手架。



作为一款版本快速更新迭代的扩展程序,立刻解决重大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资源文件的实现不做介绍,仅提供思路。



Chrome扩展程序开发文档可参考:http://chrome.cenchy.com/



二、设计项目结构规范和模板代码

  1. 项目代码结构

# 项目代码结构
/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代码文件。

  1. 功能模块模板代码

// 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文件

  1. 编写webpack.hotfix.js配置文件,用于读取需热更新的模块对象,并构建输出到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), // 打包后的文件存放的地方
}
})
  1. package.json文件配置执行命令

{
"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/文件夹,存放各热修复模块文件。

四、设计热更新代码自动部署

  1. 前后端定义协议报文

version: 当前程序版本号
name:模块名
type:热更新模块所属类型
content:模块代码
  1. 后端接口实现

接口收到数据,依次创建version/type/name.js文件,并写入content。

  1. node上传hotfix文件实现,直接贴代码

// 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)
})
}
  1. package.json新增执行指令

"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



原创作品,抄袭必究



发布于: 2020 年 11 月 06 日阅读数: 218
用户头像

梁龙先森

关注

寒江孤影,江湖故人,相逢何必曾相识。 2018.03.17 加入

1月的计划是:重学JS,点个关注,一起学习。

评论

发布
暂无评论
解决大中型浏览器(Chrome)插件开发痛点:自定义热更新方案——1.原理分析及构建部署实现