ReactJS 静态网页生成器方案-Gatsby学习总结

ReactJS 静态网页生成器方案-Gatsby学习总结

Gatsby.js 是基于 React 构建的、速度非常快的、现代化网站生成器。这里通过一个小型的项目来学习Gatsby的使用。

1. Gatsby 介绍

Gatsby 是一个静态站点生成器.
官网:https://www.gatsbyjs.org/

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 中提供了丰富且功能强大的各种类型的插件, 用什么装什么.

ReactJS 静态网页生成器方案-Gatsby学习总结

4. 创建 Gatsby 项目

  1. 全局安装脚手架工具
npm install gatsby-cli -g
  1. 创建项目
创建:gatsby new project-name https://github.com/gatsbyjs/gatsby-starter-hello-world
启动:gatsby develop 或 npm start 
访问:localhost:8000

5. 基于文件的路由系统

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

ReactJS 静态网页生成器方案-Gatsby学习总结

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

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

gatsby-node.js

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

7. Link 组件

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

import React from "react"
import { Link, graphql} from "gatsby"
import SEO from "../components/SEO" // 设置title description 等利于seo的设置
import styles from "./index.module.less"

export default function Home({data}) {
  console.log(data)
  return <div>
  <SEO title = "seo title"/>
    <Link className={styles.red} to="/list">list</Link>
    <Link to="/product">product</Link>

    <div>title:{data.site.siteMetadata.title}</div>
    <div>author:{data.site.siteMetadata.author}</div>
  </div>
}

8. GraphQL 数据层

在 Gatsby 框架中提供了一个统一的存储数据的地方,叫做数据层.
在应用构建时,Gatsby 会从外部获取数据并将数据放入数据层,组件可以直接从数据层查询数据.
数据层使用 GraphQL 构建.
调试工具:localhost:8000/___graphql(启动项目时会同时启动)
ReactJS 静态网页生成器方案-Gatsby学习总结

8.1 页面组件数据查询

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

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

