React静态站点生成器 Gatsby(二十二)

802 阅读6分钟

1.Gatsby 介绍

Gatsby 是一个静态站点生成器

官网地址是:www.gatsbyjs.cn/

2.静态应用的优势

  1. 访问速度快
  2. 更利于 SEO 搜索引擎的内容抓取
  3. 部署简单

3.Gatsby 总览

  1. 基于 React 和 GraphQL,结合了 webpack、babel、react-router 等前端领域中最先进工具,开发人员开发体验好
  2. 采用数据层和UI层分离而不失 SEO 的现代前端开发模式,对SEO非常友好
  3. 数据预读取,在浏览器空闲的时候预先读取链接对应的页面内容,使静态页面拥有 SPA 应用的用户体验,用户体验好
  4. 数据来源多样化: Headless CMS、markdown、API
  5. 功能插件化,Gatsby 中提供了丰富且功能强大的各种类型的插件,用什么装什么

4.创建 Gatsby项目

  1. 全局安装脚手架工具 :npm install gatsby-cli -g

  2. 创建项目

    创建:gatsby new project-name https://github.com/gatsbyjs/gatsby-starter-hello-world
    启动:gatsby develop 或 npm start 
    访问:localhost:8000
    

5.基于文件的路由系统

Gatsby 框架内置基于文件的路由系统,页面组件被放置在 src/pages/ 文件夹中

// src/pages/list.js
import React from "react"

export default function List() {
  return <div>List page</div>
}

访问地址:http://localhost:8000/list

6.以编程的方式创建页面

基于同一个模板创建多个 HTML 页面,有多少数据就创建多少页面,比如商品详情页面,有多少商品就生成多少商品详情展示页面

// gatsby-node.js
function createPages({ actions }) {
  const { createPage } = actions
  // 获取模板的绝对路径
  const template = require.resolve("./src/templates/person.js")
  // 获取模板所需要的数据
  const persons = [
    { slug: "zhangsan", name: "张三", age: 20 },
    { slug: "lisi", name: "李四", age: 30 },
  ]
  // 根据模板和数据创建页面
  persons.forEach(person => {
    createPage({
      // 模板绝对路径
      component: template,
      // 访问地址
      path: `/person/${person.slug}`,
      // 传递给模板的数据
      context: person,
    })
  })
}

module.exports = {
  createPages,
}
import React from "react"

export default function Person({ pageContext }) {
  const { name, age } = pageContext
  return (
    <div>
      Person {name} {age}
    </div>
  )
}

7.Link 组件

在 Gatsby 框架中页面跳转通过 Link 组件实现

import React from "react"
import { Link } from "gatsby"

export default function Home() {
  return (
    <div>
      <Link to="/person/zhangsan">张三</Link>
      <Link to="/person/lisi">李四</Link>
    </div>
  )
}

8.GraphQL 数据层

在 Gatsby 框架中提供了一个统一的存储数据的地方,叫做数据层

在应用构建时,Gatsby 会从外部获取数据并将数据放入数据层,组件可以直接从数据层查询数据

数据层使用 GraphQL 构建

调试工具:localhost:8000/___graphql

image-20220216230828164

9.GraphQL 数据查询

1.页面组件

在组件文件中导出查询命令,框架执行查询并将结果传递给组件的 prop 对象,存储在 props 对象的 data 属性中

// gatsby-config
module.exports = {
  siteMetadata: {
    title: "Hello Yun",
    author: "yunmu",
  },
  plugins: [],
}
// index.js
import React from "react"
import { graphql } from "gatsby"

export default function Home({ data }) {
  console.log(data)
  return (
    <div>
      <p>{data.site.siteMetadata.title}</p>
      <p>{data.site.siteMetadata.author}</p>
    </div>
  )
}

export const query = graphql`
  query MyQuery {
    site {
      siteMetadata {
        title
        author
      }
    }
  }
`

2. 非页面组件

通过钩子函数 useStaticQuery 进行手动查询

然后在对应页面组件引入即可

// components/Foo
import React from "react"
import { graphql, useStaticQuery } from "gatsby"
export default function Foo() {
  const data = useStaticQuery(graphql`
    query NonPageQuery {
      site {
        siteMetadata {
          title
          author
        }
      }
    }
  `)
  return (
    <div>
      <p>{data.site.siteMetadata.title}</p>
      <p>{data.site.siteMetadata.author}</p>
    </div>
  )
}

