阿里、蚂蚁、晟腾、中科加禾精彩分享 AI 基础设施洞见,现购票可享受 9 折优惠 |AICon 了解详情
写点什么

前端技术:Webpack 工程化最佳实践

  • 2020-03-23
  • 本文字数:6647 字

    阅读完需:约 22 分钟

前端技术:Webpack 工程化最佳实践

一、引言

1. 前端构建工具的演变


回想在 2015-2016 年的时候,开发者们开始渐渐把视线从大量使用 Task Runner 的 Grunt 工具,转移到 Gulp 这种 Pipeline 形式的工具。 Gulp 还可以配合上众多个性化插件(如 gulp-streamify),从而使得整个前端的准备工作链路,变得清晰易控,如刷新页面、代码的编译和压缩等等。自动化“流水线”工具取代了很多繁杂的手动工作,可以说,是具有跨时代意义的。之于 Webpack 而言,其本质是是基于“模块化”思想的一个“JS 预编译”解决方案,诞生初期,和其相似的方案还有 Browserify,和 Webpack 属于同门不同派别的还有 sea.js 或 require.js,这二者需“在线依赖”解释器编译。


时至今日,多数日常工作接触的项目,已经可以完全的舍弃 Gulp 了。但工作中有时还会接触一些老项目,其中 Gulp 的使用和维护屡见不鲜。2019 年初之时,通过一个老项目(gulp 3.x + webpack 3.x)的技术升级,借机了解了 gulp 4.x 的动态,又不禁让人回想起 gulp-browserify,和 gulp-webpack(五年前发布,目前改名为 webpack-stream)。所以,Webpack 做为某一个垂直方向的解决方案,当然可以 manaually built-in Gulp 中。在拿 Webpack“方案”和 Gulp 类“工具”去做正面比较的时候,需要明晰两者解决问题的范围和思路。如今再次回顾历史,对技术的发展演变顺序,能有一个基本客观的概念。


在 2017 年的时候,Gulp 和 Webpack 在用户的使用率和“将继续使用”的意向上,还不分伯仲。但从《State of Javascript 2019》中可以看到,Webpack 已经完全碾压了其它工具和类库,成为了首屈一指被大家广泛使用、讨论的 Build Tool。2018 年 2 月 25 日 Webpack 发布了 4.0.0 正式版本;对不少项目进行了 Webpack 4.10.2 版本的升级后,又将部分项目升级到了 4.29.0 最新版本。这一系列的“跟进式升级”中,一方面是在不断融入 Webpack 对于模块构建的新思路和理念,为了能够更好的适应其未来的变化,另一方面是在一个好的方案中不断尝试,结合项目的基础设施优化,从而提高效能,保障产品稳定。


2. 本次回顾


Webpack 工具虽说只是前端项目 CI 流程的一个小部分(构建 build),就它自身而言,所涉及到的 Node 知识和包依赖管理经验,是一整块技能。细节来看,里面涉及了 Webpack 自己的包和第三方 plugin 生态,还要配合恰当的 babel、typescript、flow.js、eslint 配置等多个生态,去处理 Javascript 语言本身的编译/转译。以及,正确管理本地静态资源文件和远端 CDN 资源文件路径(打包配置决定打包结果),涉及到了跨域知识和 Node 层服务配置、模板配置知识。更进一步还有,NPM 众多包的版本管理等让人头疼的问题。其中琐碎细节数不胜数,当所有第三方工具正确使用的前提下,也许还有些 plugin 小工具,需要开发者去自研发。知识谱系之大,可见一斑。


本文不描述 Webpack Docs 使用指南,也不描述第三方插件的使用“指北”。更多的是结合过往项目经验,记录实践得出的使用技巧,也记录一些走过的弯路所带来的问题,希望对其它众多的前端技术人能够起到一点借鉴作用。(Package Checking List:React: 16.3.2,Babel: 7.0.0, Webpack: 4.29.0,Node: 11.8.0)

二、文件结构

在 4.x 版本中的早期,CLI 工具集里的命令是 Webpack 主包自带的,但在 Webpack 4.x 后期的版本,将 webpack-cli 作为独立包剔除出去,需要手动单独安装才可以执行 tnpm run start 这样的脚本命令。其次,对于开发/日常环境(dev)和预发/生产环境(prod)来说,打包的策略是截然不同的:


1. 对于 dev 日常环境:


1)方便的 debug 和 troubleshooting,有比较强的 source mapping;


2)希望能够得到颗粒度较小、且有根据变动代码针对性的的加载(live reloading/hot module replacement);


