
代码拆分。 代码拆分无处不在。 但是,为什么呢? 只是因为现在有太多的javascript ,并且并非所有的JavaScript都在同一时间使用。
JS是一件很沉重的事情。 不适用于您的iPhone Xs或全新的i9笔记本电脑,但适用于数百万(可能是数十亿) 速度较慢的设备所有者。 或者,至少对于您的手表。
所以-JS不好,但是如果我们禁用它 ,会发生什么 -问题将消失……对于某些站点,对于基于React的站点,它会“随站点”消失。 但是无论如何-有些站点可以在没有JS的情况下运行...我们应该向他们学习一些东西...
代码拆分
今天,我们有两种方法,可以使它变得更好或不使它变得更糟:
1.写更少的代码
那是你最好的事情。 尽管React Hooks
可以让您减少一些代码,而Svelte
解决方案却可以使您生成的代码比平时少,但这并不是一件容易的事。
它不仅与代码有关,而且与功能有关-要使代码“紧凑”,就必须使其“紧凑”。 如果应用程序包做很多事情(并以20种语言提供),则无法保持它很小。
有写简短代码的方法,有写相反的实现的方法- 血腥的企业 。 而且,您都知道,两者都是合法的。

但是主要的问题-代码本身。 一个简单的react应用程序可以轻松绕过“推荐”的250kb。 您可能需要花费一个月的时间对其进行优化,然后将其缩小。 “小型”优化已得到充分证明,并且非常有用-只需获得具有size-limit
bundle-analyzer
并恢复其形状即可。
有许多库在为每个字节而战,试图将您限制在自己的范围内-preact和storeon ,仅举几例。
但是我们的应用程序超出了200kb。 接近100Mb 。 删除千字节毫无意义。 即使删除兆字节也没有任何意义。
片刻之后,将您的应用程序缩小是不可能的。 随着时间的推移,它将变得更大。
2.减少代码
或者, code split
。 换句话说- 投降 。 拿起100mb的捆绑包,然后用它制作20个5mb的捆绑包。 老实说-这是处理应用程序的唯一可能的方法-如果应用程序很大,请从中创建一堆较小的应用程序。
但是,您现在应该知道一件事:无论您选择什么选项,它都是实现细节,而我们正在寻找更可靠的东西。
关于代码拆分的真相
关于代码拆分的真相是,它的本质是TIME SEPARATION 。 您不仅在拆分代码,还以一种在单个时间点使用尽可能少的方式拆分代码。
只是不要立即发布您不需要的代码。 摆脱它。

说起来容易,很难做。 我有一些繁重但没有充分拆分的应用程序,其中任何页面的加载量占所有内容的50%。 有时, code splitting
会变成code separation
,我的意思是-您可以将代码移到不同的块中,但仍然可以全部使用。 Recal说: “现在就不要发布您不需要的代码” ,-我需要 50%的代码,这才是真正的问题。
有时仅在此处添加import
是不够的。 直到不是时间分隔,而只有空间分隔-完全没有关系。
有3种常见的代码拆分方式:
- 只是动态
import
。 这些天很少使用。 更多有关跟踪状态的问题。 Lazy
组件,当您可以推迟渲染和加载React组件时。 这些天,大约90%的“反应代码拆分”。- 懒惰
Library
,实际上是.1
,但是您将通过React渲染道具获得库代码。 在react-imported-component和loadable-components中实现 。 很有用,但并不广为人知。
组件级代码拆分
这是最受欢迎的。 作为按路由的代码拆分或按组件的代码拆分。 做到这一点并保持良好的感知结果并非易事。 死于Flash of Loading Content
。
好的技术是:
- 并行加载
js chunk
和路由data
。 - 使用
skeleton
在页面加载之前显示类似于页面的内容(例如Facebook)。 prefetch
块,甚至可以使用guess-js进行更好的预测。- 使用一些延迟,加载指示符,
animations
和Suspense
(以供将来使用)来缓和过渡。
而且,这一切都与感知性能有关。

来自具有Ghost Elements的改进UX的图像
听起来不好
您知道,我可以称自己为代码拆分专家-但是我有自己的失败经历。
有时我可能无法减小捆绑包的大小。 有时,只要the _more_ code splitting you are introducing - the more you spatially split your page - the more time you need to _reassemble_ your page back
,我就可能无法提高结果的性能the _more_ code splitting you are introducing - the more you spatially split your page - the more time you need to _reassemble_ your page back
*。 这就是所谓的波涛 。
- 无需SSR或预渲染。 目前,正确的SSR可以改变游戏规则。

