React 静态站点生成器: Gatsby
文章出处: 拉 勾 大前端 高薪训练营
Gatsby 是一个静态站点生成器,官网地址是:https://www.gatsbyjs.cn/
一、静态应用的优势
- 访问速度快
- 更利于 SEO 搜索引擎的内容抓取
- 部署简单
二、Gatsby 总览
- 基于 React 和 GraphQL, 结合了 webpack, babel, react-router 等前端领域中最先进工具,开发人员开发体验好;
- 采用数据层和UI层分离而不失 SEO 的现代前端开发模式.对SEO非常友好;
- 数据预读取,在浏览器空闲的时候预先读取链接对应的页面内容.使静态页面拥有 SPA 应用的用户体验,用户体验好;
- 数据来源多样化: Headless CMS, markdown, API;
- 功能插件化, Gatsby 中提供了丰富且功能强大的各种类型的插件,用什么装什么。
三、创建 Gatsby 项目
- 全局安装脚手架工具
npm install gatsby-cli -g
- 创建项目
创建: gatsby new project-name https://github.com/gatsbyjs/gatsby-starter-hello-world
启动: gatsby develop
或 npm start
访问: localhost:8000
四、基于文件的路由系统
Gatsby 框架内置基于文件的路由系统,页面组件被放置在 src/pages/ 文件夹中
src/pages/list.js
import React from 'react'
export default function List () {
return (
<div>List page</div>
)
}
访问地址: http://localhost:8000/list
五、以编程的方式创建页面
基于同一个模板创建多个 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
}
src/templates/person.js
import React from 'react'
export default function Person({
pageContext}) {
const {
name, age } = pageContext
return (
<div>
Person {
name} {
age}
</div>
)
}
六、 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>
}
七、 GraphQL 数据层
在 Gatsby 框架中提供了一个统的存储数据的地方,叫做数据层。
在应用构建时,Gatsby 会从外部获取数据并将数据放入数据层,组件可以直接从数据层查询数据。
数据层使用GraphQL构建。
调试工具: http://localhost:8000/___graphql
八、 GraphQL 数据查询
1. 页面组件
在组件文件中导出查询命令,框架执行查询并将结果传递给组件的 prop 对象.存储在 props 对象的 data 属性中。
import React from "react"
import {
Link, graphql} from 'gatsby'
export default function Home({
data}) {
console.log(data)
return <div>
<p>{
data.site.siteMetadata.title}</p>
<p>{
data.site.siteMetadata.auth}</p>
</div>
}
export const query = graphql`
query MyQuery {
site {
siteMetadata {
title
auth
}
}
}
`
2. 非页面组件
通过钩子函数 useStaticQuery 进行手动查询
import React from 'react'
import {
graphql, useStaticQuery } from "gatsby";
export default function Header() {
const data = useStaticQuery(graphql`
query NonPageQuery {
site {
siteMetadata {
title
auth
}
}
}
`)
return (
<div>
<p>{
data.site.siteMetadata.title}</p>
<p>{
data.site.siteMetadata.auth}</p>
</div>
)
}
九、 Gatsby 插件
Gatsby 框架内置插件系统,插件是为应用添加功能的最好的方式.
在 Gatsby 中有三种类型的插件:分别为数据源插件(source),数据转换插件(transformer),功能插件(plugin)
数据源插件: 负责从应用外部获取数据,将数据统一放在 Gatsby 的数据层中
数据转换插件: 负责转换特定类型的数据的格式,比如将 markdown 文件中的内容转换为对象形式
功能插件: 为应用提供功能,比如通过插件让应用支持 Less 或者 TypeScript.
https://www.gatsbyjs.org/plugins/
十、将 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 gatsby',
auth: 'Cathy'
},
/* Your site config here */
plugins: [
{
resolve: 'gatsby-source-filesystem',
options: {
name: 'json',
path: `${
__dirname}/json/`
}
},
'gatsby-transformer-json'
],
}
十一、图像优化
- 图像文件和数据文件不在源代码中的同一位置
- 图像路径基于构建站点的绝对路径,而不是相对于数据的路径,难以分析出图片的真实位置
- 图像没有经过任何优化操作
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
十二、将 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'
]
}
}
注:貌似不使用 gatsby-remark-images 也可以展示图片,而使用了 gatsby-remark-images 反而还得安装 gatsby-plugin-sharp, gatsby-plugin-sharp 需要翻墙才能安装,无法翻墙就无法安装,不安装 gatsby-remark-images 会导致 markdownRemark 的 html 字段消失,报错为
Cannot query field "html" on type "MarkdownRemark".
。所以我就没有使用 gatsby-remark-images 了,也可以正常展示图片。
十三、从 Strapi 获取数据
创建项目:npx create-strapi-app
项目名称
http://github.com/strapi/strapi
npx create-strapi-app cms
访问:http://localhost:1337/admin/
第一次登录要设置用户名和账号密码
配置插件
{
resolve: 'gatsby-source-strapi',
options: {
apiURL: 'http://localhost:1337',
contentTypes: [`posts`]
}
}
十四、Gatsby Source 插件开发
数据源插件负责从 Gatsby 应用外部获取数据,创建数据查询节点供开发者使用
gatsby clean
清除上一-次的构建内容- 在项目根目录里下创建
plugins
文件夹,在此文件夹中继续创建具体的插件文件夹,比如gatsby-source-mystrapi
文件夹 - 在插件文件夹中创建
gatsby-node.js
文件 - 插件实际上就是 npm 包
- 导出
sourceNodes
方法用于获取外部数据,创建数据查询节点 - 在
gatsby-config.js
文件中配置插件,并传递插件所需的配置参数 - 重新运行应用
npm init -y
npm install axios
npm install pluralize
pluralize 模块是用来将单词转化成复数形式的。
将数据添加到数据层:创建节点对象,将节点对象添加到数据层
npm install [email protected]
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,
}
十五、Gatsby Transformer 插件开发
transformer 插件将 source 插件提供的数据转换为新的数据
- 在 plugins 文件夹中创建 gatsby-transformer-xml 文件件
- 在插件文件夹中创建 gatsby-node.js 文件
- 在文件中导出 onCreateNode 方法用于构建 Gatsby 查询节点
- 根据节点类型筛选 xml 节点 node.internal.mediaType -> application/xml
- 通过 loadNodeContent 方法读取节点中的数据
- 通过 xml2js 将 xml 数据转换为对象
- 将对象转换为 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,
}
十六、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"/>
十七、 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>