3)希望可以做一些代理 Proxy 相关的调试;


4)可以方便的根据开发者的情况,对本地的 dev-server 进行配置等。


2. 对于 Prod 生产环境:


1)通过压缩 Javscript/CSS 代码,获取更小的文件加载体积;


2)通过包的拆解来得到更优的加载策略,从而降低 load time;


3)比较轻量的 source mapping(当然,当你需要一些 trace 信息做日志和报警的时候是另外一番情景);


4)线上的产品的一些个性诉求(比如,对同一份 Javascript 代码也许要匹配不同的样式文件)等。


3. 通常评估效率维度主要有以下几个,文中提到的数据来源主要属于前三个:


  • 本地开发 compile(w/ DLL or NO DLL);

  • 本地开发 re-compile(w/ DLL or NO DLL);

  • 本地测试 build(webpack analyse 分析的重点部分);

  • 云构建时长 (NO DLL or 配置化 OSS 支撑 DLL)。


在 Webpack 的新版本中, webpack-merge: 4.2.1 这个独立包的使用,开发者使用 webpack.common.js 文件对开发和生产环境中的公共部分进行配置,webpack.dev.js 针对开发环境,webpack.prod.js 针对生产环境。区分后,两种环境的配置差异,一目了然:



(图:webpack 配置文件结构)


关于 cz.config.js 和 flowGlobalVars.js 里面“话题点”颇多,不在此处重点描述。


如果需要 DLL 配置(在后面的优化部分会重点讲),还需要单独加入一个 webpack.dll.js 打包的配置文件。当然,dll 其实也是一个普通的文件 Output,我们可以在 webpack.common.js 文件中 module.exports 时,写两个区分开。通过这种不是很常见的灵活写法(Exporting multiple configurations),可以更多的去理解文件的 I/O 和 module 模块的概念。

三、基础/自定义配置

1. CommonsChunkPlugin 被取代


被移入到了 webpack.optimization.splitChunks 中。有关拆包切分和颗粒度控制,这个其实从 Webpack 的层面已经为我们做了很多优化,自身也是有一套基础默认的优化策略的。类比来看, React 生态里面 diff 算法本身也是有策略机制的,更多的优化,使用者可以在这个对象里面加入回调方法,自己去细化控制。


这里需要特别注意的是 cacheGroups,当不明确哪些内容需要被 cache 时,或者是颗粒度不好把控时,这样的切分会给我们带来非常多的冗余文件。定义一个 vendors 对象,那么我们的 output 文件(不包含 chunksFiles)的每一个都会生成一个 cache 文件。加入 output 的有 app.bundle.js 和 polyfill.bundle.js,一旦加入这个 vendors 对象,打包的时候会额外的生成两份文件,分别是 vendors-app.js 和 vendors-polyfill.js。虽然不用担心这两个文件内容会重新打包代码进去,里面只是放一些 cache 索引,但这两个文件如果在不确定要用他们来做什么的时候,cacheGroups 的设置,需要重新认真去考虑。


2. OccurrenceOrderPlugin


本身不再是一个 webpack 类下面的构造器,而是被重新命名(之前的名称因为单词拼写错误了),然后放入到新的位置,调用起来需要重新去书写:new webpack.optimize.OccurrenceOrderPlugin()。


3. terser(默认的内置压缩工具包)


webpack.optimization.minimizer 的新版本中,default built-in 的工具已经由旧有的 uglifyJS 变成了 terserJS,旧的 uglify 已经被 depreacted 处理,相信不久之后的状态就会变成 legacy,新的 terser 更好的性能,对 ES6+的语法支持的更多,也同时兼容了 babel 7 的生态,同步其它第三方库代码压缩后的诉求。目前我在使用的是 terser-webpack-plugin,和普通的 terser 配置的参数上有一些差异,需要自己手动引入(官方文档推荐)。


4. module.rules.exclude[0]


module.rules.exclude[0]的文件地址书写,要求更加严格( 4.11.0 以后的版本)。


以往我们在对 module.rules 做配置时,有些文件不希望被遍历到,那么我们通过 exclude 这个参数配置,将其跳过,有时候会使用’src/contianer/xx.jsx’这样的写法,如果是多个 path 索引,那就放到一个 Array 中就好。但这种写法,在新版本中是不被允许的,我们只能使用 path.resolve() 或/regExp/的写法去声明文件路径地址。(Bonus Basic Tips,如何用正则书写并集和特定路径,如我希望 include 所有 src 加上一个指定的 npm 包 :/(src\/.*)|(node_modules\/.*@ali\/lark-components)/)


