【二】项目规范和项目管理

2024-09-24 14:40:13 浏览数 (2)

前言

在这一章我们进行一个简单的项目规范和项目管理,为了更好的代码协同,我们选择使用 Git 对代码进行管理并通过一系列 npm 包配置相应的规范约束。

内容

本章内容涉及到的文档如下:

git:https://git-scm.com/

editorconfig:https://editorconfig.org/

EditorConfig for VS Code:EditorConfig for VS Code

配置Git

让我们打开项目,运行终端,现在开始执行第一个命令,让我们初始化 Git 仓库:

代码语言:shell复制
$ git init   
已初始化空的 Git 仓库于 /Users/wangyang/Documents/eaui/.git/

接下来,让我们来添加远程仓库地址,并进行第一次初始化提交:

代码语言:shell复制
$ git remote add origin git@github.com:oyo-cool/eaui.git
$ git add .
$ git commit -m "init"  
$ git push --set-upstream origin main

如果没有出现意外的话,您将会看到类似以下的输出:

代码语言:shell复制
枚举对象中: 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,内容如下:

代码语言:txt复制
*.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脚手架已经帮我们配置好了ESlintPrettier ,不过那样远远不够,现在让我们根据自己的规则再来进行完善。

配置.editorconfig

EditorConfig 有助于为跨各种编辑器和 IDE 处理同一项目的多个开发人员保持一致的编码样式。

代码语言:shell复制
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 进行更新,强迫症患者又加上是新项目,刚好看看有没有坑,可以提前踩踩。

代码语言:shell复制
$ pnpm up prettier

更新完成后,我们移除当前项目中的配置文件.prettierrc.json ,创建一个新的配置文件 .prettierrc.js ,配置内容如下:

代码语言:ts复制
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',
};

配置好之后,让我们来试试运行命令会有什么样的结果:

代码语言:shell复制
$ 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

代码语言:shell复制
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

代码语言:txt复制
dist
lib
es
esm
node_modules
static
cypress
playwright-report
tests-results
static/
!.prettierrc.js

配置好忽略文件之后,先移除当前的配置文件 .eslintrc.cjs,再创建一个全新的配置文件 .eslintrc ,内容如下:

代码语言:js复制
{
  "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 增加以下命令:

代码语言:json复制
{
"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 进行验证:

代码语言:shell复制
 $ 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

代码语言:shell复制
$ pnpm add -D stylelint stylelint-config-standard stylelint-order postcss-html postcss-less

安装完成后,我们先增加一个忽略配置 .stylelintignore ,内容如下:

代码语言:txt复制
*.min.css

# 其他类型文件
*.js
*.jpg
*.woff

接下来就是正主了,添加配置文件 stylelint.config.js,内容如下:

代码语言: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 中增加以下命令:

代码语言:json复制
{
   "scripts": {
    "stylelint": "stylelint src/**/*.{html,vue,sass,less}",
    "stylelint:fix": "stylelint --fix src/**/*.{html,vue,vss,sass,less}",
   }
}

同样的,我们在代码中搞一些不符合规则的东西,然后再运行命令检查:

代码语言:shell复制
$ 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.

现在再让我们尝试下,运行修复命令看看是否可以正常修复:

代码语言:shell复制
$  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/@commitlint/cli

config-conventional:https://github.com/conventional-changelog/commitlint/tree/master/@commitlint/config-conventional

代码语言:shell复制
$ pnpm add -D  @commitlint/{cli,config-conventional}

安装完成后,我们来配置一下:

代码语言:shell复制
echo "export default { extends: ['@commitlint/config-conventional'] };" > commitlint.config.js

为了更好的限制,我们增加下类型枚举,具体的含义建议直接访问 conventionalcommits 进行查看,配置内容如下:

代码语言:js复制
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/

代码语言:shell复制
$  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

代码语言:shell复制
$ 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 的配置,配置内容如下:

代码语言:json复制
{
 "lint-staged": {
    "*.{js,jsx,vue,ts,tsx}": [
      "prettier --write",
      "pnpm lint:fix"
    ],
    "*.{html,vue,vss,sass,less}": [
      "pnpm stylelint:fix"
    ]
  }
}

测试

现在基本的已经配置完成,我们来进行看看。

代码语言:shell复制
$ git add .
$ git commit -m "this will fail"

运行结果如下:

代码语言:shell复制
✔ 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)

从结果来看已经生效了,现在让我们按着提示先把检测出来的问题进行修复,再次运行命令进行查看:

代码语言:shell复制
$ 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.editorconfigPrettierESlintStylelint

commitlintcommitizenhusky ,在这些规则的约束下能让我们更好的协同,也能写出更好的代码,当然了实际中也需要书面的一些规范约束,不过我们这里不进行展开。

0 人点赞