经验首页 前端设计 程序设计 Java相关 移动开发 数据库/运维 软件/图像 大数据/云计算 其他经验
当前位置:技术经验 » 其他 » 职业生涯 » 查看文章
如何提升项目的本地构建效率?
来源:cnblogs  作者:前端南玖  时间:2023/5/29 14:11:13  对本文有异议

前言

最近写H5的项目比较多,该项目从年龄上看着还算比较年轻??,整个架构应该是直接使用vue-cli基于vue2生成的,那底层打包工具自然也就是webpack,我们知道webpack有个通病,那就是随着项目的不断增大每次构建的时间也会随之越来越长。比如我们这个项目的单次冷启动就达到了惊人的1分20秒左右,每次跑完电脑风扇转的飞起,简直忍不了!(可能是电脑太老了)

下面一起看看如何将项目的冷启动时长从1分20秒左右优化到十几秒左右吧~

是什么让构建效率这么慢?

页面数量

由于我们这个项目是个SPA项目,路由是通过vue-auto-routing来自动生成的。为了更直观的看到里面有多少个页面,于是我把routes打印出来了。

build-1.png

居然有258个之多!页面这么多,webpack打包构建的速度自然就会慢。

很好奇的一点这么多页面都是线上在跑的?

时间都用在哪?

为了对项目做一些有针对性的优化,我们需要了解整个编译过程中耗时分布,知道了各模块的耗时数据我们才能对症下药。

这里可以使用speed-measure-webpack-plugin插件来进行分析。

speed-measure-webpack-plugin不仅可以分析总的打包时间,还能分析各阶段loader 的耗时。

  1. // 使用
  2. const SpeedMeasurePlugin = require('speed-measure-webpack-plugin')
  3. const plugins = [
  4. // ...
  5. new SpeedMeasurePlugin(),
  6. ]

build-2.png

从上图来看,编译过程中的大部分时间都是用在vue文件的编译处理loader上。说白了还是文件太多了导致编译耗时比较长。

如何优化?

通用方案

开启缓存

webpack 中几种缓存方式:

  • cache-loader
  • hard-source-webpack-plugin
  • babel-loader 的 cacheDirectory 标志

我们这个项目使用的vue-cli版本是4.1.0,它已经内置了 cache-loader 和 **babel-loader 的 cacheDirectory 标志**两种缓存

我们可以二次启动看看

build-3.png

二次启动花费了大概43秒,提升还是蛮大的,主要原因是 "冷启动" 时已经将 babel-loader、vue-loader 进行了缓存:

build-4.png

另外一种缓存可自行测试 hard-source-webpack-plugin,主要缓存这种方案只在二次启动才能有明显的性能提升,与我首次冷启动就要**快**的预期不符。这种方案这里就不再试了

开启多线程

由于js单线程的特点,当有多个任务同时存在,它们也只能排队串行执行。

所以有没有可以使用类似web Worker的技术实现多线程编译处理,将部分任务分解到多个子进程中去并行处理,子进程处理完成后把结果发送到主进程中,从而减少总的构建时间。

可选方案:

  • thread-loader(官方推出)
  • parallel-webpack
  • HappyPack

从上面可以发现,编译过程,大部分时间都是在处理vue文件,所以可以针对vue-loader使用thread-loader

  1. {
  2. test: /\.vue$/,
  3. include: path.resolve('src'),
  4. use: [
  5. {
  6. loader: 'thread-loader',
  7. options: {
  8. workers: 2,
  9. },
  10. },
  11. ],
  12. },

注意:仅在耗时的操作中使用 thread-loader,否则使用 thread-loader 会后可能会导致项目构建时间变得更长,因为每个 worker 都是一个独立的 node 进程,创建worker的过程也是耗时的,尽量不要得不偿失。

此时的编译时间为41秒左右,提升好像并不是特别明显,可能在大型项目中才会发挥出更大的作用。

build-5.png

当然还有很多方案可以一一尝试,但我觉得达到的效果应该都不会超过下面这个针对性方案。

针对性方案

该方案其实就是缩小我们的构建目标,整个项目虽然有很多页面,从上面路由来看多达258个,但我们平时在开发过程中其实只关注我们当前需要修改的页面,所以有没有可能在开发过程中,我只构建我需要用的页面,对于那些不需要的页面不参与构建,这样的话肯定能够大幅提升我们的本地构建时间。

这里还需要考虑的是,怎么对原有构建代码的侵入性做到最小?

思路

  • 新增构建脚本,原有npm run dev保持不变
  • 处理需要启动的页面,生成对应的路由routes.dev.js
  • 把原有routes提取成文件routes.pro.js
  • 再通过NormalModuleReplacementPlugin插件在编译过程进行文件替换
  • 最后再进行构建

构建脚本

新增start命令

  1. // package.json
  2. "start": "node ./build/cli.js start",

