ReactJS 静态网页生成器方案-Gatsby学习总结
Gatsby.js 是基于 React 构建的、速度非常快的、现代化网站生成器。这里通过一个小型的项目来学习Gatsby的使用。
1. Gatsby 介绍
Gatsby 是一个静态站点生成器.
官网:https://www.gatsbyjs.org/
2. 静态应用的优势
- 访问速度快
- 更利于 SEO 搜索引擎的内容抓取
- 部署简单
3. Gatsby 总览
- 基于 React 和 GraphQL. 结合了 webpack, babel, react-router 等前端领域中最先进工具. 开发人员开发体验好
- 采用数据层和UI层分离而不失 SEO 的现代前端开发模式. 对SEO非常友好
- 数据预读取, 在浏览器空闲的时候预先读取链接对应的页面内容. 使静态页面拥有 SPA 应用的用户体验, 用户体验好
- 数据来源多样化: Headless CMS, markdown, API.
- 功能插件化, Gatsby 中提供了丰富且功能强大的各种类型的插件, 用什么装什么.
4. 创建 Gatsby 项目
- 全局安装脚手架工具
npm install gatsby-cli -g
- 创建项目
创建:gatsby new project-name https://github.com/gatsbyjs/gatsby-starter-hello-world
启动:gatsby develop 或 npm start
访问:localhost:8000
5. 基于文件的路由系统
Gatsby 框架内置基于文件的路由系统, 页面组件被放置在 src/pages 文件夹中.
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(启动项目时会同时启动)
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 )
- 数据源插件:负责从应用外部获取数据,将数据统一放在 Gatsby 的数据层中
- 数据转换插件:负责转换特定类型的数据的格式,比如将 markdown 文件中的内容转换为对象形式
- 功能插件:为应用提供功能,比如通过插件让应用支持 Less 或者 TypeScript.
9.1 将 JSON 数据放入数据层
要将本地 JSON 文件中的数据放入数据层需要用到两个插件.
gatsby-source-filesystem: 用于将本地文件中的数据添加至数据层.
gatsby-transformer-json:将原始JSON字符串转换为JavaScript对象.
9.2 图像优化
- 图像文件和数据文件不在源代码中的同一位置
- 图像路径基于构建站点的绝对路径, 而不是相对于数据的路径, 难以分析出图片的真实位置
- 图像没有经过任何优化操作
- 生成多个具有不同宽度的图像版本, 为图像设置 srcset 和 sizes 属性, 因此无论您的设备是什么宽度都可以加载到合适大小的图片
- 使用"模糊处理"技术, 其中将一个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 数据放入数据层
- 通过 gatsby-source-filesystem 将markdown文件数据放入数据层
- 通过 gatsby-transformer-remark 将数据层中的原始 markdown 数据转换为对象形式
- 组件数据查询
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构建页面生成文章详情
- 重新构建查询数据, 添加 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
})
}
}
- 根据 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
}
})
})
}
- 组件数据查询
export const query = graphql`
query ($slug:String) {
markdownRemark(fields: {slug: {eq: $slug}}) {
html
frontmatter {
date
title
}
id
}
}
`
- 处理 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
- 使用插件导入
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 应用外部获取数据,创建数据查询节点供开发者使用
- gatsby clean 清除上一次的构建内容
- 在项目根目录里下创建 plugins 文件夹,在此文件夹中继续创建具体的插件文件夹,比如 gatsby-source-mystrapi 文件夹
- 在插件文件夹中创建 gatsby-node.js 文件
- 插件实际上就是 npm 包
- 导出 sourceNodes 方法用于获取外部数据,创建数据查询节点
- 在 gatsby-config.js 文件中配置插件,并传递插件所需的配置参数
- 重新运行应用
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 插件开发
- 在 plugins 文件夹中创建 gatsby-transformer-xml 文件件
- 在插件文件夹中创建 gatsby-node.js 文件
- 在文件中导出 onCreateNode 方法用于构建 Gatsby 查询节点
- 根据节点类型筛选 xml 节点 node.internal.mediaType -> application/xml
- 通过 loadNodeContent 方法读取节点中的数据
- 通过 xml2js 将xml数据转换为对象
- 将对象转换为 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
- 创建仓库 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
}
-
创建 action reducer
-
配置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>
}
- 配置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