Featured image of post 拥抱 Gatsby,用 React 搭建完整博客系统(三)——渲染 Markdown 文件并生成页面

拥抱 Gatsby,用 React 搭建完整博客系统(三)——渲染 Markdown 文件并生成页面

Gatsby 学习记录。

在本系列的前两篇中,我们已经使用了官方基础模板搭建好了我们的 Gatsby 博客,并通过数据源插件实现了目录文件的读取。 但数据源插件只能够获取到文件本身的信息,却无法读取到文件内容,要读取文件内容,就需要数据源转换插件的帮助了。

前言

在本系列的前两篇中,我们已经使用了官方基础模板搭建好了我们的 Gatsby 博客,并通过数据源插件实现了目录文件的读取。

但数据源插件只能够获取到文件本身的信息,却无法读取到文件内容,要读取文件内容,就需要数据源转换插件的帮助了。

本篇就将以 markdown 文件为例,讲解一下数据源转换插件的使用。

添加示例文档

我们在 pages 目录下新建一个 markdown 文档:markdown-demo.md,内容可以随意,以下是一个示例:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
---
title: "示例 Markdown 文档"
date: "2020-01-02"
---

## 这是一个标题
这是一段话,这是**加粗文本**,这是*斜体*,这是行内代码`Javascript`

> 这是一个引用

这是一个列表:
* 第一项内容
* 第二项内容
* 第三项内容

之后,再次访问 http://localhost:8080/myfiles ,可以看到数据源插件已经获取到了该文件,这一过程是即时的,只要我们添加了文件,gatsby-source-filesystem 插件就能够将其添加到 Gatsby 的数据源中: gatsby-from-zero-3-1.png

下面我们要做的,就是读取该 md 文件内容,并将其转换为 HTML 页面。

安装 Markdown 转换插件

gatsby-transformer-remark 插件可以实现将对 Markdown 文件的读取,我们安装该插件:

1
yarn add gatsby-transformer-remark

