wujunchuan / wujunchuan.github.io

John Trump's personal blog in issues
115 stars 13 forks source link

前端项目工程化改造实录 #32

Open wujunchuan opened 7 years ago

wujunchuan commented 7 years ago

前端项目工程化改造实录

前言

在之前亚皇写的前端工程化的思考与探索这篇文章中已经提到,现在打包发布流程上的一些值得优化的点。正好接下来柚宝宝的官网需要用Node.js来重写。我没有将项目结构进行大的改造,而是在原有的基础上(nodejs-meiyou这个项目)进行扩展,克服原先打包流程上的缺陷与不足。

鉴于当今的前端日趋复杂,引申出一些需要思考的问题。如何更快提高页面加载速度;如何实现前端自动化部署;如何构建项目工具链;多人团队协作下项目如何管理等等问题。 在工具链的选型与组合中,每个项目也是各有各的不同组合。前端工程化一直是业内不倦追求的目标,至今在各个社区也有不同的见解。切记,软件工程中没有银弹,至今前端还没出现一套可以满足各种场景下的脚手架框架,本文仅致力于优化当前项目工程化的不足。

亚皇在之前的那篇文章已经提出了原先打包流程上的缺陷与不足,并且尝试去解决问题。下面是我在改造旧项目过程中的一些另外的发现与思考。

实录

1. 合理利用浏览器缓存

前端性能优化指南中,一个很重要的优化点就是利用浏览器缓存,来加快页面加载速度,减少服务器压力,节约网络资源,提高用户体验。浏览器缓存是每个前端开发工程师应该重视的。大公司的静态资源优化方案有很多,这里就不累述了,大家可以参考大公司里怎样开发和部署前端代码?浏览器缓存浅析

在旧项目中,我们在执行node ./tools/build.js过程中会在生成的js文件或者css文件的头部打上一个时间戳。

头部时间戳

举个例子,无论文件有无变化,每次打包都会产生新的benefits-{md5}.jsbenefits-{md5}.css文件。

文件目录

因为每次产生的文件名称不同,浏览器会将其当做一个独立的资源来看待,而不会调用原先的缓存资源。

revving技术

可能是为了开发人员在调试过程中发现文件构建日期是否正确,但是这绝对是一个失误。因为每次插入时间戳的时候,后面根据文件内容计算出来的md5都会不一样,那么每次发布新的版本时,都会产生大量的构建后的文件,既浪费了CDN流量、空间资源,也让浏览器缓存机制失效,每次发布项目后,用户都需要重新下载页面。

解决方案: 移除时间戳,或者在md5计算后再插入文件中

2. 代码产成品不应该纳入版本管理中

原先的打包项目是在本地打包,每个开发者都持有上传存储服务的权限,有很多不可控的因素。随着项目的开发进度,public/assets/views-pro/会堆积越来越多的文件,版本管理仓库越来越臃肿

既然开发者不会直接去修改build后的代码,而且build后的文件代码都是由工具生成的,那么还有什么理由将这两个目录下的文件纳入版本管理中,果断剔除它们。

3. 抽离公共模块

Webpack是一个模块化打包工具,既然是模块化开发,必然会存在公共模块的调用。

如果没有对公共模块进行抽离的话就会导致每个模块打包的时候都包含了jQuery代码在里面,如下图所示。

公共模块没有抽离

这样导致的后果是单个js文件都非常臃肿,加载速度缓慢。使用Webpack完全有能力将jQuery抽离出来,独立成一个文件,每个有引用到jQuery的页面都能共用同一份文件。

在Webpack中配置插件CommonsChunkPlugin,

  plugins: [
    //将css代码抽离出来成单独的文件
    new ExtractTextPlugin('[name].css'),
    //公共模块抽离
    new webpack.optimize.CommonsChunkPlugin({
      name:['common','vendor']
    })
  ],

在Webpack的entry这样配置:

 entry: merge(entry,{
   //公共模块
   common:[
     joinBaseRoot('public/common/common-alert.js'),
     joinBaseRoot('public/common/common-console.js')
   ],
   //公共模块-第三方库
   vendor:[
     joinBaseRoot('public/common/jquery.js'),
   ]
 })

4. 加速打包速度

随着项目规模的增大,项目打包的速度越来越慢。

根据分析,执行node ./tools/build.js的耗时操作主要体现在下面这几个部分:

首先针对Webpack依赖分析太耗时的问题,我尝试根据模块去动态载入webpack.config.jsentry,以减少Webpack依赖分析的过程,速度确实提升了不少。就像下面

switch (process.argv[2]) {
  case 'information':
    entry = {
      'information-index': joinBaseRoot('public/information/javascripts/index'),
      'information-images': joinBaseRoot('public/information/javascripts/images'),
      'information-video': joinBaseRoot('public/information/javascripts/video'),
      'information-detail': joinBaseRoot('public/information/javascripts/detail')
    }
    break;

  case 'channel':
    entry = {
      channel_meiyou: joinBaseRoot('public/channel/javascripts/channel_meiyou'),
      channel_youbaobao: joinBaseRoot('public/channel/javascripts/channel_youbaobao'),
      channel_youzijie: joinBaseRoot('public/channel/javascripts/channel_youzijie'),
      channel_yuer: joinBaseRoot('public/channel/javascripts/channel_yuer'),
      channel_jiazu: joinBaseRoot('public/channel/javascripts/channel_jiazu'),
    }
    break;

  default:
    break;
}

但是这是个坑!!! Webpack每次都会根据entry去分析依赖关系,每次生成的代码都会有微妙的差异。特别是在模块与模块之间存在耦合关系(例如普通模块与公共模块),每次打包出来,公共模块的包都会有所变动。所以,从减少Webpack的依赖分析的角度来优化性能并不适合

第二,原本我设想将图片压缩整合到构建过程中(原来项目没有),后来发现图片压缩是个非常耗时的操作,严重拖慢了打包过程。因此我将图片压缩移出构建过程中。

最后,我对打包工具脚本进行了改写,以适应模块化打包的需求。 实现了可以根据模块名称来替换静态资源的路径

5. 静态资源上传七牛云CDN

原本的项目使用了脚本工具qshell来实现静态资源的上传,这样多麻烦,每个开发者都要在电脑上安装qshell,读取配置文件...

我根据七牛云的Node.js SDK自己写了个脚本,整合到项目gulp流程中。实现模块化打包,上传静态资源一步到位。

6. 锁定项目依赖树

锁定依赖树需要用到npm@5,所以请及时更新npm版本

查看npm版本 npm -v,如果版本低于5.x.x,请执行:npm install -g npm@5

不用担心npm与Node.js的兼容性问题,只要是Node.js 4.0以上都可以升级,并且无副作用。

npm@5新增了很多新特性,如今与Facebook的yarn的差距已经不是那么大了

更多新特性查看这里,npm@5 is now npm@latest

为了项目的稳定性,避免项目开发环境不一样导致Webpack生成的代码有出入,导致管理混乱等问题,我们需要对项目依赖进行版本锁定。npm@5默认锁定版本,将npm@5生成的package-lock.json文件纳入版本控制系统,在团队内共享可以确保每个人安装的依赖版本都是一致的。

当执行npm install时,如果存在package-lock.json文件,它会根据package-lock.json文件指定的结构来下载模块,并不会理会package.json

执行npm install时,如果不存在package-lock.json的话,则会根据安装模块后的node_modules目录结构来创建一个package-lock.json

查看讨论npm 没有 Gemfile.lock 这样的机制,怎么想的?。npm过去在这块内容确实做得不好,在依赖不锁定的情况下, 等于把项目的稳定性交由外部社区管理,这个一个很大的风险。作者在刚入职时就曾经遇到一个依赖版本升级不兼容而导项目跑不动的问题。

不过这里有个坑,使用了npm@5之后,直接修改package.json文件相应的模块版本号,再执行npm install不会更新package-lock.json。你只能手动用npm install xxx@yy来指定版本号安装,这里就会自动更新package-lock.json文件。与锁定依赖树带来的好处相比,这点坑着实不算什么。

参考

美柚-前端工程化的思考与探索

Google-HTTP 缓存

大公司里怎样开发和部署前端代码?

浏览器缓存浅析

理解 NPM 5 中的 lock 文件

npm 没有 Gemfile.lock 这样的机制,怎么想的?