1. 背景
2. 问题
2.1 问题描述
2.2 问题分析
3. 优化方案
3.1 安装依赖阶段
3.2 打包编译阶段
4. 总结
作者简介
加入我们
1. 背景
达达快送是达达集团旗下中国领先的本地即时配送平台,在社会化同城配送的市场份额中位居第一。截至目前,业务覆盖全国2700多个县区市,日单量峰值超千万单,为数十万骑士创造了灵活工作机会。随着用户以及单量的日益增长,后台运营系统的功能也变得越来越多样与复杂。
目前整个后台运营系统前端代码量达到26万行左右,涉及的前端开发有20人左右。在这种庞大的项目中进行频繁迭代会遇到各种各样的困难,如发版过程长。达达后台运营系统前端一个需求上线需经历合代码、构建、灰度、全量四个步骤,至少需花费25分钟左右,其中构建时间10分钟左右。
2. 问题
2.1 问题描述
构建速度慢,导致以下问题:
开发过程中前后端协同效率低。
需求上线需要漫长的等待时间。
多个需求排队上线,常到晚上十一二点。
2.2 问题分析
达达后台运营系统前端原来的构建流程与耗时情况:
其中安装依赖和打包编译阶段耗费了大量的时间。我们提出的一个问题是,这两个阶段是否有优化空间呢?
3. 优化方案
3.1 安装依赖阶段
我们的项目使用的依赖管理工具是yarn,在执行yarn install的过程中经历了以下四个步骤:
整个工作流程大致如下
针对以上步骤我们可以的优化方向:
缓存node_modules
缓存node_modules
当在一个新的环境中进行构建时,这个环境中是没有node_modules的,就意味着需要重新下载项目依赖包到系统缓存,并将依赖包从缓存中复制到node_modules,这个过程非常的耗时。如果可以将其他环境中相同应用构建生成的node_modules拿过来使用,这将可以大大减少复制的量从而降低时间。具体的实现是:
在构建服务器中创建应用对应的缓存池(目前容量是6个,可视项目而定),当应用在环境1构建时,会查看缓存池中是否有空位,如果有空位,则将生成的node_modules复制(cp -r)到缓存池中;
新建一个环境时,先从缓存池中mv一个node_modules出来放到一个指定的目录下,然后将该node_modules软链到构建容器中的代码目录下。
这一步优化后,理论上在没有包更新的情况下install时间应该是1-2s,但实际却还是100s左右。
通过反复测试复制node_modules的命令,发现使用不同的命令得到的结果是不同的。下面来看下cp命令的使用方法
通过查阅资料以及阅读源码,分析到,yarn是会通过fs.Stats读取文件的更新时间来判断文件是否有发生修改。
我们copy node_modules到缓存池时使用的命令是cp -r,这个命令不会将文件的修改时间复制,导致了最终缓存失效,每次安装都重新从系统缓存拷贝全量的依赖到node_modules。
将cp -r替换为cp -a命令后,最终将install步骤的时间从100s降低为2s左右。
3.2 打包编译阶段
达达后台运营系统前端使用webpack和babel来进行项目打包编译。
webpack关键配置项介绍
entry:webpack 查找依赖的入口文件配置
output:webpack 输出
resolve:解析模块依赖的自定义项
module:模块
devtool:控制是否生成以及如何生成source map
loader:用于对模块的源代码进行转换
css-loader:对 @import 和 url() 进行处理
style-loader:将css添加到DOM中
babel-loader:用Babel将ES6的代码转换成ES5版本的
plugin:用于扩展webpack功能
define-plugin:定义环境变量
ignore-plugin:用于忽略部分文件
commons-chunk-plugin:提取公共代码
uglifyjs-webpack-plugin:通过UglifyJS压缩代码
hot-module-replacement-plugin:开启模块热替换功能
Babel工作原理
简单地说,Babel 能够转译 ECMAScript 2015+ 的代码,使它在低版本浏览器或者环境中也能够运行。
Babel的工作过程:
优化内容
通过构建速度分析工具分析,可以看到IgnorePlugin、babel-loader、sass-loader等耗费的时间比较长。
babel-loader缓存
babel-loader 中可使用 cacheDirectory 来配置开启缓存,默认是关闭缓存的。当我们配置了cacheDirectory时,loader会先查找缓存文件是否存在,文件名是通过下列方法计算得出。
可以看到,文件名是以源码source、loader选项options以及标识符identifier三个值的json字符串经过编码得到的,所以当这三个值任意一个 发生变化时,都会导致文件名发生变化。当缓存文件不存在,或者以上三个值发生变化导致缓存文件名变成一个不存在的文件时,会调用babel-core的transform方法进行编译
经排查发现,每个环境的node_modules目录路径都不相同,导致options的变化,导致最终缓存失效。
环境1:
环境2:
针对这个问题我们具体的优化是:
在每个构建的docker容器内创建相同的目录路径(/data/node_modules);
将磁盘中的node_modules文件目录挂载到容器内的/data/node_modules目录;
再将容器内的/data/node_modules与代码目录建立软链接,最终达到每个容器内使用的node_modules路径都是相同的。
其他优化
解决应用依赖重复打包问题。
引入微前端,拆分项目。
删除无用的resolve.extensions,加快查找速度。
将项目中引入的第三方库的scss文件改为css文件,减少sass的编译时间。
抽离一些库到CDN(如:moment、UI组件库等)。
去除sourceMap(后期将改为异步构建) 。
去除IgnorePlugin。
项目中IgnorePlugin只是用于处理moment的本地化,我们已将moment抽离为CDN引入,所以可以直接将这个plugin去除。
4. 总结
经过达达前端与云平台成员的共同努力,最终将项目的平均构建时间从600s降到100s左右 ,有效的提高了项目构建效率。
作者简介
夏志强:达达集团前端开发工程师,2021年加入达达集团,目前负责中后台前端组,目前致力于为业务赋能,给用户带来更好的体验。
杨婷婷:达达集团前端开发工程师,2019年加入达达集团,目前负责达达物流中后台计费模块的前端开发工作。
加入我们
达达集团目前还处于高速成长期, 欢迎对技术有追求的同学加入, 可查看链接
https://app.mokahr.com/su/zlrxpe 投递简历。