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

项目整体结构

技术选型

  • 框架
    • 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那套虽然更稳定完善性强,但是需要将所有文件编译一遍,本地服务启动、热更新较慢,不利于快速迭代开发。

  1. 构建 环境编译主要是rollup
  2. 项目环境管理 cross-env 通过读取,配置各个环境相关配置,例如是否开启mock、是否启用压缩、请求地址
  1. 执行命令 
yarn build:env // 例如 yarn build:prod

prod对应 env.prod环境配置

  1. 代码分析
yarn analysis
  1. 是否开启压缩
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;
}
  1. 开启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;
  }
}
  1. 跨域处理
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;
  }
}

权限验证

  1. 关于token需要在后端资源够得情况尽量还是后端做生成管理,前端负责存储,
  2. 路由挂载还是保持依据权限菜单的路径配置,只不过path的路径只是针对项目的文件夹
    1. 例如 /a/b/c,其实指的是views/a/b/c文件夹路径下的 index.vue / index.tsx
  3. 按钮级别权限依旧保持原有风格

规范验证

项目相关lint验证方式

  1. eslint 用于校验代码格式规范
  2. commitlint 用于校验 git 提交信息规范
  3. stylelint 用于校验 css/less 规范
  4. 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、其他配置需求,仅提供公共参数配置,以及公共模板的创建

工具配置

  1. chalk 高亮打印
  2. commander 设置一些node命令,例如
    1. help
    2. version
    3. parse参数
    4. 创建类型等
  3. leven
    1. 测量两字符串之间的差异<br/>最快的JS实现之一
  4. node fs
    1. 文件的读写,主要是为了保证在无网络、git访问权限的情况下的模板的下载
  5. shelljs
    1. 执行git相关下载,并保存到本地
  6. inquirer
    1. 用户判断是否执行
  7. validate-npm-package-name
    1. 验证用户输入的参数 

初始化创建命令以及创建入口

package文件内

"name": "name", // 全局插件名称
"bin": {
    "comman": "bin/index.js" // 插件执行创建命令
  },

之后

name command 项目名称 描述 -r(源)/-f(是否重置)/-m(是否覆盖)

执行创建

  1. 验证参数
  2. 生成创建路径
  3. 全局参数配置需要用户选择或者输入  使用介绍 
    1. 输入项目名称
    2. 输入版本号
    3. 系统名称
    4. 全局公共其他参数
    5. ….
  4. 执行拉取动作
    1. 有git权限,访问最新git
    2. 没有权限,使用cli本地旧版本
  5. 写入用户配置
  6. 结束创建流程

创建命令

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))
}

Q&A

发表评论

您的电子邮箱地址不会被公开。