您的位置:首页 >  新闻中心 > 云通讯公告
  云通讯公告
 

这些坑,我做前端构建时都遇见了

来源:原创    时间:2017-11-28    浏览:0 次

很多做前端的朋友经常问我:为什么我打包总是很慢?为什么我内存占用这么大,该怎么优化?去哪儿网是怎么做前端构建的?
这里,我和大家分享一下在去哪儿网是如何进行前端工程化相关工作的。
在企业环境下的大型工程构建会遇到种种问题,我们也是通过不断地摸索才逐渐总结出一套解决方案,现在把我们所遇到的一些坑和如何处理这些问题拿出来与大家交流。 
一、构建工具演化篇
作为公司内部的构建工具,最重要的一点是稳定性,因为假如它出了问题,不仅会导致项目发布失败,还有可能会引起线上故障。
然而在保持稳定的同时,它也一定是在不断发展的。无论是开发语言的变化,还是前端框架的迭代更新,都会带来新的需求。
为了满足这些需求,在设计时就一定要有一定的预见性,知道哪部分未来可能需要扩展以适应新的场景。
如何同时拥有稳定性和扩展性是公司级别的构建工具面临的最大挑战。
阶段一:完全定制化构建
在 2014 年我最开始加入去哪儿网的时候,公司中有一个统一的开发和构建工具——FEKit。它当时主要解决几个问题:
模块依赖。类似于 Browserify,可以遵循 CommonJS 标准打包模块。
包管理器。那时候 npm 还没有完全流行于前端,FEKit 自己实现了一套类似于 npm 的包管理器,并搭建了模块仓库,开发者可以从仓库上面安装包也可以发布自己的包。
目录约定。FEKit 对源码目录、目标目录以及打包生成资源的命名格式有自己的规定,它的好处是可以让开发和构建发布的流程相对统一,减少学习成本。
除上面之外,FEKit 还具备 SASS 编译、代码压缩、版本号生成等特性,在当时可以说是比较完备的开发构建工具了。
同时也可以看出它其实是将一整套构建流程封装好,只要开发者熟悉了它的配置,在面对任何一个 FEKit 的项目的时候都能较快上手。
但是随着时间的推移也逐渐暴露出 FEKit 在扩展性方面的不足,由于它内置的编译打包流程是规定死的,因此它很难去适应新的需求。
比如,当 ES2015 出现后,有些同学提出想把 Babel 放在 FEKit 的构建流程里面。
但是在公司的打包发布平台上面 FEKit 只能存在一个版本,任何改动都会影响到所有项目的发布,我们很难去冒这样的风险去改动所有业务的打包流程,这也阻碍了 FEKit 向前推进的脚步。 
阶段二:放开手脚自定义构建
为了实现各自的需求,不同的业务开始尝试自己搭建一套开发和发布流程。Gulp、Grunt、Webpack 全都有,大家各玩各的。
看似都能实现新的需求了,但从这类孤立搭建的工程中也发现了几个共同的问题:
重复造轮子。像是配置预编译器、生成资源版本号、Mock 服务等这些类似的工作经常被重头到尾实现一遍。
而如果它们由统一的工具来实现则可以节省很多开发成本。
缺少构建层面的优化。不管是从打包出来的资源体积以及编译速度上,不少工程都还有很大的提升空间。
我们曾试着将其中一个工程的公用库用 CommonChunks 的方式提取出来,将整体资源体积减小了多一半。
造成这个问题的原因是很多时候开发同学只是想短平快地完成一些业务,并不会过多关注流程上的优化。
沟通和学习成本高。大家各玩一套的结果是跨团队和跨工程时会由于构建工具和流程不一样而带来额外的成本。
当面对一个缺少文档的新工程时,一个刚接触的同学可能会完全不知所措,而工程之间构建工具的不一致也会导致切换环境时要花费更多的精力。 
阶段三:约束与自由的平衡
基于以上几点问题,我们决定开发一个新的构建工具,在设计时希望它能解决以下几个问题:
提供统一的配置,如资源输入输出目录、版本号规则、压缩插件等,并且允许更改这些配置。
有默认的构建流程,也允许自定义工作流。比如开发者可以选择使用 Babel、TypeScript 来编译 JavaScript,也可以用 SASS、LESS 来做样式的预编译等等。
提供统一的构建优化手段和工具服务,像是按需加载、多进程打包编译等这类工作最好内置在工具中,业务不需要过多地关心如何优化项目。
于是后来我们开发了 YKit。它最大的特点在于允许开发者在其基础上自定义配置,并且可以将一系列构建工作流封装为模块,然后就可以像搭积木一样快速地通过这些模块搭建起一个环境。
举个例子,当你想开发一个 React 项目的时候,使用 ykit init react 一行命令就可以将一个内置好各项 React 相关配置的工程初始化好。而当你需要使用其它配置的时候安装和引入更多的 YKit 插件即可。
比如,希望在项目中使用 TypeScript,则执行命令 npm install ykit-config-ts;
想要使用 Mock 数据中间件,则执行 npm install ykit-config-mock,以此类推。
YKit 其实是基于 Webpack 的一层封装,在面向开发者的时候它把复杂的 Webpack 配置封装在各个 npm 模块内部,对于开发者而言则只要关注功能即可。
同时 YKit 也留了一个更改配置的接口 —— modifyWebpackConfig,把内部的 Webpack 配置对象暴露出来,开发者可以基于自身的需求进行进一步深入定制。
从上面可以看出,YKit 是一个兼顾统一性与自由度的解决方案。在去哪儿那么多业务线,每个小团队都有自己习惯的开发方式。
YKit 希望做的事情是帮助每个团队将它们的构建配置封装起来,通过 npm 模块的方式来管理,这样当每次新建一个项目的时候,直接拿来以前封装好的配置即可,以最快的速度搭建好一整套开发和发布流程。 
二、构建速度优化篇
1. npm 包版本号固定
相面在讲包管理器的时候时候已经介绍过,使用版本固定文件可以带来两个好处。
避免语义化版本号( semver )带来的包版本变化风险。
加快 npm 解析包依赖的速度。
在去哪儿网发布前端项目时(其实也包括 Node.js 项目)会进行版本固定文件是否存在的校验,如果没有则会直接阻断发布。 
2. npm 包缓存
在一般企业环境中进行前端发布一般有两种方式。
在开发者本地进行工程构建,将打包好的资源( JS、CSS 等)上传  CDN。
通过特定的服务器将代码仓库克隆下来,进行构建和发布。 
在去哪儿网我们使用第二种方式。但是有一个问题,由于是在服务器上进行 npm 模块安装和打包,并且当时 npm 的缓存机制效率较低,通常 npm install 的耗时很长。
针对这种情况,我们设计了一个 npm 包缓存工具—— ncs( npm-cache-share )。
ncs 不仅可以充分利用本机的 npm 缓存,还可以和多台服务器间共享缓存。由于发布的服务器有多台,因此每一台的安装结果都可以缓存下来供其它的服务器使用。
通过这种缓存机制可以大大提高安装 npm 模块速度,在一个充分缓存的环境下,一个大型工程的安装过程也只有短短几秒。 
3. 打包编译优化
谈到打包和编译这部分,就又回到我们说的 YKit 上面。在每一个封装的配置模块的里面 YKit 其实已经做了优化。
比如说,对于 React 项目来说,YKit 会使用 Happypack 来进行多进程编译。
在生产环境下,会添加 process.env.NODE_ENV= production 的环境变量来使 React 去掉开发环境代码。 
在各类封装好的构建方案中 YKit 也会提供相应的最佳实践,这样业务同学基本不用踩坑,各项优化以及各种兼容性的问题基本都在方案内部得到实现和解决,对于开发者而言是透明的。
三、代码质量篇 
每个公司几乎都有自己的一套代码风格或者是编码约定。在不断有新人进公司的过程中,如何保证风格的统一?代码质量检测是一个很好的方法。
1. 代码质量控制的意义
试想一下,假如你刚进一个公司没多久,提交了自己的第一段代码。然后你的 Leader 来找你,跟你说你这个代码哪里哪里不规范(其实也许只是不符合公司的风格),会不会觉得有点不太舒服?
如果换成是一个质量检测工具,在你提交的时候自动拦截下来并指出问题。
你稍作修改之后重新提交并且完美通过,同时 Leader 看到的也是符合他编码要求的代码,这样是不是显得友好地多? 
很多代码中容易疏忽的错误都可以靠质量控制工具检测出来。
比如忘记删掉了打印调试日志的代码、哪个变量声明了但是没有用到、或者说在页面打了个 Alert 出来。
机器完全能帮我们查出来并且修改掉,为什么还要去人力测试和改正呢。 
2. Sonar
Sonar 是一个质量检测平台,在去哪儿网我们用它来做持续集成,控制前后端代码的质量。
每当我们提交了一段代码上去以后,都会自动通过 Sonar 进行质量检测,并把检测结果发回来。
结果中的信息会进行分级,有一些问题是阻断性质的,意思是必须要进行修改,否则无法进行发布。
还有一些则只是警告,告诉开发者这种编码风格不太好,但并不会影响发布流程。 
3. ESLint
对于前端开发者来说 ESLint 应该再熟悉不过了。如果说 Sonar 是公司级别的质量控制工具,那么 ESLint 则是各个前端团队自己使用的工具。
在前面我们提到的 YKit 中有一个专门为去哪儿网设计的配置插件,内部包含了一些基本的规则。
但是对于更详细的规则而言,由于不同的团队内部约束并不一样,这里留给了团队自己进行配置。 
四、发布平台篇 
之前在去哪儿网主要使用 Jenkins 作为发布平台,它基本能够满足所有业务的需求。
比如,用它来打包和发布前端、后端、客户端应用、通过 Sonar 进行代码质量检测、集成邮件通知等等。
而 Jenkins 也存在一些不足之处,比如界面比较过时、用户体验差、在一些多应用联合发布的场景下速度较慢。
因此后来我们自己搭了一个内部的发布平台 Portal,它有如下几项优势:
更现代的设计以及更友好的用户体验,特别是进度和日志得到了更清晰地呈现。
错误处理。对于不具备版本描述文件这类阻断发布性质的错误可以提前进行校验,不必让大家等了一会然后突然报错了。
性能提升。一次发布任务可能牵扯到前后端多个应用的发布,以前只能集中在一台服务器上顺序发布,现在可以多台服务器分散执行,资源调配更均衡。
在目前发布平台的使用上,Jenkins 和 Portal 都有,毕竟 Jenkins 用了这么久很多业务同学会比较习惯。
但就未来来说 Portal 这类新的平台应该会占据越来越大的比重,毕竟它更适合于快速实现一些公司内部定制化的需求。