当咱们的应用还处于小规模的时候,咱们可能不会在意Webpack的编译速度,不管使用3.X仍是4.X版本,它都足够快,或者说至少没让你等得不耐烦。但随着业务的增多,嗖嗖嗖一下项目就有上百个组件了,也是件很简单的事情。这时候当你再独立编前端模块的生产包时,或者CI工具中编整个项目的包时,若是Webpackp配置没通过优化,那编译速度都会慢得一塌糊涂。编译耗时十多秒的和编译耗时一两分钟的体验是迥然不一样的。从开发期间作临时部署时的效率和CI的总体效率这两点出发,咱们都有必要去加快前端项目的编译速度。css
本文基于笔者在月初对项目编译速度的优化实践,从工具链使用的角度介绍下基于Webpack的项目可作编译速度优化的地方。文中全部的可配置项和工具都由Webpack平台或者社区的工具提供,笔者对它们API的使用上不会作太多讲解,须要的同窗能够直接翻看对应的文档。笔者的Webpack版本为4.29.6,后文中的内容都基于这个版本。html
1、已存在的针对编译速度的优化前端
笔者这套Webpack架子源自CRA种子脚手架的eject,基于Webpack4.x,在对不一样资源模块的处理上已经是最佳实践的方案,在这方面基本上无需改动什么。但在编译速度优化上较弱,在项目规模扩大后,劣势很是明显。vue
其原有的针对编译的优化配置有这三处:node
1. 经过terser-webpack-plugin的parallel和cache配置来并行处理并缓存以前的编译结果。terser-webpack-plugin是以前UglifyPlugin的一个替代品,由于UglifyPlugin已经没有继续维护了,从Webpack4.x起,已经推荐使用terser-webpack-plugin来进行代码压缩、混淆,以及Dead Code Elimination以实现Tree Shaking。对于parallel从整个设置的名称你们就会知道它有什么用,没错,就是并行,而cache也就是缓存该插件的处理结果,在下一次的编译中对于内容未改变的文件能够直接复用上一次编译的结果。react
2. 经过babel-loader的cache配置来缓存babel的编译结果。jquery
3. 经过IgnorePlugin设置对moment的整个locale本地化文件夹导入的正则匹配,来防止将全部的本地化文件进行打包。若是你确实须要某国语言,仅手动导入那国的语言包便可。webpack
在项目逐渐变大的过程当中,生产包的编译时间也从十几秒增加到了一分多钟,这是让人受不了的,这就迫使着笔者必须进行额外的优化以加快编译速度,为编包节省时间。下面的段落就讲解下笔者作的几个额外优化。git
2、多线程(多进程模拟)支持web
从上个段落的terser-webpack-plugin的parallel设置中,咱们能够获得这个启发:启用多进程来模拟多线程(由于一个Node.js进程是单线程的),并行处理资源的编译。因而笔者引入了HappyPack,笔者以前的那套老架子也用了它,但以前没写东西来介绍那套架子,这里就一并说了。关于HappyPack,常常玩Webpack的同窗应该不会陌生,网上也有一些关于其原理是使用的介绍文章,也写得很不错。HappyPack的工做原理大体就是在Webpack和Loader之间多加了一层,改为了Webpack并非直接去和某个Loader进行工做,而是Webpack test到了须要编译的某个类型的资源模块后,将该资源的处理任务交给了HappyPack,由HappyPack在内部线程池中进行任务调度,分配一个线程调用处理该类型资源的Loader来处理这个资源,完成后上报处理结果,最后HappyPack把处理结果返回给Webpack,最后由Webpack输出到目的路径。经过这一系列操做,将本来都在一个Node.js线程内的工做,分配到了不一样的线程(进程)中并行处理。
使用方法以下:
首先引入HappyPack并建立线程池:
const HappyPack = require('happypack');
const happyThreadPool = HappyPack.ThreadPool({size: require('os').cpus().length - 1});
替换以前的Loader为HappyPack的插件:
{ test: /\.(js|mjs|jsx|ts|tsx)$/, include: paths.appSrc, use: ['happypack/loader?id=babel-application-js'], },
将原Loader中的配置,移动到对应插件中:
new HappyPack({ id: 'babel-application-js', threadPool: happyThreadPool, verbose: true, loaders: [ { loader: require.resolve('babel-loader'), options: { ...省略 }, }, ], }),
大体使用方式如上所示,HappyPack的配置讲解文章有不少,不会配的同窗能够本身搜索,本文这里只是顺带说说而已。
HappyPack老早也没有维护了,它对url-loader的处理是有问题的,会致使通过url-loader处理的图片都无效,笔者以前也去提过一个Issue,有别的开发者也发现过这个问题。总之,用的时候必定要测试一下。
对于多线程的优点,咱们举个例子:
好比咱们有四个任务,命名为A、B、C、D。
任务A:耗时5秒
任务B:耗时7秒
任务C:耗时4秒
任务D:耗时6秒
单线程串行处理的总耗时大约在22秒。
改为多线程并行处理后,总耗时大约在7秒,也就是那个最耗时的任务B的执行时长,仅仅经过配置多线程处理咱们就能获得大幅的编译速度提高。
写到这里,你们是否是以为编译速度优化就能够到此结束了?哈哈,固然不是,上面这个例子在实际的项目中根本不具备普遍的表明性,笔者实际项目的状况是这样的:
咱们有四个任务,命名为A、B、C、D。
任务A:耗时5秒
任务B:耗时60秒
任务C:耗时4秒
任务D:耗时6秒
单线程串行处理的总耗时大约在75秒。
改为多线程并行处理后,总耗时大约在60秒,从75秒优化到60秒,确实有速度上的提高,可是由于任务B的耗时太长了,致使整个项目的编译速度并无发生本质上的变化。事实上笔者以前那套Webpack3.X的架子就是由于这个问题致使编译速度慢,在大多数项目中也都是这个状况,处理某种资源类型的Loader的执行时间很是长,远远超过了其余Loader的执行时间。因此,只靠引入多线程编译就想解决大项目编译速度慢的问题是不现实的。
那咱们还有什么办法吗?固然有,咱们仍是能够从TerserPlugin获得灵感,那就是依靠缓存:在下一次的编译中可以复用上一次的结果而不执行编译永远是最快的。
至少存在有这三种方式,可让咱们在执行构建时不进行某些文件的编译,从最本质上提高前端项目总体的构建速度:
1. 相似于terser-webpack-plugin的cache那种方式,这个插件的cache默认生成在node_modules/.cache/terser-plugin文件下,经过SHA或者base64编码以前的文件处理结果,并保存文件映射关系,方便下一次处理文件时能够查看以前同文件(同内容)是否有可用缓存。其余Webpack平台的工具也有相似功能,但缓存方式不必定相同。
2. 经过externals配置在编译的时候直接忽略掉外部库的依赖,不对它们进行编译,而是在运行的时候,经过<script>标签直接从CDN服务器下载这些库的生产环境文件。
3. 将某些能够库文件编译之后保存起来,每次编译的时候直接跳过它们,但在最终编译后的代码中可以引用到它们,这就是Webpack DLLPlugin所作的工做,DLL借鉴至Windows动态连接库的概念。
后面的段落将针对这几种方式作讲解。
3、Loader的Cache
除了段落一中提到的terser-webpack-plugin和babel-loader支持cache外,Webpack还直接另外提供了一种能够用来缓存前序Loader处理结果的Loader,它就是cache-loader。一般咱们能够将耗时的Loader都经过cache-laoder来缓存编译结果。好比咱们打生产环境的包,对于Less文件的缓存你能够这样使用它:
{ test: /\.less$/, use: [ { loader: MiniCssExtractPlugin.loader, options: { ...省略 }, }, { loader: 'cache-loader', options: { cacheDirectory: paths.appPackCacheCSS, } }, { loader: require.resolve('css-loader'), options: { ...省略 }, }, { loader: require.resolve('postcss-loader'), options: { ...省略 } } ] }
Loader的执行顺序是从下至上,所以经过上述配置,咱们能够经过cache-laoder缓存postcss-loader和css-loader的编译结果。
但咱们不能用cache-loader去缓存mini-css-extract-plugin的结果,由于它的做用是要从前序Loader编译成的含有样式字符串的JS文件中把样式字符串单独抽出来打成独立的CSS文件,而缓存这些独立CSS文件并非cache-loader的工做。
但若是是要缓存开发环境的Less编译结果,cache-loader能够缓存style-loader的结果,由于style-loader并无从JS文件中单独抽出样式代码,只是在编译后的代码中添加了一些额外代码,让编译后的代码在运行时,可以建立包含样式的<style>标签并放入<head>标签内,这样的性能不是太好,因此基本上只有开发环境采用这种方式。
在对样式文件配置cache-loader的时候,必定要记住上述这两点,要否则会出现样式没法正确编译的问题。
除了对样式文件的编译结果进行缓存外,对其余类型的文件(除了会打包成独立的文件外)的编译结果进行缓存也是能够的。好比url-laoder,只要大小没有达到limitation的图片都会被打成base64,大于limitation的文件会打成单独的图片类文件,就不能被cache-loader缓存了,若是遇到了这种状况,资源请求会404,这是在使用cache-loader时须要注意的。
固然,经过使用缓存能获得显著编译速度提高的,依旧是那些耗时的Loader,若是对某些类型的文件编译并不耗时,或者说文件自己数量太少,均可以先没必要作缓存,由于即使作了缓存,编译速度的提高也不明显。
最后笔者将全部Loader和Plugin的cache默认目录从node_modules/.cache/移到了项目根目录的build_pack_cache/目录(生产环境)和dev_pack_cache目录(开发环境),经过NODE_ENV自动区分。这么作是由于笔者的CI工程每次会删除以前的node_modules文件夹,并从node_modules.tar.gz解压一个新的node_modules文件夹,因此将缓存放在node_modules/.cache/目录里面会无效,CI的这套逻辑对于前段项目来讲是比较坑的,笔者也不想去动CI的代码。通过过这个改动后,对cache文件的管理更直观一些,也能避免node_modules的体积一直增大。若是想清除缓存,直接删掉对应目录便可。固然了,这两个目录是不须要被Git跟踪的,因此须要在.gitignore中添加上。CI环境中若是没有对应的缓存目录,相关Loader会自动建立。这样作还考虑到这个缘由:由于开发环境和生产环境编译出的资源是不一样的,在开发环境下对资源的编译每每都没有作压缩和混淆处理等,所以即便某个源文件的源码没变,其在开发环境下和生产环境下的缓存也是不能通用的还会相互覆盖。因此为了有效地缓存不一样环境下的编译结果,是有必要区分开不一样编译环境下的缓存目录的。
4、外部扩展externals
按照Webpack官方的说法:咱们的项目若是想用一个库,但咱们又不想Webpack对它进行编译(由于它的源码极可能已经是通过编译和优化的生产包,能够直接使用)。而且咱们可能经过window全局方式来访问它,或者经过各类模块化的方式来访问它,那么咱们就能够把它配置进extenals里。
好比我要使用jquery能够这样配置:
externals: { jquery: 'jQuery' }
我就能够这样使用了,就像咱们直接引入一个在node_modules中的包同样:
import $ from 'jquery';
$('.div').hide();
这样作能有效的前提就是咱们在HTML文件中在上述代码执行之前就已经经过了<script>标签从CDN下载了咱们须要的依赖库了,externals配置会自动在承载咱们应用的html文件中加入:
<script src="https://code.jquery.com/jquery-1.1.14.js">
externals还支持其余灵活的配置语法,好比我只想访问库中的某些方法,咱们甚至能够把这些方法附加到window对象上:
externals : { subtract : { root: ["math", "subtract"] } }
我就能够经过 window.math.subtract 来访问subtract方法了。
对于其余配置方式若是有兴趣的话能够自行查看文档。
可是,笔者的项目并无这么作,由于在它最终交付给客户后,应该是处于一个内网环境(或者一个被防火墙严重限制的环境)中,极大可能没法访问任何互联网资源,所以经过<script>脚本请求CDN资源的方式将失效,前置依赖没法正常下载就会致使整个应用奔溃。
5、DllPlugin
在上个段落中的结尾处,提到了笔者的项目在交付用户后会面临的网络困境,因此笔者必须选择另一个方式来实现相似于externals配置可以提供的功能。那就是Webpack DLLPlugin以及它的好搭档DLLReferencePlugin。笔者有关DLLPlugin的使用都是在构建生产包的时候使用。
要使用DLLPlugiin,咱们须要单独开一个webpack配置,暂且将其命名为webpack.dll.config.js,以便和主Webpack的配置文件webpack.config.js进行区分。内容以下:
'use strict';
process.env.NODE_ENV = 'production'; const webpack = require('webpack'); const path = require('path'); const {dll} = require('./dll'); const DllPlugin = require('webpack/lib/DllPlugin'); const TerserPlugin = require('terser-webpack-plugin'); const getClientEnvironment = require('./env'); const paths = require('./paths'); const shouldUseSourceMap = process.env.GENERATE_SOURCEMAP !== 'false'; module.exports = function (webpackEnv = 'production') { const isEnvDevelopment = webpackEnv === 'development'; const isEnvProduction = webpackEnv === 'production'; const publicPath = isEnvProduction ? paths.servedPath : isEnvDevelopment && '/'; const publicUrl = isEnvProduction ? publicPath.slice(0, -1) : isEnvDevelopment && ''; const env = getClientEnvironment(publicUrl); return { mode: isEnvProduction ? 'production' : isEnvDevelopment && 'development', devtool: isEnvProduction ? 'source-map' : isEnvDevelopment && 'cheap-module-source-map', entry: dll, output: { path: isEnvProduction ? paths.appBuildDll : undefined, filename: '[name].dll.js', library: '[name]_dll_[hash]' }, optimization: { minimize: isEnvProduction, minimizer: [ ...省略 ] }, plugins: [ new webpack.DefinePlugin(env.stringified), new DllPlugin({ context: path.resolve(__dirname), path: path.resolve(paths.appBuildDll, '[name].manifest.json'), name: '[name]_dll_[hash]', }), ], }; };
为了方便DLL的管理,咱们还单独开了个dll.js文件来管理webpack.dll.config.js的入口entry,咱们把全部须要DLLPlugin处理的库都记录在这个文件中:
const dll = { core: [ 'react', '@hot-loader/react-dom', 'react-router-dom', 'prop-types', 'antd/lib/badge', 'antd/lib/button', 'antd/lib/checkbox', 'antd/lib/col', ...省略 ], tool: [ 'js-cookie', 'crypto-js/md5', 'ramda/src/curry', 'ramda/src/equals', ], shim: [ 'whatwg-fetch', 'ric-shim' ], widget: [ 'cecharts', ], }; module.exports = { dll, dllNames: Object.keys(dll), };
对于要把哪些库放入DLL中,请根据本身项目的状况来定,对于一些特别大的库,又无法作模块分割和不支持Tree Shaking的,好比Echarts,建议先去官网按项目所需功能定制一套,不要直接使用整个Echarts库,不然会白白消耗许多的下载时间,JS预处理的时间也会增加,减弱首屏性能。
而后咱们在webpack.config.js的plugins配置中加入DLLReferencePlguin来对DLLPlugin处理的库进行映射,好让编译后的代码可以从window对象中找到它们所依赖的库:
{ ...省略 plugins: [ ...省略 // 这里的...用于延展开数组,由于咱们的DLL有多个,每一个单独的DLL输出都须要有一个DLLReferencePlguin与之对应,去获取DLLPlugin输出的manifest.json库映射文件。
// dev环境下暂不采用DLLPlugin优化。 ...(isEnvProduction ? dllNames.map(dllName => new DllReferencePlugin({ context: path.resolve(__dirname), manifest: path.resolve(__dirname, '..', `build/static/dll/${dllName}.manifest.json`) })) : [] ), ...省略 ] ... }
咱们还须要在承载咱们应用的index.html模板中加入<script>,从webpack.dll.config.js里配置的output输出文件夹中前置引用这些DLL库。对于这个工做DLLPlguin和它的搭档不会帮咱们作这件事情,而已有的html-webpack-plugin也不能帮助咱们去作这件事情,由于咱们无法经过它往index.html模板加入特定内容,但它有个加强版的兄弟script-ext-html-webpack-plugin能够帮咱们作这件事情,笔者以前也用过这个插件内联JS到index.html中。但笔者懒得再往node_modules中加依赖包了,另辟了一个蹊径:
CRA这套架子已经使用了DefinePlugin来在编译时建立全局变量,最经常使用的就是建立process环境变量,让咱们的代码能够分辨是开发仍是生产环境,既然已有这样的设计,何不继续使用,让DLLPlugn编译的独立JS文件名暴露在某个全局变量下,并在index.html模板中循环这个变量数组,循环建立<script>标签不就好了,在上面提到的dll.js文件中最后导出的 dllNames 就是这个数组。
而后咱们改造一下index.html:
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title></title> <% if (process.env.NODE_ENV === "production") { %> <% process.env.DLL_NAMES.forEach(function (dllName){ %> <script src="/static/dll/<%= dllName %>.dll.js"></script> <% }) %> <% } %> </head> <body> <noscript>Please allow your browser to run JavaScript scripts.</noscript> <div id="root"></div> </body> </html>
最后咱们改造一下build.js脚本,加入打包DLL的步骤:
function buildDll (previousFileSizes){ let allDllExisted = dllNames.every(dllName => fs.existsSync(path.resolve(paths.appBuildDll, `${dllName}.dll.js`)) && fs.existsSync(path.resolve(paths.appBuildDll, `${dllName}.manifest.json`)) ); if (allDllExisted){ console.log(chalk.cyan('Dll is already existed, will run production build directly...\n')); return Promise.resolve(); } else { console.log(chalk.cyan('Dll missing or incomplete, first starts compiling dll...\n')); const dllCompiler = webpack(dllConfig); return new Promise((resolve, reject) => { dllCompiler.run((err, stats) => { ...省略 }) }); } }
checkBrowsers(paths.appPath, isInteractive) .then(() => { // Start dll webpack build. return buildDll(); }) .then(() => { // First, read the current file sizes in build directory. // This lets us display how much they changed later. return measureFileSizesBeforeBuild(paths.appBuild); }) .then(previousFileSizes => { // Remove folders contains hash files, but leave static/dll folder. fs.emptyDirSync(paths.appBuildCSS); fs.emptyDirSync(paths.appBuildJS); fs.emptyDirSync(paths.appBuildMedia); // Merge with the public folder copyPublicFolder(); // Start the primary webpack build return build(previousFileSizes); }) .then(({stats, previousFileSizes, warnings}) => { ... 省略 }) ... 省略
大体逻辑就是若是xxx.dll.js文件存在且对应的xxx.manifest.json也存在,那么就不从新编译DLL,若是缺失任意一个,就从新编译。DLL的编译过程走完后再进行主包的编译。因为咱们将DLLPlugin编译出的文件也放入build文件夹中,因此以前每次开始编译主包都须要清空整个build文件夹的操做也修改成了仅仅清空除了放置有dll.js和manifest.json的目录。
若是咱们的底层依赖库确实发生了变化,须要咱们更新DLL,按照以前的检测逻辑,咱们只须要删除整个某个dll.js文件便可,或者直接删除掉整个build文件夹。
哈哈,到此全部的有关DLL的配置就完成了,大功告成。
本段落开始时有提到过DLLPlugin的使用都是在生产环境下。由于开发环境下的打包状况很特殊并且复杂:
在开发环境下整个应用是经过webpack-dev-server来打包并起一个Express服务来serve的。Express服务在内部挂载了webpack-dev-middleware做为中间件,webpack-dev-middleware能够实现serve由Webpack compiler编译出的全部资源、将全部的资源打入内存文件系统中的功能,并能结合dev-sever实现监听源文件改动并提供HRM。webpack-dev-server接收了一个Webpack compiler和一个有关HTTP配置的config做为实例化时的参数。这个compiler会在webpack-dev-middleware中执行,用监听方式启动compiler后,compiler的outputFileSystem会被webpack-dev-middleware替换成内存文件系统,其执行后打包出来的东西都没有实际落盘,而是存放在了这个内存文件系统中,而ouputFileSystem自己是在Node.js的fs模块基础上封装的。将编译结果存放进内存中是webpack-dev-middleware内部最奇妙的地方。事实上就算将文件资源落盘,也必须先把文件从磁盘读到内存中,再以流的形式返回给客户端,这样一来会多一个从磁盘中将文件读进内存的步骤,反而尚未直接操做内存快。内存文件系统在npm start命令起的进程被干掉后,就被回收了,下一次再起进程的时候,会建立一个全新的内存文件系统。
这里顺带再说下webppack-dev-middle源码的流程:
首先在执行wdm这个入口文件导出的方法中,经过setFs方法将本来的Node.js的fs给替换为memory-fs内存文件系统,因此下面提到的文件系统都是指这个内存文件系统。
按照Express的流程,全部请求都会依次进入Express挂载的中间件中,在webppack-dev-middle中间件中,会先经过getFilenameFromUrl方法根据传入的此次请求的url,compiler和webpack的publicPath等有关配置,肯定一个finame的URI,这个URI最终会返回三种结果,这三种结果将走三种不一样的处理流程:
结果1:一个布尔值false
结果2:一个HTTP API的请求路径
结果3:一个静态文件资源的请求路径
结果1的后续处理:表示请求url带有hostname等特征,也就肯定这个发给dev-server的请求并非须要webpack-dev-middle处理的,将执行goNext(),根据是不是serverSideRender,在goNext方法中的处理过程会有所不一样,但最终都会调用Express应用的next()进入下一个中间件。
结果2的后续处理:经过文件系统的statSync方法会判断这个路径并非一个文件资源的路径,再根据isDirectory方法判断它是不是一个目录的路径,若是是的话,再看看该目录下是否有一个index.html的文件,有就返回。在开发的时候咱们在浏览器地址栏输入"localhost:3000"就是走的这个流程,会返回承载前端应用的那个入口index.html文件。
结果3的后续处理:经过文件系统的statSync方法会判断出这个路径就是一个文件资源的路径,会经过文件系统的readFileSync方法从内存中读取到该文件内容,再拿到该资源对应的MIME类型,设置好response的header返回给浏览器,浏览器就能正确解析返回的文件资源流了。咱们在开发环境下,全部对js、js.map、图片资源等除了index.html外的请求都是走的这个流程。
全部,因为开发环境打包的特殊性,在开发环境下将编译DLL的compiler的和主compiler结合起来还须要再看看,所以怎么在开发环境使用DLLPlugin还须要再研究下。因此笔者只是在开发环境使用了多线程和各类缓存。因为开发环境下,编译的工做量少于生产环境,而且对全部资源的读写都是走内存的,所以速度很快,每次检测到源文件变更并进行重编译的过程也会很快。因此开发环境的编译速度在目前来看还能够接受,暂时不须要优化。
这里顺带说一句,笔者以前在看有关DLLPlugin的文档和文章时,也注意到了一个现象,就是不少人都说DLLPlugin过期了,而externals使用起来更方便,配置更简单,甚至在CRA、vue-cli这些最佳实践的脚手架中都已经没再继续使用DLLPlugin了,由于Webpack4.x的编译速度已经足够快了。笔者的体会就是:我这个项目就是基于Webpack4.X的,项目规模变大之后,没有以为4.X有多么地快。笔者的项目在交付客户后也极大可能不能访问互联网,因此经过配置externals将资源请求导向CDN的方式对笔者的项目来讲没有用,只能经过使用DLLPlugin提升生产包的编译速度。我想这也是为何Webpack到了4.X版本依然没有去掉DLLPlugin的缘由,由于不是全部的前端项目都必定是互联网项目,实践出真知。
6、编译速度提高了多少?
笔者开发机4和8线程,单核基础频率2.2GHz。全部测试都基于已对Echarts进行功能定制。
1. 最原始的CRA脚手架编译笔者这个项目的速度。
初次编译,无任何CRA原始配置的缓存,这和最初在CI上进行编译的状况彻底同样,每次差很少都是这个耗时。
由于每次node_moduels都要删除重来,没法缓存任何以前的编译结果:
大概1分10多秒。CI上的话,大概会稍微快点,由于硬件性能好些。
有缓存之后:
若是不定制Echarts的话,直接引入整个Echarts,在没缓存的时候大概会多5秒耗时。
2. 引入dll后。
初次打包,而且无任何DLL文件和CRA原始配置的缓存:
先编译DLL,再编译主包,整个过程耗时直接变成了57秒。
有DLL文件和缓存后:
降到27秒多了。
3.最后咱们把多线程和cache-loader上了:
无任何DLL文件、CRA原始配置的缓存以及cache-loader的缓存时:
大概在接近60秒左右。
有DLL文件和全部缓存后:
最终,耗时已经降低至17秒左右了。在运行CI的编译服务器上执行的话,速度还会更快些。
在打包速度上,比最原始的1分10多秒耗时已经有本质上的提高了。这才是咱们想要的。
7、总结
至此,咱们已经将全部的底层依赖DLL化了,把几乎全部能缓存的东西都缓存了,并支持多线程(多进程模拟)编译。当项目稳定后,不管项目规模多大,小幅度的修改将始终保持在这个编译速度,耗时不会有太大的变化。
听说在Webpack5.X带来了全新的编译性能体验,很期待使用它的时候。谈及到此,笔者只以为有淡淡的忧伤,那就是前端技术、工具链、框架、开发理念这些的更新速度实在是太快了。就拿Webpack这套构建平台来讲,当Webpack5.X普及后,Webpack4.X这套优化可能也就过期了,整套工具链又须要从新学习。