今天周五了,HAPPY!!
记录一下对旧项目开发环境改造过程,提升开发体验和开发效率。 仅记录大致思路,不贴太多代码。
公司有一个5年前的项目,是一个js + jq + es5 + webpack4的旧项目。别看它使用了webpack4,它利用webpack所做的功能仅仅是将多个js打包成一个,仅此而已。它改完代码不能自动打包、不能自动刷新浏览器、不支持HMR、打包时间久、没有CICD、需要打包发布到服务器上,开发体验很差,开发效率极低。
之前对这个项目的维护并不多,他是属于另一个部门负责的,今年移交到我们部门,我负责整个部门前端会时不时抽空改造一些历史问题,提升团队效率。
前前后后我花了3次去改造,最终实现了毫秒级热更新、并且支持热模块替换、自动发布,前端同学直呼YYDS
项目介绍
一个复杂的H5展示类型的框架,会有7套皮肤,以及支持不同设备:手机、pc、安装平板、iPad。数据只会有一份,但每一套皮肤都会打包对应的js文件,引入不同的js达到使用不同皮肤的目的。月活:pv 130万,uv:15万。
老项目开发的流程
- 项目打包的结果输出在bin目录中
- 在bin目录放入index.html等相关内容
- 改完代码,需要手动执行打包 ---> 等待8秒打包完成 --> 手动刷新浏览器,整个过程花费了10几秒钟,电脑配置差的,那是更久,这简直是道德的沦丧、人性的扭曲!
- 发布的过程也很曲折,由于框架有两种不同的引用方式,需要手动打包两次,然后将它上传到OSS上,这样的话就完成了。(其它所有的H5都会引用到这个地方来)
第一次优化:自动发布
我们发布框架的方式不适合使用CICD,需要将框架上传到OSS,可以将框架应用到所有H5项目,也可单独应用于某个项目,并且需要有日志记录。所以,我将这个手动打包上传和应用的流程自动化。
流程如下:执行一个命令 --> 自动打包 --> 打包完成 --> 压缩成zip --> 在终端输入账号密码、描述 --> 上传到oss --> 打开浏览器到指定页面
将流程自动化的目的除了方便高效,还有另外一个原因:不容易将我们的时间碎片化。因为每个环节都需要有一定的等待时间,当我们在等待打包的时候为了不浪费时间去等待,就会去做其它事情,过一段时间又回过头来执行下一个阶段,甚至还有可能忘记自己在打包了。
我把打包的操作封装到了js中,再使用node去执行脚本。
在package.json 新增2条命令,打包发布到测试环境,打包发布到正式环境:
{
"scripts": {
"build:test": "cross-env buildType=test node './build/index.js' ",
"build:prod": "cross-env buildType=prod node './build/index.js' "
},
}
组装命令,然后使用child_process来执行命令:
// 1、封装 processUtil.js,执行终端命令,并且将终端的输出打印出来
const childProcess = require('child_process')
module.exports.execCmd = function (cmd, successCallback, errorCallback, dataCallback) {
let processWorker = childProcess.exec(cmd, {maxBuffer: 10240 * 10240}, function (error, stdout, stderr) {
console.log('error:' + error)
successCallback && successCallback(stdout)
})
processWorker.stderr.on('data', function (data) {
console.log(data)
errorCallback && errorCallback()
})
processWorker.stdout.on('data', function (data) {
console.log(data)
dataCallback && dataCallback()
})
}
// 2、通过 process.env.xxx 拿到在终端输入的参数组装打包命令
const cmd = 'cross-env frame=xxx outPutPath=xxx npx webpack -p'
// 3、执行命令打包
processUtil.execCmd(cmd, function () {
// 打包完成
// 使用 compressing 库对文件夹进行压缩
// 使用 axios 进行上传
// 使用 open 库打开浏览器指定页面
})
通过以上思想,就实现了一条命令自动实现打包发布的流程。
第二次优化:手写热更新
由于我需要在这个项目开发很多功能,想要高效一点,我花了1天时间去实现自动编译、自动刷新浏览器。
想达到的目的是:改完代码,自动编译,自动刷新浏览器。
我对这个项目只掌握了7成,不敢贸然改动太多,一旦出问题会影响十几万用户,所以我很保守地使用原来的方式去打包,但是把流程自动化了。
流程如下:
- 以前打包会打包所有皮肤的内容,而现在需要按需打包,调试某个皮肤就只打包某个皮肤,这样速度更快;
- 由于HTML引入多个js,即webpack entry 有多个入口,需要检测改动的是哪个js,仅打包对应入口文件,减少冗余打包;
- 本地开一个服务,服务路径指向bin,第一次打包就自动开启服务并且打开浏览器,之后每当bin目录下面文件有变化就自动刷新浏览器
01. 新建命令如: npm run dev
02. 执行命令后,检测目录是否存在index.html
// 检测文件是否存在
function checkFileExist(fileUrl) {
return new Promise((resolve, reject) => {
fs.access(fileUrl, fs.constants.F_OK, (err) => {
if (err) {
resolve(false)
} else {
resolve(true)
}
})
})
}
03. 从index.html中通过正则匹配用来某个皮肤,并将皮肤类型当做命令的参数:skin=xxx
04. 打包
新建一个文件 webpack.entyrs.js 用于存放所有entry,这个文件居然多达800行!!!
webpack.config.js 的entry通过上一步获取到的皮肤类型,获取需要打包的文件。
组装打包命令,然后使用child_process执行命令
05. 启动服务器
本地服务我使用了一个叫做live-server的库
function runServer() {
const params = {
port: 8091,
root: path.join(__dirname, '../bin/'),
open: true,
file: 'index.html',
}
liveServer.start(params)
}
06. 监听文件的改动
监听文件我使用chokidar,这个库监听速度非常快,vite也是使用这个库。
当我代码保存,监听到文件变化,获取相应入口,自动执行打包命令,打包完成后,bin目录文件就改变了,这时候服务监听到了文件变化,浏览器就会刷新
自此,自动编译和刷新的功能就实现了。这个功能虽然非常简陋,但是却很有用,每次保存代码3秒内就会自动刷新浏览器,已经给我节省了很多时间了。
第三次优化:毫秒级热更新、HMR(热模块替换)
几个月过后。。。。。。。
我日常上班开发,都是使用我自己的m1 pro芯片的MacBook Pro,速度真的非常快,其它同学都是使用Intel的 iMac,即使我知道手动写了热更新功能,他们电脑速度还是很慢。我决定继续优化。
这一次我舍弃原来手动写的热更新功能,使用webpack的能力:
- webpack-dev-server
- HardSourceWebpackPlugin
- HtmlWebpackPlugin
- webpack-merge
新建一个js文件,runServer.js,作为命令的入口。这个操作为了检查bin目录下面有无index.html文件,如果没有就报错。
async function start() {
const isFileExit = await file.checkFileExist(`${binPath}/index.html`)
if (!isFileExit) {
console.log(chalk.bgRedBright('启动失败,需要将【资源】放在bin目录下'))
return
}
runServe()
}
function runServe() {
// 组装命令,配置文件使用webpack.dev.config.js
const cmd = `cross-env frame=relative skinType=none outPutPath='./bin' npx webpack-dev-server --config webpack.dev.config.js`
processUtil.execCmd(cmd)
}
新建文件webpack.dev.config.js,用于存放开发环境webpack的配置。
这个项目有个不一样的地方,比如会打包:A B C D E文件,html只会引入A和B,剩下的C D E js文件,会在A或B中,点击某个按钮的时候才去加载对应js,然后再执行对应的事件。所以,在HtmlWebpackPlugin中,需要在chunks中排除掉这些文件(CDE),由于template中已经引入了对于js文件了,故HtmlWebpackPlugin完全不需要将任何js写入index.html中。
其实,他这么做是为了按需加载,减少启动内容,提高页面启动速度。现在我们已经不会这么去做了,这个方法太古老了。
const merge = require('webpack-merge')
const HtmlWebpackPlugin = require('html-webpack-plugin')
const webpackbaseConfig = require('./webpack.config')
const webpackEntry = require('./webpackEntry')
functin getEntry() {
.....
}
// 这份配置是在原来的基础上增加了dev的配置,故将原来配置合并过来
module.exports = merge({
entry: getEntry(), // 从html去读取需要打包的文件
plugins: [
new HtmlWebpackPlugin({
template: __dirname + '/bin/index.html',
chunks: [], // 因为index.html里面已经引入了对应的chunks,所以不需要再往里面填写内容了,并且有很多chunks不能往里面写,因为他仅仅是用于被主js文件按需加载
}),
],
devServer: {
contentBase: path.join(__dirname, 'bin'),
port: 9200,
hot: true,
open: true, // 开启热模块替换,在改css的时候就不需要刷新浏览器
watchContentBase: true, // 检测bin目录的变化,当我们改动html的时候,会重新编译和刷新浏览器
},
}, webpackbaseConfig)
新配一个命令:
"serve": "node './build/runServer.js' ",
执行命令,进行测试,确实是快了不少,我电脑更新的速度在1s左右,但其实这还不够,我很多项目一般都在300ms左右。经过研究,发现webpack4并不会开启缓存,需要手动安装hard-source-webpack-plugin插件来实现,安装完之后,再次测试。第一次启动确速度还是一样,但是第二次开始速度就非常快快了,基本控制在300ms左右。
自此,这个项目的开发体验得到了很大提升。早期维护的同学说之前需要几十秒,而我优化后,在他的破电脑也只需要1秒,速度提升几十倍,但是连贯的开发体验,带来愉悦的心情,整体开发体验提升1000倍,不过分。
总结
大家可以发现,其实我们实现的过程并不非常困难,只要了解node.js、webpack和自身项目,完全就可以配出来。但这个项目经手这么多人,这么差的开发体验,这么低的开发效率,居然没人一个人去改进它。
我问了大家,大家都说不会做,不知道怎么去优化。
- 其实很显然,大家对于webpack还没掌握(我也会抽空给组员进行培训)
- 大家没解决困难的决心,执行力低