Gatsby 是一个令人难以置信的静态站点生成器,它允许使用React作为渲染引擎引擎来搭建一个静态站点,它真正具有现代web应用程序所期望的所有优点。它通过在构建时通过服务器端渲染将动态的 React 组件呈现为静态 HTML 内容。这意味着您的用户可以获得静态站点的所有好处,比如不使用 JavaScript、搜索引擎友好性、非常快的加载速度等等,也并没有失去现代 web 所期望的活力和交互性。一旦呈现为静态 HTML,客户端站点的 React 和 JavaScript 会接管它并添加动态的内容。 Gatsby 最近发布了v1.0.0,推出了很多新特性。包括(但不限于)使用GraphQL创建内容查询的能力,与各种cms集成——包括 WordPress、Contentful、Drupal 等等。还有基于路由的代码分布使得用户体验更佳。在这篇文章中,我们将深入探讨 Gatsby 和一些新特性,并创建一个静态博客。让我们开始吧!
起步
安装cli
npm install -g gatsby-cli
Gatsby 带有一个很棒的CLI(命令行接口),它包含了一个工作站点的搭建功能,以及帮助开发该站点的命令。
gatsby new personal-blog && cd $_
该命令将创建文件夹personal-blog
,然后进入该目录。现在一个可供开发的环境已经搭建好了。Gatsby 的 CLI包含了许多常见的开发特性,比如gatsby build
(构建一个生产、静态生成的项目版本)、gatsby develop
(启动一个热加载的web开发服务器)等等。
我们现在可以开始在这个网站上进行真正的开发,并且创建一个功能齐全的,现代的博客。您通常希望使用gatsby develop
来启动本地开发服务器,以验证我们在步骤中所完成的功能。
添加必要的插件
Gatsby 支持使用丰富的插件,很多非常有用的插件都是为了完成普通任务而编写的。插件可以分为三个主要类别:功能( functional )插件、源( source )插件和转换器( transformer )插件。
功能插件
功能插件用来实现某些功能(离线支持,生成一个站点地图等等)或者他们扩展了 Gatsby 的 webpack 配置,增加了对 Typescript、Sass 等的支持。 对于这个特定的博客文章,我们想要一个单页面应用的感觉(没有页面重载),以及在head
标签中动态更改title
标签的能力。正如所提到的,Gatsby 插件的生态系统是丰富的、充满活力的,而且还在不断增长,所以通常一个已经存在的插件,可以解决你想要解决的特定问题。为了解决我们想要的这个博客的功能,我们将使用以下插件:
gatsby-plugin-catch-links
- 实现了历史
pushState
API, 不需要页面重载就可以导航到博客的不同页面
- 实现了历史
gatsby-plugin-react-helmet
- react-helmet 是一种允许修改
head
标签的工具 Gatsby 静态地呈现这些头部标签的变化
- react-helmet 是一种允许修改
使用下面的命令:
`yarn add gatsby-plugin-catch-links gatsby-plugin-react-helmet`
我们用的是yarn,但是使用 npm 也很容易:npm i --save [deps]
。 在安装了这些功能插件之后,我们将编辑gatsby-config.js
。Gatsby 在构建时加载指定插件的公开功能。
module.exports = {siteMetadata: {title: `Your Name - Blog`,author: `Your Name`,},plugins: ['gatsby-plugin-catch-links','gatsby-plugin-react-helmet',],
}
除了使用yarn install
和编辑配置文件之外,我们现在还可以编辑网站 head
标签,同时还可以实现一个无需重新加载的单页面应用,。现在,让我们通过实现一个源插件来增强基本功能,该插件可以实现从本地文件系统加载博客文章。
源插件
源插件创建节点,然后通过一个转换器插件将其转换为可用的格式。例如,一个典型的工作流通常需要使用gatsby-source-filesystem
它从磁盘上加载文件,例如 Markdown 文件,然后指定一个 Markdown 转换器将 Markdown 转换成 HTML 。 因为博客的大部分内容都使用 Markdown 格式,让我们添加gatsby-source-filesystem
,与我们之前的步骤类似,我们将安装插件,然后将其注入到我们的 gatsby-config.js
,像这样:
`yarn add gatsby-source-filesystem`
module.exports = {// previous configurationplugins: ['gatsby-plugin-catch-links','gatsby-plugin-react-helmet',{resolve: `gatsby-source-filesystem`,options: {path: `${__dirname}/src/pages`,name: 'pages',},}]
}
有一点解释在这里会很有帮助!一个options
对象可以传递给一个插件,我们正在传递文件系统路径(也就是我们的 Markdown 文件将被定位的位置),然后是源文件的名称。既然 Gatsby 知道了我们的源文件,我们就可以开始应用一些有用的转换器来将这些文件转换为可用的数据!
转换器插件
正如前面提到的,转换器插件采用了一些底层的数据格式,这种格式在当前的表单中是不可用的(Markdown,json,yaml等),我们可以用 GraphQL 查询把它转换成 Gatsby 能够理解的格式。filesystem 源插件将从我们的文件系统中加载文件节点(如 Markdown ),然后 Markdown 转换器将接管并转换为可用的 HTML 。 我们将只使用一个转换器插件(用于 Markdown ),所以让我们安装它。
- gatsby-transformer-remark
- 使用 remark Markdown解析器进行转换磁盘上的 md 文件为 HTML 。 此外,该转换器还可以选择使用插件来进一步扩展功能,例如通过
gatsby-remark-prismjs
来添加语法高亮,通过gatsby-remark-copy-linked-files
复制在 markdown 中指定的相关文件,通过gatsby-remark-images
压缩图像,并使用srcset
添加响应性图像等等。
- 使用 remark Markdown解析器进行转换磁盘上的 md 文件为 HTML 。 此外,该转换器还可以选择使用插件来进一步扩展功能,例如通过
这个过程现在应该很熟悉了,安装之后再添加到配置中。
`yarn add gatsby-transformer-remark`
编辑 gatsby-config.js
module.exports = {// previous setupplugins: ['gatsby-plugin-catch-links','gatsby-plugin-react-helmet',{resolve: `gatsby-source-filesystem`,options: {path: `${__dirname}/src/pages`,name: 'pages',},},{resolve: 'gatsby-transformer-remark',options: {plugins: [] // just in case those previously mentioned remark plugins sound cool :)}},]
};
唷!看起来像有很多设置,但是这些插件将会让 Gatsby 变得强大,并给我们一个难以置信的(但相对简单的!)开发环境。我们还需要一个更简单的步骤。我们只需创建一个 Markdown 文件,它将包含我们的第一个博客文章的内容。让我们开始吧!
书写第一个 Markdown 文章
我们先前配置的gatsby-source-filesystem
插件希望我们的内容能够放在 src/pages
。Gatsby 在命名规范方面并没有什么规定,但博客文章的一个典型做法是给文件夹起个类似MM-DD-YYYY-title
的名字,例如07-12-2017-hello-world
。让我们创建一个文件夹src/pages/07-12-2017-getting-started
,并把 index.md
放进去。 这个Markdown文件的内容将是我们的博客文章,使用 Markdown 编写(当然!)这是它该有的样子:
---
path: "/hello-world"
date: "2017-07-12T17:12:33.962Z"
title: "My First Gatsby Post"
---Oooooh-weeee, my first blog post!
被包含在横线里的部分是什么?这就是所谓的frontmatter
,而这部分内容可以供 React 组件使用(例如path,date,title等等)你可以添加其他的数据,因此,你可以自由地进行实验,找到必要的信息,以实现一个理想的博客系统,供你使用。重要的一点是,当我们动态创建页面来指定页面时,path
将会被用到识别路由。在这个例子里http://localhost:8000/hello-world
将是这个文件的路径。 现在我们已经创建了一个带有frontmatter
和一些内容的博客文章,我们可以开始编写一些可以显示这些数据的 React 组件。
创建 React 模板
当 Gatsby 支持服务器端渲染(对字符串)的 React 组件时,我们可以使用 React 编写我们的模板( 也可以使用Preact )。 我们创建一个 src/templates/blog-post.js
文件(请创建一个src/templates
文件夹)
import React from 'react';
import Helmet from 'react-helmet';// import '../css/blog-post.css'; // make it pretty!export default function Template({data // this prop will be injected by the GraphQL query we'll write in a bit
}) {const { markdownRemark: post } = data; // data.markdownRemark holds our post datareturn (<div className="blog-post-container"><Helmet title={`Your Blog Name - ${post.frontmatter.title}`} /><div className="blog-post"><h1>{post.frontmatter.title}</h1><div className="blog-post-content" dangerouslySetInnerHTML={{ __html: post.html }} /></div></div>);
}
哇,整洁!这个 React 组件将被呈现为静态 HTML 字符串,这将成为我们博客导航的基础。 在这一点上,有一种合理的混乱和魔法会发生,特别是在 props
属性注入的时候。什么是markdownRemark
?这个数据支持从何而来?这些问题,让我们通过编写一个GraphQL查询来回答,以便为我们的组件添加内容。
编写一个 GraphQL 查询
在 Template
声明下面,我们将添加一个 GraphQL 查询。这是 Gatsby 的一个非常强大的工具。这让我们可以很简单地挑选出我们想要展示给我们的博客文章的数据片段。我们的查询选择的每个数据都将通过我们前面指定的数据属性注入。
import React from 'react';
import Helmet from 'react-helmet';// import '../css/blog-post.css';export default function Template({data
}) {const { markdownRemark: post } = data;return (<div className="blog-post-container"><Helmet title={`Your Blog Name - ${post.frontmatter.title}`} /><div className="blog-post"><h1>{post.frontmatter.title}</h1><div className="blog-post-content" dangerouslySetInnerHTML={{ __html: post.html }} /></div></div>);
}export const pageQuery = graphql`query BlogPostByPath($path: String!) {markdownRemark(frontmatter: { path: { eq: $path } }) {htmlfrontmatter {date(formatString: "MMMM DD, YYYY")pathtitle}}}
`;
如果你对 GraphQL 不熟悉,这看起来可能有点让人困惑,但是我们可以把它们分解一下。
备注: 学习更多关于 GraphQL 知识, 请参考 excellent resource
查询名称 BlogPostByPath
(注意:这些查询名称必须是唯一的!)将被注入当前的路径,例如我们正在查看的特定的博客文章。这条路径将在查询中作为$path可用。比如,如果我们查看之前创建的博客文章,将从数据中提取的文件的路径将是 /hello-world
。 通过注入的属性获得 data
,在 GraphQL 查询中被命名为markdownRemark
。我们通过 GraphQL 查询获取的每个属性都可以在markdownRemark
下面找到。例如,访问转换后的 HTML 我们会通过 data.markdownRemark.html
去拿到数据。 当然,我们的数据结构是在我们的Markdown文件开始时提供的frontmatter
。我们定义的每个键都可以被注入到查询中。 现在,我们已经安装了一堆插件来从磁盘加载文件,将 Markdown 转换为HTML。我们有一个单独的 Markdown 文件,它将作为一个博客发布。最后,我们有一个针对博客文章的 React 模板,还有一个连接的 GraphQL 查询来查询博客文章,并将 React 模板注入到查询的数据中。接下来:以编程方式创建必要的静态页面(并将模板注入)与 Gatsby 的 Node API,让我们开始吧。 此时需要注意的一点是,GraphQL 查询是在构建时进行的。该组件被注入数据由 GraphQL 查询所得到。除非有什么动态处理( componentDidMount 的逻辑,state 变化),否则这个组件将是纯粹的,通过 React 渲染引擎、GraphQL 和 Gatsby 生成的HTML。
创建静态页面
Gatsby 公开了一个强大的Node API,它允许创建动态页面这样的功能(博客文章页!),扩展 babel 或 webpack 配置,修改所创建的节点或页面等。这个API写在在gatsby-node.js
文件中,在这个文件中发现的每一个导出都将由 Gatsby 分析。Gatsby详细地介绍了它的Node API规范。但是,我们只关心这个实例中的一个特定的APIcreatePages
。
const path = require('path');
exports.createPages = ({ boundActionCreators, graphql }) => {const { createPage } = boundActionCreators;
const blogPostTemplate = path.resolve(`src/templates/blog-post.js`);
}
没什么复杂的!我们已经在使用 createPages
API 了( Gatsby 将在构建时通过注入的参数来调用)。我们还将获取我们先前创建的 blogPostTemplate 的路径。最后,我们使用了 createPage
激活了 boundActionCreators
,Gatsby 在内部使用 Redux 来管理其状态,boundActionCreators
仅仅是 Gatsby 创造的一个action。对于所有公开的 action 的完整列表,请参阅 Gatsby 的文档。现在我们可以构造 GraphQL 查询,它将获取我们所有的 Markdown 贴子。
查询文章
const path = require('path');
exports.createPages = ({ boundActionCreators, graphql }) => {const { createPage } = boundActionCreators;
const blogPostTemplate = path.resolve(`src/templates/blog-post.js`);
return graphql(`{allMarkdownRemark(sort: { order: DESC, fields: [frontmatter___date] }limit: 1000) {edges {node {excerpt(pruneLength: 250)htmlidfrontmatter {datepathtitle}}}}}`).then(result => {if (result.errors) {return Promise.reject(result.errors);}});
}
我们使用 GraphQL 获取所有的 Markdown 节点,并在 GraphQL 属性 allMarkdownRemark
下使用它们。 每个公开的属性(在节点上)都可以用于查询。我们正在有效地创造一个GraphQL数据库,然后我们可以通过页面级的GraphQL查询对它进行查询。这里要注意的是exports.createPages
API 期望返回一个 Promise。 这里有一个很酷的地方是gatsby-plugin-remark
插件提供了一些有用的数据供我们使用GraphQL查询,例如 excerpt
(作为预览的一个简短的代码片段), id
(每个帖子的唯一标识符)等等。 现在我们已经编写了查询,但是我们还没有通过编程方式创建页面(使用createPage动作创建器)。下面让我们开始!
创建页面
const path = require('path');
exports.createPages = ({ boundActionCreators, graphql }) => {const { createPage } = boundActionCreators;
const blogPostTemplate = path.resolve(`src/templates/blog-post.js`);
return graphql(`{allMarkdownRemark(sort: { order: DESC, fields: [frontmatter___date] }limit: 1000) {edges {node {excerpt(pruneLength: 250)htmlidfrontmatter {datepathtitle}}}}}`).then(result => {if (result.errors) {return Promise.reject(result.errors);}
result.data.allMarkdownRemark.edges.forEach(({ node }) => {createPage({path: node.frontmatter.path,component: blogPostTemplate,context: {} // additional data can be passed via context});});});
}
我们现在得到了一个 Promise 的 graphql
查询。实际的贴子可以通过路径 result.data.allMarkdownRemark.edges
获得。我们将使用这些数据来构建一个包含 Gatsby 的页面。我们的 GraphQL“形状”直接反映在这个数据对象中,因此,当我们在GraphQL博客文章模板中查询时,我们从该查询中提取的每个属性都将可用。 createPage
API接受一个需要定义path
和component
属性的对象,我们已经在上面做过了。此外,可以使用可选属性context
来注入数据并使其可用于博客文章模板组件通过注入props(用各种 props 来查看每一个可用的 prop!)每一次我们构建 Gatsby 时, createPage
将被调用,Gatsby 将会创建一个静态的 HTML 文件路径根据我们在帖子的前面专门写的 frontmatter
。GraphQL查询的数据将注入到 stringified 和 parsed 后的 React 模板。哇,它真的开始工作起来了! 我们可以在这时运行 yarn develop
然后打开 http://localhost:8000/hello-world
查看我们的第一篇博客文章,应该如下所示:
在这一点上,我们使用 React 组件和几个 GraphQL 查询创建了一个单页静态博客。然而,这不是一个博客!我们不能期望用户猜测每个帖子的路径,我们需要有一个索引或列表页面来展示每个博客文章,简短的介绍,以及一个完整的博客文章的链接。你不知道,我们在 Gatsby 做到这一点有多容易,使用我们在博客模板中使用的类似策略,例如一个 React 组件和一个 GraphQL 查询。
创建博客列表
我在这一节中没有详细介绍,因为我们已经对我们的博客模板做了一些非常相似的事情!看看我们,我们在这一点上已经是一个专业级的 Gatsby 使用者了! 对于页面列表,Gatsby 有一个规范, 它们被放在我们指定的文件系统的根目录中gatsby-source-filesystem
,例如src/pages/index.js
。如果它不存在,就创建这个文件,让它运行。另外请注意,任何静态的 JavaScript 文件(导出一个 React 组件!)都会得到相应的静态 HTML 文件。例如,我们创造一个 src/pages/tags.js
文件,http://localhost:8000/tags/
将可访问。
import React from 'react';
import Link from 'gatsby-link';
import Helmet from 'react-helmet';
// import '../css/index.css'; // add some style if you want!
export default function Index({data
}) {const { edges: posts } = data.allMarkdownRemark;return (<div className="blog-posts">{posts.filter(post => post.node.frontmatter.title.length > 0).map(({ node: post }) => {return (<div className="blog-post-preview" key={post.id}><h1><Link to={post.frontmatter.path}>{post.frontmatter.title}</Link></h1><h2>{post.frontmatter.date}</h2><p>{post.excerpt}</p></div>);})}</div>);
}
export const pageQuery = graphql`query IndexQuery {allMarkdownRemark(sort: { order: DESC, fields: [frontmatter___date] }) {edges {node {excerpt(pruneLength: 250)idfrontmatter {titledate(formatString: "MMMM DD, YYYY")path}}}}}
`;
好的!我们在博客文章模板中采用了类似的方法,因此这应该看起来非常熟悉。我们再一次导出包含了 GraphQL 查询的 pageQuery
。 注意,我们正在提取一个稍微不同的数据集,具体来说,我们将提取250个字符的摘要,而不是完整的HTML,同时我们还在用格式字符串格式化拖拽日期!GraphQL是很优雅的。 实际的 React 组件是相当琐碎的,需要注意一点,当链接到内部内容时,你应该经常使用 gatsby-link
。 如果页面没有通过这个实用工具进行路由,Gatsby 就无法工作。另外,可以使用 pathPrefix
,这使得 Gatsby 的网站可以被部署到一个非根域。如果这个博客将托管在Github页面上,这是很有用的。或者挂在 /blog
。 现在这变得令人兴奋,感觉我们终于要成功了。现在我们有一个由 Gatsby 所生成的功能完整的博客,其中有真正的内容在 Markdown 里,有一个博客列表,以及在博客中浏览的能力。如果你执行 yarn develop
,http://localhost:8000
应该显示每个博客文章的缩略内容,每个文章标题都链接到博客文章的内容。这是一个真正的博客!
现在,你在跟随本教程的学习过程中获得的知识,将会使你做出一些不可思议的事情。您不仅可以使用CSS(styled-components!)使其变得漂亮,还可以通过实现以下功能来变得更强大!
-
添加一个 tag 列表和 tag 查询页
- 提示:
gatsby-node.js
文件中的createPages
API 在这里很有用,还有之前的frontmatter
- 提示:
- 在特定的博客文章之间添加导航(
createPages
的context
API在这里很有用) 随着我们对 Gatsby 及其API的探索,你应该感到有能力开始充分利用 Gatsby 的潜力,博客仅仅是一个起点;Gatsby 丰富的生态系统、可扩展的 API 和高级的查询功能为构建真正令人难以置信的高性能站点提供了强大的工具集。
现在去做一些伟大的事情吧!
Links
@dschau/gatsby-blog-starter-kit
- 展示 Gatsby 所有上述功能的可用的库
@dschau/create-gatsby-blog-post
- 我创建了一个实用程序和CLI,用于在预定义的 Gatsby 结构和
frontmatter
、日期、路径等方面搭建一个博客帖子。A utility and CLI I created to scaffold out a blog post following the predefined Gatsby structure with frontmatter, date, path, etc.
- 我创建了一个实用程序和CLI,用于在预定义的 Gatsby 结构和
- 博客源代码
- 我的博客的源代码,它采用了
gatsby-star-blog-post
,并以一组特性和一些更高级的功能扩展了它
。
- 我的博客的源代码,它采用了