
如何建立一個(gè)Gatsby 主題工作區(qū),并使用TypeScript、ESLint和Cypress設(shè)置?
Gatsbyjs 團(tuán)隊(duì)推薦使用 yarn 和yarn workspace開發(fā)主題,這可能會(huì)讓不熟悉工作區(qū)模式開發(fā)的人感覺陌生。如果你也是這樣的感覺,或者以前從未聽說過 Gatsby 主題,那我強(qiáng)烈建議你先訪問 Gatbsyjs 的官方文檔。在了解之后,你可能會(huì)問自己如何建立一個(gè)Gatsby themes 工作區(qū)并整合 TypeScript甚至ESLint & Cypress——這篇文章將會(huì)向你講解如何做到這一點(diǎn)!
在本教程的最后,我們將產(chǎn)出一個(gè)包含ESLint linting + Cypress tests + TypeScript的Yarn工作區(qū)項(xiàng)目,用于示例和主題。你也可以在工作區(qū)內(nèi)用TypeScript編寫你的主題,并在任何地方使用相同的ESLint配置。
基礎(chǔ)設(shè)置
如上所述,在學(xué)習(xí)本教程之前,您應(yīng)該對(duì)Gatsby主題的工作方式有一定的了解,并且可能已經(jīng)使用過一點(diǎn)了。我假設(shè)您已經(jīng)安裝了運(yùn)行工作區(qū)和Gatsby所需的所有依賴項(xiàng)(例如Yarn)。
我們將使用“Gatsby主題工作區(qū)樣板文件”作為起點(diǎn)。clone 這個(gè)示例工程,進(jìn)入新創(chuàng)建的目錄并運(yùn)行yarn來安裝依賴項(xiàng)。
準(zhǔn)備工作完成,讓我們開始吧!
TypeScript
在編寫本教程時(shí),Gatsby并沒有很好的與TypeScript集成,大多數(shù)人使用Gatsby -plugin-TypeScript,它的內(nèi)部使用Babel。在本文中,您將只使用這個(gè)插件,如果您想添加類型檢查,還需要加入 gatsby-plugin-typescript-checker。前一個(gè)插件的作用是允許你寫ts/tsx文件(但不適用于gatsby-config、gatsby-node等)。
在主題工程中安裝 typescript plugin :
yarn workspace gatsby-theme-minimal add gatsby-plugin-typescript
在主題工程的配置文件 gatbsy-config.js 中添加配置信息:
gatsby-theme-minimal/gatsby-config.js
module.exports = {
plugins: [
`gatsby-plugin-typescript`
]
}
此外,您還需要安裝必要的 types。但是,與其在每個(gè)主題中安裝它們,還不如在全局工作空間范圍中安裝它們。這樣每個(gè)工作空間就都可以使用它們。
yarn add -D -W @types/node @types/react @types/react-dom
-D 標(biāo)志用于將它們作為 devDependencies 安裝,-W 標(biāo)志告訴 yarn 將它們安裝在工作區(qū)根目錄中。
然后我們創(chuàng)建一個(gè)TypeScript文件到主題目錄,在接下來的例子中使用。進(jìn)入主題目錄,并在components目錄中創(chuàng)建一個(gè)新文件:
創(chuàng)建: gatsby-theme-minimal/src/components/say-hello.tsx
import React from 'react'
type Props = {
children: React.ReactNode
}
const Hello = ({ children }: Props) => {
return (
<div style={{ color: `red`, fontWeight: `bold` }}>
SAY: {children}
</div>
)
}
export default Hello
編輯示例主題中的 index.js 文件,并使用剛剛新創(chuàng)建的組件:
更新:example/src/pages/index.js
import React from "react"
import Hello from "gatsby-theme-minimal/src/components/say-hello"
export default () => <div>Homepage in a user's site <Hello>Hello!</Hello></div>
運(yùn)行示例的開發(fā)服務(wù),測(cè)試是否能正常工作。
yarn workspace example develop
如果一切順利,你會(huì)看到這樣的文字:
Homepage in a user site
SAY: Hello!
很好,工程正常??
類型檢查 (Type checking)
因?yàn)槲覀儸F(xiàn)在使用的是 typescript,而上面安裝插件只解決了 Babel 編譯文件的問題,所以還需要增加類型檢查包:
typescipt、tsconfig 以及 npm 啟動(dòng)腳本。
yarn add -D -W typescript
在目錄下的 tsconfig.json 中添加如下內(nèi)容:
{
"compilerOptions": {
"target": "esnext",
"module": "commonjs",
"jsx": "react",
"lib": ["dom", "es2015", "es2017"],
"moduleResolution": "node",
"strict": true,
"noEmit": true, // Don't create files when running tsc
"skipLibCheck": true,
"esModuleInterop": true
},
"include": ["./gatsby-theme-minimal/src/**/*"]
}
最后,在根目錄下的 package.json 中的 scripts 中添加 type-check
{
"scripts": {
"type-check": "tsc"
}
}
當(dāng)我們需要執(zhí)行類型檢查時(shí),只需要執(zhí)行:
yarn type-check
ESLint
我個(gè)人真的很喜歡使用ESLint和Prettier,但如果你不想用它,可以去跳過 Prettier 部分
我們使用 ESLint的TypeScript解析器和AirBnB + Prettier的預(yù)設(shè)。請(qǐng)安裝以下軟件包:
yarn add -D -W @typescript-eslint/eslint-plugin @typescript-eslint/parser eslint eslint-config-airbnb eslint-config-prettier eslint-plugin-import eslint-plugin-jsx-a11y eslint-plugin-prettier eslint-plugin-react eslint-plugin-react-hooks prettier
在根目錄中添加 .eslintrc.js 文件:
.eslintrc.js
module.exports = {
parser: '@typescript-eslint/parser', // Specifies the ESLint parser
extends: [
"airbnb",
"plugin:@typescript-eslint/recommended",
"plugin:import/typescript",
"plugin:prettier/recommended",
"prettier/@typescript-eslint"
],
plugins: ["@typescript-eslint", "prettier", "react-hooks"],
parserOptions: {
ecmaVersion: 2018, // Allows for the parsing of modern ECMAScript features
sourceType: 'module', // Allows for the use of imports
ecmaFeatures: {
jsx: true,
},
project: './tsconfig.json'
},
env: {
browser: true,
jest: true,
node: true,
},
globals: {
cy: true,
Cypress: true,
},
rules: {
"@typescript-eslint/no-unused-vars": [
1,
{
argsIgnorePattern: "res|next|stage|^err|on|config|e"
}
],
"arrow-body-style": [2, "as-needed"],
"no-param-reassign": [
2,
{
"props": false
}
],
"no-unused-expressions": [
1,
{
"allowTaggedTemplates": true
}
],
"@typescript-eslint/prefer-interface": 0,
"@typescript-eslint/explicit-function-return-type": 0,
"@typescript-eslint/no-use-before-define": 0,
"@typescript-eslint/camelcase": 0,
"@typescript-eslint/no-var-requires": 0,
"@typescript-eslint/no-explicit-any": 0,
"@typescript-eslint/no-non-null-assertion": 0,
"no-console": 0,
"spaced-comment": 0,
"no-use-before-define": 0,
"linebreak-style": 0,
"consistent-return": 0,
"import": 0,
"func-names": 0,
"import/no-extraneous-dependencies": 0,
"import/prefer-default-export": 0,
"import/no-cycle": 0,
"space-before-function-paren": 0,
"import/extensions": 0,
"react/jsx-one-expression-per-line": 0,
"react/no-danger": 0,
"react/display-name": 1,
"react/react-in-jsx-scope": 0,
"react/jsx-uses-react": 1,
"react/forbid-prop-types": 0,
"react/no-unescaped-entities": 0,
"react/prop-types": 0,
"react/jsx-filename-extension": [
1,
{
"extensions": [".js", ".jsx", ".tsx"]
}
],
"react-hooks/rules-of-hooks": "error",
"react-hooks/exhaustive-deps": "warn",
quotes: [
2,
"backtick",
{
"avoidEscape": true
}
],
indent: ["error", 2, { SwitchCase: 1 }],
"prettier/prettier": [
"error",
{
trailingComma: "es5",
semi: false,
singleQuote: false,
printWidth: 120
}
],
"jsx-a11y/href-no-hash": "off",
"jsx-a11y/anchor-is-valid": [
"warn",
{
"aspects": ["invalidHref"]
}
]
}
}
添加兩個(gè)腳本命令
{
"scripts": {
"lint": "eslint --ignore-path .gitignore . --ext ts --ext tsx --ext js --ext jsx",
"lint:fix": "yarn lint --fix"
}
}
lint命令將在除.gitignore中定義的文件之外的所有文件(以ts/tsx/js/jsx文件結(jié)尾)上運(yùn)行eslint。--fix標(biāo)志嘗試自動(dòng)修復(fù)錯(cuò)誤。運(yùn)行yarn lint時(shí),你可能會(huì)得到一些 Prettier 提示的錯(cuò)誤,就可以執(zhí)行 yarn lint:fix 進(jìn)行修復(fù)清理!
Cypress
Cypress 是非常流行的端到端測(cè)試框架,也是可以在真實(shí)場(chǎng)景中測(cè)試主題很好用的工具,比如在示例中使用主題及其不同選項(xiàng),或者將主題與其他主題一起使用。
現(xiàn)在我們開始設(shè)置:一個(gè)針對(duì)示例站點(diǎn)運(yùn)行測(cè)試的Cypress測(cè)試套件。
首先,需要安裝必要的軟件包。除了顯而易見的cypress之外,您還將安裝@test -library/cypress和gatsby-cypress。兩者都擴(kuò)展了Cypress命令,前者改進(jìn)了選擇的過程,后者提供了一個(gè)有用的助手功能。
yarn add -D -W @testing-library/cypress @types/testing-library__cypress cross-env cypress gatsby-cypress start-server-and-test
包的 start-server-and-test 允許您首先運(yùn)行Gatsby的開發(fā)服務(wù)器(或構(gòu)建命令),然后運(yùn)行適合的Cypress命令。這是一個(gè)非常方便的小工具! @test -library 和cypress 都帶有自己的TypeScript類型。在使用TypeScript編寫Cypress測(cè)試時(shí),了解這一點(diǎn)很重要。
{
"scripts": {
"cy:open": "cypress open",
"cy:run": "cross-env CYPRESS_baseUrl=http://localhost:9000 cypress run"
}
}
運(yùn)行yarn cy:open 初始 Cypress。它會(huì)自動(dòng)向存儲(chǔ)庫添加文件,就像cypress.json 或cypress文件夾。在繼續(xù)本教程之前,請(qǐng)退出應(yīng)用程序。
請(qǐng)注意:在撰寫本指南時(shí),在運(yùn)行Cypress open時(shí),WSL (Windows 10上的Linux子系統(tǒng))無法打開Cypress電子應(yīng)用程序。您需要下載可執(zhí)行文件并自己運(yùn)行它。
刪除cypress/integration的內(nèi)容(它包含示例數(shù)據(jù))并將其重命名為e2e。編輯 cypress.json 文件:
cypress.json
{
"baseUrl": "http://localhost:8000",
"integrationFolder": "cypress/e2e/build",
"viewportHeight": 900,
"viewportWidth": 1440
}
因?yàn)門ypeScript將編譯測(cè)試并將其輸出到cypress/e2e/build中,所以您必須告訴cypress查看該文件夾。如果你不使用TypeScript,它將是cypress/e2e。
為了使用已安裝的Cypress包(并添加一個(gè)自定義命令),你必須編輯這兩個(gè)文件:
cypress/support/index.js
// Import commands.js using ES2015 syntax:
import "@testing-library/cypress/add-commands"
import "gatsby-cypress/commands"
import "./commands"
cypress/support/commands.js
Cypress.Commands.add(`assertRoute`, route => {
cy.url().should(`equal`, `${window.location.origin}${route}`)
})
assertRoute命令讓您檢查當(dāng)前的URL。
最后但并非最不重要的是,需要測(cè)試的項(xiàng)目(示例)必須做好準(zhǔn)備。要啟用Gatsby測(cè)試實(shí)用程序并最終啟用Gatsby -cypress,必須使用環(huán)境變量運(yùn)行Gatsby CLI命令。在示例中添加cross-env(確??缙脚_(tái)兼容性):
yarn workspace example add -D cross-env
修改 example 工程下的 package.json
example/package.json
{
"scripts": {
"develop": "gatsby develop",
"build": "gatsby build",
"serve": "gatsby serve",
"develop:cypress": "cross-env CYPRESS_SUPPORT=y yarn develop",
"build:cypress": "cross-env CYPRESS_SUPPORT=y yarn build"
}
}
使用Cypress和TypeScript
完成了基本設(shè)置之后,現(xiàn)在可以在編寫測(cè)試之前進(jìn)行最后的步驟了!請(qǐng)繼續(xù):)
在 cypress 目錄里面創(chuàng)建一個(gè) tsonfig.json 文件
tsconfig.json
{
"compilerOptions": {
"baseUrl": "../node_modules",
"outDir": "e2e/build",
"strict": true,
"sourceMap": false,
"target": "es5",
"lib": ["es2015", "es2017", "dom"],
"types": ["cypress", "@testing-library/cypress/typings"]
},
"include": ["e2e/*.ts", "support/*.ts"]
}
稍后運(yùn)行tsc腳本時(shí),將使用這個(gè)tsconfig,而不是根目中的那個(gè)。
您添加的自定義命令和來自gatsby-cypress的命令還沒有鍵入TypeScript——讓我們改變一下吧!創(chuàng)建一個(gè)新的文件 cypress/support:
cypress/support/index.d.ts
/// <reference types="cypress" />
declare namespace Cypress {
interface Chainable<Subject> {
/**
* Assert the current URL
* @param route
* @example cy.assertRoute('/page-2')
*/
assertRoute(route: string): Chainable<any>
/**
* Waits for Gatsby to finish the route change, in order to ensure event handlers are properly setup
*/
waitForRouteChange(): Chainable<any>
}
}
您已經(jīng)可以添加一個(gè)小的測(cè)試文件:
cypress/e2e/smoke.ts
/// <reference types="../support/index" />
/// <reference types="cypress" />
/// <reference types="@types/testing-library__cypress" />
describe(`app`, () => {
it(`should work`, () => {
cy.visit(`/`)
.waitForRouteChange()
.assertRoute(`/`)
})
})
/// 意味著這個(gè)文件應(yīng)該引用這些 places/packages 中的TypeScript類型。另外:嘗試懸停在waitForRouteChange或assertRoute函數(shù)上。您的IDE應(yīng)該顯示類型和描述。這就是您在上一步中添加的內(nèi)容。這不是很酷嗎???
添加包 concurrently 支持同時(shí)并發(fā)運(yùn)行多個(gè)命令
yarn add -D -W concurrently
編輯根包。json添加了cypress/e2e內(nèi)部文件的TypeScript編譯,以及用于運(yùn)行測(cè)試的最終腳本:
{
"name": "gatsby-starter-theme-workspace",
"private": true,
"version": "0.0.1",
"main": "index.js",
"license": "MIT",
"scripts": {
"tsc:compile": "tsc --project cypress/tsconfig.json",
"tsc:compile:watch": "tsc --watch --project cypress/tsconfig.json",
"example:cy:dev": "yarn workspace example develop:cypress",
"example:cy:build": "yarn workspace example build:cypress",
"example:serve": "yarn workspace example serve",
"ssat:example:dev": "start-server-and-test example:cy:dev http://localhost:8000 cy:open",
"ssat:example:serve": "start-server-and-test example:serve http://localhost:9000 cy:run",
"e2e:dev": "concurrently --kill-others 'yarn tsc:compile:watch' 'yarn ssat:example:dev'",
"e2e:ci": "yarn tsc:compile && yarn example:cy:build && yarn ssat:example:serve"
}
}
內(nèi)容看起來很多,但并不難理解,以下是一些解釋:
- TypeScript 應(yīng)采用
cypress/tsconfig.json并使用cypress/e2e中編譯文件。--watch標(biāo)記允許在保存文件時(shí)自動(dòng)重新編譯。 - start-server-and-test 是啟用服務(wù)器腳本,然后是指定監(jiān)聽的URL,最后是測(cè)試啟動(dòng)。
- CI是應(yīng)用于構(gòu)建和服務(wù),所以示例站點(diǎn)不以開發(fā)模式運(yùn)行。
編寫測(cè)試
好經(jīng)過一系列的配置工作,終于可以編寫測(cè)試了!
編寫:cypress/e2e/home.ts
/// <reference types="../support/index" />
/// <reference types="cypress" />
/// <reference types="@types/testing-library__cypress" />
// import { render, cleanup } from 'react-testing-library'
describe(`example`, () => {
it(`contains keyword`, () => {
cy.visit(`/`)
cy.findByText(/say: hello!/i).should('exist')
})
})
運(yùn)行yarn e2e:dev,在 Cypress electron app 中 點(diǎn)擊home.js應(yīng)用, 我們將看到如下結(jié)果 ??

接下來可以做什么
到這里,我們已經(jīng)完成了本指南????,接下來我有一些想法分享,可以在這個(gè)項(xiàng)目中發(fā)揮出更多想像,讓它幫我們做更多的事情。
正如開始提到的,您可以在這個(gè)源碼庫中下載以上所有的代碼,你也可以將其用作模板。
一些想法:
- 在CI提供程序(例如CircleCI)上運(yùn)行l(wèi)inting和測(cè)試
- 根據(jù)喜好修改ESLint配置
- 添加Cypress測(cè)試來測(cè)試主題選項(xiàng)
- 添加husky + lint-stage在提交文件之前運(yùn)行l(wèi)inter
- 添加更多的主題(+ example) =>創(chuàng)建一個(gè)單一的主題。
謝謝閱讀,我希望這篇文章對(duì)您有所幫助,接下來我會(huì)持續(xù)寫一系列關(guān)于 Gatsby 主題相關(guān)的實(shí)踐請(qǐng)多多關(guān)注。如果您有問題,請(qǐng)留言。