上周我遇到了两个失败:
- 只要我的图书馆更好,但我却在一个图书馆比较中迷失了,但比另一个图书馆大得多。 我未能执行“ 1.编写更少的代码” 。
- 优化一个由我妻子在React中制作的小型网站。 它使用的是基于路由的组件拆分,但是
header
和footer
保留在主包中,以使过渡更加“可以接受”。 仅几件事,彼此紧紧耦合在一起,使捆绑包的一面飙升到320kb(在gzip之前)。 没有什么重要的事情,也没有什么我能真正去除的。 死亡减少一千 。 我未能交付更少的代码 。
React-Dom是20%,core-js是10%,react-router,jsLingui,react-powerplug ...自己代码的20%...我们已经完成了。

解决方案
我已经开始考虑如何解决我的问题,以及为什么常见的解决方案无法在我的用例中正常工作。
我做了什么 我列出了所有关键位置,没有这些应用程序将根本无法工作,并试图理解为什么我拥有其余的位置。
这真是一个惊喜。 但是我的问题是在CSS中。 在原始CSS过渡中。
这是代码
我想对这一部分进行整体编码,但是由于两个原因,我无法做到这一点:
- 一旦需要,信息应该立即可见,没有任何延迟。 业务需求。
- 信息“ chrome”应该存在之前,才能进行属性句柄转换。
使用CSSTransitionGroup或recondition可以部分解决此问题。 但是,您知道,固定一个代码再添加另一个代码听起来很奇怪,即使实际上足够了 。 我的意思是添加更多的代码可以帮助删除更多的代码。 但是...但是...
应该有更好的方法!
TL; DR-这里有两个关键点:
DisplayData
必须被挂载 ,并且先于DOM存在。FocusLock
也应该事先存在,而不是导致DisplayData
重新安装,但是一开始就不需要动脑筋 。
因此,让我们改变思维模式
蝙蝠侠和罗宾
假设我们的代码是蝙蝠侠和罗宾。 蝙蝠侠可以应付大多数坏人,但是当他无法应付时,他的搭档罗宾(Robin)便应命了。
蝙蝠侠再次参加战斗,罗宾将在稍后到达。
这是蝙蝠侠:
+<FocusLock - enabled={componentControl.value} +> - {componentControl.value && <PageTitle title={componentControl.value.title}/>} + <DisplayData + data={componentControl.value} + visible={componentControl.value !== null} + /> +</FocusLock>
这是他的搭档罗宾(Robin)::
-<FocusLock + enabled={componentControl.value} -> + {componentControl.value && <PageTitle title={componentControl.value.title}/>} - <DisplayData - data={componentControl.value} - visible={componentControl.value !== null} - /> -</FocusLock>
蝙蝠侠和罗宾可以组队 ,但实际上他们是两个不同的人。
并且不要忘记-我们仍在谈论代码拆分 。 而且,就代码拆分而言,搭档在哪里? 罗宾在哪里?

放在小车里 罗宾在一辆杂物车里等着。
边车
Batman
是您的客户必须尽快看到的所有视觉材料。 理想的瞬间。Robin
是所有逻辑和花哨的交互式功能,可能在第二秒才可用,但不是一开始就可用。
最好将其称为并行分支中存在代码分支的垂直代码拆分 ,而不是将常见的将代码分支剪切 掉的水平代码拆分 。
在某些地区 ,此三重奏被称为replace reducer
或其他延迟加载Redux逻辑和副作用的方法。
在其他一些国家 ,这被称为"3 Phased" code splitting
。
这只是关注点的另一种分离,仅适用于您可以推迟加载组件的某些部分而不是另一部分的情况。