主要构建代码如下

  1. // cli.js
  2. const shell = require('shelljs')
  3. const path = require('path')
  4. const fs = require('fs')
  5. const action = process.argv[2]
  6. const arg = process.argv.slice(3)
  7. let appName = arg[0] // 指的是你要启动的项目(文件夹名)
  8. // const startPath = arg.join('/')
  9. console.log('????------start------????')
  10. ;(() => {
  11. if (!appName) {
  12. // 未输入项目名称则开启交互命令行
  13. openInquirer()
  14. return
  15. }
  16. // 启动
  17. if (action === 'start') {
  18. start()
  19. }
  20. })()
  21. function start() {
  22. // console.log('启动项目')
  23. process.env.action = 'signle'
  24. runTask(appName)
  25. }
  26. // 启动项目
  27. async function runTask(appName) {
  28. const cmds = []
  29. console.log(`??【启动项目】${appName}`)
  30. generateRoute(appName) // 生成需要启动的路由
  31. const runProPath = path.resolve(__dirname, `../src/pages/${appName}`)
  32. // if (process.platform === 'win32') {
  33. // cmds.push(`set runProPath=${runProPath}`)
  34. // } else {
  35. // cmds.push(`export runProPath=${runProPath}`)
  36. // }
  37. // 检测项目是否存在
  38. const res = await getProject(runProPath)
  39. if (res.errno < 0) {
  40. // 抛出异常
  41. throw new Error('没有找到可启动的项目??')
  42. } else {
  43. cmds.push(`npx vue-cli-service serve --open --colors --mode dev`)
  44. }
  45. const cmd = cmds.join(' && ')
  46. // return
  47. const { code } = shell.exec(cmd)
  48. return code
  49. }

处理需要启动的页面

由于这个项目是用vue-auto-routing来自动生成路由的,所以这里我依然还是用它内部的一个库来自动生成

  1. const { generateRoutes } = require('vue-route-generator')
  2. // 处理需要启动的路由
  3. function generateRoute() {
  4. console.log('--', path.resolve(__dirname, `../src/pages/${appName}/`))
  5. const code = generateRoutes({
  6. pages: path.resolve(__dirname, `../src/pages/${appName}/`),
  7. importPrefix: `@/pages/${appName}/`,
  8. })
  9. fs.writeFileSync(path.resolve(__dirname, `../src/routes.dev.js`), code)
  10. }

替换需要启动的路由

根据用户输入的需要启动的文件夹名,我们为这个文件夹内的所有文件自动成了路由文件routes.dev.js,现在需要做的是通过webpack进行替换。

  1. new webpack.NormalModuleReplacementPlugin(
  2. /src\/routes.pro.js/,
  3. './routes.dev.js',
  4. ),

使用

主要的工作完成,现在可以来启动试一试

比如:启动某**项目

  1. # 启动命令 npm start + 项目名称(文件夹名)
  2. npm start campusArea

build-6.png

现在的启动时间大概在15秒左右,这与你当前文件夹下的文件数量有关,文件越少启动越快!二次启动时间大概在10秒左右,小项目首次启动时长大概都在10秒内

首次冷启动时长大概节省了1min,写代码的时间又变多了??

优化

可能大家都习惯了npm run devnpm start,会忘记启动页面的参数?

不要急,这一点也考虑进去了,有个非常强大的库inquirer可以为我们开启交互式命令行。

  1. // 未输入项目名称则开启交互命令行
  2. function openInquirer() {
  3. // 获取所有可启动目录
  4. const projectList = fs.readdirSync(path.resolve(__dirname, '../src/pages'))
  5. // console.log('projectList', projectList)
  6. const promptList = [
  7. {
  8. type: 'list',
  9. message: '??请选择启动的目录:',
  10. name: 'pro',
  11. choices: [...projectList],
  12. },
  13. ]
  14. inquirer.prompt(promptList).then((answers) => {
  15. console.log(answers)
  16. appName = answers.pro
  17. start()
  18. })
  19. }

当你直接npm start的时候,可以让你选择你想要启动的目录:

build-7.png

结束。

原文首发地址点这里,欢迎大家关注公众号 「前端南玖」,如果你想进前端交流群一起学习,请点这里

我是南玖,我们下期见!!!

原文链接:https://www.cnblogs.com/songyao666/p/17440267.html

 友情链接:直通硅谷  点职佳  北美留学生论坛

本站QQ群:前端 618073944 | Java 606181507 | Python 626812652 | C/C++ 612253063 | 微信 634508462 | 苹果 692586424 | C#/.net 182808419 | PHP 305140648 | 运维 608723728

W3xue 的所有内容仅供测试,对任何法律问题及风险不承担任何责任。通过使用本站内容随之而来的风险与本站无关。
关于我们  |  意见建议  |  捐助我们  |  报错有奖  |  广告合作、友情链接(目前9元/月)请联系QQ:27243702 沸活量
皖ICP备17017327号-2 皖公网安备34020702000426号