開發現代化的靜態 PWA
黃胤翔 (Ben)
黃胤翔 (Ben)
- SHOPLINE Sr. Frontend Engineer
- 飛肯設計學苑兼課講師
- COSCUP 2017 分享 ReactXP
- 新手貓奴
- 樂於
推坑助人
大綱
- 聊聊為什麼需要新工具 (GatsbyJS)
- 想辦法推你坑
- 分享一下我怎麼用
- 比較看看其他的工具
3. 連聽都沒聽過
可能被推坑、打算下次開發時用
1. 正使用 Gatsby 開發
你並不孤單,有機會還能參考一下我的經驗
2. 正在 Gatsby 與其他工具間做選擇
更清楚自己的需求,並找到最適合你的工具
動機
- 心血來潮寫個開源 Side Project
- 架部落格,準備拿來寫技術文章放履歷
- 接案賺錢
需求
- Landing Page
- SEO
- 用最短時間結案領錢
- 想寫 React、用一些潮潮工具
GatsbyJS
- Static Site Generator 的一種
- Based on React and GraphQL
- 完美支援各種 Headless CMS
- 整合 / 使用各種現代化開發工具
- 遵循 PRPL Pattern
- Hexo
- Jekyll, Octopress
- Hugo
- VuePress
- Middleman
- Pelican
現代化?
Modern web development bundles advances in performance (bundle splitting, asset prefetching, offline support, image optimization, or server side rendering), developer experience (componentization via React, transpilation via Babel, webpack, hot reloading), accessibility, and security together.
https://www.gatsbyjs.org/docs/gatsby-core-philosophy/
Better Performance, Better DX tools or tech
https://www.datocms.com/blog/static-ecommerce-website-snipcart-gatsbyjs-datocms/
On demand
Traditional SSR
每個 HTTP Request 都會回傳一個唯一的頁面,適合內容有高度動態需求的網站
Static export
預先把內容渲染出來並輸出到 html 檔,可以放到任何 Static Hosting 而不需要 Application Server
兩種 Pre-rendering
起手式
# Create new project
gatsby new [SITE_DIRECTORY] [URL_OF_STARTER_GIT_REPO]
# Run locally
gatsby develop
Official Starters
功能 | 適用情境 | |
gatsby-starter-default (Default) | 安裝基本的設定和樣板 | 適用大部分的情境 |
gatsby-starter-blog | 安裝 Blog 所需的文章列表與文章內頁 | 架 Blog |
gatsby-starter-hello-world | 僅安裝 Gatsby 核心部分 | 研究或開發用途 |
https://www.gatsbyjs.org/starters
Starters from community
Gatsby 專案架構
/
|-- /.cache
|-- /plugins
|-- /public # Build 完後的檔案都會放在這(自動產生)
|-- /src
|-- /pages # 根據檔名建立路由規則
|-- /templates # 建立動態頁面
|-- html.js
|-- /static
|-- gatsby-config.js
|-- gatsby-node.js
|-- gatsby-ssr.js
|-- gatsby-browser.js
Data Layer
- 使用 GraphQL 定義每個 Component 所需要的資料
- Page query
- StaticQuery
Page query
import { graphql } from 'gatsby'
export default ({ data }) => (
<>
{
data.allPost.edges.map(({ node }) => (
<div key={node.id}>
<Link to={node.slug}>
<h3>{node.title}</h3>
</Link>
</div>
))
}
</>
)
export const query = graphql`
query {
allPost(
sort: { fields: [createdAt] order: [DESC] }
) {
edges {
node {
id
slug
title
}
}
}
}
`
src/pages/post.js
StaticQuery
export default props => {
return (
<StaticQuery
query={graphql`
query {
allProduct(
limit: 10,
filter: { qty: { gt: 0 } },
sort: { fields: [order, createdAt] order: [DESC, DESC] }
) {
edges {
node {
id
slug
name
product_tags {
id
name
group_number
}
}
}
}
}
`}
render={({ allProduct }) => (
<>{/* render UI by renderProps */}</>
)}
/>
)
}
src/components/ProductBanner.js
Packages/Plugins
- Source
- Transformer
- Others
(gatsby-plugin-sharp, gatsby-image...)
常用的
- gatsby-image
- gatsby-plugin-sharp
- gatsby-source-filesystem
- gatsby-plugin-offline
- gatsby-plugin-manifest
- gatsby-plugin-feed
- gatsby-plugin-react-helmet
客製化你的 Gatsby
- gatsby-config.js
- gatsby-node.js
- gatsby-browser.js
- gatsby-ssr.js
module.exports = {
siteMetadata: {
title: 'ModernWeb 形象官網',
description: 'ModernWeb 2019',
author: 'Ben',
},
plugins: [
`gatsby-plugin-react-helmet`,
{
resolve: `gatsby-source-filesystem`,
options: {
name: `images`,
path: `${__dirname}/src/images`,
},
},
`gatsby-transformer-sharp`,
`gatsby-plugin-sharp`,
`gatsby-plugin-sass`
],
}
gatsby-config.js
使用場景
Code Snippet
https://www.gatsbyjs.org/packages/gatsby-remark-prismjs
Gatsby + Prism.js
https://www.gatsbyjs.org/packages/gatsby-transformer-remark
tomorrow theme
// import at top level
import 'prismjs/themes/prism-tomorrow.css'
// Use line numbers
import 'prismjs/plugins/line-numbers/prism-line-numbers.css'
// require at gatsby-browser.js
require('prismjs/themes/prism-tomorrow.css')
Styling
- Global CSS
- Modular Stylesheets
- CSS-in-JS
CSS Modules
.container {
display: flex;
margin: 3rem auto;
}
src/components/container.module.css
src/components/container.js
import React from 'react'
import containerStyles from './container.module.css'
export default ({ children }) => (
<section className={containerStyles.container}>{children}</section>
)
Emotion
module.exports = {
plugins: ['gatsby-plugin-emotion'],
}
gatsby-config.js
src/pages/index.js
import styled from '@emotion/styled'
import { css } from '@emotion/core'
const Container = styled.div`
display: flex;
margin: 3rem auto;
`
const underline = css`
text-decoration: underline;
`
export default () => (
<Container>
<h1 css={underline}>Emotion 棒棒</h1>
</Container>
)
styled-components
module.exports = {
plugins: ['gatsby-plugin-styled-components'],
}
gatsby-config.js
src/pages/index.js
import React from 'react'
import styled from 'styled-components'
import User from '../components/User'
const Container = styled.div`
display: flex;
margin: 3rem auto;
`
export default () => (
<Container>
<User name="Ben" />
<User name="Elvis" />
</Container>
)
動態生成頁面
slug === '暫停出貨通知'
Template
import React from "react"
import NewsCover from "../components/banners/newsCover"
import NewsDetail from "../components/banners/newsDetail"
import SEO from "../components/seo"
export default ({ data }) => (
<>
<SEO title={data.contentfulPost.title} keywords={['最新消息', 'ModernWeb', 'Gatsby', 'Contentful']} />
<NewsCover />
<NewsDetail data={data.contentfulPost} />
</>
)
export const query = graphql`
query($id: String!) {
contentfulPost(id: { eq: $id }) {
id
title
body {
json
}
createdAt
covers {
sizes(maxWidth: 1000) {
...GatsbyContentfulSizes_withWebp
}
}
}
}
`
src/templates/news.js
exports.createPages = ({ graphql, actions }) => {
const { createPage } = actions
const promises = [
new Promise((resolve, reject) => {
const template = path.resolve('src/templates/news.js')
resolve(
graphql(
`
{
allContentfulPost {
edges {
node {
id
slug
}
}
}
}
`
).then(result => {
if (result.errors) reject(result.errors)
result.data.allContentfulPost.edges.forEach(({ node }) => {
createPage({
path: `/news/${node.slug}`,
component: template,
context: {
id: node.id,
},
})
})
})
)
})
]
return Promise.all(promises)
}
gatsby-node.js
Troubleshooting
小心 Browser globals
const module = typeof window !== `undefined` ? require("module") : null
Build Failed
rm -rf .cache
- 無法正常從 Data Source 獲取資料
- Plugin 沒有被正確調用
Github + Contentful + Netlify
plugins: [
{
resolve: 'gatsby-source-contentful',
options: {
spaceId: 'your_space_id_grab_it_from_contentful',
accessToken: 'your_token_id_grab_it_from_contentful',
},
},
]
gatsby-config.js
export const query = graphql`
query($id: String!) {
allContentfulProductTag(sort: { fields: [group_number, order] order: [ASC, DESC] }) {
edges {
node {
id
name
group_number
}
}
}
}
`
Search
- 數量小做在前端
- 數量大或全文搜尋再考慮用 Elasticsearch
?keyword=外套&tags=CHAMPION
const getFromSearch = data => {
let search = queryString.parse(window.location.search, { arrayFormat: 'comma' })
if (!Array.isArray(search.tags)) {
search = { ...search, tags: [search.tags] }
}
return {
keyword: search.keyword,
tags: data.allContentfulProductTag.edges.reduce((tags, cur) => {
if (search.tags.find(nameOfTag => nameOfTag === cur.node.name)) return tags.concat(cur)
return tags
}, [])
}
}
useEffect(() => {
const { tags: initTags, keyword: initKeyword } = getFromSearch(data)
setSelectedTags(initTags)
setKeyword(initKeyword)
window.onpopstate = function(event) {
const { tags: newTags, keyword: newKeyword } = getFromSearch(data)
setSelectedTags(newTags)
setKeyword(newKeyword)
}
setDidMount(true)
return () => {
setSelectedTags(false)
setKeyword(false)
window.onpopstate = null
}
}, [(typeof window === 'undefined') ? null : window.location.href])
Gatsby 適合你嗎
有什麼選擇?
- 自幹 SSR
- React Static
- Next.js
- 其他能寫 React 的 Static Site Generator?
如果兩者相比,Gatsby 更適合拿來快速開發靜態頁面,Next 則適合拿來開發大型架構
擇你所需
歡迎面聊
開發現代化的靜態 PWA
By hinx
開發現代化的靜態 PWA
Let's build a modern static PWA with GatsbyJS - ModernWeb2019
- 1,678