Gatsby 是一个基于 React 的、免费开源的、用于搭建静态站点的框架。
Fast in every way that matters.
下图是官网首页的截图:描述了 Gatsby 的工作流程
Gatsby 主要的应用的技术是 React 和 GraphQL,利用 Gatsby 搭建一个简单的静态站点,是一个很好的学习过程。本文主要讲解如何从零搭建一个简单的个人博客结构的过程,也是自己学习和解决一些 bug 的过程。
通过搭建这个简单的项目,你可以学习到:
- Gatsby 的搭建流程
- GraphQL 的简单运用
- React 的函数式组件
- CSS Module
- CSS in JS
- SEO 优化
- surge 部署
- Netlify CMS
本文仓库地址: https://github.com/destinytaoer/gatsby-start
前置知识
- React:只需要对 React 基础知识有所了解即可,尤其是 JSX 语法
- GraphQL:了解基本查询用法即可
这里推荐一个 B 站 GraphQL 入门的学习视频,可以花上一两个小时快速的入门 GraphQL。
开始
安装 Gatsby 脚手架
npm install -g gatsby-cli
Gatsby 脚手架提供的常用命令有:
- gatsby new [projectName] [starter]:根据 starter 创建一个新项目
- gastby develop:开启热加载开发环境
- gastby build:打包到 public 下
- gatsby serve:在打包之后,启动一个本地的服务测试打包好的生产环境文件
新建项目
gatsby new gatsby-start
gatsby 默认会使用 gatsby-starter-default 来新建项目,你可以在后面加上其他 starter 的 GitHub 地址,来使用这个 starter 初始化项目。
starter
starter 的概念就是一个初始化模板,你可以基于这个模板进行开发。本文使用的是最基础的版本 gastby-starter-hello-world,只包含基本的依赖:react、graphql 和 prettier。
gatsby new gatsby-start https://github.com/gatsbyjs/gatsby-starter-hello-world
- 其他 starter 可查看官网
如果你使用其他 starter,可能会出现安装依赖时报错的情况:pngquant pre-build test failed。解决方案可查看下文 Gatsby 图片优化部分。
启动项目
cd gatsby-start
gatsby develop
# 或者
yarn develop
打开 localhost:8080
查看生成页面,可以打开 localhost:8000/__graphiql
GraphiQL 调试界面。
GraphiQL
GraphiQL 是 GraphQL 的调试界面,你可以在这里查看所有的数据、以及调试 query 语句是否返回相应的数据。
Gatsby 还增强了这个界面,添加了 Explorer 侧边栏,我们可以直接在左侧点击选中,就可以自动生成 query 语句。
创建页面
只需要了解 React 即可编写一个页面。
在 src/pages/
目录下,新建一个 JS 文件即可。在 src/pages/
目录下的页面,Gatsby 会自动添加路由,生成页面。
我们来创建一个 about.js
文件:
import React from 'react'
export default () => (
About destiny
location: SHEN ZHEN
)
然后打开 localhost:8000/about
查看页面
公用组件
在 src
目录下,创建一个 components
文件夹,名字不是固定的,而是约定俗称的,用于放置组件文件。
我们来尝试创建一个 Header
组件。
首先创建 header.js
文件:
import React from 'react'
// Link 是由 Gatsby 封装好的用于跳转链接的组件
import { Link } from 'gatsby'
export default () => (
Destiny'Note
About
)
然后在页面 about
中应用:
import Header from '../components/header'
export default () => (
)
布局组件
我们可以创建一个整体的布局组件,来实现一些公共的样式和逻辑。我们在 components
目录下创建 layout.js
文件
import React from 'react'
import Header from './header'
export default ({ children }) => (
{children}
)
然后在 about.js
和 index.js
文件中应用即可。
// about.js
import React from 'react'
import Layout from '../components/layout'
export default () => (
)
// index.js
import React from 'react'
import Layout from '../components/layout'
export default () => (
)
应用样式
普通样式
在 src
目录下,创建 styles
文件夹,同样是没有约束的。css 文件路径也是随你配置。
只需要在页面组件或者其他组件引入即可
import '../styles/xxx.css'
应用全局样式
我们可以在 styles
目录下创建 global.css
文件:
html {
background-color: lightblue;
}
然后在根目录下,创建一个 gatsby-browser.js
文件,注意这个文件名是固定的:
import "./src/styles/global.css"
// 或者使用 require
// require('./src/styles/global.css')
引入样式文件即可。
设置全局样式功能,主要用于初始化一些默认样式和公共的样式。
实际上,普通的 css 文件引入都是属于全局的,并没有进行 CSS 模块化,后面会讲到。
使用 Sass
你也可以使用 sass 预处理器。
安装依赖
yarn add node-sass gatsby-plugin-sass
如果你无法下载 node-sass,那么可以尝试将你的包管理器的源设置为淘宝源。
yarn config set registry http://registry.npm.taobao.org
然后找到 gatsby-config.js
文件,在 plugins
字段中加上:
plugins: [
//...
`gatsby-plugin-sass`
]
然后直接使用 sass 即可
需要注意的是,安装了新的依赖,以及编辑了根目录下的 js 文件,需要重新启动项目。
CSS Module
这是非常重要的一部分,CSS Module 可以减少全局污染、防止命名冲突和样式的覆盖。CSS Module 即将某个 CSS 文件单独应用于某个组件,不会影响外界其他的样式。
CSS Module 实现原理是:将所有模块化的类名,修改为成唯一的名称,即添加一些不重复的前缀或后缀。这样使得项目中,不同人员编写的样式不会出现覆盖和冲突。
在 Gatsby 中,我们将模块化的 CSS 文件命名为 xxx.module.css
,注意其后缀名。我们只需要按照普通 CSS 文件来编写即可:
.container {
background: red;
margin: 0 auto;
}
然后在组件中引入:
import React from "react"
// 引入这个文件,得到的是所有的类名映射对象
import containerStyles from "../styles/container.module.css"
// 在 HTML 元素上添加类名,需要像下面这样,使用映射后的类名
export default ({ children }) => (
{children}
)
可以打开控制台,来查看生成的类名。
CSS in JS
这又是一个新的知识点,CSS in JS 是在 JS 文件中,使用内联的方式编写样式。与 CSS Module 一样是为了解决 CSS 模块化的问题。
CSS in JS 的原理:在编写了内联样式的元素上,自动添加一个唯一的类名,然后生成一个 CSS 文件,将对应的类名和样式放入其中。
Gatsby 官网中推荐了两个库:Emotion 和 Styled Component。这里选用 Emotion。
在 Gatsby 中,两个写法类似。
安装依赖:
yarn add gatsby-plugin-emotion @emotion/core @emotion/styled
然后再 gatsby-config.js
文件中添加插件:
plugins: [
//...
`gatsby-plugin-emotion`
]
使用:
// header.js
import React from 'react'
import { Link } from 'gatsby'
// 两种写法,一个是用 styled,一个是用 css
import styled from "@emotion/styled"
import { css } from "@emotion/core"
// 模板字符串的写法,实际上就是函数传参
// styled 返回的是一个组件,方便复用元素
const Title = styled.h1`
display: inline;
`
export default () => (
Destiny'Note
)
// css 返回的是样式对象,方便复用样式
const inline = css`
display: inline;
`
export default () => (
Destiny'Note
)
// 或者直接内联编写
export default () => (
Destiny'Note
)
现在,你可以尝试将你的所有组件修改为这样的写法。
Typography 样式排版
Typography.js 是一个 JavaScript 库,可以让你的网站的版式设置为预先存在的版式主题,也可以自定义版式。实际上就是一个应用字体、行高等进行排版的库。
安装依赖:其中最后一个是版式主题,你可以选择自己喜欢的主题。
yarn add gatsby-plugin-typography react-typography typography typography-theme-fairy-gates
在 gatsby-config.js
中引入插件:
plugins: [
//...
// 插件的两种写法,没有配置,就是字符串,需要配置则是一个对象
{
resolve: `gatsby-plugin-typography`,
options: {
pathToConfigModule: `src/utils/typography`, // typography 配置文件路径
},
}
]
在 src
下创建 utils/typography.js
文件:
import Typography from 'typography'
import fairyGateTheme from 'typography-theme-fairy-gates'
// 自定义
const typography = new Typography({
baseFontSize: "18px",
baseLineHeight: 1.666,
headerFontFamily: ["Avenir Next", "sans-serif"],
bodyFontFamily: ["Georgia", "serif"],
})
// 使用主题
const typography = new Typography(fairyGateTheme)
export default typography
重新启动项目,即可查看现在的页面排版样式了。
具体的 API 可查看官网
获取 GraphQL 中的数据
首先我们在 gatsby-config.js
文件中创建 siteMatedata
:
module.exports = {
siteMetadata: {
title: `Destiny'Note`,
author: `destiny`
},
plugins: [
//...
]
}
在组件中,我们应该如何获取数据呢?在 Gatsby 中,需要分成两种形式。
页面组件获取数据
页面组件中,我们需要调用 Gatsby 提供的 graphql 方法来查询数据:Gatsby 会自行处理
// about.js
import React from 'react'
import Layout from '../components/layout'
import {graphql} from 'gatsby'
// GraphQL 获取的数据,会当做参数传递到页面组件中
// 数据的形式是 {errors, data},没有错误则不会有 errors
export default ({data}) => (
About ${data.site.siteMetadata.author}
location: SHEN ZHEN
)
// 调用 graphql 方法获取数据,返回的是 Promise
// 变量名没有规定
export const query = graphql`
query {
site {
siteMetadata {
title
}
}
}
`
非页面组件获取数据
非页面组件获取数据则需要结合两个 API:useStaticQuery 和 graphql。
// header.js
import React from 'react'
import { Link, useStaticQuery, graphql } from 'gatsby'
import { css } from '@emotion/core'
export default () => {
// 使用 useStaticQuery 包裹 graphql 查询,实际上相当于 async/await,等待返回数据
const data = useStaticQuery(graphql`
query {
site {
siteMetadata {
title
}
}
}
`)
return (
{data.site.siteMetadata.title}
About
)
}
获取文件数据
Gatsby 可以通过一些源(source)插件将各方面的数据转换为本地能够通过 GraphQL 提取的内容。比如 gatsby-source-filesystem、gatsby-source-wordpress 等。主要有本地的文件以及其他内容管理系统中的数据,可以用于数据的迁移。这里只讲解本地文件数据的获取。
gatsby-source-filesystem
安装插件:
yarn add gatsby-source-filesystem
然后添加插件和配置:
plugins: [
//...
{
resolve: 'gatsby-source-filesystem',
options: {
path: `${__dirname}/src`,// 文件路径
name: 'src', // 名称,可以用来过滤
ignore: [] // 可选的,忽略的文件
}
},
// 如果需要再获取第二个文件夹,可以再设置一遍
{
resolve: 'gatsby-source-filesystem',
options: {
path: `${__dirname}/src/_posts`,// 文件路径
name: 'posts' // 名称,可以用来过滤
}
}
]
这个插件为 GraphQL 添加了 allFile 和 file 两个数据字段,其中包含有很多的字段,可以自行查看 graphiql。
通过 sourceInstanceName
来过滤一开始设定的 name
:
具体的用法可以查看 文档
此时,我们就可以通过 GraphQL 来获取文件的一些数据了。
利用文件数据创建页面
我们来创建一个页面 my-file.js
:展示一个表示,查看当前 src
下文件信息
import React from 'react'
import { graphql } from 'gatsby'
export default ({ data }) => (
My Site's Files
relativePath
prettySize
extension
birthTime
{data.allFile.edges.map(({ node }, index) => (
{node.relativePath}
{node.prettySize}
{node.extension}
{node.birthTime}
))}
)
export const query = graphql`
query {
allFile(filter: { sourceInstanceName: { eq: "src" } }) {
edges {
node {
relativePath
prettySize
extension
birthTime(fromNow: true)
}
}
}
}
`
转换器插件
通常,从源插件获取的数据格式不是你需要的格式。Gatsby 支持转换器插件,这些转换器插件从源插件中获取原始内容并将其转换为更有用的东西。
例如,Markdown 文件,当用它构建页面时,需要转换为 HTML。
gatsby-transformer-remark
gatsby-transformer-remark 的作用是将 filesystem 转换的数据中,将 md 文件的数据抽取出来,进行一定的转换再存储到 GraphQL 中。
安装插件:
yarn add gatsby-transformer-remark
添加插件:
plugins: [
//...
{
resolve: `gatsby-transformer-remark`,
options: {
"excerpt_separator": `` // 设置摘要分隔符
}
}
]
这个插件添加了 allMarkdownRemark 和 markdownRemark 两个数据字段
只使用这个转换器可能是不够的,可以查看官网查找其他 插件,如:mdx、其他 remark 等等
创建示例文章
在 src
下创建 _posts
目录,添加一个 first.md
文件:
---
title: Hello World
date: 2019-11-07
---
# Hello World
hello, destiny
```js
function info() {
return {
name: 'destiny',
age: 16
}
}
```
文件开头的 ---
包裹的是 frontmatter
同样再创建一个 second.md
文件。
展示在首页
获取文章数据展示在首页
// index.js
import React from 'react'
import Layout from '../components/layout'
import { Link, graphql } from 'gatsby'
import { css } from '@emotion/core'
export default ({ data }) => (
{data.allMarkdownRemark.totalCount} Posts
{data.allMarkdownRemark.edges.map(({ node }) => (
{node.frontmatter.title}{' '}
— {node.frontmatter.date}
{node.excerpt}
))}
)
// 这里对数据根据 frontmatter 中的 date 进行排序
export const query = graphql`
query {
allMarkdownRemark(sort: { fields: [frontmatter___date], order: DESC }) {
totalCount
edges {
node {
id
frontmatter {
title
date
}
fields {
slug
}
excerpt
}
}
}
}
`
编程式创建页面
我们不可能为每一篇文章都在 pages
下创建一个文件,因此我们需要编写自动创建页面的代码,Gatsby 提供了两个 API:onCreateNode
、createNodeField
、createPages
和 createPage
。
在根目录下创建 gatsby-node.js
文件,名称是固定的。这里进行一些逻辑的编写。
分为两步:
- 生成
path
或者slug
作为路径 - 生成根据路径创建页面
const path = require('path')
const { createFilePath } = require(`gatsby-source-filesystem`)
// 每当 GraphQL 数据中创建(或更新)新节点时,Gatsby 都会调用此函数
// 注意,这个名称是固定的
exports.onCreateNode = ({ node, getNode, actions }) => {
const { createNodeField } = actions
// 在 GraphQL 数据中创建的节点类型是 MarkdownRemark
if (node.internal.type === `MarkdownRemark`) {
// 根据文件名创建 slug
let slug = createFilePath({ node, getNode, basePath: `posts` })
// 通过 createNodefield 在当前字段的 fields 下创建一个数据字段
createNodeField({
node,
name: `slug`,
value: slug,
})
}
}
// 创建页面,这个名称是固定的
exports.createPages = async ({ graphql, actions }) => {
// 获取到 createPage 方法
const { createPage } = actions
// 查询所有 md 文件数据
const result = await graphql(`
query {
allMarkdownRemark {
edges {
node {
fields {
slug
}
frontmatter {
path
}
}
}
}
}
`)
// 逐个创建相应的页面
result.data.allMarkdownRemark.edges.forEach(({ node }) => {
let path = node.frontmatter.path || node.fields.slug; // 可以使用自定义路径
createPage({
path,
// 创建页面需要模板组件
component: path.resolve(`./src/templates/post.js`),
context: {
// 传递给模板组件中在查询时, 接收的变量值
path
},
})
})
}
模板组件
所有文章使用相同的模板显示。在 src
目录下,创建 templates
文件夹,添加 post.js
:
import React from 'react'
export default function Post({data}) {
const {markdownRemark: post} = data
return (
{post.frontmatter.title}
)
}
// $path 是生成页面时的 context 中带上的变量,名称前面加上 $ 符
export const postQuery = graphql`
query ($path: String!) {
markdownRemark(frontmatter: { path: { eq: $path } }) {
html
frontmatter {
path
title
}
}
}
`
优化
添加 PWA 支持
需要两个插件:gatsby-plugin-manifest
和 gatsby-plugin-offline
。
安装依赖:
yarn add gatsby-plugin-manifest gatsby-plugin-offline
添加插件:
plugins: [
//...
{
resolve: `gatsby-plugin-manifest`,
options: {
name: `Destiny'Note`,
short_name: `Destiny`,
start_url: `/`,
background_color: `#6b37bf`,
theme_color: `#6b37bf`,
display: `standalone`,
icon: `src/images/icon.png`,
},
},
`gatsby-plugin-offline` // 这个插件必须在 manifest 后面
]
添加相应的 icon.png
SEO 优化
添加 metadata
安装依赖:
yarn add gatsby-plugin-react-helmet react-helmet
在 gatsby-config.js
中添加 siteMetadata
和插件:
{
siteMetadata: {
// 这三个属性必须要有
title: `Destiny'Note`,
author: `destiny`,
description: `my own blog`,
keywords: `react, graphql, gatsby`
},
plugins: [
//...
`gatsby-plugin-react-helmet`
]
}
在 src/components
下增加 seo.js
文件:
import React from "react"
import PropTypes from "prop-types"
import Helmet from "react-helmet"
import { useStaticQuery, graphql } from "gatsby"
function SEO({ description, lang, meta, keywords, title }) {
const { site } = useStaticQuery(
graphql`
query {
site {
siteMetadata {
title
description
author
}
}
}
`
)
const metaDescription = description || site.siteMetadata.description
const metaKeywords = keywords.join(',') || site.siteMetadata.keywords
return (
)
}
// 默认配置
SEO.defaultProps = {
lang: `zh-CN`,
meta: [],
description: ``,
}
SEO.propTypes = {
description: PropTypes.string,
lang: PropTypes.string,
meta: PropTypes.arrayOf(PropTypes.object),
title: PropTypes.string.isRequired,
}
export default SEO
然后在所有的页面中添加相应的 SEO 组件。
// post.js
import SEO from '../components/seo'
export default ({ data }) => {
const { markdownRemark: post } = data
return (
)
}
export const query = graphql`
query($slug: String!) {
markdownRemark(fields: { slug: { eq: $slug } }) {
html
frontmatter {
title
}
excerpt
}
}
`
通过 Chrome 的 LightHouse 进行性能查看
先打包文件:
yarn build
然后启动生产代码:
yarn serve
打开 localhost:9000
进行查看
打开控制台,点击 Audit
栏:
选中所有 Audits 项,然后点击 Run audits,等待一会就能看到你的审核界面。
Gatsby Imgae 优化
基本功能
在 Gatsby 中,你可以这样引入图片:
- 使用相对路径
- 利用
static
文件夹,因为静态文件夹下的所有文件都会直接复制到 public 下,所以路径可以直接将 static 作为根目录来引用图片 - 使用 webpack import 图片
import logo from "./logo.png"
console.log(logo); // 是一个经过 webpack 编译后的路径
function Header() {
return
}
下面介绍 Gatsby Image ,它帮助我们优化图片,从而在页面中更快的显示。它会在 public/static
下生成各种不同分辨率的压缩图片,会按需加载这些图片。
依赖和配置
首先安装依赖:
yarn add gatsby-image gatsby-transformer-sharp gatsby-plugin-sharp
然后在 gatsby.config.js
中引入插件:
plugins: [
`gatsby-transformer-sharp`,
`gatsby-plugin-sharp`,
{
resolve: `gatsby-source-filesystem`,
options: {
path: `./static/assets/`, // 图像存在的位置,我们统一修改为 ./static/assets/ 中
name: `image`
}
}
]
BUG
安装依赖的过程中,可能会出现 pngquant pre-build test failed,这是因为 gatsby-plugin-sharp 依赖于一个叫 pngquant-bin 的包,这个包下载出现问题。
可以参考下面的 Issue:
其中大部分出现问题都是在 Linux 系统下。一开始大部分问题出现原因是:Use raw.githubusercontent.com instead of raw.github.com for binaries #99,这个 PR 已经合并到源码中,形成了 5.0.2 版本,貌似解决了一些人的问题。但是,后面仍然有大量提交 issue 都是 5.0.2 出现这样的问题。
在 Linux 下,可能安装 libpng-dev 就能解决,可以查看 Gatsby on Linux
在 Windows 下,可以查看 Gatsby On Windows
在 Mac 下,暂时没有教程指导。可以尝试以下步骤:
- 先安装其他不依赖 pngquant-bin 的包,最后安装依赖 pngquant-bin 的包。
- 安装最后一个包时,会报错:pngquant pre-build test failed。或者,你到下载完成,在编译的时候停止下来
- 然后打开
node_modules/pngquant-bin/lib/index.js
- 再次安装,注意在运行过程中,达到上图阶段时,修改找到的
index.js
文件,将githubusercontent
改为github
现在就已经完成了,可以直接运行项目了。但是,后面再次安装这个包时,仍然会存在这样的问题。
查询图像
export const query = graphql`
query {
file(relativePath: { eq: "headers/headshot.jpg" }) {
childImageSharp {
fixed(width: 125, height: 125) {
...GatsbyImageSharpFixed
}
fluid {
...GatsbyImageSharpFluid
}
}
}
}
`
- 相对路径是指相对于配置中
path
路径的基础上 - 使用
GatsbyImageSharpFixed
返回固定的宽度和高度图片 - 使用片段
GatsbyImageSharpFluid
返回自适应的图片,这些图片将填充其容器而不是适合特定尺寸
显示图片
import Img from "gatsby-image"
export default ({data}) => (
)
具体可查看教程:
部署
surge
安装 surge:
npm install -g surge
登陆或者创建账号:
surge login
会让你输入邮箱和密码,如果没有账号,会自行创建。
打包你的代码之后:
surge public/ # 发布 public 文件夹下的文件
# 或者
cd public
surge
然后,出现 project 和 domain,如果停留在 project,记得敲回车。出现 domain 之后,这个 domain 是随机出现的,可以修改这个 domain,格式必须是 xxx.surge.sh。然后回车、回车、回车。
我第一次使用的时候,以为是自动化的,完全没有提示和说明,然后一直以为卡住了。进入那个域名也不是自己的网页,所以,记得回车。
回车之后如果域名有人部署了,会有提示并失败。
GitHub Pages
GitHub Pages 则需要提交到 GitHub 仓库,将部署代码发布到 gh-page 分支
yarn build
cd public
git init
git add -A
git commit -m 'update'
git push -f [email protected]:/.git master:gh-pages
然后在仓库中进行点击 Settings
, 找到 GitHub Pages,选择 Source 为 gh-pages 分支,确定即可。
Netlify
Netify 用于部署以及内容管理系统。你可以将网页部署到 Netlify,还可以发布你的 CMS 系统,随时登陆到你部署的 CMS 上发布和修改文章,而不需要在本地代码库中发布。
安装依赖:
yarn add netlify-cms-app gatsby-plugin-netlify-cms
添加插件:
plugins: [
`gatsby-plugin-netlify-cms`,
//...
]
插件配置具体查看 官网
然后,在 static
目录下,创建 admin/config.yml
文件:
backend:
name: github
repo: /
media_folder: static/assets # 媒体文件,一般是图片的存储路径
public_folder: assets # 媒体文件夹名称
display_url: https://your-website.cn # CMS 上展示一个指向你的网站的 URL
collections: # 收藏集,CMS 的侧边栏分类
- name: blog
label: Blog
folder: src/posts
create: true # 允许新增
fields: # 编辑文件时,需要填写的数据,会将其放在 frontmatter 中
- { name: path, label: Path, required: false }
- { name: date, label: Date, widget: date }
- { name: title, label: Title }
- { name: tags, label: Tags, widget: list}
- { name: categories, label: Categories, widget: list}
- { name: body, label: Body, widget: markdown }
具体配置查看 Netlify 配置文件
开发环境下,你可以在 localhost:8000/admin
中查看你的 CMS 界面。
最后将代码打包上传到 GitHub 上,到 app.netlify.com 上注册账号,然后创建新的网站,关联你的 GitHub 的仓库,将项目都部署到 Netlify 上。
之后,你需要获得一个 GitHub OAuth token,进入页面之后,点击 New OAuth app。
注意:
HomePage URL 可以设置为你的部署地址,这个没有硬性要求。但是,最后的 callback URL 必须设置为https:api.netlify.com/auth/done
,否则在 CMS 页面,无法进行登陆。
设置完成之后,点击进去,找到 clientID 和 clientScret。
然后在 Netlify 中,你部署的项目的设置界面,找到 Access Control,点击 Install Provider,然后粘贴 clientID 和 clientScret。
之后,即可访问你的 CMS 管理界面。