-
在 github 中配置
- 默認分支
- 保護分支,注意里面的配置項
VSCode 中添加 Code Spell Checker 進行拼寫檢查
-
VSCode 中添加 EditorConfig for VS Code 進行風格統(tǒng)一
- 參考 EditorConfig 官網
- 項目根目錄添加 .editorconfig 文件
- editorConfig 不是什么軟件,而是一個名稱為 .editorconfig 的自定義文件,該文件用來定義項目的編碼規(guī)范,編輯器的行為會與.editorconfig 文件中定義的一致,并且其優(yōu)先級比編輯器自身的設置要高
-
格式檢查
- 參考 prettier 官網 進行配置,它可以很好的集成的到項目中,利用 git 的 hooks 的機制,在提交 commit 時自動調用 prettier,使用 husky 和 lint-staged 配合使用
- husky :可以方便的通過 npm scripts 來調用各種 git hooks
- lint-staged :利用 git 的 staged 特性,可以提取出本次提交的變動文件,讓 prettier 只處理這些文件
- husky 配合 lint-stage 的過程可以通過 pretty-quick 來取代,但如果項目中也使用了其它工具,比如ESLint,請使用lint-stage
- VSCode 中添加 Prettier - Code formatter 插件
- 執(zhí)行
./node_modules/.bin/prettier --single-quote --write "src/**/*.{js,jsx,json,css}"來檢查整個項目
- 參考 prettier 官網 進行配置,它可以很好的集成的到項目中,利用 git 的 hooks 的機制,在提交 commit 時自動調用 prettier,使用 husky 和 lint-staged 配合使用
-
樣式檢查
- 參考 stylelint 進行配置
- 安裝 stylelint-config-standard、stylelint-order
- VSCode 中添加 stylelint 插件
-
語法檢查
- 參考 TSLint 官網 tslint-react 進行配置
- tslint-config-prettier 防止 tslint 和 prettier 發(fā)生沖突,prettier 負責格式,tslint 負責其它
- VSCode 中添加 TSLint 插件
-
自動化測試
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屬性值應為 primary, info, success, warning, error 中的一個', () => { 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]}>職問</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,進入 Projects,Add Project,找到項目,Follow Project,Builds 中運行
- 登錄 GitHub,github setting branches,require status check,ci/circleci
- 項目中配置 .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在運行測試時
yarn test命令有時會帶參數yarn test --maxWorkers 2,Jest 官方文檔描述如下:設定測試會使用的最大 worker 數目。 默認會使用你的計算機上可用的內核的數量。 在類似 CI 等有資源限制的環(huán)境下需要進行相關調整時很有用。但多數場景都應該使用默認值。
注意:TypeScript 2.7 支持 import React from 'react' 的方式,需要在 ts.config 中配置 "module": "commonjs" "esModuleInterop": true。