有尝试过基于各种工具搭建 Web 网站和应用,可以在幽居空谷轩,查看具体效果;对于这些工具,有在如何选定搭建个人独立博客工具做过记录,因为这些工具使用起来,相对都颇为简单,就只是做了简单的介绍。前段时间,因为工作需要,需要搭建一款在线文档,考虑到自定义程度较高,且要具备全文检索能力,权衡之下有选择了 GatsbyJS。GatsbyJS,不仅用到了 React 和 GraphQL,而且融入了更多设计理念,使得入门相对复杂。在此将之前使用心得,分享给需要的朋友们。
Gatsby is a free and open source framework based on React that helps developers build blazing fast websites and apps.
推荐语:GatsbyJS 是基于 React 的免费开源框架,可帮助开发人员构建快速的网站和应用;是可以用于快速浏览网站的现代 Web 框架:
超越静态网站。不受限制地获得静态网站的所有好处。Gatsby 网站是功能齐全的 React 应用程序,因此您可以创建高质量,动态的 Web 应用程序,从博客到电子商务网站再到用户仪表板。
对每个站点使用现代堆栈。无论数据来自何处,Gatsby 站点都是使用 React 和 GraphQL 构建的。无论数据是否来自同一后端,都为您和您的团队构建统一的工作流程。
从任何地方加载数据。Gatsby 可以从任何数据源中提取数据,无论是 Markdown 文件,Contentful 或 WordPress 之类的无头 CMS,还是 REST 或 GraphQL API。使用源插件来加载数据,然后使用 Gatsby
的统一 GraphQL
接口进行开发。
性能得到提高。Gatsby 可以自动执行代码拆分,图像优化,内联关键样式,延迟加载和预取资源等操作,以确保您的网站快速运行-无需手动调整。
便于部署托管。Gatsby 站点不需要服务器,因此您可以将整个站点托管在 CDN 上,而成本仅为服务器渲染站点的一小部分。许多 Gatsby 网站可以完全免费托管在 GitHub Pages 和 Netlify 等服务上。── 出自倾城之链 | GatsbyJS。
gatsby-config.js 配置
// gatsby-config.js
module.exports = {
siteMetadata: {
siteUrl: 'https://www.yourdomain.tld',
title: '您的应用名称',
},
plugins: [
'gatsby-plugin-sass',
'gatsby-plugin-react-helmet',
'gatsby-plugin-image',
{
resolve: 'gatsby-plugin-google-analytics',
options: {
trackingId: 'G-XXXXYYYY',
},
},
'gatsby-plugin-react-helmet',
'gatsby-plugin-sitemap',
{
resolve: 'gatsby-plugin-manifest',
options: {
icon: 'src/images/icon.png',
},
},
{
resolve: `gatsby-transformer-remark`,
options: {
plugins: [
{
resolve: `gatsby-remark-table-of-contents`,
options: {
exclude: 'Table of Contents',
tight: false,
ordered: false,
fromHeading: 2,
toHeading: 4,
className: 'table-of-contents',
},
},
`gatsby-remark-autolink-headers`,
],
},
},
'gatsby-plugin-mdx',
'gatsby-plugin-sharp',
'gatsby-transformer-sharp',
{
resolve: 'gatsby-source-filesystem',
options: {
name: 'images',
path: './src/images/',
},
__key: 'images',
},
{
resolve: 'gatsby-source-filesystem',
options: {
name: 'pages',
path: `${__dirname}/src/`,
},
__key: 'pages',
},
{
resolve: 'gatsby-plugin-no-sourcemaps',
},
],
}
gatsby-node.js 配置
// gatsby-node.js
const path = require(`path`)
const { execSync } = require('child_process')
const marked = require('marked')
const striptags = require(`striptags`)
const { GraphQLJSONObject } = require(`graphql-type-json`)
const { createFilePath } = require(`gatsby-source-filesystem`)
const lunr = require('lunr')
require('./src/helper/lib/lunr.stemmer.support.js')(lunr)
require('./src/helper/lib/lunr.zh.js')(lunr)
exports.onCreateNode = ({ node, getNode, actions }) => {
const { createNodeField } = actions
if (node.internal.type === `MarkdownRemark`) {
const slug = createFilePath({ node, getNode, basePath: `pages` })
createNodeField({
node,
name: `slug`,
value: slug,
})
const gitAuthorTime = execSync(
`git log -1 --pretty=format:%aI ${node.fileAbsolutePath}`
).toString()
createNodeField({
node,
name: 'gitAuthorTime',
value: gitAuthorTime,
})
}
}
exports.createPages = async ({ graphql, actions }) => {
const { createPage } = actions
const result = await graphql(`
query {
allMarkdownRemark {
edges {
node {
fields {
slug
gitAuthorTime
}
}
}
}
}
`)
result.data.allMarkdownRemark.edges.forEach(({ node }) => {
const slug = node.fields.slug
createPage({
path: slug,
component: path.resolve(`./src/templates/blog.js`),
context: {
// Data passed to context is available
// in page queries as GraphQL variables.
slug,
},
})
})
}
exports.createResolvers = ({ cache, createResolvers }) => {
createResolvers({
Query: {
LunrIndex: {
type: GraphQLJSONObject,
resolve: (source, args, context, info) => {
const docsNodes = context.nodeModel.getAllNodes({
type: `MarkdownRemark`,
})
const type = info.schema.getType(`MarkdownRemark`)
return createIndex(docsNodes, type, cache)
},
},
},
})
}
const createIndex = async (docsNodes, type, cache) => {
const cacheKey = `IndexLunr`
const cached = await cache.get(cacheKey)
if (cached) {
return cached
}
const documents = []
const store = {}
// iterate over all posts
for (const node of docsNodes) {
const { slug } = node.fields
const title = node.frontmatter.title
const tag = node.frontmatter.tag
const content = striptags(marked.parse(node.rawMarkdownBody))
documents.push({
slug,
title,
tag,
content,
})
store[slug] = {
title,
tag,
content,
}
}
const index = lunr(function () {
this.use(lunr.zh)
this.ref('slug')
this.field('title')
this.field('tag')
this.field('content')
for (const doc of documents) {
this.add(doc)
}
})
const json = { index: index.toJSON(), store }
await cache.set(cacheKey, json)
return json
}
blog.js 模版
// blog.js
import React from 'react'
import { graphql } from 'gatsby'
import Seo from '../components/seo'
import Header from '../components/header'
import Layout from '../components/layout'
import Elevator from '../components/elevator'
import Footer from '../components/footer'
import { setBaseData } from '../helper/utils.js'
const Post = ({ data, location }) => {
setBaseData(data)
const post = data.markdownRemark
return (
<main>
<Seo />
<Header pathname={location.pathname} />
<Layout updatetime={post.fields.gitAuthorTime} hash={decodeURI(location.hash)}>
<div className="wrapper">
<div className="content" dangerouslySetInnerHTML={{ __html: post.html }} />
</div>
<Footer />
</Layout>
<Elevator />
</main>
)
}
export default Post
export const query = graphql`
query ($slug: String!) {
markdownRemark(fields: { slug: { eq: $slug } }) {
html
fields {
gitAuthorTime
slug
}
}
LunrIndex
}
`
SEO 组件
// components/seo.js
import React from 'react'
import { Helmet } from 'react-helmet'
import { useStaticQuery, graphql } from 'gatsby'
const SEO = ({ description, meta, title }) => {
const { site } = useStaticQuery(
graphql`
query {
site {
siteMetadata {
title
description
}
}
}
`
)
const metaDescription = description || site.siteMetadata.description
return (
<Helmet>
<meta charSet="utf-8" />
<title>{site.siteMetadata.title}</title>
<meta name="description" content={metaDescription}></meta>
<meta name="viewport" content="width=device-width,initial-scale=1,user-scalable=no"></meta>
</Helmet>
)
}
export default SEO