export default function List({data}) {  
  return <div>
  {
    data.allMarkdownRemark.nodes.map( node => (
      <div key={node.id}>
        <p>{node.frontmatter.title}</p>
        <p>{node.frontmatter.date}</p>
        <Link to={`/article/${node.fields.slug}`}> to {node.fields.slug}`</Link> 
        <div dangerouslySetInnerHTML={{__html:node.html}}></div>
      </div>
    ))
  }</div>
}

export const query = graphql`
  query {
    allMarkdownRemark {
      nodes {
        html
        frontmatter {
          date
          title
        }
        internal {
          type
        }
        fileAbsolutePath
        id
        fields {
          slug
        }
      }
    }
  }
`

8.2 非页面组件数据查询

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

import React from "react"
import { graphql, useStaticQuery} from "gatsby"


export default function Header({data}) {
const query = useStaticQuery(graphql`
    query {
            site {
            siteMetadata {
                title
                author
            }
            }
        }
    `)
  return <div>
    <div>list-title:{query.site.siteMetadata.title}</div>
    <div>list-author:{query.site.siteMetadata.author}</div>
  </div>
}

9. Gatsby 插件 (网址)

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

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

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

9.1 将 JSON 数据放入数据层

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

gatsby-source-filesystem: 用于将本地文件中的数据添加至数据层.
gatsby-transformer-json:将原始JSON字符串转换为JavaScript对象.

9.2 图像优化

  1. 图像文件和数据文件不在源代码中的同一位置
  2. 图像路径基于构建站点的绝对路径, 而不是相对于数据的路径, 难以分析出图片的真实位置
  3. 图像没有经过任何优化操作
  4. 生成多个具有不同宽度的图像版本, 为图像设置 srcset 和 sizes 属性, 因此无论您的设备是什么宽度都可以加载到合适大小的图片
  5. 使用"模糊处理"技术, 其中将一个20px宽的小图像显示为占位符, 直到实际图像下载完成为止.

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


gatsby-source-filesystem: 用于将本地文件信息添加至数据层.
gatsby-plugin-sharp: 提供本地图像的处理功能(调整图像尺寸, 压缩图像体积 等等).
gatsby-transformer-sharp: 将 gatsby-plugin-sharp 插件处理后的图像信息添加到数据层.
gatsby-image: React 组件, 优化图像显示, 基于 gatsby-transformer-sharp 插件转化后的数据.

9.3 将 markdown 数据放入数据层

  1. 通过 gatsby-source-filesystem 将markdown文件数据放入数据层
  2. 通过 gatsby-transformer-remark 将数据层中的原始 markdown 数据转换为对象形式
  3. 组件数据查询

gatsby-config.js

/**
 * Configure your Gatsby site with this file.
 *
 * See: https://www.gatsbyjs.com/docs/gatsby-config/
 */

module.exports = {
  /* Your site config here */
  plugins: [
    //将本地JSON文件数据添加至graphiQL数据层
    {
      resolve:"gatsby-source-filesystem",
      options:{
        name:"json",
        path:`${__dirname}/json/`
      }
    },
    //将本地MD文件数据添加至graphiQL数据层
    {
      resolve:"gatsby-source-filesystem",
      options:{
        name:"markdown",
        path:`${__dirname}/posts/`
      }
    },
    //将本地xml文件数据添加至graphiQL数据层
    {
      resolve:"gatsby-source-filesystem",
      options:{
        name:"xml",
        path:`${__dirname}/xml/`
      }
    },
    // 将原始JSON字符串转换为JS对象
    "gatsby-transformer-json",
    //本地插件 转换xml
    "gatsby-transformer-xml",
    "gatsby-plugin-sharp",
    "gatsby-transformer-sharp",
    // 将原始MD字符串转换为JS对象
    {
      resolve:"gatsby-transformer-remark",
      //处理md文件中的图片 md文件中的图片会被转换为html
      options:{plugins:["gatsby-remark-images"]}
    },
    //获取外部strapi数据
    // {
    //   resolve:"gatsby-source-strapi",
    //   options:{
    //     apiURL:"http://localhost:1337",
    //     contentTypes:["POST"]
    //   }
    // },
    // 本地插件 plugins文件夹内 实现gatsby-source-strapi类似效果
    {
      resolve:"gatsby-source-mystrapi",
      //处理md文件中的图片 md文件中的图片会被转换为html
      options:{
        apiURL:"http://localhost:1337",
        contentTypes:["POST", "Product"]
      }
    },
    //SEO 优化管理员数据 处理head中的meta title标签
    "gatsby-plugin-react-helmet",
    //支持less
    "gatsby-plugin-less",
  ],
  siteMetadata:{
    title:'hello Gatsby',
    author:'john'
  }
}


9.4 根据markdown构建页面生成文章详情

  1. 重新构建查询数据, 添加 slug 作为请求标识, slug 值为文件名称
gatsby.md    ->  /posts/gatsby
react.md     ->  /posts/react

gatsby-node.js

//为了给每篇文章加上唯一标识(此处使用文件名作为唯一标示)
function onCreateNode ({ node, actions}) {
    const { createNodeField} = actions;
    if (node.internal.type === "MarkdownRemark") {
        const slug = path.basename(node.fileAbsolutePath, '.md')
        createNodeField({
            node,
            name:"slug",
            value:slug
        })
        
    }
}
  1. 根据 slug 标识构建页面
async function createPages({actions, graphql}) {
    const { createPage} = actions
    // 获取模板绝对路径
    const template = require.resolve("./src/templates/article.js")
    // 获取模板所需数据
    let {data} = await graphql(`
      query {
        allMarkdownRemark {
          nodes {
            fields {
              slug
            }
          }
        }
      }
    `)
    // 根据模板及数据创建页面
    data.allMarkdownRemark.nodes.forEach(node => {
        createPage({
            //模板绝对路径
            component:template,
            //访问地址
            path:`/article/${node.fields.slug}`,
            //传递给模板的数据
            context:{
                slug:node.fields.slug
            }
        })
    })
    
}
  1. 组件数据查询
export const query = graphql`
    query ($slug:String) {
        markdownRemark(fields: {slug: {eq: $slug}}) {
            html
            frontmatter {
                date
                title
            }
            id
        }
    }
`
  1. 处理 markdown 文件中图片

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

// 将原始MD字符串转换为JS对象
{
    resolve:"gatsby-transformer-remark",
    //处理md文件中的图片 md文件中的图片会被转换为html
    options:{plugins:["gatsby-remark-images"]}
},

10. 从 Strapi 中获取数据

1.创建Strapi项目

创建项目: npx create-strapi-app <项目名称>
https://github.com/strapi/strapi

  1. 使用插件导入
    https://www.gatsbyjs.org/packages/gatsby-source-strapi/?=strapi
    // 获取外部strapi数据
    {
      resolve:"gatsby-source-strapi",
      options:{
        apiURL:"http://localhost:1337", //strapi网址
        contentTypes:["POST"]
      }
    },

11. 插件开发

11.1 Gatsby Source 插件开发

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

  1. gatsby clean 清除上一次的构建内容
  2. 在项目根目录里下创建 plugins 文件夹,在此文件夹中继续创建具体的插件文件夹,比如 gatsby-source-mystrapi 文件夹
  3. 在插件文件夹中创建 gatsby-node.js 文件
  4. 插件实际上就是 npm 包
  5. 导出 sourceNodes 方法用于获取外部数据,创建数据查询节点
  6. 在 gatsby-config.js 文件中配置插件,并传递插件所需的配置参数
  7. 重新运行应用

plugins/gatsby-source-mystrapi/gatsby-node.js

const axios = require("axios")
const pluralize = require("pluralize")
const createNodeHelper = require("gatsby-node-helpers").default

async function sourceNodes({actions}, configOptions) {
  const {apiURL, contentTypes} = configOptions
  const { createNode } = actions
  // POST - posts Product - products
  const types = contentTypes.map(type => type.toLowerCase()).map(type => pluralize(type))
  //types [ 'posts', 'products' ]
  let final = await getContents(types, apiURL)

  // console.log(final)
  for (let [key, value] of Object.entries(final)) {
    //构建数据节点对象 allPostsContent allProductsContent
    const {createNodeFactory} = createNodeHelper({
      typePrefix:key,
    })
    const createNodeObject = createNodeFactory("content")
    //根据数据节点对象创建节点
    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
}

gatsby-config.js

    {// 自定义插件
      resolve:"gatsby-source-mystrapi",
      options:{
        apiURL:"http://localhost:1337",
        contentTypes:["POST", "Product"]
      }
    },

11.2 Gatsby Transformer 插件开发

  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 查询节点

plugins/gatsby-transformer-xml/gatsby-node.js

const {parseString} = require("xml2js")
const {promisify} = require("util")
const parse = promisify(parseString)
const createNodeHelper = require("gatsby-node-helpers").default

async function onCreateNode ({ node, loadNodeContent, actions}) {
    const { createNode} = actions;
    if (node.internal.mediaType === "application/xml") {
        let content = await loadNodeContent(node)
        let obj =await parse(content, {explicitArray:false, explicitRoot:false})
        //构建数据节点对象 allPostsContent allProductsContent
        const {createNodeFactory} = createNodeHelper({
            typePrefix:"XML",
          })
          const createNodeObject = createNodeFactory("parsedContent")
          //根据数据节点对象创建节点
          createNode(createNodeObject(obj))
    }
}
module.exports = {
    onCreateNode
}

12. 相关插件使用

12.1 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 const SEO = ({title, description, meta, lang}) => {
    const {site} = useStaticQuery(graphql`
        query {
            site {
                siteMetadata {
                    title
                    author
                }
            }
        }
    `)
    const metaDes = description || site.siteMetadata.description
    return <Helmet 
        htmlAttributes={{lang}} 
        title={title} 
        titleTemplate={`%s | ${site.siteMetadata.title}`}
        meta={[{
            name:'description',
            content:metaDes
        }].concat(meta)}
    />
}
SEO.defaultProps = {
    description:"test des",
    meta:[],
    lang:'en'
}
export default SEO

12.2 在 gatsby 应用中使用 less

下载插件:npm install --save gatsby-plugin-less
配置插件:plugins: [`gatsby-plugin-less`]
创建样式:index.module.less
引入样式:import styles from './index.module.less'

13. 配置redux

  1. 创建仓库 store/createStor
import { createStore, applyMiddleware } from "redux"
import thunk from "redux-thunk"
import reducer from "./reducer"

export default () => {
    const store = createStore(reducer, applyMiddleware(thunk))
    return store
}
  1. 创建 action reducer

  2. 配置gatsby-browser.js
    gatsby-browser.js

require("./src/styles/global.css");
const React = require("react")
const { Provider } = require("react-redux")
const createStore = require("./store/createStore").default

exports.wrapRootElement = ({ element }) => {
    return <Provider store={createStore()}>{element}</Provider>
}

  1. 配置gatsby-ssr.js
    gatsby-ssr.js
const React = require("react")
const { Provider } = require("react-redux")
const createStore = require("./store/createStore").default

exports.wrapRootElement = ({ element }) => {
    return <Provider store={createStore()}>{element}</Provider>
}

14. 完整代码

代码地址:https://gitee.com/liannian9/fed-e-task-04-04/tree/master/homework-gatsby

上一篇:Javascript-为什么Material-UI无法识别theme.spacing函数?


下一篇:ReactJS+ReactNative 笔记