之后就像我们安装 gatsby-source-filesystem 插件之后所做的一样,将该插件添加到 gatsby-config.js 文件中去,你的 gatsby-config.js 文件看起来将是这样的:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
plugins: [
    `gatsby-plugin-react-helmet`,
    {
      resolve: `gatsby-source-filesystem`,
      options: {
        name: `src`,
        path: `${__dirname}/src`,
      },
    },
    {
      resolve: `gatsby-source-filesystem`,
      options: {
        name: `src`,
        path: `${__dirname}/src`,
      },
    },
    `gatsby-transformer-remark`,
    ...

之后重启开发服务器,再次打开 GraphiQL 就可以看到插件已经将 Markdown 文件的内容提到到了数据源中: gatsby-from-zero-3-2.png

我们可以通过 GraphQL 查询查看插件读取到的文件内容: gatsby-from-zero-3-3.png

为博客添加一个文章列表页

我们最终的目标是实现一个博客,而博客一般都会拥有一个包含所有文章的列表页,通过上文内容,我们已经能够获取到目录下所有文件,并能够读取到 Markdown 文件的内容,通过这些,我们已经可以实现一个简单的列表页了。

修改 pages 目录下的 index.js 文件:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
import React from "react"
import { graphql } from "gatsby"
import Layout from "../components/layout"

const Index = ({ data }) => {
  console.log(data)
  return (
    <Layout>
      <div>
        <h1>文章列表</h1>
        <h4>{data.allMarkdownRemark.totalCount} 篇文章</h4>
        {data.allMarkdownRemark.edges.map(({ node }) => (
          <div key={node.id}>
            <h3>
              {node.frontmatter.title}{" "}
              <span>
                 {node.frontmatter.date}
              </span>
            </h3>
            <p>{node.excerpt}</p>
          </div>
        ))}
      </div>
    </Layout>
  )
}

export default Index

export const query = graphql`
  query {
    allMarkdownRemark {
      totalCount
      edges {
        node {
          id
          frontmatter {
            title
            date(formatString: "DD MMMM, YYYY")
          }
          excerpt
        }
      }
    }
  }
`

再次访问 http://localhost:8000 就能看到我们的页面已经发生了变化: gatsby-from-zero-3-4.png

我们继续向 pages 目录中添加 Markdown 文件,首页也会继续发生变化: gatsby-from-zero-3-5.png

将 Markdown 文件渲染为对应的 HTML 页面

前文中,我们通过 gatsby-transformer-remark 插件将获取到了所有的 Markdown 文件,并且将所有文件和内容生成了一个文章列表页,但作为一个博客,需要在点击每一个标题时,跳转到对应的页面,这该如何实现呢?

很容易想到的自然是手动添加对应的 js 文件,比如我们可以添加一个 markdown-demo.js,然后借助于 GraphQL 查询获取到 Markdown 文件对应的 HTML 内容,渲染为页面。

这种方法自然是可行的,但是如果每添加一篇文章都需要这样处理那也未免太麻烦了,本部分就将讲解如何动态的将 GraphQL 的查询结果映射为页面。

整个过程中需要两个步骤:创建路径生成内容

为页面创建 slug 或路径

slug 为官方文档中所使用的单词,可以理解为别名,在这里大致和路径类似,本单词本文将不翻译直接使用

本部分中我们将使用多个 Gatsby 提供的 API, Gatsby 提供的所有 API 可以查看官方文档

首先用到的是 onCreateNode 这一API,其中的方法将在每个节点创建或更新时调用,要使用该 API,我们需要编辑项目根目录下的 gatsby-node.js 文件,在其中添加以下内容:

1
2
3
exports.onCreateNode = ({node}) => {
  console.log(node)
}

之后重启服务,可以看到在终端中打印出了所有节点: gatsby-from-zero-3-7.png

从打印内容中我们可以看到能获取到的信息,我们需要做的是为所有 Markdown 类型的节点生成路径

要实现这一目标,最方便的方式是使用 gatsby-source-filesystem 插件,其提供了 createFilePath 方法,我们修改 onCreateNode 方法:

1
2
3
4
5
6
7
const { createFilePath } = require(`gatsby-source-filesystem`) 

exports.onCreateNode = ({ node, getNode }) => {
  if (node.internal.type === `MarkdownRemark`) {
    console.log(createFilePath({ node, getNode, basePath: `pages` }))
  }
}

重启开发服务器,就可以看到,我们创建的两个 Markdown 文件的路径被打印出来了: gatsby-from-zero-3-8.png

将 slug 信息添加到数据源

我们已经获取到了每一个 Markdown 文档的 slug,那当我们访问这一路径时,如何知道他们对应的是哪一个文件?一个可行的处理方式是将 slug 信息添加到数据源对应的条目中,这样我们就可以通过 GraphQL 查询通过 slug 找到对应的文件了。

我们需要使用使用 createNodeField 这一 API,其功能是扩展某节点,向一个节点添加任意信息,新添加的信息将被存储在 fields 属性下。

改造我们的 onCreateNode 方法:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
const { createFilePath } = require(`gatsby-source-filesystem`)

exports.onCreateNode = ({ node, getNode, actions }) => {
  const { createNodeField } = actions // highlight-line
  if (node.internal.type === `MarkdownRemark`) {
    const slug = createFilePath({ node, getNode, basePath: `pages` })
    createNodeField({
      node,
      name: `slug`,
      value: slug,
    })
  }
}

重启开发服务器,打开 GraphiQL 浏览器,就可以找到新添加的属性了: gatsby-from-zero-3-9.png

我们可以通过 GraphQL 查询到 slug 对应的节点内容: gatsby-from-zero-3-10.png

有了这些,下一步就是创建页面了。

创建页面

创建页面需要使用 createPages API,该 API 将在数据源初始化完成,数据源转换插件执行完成后执行,因此可以在其中执行 GraphQL 查询语句。

创建页面的主要操作如下:

  • 通过 GraphQL 查询到所有的 slug
  • 使用 createPages 所提供的 createPage 方法为每个 slug 生成页面

createPage 接受几个主要参数:

  • path: 要生成的页面路径,在页面及页面 GraphQL 查询中均可用
  • component: 生成页面的模板
  • context: 可选项,要传递的额外信息,传递的信息将在页面 GraphQL 查询中可用

所以,我们先创建一个最简单的模板/src/templates/post.js,暂不渲染内容,只将传递到页面中的 props 打印出来:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
import React from "react"
import Layout from "../components/layout"

export default (props) => {
  console.log(props)
  return (
    <Layout>
      <div>Hello blog post</div>
    </Layout>
  )
}

之后,再创建页面,我们修改 gatsby-node.js 方法,增加以下内容:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
exports.createPages = async ({ graphql, actions }) => {
  const { createPage } = actions
  const result = await graphql(`
    query {
      allMarkdownRemark {
        edges {
          node {
            fields {
              slug
            }
          }
        }
      }
    }
  `)

  result.data.allMarkdownRemark.edges.forEach(({ node }) => {
    createPage({
      path: node.fields.slug,
      component: path.resolve(`./src/templates/post.js`),
      context: {
        slug: node.fields.slug,
      },
    })
  })
}

我们通过 GraphQL 查询到所有 Markdown 文件的 slug,然后为每一个 slug 生成了一个页面。

重启开发服务器,访问相关页面,可以看到页面已经创建好了,在控制台也打印出了传递到页面中的信息: gatsby-from-zero-3-11.png

渲染页面内容

我们已经为每一个 Markdown 文档生成了页面,但所有页面都只显示了模板内容,我们需要将 Markdown 文档的内容渲染到页面中。

我们已经将 slug 信息等传递到了每一个页面,只需要在模板文件中根据传递的信息进行查询,即可获得文件内容,之后使用获得的内容动态渲染模板文件即可。

我们修改模板文件 /src/templates/post.js 内容:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
import React from "react"
import { graphql } from "gatsby"
import Layout from "../components/layout"

export default ({ data }) => {
  const post = data.markdownRemark
  return (
    <Layout>
      <div>
        <h1>{post.frontmatter.title}</h1>
        <div dangerouslySetInnerHTML={{ __html: post.html }} />
      </div>
    </Layout>
  )
}

export const query = graphql`
  query($slug: String!) {
    markdownRemark(fields: { slug: { eq: $slug } }) {
      html
      frontmatter {
        title
      }
    }
  }
`

注意上面的 GraphQL 查询,在 createPage 方法的说明中我们已经提及了,path 变量以及在 context 中的变量均可在这里使用,所以如果我们在这里将 $slug 换成 $path 也是可行的。

再次打开页面,此时已经正常渲染了 Markdown 文件内容: gatsby-from-zero-3-12.png

为文章列表页添加链接

为了方便跳转,给我们前面创建的文章列表页增加跳转链接,修改 pages/index.js 文件内容,增加 GraphQL 查询内容,并将文章标题内容使用 Link 标签包裹:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
import React from "react"
import { Link, graphql } from "gatsby"
import Layout from "../components/layout"

const Index = ({ data }) => {
  return (
    <Layout>
      <div>
        <h1>文章列表</h1>
        <h4>{data.allMarkdownRemark.totalCount} 篇文章</h4>
        {data.allMarkdownRemark.edges.map(({ node }) => (
          <div key={node.id}>
            <Link to={node.fields.slug}>
            <h3>
              {node.frontmatter.title}{" "}
              <span>
                 {node.frontmatter.date}
              </span>
            </h3>
             </Layout>
            <p>{node.excerpt}</p>
            </Link>
          </div>
        ))}
      </div>
  )
}

export default Index

export const query = graphql`
  query {
    allMarkdownRemark {
      totalCount
      edges {
        node {
          id
          frontmatter {
            title
            date(formatString: "DD MMMM, YYYY")
          }
          fields {
            slug
          }
          excerpt
        }
      }
    }
  }
`

再次打开首页,已经可以跳转到生成的页面了: gatsby-from-zero-3-13.png

总结

通过本篇内容,我们了解了 Gatsby 如何利用数据源转换插件来读取文件内容并将其加入数据源,以及如何利用数据源中的内容动态生成页面。到目前为止,我们已经实现了一个基本的博客功能,现在只要在 pages 目录下添加 Markdown 文件,就会自动生成对应的页面,并加入到首页的文章列表中。

当然,目前我们的博客还比较简陋,因为我们未添加任何样式,在本系列的后续内容中,将会有专门学习 Gatsby 样式相关的知识,并一起来进行网站的美化。在下一部分,我们将了解 Strapi 相关的知识,了解如何从远程 API 获得数据并进行渲染。

comments powered by Disqus
Built with Hugo
主题 StackJimmy 设计