前几天晚上下班的时候, 路过隔壁项目组, 听他们在聊项目构建的事:javascript
如今线上打包时间太长了, 修个 bug 1 分钟, 发布一下半小时, 贼难受。
他们项目比较庞大, 线上构建时间特别长, 基本都在15分钟以上
。css
和他们简单聊了会, 回去瞅了一下本身项目的构建时间:html
其实也挺长的, 因而抽空优化了一下, 效果仍是比较明显的:java
在正文部分,我将分享的内容主要是:react
一些提高 webpack 打包性能的配置
优化大型项目构建时间的一些思考
但愿对你们有所启发。webpack
咱们项目不是很大, 是一个中型的国际化项目, 一百来个页面。 git
以前本地构建时间挺长的,初次启动要三次分钟, 后面我配置了 Vite
, 本地启动时间下降到了 20s
左右,感兴趣的能够移步我这篇文章:github
看了一下,线上构建时间五六分钟,不痛不痒,可是应该也有优化空间,因而准备优化一下。typescript
既然要优化构建时间, 第一步固然是先发现问题
, 找出比较耗时
的阶段,再加以优化。
这里我用到了SMP
插件。
SMP
插件用法很是简单, 这里也简单提一下:
// webpack.config.js const SpeedMeasurePlugin = require('speed-measure-webpack-plugin'); const smp = new SpeedMeasurePlugin(); module.exports = smp.wrap({ // ... });
利用 SMP
插件得出各个阶段的打包耗时:
发现两个比较明显的问题:
IgnorePlugin
耗时接近 20 秒。less-loader
部分执行了2次,浪费了一分多钟。ts-loader
耗时一分半, 也挺长的。查看了一下配置, 发现配置里的 IgnorePlugin
并无达到预期的效果, 删掉。
查看配置后发现, 在处理less
的部分,确实多处理了一遍。
less 文件的处理,能够直接看官方文档,文档地址:
https://webpack.docschina.org...
个人配置:
{ test: /\.less$/, use: [ 'style-loader', 'css-loader', { loader: 'less-loader', options: { javascriptEnabled: true, sourceMap: true, modifyVars: { // inject our own global vars // https://github.com/ant-design/ant-design/issues/16464#issuecomment-491656849 hack: `true;@import '${require.resolve('./src/vars.less')}';`, ...themeVariables, }, limit: 10000, name: '[name].[hash:7].[ext]', outputPath: 'styles/', }, }, ], }, { test: /\.css$/, use: ['style-loader', 'css-loader'], },
对于ts-loader
部分的优化, 能够参考:
https://webpack.js.org/guides...
文档上也有比较清晰的描述:
文档建议, 咱们开启transpileOnly
选项,关闭类型检查。
若是要类型检查, 可使用 ForkTsCheckerWebpackPlugin
,这个插件会在另一个进程中作相关的检查。
这个插件,咱们在优化构建时内存溢出的问题上, 也作了探索, 感兴趣的能够移步我这篇文章:
如今咱们也开启这个选项。
开启以后, 本地构建的时候, 本地报了个警告:
这个错误, 十分的眼熟, 是以前咱们讲过的 import type
的问题,
修复一下:
问题解决。
从新构建, 获得以下结果:
优化以后以后, 咱们发现:
IgnorePlugin、HtmlWebpackPlugin
时间大幅缩短。less-loader
等恢复了正常,只执行了一次。ts-loader
时间大幅缩短,由1分30秒缩短为40秒。本地效果明显,须要去线上构建验证。
在线上执行以后, 获得以下结果:
而后去检查了一下页面,也都是正常的。
完美!
回头看,不难发现,其实也没改多少东西, 就收获了不错的效果。
针对中小型项目来讲, 改改配置每每就能达到咱们的要求, 可是若是是面对大型项目
呢?
好比那种数十个模块, 几百个页面的项目。
回到开头那个问题: 修个 bug 1 分钟, 发布一下半小时
。
简单的修改配置, 都没法把时间降下来, 这时候该怎么办呢?
假设咱们有一个项目,大模块就有将近30个:
每一个大模块里面又有几十个页面,这种系统构建时间会比较久, 须要作优化。
并且到了项目后期,问题会愈来愈明显, 好比:
面对这种状况,一种可行的作法是:拆分子应用
。
拆分以后的架构:
每一个子项目都有单独的入口, 是能够独立部署的项目。
子项目打成单独umd
包:
在主项目启动的时候, 再去加载这些子项目:
加载完成以后, 须要处理路由
以及store
, 示例代码:
// base export const bootstrap = () => { // ... ReactDOM.render(( <Provider store={store}> <Router history={history}> <App defaultInitialData={_initialData} /> </Router> </Provider> ), document.getElementById('root')); return Promise.resolve(); }; // main const loadSubApp = (htmlEntry: string) => { return importHTML(`${htmlEntry}?${Date.now()}`) .then((res: any) => res.execScripts()) .then((exportedValues: any) => { console.log(`importHTML: ${htmlEntry} loaded, exports:`, exportedValues); const { store, router } = exportedValues || {} as any; router && addCustomRouter(router); store && addCustomStore(store); }) .catch(e => { console.error('importHTML: ${htmlEntry} load error:', e); }); }; const load = () => { if (__ENV__ !== 'dev') { const paths: string[] = []; subAppConfig.subApps.forEach(item => { if (item.project === localStorage.getItem('ops_project')) { paths.push(...item.paths); } }); Promise.all(paths.map(path => loadSubApp(path))) .catch(e => console.log(e)) .finally(setAllLoaded); } else { setAllLoaded(); } }; const init = () => { console.log('init: Start to bootstrap the main APP'); addCustomStore(rootStore); bootstrap().then(() => { load(); }); }; init();
common
包externals
给样式添加以子项目为名的 namespace
:
以 ops 项目为例。
让开发调试 ops-common 包像本地文件同样方便:
在同一个project上为每一个子项目申请独立module
优势:
独立发布
, 子模块和主模块解偶
。单独编译
的,主项目只须要作引入便可, 以此减小主模块的构建时间
。缺点:
通常来讲,对于中小型项目,作好打包配置的优化, 可以解决一部分问题。
大型项目的构建时间优化, 能够考虑拆分子应用的模式。
只不过这种模式须要考虑一些维护
的问题,好比如何维护版本 tag、如何快速回滚等。
这些须要结合大家项目的实际状况
再作决定。
今天的内容就这么多,但愿对你们有所启发。
祝你们五一快乐~~
若是以为文章内容有帮助, 能够关注下我哦, 掌握最新动态。
也能够加我微信 「 scaukk 」, 一块儿探讨。