Gatsby构建与优化全解析
1. Webpack构建生成的捆绑包
Webpack在完成JavaScript构建阶段后,会生成多个不同的捆绑包,具体如下表所示:
| 文件名 | 描述 |
| — | — |
| app-[contenthash].js | 由production - app.js生成,并在webpack.config.js中配置。 |
| webpack - runtime - [contenthash].js | 包含webpack运行时的单独捆绑包(在优化部分配置),通常与应用捆绑包一起使用。 |
| framework - [contenthash].js | 包含React,作为单独的捆绑包可提高缓存命中率,因为React库很少频繁更新。 |
| commons - [contenthash].js | 每个Gatsby页面使用的库被捆绑到这个文件中,这样它们只需下载一次。 |
| component—[name]-[contenthash].js | 为每个页面提供单独的捆绑包,以实现代码分割。 |
2. production - app.js文件的作用
production - app.js文件是Webpack的入口点,它生成app - [contenthash].js文件,负责在浏览器加载初始HTML后进行所有导航和页面加载。首次加载时,HTML会立即加载,其中包含一个CDATA部分,将页面信息注入window对象,以便在JavaScript中立即使用。例如,在Gatsby网站的/blog/3页面刷新浏览器后,会有如下输出:
<![
CDATA[ */
window.page={
"path": "/blog/3.js",
"componentChunkName": "component---src-blog-3-js",
"jsonName": "blog-3-995"
};
window.dataPath="621/path---blog-3-995-a74-dwfQIanOJGe2gi27a9CLKHjamc";
*/ ]
]>
之后,应用程序、webpack运行时、组件、共享库和数据JSON捆绑包通过
<link>
和
<script>
元素加载,随后production - app.js代码初始化。
3. 应用在浏览器中的执行步骤
- 执行onClientEntry浏览器API :应用在浏览器中首先执行onClientEntry浏览器API,这使插件能够在任何其他页面加载逻辑之前执行重要操作,例如gatsby - plugin - glamor执行的重新水化操作。api - runner - browser.js会遍历已注册的浏览器插件,并逐个执行它们(在从./cache/api - runner - browser - plugins.js检索插件列表之后,该文件在Gatsby引导早期生成)。
-
执行hydrate函数
:捆绑包执行hydrate函数,这是一个ReactDOM函数,其行为与render类似,但它不会生成全新的DOM树并插入文档中,而是期望页面上已经存在具有相同结构的ReactDOM树。识别匹配的树后,它会遍历树以附加所需的事件监听器,使React DOM“活跃”起来。此水化过程在cache - dir/default - html.js中的
<div id="___gatsby">...</div>元素上操作。 -
使用@reach/router替换DOM
:production - app.js文件使用@reach/router将现有DOM替换为RouteHandler组件,该组件使用PageRenderer创建用户刚刚导航到的页面,并加载该路径的页面资源。首次加载时,由于
<link rel="preload" ... />元素,给定路径的页面资源已经在页面的初始HTML中可用。这些资源包括导入的组件,Gatsby通过执行React.createElement()生成页面组件,然后将该元素呈现给RouteHandler供@reach/router执行渲染。
4. 预加载背景资源
在重新水化之前,Gatsby会提前开始加载背景资源,即用户开始通过页面上的链接和其他元素进行导航时所需的页面资源。页面资源的加载在cache - dir/loader.js中进行,其主要函数是getResourcesForPathname。该函数接受一个路径,发现关联的页面,并导入组件模块的JSON查询结果。对该信息的访问由async - requires.js提供,它包含Gatsby网站上每个页面的列表以及每个关联的dataPath。fetchPageResourcesMap函数负责检索该文件,这在首次调用getResourcesForPathname时发生。
5. 全局状态变量
为了提供全局状态,Gatsby将状态变量附加到window对象上,以便插件可以使用,例如
window.___loader
、
window.___emitter
、
window.___chunkMapping
、
window.___push
、
window.___replace
和
window.___navigate
。
6. 代码分割和预取
在Gatsby中,代码分割在两种情况下利用Webpack的动态分割功能:
- 如果Webpack遇到import函数调用,则将导入的文件分割成单独的捆绑包。
- 如果通过require加载相关模块,则将其包含在原始捆绑包中。
Webpack将决定要分割哪些模块的问题留给Gatsby和网站开发者。在浏览器中加载页面时,无需加载网站中其他页面所需的所有脚本和样式表,除非需要预取它们以实现即时导航。Gatsby在捆绑过程中最后要做的工作是确保正确的JavaScript位于正确的位置,以便Webpack进行适当的代码分割。
7. 分割和命名块
在Gatsby的引导阶段结束并生成完整页面时,会输出.cache/async - requires.js文件。该文件导出一个components对象,其中包含ComponentChunkNames到负责导入磁盘上每个组件文件的函数的映射。示例如下:
exports.components = {
"component--src-blog-js": () =>
import(
"/home/site/src/blog.js"
/* webpackChunkName: "component---src-blog-js" */
),
// more components
}
Webpack的入口点(production - app.js)需要这个async - requires.js文件来实现页面组件文件的动态导入。Webpack随后会进行动态分割,为每个导入的文件生成不同的块。该文件的一个导出还包括一个data函数,用于动态导入data.json文件,该文件也进行了代码分割。Gatsby可以通过Webpack配置的输出部分中的chunkFilename配置自定义磁盘上这些文件的命名,默认在webpack.config.js中设置为[name] - [contenthash].js。其中,[contenthash]表示最初进行代码分割的块内容的哈希值,[name]源自前面示例中的webpackChunkName。
8. 块与块资产的映射
为了生成客户端导航和未来即时加载所需的映射,Gatsby需要创建:
- 与Gatsby运行时块对应的
<link>
和
<script>
元素。
- 给定页面的相关页面块(例如,带有内容哈希的component–src - blog - js - 2e49587d85e03a033f58.js)。
Gatsby通过Webpack的编译钩子(done)获取包含所有块组的stats数据结构。使用自定义的Webpack插件GatsbyWebpackStatsExtractor,Gatsby将块数据写入公共目录中的webpack.stats.json文件。示例如下:
// public/webpack.stats.json
{
"assetsByChunkName": {
"app": [
"webpack-runtime-e402cdceeae5fad2aa61.js",
"app-2e49587d85e03a033f58.js"
],
"component---src-blog-2-js": [
"0.f8e7f9e53550f997bc53.css",
"0-d55d2d6645e11739b63c.js",
"1.93002d5bafe5ca491b1a.css",
"1-4c94a37dc2061cb7beb9.js",
"component---src-blog-2-js-cebc3ae7596cbb5b0951.js"
]
}
}
此外,Gatsby的自定义Webpack配置还会生成一个chunk - map.json文件,将每个块组映射到组件的核心块,为每个单独的块组中的JavaScript和CSS资产生成单个组件块。示例如下:
// public/chunk-map.json
{
"app":["/app-2e49587d85e03a033f58.js"],
"component---src-blog-2-js": [
"/component---src-blog-2-js-cebc3ae7596cbb5b0951.css",
"/component---src-blog-2-js-860f9fbc5c3881586b5d.js"
]
}
9. 在当前页面HTML中引用块
static - entry.js加载webpack.stats.json和chunk - map.json文件,在构建当前页面的
<link>
和
<script>
元素以及预取后续导航的块时,搜索与各个componentChunkName值匹配的块资产。
-
创建
<link>
和
<script>
元素
:生成当前活动页面的HTML后,static - entry.js在当前页面的头部创建必要的
<link>
元素,并在结束的
</body>
标签之前创建
<script>
元素,它们都引用与该页面相关的JavaScript运行时和客户端JavaScript。Gatsby运行时捆绑包(名为app)通过使用componentChunkName搜索assetsByChunkName项来获取页面和组件的所有块资产文件,然后将这两个块资产数组合并,每个块在
<link>
元素中引用如下:
<link
as="script"
rel="preload"
key="app-2e49587d85e03a033f58.js"
href="/app-2e49587d85e03a033f58.js"
/>
在HTML主体末尾,对于JavaScript资产,Gatsby插入引用预加载资产的
<script>
元素:
<script
key="app-2e49587d85e03a033f58.js"
src="app-2e49587d85e03a033f58.js"
async
/>
对于CSS资产,CSS直接内联注入HTML头部:
<style
data-href="/1.93002d5bafe5ca491b1a.css"
dangerouslySetInnerHTML="...contents of public/1.93002d5bafe5ca491b1a.css"
/>
10. 预取块的引用
当前页面加载完成后,Gatsby会继续在页面中查找可以预取的链接。当Gatsby的浏览器运行时遇到
<link rel="prefetch" href="..." />
元素时,它会在当前活动页面所需的所有资源加载完成后,以低优先级开始下载资源。
<Link />
组件的componentDidMount回调被调用时,Gatsby会自动将目标路径排入production - app.js文件的加载器中进行预取。
为了确定组件所需的块组,static - entry.js文件需要chunk - map.json文件,并将其直接注入当前页面HTML的CDATA部分的window.___chunkMapping下,以便任何production - app.js代码都可以引用它:
<![
CDATA[ */
window.___chunkMapping={
"app":[
"/app-2e49587d85e03a033f58.js"
],
"component---src-blog-2-js": [
"/component---src-blog-2-js-cebc3ae7596cbb5b0951.css",
"/component---src-blog-2-js-860f9fbc5c3881586b5d.js"
]
}
*/ ]
]>
借助这些信息,production - app.js加载器可以推导出完整的组件资产路径,并在prefetch.js中动态生成
<link rel="prefetch" ... />
元素,然后将其注入DOM供浏览器处理,从而实现Gatsby页面之间的即时导航。预取功能可以通过在Gatsby中实现disableCorePrefetching浏览器API并返回true来禁用。
11. Gatsby CLI的使用
11.1 安装Gatsby CLI
首先,安装全局可执行文件:
$ npm install -g gatsby-cli
执行
gatsby --help
可获取所有可用命令和选项的完整列表。
11.2 常用CLI命令
| 命令 | 描述 |
|---|---|
| gatsby new my - gatsby - site - name | 使用默认starter创建新的本地Gatsby网站。 |
| gatsby develop | 启动Gatsby开发服务器,可选择使用 - H和–host设置主机(默认为localhost),- p和–port设置端口(默认为8000),- o和–open在默认浏览器中打开网站,- S和–https使用HTTPS。 |
| gatsby build | 编译应用程序并为部署做准备,可选择使用–prefix - paths在设置pathPrefix后构建带有前缀链接路径的网站,–no - uglify构建不压缩JavaScript捆绑包的网站(用于调试),–open - tracing - config - file启用使用OpenTracing兼容的配置文件。 |
| gatsby serve | 为测试提供生产构建服务,可选择使用 - H和–host设置主机(默认为localhost),- p和–port设置端口(默认为9000),- o和–open在默认浏览器中打开网站,–prefix - paths提供带有前缀链接路径的网站(如果使用pathPrefix构建)。 |
| gatsby info | 获取有用的环境信息,用于在https://github.com/gatsbyjs/gatsby/issues报告问题,可选择使用 - C和–clipboard自动将环境信息复制到剪贴板。 |
| gatsby clean | 删除Gatsby的.cache和public目录。 |
11.3 快速启动命令
使用博客starter创建新的Gatsby网站:
$ gatsby new my-gatsby-blog-name \
https://github.com/gatsbyjs/gatsby-starter-blog
$ cd my-gatsby-blog-name
$ gatsby develop
此时,网站将在https://localhost:8000运行,你可以在https://localhost:8000/___graphql上试验查询数据。
11.4 有用的文件定义
在Gatsby项目的根目录应该有以下文件:
-
gatsby - config.js
:配置Gatsby网站的选项,包括项目标题、描述、插件等元数据。
-
gatsby - node.js
:实现Gatsby的Node.js API,以自定义和扩展影响构建过程的默认设置。
-
gatsby - browser.js
:使用Gatsby的浏览器API自定义和扩展影响浏览器的默认设置。
-
gatsby - ssr.js
:使用Gatsby的服务器端渲染(SSR)API自定义影响服务器端渲染的默认设置。
11.5 Gatsby CLI详细命令
- gatsby new :不带任何参数运行时,会启动一个交互式shell,询问一系列问题并为你创建Gatsby网站:
$ gatsby new
What would you like to name the folder where your site will be created?
my-gatsby-site
Will you be using a CMS? (single choice)
No (or I'll add it later)
–
WordPress
Contentful
Sanity
DatoCMS
Shopify
Would you like to install a styling system? (single choice)
No (or I'll add it later)
–
CSS Modules/PostCSS
styled-components
Emotion
Sass
Theme UI
Would you like to install additional features with other plugins? (multiple choice)
◯ Add the Google Analytics tracking script
◯ Add responsive images
◯ Add page meta tags with React Helmet
◯ Add an automatic sitemap
◯ Enable offline functionality
◯ Generate a manifest file
◯ Add Markdown support (without MDX)
◯ Add Markdown and MDX support
也可以通过提供一到两个参数,基于starter创建Gatsby网站,不会提示你进行自定义设置:
$ gatsby new [site-name [starter-url]]
综上所述,Gatsby通过Webpack实现了高效的代码分割和预取,结合强大的CLI工具,为开发者提供了便捷的网站构建和优化方案。开发者可以深入了解这些内部机制,为Gatsby项目的开发和优化做出贡献。
Gatsby构建与优化全解析
12. 核心文件作用总结
| 文件 | 作用 |
|---|---|
| production - app.js | Webpack入口点,负责导航和页面加载,依赖async - requires.js实现组件动态导入 |
| async - requires.js | 导出components对象,映射ComponentChunkNames到导入组件文件的函数,还包含data函数用于动态导入data.json |
| webpack.stats.json | 映射chunk groups到依赖的chunk asset names |
| chunk - map.json | 映射每个chunk group到组件的核心块,为JavaScript和CSS资产生成单个组件块 |
| static - entry.js |
加载webpack.stats.json和chunk - map.json,构建当前页面的
<link>
和
<script>
元素,处理块的引用和预取
|
13. 构建流程总结
以下是Gatsby构建的主要流程,使用mermaid流程图展示:
graph LR
classDef startend fill:#F5EBFF,stroke:#BE8FED,stroke-width:2px
classDef process fill:#E5F6FF,stroke:#73A6FF,stroke-width:2px
A([开始]):::startend --> B(Webpack编译):::process
B --> C(生成捆绑包):::process
C --> D(生成async - requires.js):::process
D --> E(生成webpack.stats.json和chunk - map.json):::process
E --> F(生成HTML页面):::process
F --> G(注入块映射信息):::process
G --> H(加载页面资源):::process
H --> I(预取资源):::process
I --> J([结束]):::startend
14. 代码分割和预取的优势
- 性能优化 :代码分割将大的代码块拆分成小的、按需加载的模块,减少了初始加载时间。预取功能在后台提前加载未来可能需要的资源,当用户导航到其他页面时,可以实现即时加载,提高了用户体验。
- 缓存命中率提升 :将不常更新的库(如React)单独打包成framework - [contenthash].js,可提高缓存命中率,减少不必要的重复下载。
- 资源管理 :通过将每个页面的资源单独打包,避免了在加载一个页面时加载其他页面的不必要资源,提高了资源利用效率。
15. 开发建议
- 合理使用代码分割 :根据页面的实际情况,合理使用Webpack的动态分割功能,将不同页面或功能模块的代码进行分割,避免一次性加载过多代码。
- 预取策略优化 :根据用户的行为习惯和页面的访问频率,优化预取策略。例如,对于热门页面或用户经常访问的页面,可以提前预取相关资源。
- 关注文件命名和映射 :理解Gatsby对文件命名和块映射的机制,确保在开发过程中能够正确引用和管理资源。
- 利用CLI工具 :熟练掌握Gatsby CLI的各种命令,如创建项目、启动开发服务器、构建和部署等,提高开发效率。
16. 未来发展方向
随着Web技术的不断发展,Gatsby也在不断演进。未来,Gatsby可能会在以下方面进行改进和优化:
-
更智能的代码分割和预取
:结合机器学习和用户行为分析,实现更智能的代码分割和预取策略,进一步提高性能和用户体验。
-
更好的跨平台支持
:支持更多的平台和设备,如移动应用、桌面应用等,扩大Gatsby的应用范围。
-
与更多技术的集成
:与更多的前端技术和后端服务进行集成,如微服务、容器化等,提供更强大的功能和扩展性。
17. 总结
Gatsby是一个强大的静态网站生成器,通过Webpack实现了高效的代码分割和预取,结合功能丰富的CLI工具,为开发者提供了便捷的网站构建和优化方案。通过深入了解Gatsby的内部机制,如捆绑包的生成、代码分割和预取的实现、块与资产的映射等,开发者可以更好地利用这些特性,提高网站的性能和用户体验。同时,开发者也可以根据自己的需求和经验,为Gatsby的发展做出贡献,推动其不断进步和完善。
在实际开发中,开发者可以按照以下步骤进行操作:
1. 使用Gatsby CLI创建项目,根据需求选择合适的starter。
2. 在开发过程中,合理使用代码分割和预取功能,优化资源加载。
3. 利用Gatsby的各种API和插件,如Node.js API、Browser API、SSR API等,自定义和扩展网站的功能。
4. 在构建和部署阶段,使用Gatsby CLI的相关命令,确保网站的顺利发布。
通过以上步骤和方法,开发者可以充分发挥Gatsby的优势,构建出高性能、易维护的网站。
超级会员免费看
15

被折叠的 条评论
为什么被折叠?



