「项目实战」优化项目构建时间

开发 项目管理
一般来说,对于中小型项目,做好打包配置的优化, 能够解决一部分问题。大型项目的构建时间优化, 可以考虑拆分子应用的模式。

[[399761]]

背景

前几天晚上下班的时候, 路过隔壁项目组, 听他们在聊项目构建的事:

现在线上打包时间太长了, 修个 bug 1 分钟, 发布一下半小时, 贼难受。

他们项目比较庞大, 线上构建时间特别长, 基本都在15分钟以上。

和他们简单聊了会, 回去瞅了一下自己项目的构建时间:

其实也挺长的, 于是抽空优化了一下, 效果还是比较明显的。

优化后:

在正文部分,我将分享的内容主要是:

  • 一些提升 webpack 打包性能的配置
  • 优化大型项目构建时间的一些思考

希望对大家有所启发。

 [[399762]]

正文

我们项目不是很大, 是一个中型的国际化项目, 一百来个页面。

之前本地构建时间挺长的,初次启动要三次分钟, 后面我配置了 Vite, 本地启动时间降低到了 20s 左右,感兴趣的可以移步我这篇文章:

[项目实战] Webpack to Vite, 为开发提速!

看了一下,线上构建时间五六分钟,不痛不痒,但是应该也有优化空间,于是准备优化一下。

1. 发现问题

既然要优化构建时间, 第一步当然是先发现问题, 找出比较耗时的阶段,再加以优化。

这里我用到了SMP 插件。

SMP 插件用法非常简单, 这里也简单提一下:

  1. // webpack.config.js 
  2. const SpeedMeasurePlugin = require('speed-measure-webpack-plugin'); 
  3. const smp = new SpeedMeasurePlugin(); 
  4.  
  5. module.exports = smp.wrap({ 
  6.   // ... 
  7. }); 

利用 SMP 插件得出各个阶段的打包耗时:

发现问题

发现两个比较明显的问题:

  1. IgnorePlugin 耗时接近 20 秒。
  2. less-loader 部分执行了2次,浪费了一分多钟。
  3. ts-loader 耗时一分半, 也挺长的。

2. 解决问题

1. IgnorePlugin

查看了一下配置, 发现配置里的 IgnorePlugin 并没有达到预期的效果, 删掉。

2. less-loader

查看配置后发现, 在处理less的部分,确实多处理了一遍。

less 文件的处理,可以直接看官方文档,文档地址:

https://webpack.docschina.org/loaders/less-loader/

我的配置:

  1.   test: /\.less$/, 
  2.   use: [ 
  3.     'style-loader'
  4.     'css-loader'
  5.     { 
  6.       loader: 'less-loader'
  7.       options: { 
  8.         javascriptEnabled: true
  9.         sourceMap: true
  10.         modifyVars: { 
  11.           // inject our own global vars 
  12.           // https://github.com/ant-design/ant-design/issues/16464#issuecomment-491656849 
  13.           hack: `true;@import '${require.resolve('./src/vars.less')}';`, 
  14.           ...themeVariables, 
  15.         }, 
  16.         limit: 10000, 
  17.         name'[name].[hash:7].[ext]'
  18.         outputPath: 'styles/'
  19.       }, 
  20.     }, 
  21.   ], 
  22. }, 
  23.   test: /\.css$/, 
  24.   use: ['style-loader''css-loader'], 
  25. }, 

3. ts-loader

对于ts-loader部分的优化, 可以参考:

https://webpack.js.org/guides/build-performance/#typescript-loader

文档上也有比较清晰的描述:

文档建议, 我们开启transpileOnly选项,关闭类型检查。

如果要类型检查, 可以使用 ForkTsCheckerWebpackPlugin,这个插件会在另外一个进程中做相关的检查。

这个插件,我们在优化构建时内存溢出的问题上, 也做了探索, 感兴趣的可以移步我这篇文章:

项目构建内存溢出了?看看 Node 内存限制

现在我们也开启这个选项。

开启之后, 本地构建的时候, 本地报了个警告:

这个错误, 十分的眼熟, 是之前我们讲过的 import type 的问题:

你不知道的 「 import type 」

修复一下: 

问题解决。

重新构建, 得到如下结果:

优化之后之后, 我们发现:

  • IgnorePlugin、HtmlWebpackPlugin 时间大幅缩短。
  • less-loader 等恢复了正常,只执行了一次。
  • ts-loader 时间大幅缩短,由1分30秒缩短为40秒。

本地效果明显,需要去线上构建验证。

3. 确认有效

在线上执行之后, 得到如下结果:

然后去检查了一下页面,也都是正常的。

完美!

回头看,不难发现,其实也没改多少东西, 就收获了不错的效果。

针对中小型项目来说, 改改配置往往就能达到我们的要求, 但是如果是面对大型项目呢?

比如那种数十个模块, 几百个页面的项目。

回到开头那个问题:修个 bug 1 分钟, 发布一下半小时。

简单的修改配置, 都无法把时间降下来, 这时候该怎么办呢?

优化大型项目构建时间的一些思考

拆分子应用

假设我们有一个项目,大模块就有将近30个:

每个大模块里面又有几十个页面,这种系统构建时间会比较久, 需要做优化。

而且到了项目后期,问题会越来越明显, 比如:

  • 代码越来越臃肿
  • 业务模块本身无关联
  • 构建速度越来越慢
  • 无法独立部署

面对这种情况,一种可行的做法是:拆分子应用。

拆分之后的架构:

每个子项目都有单独的入口, 是可以独立部署的项目。

子项目打成单独umd包:

在主项目启动的时候, 再去加载这些子项目:

加载完成之后, 需要处理路由以及store, 示例代码:

  1. // base 
  2. export const bootstrap = () => { 
  3.   // ... 
  4.   ReactDOM.render(( 
  5.     <Provider store={store}> 
  6.       <Router history={history}> 
  7.         <App defaultInitialData={_initialData} /> 
  8.       </Router> 
  9.     </Provider> 
  10.   ), document.getElementById('root')); 
  11.   return Promise.resolve(); 
  12. }; 
  13.  
  14. // main 
  15. const loadSubApp = (htmlEntry: string) => { 
  16.   return importHTML(`${htmlEntry}?${Date.now()}`) 
  17.     .then((res: any) => res.execScripts()) 
  18.     .then((exportedValues: any) => { 
  19.       console.log(`importHTML: ${htmlEntry} loaded, exports:`, exportedValues); 
  20.       const { store, router } = exportedValues || {} as any
  21.       router && addCustomRouter(router); 
  22.       store && addCustomStore(store); 
  23.     }) 
  24.     .catch(e => { 
  25.       console.error('importHTML: ${htmlEntry} load error:', e); 
  26.     }); 
  27. }; 
  28.  
  29. const load = () => { 
  30.   if (__ENV__ !== 'dev') { 
  31.     const paths: string[] = []; 
  32.     subAppConfig.subApps.forEach(item => { 
  33.       if (item.project === localStorage.getItem('ops_project')) { 
  34.         paths.push(...item.paths); 
  35.       } 
  36.     }); 
  37.     Promise.all(paths.map(path => loadSubApp(path))) 
  38.       .catch(e => console.log(e)) 
  39.       .finally(setAllLoaded); 
  40.   } else { 
  41.     setAllLoaded(); 
  42.   } 
  43. }; 
  44.  
  45. const init = () => { 
  46.   console.log('init: Start to bootstrap the main APP'); 
  47.   addCustomStore(rootStore); 
  48.   bootstrap().then(() => { 
  49.     load(); 
  50.   }); 
  51. }; 
  52.  
  53. init(); 

代码共享

  • common包
  • component
  • utils
  • typings
  • ..
  • externals
  • react全家桶
  • moment
  • antd
  • ..

样式隔离

给样式添加以子项目为名的 namespace :

开发调试

以 ops 项目为例。

让开发调试 ops-common 包像本地文件一样方便:

1.让项目来编译 common 包

2.wepback alias

3.TS alias

独立部署

在同一个project上为每个子项目申请独立module

拆分子应用的优缺点

优点:

每个子应用都可以独立发布, 子模块和主模块解耦。

子项目是可以单独编译的,主项目只需要做引入即可, 以此减少主模块的构建时间。

缺点:

额外的复杂性和维护成本

结论

一般来说,对于中小型项目,做好打包配置的优化, 能够解决一部分问题。

大型项目的构建时间优化, 可以考虑拆分子应用的模式。

只不过这种模式需要考虑一些维护的问题,比如如何维护版本 tag、如何快速回滚等。

这些需要结合你们项目的实际情况再做决定。

今天的内容就这么多,希望对大家有所启发。

 

责任编辑:姜华 来源: 前端皮小蛋
相关推荐

2024-01-30 08:58:22

JenkinsGit流程

2022-01-07 15:11:27

项目Go 框架

2013-01-21 10:19:50

JavaScript项目管理JS

2022-01-06 11:08:34

JekinsDockerReact

2013-07-18 17:00:12

Gradle构建AndAndroid开发Android学习

2012-08-15 09:22:49

JavaScript

2019-12-05 09:50:54

GitHub 技术深度学习

2022-12-30 08:17:28

Gradle多模块项目

2020-07-17 19:55:50

Vue前端性能优化

2017-07-28 11:31:59

iOS结构优化项目

2013-05-07 09:58:20

RequireJS优化RequireJS项目

2014-09-29 09:31:35

Angular

2014-03-20 09:34:13

项目项目评估

2012-05-15 02:54:31

项目开发项目

2021-09-26 10:20:06

开发Golang代码

2015-03-09 15:13:33

Java项目构建系统Apache Buil

2018-04-19 15:13:53

javascriptwebpackvue.js

2024-03-01 19:53:37

PyBuilderPython开发

2013-05-23 10:39:29

MVC架构MVC项目搭建

2024-01-31 08:41:43

异步设计项目
点赞
收藏

51CTO技术栈公众号