项目基础配置

  • 在 github 中配置

    • 默认分支
    • 保护分支,注意里面的配置项
  • VSCode 中添加 Code Spell Checker 进行拼写检查

  • VSCode 中添加 EditorConfig for VS Code 进行风格统一

    • 参考 EditorConfig 官网
    • 项目根目录添加 .editorconfig 文件
    • editorConfig 不是什么软件,而是一个名称为 .editorconfig 的自定义文件,该文件用来定义项目的编码规范,编辑器的行为会与.editorconfig 文件中定义的一致,并且其优先级比编辑器自身的设置要高
  • 格式检查

    • 参考 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 插件
    • 执行 ./node_modules/.bin/prettier --single-quote --write "src/**/*.{js,jsx,json,css}" 来检查整个项目
  • 样式检查

    • 参考 stylelint 进行配置
    • 安装 stylelint-config-standard、stylelint-order
    • VSCode 中添加 stylelint 插件
  • 语法检查

  • 自动化测试

    • 参考 Jest,需安装 @types/jest
    • 参考 ts-jest,作用是将 ts 写的测试文件转为 js 的,再对这些文件执行 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属性值应为 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,
    };
    
  • 持续集成 CI

    • 参考 CircleCi
    • 登录 CircleCi,进入 Projects,Add Project,找到项目,Follow Project,Builds 中运行
    • 登录 GitHub,github setting branches,require status check,ci/circleci
    • 项目中配置 .circleci/config.yml 文件如下
    • CI 中需配置环境变量
    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 2Jest 官方文档描述如下:

    设定测试会使用的最大 worker 数目。 默认会使用你的计算机上可用的内核的数量。 在类似 CI 等有资源限制的环境下需要进行相关调整时很有用。但多数场景都应该使用默认值。

注意:TypeScript 2.7 支持 import React from 'react' 的方式,需要在 ts.config 中配置 "module": "commonjs" "esModuleInterop": true

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 216,193评论 6 498
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,306评论 3 392
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 162,130评论 0 353
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,110评论 1 292
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,118评论 6 388
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,085评论 1 295
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,007评论 3 417
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,844评论 0 273
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,283评论 1 310
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,508评论 2 332
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,667评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,395评论 5 343
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,985评论 3 325
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,630评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,797评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,653评论 2 368
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,553评论 2 352