找出向用户提供文件的最佳方式可能是一项棘手的工作。有许多不同的场景、不同的技术、不同的术语。在本文中,我希望为您提供所需的一切,以便您能够:了解哪种文件拆分策略最适合您的站点和用户知道如何去做根据Webpack词汇表,有两种不同类型的文件拆分。这些术语听起来可以互换,但显然不是。webpack文件分离包括两部分,一是Bundlesplitting,二是Codesplitting:Bundlesplitting:创建更多更小的文件并并行加载,以获得更好的缓存效果。主要作用是使浏览器并行下载,提高下载速度。并且使用浏览器缓存,只有当代码被修改,文件名中的哈希值发生变化时,才会重新加载。代码拆分:只加载用户最需要的部分,其余代码遵循懒加载策略。主要作用是加快页面的加载速度,不加载不必要的代码。第二个听起来更有吸引力,不是吗?事实上,许多关于该主题的文章似乎都认为这是制作更小的JavaScript文件的唯一有价值的情况。但我在这里要告诉你的是,第一个在许多网站上更有价值,应该是你对所有网站做的第一件事。让我们找出来。Bundlesplittingbundlesplitting背后的想法很简单,如果你有一个巨大的文件并且你改变了一行代码,用户必须重新下载整个文件。但是如果你把它分成两个文件,那么用户只需要下载改变的文件,浏览器就会从缓存中提供另一个文件。值得注意的是,由于bundlesplitting都是关于缓存的,所以它对第一次访问没有影响。(我认为关于性能的过多讨论是关于第一次访问网站,部分原因可能是“第一印象很重要”,部分原因是它很好且易于衡量。对于频繁使用的用户来说,量化性能增强的影响可能很棘手,但我们必须这样做!这将需要一个电子表格,因此我们需要针对一组非常具体的环境,我们可以针对这些环境测试每种缓存策略。这就是我在上一段中提到的情况:Alice每周访问我们的网站一次,持续10周我们每周更新网站我们每周更新我们的“产品列表”页面我们也有一个“产品详细信息”页面,但是我们'目前没有开发它。在第5周,我们向网站添加了一个新的npm包。在第8周,我们更新了一个现有的npm包。某些类型的人(比如我)会尝试让这个场景尽可能逼真。不要这样做。这真的无关紧要,我们稍后会找出原因。基线假设我们的JavaScript包的总大小为400KB,我们当前将其加载为一个名为main.js的文件。我们有一个Webpack配置如下(我省略了一些无关的配置)//webpack.config.jsconstpath=require('path')module.exports={entry:path.resolve(__dirame,'src/index.js')output:{path:path.resolve(__dirname,'dist'),filename:'[name].[contenthash].js'}}对于那些新的缓存破坏者:每当我说main.js时,我实际上指的是main.xMePWxHo.js,其中的字符串是文件内容的哈希值。这意味着当应用程序中的代码更改时会出现不同的文件名,从而迫使浏览器下载新文件。当我们对站点进行一些新更改时,此包的内容哈希每周都会更改。所以爱丽丝每周都会访问我们的网站并下载一个400kb的新文件。如果我们把这些事件做成一个表,它会是这样的。那是10周内的4.12MB,我们可以做得更好。拆分供应商包让我们将包拆分为main.js和vendor.js文件。//webpack.config.jsconstpath=require('path')module.exports={entry:path.resolve(__dirname,'src/index.js'),output:{path:path.resolve(__dirname,'dist'),filename:'[name].[contenthash].js',},optimization:{splitChunks:{chunks:'all'}}}Webpack4会为你做最好的事情,而不会告诉你你想如何拆分Subcontract。这导致我们对webpack如何拆分chunks知之甚少,结果人们会问“你到底对我的chunks做了什么?”添加optimization.splitChunks.chunks='all'的一种方法是“将块放在node_modules中,所有内容都放入名为vendors~main.js的文件中”。通过这种基本的bundle拆分,Alice每次访问时仍会下载一个新的200kbmain.js,但仅在***、8和5周(不按此顺序)下载200kbvendor.js。总计:2.64MB。减少36%。在我们的配置中添加五行代码还不错。在进一步阅读之前先做。如果你需要从Webpack3升级到4,别担心,这很容易。我认为这种性能改进看起来更抽象,因为它已经超过10周了,但它确实为忠实用户带来了36%的字节减少,我们应该为自己感到自豪。但我们可以做得更好。分离每个npm包我们的vendor.js遇到了与我们的main.js文件相同的问题-对其中一部分的更改意味着重新下载它的所有部分。那么为什么不为每个npm包创建一个单独的文件呢?这很容易做到。所以将react、lodash、redux、moment等拆分成不同的文件:constpath=require('path');constwebpack=require('webpack');module.exports={entry:path.resolve(__dirname,'src/index.js'),plugins:[newwebpack.HashedModuleIdsPlugin(),//sothatfilehashesdon'tchangeunexpectedly],output:{path:path.resolve(__dirname,'dist'),filename:'[name].[contenthash].js',},optimization:{runtimeChunk:'single',splitChunks:{chunks:'all',maxInitialRequests:Infinity,minSize:0,cacheGroups:{vendor:{test:/[\\/]node_modules[\\/]/,name(module){//getthename.E.g.node_modules/packageName/not/this/part.js//ornode_modules/packageNameconstpackageName=module.context.match(/[\\/]node_modules[\\/](.*?)([\\/]|$)/)[1];//npm包名称是URL安全的,但有些服务器不喜欢@symbolsreturn`npm.${packageName.replace('@','')}`;},},},},},};文档会很好地解释这里发生的大部分事情,但我会稍微解释一下需要注意的部分,因为它们占用了我太多时间。Webpack有一些不太聪明的默认设置,比如拆分输出文件时最多3个文件和30KB的最小文件大小(所有较小的文件将被连接),所以我覆盖了那些。cacheGroups是我们定义Webpack如何将数据块分组到输出文件中的规则的地方。有一个名为“vendor”的模块,它将用于从node_modules加载的任何模块。通常,您只需将输出文件的名称定义为字符串。但是我将name定义为一个函数(这个函数将被每个解析的文件调用)。然后从模块的路径返回包的名称。所以我们将为每个包获取一个文件,比如npm.react-dom.899sadfhj4.js。NPM包名称必须是URL安全的才能发布,因此我们不需要packageName的encodeURI。但是,我遇到了一个.NET服务器无法提供名称中带有@的文件(来自范围包),因此我在此代码片段中替换了@。整个设置很棒,因为它是不可变的。无需维护-无需按名称引用任何包。Alice仍然会每周重新下载200KB的main.js文件,并且仍然会在第一次访问时下载200KB的npm包,但她永远不会下载同一个包两次。总计:2.24MB。与基线相比减少了44%,这对于一些可以从博客文章中复制/粘贴的代码来说非常酷。我想知道是否有可能超过50%?那很好。分隔应用程序代码的区域让我们转到main.js文件,可怜的Alice一次又一次下载该文件。我之前提到过,我们在这个站点上有两个不同的部分:产品列表和产品详细信息页面。每个区域25KB的唯一代码(共享代码150KB)。我们的产品详细信息页面现在没有太大变化,因为我们做得很好。所以如果我们把它做成一个单独的文件,大多数时候我们可以从缓存中获取它。另外,我们的网站有一个很大的用于渲染图标的内联SVG文件,只有25KB,而且这个文件很少更改,我们也需要对其进行优化。我们只是手动添加一些入口点,告诉Webpack为每个项目创建一个文件。module.exports={entry:{main:path.resolve(__dirname,'src/index.js'),ProductList:path.resolve(__dirname,'src/ProductList/ProductList.js'),ProductPage:path.resolve(__dirname,'src/ProductPage/ProductPage.js'),Icon:path.resolve(__dirname,'src/Icon/Icon.js'),},输出:{path:path.resolve(__dirname,'dist'),filename:'[name].[contenthash:8].js',},plugins:[newwebpack.HashedModuleIdsPlugin(),//sothatfilehashesdon'tchangeunexpectedly],optimization:{runtimeChunk:'single',splitChunks:{chunks:'all',maxInitialRequests:Infinity,minSize:0,cacheGroups:{vendor:{test:/[\\/]node_modules[\\/]/,name(module){//getthename.E.g.node_modules/packageName/not/this/part.js//ornode_modules/packageNameconstpackageName=module.context.match(/[\\/]node_modules[\\/](.*?)([\\/]|$)/)[1];//npmpackagenamesareURL-安全,但有些服务器不喜欢@symbolsreturn`npm.${packageName.replace('@','')}`;},},},},},};Webpack还将提供ProductList和ProductPage内容创建文件之间的共享,因此我们不会以重复代码告终。在大多数情况下,这将为爱丽丝节省50KB的下载空间。只有1.815MB!我们为爱丽丝节省了高达56%的下载量,而且这种节省(在我们的理论场景中)将持续到时间结束。所有这些都只在webpack配置中发生了变化——我们没有对应用程序代码进行任何更改。正如我之前提到的,测试中的确切场景并不重要。那是因为,无论你想到什么方案,结论都是一样的:将你的应用程序拆分成合理的小文件,以便用户下载更少的代码。很快,=将讨论“代码拆分”——另一种类型的文件拆分——但首先我想解决您现在正在考虑的三个问题。#1:很多网络请求不是更慢吗?答案当然是否定的。这曾经是HTTP/1.1时代的情况,但在HTTP/2时代不是这样。尽管如此,这篇2016年的文章和可汗学院2015年的文章都得出结论,即使使用HTTP/2,下载太多文件也会变慢。但在这两篇文章中,“太多”的意思是“数百”。所以请记住,如果您有数百个文件,一开始可能会达到并发限制。如果你想知道,对HTTP/2的支持可以追溯到Windows10上的ie11。我做了详尽的调查,每个人都使用比这更旧的设置,他们一致向我保证,他们不关心网站加载的速度。#2:每个webpack包中没有开销/参考代码?是的,这也是事实。好吧,该死的:更多文件=更多Webpack引用更多文件=无压缩让我们量化这一点,以便我们确切地知道需要担心多少。好的,我刚刚做了一个测试,将一个190KB的网站分成19个文件,将发送到浏览器的总字节数增加了大约2%。所以...第一次访问增加2%,每次访问减少60%,直到网站出现故障。正确的担忧是:一点也不。当我测试1个文件与19个文件时,我想我应该在几个不同的网络上尝试一下,包括3G和4G上的HTTP/1.1,该网站的加载时间减少了30%,文件减少了19%。这是非常混乱的数据。比如在4G跑2号上,站点加载时间是646ms,经过两次跑后,加载时间是1116ms,比之前长了73%,没有变化。所以声称HTTP/2“快30%”似乎有点狡猾。我创建此表是为了尝试量化HTTP/2带来的差异,但实际上我唯一能说的是“它可能不会产生显着差异”。真正惊喜的是***两条线。那是旧的Windows和HTTP/1.1,我敢打赌它要慢得多,我想我需要稍微放慢互联网速度。我从微软的网站下载了一个Windows7虚拟机来测试这些东西。它随IE8一起提供,我想将它升级到IE9,所以我访问了Microsoft的IE9下载页面...还有一个关于HTTP/2的问题,你知道它现在内置在Node中了吗?如果您要尝试一下,我已经编写了一个带有gzip、brotli和响应缓存的小型100行HTTP/2服务器,以供您进行测试。这就是我要说的关于捆绑拆分的全部内容。我认为这种方法的唯一缺点是必须不断说服人们加载大量小文件是可以的。Codesplitting(加载你需要的代码)我说,这种特殊的方法只对某些站点有意义。我喜欢应用我刚刚制定的20/20规则:如果您网站的某个部分只有20%的用户访问,并且它大于您网站JavaScript的20%,那么您应该按需加载该代码。你怎么决定?假设您有一个购物网站,想知道是否应该将“结帐”代码分开,因为只有30%的访问者会去那里。首先要做的是卖更好的东西。第二件事是弄清楚有多少代码是完全独立的用于结帐功能。由于在进行“代码拆分”之前您应该始终“捆绑拆分”,因此您可能已经知道这部分代码有多大。它可能比你想象的要小,所以在你太兴奋之前做加法。例如,如果你有一个React站点,那么你的store、reducer、路由、操作等都将在整个站点共享。唯一的部分将主要是组件和它们的帮助类。所以您注意到您的结帐页面有7KB的完全唯一的代码。该网站的其余部分是300KB。我会看着这个并说,我不会因为以下原因将其拆分:急切加载不会变慢。请记住,您正在并行加载所有这些文件。看看你能不能记录下300KB和307KB之间的加载时间差异。* 如果稍后加载此代码,用户必须在单击“拿走我的钱”后等待文件-您希望延迟的最短时间。代码拆分需要更改应用程序代码。它在以前只有同步逻辑的地方引入了异步逻辑。这不是火箭科学,但我认为复杂性应该通过感知到的用户体验改进来证明。让我们看一下代码拆分的两个示例。Polyfills我将从这个开始,因为它适用于大多数站点并且是一个很好的介绍。我在我的网站上使用了一些奇特的功能,所以我有一个导入我需要的所有polyfill的文件,它由以下八行组成://polyfills.jsrequire('whatwg-fetch');require('intl');require('url-polyfill');require('core-js/web/dom-collections');require('core-js/es6/map');require('core-js/es6/string');require('core-js/es6/array');require('core-js/es6/object');在index.js中导入此文件。//index-always-poly.jsimport'./polyfills';importReactfrom'react';importReactDOMfrom'react-dom';importAppfrom'./App/App';import'./index.css';constrender=()=>{ReactDOM.render(
