-
在 github 中配置
- 默認(rèn)分支
- 保護(hù)分支,注意里面的配置項(xiàng)
VSCode 中添加 Code Spell Checker 進(jìn)行拼寫(xiě)檢查
-
VSCode 中添加 EditorConfig for VS Code 進(jìn)行風(fēng)格統(tǒng)一
- 參考 EditorConfig 官網(wǎng)
- 項(xiàng)目根目錄添加 .editorconfig 文件
- editorConfig 不是什么軟件侦高,而是一個(gè)名稱為 .editorconfig 的自定義文件也颤,該文件用來(lái)定義項(xiàng)目的編碼規(guī)范,編輯器的行為會(huì)與.editorconfig 文件中定義的一致,并且其優(yōu)先級(jí)比編輯器自身的設(shè)置要高
-
格式檢查
- 參考 prettier 官網(wǎng) 進(jìn)行配置,它可以很好的集成的到項(xiàng)目中,利用 git 的 hooks 的機(jī)制著蛙,在提交 commit 時(shí)自動(dòng)調(diào)用 prettier,使用 husky 和 lint-staged 配合使用
- husky :可以方便的通過(guò) npm scripts 來(lái)調(diào)用各種 git hooks
- lint-staged :利用 git 的 staged 特性耳贬,可以提取出本次提交的變動(dòng)文件踏堡,讓 prettier 只處理這些文件
- husky 配合 lint-stage 的過(guò)程可以通過(guò) pretty-quick 來(lái)取代,但如果項(xiàng)目中也使用了其它工具咒劲,比如ESLint顷蟆,請(qǐng)使用lint-stage
- VSCode 中添加 Prettier - Code formatter 插件
- 執(zhí)行
./node_modules/.bin/prettier --single-quote --write "src/**/*.{js,jsx,json,css}"
來(lái)檢查整個(gè)項(xiàng)目
- 參考 prettier 官網(wǎng) 進(jìn)行配置,它可以很好的集成的到項(xiàng)目中,利用 git 的 hooks 的機(jī)制著蛙,在提交 commit 時(shí)自動(dòng)調(diào)用 prettier,使用 husky 和 lint-staged 配合使用
-
樣式檢查
- 參考 stylelint 進(jìn)行配置
- 安裝 stylelint-config-standard、stylelint-order
- VSCode 中添加 stylelint 插件
-
語(yǔ)法檢查
- 參考 TSLint 官網(wǎng) tslint-react 進(jìn)行配置
- tslint-config-prettier 防止 tslint 和 prettier 發(fā)生沖突腐魂,prettier 負(fù)責(zé)格式帐偎,tslint 負(fù)責(zé)其它
- VSCode 中添加 TSLint 插件
-
自動(dòng)化測(cè)試
- 參考 Jest,需安裝 @types/jest
- 參考 ts-jest蛔屹,作用是將 ts 寫(xiě)的測(cè)試文件轉(zhuǎn)為 js 的削樊,再對(duì)這些文件執(zhí)行 jest
- VSCode 中添加 jest 插件
import * as React from 'react'; import * as renderer from 'react-test-renderer'; import configureStore from 'redux-mock-store'; import thunk from 'redux-thunk'; import App from '../App'; import Badge, { BadgeVariant } from './Badge'; const middlewares = [thunk]; const mockStore = configureStore(middlewares); const initialState = {}; test('正確渲染', () => { const store = mockStore(initialState); let tree = renderer .create( <App context={{ fetch: () => { return; }, store, client: {}, }} > <Badge className="badge">{10}</Badge> </App>, ) .toJSON(); expect(tree).toMatchSnapshot(); tree = renderer .create( <App context={{ fetch: () => { return; }, store, client: {}, }} > <Badge>New</Badge> </App>, ) .toJSON(); expect(tree).toMatchSnapshot(); }); test('variant屬性值應(yīng)為 primary, info, success, warning, error 中的一個(gè)', () => { const store = mockStore(initialState); for (const variant in BadgeVariant) { if (BadgeVariant[variant]) { const tree = renderer .create( <App context={{ fetch: () => { return; }, store, client: {}, }} > <Badge variant={BadgeVariant[variant]}>職問(wèn)</Badge> </App>, ) .toJSON(); expect(tree).toMatchSnapshot(); } } });
-
配置文件
- .editorconfig 文件
root = true [*] indent_style = space indent_size = 2 end_of_line = lf charset = utf-8 trim_trailing_whitespace = true insert_final_newline = true # editorconfig-tools is unable to ignore long strings or urls max_line_length = null
- .prettierrc 文件
{ "singleQuote": true, "trailingComma": "all" }
- .stylelintrc 文件
{ extends: 'stylelint-config-standard', plugins: [ 'stylelint-order', ], rules: { 'property-no-unknown': [ true, { ignoreProperties: [ 'composes', ], }, ], 'selector-pseudo-class-no-unknown': [ true, { ignorePseudoClasses: [ 'global', ], }, ], 'string-quotes': 'single', 'order/order': [ 'custom-properties', 'dollar-variables', 'declarations', 'at-rules', 'rules', ], 'order/properties-order': [ 'composes', 'position', 'top', 'right', 'bottom', 'left', 'z-index', 'display', 'align-content', 'align-items', 'align-self', 'flex', 'flex-basis', 'flex-direction', 'flex-flow', 'flex-grow', 'flex-shrink', 'flex-wrap', 'justify-content', 'order', 'float', 'width', 'height', 'max-width', 'max-height', 'min-width', 'min-height', 'padding', 'padding-top', 'padding-right', 'padding-bottom', 'padding-left', 'margin', 'margin-top', 'margin-right', 'margin-bottom', 'margin-left', 'margin-collapse', 'margin-top-collapse', 'margin-right-collapse', 'margin-bottom-collapse', 'margin-left-collapse', 'overflow', 'overflow-x', 'overflow-y', 'clip', 'clear', 'font', 'font-family', 'font-size', 'font-smoothing', 'osx-font-smoothing', 'font-style', 'font-weight', 'hyphens', 'src', 'line-height', 'letter-spacing', 'word-spacing', 'color', 'text-align', 'text-decoration', 'text-indent', 'text-overflow', 'text-rendering', 'text-size-adjust', 'text-shadow', 'text-transform', 'word-break', 'word-wrap', 'white-space', 'vertical-align', 'list-style', 'list-style-type', 'list-style-position', 'list-style-image', 'pointer-events', 'cursor', 'background', 'background-attachment', 'background-color', 'background-image', 'background-position', 'background-repeat', 'background-size', 'border', 'border-collapse', 'border-top', 'border-right', 'border-bottom', 'border-left', 'border-color', 'border-image', 'border-top-color', 'border-right-color', 'border-bottom-color', 'border-left-color', 'border-spacing', 'border-style', 'border-top-style', 'border-right-style', 'border-bottom-style', 'border-left-style', 'border-width', 'border-top-width', 'border-right-width', 'border-bottom-width', 'border-left-width', 'border-radius', 'border-top-right-radius', 'border-bottom-right-radius', 'border-bottom-left-radius', 'border-top-left-radius', 'border-radius-topright', 'border-radius-bottomright', 'border-radius-bottomleft', 'border-radius-topleft', 'content', 'quotes', 'outline', 'outline-offset', 'outline-width', 'outline-style', 'outline-color', 'opacity', 'filter', 'visibility', 'size', 'zoom', 'transform', 'box-align', 'box-flex', 'box-orient', 'box-pack', 'box-shadow', 'box-sizing', 'table-layout', 'animation', 'animation-delay', 'animation-duration', 'animation-iteration-count', 'animation-name', 'animation-play-state', 'animation-timing-function', 'animation-fill-mode', 'transition', 'transition-delay', 'transition-duration', 'transition-property', 'transition-timing-function', 'background-clip', 'backface-visibility', 'resize', 'appearance', 'user-select', 'interpolation-mode', 'direction', 'marks', 'page', 'set-link-source', 'unicode-bidi', 'speak', ], }, }
- tslint.json 文件
{ { "extends": ["tslint:latest", "tslint-config-prettier", "tslint-react"], "rules": { "interface-name": [true, "never-prefix"], "no-submodule-imports": false, "jsx-boolean-value": false, "jsx-no-multiline-js": false, "jsx-wrap-multiline": false, "class-name": true, "comment-format": [true, "check-space"], "curly": true, "indent": [true, "spaces"], "one-line": [true, "check-open-brace", "check-whitespace"], "no-var-keyword": true, "quotemark": [true, "single", "avoid-escape", "jsx-double"], "semicolon": [true, "always", "ignore-bound-class-methods"], "whitespace": [ true, "check-branch", "check-decl", "check-operator", "check-module", "check-separator", "check-type" ], "typedef-whitespace": [ true, { "call-signature": "nospace", "index-signature": "nospace", "parameter": "nospace", "property-declaration": "nospace", "variable-declaration": "nospace" }, { "call-signature": "onespace", "index-signature": "onespace", "parameter": "onespace", "property-declaration": "onespace", "variable-declaration": "onespace" } ], "no-internal-module": true, "no-trailing-whitespace": true, "no-null-keyword": true, "prefer-const": true, "jsdoc-format": true, "object-literal-sort-keys": false } } }
- tsConfig.json 文件
{ "compilerOptions": { "outDir": "build/dist", "module": "esnext", "target": "es5", "lib": ["es7", "dom"], "sourceMap": true, "allowJs": true, "jsx": "react", "moduleResolution": "node", "rootDirs": ["src", "config"], "forceConsistentCasingInFileNames": true, "noImplicitReturns": true, "noImplicitThis": true, "noImplicitAny": true, "strictNullChecks": true, "suppressImplicitAnyIndexErrors": true, "noUnusedLocals": true }, "exclude": [ "node_modules", "build", "scripts", "acceptance-tests", "webpack", "jest", "src/setupTests.ts" ] }
- package.json 文件
{ ... "lint-staged": { "*.{json,md,graphql}": [ "prettier --write", "git add" ], "*.{ts,tsx}": [ "prettier --write", "tslint --fix", "git add" ], "*.{css,less,scss,sass,sss}": [ "prettier --write", "stylelint --fix", "git add" ] }, "scripts": { "precommit": "lint-staged", "lint": "yarn run lint-ts && yarn run lint-css", "fix": "yarn run fix-ts && yarn run fix-css", "lint-ts": "tslint 'src/**/*.{ts,tsx}'", "fix-ts": "tslint --fix 'src/**/*.{ts,tsx}'", "lint-css": "stylelint 'src/**/*.{css,less,scss,sass,sss}'", "fix-css": "stylelint --fix 'src/**/*.{css,less,scss,sass,sss}'", ... } }
jest.config.js 文件
module.exports = { automock: false, browser: false, bail: false, collectCoverageFrom: [ 'src/**/*.{ts,tsx}', '!**/node_modules/**', '!**/vendor/**', ], coverageDirectory: '<rootDir>/coverage', globals: { __DEV__: true, }, moduleFileExtensions: ['ts', 'tsx', 'js', 'jsx', 'json', 'node'], moduleNameMapper: { '\\.(css|less|scss|sss)$': 'identity-obj-proxy', '\\.(jpg|jpeg|png|gif|eot|otf|webp|svg|ttf|woff|woff2|mp4|webm|wav|mp3|m4a|aac|oga)$': 'GlobalImageStub', }, transform: { '^.+\\.tsx?$': 'ts-jest', }, testRegex: '(/__tests__/.*|(\\.|/)(test|spec))\\.(jsx?|tsx?)$', verbose: true, };
-
持續(xù)集成 CI
- 參考 CircleCi
- 登錄 CircleCi,進(jìn)入 Projects兔毒,Add Project漫贞,找到項(xiàng)目,F(xiàn)ollow Project眼刃,Builds 中運(yùn)行
- 登錄 GitHub绕辖,github setting branches摇肌,require status check擂红,ci/circleci
- 項(xiàng)目中配置 .circleci/config.yml 文件如下
- CI 中需配置環(huán)境變量
version: 2 jobs: build: working_directory: ~/repo docker: - image: circleci/node:latest steps: - checkout - restore_cache: keys: - v1-dependencies-{{ checksum "package.json" }} - v1-dependencies- - run: yarn install - save_cache: paths: - node_modules key: v1-dependencies-{{ checksum "package.json" }} - run: yarn run lint - run: yarn run test - run: name: yarn build command: | if [ "$CIRCLE_BRANCH" != "develop" ] && [ "$CIRCLE_BRANCH" != "master" ]; then yarn build; fi - store_artifacts: path: build destination: build - store_test_results: path: coverage
在運(yùn)行測(cè)試時(shí)
yarn test
命令有時(shí)會(huì)帶參數(shù)yarn test --maxWorkers 2
,Jest 官方文檔描述如下:設(shè)定測(cè)試會(huì)使用的最大 worker 數(shù)目围小。 默認(rèn)會(huì)使用你的計(jì)算機(jī)上可用的內(nèi)核的數(shù)量昵骤。 在類似 CI 等有資源限制的環(huán)境下需要進(jìn)行相關(guān)調(diào)整時(shí)很有用。但多數(shù)場(chǎng)景都應(yīng)該使用默認(rèn)值肯适。
注意:TypeScript 2.7 支持 import React from 'react'
的方式变秦,需要在 ts.config 中配置 "module": "commonjs"
"esModuleInterop": true
。