前言
在这一章我们进行一个简单的项目规范和项目管理,为了更好的代码协同,我们选择使用 Git
对代码进行管理并通过一系列 npm
包配置相应的规范约束。
内容
本章内容涉及到的文档如下:
editorconfig
:https://editorconfig.org/
EditorConfig for VS Code
:EditorConfig for VS Code
配置Git
让我们打开项目,运行终端,现在开始执行第一个命令,让我们初始化 Git
仓库:
$ git init
已初始化空的 Git 仓库于 /Users/wangyang/Documents/eaui/.git/
接下来,让我们来添加远程仓库地址,并进行第一次初始化提交:
$ git remote add origin git@github.com:oyo-cool/eaui.git
$ git add .
$ git commit -m "init"
$ git push --set-upstream origin main
如果没有出现意外的话,您将会看到类似以下的输出:
枚举对象中: 47, 完成.
对象计数中: 100% (47/47), 完成.
使用 12 个线程进行压缩
压缩对象中: 100% (42/42), 完成.
写入对象中: 100% (47/47), 64.76 KiB | 9.25 MiB/s, 完成.
总共 47(差异 0),复用 31(差异 0),包复用 0(来自 0 个包)
To github.com:oyo-cool/eaui.git
+ 150853f...d4e3320 main -> main (forced update)
分支 'main' 设置为跟踪 'origin/main'。
恭喜您,现在您已经成功的将代码推送到了远程的仓库上!不过这里还没有结束,我们避免不同系统之间的换行符的差异,所以这里我们增加个配置 .gitattributes
,内容如下:
*.ts text eol=lf
*.vue text eol=lf
*.tsx text eol=lf
*.jsx text eol=lf
*.html text eol=lf
*.json text eol=lf
配置规则
现在 Git
已经配置完成,接下来让我们一起来配置下项目的规范和约束,当前在最开始初始化项目的时候,vue脚手架已经帮我们配置好了ESlint
和 Prettier
,不过那样远远不够,现在让我们根据自己的规则再来进行完善。
配置.editorconfig
EditorConfig 有助于为跨各种编辑器和 IDE 处理同一项目的多个开发人员保持一致的编码样式。
root = true
[*]
indent_style = space
end_of_line = lf
charset = utf-8
trim_trailing_whitespace = true
insert_final_newline = true
[*.md]
trim_trailing_whitespace = false
[*.{ts,js,vue,css}]
indent_size = 2
配置Prettier
首先我们先对 Prettier
进行更新,强迫症患者又加上是新项目,刚好看看有没有坑,可以提前踩踩。
$ pnpm up prettier
更新完成后,我们移除当前项目中的配置文件.prettierrc.json
,创建一个新的配置文件 .prettierrc.js
,配置内容如下:
export default {
// 一行最多 120 字符..
printWidth: 120,
// 使用 2 个空格缩进
tabWidth: 2,
// 不使用缩进符,而使用空格
useTabs: false,
// 行尾需要有分号
semi: true,
// 使用单引号
singleQuote: true,
// 对象的 key 仅在必要时用引号
quoteProps: 'as-needed',
// jsx 不使用单引号,而使用双引号
jsxSingleQuote: false,
// 末尾需要有逗号
trailingComma: 'all',
// 大括号内的首尾需要空格
bracketSpacing: true,
// jsx 标签的反尖括号需要换行
jsxBracketSameLine: false,
// 箭头函数,只有一个参数的时候,也需要括号
arrowParens: 'always',
// 每个文件格式化的范围是文件的全部内容
rangeStart: 0,
rangeEnd: Infinity,
// 不需要写文件开头的 @prettier
requirePragma: false,
// 不需要自动在文件开头插入 @prettier
insertPragma: false,
// 使用默认的折行标准
proseWrap: 'preserve',
// 根据显示样式决定 html 要不要折行
htmlWhitespaceSensitivity: 'css',
// vue 文件中的 script 和 style 内不用缩进
vueIndentScriptAndStyle: false,
// 换行符使用 lf
endOfLine: 'lf',
};
配置好之后,让我们来试试运行命令会有什么样的结果:
$ pnpm format
> eaui@0.0.0 format /Users/wangyang/Documents/eaui
> prettier --write src/
src/App.vue 57ms (unchanged)
src/assets/base.css 6ms (unchanged)
src/assets/main.css 2ms (unchanged)
src/components/__tests__/HelloWorld.spec.ts 7ms (unchanged)
src/components/HelloWorld.vue 21ms (unchanged)
src/components/icons/IconCommunity.vue 3ms (unchanged)
src/components/icons/IconDocumentation.vue 1ms (unchanged)
src/components/icons/IconEcosystem.vue 2ms (unchanged)
src/components/icons/IconSupport.vue 1ms (unchanged)
src/components/icons/IconTooling.vue 1ms (unchanged)
src/components/TheWelcome.vue 7ms (unchanged)
src/components/WelcomeItem.vue 5ms (unchanged)
src/main.ts 3ms (unchanged)
src/stores/counter.ts 4ms (unchanged)
好的,很完美,目前看来好像没有出现什么坑。
配置ESlint
这里我们需要安装的插件比较多,不过其中有一些是脚手架已经安装过的,每个插件具体的用处,可查看对应的文档来了解,这里不一一解释。
eslint-config-prettier
:https://www.npmjs.com/package/eslint-config-prettier
eslint-plugin-prettier
:https://www.npmjs.com/package/eslint-plugin-prettier
eslint-plugin-playwright
:https://www.npmjs.com/package/eslint-plugin-playwright
eslint-plugin-vue-scoped-css
:https://www.npmjs.com/package/eslint-plugin-vue-scoped-css
eslint-plugin-vue
:https://www.npmjs.com/package/eslint-plugin-vue
eslint-config-airbnb-base
:https://www.npmjs.com/package/eslint-config-airbnb-base
eslint-config-typescript
:https://www.npmjs.com/package/@vue/eslint-config-typescript
@typescript-eslint/eslint-plugin
:https://www.npmjs.com/package/@typescript-eslint/eslint-plugin
eslint-plugin-simple-import-sort
:https://www.npmjs.com/package/eslint-plugin-simple-import-sort
@typescript-eslint/parser
:https://www.npmjs.com/package/@typescript-eslint/parser
@typescript-eslint/eslint-plugin
:https://www.npmjs.com/package/@typescript-eslint/eslint-plugin
pnpm add -D eslint-config-prettier eslint-plugin-prettier eslint-plugin-playwright eslint-plugin-vue-scoped-css eslint-plugin-vue eslint-config-airbnb-base @vue/eslint-config-typescript @typescript-eslint/eslint-plugin eslint-plugin-simple-import-sort @typescript-eslint/parser @typescript-eslint/eslint-plugin
安装完插件后,我们先配置下忽略文件 .eslintignore
:
dist
lib
es
esm
node_modules
static
cypress
playwright-report
tests-results
static/
!.prettierrc.js
配置好忽略文件之后,先移除当前的配置文件 .eslintrc.cjs
,再创建一个全新的配置文件 .eslintrc
,内容如下:
{
"extends": [
"plugin:@typescript-eslint/recommended",
"eslint-config-airbnb-base",
"@vue/typescript/recommended",
"plugin:vue/vue3-recommended",
"plugin:vue-scoped-css/base",
"plugin:prettier/recommended",
],
"env": {
"browser": true,
"node": true,
"jest": true,
"es6": true,
},
"globals": {
"defineProps": "readonly",
"defineEmits": "readonly",
},
"plugins": ["vue", "@typescript-eslint", "simple-import-sort"],
"parserOptions": {
"parser": "@typescript-eslint/parser",
"sourceType": "module",
"allowImportExportEverywhere": true,
"ecmaFeatures": {
"jsx": true,
},
},
"settings": {
"import/extensions": [".js", ".jsx", ".ts", ".tsx"],
"eslint.packageManager": "pnpm",
},
"rules": {
"no-console": "off",
"no-continue": "off",
"no-restricted-syntax": "off",
"no-plusplus": "off",
"no-param-reassign": "off",
"no-shadow": "off",
"guard-for-in": "off",
"camelcase": [
"error",
{
"ignoreDestructuring": true,
"properties": "never",
},
],
"import/extensions": "off",
"import/no-unresolved": "off",
"import/no-extraneous-dependencies": "off",
"import/prefer-default-export": "off",
"import/first": "off", // https://github.com/vuejs/vue-eslint-parser/issues/58
"@typescript-eslint/no-explicit-any": "off",
"@typescript-eslint/explicit-module-boundary-types": "off",
"vue/first-attribute-linebreak": 0,
"class-methods-use-this": "off",
"@typescript-eslint/no-unused-vars": [
"error",
{
"argsIgnorePattern": "^_",
"varsIgnorePattern": "^_",
},
],
"no-unused-vars": [
"error",
{
"argsIgnorePattern": "^_",
"varsIgnorePattern": "^_",
},
],
"no-use-before-define": "off",
"@typescript-eslint/no-use-before-define": "off",
"@typescript-eslint/ban-ts-comment": "off",
"@typescript-eslint/ban-types": "off",
"simple-import-sort/imports": "error",
"simple-import-sort/exports": "error",
},
"overrides": [
{
"files": [
"e2e/**/*.{test,spec}.{js,ts,jsx,tsx}"
],
"extends": [
"plugin:playwright/recommended"
]
},
{
"files": ["*.vue"],
"rules": {
"vue/component-name-in-template-casing": [2, "kebab-case"],
"vue/require-default-prop": 0,
"vue/multi-word-component-names": 0,
"vue/no-reserved-props": 0,
"vue/no-v-html": 0,
"vue-scoped-css/enforce-style-type": ["error", {
"allows": ["scoped"] }],
},
},
{
"files": ["*.ts", "*.tsx"], // https://github.com/typescript-eslint eslint-recommended
"rules": {
"constructor-super": "off", // ts(2335) & ts(2377)
"getter-return": "off", // ts(2378)
"no-const-assign": "off", // ts(2588)
"no-dupe-args": "off", // ts(2300)
"no-dupe-class-members": "off", // ts(2393) & ts(2300)
"no-dupe-keys": "off", // ts(1117)
"no-func-assign": "off", // ts(2539)
"no-import-assign": "off", // ts(2539) & ts(2540)
"no-new-symbol": "off", // ts(2588)
"no-obj-calls": "off", // ts(2349)
"no-redeclare": "off", // ts(2451)
"no-setter-return": "off", // ts(2408)
"no-this-before-super": "off", // ts(2376)
"no-undef": "off", // ts(2304)
"no-unreachable": "off", // ts(7027)
"no-unsafe-negation": "off", // ts(2365) & ts(2360) & ts(2358)
"no-var": "error", // ts transpiles let/const to var, so no need for vars any more
"prefer-const": "error", // ts provides better types with const
"prefer-rest-params": "error", // ts provides better types with rest args over arguments
"prefer-spread": "error", // ts transpiles spread to apply, so no need for manual apply
"valid-typeof": "off", // ts(2367)
},
},
],
}
然后我们故意在代码中,做一些违反规则的代码,然后在 package.json
中的 scripts
增加以下命令:
{
"scripts": {
"lint": "eslint --ext .vue,.js,.jsx,.ts,.tsx ./ --max-warnings 0",
"lint:fix": "eslint --ext .vue,.js,jsx,.ts,.tsx ./ --max-warnings 0 --fix",
}
}
然后我们在终端中运行命令 pnpm lint
进行验证:
$ pnpm lint
> eaui@0.0.0 lint /Users/wangyang/Documents/eaui
> eslint . --ext .vue,.js,.jsx,.cjs,.mjs,.ts,.tsx,.cts,.mts ./ --max-warnings 0
/Users/wangyang/Documents/eaui/src/App.vue
7:7 error Identifier 't_s' is not in camel case camelcase
7:7 error 't_s' is assigned a value but never used. Allowed unused vars must match /^_/u @typescript-eslint/no-unused-vars
7:7 error 't_s' is assigned a value but never used. Allowed unused vars must match /^_/u no-unused-vars
✖ 3 problems (3 errors, 0 warnings)
ELIFECYCLE Command failed with exit code 1.
很好,现在看来我们配置的 ESlint
规则已经生效,那么我们接下来可以继续配置其他的规则了。
配置Stylelint
在配置之前,我们同样需要安装一些插件,插件列表如下:
stylelint
:https://stylelint.io/
stylelint
:https://www.npmjs.com/package/stylelint
stylelint-config-standard
:https://www.npmjs.com/package/stylelint-config-standard
stylelint-order
:https://www.npmjs.com/package/stylelint-order
postcss-html
:https://www.npmjs.com/package/postcss-html
postcss-less
:https://www.npmjs.com/package/postcss-less
$ pnpm add -D stylelint stylelint-config-standard stylelint-order postcss-html postcss-less
安装完成后,我们先增加一个忽略配置 .stylelintignore
,内容如下:
*.min.css
# 其他类型文件
*.js
*.jpg
*.woff
接下来就是正主了,添加配置文件 stylelint.config.js
,内容如下:
export default {
defaultSeverity: 'error',
extends: ['stylelint-config-standard'],
plugins: ['stylelint-less'],
rules: {
'no-descending-specificity': null,
'import-notation': 'string',
'no-empty-source': null,
'custom-property-pattern': null,
'selector-class-pattern': null,
'selector-pseudo-class-no-unknown': [
true,
{
ignorePseudoClasses: ['deep'],
},
],
'media-query-no-invalid': null, // https://stylelint.io/user-guide/rules/media-query-no-invalid/#options
},
overrides: [
{
files: ['**/*.html', '**/*.vue'],
customSyntax: 'postcss-html',
},
{
files: ['**/*.less'],
customSyntax: 'postcss-less',
},
],
};
最后在 package.json
中增加以下命令:
{
"scripts": {
"stylelint": "stylelint src/**/*.{html,vue,sass,less}",
"stylelint:fix": "stylelint --fix src/**/*.{html,vue,vss,sass,less}",
}
}
同样的,我们在代码中搞一些不符合规则的东西,然后再运行命令检查:
$ pnpm stylelint
> eaui@0.0.0 stylelint /Users/wangyang/Documents/eaui
> stylelint src/**/*.{html,vue,sass,less}
src/components/WelcomeItem.vue
34:3 ✖ Unexpected empty line before declaration declaration-empty-line-before
42:3 ✖ Unexpected empty line before declaration declaration-empty-line-before
✖ 2 problems (2 errors, 0 warnings)
2 errors potentially fixable with the "--fix" option.
ELIFECYCLE Command failed with exit code 2.
现在再让我们尝试下,运行修复命令看看是否可以正常修复:
$ pnpm stylelint:fix
> eaui@0.0.0 stylelint:fix /Users/wangyang/Documents/eaui
> stylelint --fix src/**/*.{html,vue,css,sass,less}
好了,现在让我们继续开始下面的配置。
配置commitlint
老生常谈了,先安装插件,插件内容如下:
commitlint.js
:https://commitlint.js.org/
conventionalcommits
:https://www.conventionalcommits.org/zh-hans/v1.0.0/
cli
:https://github.com/conventional-changelog/commitlint/tree/master/%40commitlint/cli
config-conventional
:https://github.com/conventional-changelog/commitlint/tree/master/%40commitlint/config-conventional
$ pnpm add -D @commitlint/{cli,config-conventional}
安装完成后,我们来配置一下:
echo "export default { extends: ['@commitlint/config-conventional'] };" > commitlint.config.js
为了更好的限制,我们增加下类型枚举,具体的含义建议直接访问 conventionalcommits 进行查看,配置内容如下:
export default {
extends: ['@commitlint/config-conventional'],
rules: {
'type-enum': [
2,
'always',
['build', 'chore', 'ci', 'docs', 'feat', 'fix', 'perf', 'refactor', 'revert', 'style', 'test', 'types'],
],
},
};
不过这样好像还不够,接下来我们再通过 commitizen
来进行限制。
配置commitizen
cz-cli
:https://commitizen.github.io/cz-cli/
$ pnpm add -D commitizen
$ npx commitizen init cz-conventional-changelog --pnpm --save-dev --save-exact
好了,接下来要到我们最关键的配置了,husky
。
配置husky
husky
:https://github.com/typicode/husky
husky
:https://www.npmjs.com/package/husky
lint-staged
:https://github.com/lint-staged/lint-staged
lint-staged
:https://www.npmjs.com/package/lint-staged
$ pnpm add -D husky lint-staged
$ pnpm exec husky init
$ echo "pnpm lint-staged" > .husky/pre-commit
$ echo "pnpm dlx commitlint --edit \$1" > .husky/commit-msg
$ echo "exec < /dev/tty && npx cz --hook || true" > .husky/prepare-commit-msg
在 package.json
中增加 lint-staged
的配置,配置内容如下:
{
"lint-staged": {
"*.{js,jsx,vue,ts,tsx}": [
"prettier --write",
"pnpm lint:fix"
],
"*.{html,vue,vss,sass,less}": [
"pnpm stylelint:fix"
]
}
}
测试
现在基本的已经配置完成,我们来进行看看。
$ git add .
$ git commit -m "this will fail"
运行结果如下:
✔ Preparing lint-staged...
⚠ Running tasks for staged files...
❯ package.json — 27 files
❯ *.{js,jsx,vue,ts,tsx} — 14 files
✔ prettier --write
✖ pnpm lint:fix [FAILED]
✔ *.{html,vue,vss,sass,less} — 4 files
↓ Skipped because of errors from tasks.
✔ Reverting to original state because of errors...
✔ Cleaning up temporary files...
✖ pnpm lint:fix:
> eaui@0.0.0 lint:fix /Users/wangyang/Documents/eaui
> eslint --ext .vue,.js,jsx,.ts,.tsx ./ --max-warnings 0 --fix "/Users/wangyang/Documents/eaui/.prettierrc.js" "/Users/wangyang/Documents/eaui/commitlint.config.js" "/Users/wangyang/Documents/eaui/e2e/vue.spec.ts" "/Users/wangyang/Documents/eaui/playwright.config.ts" "/Users/wangyang/Documents/eaui/src/App.vue" "/Users/wangyang/Documents/eaui/src/components/HelloWorld.vue" "/Users/wangyang/Documents/eaui/src/components/TheWelcome.vue" "/Users/wangyang/Documents/eaui/src/components/WelcomeItem.vue" "/Users/wangyang/Documents/eaui/src/components/__tests__/HelloWorld.spec.ts" "/Users/wangyang/Documents/eaui/src/main.ts" "/Users/wangyang/Documents/eaui/src/stores/counter.ts" "/Users/wangyang/Documents/eaui/stylelint.config.js" "/Users/wangyang/Documents/eaui/vite.config.ts" "/Users/wangyang/Documents/eaui/vitest.config.ts"
/Users/wangyang/Documents/eaui/src/App.vue
7:7 error Identifier 't_s' is not in camel case camelcase
7:7 error 't_s' is assigned a value but never used. Allowed unused vars must match /^_/u @typescript-eslint/no-unused-vars
7:7 error 't_s' is assigned a value but never used. Allowed unused vars must match /^_/u no-unused-vars
✖ 3 problems (3 errors, 0 warnings)
ELIFECYCLE Command failed with exit code 1.
husky - pre-commit script failed (code 1)
从结果来看已经生效了,现在让我们按着提示先把检测出来的问题进行修复,再次运行命令进行查看:
$ git add .
$ git commit -m "this will fail"
✔ Preparing lint-staged...
✔ Running tasks for staged files...
✔ Applying modifications from tasks...
✔ Cleaning up temporary files...
cz-cli@4.3.0, cz-conventional-changelog@3.3.0
? 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 (max 94 chars):
(20) code and style rules
? Provide a longer description of the change: (press enter to skip)
some rules for project
? Are there any breaking changes? No
? Does this change affect any open issues? No
Packages: +108
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Progress: resolved 108, reused 107, downloaded 1, added 108, done
[main 10f75d9] feat: code and style rules
29 files changed, 3356 insertions(+), 239 deletions(-)
create mode 100644 .editorconfig
create mode 100644 .eslintignore
create mode 100644 .eslintrc
delete mode 100644 .eslintrc.cjs
create mode 100644 .gitattributes
create mode 100644 .husky/commit-msg
create mode 100644 .husky/pre-commit
create mode 100644 .husky/prepare-commit-msg
create mode 100644 .prettierrc.js
delete mode 100644 .prettierrc.json
create mode 100644 .stylelintignore
create mode 100644 commitlint.config.js
create mode 100644 stylelint.config.js
$ git push
枚举对象中: 59, 完成.
对象计数中: 100% (59/59), 完成.
使用 12 个线程进行压缩
压缩对象中: 100% (31/31), 完成.
写入对象中: 100% (36/36), 43.00 KiB | 7.17 MiB/s, 完成.
总共 36(差异 16),复用 3(差异 0),包复用 0(来自 0 个包)
remote: Resolving deltas: 100% (16/16), completed with 16 local objects.
To github.com:oyo-cool/eaui.git
d4e3320..10f75d9 main -> main
完美,现在我们配置的所有规则都可以通过 husky
来进行触发了。
总结
在这一章里面,我们根据自己的情况配置了git
、.editorconfig
、Prettier
、ESlint
、Stylelint
、
commitlint
、commitizen
和 husky
,在这些规则的约束下能让我们更好的协同,也能写出更好的代码。