使用React,GraphQL和Relay构建新的facebook.com中的图像,其中importForInteractions
或importAfter
是sidecar
。
而且有一个有趣的发现-虽然Batman
对客户来说更有价值,但只要有客户看到的东西,他就始终处于健康状态……而Robin
可能有点超重 ,并且需要更多字节生活。
结果-蝙蝠侠独自一人对客户来说是可以承受的-他以较低的成本提供了更多的价值。 你是我的英雄蝙蝠!
可以移动到边车上的东西:
- 大多数
useEffect
, componentDidMount
和朋友。 - 像所有模态效果一样 即
focus
和scroll
锁定。 您可能首先显示模态,然后才使模态模态 ,即“锁定”客户的注意力。 - 表格 将所有逻辑和验证移至边车,并阻止表单提交,直到加载该逻辑。 客户可能会开始填写表格,而不知道这只是
Batman
。 - 一些动画。 就我而言,这是一个完整的
react-spring
。 - 一些视觉的东西。 类似于“ 自定义”滚动条 ,它可能会在一秒钟后显示精美的滚动条。
另外,请不要忘记-卸载到Sidecar的每段代码,也卸载了已删除的代码所使用的core-js poly-和ponyfills之类的东西。
代码拆分比今天的应用程序更智能。 我们必须意识到有两种代码可以拆分:1)视觉方面2)交互方面。 稍后可能会出现后者。 Sidecar
可以无缝地拆分两个任务,从而使您感觉到所有内容的加载速度都更快 。 而且会的。
最早的代码拆分方法
虽然尚不清楚何时以及什么是sidecar
,但我将给出一个简单的解释:
Sidecar
是您的全部脚本 。 Sidecar是我们在今天得到的所有前端东西之前进行代码拆分的方式。
我说的是服务器端渲染( SSR )或只是纯HTML ,我们都习惯了昨天。 当包含HTML和逻辑的页面分别存在于可嵌入的外部脚本中(关注点分离)时, Sidecar
使事情变得像以前一样容易。
我们有HTML,CSS,内联的脚本以及提取到.js
文件中的其余脚本。
HTML
+ CSS
+ inlined-js
是Batman
,而外部脚本是Robin
,并且该网站能够在没有Robin的情况下正常运行,并且说实话,在没有Batman的情况下(他将在双腿断断续续的情况下继续战斗)。 那只是昨天,许多“非现代而酷”的网站今天都一样。
如果您的应用程序支持SSR,请尝试禁用js并使其在没有它的情况下正常工作。 然后,很明显可以将什么移动到边车上。
如果您的应用程序是仅客户端SPA,请尝试想象一下,如果存在SSR,它将如何工作。
例如,用React编写的-theurge.com功能全面, 无需启用任何js 。
您可能需要将很多事情转移到边车上。 例如:
- 评论。 只要可能需要更多的代码(包括WYSIWYG编辑器)(最初不需要),您就可以附带
display
注释的代码,但不answer
。 最好延迟评论框 ,甚至只是将代码隐藏在动画后面,而不是延迟整个页面。 - 视频播放器。 投放没有“控件”的“视频”。 一秒钟后加载它们,他们的客户可能会尝试与之交互。
- 图片库,如
slick
。 绘制它并不是什么大问题,但要赋予动画和管理难度就更大了。 很明显,什么可以转移到边车上。
只需考虑对您的应用程序必不可少的内容,而对于应用程序而言则不尽相同...
实施细节
(DI)组件代码拆分
sidecar
的最简单形式很容易实现-只需将所有内容移动到子组件,您就可以使用“旧”方式对代码进行拆分。 这几乎是Smart和Dumb组件之间的分离,但是这次Smart并不是在说Dumb一个-恰恰相反。
const SmartComponent = React.lazy( () => import('./SmartComponent')); class DumbComponent extends React.Component { render() { return ( <React.Fragment> <SmartComponent ref={this} /> // <-- move smart one inside <TheActualMarkup /> // <-- the "real" stuff is here </React.Fragment> } }
这也需要将初始化代码移至哑巴代码,但您仍然可以对代码中最重的部分进行代码拆分。
您现在可以看到parallel
或vertical
代码拆分模式吗?
使用Sidecar
我已经在这里提到过, 使用React,GraphQL和Relay构建New facebook.com的概念是loadAfter
或importForInteractivity
,这与sidecar概念非常相似。
同时,我不建议您创建诸如useSidecar
类的东西,只要您可能有意尝试在其中使用hooks
,但是以这种形式进行代码拆分会破坏钩子规则 。
请选择一种更具声明性的组件方式。 而且,您可以在SideCar
组件内使用hooks
。
const Controller = React.lazy( () => import('./Controller')); const DumbComponent = () => { const ref = useRef(); const state = useState(); return ( <> <Controller componentRef={ref} state={state} /> <TheRealStuff ref={ref} state={state[0]} /> </> ) }
预取
别忘了-您可以使用加载优先级提示来预加载或预取sidecar
,并使它的运输更加透明和不可见。
重要事项-预取脚本将通过网络加载它,但除非实际需要,否则不执行(并花费CPU)。
固态继电器
与常规代码拆分不同,SSR不需要采取特殊操作。 Sidecar
可能不是SSR流程的一部分,并且在hydration
步骤之前不需要。 可以“按设计”将其推迟。
因此-可以随时使用React.lazy
(理想情况下没有 Suspense
,这里不需要任何故障回复(加载)指示符)或任何其他库,但如果没有SSR支持,最好在SSR过程中跳过 Sidecar块。
不良部位
但是这个想法有一些坏处
蝙蝠侠不是产品名称
虽然Batman
/ Robin
Batman
/ Robin
可能是一个好主意,而maincar
本身就是该技术的完美搭配-这款主sidecar
并没有“好”的名字。 没有主车这样的东西,显然Batman
, Lonely Wolf
, Solitude
, Driver
和Solo
不会被用来命名非人车零件。
Facebook已经使用了display
和interactivity
,这对于我们所有人来说可能是最好的选择。
如果您对我有个好名字-请将其保留在评论中
摇树
从捆绑器的角度来看,更多的是关注点的分离。 假设您有Batman
和Robin
。 还有stuff.js
然后,您可以尝试拆分组件代码以实现Sidecar
简而言之-上面的代码可以工作,但不能做“工作”。
- 如果您仅使用
stuff.js
batman
-摇树只会保留它。 - 如果您仅使用
stuff.js
robin
-摇树只会保留它。 - 但是,如果您同时使用这两者,即使它们使用的是不同的块-两者都将捆绑在第一次出现的
stuff.js
,即主捆绑 。
树摇不是代码拆分友好的。 您必须按文件分开关注点。
取消导入
每个人都忘记的另一件事是javascript的成本。 在jQuery时代( jsonp
有效载荷时代)非常普遍,它用于加载脚本(带有json
有效载荷),获取有效载荷并删除脚本。
如今,我们都import
脚本,即使不再需要它也将永远被导入。
就像我之前说过的-JS太多了,迟早要进行连续导航,您将全部加载。 我们应该找到一种方法,取消导入不再需要的块,清除所有内部缓存并释放内存以使Web更加可靠,并且不使用内存不足异常破坏应用程序。
un-import
的能力(webpack 可以做到 ) 也许是我们坚持使用基于组件的 API的原因之一,只要它使我们能够处理unmount
。
到目前为止,ESM模块标准与此类内容无关,也与缓存控制无关,也没有与导入操作相反的内容。
创建支持sidecar的库
到目前为止,只有一种方法可以创建启用了sidecar
库:
- 将您的组件分成几部分
- 通过
index
公开main
部分和connected
部分(不破坏API) - 通过单独的入口点暴露
sidecar
。 - 在目标代码中-导入
main
零件和sidecar
-摇树应该切断connected
零件。
这个时间树摇动应该正常工作,唯一的问题-是如何命名main
部分。
export const Main = ({sidecar, ...props}) => ( <div> {sidecar} .... </div> );
import Main from './Component'; import Sidecar from './Sidecar'; export const Connected = props => ( <Main sidecar={<Sidecar />} {...props} /> );
export * from './Main'; export * from './Connected';
import * from './Sidecar';
简而言之,可以通过较小的比较来表示更改
理论上, dynamic import
可以在node_modules内部使用,从而使组装过程更加透明。
无论如何-它只不过是children
/ slot
模式,在React中很常见。
未来
Facebook
证明了这个想法是正确的。 如果您还没有看过该视频,请立即进行操作。 我只是从不同角度解释了相同的想法(并在F8会议召开的一周前开始撰写本文)。
现在,它需要对您的代码库进行一些代码更改。 它需要更明确地分离关注点才能真正将它们分离,并且让代码不是水平而是垂直拆分,以交付更少的代码以提供更大的用户体验。
除了老式SSR之外, Sidecar
可能是处理BIG代码库的唯一方法。 当您拥有大量代码时,这是最后机会发布少量代码。
它可以使BIG应用程序更小,而SMALL应用程序更小。
10年前,中型网站已在300毫秒内“准备就绪”,并在几毫秒后才真正准备就绪。 今天,秒数甚至超过10秒是常见数字。 真可惜
让我们暂停一下,思考-我们如何解决问题,并使UX再次出色...

总的来说
- 组件代码拆分是功能最强大的工具,它使您能够完全拆分某些内容,但要付出一定的代价-您可能不会显示任何内容,除非是空白页或一段时间。 那是一个水平的分离。
- 库代码拆分可能会在组件拆分不起作用时提供帮助。 那是一个水平的分离。
- 卸载到Sidecar上的代码将使图片更完整,并可以使您提供更好的用户体验。 但也需要一些工程上的努力。 这是垂直分隔。
让我们来讨论一下 。
别说了 那么您尝试解决的问题呢?
好吧,那只是第一部分。 我们现在正处于最后阶段 ,写下该提议的第二部分还需要几个星期。 同时...上车!