头图

NEJ Build太慢怎么办?试试MOOC NEJ吧,只需两步,提升70%构建性能!

由于历史包袱,中国大学MOOC(简称中M)的主站工程生产构建时间大约在21分钟,构建采用NEJ Build,由于NEJ当前已无人维护且在部门内应用较多,因此中M通过fork原工程在保留NEJ原有功能的前提下,将NEJ的核心打包流程进行改造升级,并提出一套通用解决方案即MOOC NEJ。MOOC NEJ已于今年年初上线验证,构建时长缩短至6分钟,提升70%构建性能,经过8个月的线上稳定运行,暂未发现引迁移引起的问题。

一、历史背景

中国大学MOOC(以下简称中M)和许多网易前端er一样在过去几年搭建工程使用的框架为RegularJS与NEJ,同时在打包上采用NEJ的工具集toolkit2即NEJ Build(https://github.com/genify/toolkit2

在项目的开始总是愉快的,那时候开发维护NEJ的人还在(甚至还可以提一些feature),业务项目的人也还在,代码量总是清清爽爽,构建的时光总是“咻”一下就过去了。
在这里插入图片描述
然而!时间过去了三五年,当我接手到中M主站这个项目的时候,它的构建时间已经达到了将近21分钟
在这里插入图片描述
平时等就等呗,最多在后端大佬部署的时候提前构建好等着,要命的是上完线已经10点了,这时候测试大佬来报:后端没问题了,前端这里有个问题修一下。

然后,大概就是修复5分钟,打包验证半小时。。。。。同时保佑不要再出(deng)一(ban)个(ge)问(xiao)题(shi)。

在这里插入图片描述

二、究竟为啥这么慢?

速度慢的原因有两方面:

1. 中M工程架构:
由于历史原因,中M的主站将web和mobile端以及一并放在front-main项目下,同时,加载了两个看不出来有什么区别的超大lib,导致需要打包的文件非常多,东西多了自然就蚌埠住了。
在这里插入图片描述
2. NEJ合并策略
toolkit有一个合并策略的参数来决定一个文件引用计数超过多少次将合并入core.js,当这个参数设置的越大,core.js的size将越小,构建时间就越长。我们之前为了快速提高开发效率,在测试环境将该参数调至6,打包时间约11分钟,而预发和线上该参数在13,打包时间约21分钟。(毕竟我们不能因为想要打包快,就粗暴的使生产环境的资源变大😖)

三、NEJ的合并策略为何影响打包速度【核心原理】

由于NEJ已早早无人维护,但万幸它是开源的,咱们虽然找不到作者,但是可以通过对toolkit2的源码阅读来找到答案,我大致梳理了一下NEJ打包的流程。
流程图

  1. toolkit2全过程使用同步打包,每一步的处理结果都通过one by one的接力形式来传递。
  2. toolkit2打包的本质是通过对合并策略(就是二.2提到的参数)等参数将每个文件的代码转成抽象语法树(AST),再对AST通过UgilifyJS进行压缩混淆。

看起来这个流程没什么问题,因为AST和UgilifyJS在现在也都是很主流的操作(如webpack也是使用UgilifyJS),对前端来讲是再熟悉不过了。

那么为什么会导致打包慢呢?直到我看到了AST的生成过程:

  1. 先将所有文件通过concat生成一个大AST(源码的lib/adapter/script.js的_mergeCodeAndToAST
    在这里插入图片描述
  2. 对且仅对这一个AST进行Uglify压缩混淆(源码的lib/adapter/script.js的parse)在这里插入图片描述

结合中M主站的历史架构,当我打印出这一个AST时,
单主站web端的Core.js就大概由400个小AST形成。。。。
然后它就开始压缩。。。。
然后它就卡住了。。。。住了。。。。了。。。。在这里插入图片描述

四、解决方案考量

针对当前我们对自身工程和NEJ的了解,我们大概有以下几个思路去对打包时间进行优化:

【针对中M主站工程】

  1. 老生常谈的拆分工程&重构迁移:拆分需要对业务足够了解,才能对其做领域拆分。重构代价太大。同时功能回归点太多。目前一部分功能由于迭代需要已用React+Webpack做了替换。
  2. webpack替换nej:需要兼容regular和nej模块,且对老项目内部也要替换webpack所需插件,成本较大。

【针对NEJ工具集】

  1. Uglify2.0升级3.0:3.0有一个快速打包模式,但经过测试,3.0有部分不向下兼容的api,nej有用到。(此处有踩坑经验)
  2. 多进程打包:不改造打包产物,只变更打包流程,成本可控,上线后风险较小,如遇到紧急问题可快速回滚至nej build,风险可控

最终,我们选择了给NEJ架上webpack同款多进程功能,打包需要什么,我们就造什么

五、实现架构

我们并不想多造一个轮子,来做一些颠覆性的改变,使性能得到提升的同时,写的人也难受(考虑太多写一些冗余代码),使用的人也难受(迁移成本高)。
因此我们选择将toolkit2 fork下来,将其所有的api保留、功能保留,不影响任何老功能的使用姿势,只修改关键路径,缩小改造和迁移成本。

最后生成的库是:@edu/toolkit3
http://npm.hz.netease.com/package/@edu/toolkit3

我们的大致思路是这样:
在这里插入图片描述

  1. 不对ast做concat操作,每个文件单独uglify
  2. 参考uglify-webpack-plugin插件的多进程思路,将每个文件作为task,并发打包
    (我又去看了一下uglify-webpack-plugin的源码,做了一张图,毕竟知己知彼才能顺利改造。)
    在这里插入图片描述
  3. 将同步流程改成异步,完成所有task后回调结果,调用后续操作

六、代码实现

fork目录后,新增一个cluster文件夹用于存放多进程流程。

在这里插入图片描述

  1. minifiy.js:执行ugilify压缩操作,生成压缩混淆后的ast和string代码;
  2. TaskRunner.js:利用worker-farm来进行任务的分发、执行、计数、回调等;
  3. worker.js: worker-farm必须要新建一个workerfile才能使用,这个workerfile用于承接;
再配合修改原文件lib/adapter/script.js
  1. _mergeCodeAndToAST:取消concat以及this.ast的生成;
  2. parse:增加callback参数,根据filename、file、minify参数创建task,run each task,并执行callback;
配合修改原文件lib/deploy.js
  1. _afterResPrepared:将最后的embed(将变量嵌入css、js、html)操作改成异步,作为callback传入,等待完成所有的压缩后,执行最后输出前的embed操作。
    (详细代码可以前往仓库查看)

七、使用方式&迁移(安利)

说了这么久,终于要开始迁移,还记得标题吗,

只需两步!无忧迁移!
不影响任何老功能的使用!

第一步:安装mooc-nej(目前稳定版本在0.3.0,0.4.0尚属beta版本,正在内测中)

npm install @edu/toolkit3 -g

第二步:使用它,甚至不用修改命令,只需将nej build替换为mooc-nej即可

 // 所有的nej build改成mooc-nej build即可
 mooc-nej build deploy/mobile/release.js 
 
 // 或者构建机安装直接引用build文件
<property name="mooc-nej-build.js" value="/home/appops/study/install/node_modules/@edu/toolkit3/bin/build.js"/>

<exec dir="." executable="${mooc-nej-build.js}"failonerror="true">
<arg line ="${ob_baseline.dir}/deploy/web/release.js"/>
</exec>

backup:如果你发现有问题,还可以快速回滚到nej build。

八、生产验证

在这里插入图片描述
中M在年初就已经使用上了mooc-nej,目前稳定运行半年有余,暂未发现任何由迁移引起的bug请大家放心食用

九、总结

大概从开始改造到结束改造用了去年12月的日常空余时间,其间的心路历程大致是:

😢(打包好久555)

🤔(什么东西卡这么久)

😯(哇发现了原因)

😎(改造,冲!)

😏😤😏😤😏😤...(改完了挂了...又改完了又挂了...*n)

🤨(换个思路)

​💓(忐忑验证)

😁(成功上线)

但是回头看,这些踩坑都是值得的,我们终于不用再漫长的等待构建了!

-END-


有道AI情报局
788 声望7.9k 粉丝

引用和评论

0 条评论