如何建立一個(gè)Gatsby主題工作區(qū)

image.png

如何建立一個(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/cypressgatsby-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 -librarycypress 都帶有自己的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é)果 ??

image.png

接下來可以做什么

到這里,我們已經(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)留言。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

友情鏈接更多精彩內(nèi)容