10.Gatsby 插件

Gatsby 框架内置插件系统,插件是为应用添加功能的最好的方式

在 Gatsby 中有三种类型的插件: 分别为数据源插件 ( source )、数据转换插件 ( transformer )、功能插件 ( plugin )

  • 数据源插件:负责从应用外部获取数据,将数据统一放在 Gatsby 的数据层中
  • 数据转换插件:负责转换特定类型的数据的格式,比如将 markdown 文件中的内容转换为对象形式
  • 功能插件:为应用提供功能,比如通过插件让应用支持 Less 或者 TypeScript

插件查询:www.gatsbyjs.org/plugins/

11.将 JSON 数据放入数据层

要将本地 JSON 文件中的数据放入数据层需要用到两个插件

gatsby-source-filesystem:用于将本地文件中的数据添加至数据层

gatsby-transformer-json:将原始 JSON 字符串转换为 JavaScript 对象

根目录创建 json/products.json

[
  {
    "title": "Vans 男子短袖T恤 Work Weird 新款运动休闲 TEE 迷彩官方正品",
    "price": 208,
    "url": "/images/product-1.jpg",
    "address": "上海",
    "id": "1"
  },
  {
    "title": "女 韩版 长袖 卫衣 假两件",
    "price": 67,
    "url": "/images/product-2.jpg",
    "address": "北京",
    "id": "2"
  },
  {
    "title": "女 春秋 阔腿裤 九分裤 打底裤",
    "price": 399,
    "url": "/images/product-3.jpg",
    "address": "杭州",
    "id": "3"
  },
  {
    "title": "波司登 羽绒服 男女两穿 中长款",
    "price": 544,
    "url": "/images/product-4.jpg",
    "address": "南京",
    "id": "4"
  }
]

存放图片的 images 文件夹在根目录静态资源文件夹 static 目录下

安装插件:

npm install gatsby-source-filesystem gatsby-transformer-json

配置插件:

// gatsby-config.js
module.exports = {
  siteMetadata: {
    title: "Hello Yun",
    author: "yunmu",
  },
  /* Your site config here */
  plugins: [
    {
      resolve: "gatsby-source-filesystem",
      options: {
        name: "json",
        path: `${__dirname}/json/`,
      },
    },
    "gatsby-transformer-json",
  ],
}

12.图像优化

之前的图像存在如下几个问题:

  1. 图像文件和数据文件不在源代码中的同一位置
  2. 图像路径基于构建站点的绝对路径,而不是相对于数据的路径,难以分析出图片的真实位置
  3. 图像没有经过任何优化操作

gatsby-source-filesystem: 用于将本地文件信息添加至数据层

gatsby-plugin-sharp: 提供本地图像的处理功能(调整图像尺寸,压缩图像体积等等)

gatsby-transformer-sharp: 将 gatsby-plugin-sharp 插件处理后的图像信息添加到数据层

gatsby-image:React 组件,优化图像显示,基于 gatsby-transformer-sharp 插件转化后的数据

生成多个具有不同宽度的图像版本, 为图像设置 srcset 和 sizes 属性, 因此无论您的设备是什么宽度都可以加载到合适大小的图片

使用"模糊处理"技术, 其中将一个20px宽的小图像显示为占位符, 直到实际图像下载完成为止

npm install gatsby-plugin-sharp gatsby-transformer-sharp gatsby-image

将 images 文件夹放置到 json 文件夹下并修改 json 文件夹图片路径为相对路径

然后在其他组件可以查询使用该 json 数据并展示图片

import React from "react"
import { graphql } from "gatsby"
import Img from "gatsby-image"

export default function Product({ data }) {
  return data.allProductsJson.nodes.map(node => (
    <div key={node.title}>
      <p>{node.title}</p>
      <p>{node.address}</p>
      <p>{node.price}</p>
      <div style={{ width: 400 }}>
        <Img fixed={node.url.childImageSharp.fixed} />
      </div>
    </div>
  ))
}

