項(xiàng)目基礎(chǔ)配置

  • 在 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)目
  • 樣式檢查

    • 參考 stylelint 進(jìn)行配置
    • 安裝 stylelint-config-standard、stylelint-order
    • VSCode 中添加 stylelint 插件
  • 語(yǔ)法檢查

  • 自動(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 2Jest 官方文檔描述如下:

    設(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

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末框舔,一起剝皮案震驚了整個(gè)濱河市蹦玫,隨后出現(xiàn)的幾起案子赎婚,更是在濱河造成了極大的恐慌,老刑警劉巖樱溉,帶你破解...
    沈念sama閱讀 211,817評(píng)論 6 492
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件挣输,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡福贞,警方通過(guò)查閱死者的電腦和手機(jī)撩嚼,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,329評(píng)論 3 385
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)挖帘,“玉大人完丽,你說(shuō)我怎么就攤上這事∧匆ǎ” “怎么了逻族?”我有些...
    開(kāi)封第一講書(shū)人閱讀 157,354評(píng)論 0 348
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)骄崩。 經(jīng)常有香客問(wèn)我瓷耙,道長(zhǎng),這世上最難降的妖魔是什么刁赖? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 56,498評(píng)論 1 284
  • 正文 為了忘掉前任搁痛,我火速辦了婚禮,結(jié)果婚禮上宇弛,老公的妹妹穿的比我還像新娘鸡典。我一直安慰自己,他們只是感情好枪芒,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,600評(píng)論 6 386
  • 文/花漫 我一把揭開(kāi)白布彻况。 她就那樣靜靜地躺著,像睡著了一般舅踪。 火紅的嫁衣襯著肌膚如雪纽甘。 梳的紋絲不亂的頭發(fā)上,一...
    開(kāi)封第一講書(shū)人閱讀 49,829評(píng)論 1 290
  • 那天抽碌,我揣著相機(jī)與錄音悍赢,去河邊找鬼。 笑死货徙,一個(gè)胖子當(dāng)著我的面吹牛左权,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播痴颊,決...
    沈念sama閱讀 38,979評(píng)論 3 408
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼赏迟,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了蠢棱?” 一聲冷哼從身側(cè)響起锌杀,我...
    開(kāi)封第一講書(shū)人閱讀 37,722評(píng)論 0 266
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤甩栈,失蹤者是張志新(化名)和其女友劉穎,沒(méi)想到半個(gè)月后糕再,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體谤职,經(jīng)...
    沈念sama閱讀 44,189評(píng)論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,519評(píng)論 2 327
  • 正文 我和宋清朗相戀三年亿鲜,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了允蜈。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 38,654評(píng)論 1 340
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡蒿柳,死狀恐怖饶套,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情垒探,我是刑警寧澤妓蛮,帶...
    沈念sama閱讀 34,329評(píng)論 4 330
  • 正文 年R本政府宣布,位于F島的核電站圾叼,受9級(jí)特大地震影響蛤克,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜夷蚊,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,940評(píng)論 3 313
  • 文/蒙蒙 一构挤、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧惕鼓,春花似錦筋现、人聲如沸。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 30,762評(píng)論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至呀邢,卻和暖如春洒沦,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背价淌。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 31,993評(píng)論 1 266
  • 我被黑心中介騙來(lái)泰國(guó)打工申眼, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人输钩。 一個(gè)月前我還...
    沈念sama閱讀 46,382評(píng)論 2 360
  • 正文 我出身青樓豺型,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親买乃。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,543評(píng)論 2 349