什麽是 Code splitting?为什麽要做 Code splitting?
如果你的网站是用 Create React App, Next.js, Gatsby 或者其他类似工具写的,那你有很大的机率,会使用 bundlers 来打包你的网站。
随着我们的网站成长,功能变多、内部逻辑越来越复杂,CSS、JavaScript 的档案或 bundles 越来越大包,如果你有使用第三方套件,这个问题会更严重。当你的网站运行时,下载一大包的档案是个负担,拉长网站的 JavaScript execution time,这就是 Code splitting 方法上用场的地方。
如何实现 Code splitting?
与其一次下载一整包档案,不如等到这些 code 会被使用上时(或者说当这些画面,进入视窗范围时),才去下载,藉此提升网站的 initial load performance 。大致上的做法是:把 code 分成数个档案,常见 bundlers 例如 Webpack、Browserify 都有支援这种做法,他们可以创造出数个 bundle 档(原本是只有一包),然後采用一种叫做 Lazy load 的策略做 bundle 的动态载入。
在开始之前:来个自我检测
你的专案可能有 JavaScript execution time 过长的问题吗?让我们拿随意一个网站来做检测,按照下面的红框处,打开你的 Lighthouse 并按下 Generate Report。
Report 看起来会像这样:
你可以往下滑,找找看有无「Reduce JavaScript execution time」的建议,点选 Learn more,可以看到 Google 的 web.dev 提供的更多优化方法,code splitting 是其中一种。
React 提供的 Code splitting 方法
当 Webpack 读到 dynamic import 的指令时,会自动针对你的网站做 code-splitting。dynamic import 使用 then 方法,当网站需要用到这段 code 的时候,去 call then 方法里面的函示。
// 原本的 import 看起来像
import { add } from './math';
console.log(add(16, 26));
// dynamic import 看起来像
import("./math").then(math => {
console.log(math.add(16, 26));
});
如果你是自行设定 Webpack 的用户(没有使用 Create React App 、Next.js...类似工具),需要额外的设定来启用这个功能。
Webpack 设定官方文件:https://webpack.js.org/guides/code-splitting/
React team 的 Dan Abramov 提供了一套范例,你的 Webpack 设定看起来会像这样 :https://gist.github.com/gaearon/ca6e803f5c604d37468b0091d9959269
如果你还搭着 Babel 一起用,要确认 Bable 有办法正确的 parse,你可以参考这个套件:https://developer.mozilla.org/en-US/docs/Glossary/Code_splitting
注意:此方法不适用於 server-side rendering 的网站,SSR 须参考 loadable-components 这个套件。
React.lazy 让你用更习惯的方法来实作 dynamic import 。在下面的 code 里,当 OtherComponent 被初次 render 时,才会载入这包 bundle。
实作方法是在 React.Lazy 传入一个匿名函示,呼叫 dynamic import()
的方法,这个动作会 return 出一个 Promise
,这个 Promise
会 resolve 出一个 module,这个 module 内,是一个带有 React component 的 default
export。
// 使用 React.Lazy 的动态载入
const OtherComponent = React.lazy(() => import('./OtherComponent'));
如果 React 准备要 render component 了,相关联的 code 还没下载好,该怎麽办?要将 lazy component render 出来,必须包在 <Suspense>
元件内,而这个 <Suspense>
可以接收 fallback 设定,表示 code 尚未到位时,画面上应该显示什麽提示。
import React, { Suspense } from 'react';
const OtherComponent = React.lazy(() => import('./OtherComponent'));
function MyComponent() {
return (
<div>
// 当底下的 OtherComponent 尚未准备完成,画面上会显示 Loading...
// fallback prop 也可以是 React elements
<Suspense fallback={<div>Loading...</div>}>
<OtherComponent />
</Suspense>
</div>
);
}
只要包在 lazy component 外面,你可以把 <Suspense>
元件放在任何位置,甚至也可以用一个 <Suspense>
元件来包住多个 lazy component。
import React, { Suspense } from 'react';
const OtherComponent = React.lazy(() => import('./OtherComponent'));
const AnotherComponent = React.lazy(() => import('./AnotherComponent'));
function MyComponent() {
return (
<div>
<Suspense fallback={<div>Loading...</div>}>
<section>
<OtherComponent />
<AnotherComponent />
</section>
</Suspense>
</div>
);
}
如果今天不是「还没载入完成」的问题,而是「载入失败」呢?你可以客制一个 Error Boundary 来处理这个问题。ErrorBoundary 该如何实作,又能够控制到什麽程度?请见下一篇文章。
import React, { Suspense } from 'react';
import MyErrorBoundary from './MyErrorBoundary';
const OtherComponent = React.lazy(() => import('./OtherComponent'));
const AnotherComponent = React.lazy(() => import('./AnotherComponent'));
const MyComponent = () => (
<div>
// 使用 ErrorBoundary 来包住 lazy component
<MyErrorBoundary>
<Suspense fallback={<div>Loading...</div>}>
<section>
<OtherComponent />
<AnotherComponent />
</section>
</Suspense>
</MyErrorBoundary>
</div>
);
虽然 React.Lazy 提供的方法看来简单,要选择运用在哪里,却是困难的事情,毕竟如果 split bundles 位置挑选不好,可能会影响使用者体验。
一个比较好的选择,是在页面切换的时候。虽然页面切换通常有一些相依性(dependencies),但使用者也已经习惯切换页面时的几秒延迟。
React.lazy
与 React Router 的合作方式也很简洁,可以直接将 lazy component 传入 Route 元件,并且把 <Suspense>
包在 switch 之外即可。
import React, { Suspense, lazy } from 'react';
import { BrowserRouter as Router, Route, Switch } from 'react-router-dom';
const Home = lazy(() => import('./routes/Home'));
const About = lazy(() => import('./routes/About'));
const App = () => (
<Router>
<Suspense fallback={<div>Loading...</div>}>
<Switch>
<Route exact path="/" component={Home}/>
<Route path="/about" component={About}/>
</Switch>
</Suspense>
</Router>
);
参考资料:
https://web.dev/code-splitting-suspense/
https://reactjs.org/docs/code-splitting.html
https://blog.logrocket.com/code-splitting-in-react-an-overview/
<<: [Golang] Custom Type Declarations and Struct
>>: Proxmox VE 安装虚拟机:Windows 10 (一)
30 - Handle API response with value objects 本篇将介绍撰...
来轻松聊聊 终於来到基础CSS的最後一篇,这次要分享的是CSS的变量。 想像一个情境,你正在负责一个...
此系列文章会同步发文到个人部落格,有兴趣的读者可以前往观看喔。 今天要跟大家分享当网站有用到 Ja...
本质上是一样的东西,只是一个是在 request 前执行、一个是在收到 response 後执行,分...
4.speeds and feeds Google後发现CNC 的language是"G-...