// export const query = graphql`
//   query {
//     allProductsJson {
//       nodes {
//         address
//         price
//         title
//         url {
//           childImageSharp {
//             fluid {
//               aspectRatio
//               sizes
//               src
//               srcSet
//             }
//           }
//         }
//       }
//     }
//   }
// `

export const query = graphql`
  query {
    allProductsJson {
      nodes {
        address
        price
        title
        url {
          childImageSharp {
            fixed(width: 200, height: 200) {
              height
              width
              src
              srcSet
            }
          }
        }
      }
    }
  }
`

13.将 markdown 数据放入数据层

1.构建文件列表

通过 gatsby-source-filesystem 将 markdown 文件数据放入到数据层

{
  resolve: `gatsby-source-filesystem`,
  options: {
    name: 'posts',
    path: `${__dirname}/src/posts`
  }
}

通过 gatsby-transformer-remark 将数据层中的原始 markdown 数据转换为对象形式

module.exports = {
    plugins: [`gatsby-transformer-remark`]
}

2.构建文章详情

重新构建查询数据,添加 slug 作为请求标识, slug 值为文件名称

gatsby.md => /posts/gatsby

react.md => /posts/react

// gatsby-node.js
const onCreateNode = ({node, actions}) => {
  const {createNodeField} = actions;
  if (node.internal.type === 'MarkdownRemark') {
    const slug = path.basename(node.fileAbsolutePath, '.md');
    createNodeField({
      node,
      name: 'slug',
      value: slug
    })
  }
}

根据 slug 标识构建页面

// gatsby-node.js 重写 createPages 方法
async function createPages ({graphql, actions}) {
  const {createPage} = actions
  // 1. 获取模板文件的绝对路径
  const template = require.resolve('./src/templates/article.js')
  // 2. 获取页面的访问标识
  let {data} = await graphql(`
    query {
      allMarkdownRemark {
        nodes {
          fields {
            slug
          }
        }
      }
    }
  `)
  // 3. 创建页面
  data.allMarkdownRemark.nodes.forEach(node => {
    createPage({
      component: template,
      path:`/article/${node.fields.slug}`,
      context: {
        slug: node.fields.slug
      }
    })
  })
}
// src/pages/templates/article.js
import React from 'react'

export default function Article({data}) {
  console.log(data)
  const {markdownRemark} = data
  return (
    <div>
      <p>{markdownRemark.frontmatter.title}</p>
      <p>{markdownRemark.frontmatter.date}</p>
      <p dangerouslySetInnerHTML={{__html: markdownRemark.html}}></p>
    </div>
  )
}

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

3. 处理 markdown 文件中的图片

gatsby-remark-images: 处理 markdown 中的图片,以便可以在生产环境中使用

{
  resolve: 'gatsby-transformer-remark',
  options: {
    plugins: [
      'gatsby-remark-images'
    ]
  }
}

14.从 Strapi 获取数据

创建项目:npx create-strapi-app 项目名称

npx create-strapi-app cms

访问:http://localhost:1337/admin/

第一次登录要设置用户名和账号密码

配置插件:

{
  resolve: 'gatsby-source-strapi',
  options: {
    apiURL: 'http://localhost:1337',
    contentTypes: [`posts`]
  }
}

15.Gatsby Source 插件开发

数据源插件负责从 Gatsby 应用外部获取数据,创建数据查询节点供开发者使用

  1. gatsby clean清除上一次的构建内容

  2. 在项目根目录里下创建plugins文件夹,在此文件夹中继续创建具体的插件文件夹,比如gatsby-source-mystrapi文件夹

  3. 在插件文件夹中创建gatsby-node.js文件

  4. 插件实际上就是 npm 包

  5. 导出sourceNodes方法用于获取外部数据,创建数据查询节点

  6. gatsby-config.js文件中配置插件,并传递插件所需的配置参数

  7. 重新运行应用

    pluralize 模块是用来将单词转化成复数形式的

npm init -y
npm install axios
npm install pluralize 

将数据添加到数据层:创建节点对象,将节点对象添加到数据层

npm install gatsby-node-helpers@0.3.0
// gatsby-node.js
const axios = require('axios')
const pluralize = require('pluralize') // 单词转复数形式
const createNodeHelpers = require('gatsby-node-helpers').default

async function sourceNodes ({actions}, configOptions) {
  const {createNode} = actions
  const { apiURL, contentTypes } = configOptions
  // Post -> posts  Product -> products
  const types = contentTypes.map(type => pluralize(type.toLowerCase()))
  // console.log(types) //  [ 'posts', 'products' ]

  // 从外部数据源中获取数据
  let final = await getContents(types, apiURL)
  for(let [key, value] of Object.entries(final)) {
    // 1. 构建数据节点对象 allPostsContent allProductsContent
    console.log('key', key)
    const {createNodeFactory} = createNodeHelpers({
      typePrefix: key,
    })
    const createNodeObject = createNodeFactory('content')
    // 2. 根据数据节点对象对象创建节点
    value.forEach(item => {
      createNode(createNodeObject(item))
    })
  }
}

async function getContents(types, apiURL) {
  const size = types.length
  let index = 0
  // {posts: [], products: []}
  const final = {}

  await loadContents()

  async function loadContents () {
    if (index === size) return
    let {data} = await axios.get(`${apiURL}/${types[index]}`)
    final[types[index++]] = data
    await loadContents()
  }
  return final
}

module.exports = {
  sourceNodes,
}

16.Gatsby Transformer 插件开发

transformer 插件将 source 插件提供的数据转换为新的数据

  1. 在 plugins 文件夹中创建 gatsby-transformer-xml 文件
  2. 在插件文件夹中创建 gatsby-node.js 文件
  3. 在文件中导出 onCreateNode 方法用于构建 Gatsby 查询节点
  4. 根据节点类型筛选 xml 节点 node.internal.mediaType -> application/xml
  5. 通过 loadNodeContent 方法读取节点中的数据
  6. 通过 xml2js 将 xml 数据转换为对象
  7. 将对象转换为 Gatsby 查询节点
const { parseString } = require('xml2js')
const { promisify } = require('util')
const parse = promisify(parseString)
const createNodeHelpers = require('gatsby-node-helpers').default

async function onCreateNode({ node, loadNodeContent, actions }) {
  const { createNode } = actions
  if (node.internal.mediaType === 'application/xml') {// 判断 node 是否是我们需要转换的节点
    let content = await loadNodeContent(node)
    let obj = await parse(content, {explicitArray: false, explicitRoot: false})
    console.log('xml obj', obj)
    console.log('xml test', content)
    const {createNodeFactory} = createNodeHelpers({
      typePrefix: 'XML'
    })
    const createNodeObject = createNodeFactory('parsedContent')
    createNode(createNodeObject(obj))
  }
}

module.exports = {
  onCreateNode,
}

17.SEO 优化

gatsby-plugin-react-helmet

react-helmet是一个组件,用于控制页面元数据.这对于SEO非常重要

此插件用于将页面元数据添加到Gatsby构建的静态HTML页面中

npm install gatsby-plugin-react-helmet react-helmet
import React from 'react'
import { graphql, useStaticQuery } from 'gatsby'
import { Helmet } from 'react-helmet'


export default function SEO({ title, description, meta, lang }) {
  const { site } = useStaticQuery(graphql`
    query {
      site {
        siteMetadata {
          title
          description
        }
      }
    }
  `)
  console.log('site', site)
  const metaDescription = description || site.siteMetadata.description
  return (
    <Helmet
      htmlAttributes={{ lang }}
      title={title}
      titleTemplate={`%s | ${site.siteMetadata.title}`}
      meta={[{
        name: 'description',
        content: metaDescription
      }].concat(meta)}
    />
  )
}

SEO.defaultProps = {
  description: 'test description',
  meta: [],
  lang: 'en'
}

页面中引入SEO组件:

<SEO title="Index Page" />

// 或者

<SEO title="List Page " description="list page description"/>

18.Less 支持

在 Gatsby 应用中使用 less

下载插件: npm install --save gatsby-plugin-less

配置插件: plugins: [ 'gatsby-plugin-less' ]

创建样式: index.module.less

.red {
  color: red
}

引入样式: import styles from './index.module.less'

<Link class={styles.red} to="/person/zhangsan">张三</Link>

19.realworld

案例代码:React/gatsby-realworld · 云牧/exampleCode - 码云 - 开源中国 (gitee.com)