【大厂企业级项目架构】之项目搭建和代码规范

3,988 阅读19分钟

携手创作,共同成长!这是我参与「掘金日新计划 · 8 月更文挑战」的第3天,点击查看活动详情

这是大厂企业级项目架构系列的第一篇,这系列的文章如下:

首先简单列一下,在一个新项目初始阶段,我们应该做些什么。

  • 首先是技术选型,比如框架选型(Vue|React等),构建工具,包管理工具等等。
  • 代码规范,提交规范
  • 环境区分,包括开发、测试、生产的构建区分等
  • 样式方案,图标方案,布局方案,组件库等
  • 基础能力,包括路由、状态管理、持久化、网络请求等
  • mock方案
  • 根据自身的业务类型来封装相应的公共模块
  • 单元测试
  • 自动化构建部署
  • ....

本篇将以vue3 + vite2 + ts + pnpm来搭建我们的项目,这是大厂的企业级项目架构系列的第一篇,我会先讲解项目的搭建和代码规范。跟着我一步步慢慢搭建出一个大厂该有的企业级项目。

耐心看完,你将会了解到在项目开始阶段是如何落地代码规范的,我们将使用prettier + eslint + stylelint + lint-staged来规范我们的代码。

搭建项目框架

node版本 16.16.0

  1. 使用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-prettiereslint-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校验和修复,没通过检验则禁止提交。

我们借助huskylint-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-recommended
  • stylelint-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来做提交前的代码校验。接下来的系列文章我将继续完善项目,讲解关于提交规范、样式方案、基础能力(网络请求、路由、数据存储)等内容,有兴趣的同学可以关注一下,如果可以的话,点个赞支持一下吧。

往期文章推荐: