前端开发人员面临的一个持续挑战是我们的应用程序的性能。我们如何才能为我们的用户提供一个强大且功能齐全的应用程序,而不强迫他们等待页面加载的永恒?用于加速网站的技术如此之多,以至于在优化性能和速度时决定将精力集中在哪里往往会令人困惑。
值得庆幸的是,解决方案并不像有时看起来那么复杂。在这篇文章中,我将分解大型 Web 应用程序用来加速用户体验的最有效技术之一。我将通过一个包来促进这一点,并确保我们可以更快地将我们的应用程序交付给用户,而不会让他们注意到任何事情发生了变化。
网站快速意味着什么?
Web 性能的问题既广泛又深入。为了这篇文章,我将尝试用最简单的术语来定义性能: 尽可能快地发送尽可能少的内容。当然,这可能是对问题的过度简化,但实际上,我们可以通过发送更少的数据供用户下载并快速发送数据来实现显着的速度 提升。
出于本文的目的,我将重点关注该定义的第一部分——向用户的浏览器发送尽可能少的信息。
在降低我们的应用程序速度方面,最大的罪魁祸首总是图像和javascript。在这篇文章中,我将向您展示如何处理大型应用程序包的问题并在此过程中加快我们的网站速度。
反应可加载
react Loadable 是一个允许我们仅在应用程序需要时才延迟加载 JavaScript 的包。当然,并非所有网站都使用 React,但为了简洁起见,我将重点介绍在使用webp ack 构建的服务器端渲染应用程序中实现 React Loadable。最终结果将是多个 javaScript 文件在需要该代码时自动传送到用户的浏览器。如果您想试用完整的代码,您可以从我们的 GitHub 存储库中克隆示例源代码。
使用我们之前的定义,这仅仅意味着我们预先向用户发送 更少 的信息,以便可以 更快地下载数据,并且我们的用户将体验到更高性能的网站。
1. 将 React 添加Loadable
到您的组件中
我将举一个 React 组件的示例, MyComponent
. 我假设这个组件由两个文件组成, 并且 .MyComponent/MyComponent.jsx
MyComponent/index.js
在这两个文件中,我定义了 React 组件,就像我通常在 MyComponent.jsx
. 在 index.js
中,我导入 React 组件并重新导出它——这次包装在 Loadable
函数中。使用 ECMAScript import
特性,我可以向 webpack 表明我希望这个文件是动态加载的。这种模式使我可以轻松地延迟加载我已经编写的任何组件。它还允许我将延迟加载和渲染之间的逻辑分开。这听起来可能很复杂,但实际上是这样的:
// MyComponent/MyComponent.jsx export default () => ( <div> This component will be lazy-loaded! </div> )
// MyComponent/index.js import Loadable from 'react-loadable' export default Loadable({ // The import below tells webpack to // separate this code into another bundle loader: import('./MyComponent') })
然后我可以像往常一样导入我的组件:
// anotherComponent/index.js import MyComponent from './MyComponent' export default () => <MyComponent />
我现在已经将 React Loadable 引入到 MyComponent
. 如果我愿意,我可以稍后向这个组件添加更多逻辑——这可能包括向组件引入加载状态或错误处理程序。感谢 Webpack,当我们运行构建时,我现在将获得两个单独的 JavaScript 包: app.min.js
是我们的常规应用程序包,并 myComponent.min.js
包含我们刚刚编写的代码。稍后我将讨论如何将这些捆绑包交付给浏览器。
2. 使用 Babel 简化设置
Loadable
通常,在将对象传递给函数 时,我必须包含两个额外的选项 ,modules
并且 webpack
. 这些帮助 Webpack 确定我们应该包含哪些模块。react-loadable/babel
值得庆幸的是,通过使用插件,我们可以避免在每个组件中包含这两个选项 。这会自动为我们包括以下选项:
// input file import Loadable from 'react-loadable' export default Loadable({ loader: () => import('./MyComponent') })
// output file import Loadable from 'react-loadable' import path from 'path' export default Loadable({ loader: () => import('./MyComponent'), webpack: () => [require.resolveWeak('./MyComponent')], modules: [path.join(__dirname, './MyComponent')] })
我可以通过将它添加到我的 .babelrc文件中的插件列表来包含这个插件,如下所示:
{ "plugins": ["react-loadable/babel"] }
我现在离延迟加载我们的组件又近了一步。但是,就我而言,我正在处理服务器端渲染。目前,服务器将无法渲染我们的延迟加载组件。
3. 在服务器上渲染组件
在我的服务器应用程序中,我有一个看起来像这样的标准配置:
// server/index.js app.get('/', (req, res) => { const markup = ReactdomServer.renderToString( <MyApp/> ) res.send(` <html> <body> <div id="root">${markup}</div> <script src="/build/app.min.js"></script> </body> </html> `) }) app.listen(8080, () => { console.log('Running...') })
第一步是指示 React Loadable 我希望所有模块都被预加载。这使我可以决定哪些应该立即加载到客户端。我通过server/index.js
像这样修改我的文件来做到这一点:
// server/index.js Loadable.preloadAll().then(() => { app.listen(8080, () => { console.log('Running...') }) })
下一步是将我要渲染的所有组件推送到一个数组中,以便我们稍后确定哪些组件需要立即加载。这样一来,HTML 就可以通过脚本标签包含正确的 JavaScript 包返回(稍后会详细介绍)。现在,我将像这样修改我的服务器文件:
// server/index.js import Loadable from 'react-loadable' app.get('/', (req, res) => { const modules = [] const markup = ReactDOMServer.renderToString( <Loadable.Capture report={moduleName => modules.push(moduleName)}> <MyApp/> </Loadable> ) res.send(` <html> <body> <div id="root">${markup}</div> <script src="/build/app.min.js"></script> </body> </html> `) }) Loadable.preloadAll().then(() => { app.listen(8080, () => { console.log('Running...') }) })
每次使用需要 React 的组件时Loadable
,它都会被添加到modules
数组中。这是一个由 React 完成的自动过程Loadable
,所以这就是我们在这个过程中所需要的全部。
现在我们有了一个我们知道需要立即渲染的模块列表。我们现在面临的问题是将这些模块映射到 Webpack 自动为我们生成的包。
4. 将 Webpack Bundle 映射到模块
所以现在我已经指示 Webpack 创建 myComponent.min.js,并且我知道它 MyComponent
会立即被使用,所以我需要在我们交付给用户的初始 HTML 有效负载中加载这个包。值得庆幸的是,React Loadable 也为我们提供了实现这一目标的方法。在我的客户端 Webpack 配置文件中,我需要包含一个新插件:
// webpack.client.config.js import { ReactLoadablePlugin } from 'react-loadable/webpack' plugins: [ new ReactLoadablePlugin({ filename: './build/loadable-manifest.json' }) ]
loadable-manifest.json文件将为我提供模块和包之间的映射,以便我可以使用我之前设置的 数组 modules
来加载我知道我需要的包。就我而言,这个文件可能看起来像这样:
// build/loadable-manifest.json { "MyComponent": "/build/myComponent.min.js" }
这还需要一个通用的 Webpack 清单文件来包含模块和文件之间的映射,以供内部 Webpack 使用。我可以通过包含另一个 Webpack 插件来做到这一点:
plugins: [ new webpack.optimize.CommonsChunkPlugin({ name: 'manifest', minChunks: Infinity }) ]
5. 在 HTML 中包含捆绑包
在服务器上加载我们的动态包的最后一步是将它们包含在我们提供给用户的 HTML 中。对于这一步,我将合并第 3 步和第 4 步的输出。我可以从修改上面创建的服务器文件开始:
// server/index.js import Loadable from 'react-loadable' import { getBundles } from 'react-loadable/webpack' import manifest from './build/loadable-manifest.json' app.get('/', (req, res) => { const modules = [] const markup = ReactDOMServer.renderToString( <Loadable.Capture report={moduleName => modules.push(moduleName)}> <MyApp/> </Loadable> ) const bundles = getBundles(manifest, modules) // My rendering logic below ... }) Loadable.preloadAll().then(() => { app.listen(8080, () => { console.log('Running...') }) })
在此,我导入了清单并要求 React Loadable 创建一个具有模块/捆绑映射的数组。我唯一要做的就是将这些包呈现为 HTML 字符串:
// server/index.js app.get('/', (req, res) => { // My App & modules logic res.send(` <html> <body> <div id="root">${markup}</div> <script src="/build/manifest.min.js"></script> ${bundles.map(({ file }) => `<script src="/build/${file}"></script>` }).join('\n')} <script src="/build/app.min.js"></script> </body> </html> `) }) Loadable.preloadAll().then(() => { app.listen(8080, () => { console.log('Running...') }) })
6. 在客户端加载服务器渲染的包
使用我们在服务器上加载的包的最后一步是在客户端上使用它们。这样做很简单——我可以指示 ReactLoadable
预加载它发现立即可用的任何模块:
// client/index.js import React from 'react' import { hydrate } from 'react-dom' import Loadable from 'react-loadable' import MyApplication from './MyApplication' Loadable.preloadReady().then(() => { hydrate( <MyApplication />, document.getElementById('root') ); });
结论
按照这个过程,我可以根据需要将我的应用程序包拆分为多个更小的包。通过这种方式,我的应用程序向用户发送 的信息更少,并且 仅在他们需要时发送。我已经减少了需要发送的代码量,以便可以 更快地发送。这可以为大型应用程序带来显着的性能提升。如果需要,它还可以为 r api增长设置较小的应用程序。
- 1. 将 React 添加Loadable到您的组件中
- 2. 使用 Babel 简化设置
- 3. 在服务器上渲染组件
- 4. 将 Webpack Bundle 映射到模块
- 5. 在 HTML 中包含捆绑包
- 6. 在客户端加载服务器渲染的包