快速搭建一个TypeScript项目

在团队开发中为了更好的协作,我们应该在项目中制定各种规则,比如:代码风格、变量命名、文件命名、git commit 提交格式等等。 这篇文章就介绍如何搭建快速搭建一个具有自定义规则的 TypeScript 项目。

创建项目

1
2
3
4
$ mkdir ts-project
$ cd ts-project
$ yarn init
$ git init

创建.gitignore

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
# server
server/

.DS_Store
node_modules/
dist/
npm-debug.log*
yarn-debug.log*
yarn-error.log*
test/unit/coverage
test/e2e/reports
selenium-debug.log
doc/
lib/

# Editor directories and files
.idea
.vscode
*.suo
*.ntvs*
*.njsproj
*.sln

# cache
.sass-cache/
# package-lock.json
yarn-error.log

创建.dockerignore

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
_tmp
!_tmp/deps
.github
.vscode
.yarn
!.yarn/plugins
!.yarn/releases
docs
node_modules

**/.env
**/lib
**/dist
**/build
**/coverage
**/node_modules
**/yarn-error.log

安装配置typescript

安装 typescript 作为开发阶段的依赖项

1
$ yarn add typescript -D

在根目录新建 tsconfig.json,配置项具体的意义可以参考 ts 官方文档

1
$ npx tsc --init

配置文件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
{
"version": "1.8.0",
"compilerOptions": {
"outDir": "./dist",
"rootDir": "./src",
"lib": [ "es6" ],
"target": "es5",
"module": "commonjs",
"moduleResolution": "node",
"emitDecoratorMetadata": true,
"experimentalDecorators": true,
"sourceMap": true,
"noImplicitAny": true,
"declaration": true
},
"exclude": ["dist", "node_modules","**/*.js"],
"include": ["src/**/*.ts", "src/**/*.d.ts"] // "src/**/*.tsx", "src/**/*.vue"
}

安装 @types/node 让 node 的核心包具备类型提示

1
$ yarn add @types/node -D

在根目录新建 src 目录,用于存放所有的 TypeScript 源文件,然后在 src 下新建 index.ts 作为入口文件

1
console.log('Hello TypeScript!');

在开发阶段为了能直接执行并且监听 ts 文件的变化,安装 ts-node-dev

1
2
3
$ yarn add ts-node-dev -D
$ yarn add ts-node -D
$ yarn add nodemon -D

在 package.json 中定义一个启动脚本

1
2
3
4
5
"scripts": {
"build": "tsc",
"start": "ts-node-dev --respawn src/index.ts",
"dev": "nodemon --exec ts-node src/index.ts"
}

运行

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
$ yarn start
yarn run v1.22.10
$ ts-node-dev --respawn src/index.ts
[INFO] 21:31:38 ts-node-dev ver. 1.1.1 (using ts-node ver. 9.1.1, typescript ver. 4.1.3)
Hello TypeScript!

$ yarn dev
yarn run v1.22.10
$ nodemon --exec ts-node src/index.ts
[nodemon] 2.0.7
[nodemon] to restart at any time, enter `rs`
[nodemon] watching path(s): *.*
[nodemon] watching extensions: ts,json
[nodemon] starting `ts-node src/index.ts`
Hello TypeScript!
[nodemon] clean exit - waiting for changes before restart

$ yarn build
yarn run v1.22.10
$ tsc
Done in 0.85s.

安装jest测试框架

https://juejin.cn/post/6844903888512876558

代码风格prettier

不管团队中的成员用的是什么编辑器,我们都应该保持一致的代码风格,这就需要使用 prettier

1
$ yarn add prettier -D

然后在根目录下新增一个prettier.config.js(或者.prettierrc.js),用于定义代码格式化的规则

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
module.exports = {
// 一行最多 140 字符
printWidth: 140,
// 使用 4 个空格缩进
tabWidth: 4,
// 不使用缩进符,而使用空格
useTabs: false,
// 行尾需要有分号
semi: true,
// 使用单引号
singleQuote: true,
// jsx 不使用单引号,而使用双引号
jsxSingleQuote: false,
// 末尾不需要逗号
trailingComma: 'none',
// 大括号内的首尾需要空格
bracketSpacing: true,
// jsx 标签的反尖括号需要换行
jsxBracketSameLine: false,
// 箭头函数,只有一个参数的时候,也需要括号
arrowParens: 'always',
// 每个文件格式化的范围是文件的全部内容
rangeStart: 0,
rangeEnd: Infinity,
// 不需要写文件开头的 @prettier
requirePragma: false,
// 不需要自动在文件开头插入 @prettier
insertPragma: false,
// 使用默认的折行标准
proseWrap: 'preserve',
// 根据显示样式决定 html 要不要折行
htmlWhitespaceSensitivity: 'css',
// 换行符使用 lf
endOfLine: 'lf'
};

.prettierignore

1
2
3
4
5
6
7
8
9
/dist/*
.local
.output.js
/node_modules/**

**/*.svg
**/*.sh

/public/*

在 package.json 中新增一条 script

1
2
3
4
5
6
"scripts": {
"build": "tsc",
"start": "ts-node-dev --respawn src/index.ts",
"dev": "nodemon --exec ts-node src/index.ts"
"style:prettier": "prettier --write --loglevel warn \"src/**/*.{js,ts,json,tsx,css,less,scss,vue,html,md}\""
}

运行 yarn style:prettier 将会自动格式化 src 目录下的所有 .ts 文件

1
2
3
4
5
$ yarn style:prettier
yarn run v1.22.10
$ prettier --write "src/**/*.ts"
src\index.ts 143ms
Done in 0.41s.

安装eslint

安装 eslint 用于检查项目文件的命名、变量的命名等等

1
2
3
$ yarn add eslint -D
$ yarn add @typescript-eslint/parser -D
$ yarn add @typescript-eslint/eslint-plugin -D

在根目录下新建 tslint.json,根据自己的需要定义 rules

.eslintrc

1
2
3
4
5
6
7
8
9
10
11
12
{
"root": true,
"parser": "@typescript-eslint/parser",
"plugins": [
"@typescript-eslint"
],
"extends": [
"eslint:recommended",
"plugin:@typescript-eslint/eslint-recommended",
"plugin:@typescript-eslint/recommended"
]
}

.eslintignore

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
*.sh
node_modules
lib
*.md
*.scss
*.woff
*.ttf
.vscode
.idea
/dist/
/mock/
/public
/docs
.vscode
.local
/bin
/build
/config
Dockerfile
vue.config.js
commit-lint.js
postcss.config.js
stylelint.config.js
commitlint.config.js

在 package.json 中新增一条 script

1
2
3
4
5
6
7
"scripts": {
"build": "tsc",
"start": "ts-node-dev --respawn src/index.ts",
"dev": "nodemon --exec ts-node src/index.ts",
"style:prettier": "prettier --write \"src/**/*.ts\"",
"style:lint": "eslint \"{src,mock}/**/*.{vue,ts,tsx}\" --fix"
}

运行 yarn style:lint,会执行 lint 检查

1
2
3
4
$ yarn style:lint
yarn run v1.22.10
$ eslint "{src,mock}/**/*.{vue,ts,tsx}" --fix
Done in 1.03s.

安装stylelint

1
2
3
4
$ yarn add stylelint -D
$ yarn add stylelint-config-standard -D
$ yarn add stylelint-order -D
$ yarn add stylelint-config-prettier -D

配置方式

按顺序查找,任何一项有值,就会结束查找

  • 在 package.json 中的stylelint属性指定规则
  • 在 .stylelintrc 文件中指定,文件格式可以是JSON或者YAML。也可以给该文件加后缀,像这样: .stylelintrc.json , .stylelintrc.yaml , .stylelintrc.yml , .stylelintrc.js
  • stylelint.config.js 文件,该文件 exports 一个配置对象

stylelint.config.js

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
module.exports = {
root: true,
plugins: ['stylelint-order'],
extends: ['stylelint-config-standard', 'stylelint-config-prettier'],
rules: {
'selector-pseudo-class-no-unknown': [
true,
{
ignorePseudoClasses: ['global'],
},
],
'at-rule-no-unknown': [
true,
{
ignoreAtRules: ['function', 'if', 'each', 'include', 'mixin'],
},
],
'no-empty-source': null,
'unicode-bom': 'never',
'no-descending-specificity': null,
'font-family-no-missing-generic-family-keyword': null,
'declaration-colon-space-after': 'always-single-line',
'declaration-colon-space-before': 'never',
'declaration-block-trailing-semicolon': 'always',
'rule-empty-line-before': [
'always',
{
ignore: ['after-comment', 'first-nested'],
},
],
// 指定声明块内属性的字母顺序
'order/properties-order': [
'position',
'top',
'right',
'bottom',
'left',
'z-index',
'display',
'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',
'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',
],
},
};

.stylelintignore

1
2
/dist/*
/public/*

在 package.json 中新增一条 script

1
2
3
4
5
6
7
8
9
10
"scripts": {
"build": "tsc",
"start": "ts-node-dev --respawn src/index.ts",
"dev": "nodemon --exec ts-node src/index.ts",
"style": "run-s style:**",
"style:prettier": "prettier --write --loglevel warn \"src/**/*.{js,ts,json,tsx,css,less,scss,vue,html,md}\"",
"style:lint": "eslint \"{src,mock}/**/*.{vue,ts,tsx}\" --fix",
"changelog": "conventional-changelog -p angular -i CHANGELOG.md -s -r 0",
"style:stylelint": "stylelint --fix \"**/*.{vue,less,postcss,css,scss}\" --cache --cache-location node_modules/.cache/stylelint/"
}

安装commitizen和changelog

安装 commitizen,让我们的项目具有统一的 git commit 规范

1
$ npm install -g commitizen

安装changelog

1
$ npm install -g conventional-changelog conventional-changelog-cli

支持Angular的Commit message格式

1
2
3
4
5
6
7
8
$ commitizen init cz-conventional-changelog --save --save-exact
Attempting to initialize using the npm package cz-conventional-changelog
npm notice created a lockfile as package-lock.json. You should commit this file.
npm WARN create-ts-project@1.0.0 No description

+ cz-conventional-changelog@2.1.0
added 7 packages from 16 contributors, removed 1 package, updated 131 packages and audited 219 packages in 10.509s
found 0 vulnerabilities

执行后 package.json 中会多出 commitizen 的配置项

1
2
3
4
5
"config": {
"commitizen": {
"path": "./node_modules/cz-conventional-changelog"
}
}

将所有的 git commit 操作替换成 git cz

在package.json中添加一条scripts

1
2
3
4
5
{
"scripts": {
"changelog": "conventional-changelog -p angular -i CHANGELOG.md -s -r 0"
}
}

执行git cz

1
2
3
4
5
6
7
8
9
10
11
12
13
14
$ git cz
cz-cli@3.0.5, cz-conventional-changelog@2.1.0

Line 1 will be cropped at 100 characters. All other lines will be wrapped after 100 characters.

? Select the type of change that you're committing: (Use arrow keys)
> feat: A new feature
fix: A bug fix
docs: Documentation only changes
style: Changes that do not affect the meaning of the code (white-space, formatting, missing semi-colons, etc)
refactor: A code change that neither fixes a bug nor adds a feature
perf: A code change that improves performance
test: Adding missing tests or correcting existing tests
(Move up and down to reveal more choices)

生成changelog

1
$ yarn changelog

安装commit规范检查

我们还需要在 git 提交的时候进行 commit 规范的检查,首先安装 validate-commit-msg 并在根目录中新建一个 .vcmrc 文件用于定义 commit 的验证规则

1
$ yarn add validate-commit-msg -D

.vcmrc

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
{
"types": [ "feat", "fix", "docs", "style", "refactor", "perf", "test", "build", "ci", "chore", "revert" ],
"scope": {
"required": false,
"allowed": [ "*" ],
"validate": false,
"multiple": false
},
"warnOnFail": false,
"maxSubjectLength": 100,
"subjectPattern": ".+",
"subjectPatternErrorMsg": "subject does not match subject pattern!",
"helpMessage": "",
"autoFix": true
}

然后安装 husky 让我们可以很方便的定义 git hooks

1
$ yarn add husky -D

在 package.json 中定义 git hooks,使用 validate-commit-msg 来检查 commit 格式

1
2
3
4
5
"husky": {
"hooks": {
"commit-msg": "validate-commit-msg"
}
}

现在如果提交的 commit 不符合 .vcmrc 定义的格式就会提交失败

1
2
3
4
5
$ git commit -m "2223"
husky > commit-msg (node v14.5.0)
INVALID COMMIT MSG: does not match "<type>(<scope>): <subject>" !
2223
husky > commit-msg hook failed (add --no-verify to bypass)

现在我们还希望能在 commit 时自动执行代码格式化和语法检查,要使用一条命令执行多项操作可以安装 npm-run-all

1
$ yarn add npm-run-all -D

npm-run-all 提供两个命令 run-s 串行执行多条命令 run-p 并行执行多条命令,在 package.json 中定义一个 script,用于串行执行所有以 style: 开头的 script

1
2
3
4
5
6
7
8
9
"scripts": {
"build": "tsc",
"start": "ts-node-dev --respawn src/index.ts",
"dev": "nodemon --exec ts-node src/index.ts",
"style": "run-s style:**",
"style:prettier": "prettier --write \"src/**/*.ts\"",
"style:lint": "tslint -p tsconfig.json -c tslint.json",
"changelog": "conventional-changelog -p angular -i CHANGELOG.md -s -r 0"
},

运行一下,看到确实执行了两条命令

1
2
3
4
5
6
7
$ yarn style
yarn run v1.22.10
$ run-s style:**
$ prettier --write "src/**/*.ts"
src\index.ts 141ms
$ tslint -p tsconfig.json -c tslint.json
Done in 2.07s.

修改 package.json 定义的 git hooks

1
2
3
4
5
6
"husky": {
"hooks": {
"pre-commit": "yarn style",
"commit-msg": "validate-commit-msg"
}
},

试着进行一次提交

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
$ git add .
$ git cz
cz-cli@3.0.5, cz-conventional-changelog@2.1.0


Line 1 will be cropped at 100 characters. All other lines will be wrapped after 100 characters.

? Select the type of change that you're committing: feat: A new feature
? What is the scope of this change (e.g. component or file name)? (press enter to skip)

? Write a short, imperative tense description of the change:
infrastructure
? Provide a longer description of the change: (press enter to skip)

? Are there any breaking changes? No
? Does this change affect any open issues? No
husky > pre-commit (node v10.14.0)
yarn run v1.12.3
$ run-s style:**
$ prettier --write "src/**/*.ts"
src\index.ts 228ms
$ tslint -p tsconfig.json -c tslint.json
Done in 3.01s.
husky > commit-msg (node v10.14.0)
[master 5c75d75] feat: infrastructure
6 files changed, 2666 insertions(+)
create mode 100644 package-lock.json
create mode 100644 package.json
create mode 100644 src/index.ts
create mode 100644 tsconfig.json
create mode 100644 tslint.json
create mode 100644 yarn.lock

确实在提交前格式化了所有代码文件,并且进行了 commit 检查,至此我们的团队协作项目就搭建好了,再也不用担心五花八门的代码格式和 commit 提交说明了。