项目模板设计
随着业务发展,需求更迭快,开发人员增多,为了保持项目持久的稳定性和规范性,一个功能全面、规范化、易用性、安全性强大的项目模板或者脚手架是必须的,其次统一规范的统一流程的模板大大减少开发的重复工作,降低重复代码的概率
项目整体结构

技术选型
- 框架
- vue3
- element UI | ant-design-vue
- 编译
- vite
- 预编译
- sass
- less
- stylus
- 规范化
- eslint
- husky + commit.lint
- prettier
- 状态管理
- vuex
- reactive
- pinia(推荐)
支持两种语法创建 Store:Options Api 和 Composition Api;
删除 mutations,只支持 state、getters、actions;
模块化的设计,能很好支持代码分割;
没有嵌套的模块,只有 Store 的概念;
完整的 TypeScript 支持;
- 静态类型检查
- typescript
- 包管理器
- pnpm
- yarn
- npm
环境变量配置
项目的环境变量配置位于项目根目录下的env, env[mode]
.env # 在所有的环境中被载入
.env.local # 在所有的环境中被载入,但会被 git 忽略
.env.[mode] # 只在指定的模式中被载入
.env.[mode].local # 只在指定的模式中被载入,但会被 git 忽略
构建
构建方面主要是求快,稳定,易用,webpack那套虽然更稳定完善性强,但是需要将所有文件编译一遍,本地服务启动、热更新较慢,不利于快速迭代开发。
- 构建 环境编译主要是rollup
- 项目环境管理 cross-env 通过读取,配置各个环境相关配置,例如是否开启mock、是否启用压缩、请求地址
- 执行命令
yarn build:env // 例如 yarn build:prod
prod对应 env.prod环境配置
- 代码分析
yarn analysis
- 是否开启压缩
env.xxx下
VITE_BUILD_COMPRESS="none gzip brotli"
// 功能主要是由vite-plugin-compression
开启gzip需要Nginx配置
http {
# 开启gzip
gzip on;
# 开启gzip_static
# gzip_static 开启后可能会报错,需要安装相应的模块, 具体安装方式可以自行查询
# 只有这个开启,vue文件打包的.gz文件才会有效果,否则不需要开启gzip进行打包
gzip_static on;
gzip_proxied any;
gzip_min_length 1k;
gzip_buffers 4 16k;
#如果nginx中使用了多层代理 必须设置这个才可以开启gzip。
gzip_http_version 1.0;
gzip_comp_level 2;
gzip_types text/plain application/javascript application/x-javascript text/css application/xml text/javascript application/x-httpd-php image/jpeg image/gif image/png;
gzip_vary off;
gzip_disable "MSIE [1-6]\.";
# 开启 brotli压缩
# 需要安装对应的nginx模块,具体安装方式可以自行查询
# 可以与gzip共存不会冲突
brotli on;
brotli_comp_level 6;
brotli_buffers 16 8k;
brotli_min_length 20;
brotli_types text/plain text/css application/json application/x-javascript text/xml application/xml application/xml+rss text/javascript application/javascript image/svg+xml;
}
- 开启history模式
import { createRouter, createWebHashHistory, createWebHistory } from 'vue-router';
createRouter({
history: createWebHashHistory(),
// or
history: createWebHistory(),
});
nginx
server {
listen 80;
location / {
# 用于配合 History 使用
try_files $uri $uri/ /index.html;
}
}
- 跨域处理
server {
listen 8080;
server_name localhost;
# 接口代理,用于解决跨域问题
location /api {
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
# 后台接口地址
proxy_pass http://110.110.1.1:8080/api;
proxy_redirect default;
add_header Access-Control-Allow-Origin *;
add_header Access-Control-Allow-Headers X-Requested-With;
add_header Access-Control-Allow-Methods GET,POST,OPTIONS;
}
}
权限验证
- 关于token需要在后端资源够得情况尽量还是后端做生成管理,前端负责存储,
- 路由挂载还是保持依据权限菜单的路径配置,只不过path的路径只是针对项目的文件夹
- 例如 /a/b/c,其实指的是views/a/b/c文件夹路径下的 index.vue / index.tsx
- 按钮级别权限依旧保持原有风格
规范验证
项目相关lint验证方式
- eslint 用于校验代码格式规范
- commitlint 用于校验 git 提交信息规范
- stylelint 用于校验 css/less 规范
- prettier 代码格式化
优势
- 较少 bug 错误率
- 高效的开发效率
- 更高的可读性
- 保持人员过多情况下风格统一
eslint
eslint主要是一个代码规范和错误检查的工具
可以手动执行全局eslint检查
yarn lint:eslint
配置项
主要是项目中的.eslintrc.js, 可以按照开发过程中的实际需求设定代码规范
配合vscode使用效果更好
推荐使用 vscode 进行开发,vscode 自带 eslint 插件,可以自动修改一些错误。
同时项目内也自带了 vscode eslint 配置,具体在 .vscode/setting.json 文件夹内部。只要使用 vscode 开发不用任何设置即可使用
commitLint
在一个团队中,每个人的 git 的 commit 信息都不一样,五花八门,没有一个机制很难保证规范化,如何才能规范化呢?可能你想到的是 git 的 hook 机制,去写 shell 脚本去实现。这当然可以,其实 JavaScript 有一个很好的工具可以实现这个模板,它就是 commitlint(用于校验 git 提交信息规范)。
配置
commit-lint 的配置位于项目根目录下 commitlint.config.js
Git 提交规范
- 参考 vue 规范 (Angular)
- feat 增加新功能
- fix 修复问题/BUG
- style 代码风格相关无影响运行结果的
- perf 优化/性能提升
- refactor 重构
- revert 撤销修改
- test 测试相关
- docs 文档/注释
- chore 依赖更新/脚手架配置修改等
- workflow 工作流改进
- ci 持续集成
- mod 不确定分类的修改
- wip 开发中
- types 类型修改
示例
git commit -m "feat: 添加一个新页面"
// 每次commit都会走格式验证,稍微有点时间,不过不长,几秒钟
styleLint
stylelint 用于校验项目内部 css 的风格,加上编辑器的自动修复,可以很好的统一项目内部 css 风格
配置
stylelint 配置位于根目录下 stylelint.config.js
配合vscode
需要额外安装styleLint插件,并开启
prettier
prettier 可以用于统一项目代码风格,统一的缩进,单双引号,尾逗号等等风格
配置
prettier 配置文件位于项目根目录下 prettier.config.js
配合vscode
一般都是使用项目域 的 .vscode配置,因为容易冲突
git hook
git hook 一般结合各种 lint,在 git 提交代码的时候进行代码风格校验,如果校验没通过,则不会进行提交。需要开发者自行修改后再次进行提交
husky配置
有一个问题就是校验会校验全部代码,但是我们只想校验我们自己提交的代码,这个时候就可以使用 husky。
最有效的解决方案就是将 Lint 校验放到本地,常见做法是使用 husky 或者 pre-commit 在本地提交之前先做一次 Lint 校验。
项目在 .husky 内部定义了相应的 hooks
lint-staged配置
用于自动修复提交文件风格问题
lint-staged 配置存放在位于项目 .husky 目录下 lintstagedrc.js
module.exports = {
// 对指定格式文件 在提交的时候执行相应的修复命令
'*.{js,jsx,ts,tsx}': ['eslint --fix', 'prettier --write'],
'{!(package)*.json,*.code-snippets,.!(browserslist)*rc}': ['prettier --write--parser json'],
'package.json': ['prettier --write'],
'*.vue': ['eslint --fix', 'stylelint --fix', 'prettier --write', 'git add .'],
'*.{scss,less,styl,css,html}': ['stylelint --fix', 'prettier --write', 'git add .'],
'*.md': ['prettier --write'],
};
模版拉取CLI
公司内部的公共模板一般都是预先设计的统一风格,所以不会提供个性化的框架、UI、其他配置需求,仅提供公共参数配置,以及公共模板的创建
工具配置
- chalk 高亮打印
- commander 设置一些node命令,例如
- help
- version
- parse参数
- 创建类型等
- leven
- 测量两字符串之间的差异<br/>最快的JS实现之一
- node fs
- 文件的读写,主要是为了保证在无网络、git访问权限的情况下的模板的下载
- shelljs
- 执行git相关下载,并保存到本地
- inquirer
- 用户判断是否执行
- validate-npm-package-name
- 验证用户输入的参数
初始化创建命令以及创建入口
package文件内
"name": "name", // 全局插件名称
"bin": {
"comman": "bin/index.js" // 插件执行创建命令
},
之后
name command 项目名称 描述 -r(源)/-f(是否重置)/-m(是否覆盖)
执行创建
- 验证参数
- 生成创建路径
- 全局参数配置需要用户选择或者输入 使用介绍
- 输入项目名称
- 输入版本号
- 系统名称
- 全局公共其他参数
- ….
- 执行拉取动作
- 有git权限,访问最新git
- 没有权限,使用cli本地旧版本
- 写入用户配置
- 结束创建流程
创建命令
program
.command('command <app-name>')
.description('通过脚手架创建一个项目')
.option('-r, --registry <url>', '项目模板来源)')
.option('-f, --force', '重新写入')
.option('-m, --merge', '合并覆盖')
.action((name, cmd) => {
create(name, cmd)
})
验证参数合法性
async function create(projectName, options) {
const cwd = options.cwd || process.cwd()
const inCurrent = projectName === '.'
const name = inCurrent ? path.relative('../', cwd) : projectName
const targetDir = path.resolve(cwd, projectName || '.')
const result = validateProjectName(name)
// 验证名称的合法性
if (!result.validForNewPackages) {
console.error(chalk.red(`Invalid project name: "${name}"`))
result.errors && result.errors.forEach(err => {
console.error(chalk.red.dim('Error: ' + err))
})
result.warnings && result.warnings.forEach(warn => {
console.error(chalk.red.dim('Warning: ' + warn))
})
process.exit(1)
}
.....
}
执行公共配置录入以及信息获取
const actions = await inquirer.prompt(promptsModules)
执行拉取项目
// 确定git仓库地址
const remote = '仓库地址'
// 执行shell命令将最新的代码
await shell.exec(`
git clone ${remote} ${targetDir}
`, async (err, stdout, stderr) => {
// 克隆鉴权失败
if (err !== 0) {
// 执行本地备份数据拷贝
createByBackUp(targetDir, actions, name)
return
}
// 克隆成功
await shell.exec(`
rm -rf ${targetDir}/.git
cd ${targetDir}
echo '📦 Installing additional dependencies...'
git init -q
${options.registry && 'git remote add origin ' + options.registry}
${ hasYarn() ? 'yarn' : 'npm i' }
`, async (err, stdout, stderr) => {
if (err !== 0) {
createByBackUp(targetDir, actions, name)
return
}
// 写入配置文件信息
console.log(`✨ Writing configuration information to the project.`)
await writeConfig(targetDir, actions)
console.log(`🎉 Successfully created project ${chalk.yellow(name)}.`)
console.log(`👉 Get started with the following commands:\n\n` +
(targetDir === process.cwd() ? `` : chalk.cyan(` ${chalk.gray('$')} cd ${name}\n`)) +
chalk.cyan(` ${chalk.gray('$')} yarn serve`))
})
})
本地数据拷贝
const createByBackUp = (targetDir, actions, name) => {
console.log(`🍊 使用备份数据创建 ${chalk.yellow(targetDir)}.`)
// 创建根目录
mkdirp.sync(name)
const generator = new Generator({
name,
env: { cwd: targetDir },
resolved: require.resolve("本地模板地址")
});
generator.run(async () => {
await writeConfig(targetDir, actions)
console.log(`🎉 Successfully created project ${chalk.yellow(name)}.`)
console.log(`👉 Get started with the following commands:\n\n` +
(targetDir === process.cwd() ? `` : chalk.cyan(` ${chalk.gray('$')} cd ${name}\n`)) +
chalk.cyan(` ${chalk.gray('$')} yarn \n`)+
chalk.cyan(` ${chalk.gray('$')} yarn serve`))
})
}
配置信息写入
const { writeFileSync } = require('fs-extra')
const { join } = require('path')
function writeConfig (context, actions) {
const { systemName, projectName, description, version } = actions
const baseConfig = {
devEnv,
systemName,
projectName,
...
}
const package = require(join(context, '/package.json'))
package.name = projectName
package.description = description
package.version = version || '1.0.0'
writeFileSync(join(context, '模板配置文件地址'), JSON.stringify(baseConfig, null, 2))
writeFileSync(join(context, '/package.json'), JSON.stringify(package, null, 2))
}