携手创作,共同成长!这是我参与「掘金日新计划 · 8 月更文挑战」的第3天,点击查看活动详情
这是大厂企业级项目架构系列的第一篇,这系列的文章如下:
首先简单列一下,在一个新项目初始阶段,我们应该做些什么。
- 首先是技术选型,比如框架选型(Vue|React等),构建工具,包管理工具等等。
- 代码规范,提交规范
- 环境区分,包括开发、测试、生产的构建区分等
- 样式方案,图标方案,布局方案,组件库等
- 基础能力,包括路由、状态管理、持久化、网络请求等
- mock方案
- 根据自身的业务类型来封装相应的公共模块
- 单元测试
- 自动化构建部署
- ....
本篇将以vue3 + vite2 + ts + pnpm来搭建我们的项目,这是大厂的企业级项目架构系列的第一篇,我会先讲解项目的搭建和代码规范。跟着我一步步慢慢搭建出一个大厂该有的企业级项目。
耐心看完,你将会了解到在项目开始阶段是如何落地代码规范的,我们将使用prettier + eslint + stylelint + lint-staged来规范我们的代码。
搭建项目框架
node版本 16.16.0
- 使用vite创建项
// 不同版本或者包管理工具的创建命令可看vite官网
pnpm create vite
创建完后的项目结构如下
├── .vscode 存放本项目推荐安装的vscode插件等
├── README.md
├── .gitignore 提交到git时需要忽略的文件
├── index.html 页面模板
├── node_modules 安装的依赖
├── package.json
├── pnpm-lock.yaml
├── public 静态资源目录,放在这个目录下的资源只会被简单地复制,不会被vite处理,需要绝对路径引用
│ └── vite.svg
├── src 源码目录
│ ├── App.vue
│ ├── assets
│ │ └── vue.svg
│ ├── components
│ │ └── HelloWorld.vue
│ ├── main.ts
│ ├── style.css
│ └── vite-env.d.ts
├── tsconfig.json ts配置文件,针对我们的源码的ts配置文件
├── tsconfig.node.json 专门针对vite.config.ts的ts配置文件,上面的tsconfig.json引用了这个文件
└── vite.config.ts vite配置文件
着重解释一下两个文件
初始化的时候项目依赖如下(package.json)
"dependencies": {
"vue": "^3.2.37" // 用vue,那肯定得依赖vue了
},
"devDependencies": {
"@vitejs/plugin-vue": "^3.0.0", // vite的vue插件,用来解析vue的单文件组件
"typescript": "^4.6.4", // 用到ts,就需要typescript
"vite": "^3.0.0", // 用vite做构建
"vue-tsc": "^0.38.4" //针对vue单文件组件,使用vue-tsc做类型检查
}
全局类型声明文件vite-env.d.ts
/// <reference types="vite/client" />
// 上面是三斜线指令,用于告诉编译器在编译的过程中要引用额外的文件
// 如果是types属性,则用于声明对另一个库的依赖,如上面,则是对vite这个库下的cliend.d.ts的引用,具体可见node_modules
// 如果是path属性,则用于声明对另一个文件的依赖
// 因为这是一个全局的声明文件,所以使用了三斜线指令,如果用了import,则文件就会变成了模块声明文件了
// 下面的意思是是,全局定义一个模块,当import xxx from 'xx.vue';的方式引入模板文件的时候,编译器不会报错
// 且引入的类型是DefineComponent类型
declare module '*.vue' {
import type { DefineComponent } from 'vue'
const component: DefineComponent<{}, {}, any>
export default component
}
代码规范
创建好项目之后,就可以接入代码规范了
使用editorConfig统一不同编辑器的编码风格
在项目根目录下增加.editorConfig,内容如下
root = true # 表明这是最顶层的editorConfig配置文件
[*] # 表示适用于所有文件
charset = utf-8 # 字符集 utf-8
indent_style = tab # 缩进风格 space|tab
indent_size = 4 # 缩进大小是几个空格
end_of_line = lf # 控制换行类型 lf|cr|crlf
trim_trailing_whitespace = true # 去除行首任意空白字符
insert_final_newline = true # 始终在文件末尾插入一个新行
[*.md] # 表示仅 md 文件适用以下规则
max_line_length = off
trim_trailing_whitespace = false
增加配置后安装EditorConfig for VS Code插件即可
使用prettier格式化代码
在根目录下创建.prettierrc,内容如下
module.exports = {
printWidth: 100, // 1行最多100个字符
semi: true, // 要分号结尾
//vueIndentScriptAndStyle: true, // 配置了这个,vue模板文件里面的script 和 style 内就会缩进
singleQuote: true, // 使用单引号
// trailingComma: 'all', // 末尾要有逗号
proseWrap: 'preserve', // 针对markdown文件,如果超过了最大字符数 always 换行| never 不换行| preserve 原样展示
htmlWhitespaceSensitivity: 'strict', // html空格敏感
endOfLine: 'auto', // 行结尾,跟editorConfig的end_of_line一样
};
增加.prettierignore,忽略不需要格式化的内容
/dist/*
/node_modules/**
pnpm-lock.yaml
**/*.svg
**/*.sh
/public/*
增加配置后,安装vscode插件 Prettier - Code formatter,如果想要保存的时候自动格式化,则可以添加配置,为了不影响其他项目,可以在自己项目下的.vscode目录下增加settings.json
{
"editor.formatOnSave": true
}
同时,需要安装prettier,并在package.json中增加脚本,以便后续提交前做格式化
pnpm i -D prettier
{
"scripts": {
"prettier": "npx prettier --write ./src/**/*.{vue,ts,tsx,js,jsx,css,less,scss,json,md}"
}
}
使用eslint保证代码质量
prettier只是处理了代码格式,eslint可以保障我们的代码质量,帮我们规避一些低级错误,如果有不了解eslint的,这里建议先看完我的另外一篇文章,有详细讲解eslint的各种配置,看完这篇文章,我不信你还对eslint一知半解,看完之后你自然就明白为什么会那么配置了。
新增eslint配置
在项目根目录下增加.eslintrc.js
因为要使用eslint以及相应的解释器和插件,使用前需先安装,具体的作用可看配置里面的注释
pnpm i -D eslint vue-eslint-parser eslint-plugin-vue @typescript-eslint/parser @typescript-eslint/eslint-plugin
因为这个项目用的vue3 + ts,所以这里就只针对vue|ts|tsx这几种文件进行校验了。
具体配置和解释如下:
module.exports = {
root: true, // 让eslint不要往父级去继续查找配置
parser: 'vue-eslint-parser', // 因为需要校验vue模板文件里面的html,所以这里使用vue-eslint-parser这个parser作为解释器
parserOptions: {
// 因为上面使用了vue-eslint-parser,它只会校验模板里面的html,对于script里面的ts,仍然需要其他解释器去处理
// 所以vue-eslint-parser就预留了parser选项让我们传进去解释器
parser: '@typescript-eslint/parser',
ecmaVersion: 2020, // 这里我们使用es2020的语法,所以在这里指定一下
sourceType: 'module', // 我们的文件都是模块而非script,所以这里指定成module
ecmaFeatures: {
jsx: true // 项目用到了jsx的写法,所以要开启jsx
},
},
env: {
browser: true, // 项目运行在浏览器环境,所以指定浏览器相关的全局变量
node: true, // 因为某些文件,如某些构建脚本之类的运行在node环境,所以这里也指定了node相关的全局变量
//虽然上面的ecmaVersion指定支持2020的语法,但并不意味着支持es6最新的全局变量和类型,比如Set,所以在这里指定一下。
//需要注意的是,在这里设置es6:true,就会自动启用es6语法,但是上面设置ecmaVersion:6并不会启用es6全局变量
es6: true,
},
extends: [
// 使用eslint-plugin-vue的vue3-recommended规则集校验vue模板
'plugin:vue/vue3-recommended',
// 使用@typescript-eslint/eslint-plugin来校验ts,注意需配合@typescript-eslint/parser使用
'plugin:@typescript-eslint/recommended',
]
};
配置完可以先命令行尝试一下是否生效,在package.json中增加脚本如下,然后写一些不符合规则的代码测试一下,比如使用一个未定义的变量
{
"scripts": {
"lint": "npx eslint ./src --ext .vue,.ts,.tsx",
"lint:fix": "npx eslint ./src --ext .vue,.ts,.tsx --fix"
}
}
确保配置没问题后,可以在vscode中安装eslint插件,同时可以把这个插件加入到工作空间的推荐插件(.vscode/extensions.json)里面,然后在.vscode/settings.json里面增加如下配置:
{
// onSave:保存的时候进行校验|onType:输入的时候就进行lint
"eslint.run": "onSave",
// 假如你想保存的时候帮你修复错误,则可以添加如下提示,但是因为本人想自己养成好的编码习惯,所以就不采用保存时修复了
// "editor.codeActionsOnSave": {
// "source.fixAll.eslint": true
// }
}
配置完之后就可以在编辑器里面有相应的提示了,如果没有,则可能你需要升级你的vscode的eslint插件到最新版本,或者重启vscode
解决prettier和eslint的冲突
假如你同时在vscode插件里吗配置了prettier和eslint的保存时修复,则可能因为规则不一样而引起冲突,因为prettier负责代码格式,eslint则同时可以支持代码格式和代码质量,这个时候我们希望代码格式由prettier负责就好,而eslint则只需负责代码质量。
解决方案是借助eslint-config-prettier和eslint-plugin-prettier。
eslint-config-prettier作用是关闭eslint中与prettier冲突的规则。eslint-plugin-prettier作用是让eslint用prettier来格式化,相当于当代码格式不符合prettier的配置的时候,也可以报错一个eslint错误
修改配置如下:
module.exports = {
extends: [
// 新增如下的配置,实际上就是eslint-plugin-prettier的推荐规则集
// 而eslint-plugin-prettier实际上就是依赖eslint-config-prettier来关闭冲突的,使用前需同时安装这两个包
'plugin:prettier/recommended'
]
};
有兴趣的同学可以看一下下面eslint-plugin-prettier是如何实现的,没兴趣的可以略过
// eslint-plugin-prettier.js
module.exports = {
// plugin:prettier/recommended 就是加载这个
configs: {
recommended: {
// 里面依赖了eslint-config-prettier
extends: ['prettier'],
plugins: ['prettier'],
rules: {
'prettier/prettier': 'error',
'arrow-body-style': 'off',
'prefer-arrow-callback': 'off',
},
},
},
// ...rules
}
最后,完整的eslint配置如下:
module.exports = {
root: true, // 让eslint不要往父级去继续查找配置
parser: 'vue-eslint-parser', // 因为需要校验vue模板文件里面的html,所以这里使用vue-eslint-parser这个parser作为解释器
parserOptions: {
// 因为上面使用了vue-eslint-parser,它只会校验模板里面的html,对于script里面的ts,仍然需要其他解释器去处理
// 所以vue-eslint-parser就预留了parser选项让我们传进去解释器
parser: '@typescript-eslint/parser',
ecmaVersion: 2020, // 这里我们使用es2020的语法,所以在这里指定一下
sourceType: 'module', // 我们的文件都是模块而非script,所以这里指定成module
jsxPragma: 'React',
ecmaFeatures: {
jsx: true, // 项目用到了jsx的写法,所以要开启jsx
},
},
env: {
browser: true, // 项目运行在浏览器环境,所以指定浏览器相关的全局变量
node: true, // 因为某些文件,如某些构建脚本之类的运行在node环境,所以这里也指定了node相关的全局变量
//虽然上面的ecmaVersion指定支持2020的语法,但并不意味着支持es6最新的全局变量和类型,比如Set,所以在这里指定一下。
//需要注意的是,在这里设置es2020:true,就会自动启用es2020语法,但是上面设置ecmaVersion:2020并不会启用es2020全局变量
es2020: true,
},
extends: [
'eslint:recommended',
// 使用eslint-plugin-vue的vue3-recommended规则集校验vue模板
'plugin:vue/vue3-recommended',
// 使用@typescript-eslint/eslint-plugin来校验ts,注意需配合@typescript-eslint/parser使用
'plugin:@typescript-eslint/recommended',
// 用eslint-plugin-prettier的推荐规则集来解决prettier和eslint的冲突
// 而eslint-plugin-prettier实际上就是依赖eslint-config-prettier来关闭冲突的
// eslint-config-prettier作用是关闭eslint中与prettier冲突的规则。
// eslint-plugin-prettier作用是让eslint用prettier来格式化,相当于当代码格式不符合prettier的配置的时候,也可以报错一个eslint错误
'plugin:prettier/recommended',
],
rules: {
'vue/multi-word-component-names': 'off',
'vue/attributes-order': 'off',
'vue/one-component-per-file': 'off',
'vue/html-closing-bracket-newline': 'off',
'vue/max-attributes-per-line': 'off',
'@typescript-eslint/ban-ts-ignore': 'off',
'@typescript-eslint/explicit-function-return-type': 'off',
'@typescript-eslint/no-explicit-any': 'off',
// 剩下具体的规则可自行根据需要关闭
},
};
我们还需要在根目录增加一个.eslintignore来忽略那些无需校验的文件,如下:
*.sh
node_modules
*.md
*.woff
*.ttf
.vscode
.idea
dist
/public
/docs
.husky
/bin
至此,就完成了对项目代码规范相关的配置了
使用husky和lint-staged
此时在开发阶段假如我们的代码不符合规范,vscode编辑器就会给我们相应的提示了,且也支持保存时自动修复了,但是这也不能确保每个人都会遵守这些规则,也许有些人就是不想装这些插件,直接把不合规范的代码push了。所以我们需要再加一些限制,防止没通过eslint检测的代码直接被push到代码仓库。
要想实现上述的限制,我们可以利用git hooks,在commit之前执行一些动作,比如eslint校验和修复,没通过检验则禁止提交。
我们借助husky和lint-staged来实现提交前校验。
你可以跟着npm上写的步骤来安装和初始化husky,也可以使用husky-init命令来初始化,两种方式的作用是一样的,我这里使用husky-init,如下所示
pnpm i -D husky-init
npx husky-init
执行完命令husky-init之后,它就会自动帮我们安装husky,并创建.husky目录,在该目录下会创建一个pre-commit的hook,我们只需要修改这个hook里面的命令,改成我们想要执行的操作即可,如下所示
#!/usr/bin/env sh
. "$(dirname -- "$0")/_/husky.sh"
# 这里的lint脚本是在package.json里面配置的,执行这个就相当于执行npx eslint
npm run lint
配置完成后,我们每次commit前就会执行eslint来校验我们的代码了,只有校验通过才可以commit。
但是此时效率是非常低的,因为我们每次commit都会对项目的所有文件执行lint,如果项目很大的话,效率就很低了。我们其实只想校验本次改动的代码,这个时候lint-staged就派上用场了。
lint-staged可以让husky的hook触发的命令只作用于我们本次通过git add添加到暂存区的那些文件,可以大大地优化我们的项目。
老规矩,使用前先安装
pnpm i -D lint-staged
然后修改package.json如下
{
"scripts": {
"lint:lint-staged": "lint-staged"
},
// 针对不同类型的文件做不同的处理
"lint-staged": {
"*.{vue,js,jsx,ts,tsx}": [
"prettier --write",
// 这里就不fix了,只校验,当然你也可以选择fix
"eslint"
],
"package.json": [
"prettier --write"
],
"*.md": [
"prettier --write"
]
}
}
然后将公共pre-commit这个hook里的脚本改成npm run lint:lint-staged,此时就可以commit一下测试有没有生效了
规范样式代码(可选)
除了上述的对html和js的代码的规范,还可以使用stylelint对样式代码进行规范,当然,这个就看团队喜好了,可能很多团队会觉得这个的重要性并没有那么高,就没有强制对样式代码作出规范。不过其实使用stylelint除了规范样式代码,还有个有点,就是可以保证我们的css属性的书写顺序。好的书写顺序可以减少浏览器回流,提高渲染性能,所以我们本次的工程也要接入stylelint。
因为eslint的配置我单独写过一篇文章,但是stylelint没有,所以这里会写的相对详细一点,让大家明白为什么要这么配置。
stylelint的使用跟eslint差不多,首先要安装stylelint和相关的包,相关包的作用如下:
stylelint-config-standard: stylelint官方的规则集,它继承了stylelint-config-recommendedstylelint-config-prettier: 用来禁用掉stylelint中与格式相关的规则, 防止跟eslint冲突
pnpm i -D stylelint stylelint-config-standard
在项目根目录下创建stylelint.config.js,配置内容如下,详细配置看里面的注释
module.exports = {
// 继承官方的规则集,同时stylelint-config-prettier放在后面,禁用格式相关的规则,覆盖掉前面的
extends: ['stylelint-config-standard', 'stylelint-config-prettier'],
// 忽略这些后缀的文件
ignoreFiles: ['**/*.js', '**/*.jsx', '**/*.tsx', '**/*.ts'],
};
这个时候已经可以对css文件进行校验了,但还不能对.vue文件中style标签里面的样式进行校验,此时需要其他的语法解释器来处理.vue文件,类似于eslint中parser的概念,这里我们用postcss-html来处理类html文件(html、vue等),使stylelint可以对这些文件做校验。
安装postcss和postcss-html
pnpm i -D postcss postcss-html
修改配置如下
module.exports = {
// 继承官方的规则集,同时stylelint-config-prettier放在后面,禁用格式相关的规则,覆盖掉前面的
extends: ['stylelint-config-standard', 'stylelint-config-prettier'],
// 忽略这些后缀的文件
ignoreFiles: ['**/*.js', '**/*.jsx', '**/*.tsx', '**/*.ts'],
// 加上这个,就可以处理vue模板
customSyntax: 'postcss-html'
};
但此时像上面的这么改,虽然可以校验.vue里面的模板文件,但是你会发现css文件又校验不了了,所以我们可以像下面这么写,单独针对.vue文件做配置
module.exports = {
// 继承官方的规则集,同时stylelint-config-prettier放在后面,禁用格式相关的规则,覆盖掉前面的
extends: ['stylelint-config-standard', 'stylelint-config-prettier'],
// 忽略这些后缀的文件
ignoreFiles: ['**/*.js', '**/*.jsx', '**/*.tsx', '**/*.ts'],
// 通过overrides单独针对某些类型的文件处理
overrides: [
{
// 针对html和vue文件进行处理
files: ['*.html', '**/*.html', '**/*.vue'],
// 在这里配置针对vue文件使用postcss-html作为语法解析器
customSyntax: 'postcss-html'
},
],
};
此时就既可以校验vue文件又可以校验css文件了,不过上面👆🏻的写法其实还可以简化成像下面一样
module.exports = {
// 使用前需先安装
// stylelint-config-recommended-vue也继承了stylelint-config-standard
// 所以这里就不用再写stylelint-config-standard了
// 同时stylelint-config-recommended-vue里面也是像上面一样通过override为vue文件指定了解释器
// stylelint-config-recommended-vue还帮我们增加了几条针对vue文件里面的样式校验规则
extends: ['stylelint-config-recommended-vue', 'stylelint-config-prettier'],
// 忽略这些后缀的文件
ignoreFiles: ['**/*.js', '**/*.jsx', '**/*.tsx', '**/*.ts'],
};
改成像上面的配置之后,可以先验证一下是否可以校验css和vue文件
npx stylelint ./src
但是因为我们的项目打算使用less,所以我们还需要针对.less文件指定postcss-less,假如你项目用的是scss那你就指定成postcss-scss就可以了
module.exports = {
extends: ['stylelint-config-recommended-vue', 'stylelint-config-prettier'],
ignoreFiles: ['**/*.js', '**/*.jsx', '**/*.tsx', '**/*.ts'],
overrides: [
{
// 针对less件进行处理
files: ['*.less', '**/*.less'],
// 在这里配置针对less文件使用postcss-less作为语法解析器
customSyntax: 'postcss-less',
},
],
};
通过上面的配置之后,你就可以同时对vue|css|less做校验了。
此时,我们还有很重要的一步,就是要规范我们的css属性的书写顺序。这个时候我们可以通过stylelint-order这个插件来处理,这个插件使用方式如下
module.exports = {
//... 前面的省略
plugins: ['stylelint-order'],
rules: {
// css属性的书写顺序需按照下面的来
"order/properties-order": [
"position",
"top",
"right",
"bottom",
"left",
// ....
]
},
};
但是像上面那么写就有点太长了,此时我们可以使用第三方开源的包stylelint-config-rational-order,使用他们封装好的推荐顺序,所以上面可以改成如下的写法:
module.exports = {
extends: [
// 注意stylelint 14的版本里在vue文件中可能会出现Unknown word (CssSyntaxError)这个报错
// 此时可以使用stylelint-config-standard-scss这个包来解决这个报错
// 当然你也可以选择降低版本来解决
'stylelint-config-standard-scss',
// 'stylelint-config-standard',
// 下面这个包已经包含了stylelint-config-standard,所以上面就可以注释掉了
'stylelint-config-recommended-vue',
// 关闭stylelint中校验格式的规则,以免跟prettier冲突
'stylelint-config-prettier',
// 下面是基于stylelint-order的规范css属性书写顺序的包,这样我们就不用自己在rules中手动指定了
'stylelint-config-rational-order',
],
ignoreFiles: ['**/*.js', '**/*.jsx', '**/*.tsx', '**/*.ts'],
rules: {
// 使用tab缩进
indentation: 'tab',
// 如果你有想配置的规则或者关闭的规则,只需在这里配置即可
},
overrides: [
// 上面的extends里的stylelint-config-recommended-vue已经帮我们指定了vue文件的解释器了,所以下面的也可以注释掉了
// {
// // 针对html和vue文件进行处理
// files: ['*.html', '**/*.html', '**/*.vue'],
// // 在这里配置针对vue文件使用postcss-html作为语法解析器
// customSyntax: 'postcss-html'
// },
{
files: ['**/*.less'],
customSyntax: 'postcss-less',
},
],
};
我们需要再增加一个stylelint的忽略配置.stylelintignore如下
/dist/*
/public/*
public/*
/node_modules/*
到这里你就可以在package.json中增加样式的校验脚本了,如下
{
"scripts": {
"lint:style": "stylelint --fix \"**/*.{vue,less,postcss,css,scss}\""
},
}
确保脚本可以正常运行之后,我们再安装一个stylelint的vscode插件,这样就可以在输入的时候就给我们提示,也可以保存时给我们修复了,将这个插件也加入到.vscode/extensions.json里作为共享的配置,并在.vscode/settings.json里面增加如下配置开启校验
{
"stylelint.enable": true,
"stylelint.validate": ["css", "less", "postcss", "scss", "vue", "sass"],
"editor.codeActionsOnSave": {
"source.fixAll.stylelint": true
}
}
最后,我们同样需要在commit之前校验样式,所以我们在上面写好的lint-stage的基础上修改成如下配置:
// package.json
{
"scripts": {
"lint:lint-staged": "lint-staged"
},
// 针对不同类型的文件做不同的处理
"lint-staged": {
"*.{js,jsx,ts,tsx}": [
// 这里就不fix了,只校验,当然你也可以选择fix
"eslint"
"prettier --write",
],
"*.vue": [
// 不fix逻辑代码
"eslint",
"prettier --write",
"stylelint --fix"
],
"package.json": [
"prettier --write"
],
"*.md": [
"prettier --write"
]
}
}
做完这一步之后,关于代码规范相关的就配置完了,
总结
代码规范是前端项目架构的重要一环。为了避免篇幅过长,本篇先讲解了项目初始化阶段是如何使用prettier + eslint + stylelint来规范我们的代码的,同时使用husky + stylelint来做提交前的代码校验。接下来的系列文章我将继续完善项目,讲解关于提交规范、样式方案、基础能力(网络请求、路由、数据存储)等内容,有兴趣的同学可以关注一下,如果可以的话,点个赞支持一下吧。
往期文章推荐: