随着客户端应用程序复杂性的增加,其捆绑包的大小也越来越大。 在这种情况下,由于各种原因,人们遭受最大的压力是不得不使用缓慢的Internet连接。 而且,每天只会变得更糟。

这篇文章的作者(我们今天出版的翻译)是使用Wix编写的。 他想谈谈如何使用Webpack Analyzer和React Lazy / Suspense将一个捆绑包的大小减少约80%。
优化要多早开始?
如果您刚刚开始在新的Web应用程序上工作,那么您可能正在尝试着重于使其成为一个新的Web应用程序,也就是说,“动手做”。 您可能不太在意性能或套件大小。 我能理解 但是,我的经验表明,捆绑包的性能和大小应从一开始就加以考虑。 良好的应用程序体系结构和及时的“对项目的未来进行反思”从长远来看将为您节省大量时间,并有助于避免积累严重的技术债务。 显然,很难预先“预见”所有事情,但是从项目的第一天开始,您应该尽一切努力。
我认为应该从一开始就使用几个出色的工具。 这些工具即使在NPM程序包在应用程序中不占重要位置之前,也可以帮助您识别它们。
▍仇视
Bundlephobia是一个使您知道某个NPM软件包将增加捆绑大小的站点的网站。 这是一个很好的工具,可以帮助程序员针对他可能需要的第三方程序包的选择做出正确的决定。 Bundlephobia帮助设计应用程序体系结构,以免其大小变得太大。 下图显示了检查流行的库中是否有时间工作的结果,这称为矩。 您会看到该库非常大-压缩形式的库将近66 Kb。 对于使用高速Internet的许多用户而言,这没什么。 但是,值得注意的是,此软件包在2G / 3G网络中的下载时间会变成多少。 它分别是2.2秒和1.32秒。 而且,请注意,我们仅在谈论这一软件包。
Bundlephobia矩包分析结果Cost进口费用
对于许多流行的代码编辑器来说,
导入成本是一个非常有趣的扩展(对于
VS Code而言,下载量已超过一百万)。 它可以显示导入包的“成本”。 我特别喜欢它,因为它有助于在处理应用程序时立即确定应用程序的问题区域。 下图(取自GitHub上的Import Cost)显示了一个很好的示例,说明了另一种导入实体方法对项目规模的影响。 因此,从Lodash导入
uniqueId
id属性会导致将整个库导入到项目中(70 Kb)。 而且,如果直接导入
uniqueId
函数,则项目大小将仅增加2 Kb。
在此处阅读有关进口成本的更多信息。
将整个Lodash库导入项目的“成本”以及该库中仅一个特定功能的导入捆束不合理的情况
因此,您已经创建了出色的应用程序。 它可以在高速Internet和功能最强大的,装有RAM的计算机上很好地工作。 您已将其发布到现实世界中。 一段时间后,投诉开始来自您或您自己的分析师。 这些投诉与应用程序加载时间有关。 当我们在Wix推出我正在研究的新功能时,最近发生了类似的事情。
为了让您快速入门,让我们谈谈这个新机会。 这是一个新的进度栏,位于用户站点设置界面的侧面板顶部。 该机制的目的是将用户的注意力吸引到他需要执行的各个步骤上,以使他的业务项目获得更大的成功机会(连接到SEO,添加要交付商品的区域,添加第一个产品,依此类推)。
通过使用Web套接字连接到服务器,进度条会自动更新。 当用户完成所有建议的步骤时,将显示一个弹出窗口,祝贺您。 关闭此窗口后,进度栏将被隐藏,并且在使用它的网站上不会再显示。 这就是我们刚才所说的。
进度栏和祝贺窗口怎么了 为什么我们的分析师向我抱怨页面加载时间增加了? 当我使用Chrome开发人员工具的“网络”标签检查事务状态时,立即发现我的捆绑包非常大。 即,其大小为190 Kb。
使用Chrome开发者工具找到的捆绑包大小“为什么这件小东西需要相对较大的捆?”我当时想。 但是事实-为什么?
搜索捆绑中的问题点
在我意识到捆绑包的尺寸太大之后,该该找出原因了。 这是
Webpack Bundle Analyzer派上用场的地方-这是一个用于识别包问题区域的好工具。 它打开一个新的浏览器选项卡并显示依赖项信息。
这是在我使用此工具分析捆绑软件之后发生的事情。
Webpack捆绑分析器结果在分析仪的帮助下,我能够检测到“犯罪分子”。 这里使用的是lottie-web软件包,它使捆绑包的大小增加了61.45 Kb。 Lottie是一个非常不错的JavaScript库,它允许使用标准浏览器工具输出在Adobe After Effects中创建的动画。 以我为例,这是为了使我们的设计师需要一个不错的动画,该动画是在出现祝贺窗口时执行的。 他创建了此动画,并以JSON文件的形式将其提供给我,然后我将该文件转移到Lottie包中,并获得了精美的动画。 除了lottie-web程序包之外,我还有一个带有动画说明的JSON文件。 该文件的大小为26 Kb。 结果,Lottie库,JSON文件以及一些辅助的小依赖项“花费”了我大约94 Kb。 这只是窗口的动画,向用户表示祝贺。 用户看到这些祝贺时,应该会感到高兴。 这一切让我难过。
React Lazy / Suspense技术助您一臂之力
发现问题的原因之后,该解决该问题了。 很明显,没有必要在工作开始时就加载动画所需的所有内容。 实际上,在用户当前会话期间,他有很大的机会不必显示祝贺窗口。 然后我熟悉了最近出现的React Lazy / Suspense技术,并认为现在我可能有很好的机会对其进行测试。
如果您不熟悉“惰性”(lazy)组件的概念,那么请知道它们的含义是将应用程序分成小段代码。 仅在需要时才下载这些片段。 就我而言,这是因为我需要从进度条的主要功能中选择负责表示祝贺的组件。 仅当用户完成建议的步骤序列时才有必要加载此组件。
React 16.6.0(及更高版本)具有一个简单的API,旨在呈现惰性组件。 这些是
React.lazy和React.Suspense 。 考虑一个例子:
const OtherComponent = React.lazy(() => import('./OtherComponent')); function MyComponent() { return ( <div> <React.Suspense fallback={<div>Loading...</div>}> <OtherComponent /> </React.Suspense> </div> ); }
我们有一个显示
<div>
元素的组件,并且其中包含一个
Suspense
组件,该组件包装了
OtherComponent
组件。 如果查看此示例的第一行,则可以看到
OtherComponent
没有直接导入到代码中。 通常,这样的导入看起来像是
import OtherComponent from './OtherComponent';
。
取而代之的是,将导入命令构造为采用文件路径的函数。 该机制有效,因为Webpack具有内置的代码分离工具。 当代码中存在类似的构造时,将返回promise,该promise在下载文件后,将使用该文件的内容进行解析。 我们的导入团队包装在
React.lazy
函数中。
在
MyComponent
返回的渲染材料中,
OtherComponent
包装在具有
fallback
属性的
React.Suspense
组件中。 在我们的案例中,事实证明,当渲染到达
OtherComponent
,开始加载相应的组件。 同时,呈现写入
fallback
属性的内容。 在这个例子中,这是文本
Loading…
实际上,仅此而已。 这些机制就可以发挥作用。
的确,在使用懒惰/暂挂时,需要考虑几个功能。
- 以“惰性”方式导入的组件必须包含默认导出,这将是该组件的入口点。 在此不能使用命名出口。
- 您需要将使用
React.lazy
函数导入的组件包装在React.Suspense
组件中。 React.Suspense
组件React.Suspense
提供fallback
属性。 否则,将发生错误。 但是,如果您只是不想在组件完成延迟加载之前呈现任何内容,则可以在fallback
简单地编写null
,而不必试图以某种棘手的方式避免在此属性中编写内容。
使用React Lazy / Suspense是否对我有帮助?
是的,它有帮助! 代码拆分效果非常好。 让我们看一下使用Webpack分析新捆绑软件的结果。
拆分代码后的Webpack Bundle Analyzer结果如您所见,我的捆绑包大小减少了约50%-降至96 Kb。 太好了!
那又如何解决了呢? 不,很不幸。 当我查看该页面时,事实证明带有祝贺的弹出窗口失去了定位。
祝贺窗口不会出现在您需要的地方问题是我通过更改React组件的状态来“询问”要打开的窗口。 同时,我已经使用
React.Suspense
组件渲染了
null
(也就是说,我还没有渲染任何东西)。 延迟加载必要的数据后,将相关材料添加到DOM。 但是,弹出窗口的定位已经完成。 结果,由于相应组件的属性没有更改,因此该组件“不知道”它需要解决有关定位的问题。 如果更改了浏览器窗口的大小,则弹出窗口会出现在正确的位置,原因是相应的组件正在观察属性的变化和调整窗口大小的事件,并在必要时启动重新定位。
如何解决这个问题? 解决的办法是消除“中介”。
我必须首先加载“惰性”组件,然后才写入状态,以祝贺窗口需要打开。 我能够使用相同的Webpack代码共享机制来做到这一点,但是现在-无需使用
React.lazy
实现导入:
async loadAndSetHappyMoment() { const component = await import( '../SidebarHappyMoment/SidebarHappyMoment.component' ); this.SidebarHappyMoment = component.SidebarHappyMoment; this.setState({ tooltipLevel: TooltipLevel.happyMoment, }); }
在通过Web套接字机制通知我的组件它需要显示一个带有祝贺性的窗口之后,调用此函数。 我使用了Webpack
import
功能(第二行代码)。 如果您还记得,我在上面说过,该函数返回一个Promise,因此我能够使用
async/await
构造。
组件完成加载后,我将其写入组件实例(使用命令
this.SidebarHappyMoment = component.SidebarHappyMoment;
)。 这使我有机会稍后在渲染时使用它。 请注意,我现在可以使用命名导出。 就我而言,在上一行中,我使用了名称
SidebarHappyMoment
。 最后,这同样重要,我“通知”窗口需要打开,并在知道组件已准备好运行后相应地更改其状态。
结果,呈现代码现在如下所示:
renderTooltip() { if (this.state.tooltipLevel === TooltipLevel.happyMoment) { return <this.SidebarHappyMoment />; } // ... }
请注意,该命令
return <this.SidebarHappyMoment />;
返回
this.SidebarHappyMoment
我之前写到组件实例的内容。 现在,这是一个普通的同步渲染功能,与您使用一百万次的功能相同。 现在,带有祝贺字样的窗口将准确显示在应该显示的位置。 事实是,只有在其内容准备就绪后才能打开。
产品定义架构
如果本节标题中的想法在您的想象中引起了很大的问号,那么请注意就是这种情况。 该产品定义了体系结构。
关于以下事实的事实:产品定义了在首次渲染组件时应显示和交互的内容。 这有助于开发人员弄清楚他可以从主代码中分离出什么,并在以后必要时进行加载。 我考虑了这种情况,并“记住”,在用户完成建议的网站设置步骤后,或者如果他不是网站管理员,则无需向他显示进度栏及其内容已连接。 现在,使用此信息,我可以继续共享该捆绑包。 那就是我得到的。
束的持续分离此后,束的大小仅为38 Kb。 我想提醒您,我们从190 Kb开始。 捆绑包的尺寸减少了80%。 顺便说一下,我已经看到了代码分离的其他可能性。 我迫不及待要继续优化套件。
总结
开发人员有能力努力停留在自己的“舒适区”,而不是深入研究代码本身及其功能的设备。 但是,使用上述工具,具有创造力的思想并与其他专家紧密合作的程序员,可能会通过显着减小包含开始使用此应用程序所需的包的大小来提高其应用程序的性能。
亲爱的读者们! 您是否使用代码拆分来加快Web应用程序的加载速度?
