Photo by Markus Spiske on Unsplash
建立專案
請先確認nodejs版本在v10以上。
先global安裝gatsby-cli
$ yarn global add gatsby-cli
若是成功安裝gatsby-cli,在終端機輸入gatsby --version
,會出現Gatsby CLI version,例如:
$ Gatsby CLI version: 2.12.63
接著複製以下指令:
$ gatsby new my-blog-starter https://github.com/gatsbyjs/gatsby-starter-blog
其中my-blog-starter為專案名稱,你可以改成自己喜歡的名字。 後面的網址為gatsby官方提供的部落格環境懶人包。
下載完成後,先將專案跑起來看看:
$ cd my-blog-starter
$ gatsby develop
若是看到以下畫面,代表專案成功運行:
建立Typescript環境
首先,先下載幾個必要的dependencies:
$ yarn add gatsby-plugin-typescript
$ yarn add typescript --dev
gatsby-plugin-typescript會將Gatsby環境中的.js
編譯成.tsx
檔。
打開gatsby-config.js
,拉到plugins: []
的最後一行,將gatsby-plugin-typescript
添加到plugins裡面。
// gatsby-config.js
module.exports = {
plugins: [
// default plugins...
{
resolve: `gatsby-plugin-typescript`, options: { isTSX: true, // defaults to false jsxPragma: `jsx`, // defaults to "React" allExtensions: true, // defaults to false }, },
],
}
接著,在根目錄的位置,新增兩個檔案,分別是.eslintrc.js
與tsconfig.json
。
.eslintrc.js
是校正ts語法/格式的設定檔:
// .eslintrc.js
module.exports = {
parser: "@typescript-eslint/parser", // Specifies the ESLint parser
extends: [
"eslint:recommended",
"plugin:react/recommended",
"plugin:@typescript-eslint/recommended",
"prettier/@typescript-eslint",
"plugin:prettier/recommended",
],
settings: {
react: {
version: "detect",
},
},
env: {
browser: true,
node: true,
es6: true,
},
plugins: ["@typescript-eslint", "react"],
parserOptions: {
ecmaFeatures: {
jsx: true,
},
ecmaVersion: 2018, // Allows for the parsing of modern ECMAScript features
sourceType: "module", // Allows for the use of imports
},
rules: {
"react/prop-types": "off", // Disable prop-types as we use TypeScript for type checking
"@typescript-eslint/explicit-function-return-type": "off",
},
overrides: [
// Override some TypeScript rules just for .js files
{
files: ["*.js"],
rules: {
"@typescript-eslint/no-var-requires": "off", //
},
},
],
}
tsconfig.json
是檢查typescript語法的設定檔:
// tsconfig.json
{
"compilerOptions": {
"module": "commonjs",
"target": "esnext",
"jsx": "preserve",
"lib": ["dom", "esnext"],
"strict": true,
"noEmit": true,
"isolatedModules": true,
"esModuleInterop": true,
"noUnusedLocals": false,
"allowJs": true
},
"exclude": ["node_modules", "public", ".cache"]
}
在.eslintrc.js
中,我們會用到@typescript-eslint
這個套件,因此還要下載dependencies:
$ yarn add -D @typescript-eslint/eslint-plugin @typescript-eslint/parser eslint
最後一件事,在package.json中,貼上一個指令,做全專案的typescript型別檢查:
"scripts": {
// ...
"type-check": "tsc --noEmit"
}
到目前為止,環境設定就告一個段落。先commit,接下來會將所有.js
檔修改成.tsx
。
改寫成typescript
src/components/bio.js
將副檔名改成.tsx
。
src/components/layout.js
將副檔名改成.tsx
。
將const Layout = ({ location, title, children }) =>
改成,以Typescript的語法宣告
interface Props { location: Location title: string}
const Layout: React.FC<Props> = ({ location, title, children }) => { const rootPath: string = `${__PATH_PREFIX__}/`
}
其中,const rootPath = '${__PATH_PREFIX__}/'
當中的__PATH_PREFIX__
是Gatsby的global變數。雖然tslint有紅色底線的警告,但不影響程式運行。
src/components/seo.js
將副檔名改成.tsx
。
改成tsx後,import { Helmet } from "react-helmet"
隨即出現了紅色底線毛毛蟲。
由於不是所有套件都有使用typescript,所以我們要在專案的根目錄新增一個檔案node_modules.d.ts
。並貼上下列程式碼:
// node_modules.d.ts
declare module "react-helmet"
接著,繼續將宣告的語法改成typescript:
interface Props { description?: string lang?: string meta?: [] title: string}
const SEO: React.FC<Props> = ({ description = "", lang = "en", meta = [], title = "",}) => {
const { site } = useStaticQuery(
graphql`
query {
site {
siteMetadata {
title
description
social {
twitter
}
}
}
}
`
)
const metaDescription = description || site.siteMetadata.description
return (
<Helmet
htmlAttributes={{
lang,
}}
title={title}
titleTemplate={`%s | ${site.siteMetadata.title}`}
meta={[
{
name: `description`,
content: metaDescription,
},
{
property: `og:title`,
content: title,
},
{
property: `og:description`,
content: metaDescription,
},
{
property: `og:type`,
content: `website`,
},
{
name: `twitter:card`,
content: `summary`,
},
{
name: `twitter:creator`,
content: site.siteMetadata.social.twitter,
},
{
name: `twitter:title`,
content: title,
},
{
name: `twitter:description`,
content: metaDescription,
},
].concat(meta || [])}
/>
)
}
// Comment out the typechecking with PropTypes(By the library 'prop-types')
// SEO.defaultProps = {// lang: `en`,// meta: [],// description: ``,// }
// SEO.propTypes = {// description: PropTypes.string,// lang: PropTypes.string,// meta: PropTypes.arrayOf(PropTypes.object),// title: PropTypes.string.isRequired,// }
export default SEO
由於已經使用了Typescript,defaultProps
和propTypes
可以註解或刪除。
src/pages/404.js
將副檔名改成.tsx
。
在官方範例using-typescript.tsx
當中,gatsby函式庫中的PageProps
可取得window.location
。
在改寫404.tsx
時,會參考官方作法,將PageProps
注入元件中。
而Component所需要的Props,則會被PageProps
打包成data
物件。
以404.js
為例,要取得site
,得從data
物件中取得。
import { graphql, PageProps } from "gatsby"
interface Props {
site: { siteMetadata: { title: string } }}
const NotFoundPage: React.FC<PageProps<Props>> = ({ data, location }) => { // get your props from data
const siteTitle = data.site.siteMetadata.title
return (
<Layout location={location} title={siteTitle}>
<SEO title="404: Not Found" />
<h1>Not Found</h1>
<p>You just hit a route that doesn't exist... the sadness.</p>
</Layout>
)
}
export default NotFoundPage
src/pages/index.js
將副檔名改成.tsx
。
參考404.jsx
將PageProps
注入元件中。
interface Props { allMarkdownRemark: any site: { siteMetadata: { title: string } }}
const BlogIndex: React.FC<PageProps<Props>> = ({ data, location }) => { const siteTitle = data.site.siteMetadata.title const posts = data.allMarkdownRemark.edges
return (
<Layout location={location} title={siteTitle}>
<SEO title="All posts" />
<Bio />
{posts.map(({ node }) => {
const title = node.frontmatter.title || node.fields.slug
return (
<article key={node.fields.slug}>
<header>
<h3
style={{
marginBottom: rhythm(1 / 4),
}}
>
<Link style={{ boxShadow: `none` }} to={node.fields.slug}>
{title}
</Link>
</h3>
<small>{node.frontmatter.date}</small>
</header>
<section>
<p
dangerouslySetInnerHTML={{
__html: node.frontmatter.description || node.excerpt,
}}
/>
</section>
</article>
)
})}
</Layout>
)
}
export default BlogIndex
src/templates/blog-post.js
將副檔名改成.tsx
。
參考index.jsx
將PageProps
注入元件中。
其中在下方第38行的pageContext,是gatsby用來記錄網誌上一篇/下一篇的工具。
interface PageContext { fields: { slug: string } frontmatter: { title: string }}
interface Props { markdownRemark: any site: { siteMetadata: { title: string } }}
const BlogPostTemplate: React.FC<PageProps<Props>> = ({ data, pageContext,}) => {
const post = data.markdownRemark const siteTitle = data.site.siteMetadata.title const { previous, next, }: { previous?: PageContext; next?: PageContext } = pageContext
return (
<Layout location={window.location} title={siteTitle}>
<SEO
title={post.frontmatter.title}
description={post.frontmatter.description || post.excerpt}
/>
<article>
<header>
<h1
style={{
marginTop: rhythm(1),
marginBottom: 0,
}}
>
{post.frontmatter.title}
</h1>
<p
style={{
...scale(-1 / 5),
display: `block`,
marginBottom: rhythm(1),
}}
>
{post.frontmatter.date}
</p>
</header>
<section dangerouslySetInnerHTML={{ __html: post.html }} />
<hr
style={{
marginBottom: rhythm(1),
}}
/>
<footer>
<Bio />
</footer>
</article>
<nav>
<ul
style={{
display: `flex`,
flexWrap: `wrap`,
justifyContent: `space-between`,
listStyle: `none`,
padding: 0,
}}
>
<li>
{previous && (
<Link to={previous.fields.slug} rel="prev">
← {previous.frontmatter.title}
</Link>
)}
</li>
<li>
{next && (
<Link to={next.fields.slug} rel="next">
{next.frontmatter.title} →
</Link>
)}
</li>
</ul>
</nav>
</Layout>
)
}
export default BlogPostTemplate
src/utils/typography.js
這裡沒有return虛擬DOM元件,所以副檔名改成.ts
即可(不必是.tsx
)。
要注意的是,typography
與typography-theme-wordpress-2016
這兩個套件在gatsby官方提供的懶人包當中,沒有typescript dependency。
就如同前面seo.tsx
,當中的react-helmet
一樣,我們要在node_modules.d.ts
補上typography
:
// node_modules.d.ts
declare module "typography"declare module "typography-theme-wordpress-2016"
declare module "react-helmet"
/gatsby-node.js
最後,我們要到根目錄的gatsby-node.js
,將部落格渲染的path,將副檔名從.js
改成.tsx
。
大約是在第7行的位置:
const blogPost = path.resolve(`./src/templates/blog-post.tsx`)
費了一番功夫改成typescript,我們終於可以把localhost跑起來了!應該是可以成功運行的。