5. alias 和绝对路径


webpack 在打包的时候,通常需要对文件的路径去做查找、搜索,它需要明确知道文件的引用位置和引用关系,从而能够完整的知道整个映射 mapping 关系。减少这方面的开销,我们可以考虑去配置 alias,从而以绝对路径的写法代替大量相对路径写法。好处的话,一方面是帮助 webpack 更快的去定位文件位置,另一方面书写起来,也不再用被输入 ‘…/…/’ 还是 '…/…/…/’ 而困扰。


  • Webstorm 寻找绝对路径:在配置里面对 webpack 配置项加入 webpack 文件路径就好,Webstorm IDE 会自己找到对应的 alias 关系;

  • VSCode 寻找绝对路径:插件层面没有发现太好的办法,如果项目正在使用 typescript,可以在 tsconfig.json 里面配置相关的编译项,可以达到和上面 Webstorm 同样的效果。


6. 大图片上传 CDN


上传 CDN 后可以大幅减小包体积。另外,webpack 也不需要再去关注那些图片的文件索引路径了。项目稍微大一些,本地图片 5Mb ~ 10Mb 的情况非常普遍,亟待优化。


7. devServer Proxy 的代理能力


去调研这个能力,得益于一次请求层的改造。诉求是希望 Token 不再显示传递,而是通过塞到 Header 去实现。在本地开发的环境,我们通常使用 jsonp 去解决跨域问题,但其本质其实是在网页中嵌入一段,自然也就不能写入 Header 信息,这个和我们的初衷并不相符,无法满足诉求。所以对于这样的跨域问题,我们通过几个简单的参数配置,在请求发起和请求返回的两端,分别做了代理配置,从而“欺骗”了“源 Origin”,得以解决本地开发的跨域问题:</p><pre><code>devServer:&nbsp;{<br/>&nbsp;&nbsp;&nbsp;//&nbsp;...<br/>&nbsp;&nbsp;&nbsp;headers:&nbsp;{<br/>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;'Access-Control-Allow-Origin':&nbsp;'*',&nbsp;//&nbsp;CORS<br/>&nbsp;&nbsp;&nbsp;},<br/>&nbsp;&nbsp;&nbsp;proxy:&nbsp;{&nbsp;//&nbsp;for&nbsp;ajax&nbsp;cors<br/>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;'/h5/ajaxObj':&nbsp;{<br/>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;target:&nbsp;'http://xxx.xxx.xxx.com',<br/>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;onProxyReq:&nbsp;(proxyReq)&nbsp;=&gt;&nbsp;{<br/>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;proxyReq.setHeader('Origin',&nbsp;'http://xxx.xxx.com');<br/>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;},<br/>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;onProxyRes:&nbsp;(proxyRes)&nbsp;=&gt;&nbsp;{&nbsp;//&nbsp;…},<br/>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;},<br/>&nbsp;&nbsp;&nbsp;},<br/>&nbsp;},<br/></code></pre><h2>四、优化性能 by Node / Happypack</h2><p>基础配置和需要的自定义配置已经有了,整个项目的构建时间有可能还是非常不理想的,当前本文提及的测试项目,大概有 57s 的时间,还是有很多地方没有补足的,可优化的空间非常大。</p><p>第一步可以先关注下 Node 版本,经过测试,是对整体速度可以至少提升 30%的事情,尤其是在 Node V8 版本到 V10 的时候,以下是之前在另一个项目做技术改造时记录到的数据:</p><table><thead><tr><th>Node 版本</th><th>v 8.x</th><th>v 10.x</th></tr></thead><tbody><tr><td>compile</td><td>32s - 36s</td><td>26s</td></tr><tr><td>re</td><td>compile</td><td>8s - 9s</td></tr></tbody></table><p>但是这次,在把项目直接升级到了 v 11.x 后发现,有带 node-sass 的项目编译构建都崩溃了。才意识到,node-sass 的版本也需要相应的版本更新。也测试了 Babel v 6.x 到 v 7.x 版本的升级效果,本来以为 babel 的大版本升级会带来显著的编译速度提高,实际上却并不理想(基本可以忽略不计)。</p><p>打算开启多线程能力,去处理模块化打包里面那些本是单线程执行的 loaders 们的工作。 Happypack 的提升效率对整个项目的首次编译而言,效果是 20%左右,比较明显。加入 Happypack 能力的时候,有两点需要注意:</p><ul><li>其对 file-loader 和 url-loader 的支持不好,可以考虑不加,毕竟我们项目里面图片类(最好上传 CDN)的和非常规格式的文件只是小部分;</li><li>这次也尝试了把 ts-loader 加入到多线程中,但是也出现了不少编译问题。大概率怀疑是我个人的配置问题,但过程中去看 issues 见到了不少 ts-loader 和 ts 生态依赖兼容性的问题。目前这个项目.ts 只是少数文件,作为一种尝试,大部分文件还都是.jsx 和.js,所以针对 ts 也先不加入 Happypack 能力了。</li></ul><h2>五、优化性能 by DLL/ Optimization</h2><p>首先需要借助一些工具来进行分析,如:webpack-bundle-analyzer ,通过这个工具我们可以对整个构建(用于生产,Webpack Analyse 针对的 build 过程,不是 compile)过程和结果进行数据、图形上的分析,从而得知问题具体出现在了哪里。进而得知 DLL 所需拆分的内容是什么。以下内容是在第一次分析时得出的:</p><p><img src="https://static001.infoq.cn/resource/image/eb/cf/eb19ddbdb77740e3c333587c24c9bfcf.png" alt=""/></p><p>这个图片的 3532 modules 和 62 chunks 可以看到具体的模块以及 chunks 划分后的情况。更加直观的我们来看下面这张图,可以看到 Parsed 的尺寸,入口文件(7.09MB)和主 chunk(2.04MB,主要是一些首页就需要加载的 node_module)的大小都很夸张,并且 node_modules 里面的包基本上是一一打包、整整齐齐:</p><p><img src="https://static001.infoq.cn/resource/image/a8/9c/a8d7d85d04abf5c786fdbcec16bb279c.png" alt=""/></p><p>有了这些分析结果,对应解法的思路就很清晰了:首先要抽离常用的 node_modules(这是 DLL 的意义),然后要逐个分析,把不被经常用到的 node_module 们(仅被某些页面使用,不具有公共特点)也抽出去。</p><p>对于 React 项目中的 React, React-Dom, React-router, Redux 等,还要一些第三方比较大的库,比如 antv 或者 G2 相关的,也要进行 DLL 抽离了:</p><p><img src="https://static001.infoq.cn/resource/image/2c/c1/2c9b0471cfbfb2a01f656ac26e7142c1.png" alt=""/></p><center>(modules 数量由 3532 降低到 1500,编译时间缩短了三倍)</center><p>在做了上述 DLL 的抽离后其实效果已经很明显了,进一步的提升空间,可以对 optimization 进行了配置(用法详见官方文档):</p><p>1)terser;</p><p>2)chunksAll;</p><p>3)no mimimizer sourceMap。</p><h2>六、结尾</h2><p>本文大概主要介绍了一些工具衍变背景、基础的组织结构和自定义配置,以及如何通过分析工具去来做性能优化,其中很多小的细节没办法一一提到,比如我们看到加载的 chunk 都是 hash 值的时候,如何能够辨别是什么组件呢:解法是可以在路由处通过配置 moduleName 的方式去做:</p><pre><code>()&nbsp;=&gt;&nbsp;import(/*&nbsp;webpackChunkName:&nbsp;&quot;chunkNameDisplay&quot;<br/>*/'../containers/UserList/chunkNameDisplay')<br/></code></pre><p>诸如此类,实在繁多。随着 Webpack 5.x 版本的陆续发布和众多团队使用之后,也许很多东西又会有大的改变。并且各种框架的集成已经越来越丰富,更多的解放程序员在工程化维护上的双手,我们关注工程化的演进,看看 Webpack 生态会给我们带来什么样的惊喜。</p><p><strong>作者介绍</strong>:</p><p>阿里文娱前端开发专家 芃苏</p><p><strong>相关阅读</strong></p><p><a href="https://www.infoq.cn/article/Hvrk1b4eBT5ftGLPqm8Q">电影垂直行业的云智开放平台如何炼成?</a></p><p><a href="https://www.infoq.cn/article/1mdwJYDeUDFT92vKy1Jt">阿里工程师带你了解 B 端垂类营销中心如何设计?</a></p><p><a href="https://www.infoq.cn/article/3CZuzwZVoRpGCG60t4Xo">云智前端技术如何赋能场馆院线?</a></p><p><a href="https://www.infoq.cn/article/YRFUdHJ1iI2CSsE9iqZ4">60 秒售出 5 万张票!电影节抢票技术揭秘</a></p><p><a href="https://www.infoq.cn/article/qODRHtSEPI0Xe3iZ8u7T">电影行业提升 DCP 传输效率,还能这样做!</a></p><p><a href="https://www.infoq.cn/article/NEVU9NawMhVeeuET0vfu">超大型场馆的绘座选座解决方案</a></p><p><a href="https://www.infoq.cn/article/6he5gohhU05b4hWOfy2C">大型赛事稳定性保障:Dpath 为世界军人运动会护航</a></p><p><a href="https://www.infoq.cn/article/JhDDwGeDDUvQBlyNWidT">世界顶级赛事的票务支撑:百万座位与限时匹配</a></p>


2020-03-23 10:001873

评论

发布
暂无评论
发现更多内容

翻过三座大山:MatrixOne从 NewSQL 到 HTAP 分布式架构演进

MatrixOrigin

分布式数据库 MatrixOrigin MatrixOne 架构升级

四川农信:与先进科技融合,更好服务广大用户|客户之声

OceanBase 数据库

数据库 oceanbase

ChatGPT会在三年内终结编程吗?| 社区征文

二哈侠

程序员 ChatGPT 三周年征文

如何解决制造企业数字化转型中的数据散乱和管理难题,实现顺利转型?

i生活i科技

趣谈之什么是 API 货币化?

API7.ai 技术团队

api 网关 APISIX api 货币化

新晋 Committer 也有 “产学研联动”?速来围观不同视角共建 IoTDB 社区的故事!

Apache IoTDB

IoTDB Apache IoTDB

StarRocks 3.0 新特性介绍

StarRocks

c++ 数据湖 #java 数据库· 大数据‘’

“程序员”即将失业 | 社区征文

六月的雨在InfoQ

程序员 ChatGPT GPT-4 三周年征文

华为云ERP上云解决方案助力企业迈向云端,提升业务效率与安全性

i生活i科技

时序数据库能做什么|用 GreptimeDB 进行程序员键盘行为分析,最高频按键竟然是它

Greptime 格睿科技

云原生 时序数据库 数据库·

【分布式技术专题】「单点登录技术架构」一文带领你好好认识以下Saml协议的运作机制和流程模式

洛神灬殇

分布式 SAML SSO 单点登录

前端线下面授培训机构该怎么选择

小谷哥

如何使用文件传输协议ftp,教你使用文件传输协议命令行

镭速

历史性的时刻!华为云跨端、跨框架开源组件库项目 OpenTiny 正式升级 TypeScript,10 万行代码重获新生!

英勇无比的消炎药

开源 前端 UI组件库

码头风云——5G降临

白洞计划

5G 智慧码头

数据散、管理难和上云难,看华为云解决制造业数字化转型难题

与时俱进的时代

数字化转型困局?华为云提供多款解决方案助力制造业企业上云加速转型

与时俱进的时代

架构师应该具备的特质

agnostic

架构师

多库多表场景下使用 Amazon EMR CDC 实时入湖最佳实践

亚马逊云科技 (Amazon Web Services)

Java

AI日课@20230409:对话式用户界面

无人之路

ChatGPT

前端工程化实战:React 的模块化开发、性能优化和组件化实践

兴科Sinco

性能优化 前端工程化 React Native 前端模块化 组件化开发

3DCAT实时云渲染助力数字孪生检修车间建设,为智能制造赋能!

3DCAT实时渲染

数字孪生 实时渲染 3D实时云渲染

什么是安全沙箱技术?如何保护用户隐私和系统安全?

FinFish

前端容器 小程序容器 安全沙箱 小程序安全沙箱

BUFF NETWORK:去中心化衍生品交易的未来

股市老人

打卡智能中国(三):一位水厂文员的多重身份

脑极体

云计算

NCCL源码解析②:Bootstrap网络连接的建立

OneFlow

人工智能 深度学习

iOS SKAN 4.0 时代的广告追踪优化:掌握隐私友好的营销策略

37手游iOS技术运营团队

SKAdNetwork SKAN IDFA ATT App Tracking Trans

软件测试/测试开发丨该如何测客户端专项测试?

测试人

软件测试 自动化测试 测试开发 专项测试

谈谈现在编程行业的热门话题| 社区征文

魏铁锤

三周年征文

Django笔记六之外键ForeignKey介绍

Hunter熊

Python django 外键 ForeignKey

成立数科公司之余,央国企推进数智化转型还需要底座支撑

用友BIP

前端技术:Webpack 工程化最佳实践_文化 & 方法_阿里巴巴文娱技术_InfoQ精选文章