vue3项目经验分享

3,793 阅读3分钟

前言

初衷

在这里分享一下公司自己的vue3项目从0到1创建的过程,包括其中遇到的一些问题和解决方式,以及一些能够提高我们开发效率的小技巧。如果又不好或者可以改进的地方,欢迎大家指正。

使用背景

当时还没有出vu3.1的版本,出于稳定性考虑,当时使用的是vue3.0.0版本,当时是觉得这个版本兼容了2.x的写法(当然我们都是推荐是3的写法,对于没有接触过3.x版本的开发同学会更友好,让他们能有一个慢慢适应过程。)

项目介绍

包管理工具

  • 建议使用 yarn,也是 vue-cli4.0+ 默认工具(我用的是@vue/cli 4.5.13)

主要用到的库

  • vue 全家桶 vue3 + vue-router + vuex + typescript + ant-design-vue + less
  • http 请求:axios
  • ui 库:ant-design-vue
  • 提交规范:git cz commitizen
  • 版本更改历史: changelog(暂无)
  • 代码检查:eslint+eslint-typescript,格式化:prettier.
  • webpack 插件:terser-webpack-plugin(压缩文件)webpack-bundle-analyzer(打包文件分析)
  • 数据持久化:使用vuex-persistedstate包实现数据持久化

代码基础架构说明

|-- 根目录
    
    |-- dist 项目 build 之后的文件夹
    |-- public 项目静态资源,不经过 webpack,以及默认的模版,适合存放第三方压缩好的资源
    |-- src 主要的开发目录
    | |-- @types 项目共用的 type
    | |-- App.vue 页面渲染根节点
    | |-- main.ts 入口文件
    | |-- shims-vue.d.ts vue 文件类型的 type
    | |-- services http 请求相关
    | | |-- config  请求配置相关
    | | | |-- axios.ts 业务请求封装
    | | | |-- download.ts 文件下载方法封装
    | | | |-- plugin.ts 相关插件封装
    | | | |-- prefix.ts 静态网关头配置
    | | |-- modules  各个模块请求接口配置
    | | | |-- moduleA  A模块接口
    | | | |-- moduleB  B模块接口
    | |-- assets 存放静态资源,这个文件夹下的文件会走 webpack 压缩流程
    | |-- components
    | | |-- global 全局组件放在这里 最好按功能类型划分文件夹(配置了子文件夹一会递归全局注册)
    | | |-- index.ts 全局组件自动注册脚本
    | |-- config 全局静态配置
    | |-- layout 页面页面骨架
    | |-- plugins 存放第三方插件
    | | |-- index.ts 插件挂载入口
    | | |-- antd.ts antd组件注册入口
    | |-- router 路由
    | | |-- index.ts 路由入口
    | | |-- ... 其他模块路由配置,会自动装载
    | |-- store vuex
    | | |-- modules 多个模块
    | | |-- index.ts 自动装载模块
    | | |-- app app 模块
    | |-- styles 全局样式, ui 库主题样式
    | | |-- antd.less
    | | |-- reset.less
    | | |-- index.less
    | | |-- normalize.css 标准化各个浏览器差异
    | | |-- var.less 主题配置文件
    | |-- utils 常用函数以及其他有用工具
    | | |-- common.ts
    | |-- views 页面级组件
    | |-- Home.vue 正常页面
    | |-- Test.vue
    |-- .env.development 开发环境配置
    |-- .env.preview 测试环境配置
    |-- .env.production 生产环境配置
    |-- .eslintignore eslint 要忽略的文件夹
    |-- .eslintrc.js eslint 规则配置
    |-- .gitignore git 忽略的文件
    |-- .prettierrc.js 格式化插件配置 可以按照公司规范定制
    |-- README.md 项目说明
    |-- .cz-config 自定义git-commit配置信息
    |-- babel.config.js babel 设置 (包含Ui框架的按需引入配置)
    |-- global.d.ts 全局的 type
    |-- package.json npm 配置
    |-- tsconfig.json typescript 配置
    |-- typedoc.json 文档配置文件
    |-- vue.config.js vue-cli 脚手架配置文件

项目架构github地址:github.com/XiaoRIGE/vu… 该项目开箱即用,如果对你有帮助,欢迎star

项目中的一些小技巧分享

配置全局主题样式

依赖的包是 style-resources-loader、vue-cli-plugin-style-resources-loader,目的是为了在单文件中使用时不再需要每次都去做引入操作。

    • 安装对应的依赖
    yarn add style-resources-loader vue-cli-plugin-style-resources-loader --dev
    或者
    npm i style-resources-loader vue-cli-plugin-style-resources-loader --save-dev
    • vue.config.js中配置
 pluginOptions: {
    "style-resources-loader": {
      preProcessor: "less",
      patterns: ["./src/styles/var.less"],
    },
  },
    • 重启项目,文件中使用变量文件

全局使用效果

自动化注册(批处理脚本)

首先你得知道这个函数,以及对应的参数含义,了解了之后你大概也能知道他能帮我们做什么了。

require.context(directory,useSubdirectories,regExp)

  • directory:表示检索的目录
  • useSubdirectories:表示是否检索子文件夹
  • regExp:匹配文件的正则表达式,一般是文件名

使用require.context实现一些自动注册脚本,减少重复劳动,比如:

  1. 批量引入并注册组件(只要将组件放在components文件夹内,就能实现自动注册),全局注册组件也是同理。
<template>
  <div class="test">
    <h1>test</h1>
    <Component-A />
    <Component-B />
    <Component-C />
  </div>
</template>

<script lang="ts">
const path = require("path");
const files = require.context("./components", false, /\.vue$/);
const modules: any = {};
files.keys().forEach((key) => {
  const name = path.basename(key, ".vue");
  modules[name] = files(key).default || files(key);
});

import { defineComponent } from "vue";
export default defineComponent({
  name: "test",
  components: modules,
  setup() {
    return {};
  },
});
</script>

<style lang="less" scoped>
.test {
}
</style>
  1. 动态引入vuex的module
const files = require.context('.', true, /\.ts$/);
const modules: any = {};

files.keys().forEach(key => {
  if (key === './index.ts') return;
  const path = key.replace(/(\.\/|\.ts)/g, '');
  const [namespace, imported] = path.split('/');
  if (!modules[namespace]) {
    modules[namespace] = {
      namespaced: true,
    };
  }
  modules[namespace][imported] = files(key).default;
});

export default modules;

对应的目录结构如下

层级结构 当然,项目中还有很多地方可以举一反三的,能够帮我们省去很多重复操作的时间。

vuex使用方法升级

通过对module中模块写法进行统一,然后使用工具函数实现统一操作

  1. 首先上目录结构如下

module目录结构

  1. 配置工具函数
/**
 * @description setStoreState -方法是一个 mutaitions 的操作
 * @type {T} T - 你要更改的模块的类型
 * @param {string}  module - 要操作的state 的 module 名
 * @param {string}  key - 要操作的state 的 module 下的 key 值
 * @param {any} value - 当有 msg 参数时,视为赋值操作,触发 mutation,msg 则为要复制的数据.
 * @example 如果需要更改 app 模块下的 theme为 dark,这样使用:setStoreState('app','theme','dark')
 * @example 目前只支持更改 module 的 state 第一层,不支持单独修改深层嵌套的 key,如需更改,请直接替换第一层的对象
 *  如
 *   ``` const state = {
 *                 name: {
 *                   firstName:'jack',
 *                   lastName:'Ma'
 *                 }
 *               }
 *   ```
 *  想要单独修改 firstName,直接使用 setStoreState<AppStateType>('app','name',{firstName:'modifiedName',lastName:'Ma'})
 */
export const setStoreState = function setStoreState<T>(
  module: ModuleNameType,
  key: keyof T,
  value: any
) {
  store.commit({
    type: `${module}/__set`,
    key,
    val: value
  });
};

/**
 * @description 封装 dispatch 方法
 * @type {T} T  你要派发actions的模块的类型
 * @example 使用方法如下  const result = await dispatchActions<UserActionsType>('console','refreshToken',1)
 */
export const dispatchAction = function dispatchAction<T>(
  module: ModuleNameType,
  key: keyof T,
  value?: any
) {
  store.dispatch(`${module}/${key}`, value);
};

/**
 * @description 封装 dispatch 方法
 * @type {T} T  你要获取 getters的模块的类型
 * @example 使用方法如下  const result =  getStoreGetter<ConsoleGetterType>('console','list')
 */
export const getStoreGetter = function getStoreGetter<T>(
  module: ModuleNameType,
  key: keyof T
) {
  return store.getters[`${module}/${key}`];
};

配置commit规范

在一个开发团队中,前端开发的代码管理越来越规范化、工程化,而代码commit更是需要一个统一的规范去约束,保证代码commit时的规范性。尤其多人参与一个项目开发时,大家的代码commit风格不相同,不便于后续的代码统一管理和可读性;所以良好的git commit风格是很重要的。在这里就介绍几个我们项目中使用到的工具

依赖介绍

  1. commitlint 很多时候我们提交代码时,运行 git commmit -m 'xxx' 即可,而commitlint可以对message进行约束,是判断message是否符合格式要求的工具。commitlint 推荐结合config-conventional 配置使用,正确的提交格式是: git commit -m <type>[optional scope]: <description>

  2. commitizen+cz-customizable commitizen是规范commit message的工具,提供选择的commit message类型,快速生成commit message说明;而cz-customizable作为它的适配器,可以定制提交说明信息的type。

  3. husky + lint-staged 进行代码质量规范检查时,husky可以阻止不符合规范的commit,push等操作,husky是注册在git pre-commit钩子函数被调用时执行lint-staged,pre-commit 钩子在git commit 时就会触发。lint-staged对暂存区中有改动的文件根据ESLint 和 Prettier的规则进行检测;eslint+husky+prettier+lint-staged工具的配合使用可以规范我们的代码格式统一,进行代码格式检查 和代码美化,保证代码质量。

配置步骤

  1. 安装commitlint
npm install  @commitlint/cli @commitlint/config-conventional  -D

在根目录下新建.commitlintrc.js或者commitlint.config.js文件

module.exports = {
    extends: ['@commitlint/config-conventional']
};
  1. 安装commitizen+cz-customizable
npm install commitizen cz-customizable -D

在根目录下新建.cz-config.js文件,配置commit type。

// 相关配置:https://github.com/leoforfree/cz-customizable
module.exports = {
    types: [
        {
            value: 'feat',
            name: '✨ feat(新功能)'
        },
        {
            value: 'fix',
            name: '🐛 fix(Bug 修复)'
        },
        {
            value: 'docs',
            name: '📝 docs(文档更新)'
        },
        {
            value: 'style',
            name: '💄 style(代码样式更改,例如空格、格式、缺少分号等)'
        },
        {
            value: 'refactor',
            name: '💡 refactor(重构代码)'
        },
        {
            value: 'perf',
            name: '⚡️ perf(性能优化)'
        },
        {
            value: 'test',
            name: '✅ test(添加缺失或修正测试代码)'
        },
        {
            value: 'chore',
            name: '🔨 chore(构建相关的代码或工具库,如文档生成等)'
        }
    ],
    messages: {
        type: '请选择提交类型:(必填)',
        customScope: '请输入影响范围:(可选)',
        subject: '请输入简要描述:(必填)',
        body: '请输入详细描述,使用 "|" 分行:(可选)',
        breaking: '请列出所有的破坏性变更,例如:描述、理由或迁移方式等:(可选)',
        footer: '请列出需关闭的 issue,例如:#31, #34:(可选)',
        confirmCommit: '请确认此提交信息?'
    },
    subjectLimit: 100,// subject文字长度默认
    allowCustomScopes: true,
    allowBreakingChanges: ['feat', 'fix'],
    skipQuestions: ['scope', 'footer'] //默认跳过
};

配置package.json

"config": {
  "commitizen": {
      "path": "cz-customizable"
  }
}
  1. 安装 husky + lint-staged
npm install  husky lint-staged -D

配置package.json

"lint-staged": {
      "*.{scss,vue}": [
        "stylelint --fix"
    ],
    "*.{js,ts,vue}": [
        "vue-cli-service lint"
    ]
  },
  "gitHooks": {
    "pre-commit": "lint-staged",
    "commit-msg": "commitlint -eV"
}

4.新增脚本命令,之后便可以git add .,npm run commit输入自己的提交信息,这样下来,日后看commit记录的时候也方便我们去追溯代码

npm set-script commit "npx cz"

效果如图

配置changelog

如果我们想要像开源仓库里一样可以看到每次的commit信息以及时间、版本等信息的话,我们可以借助conventional-changelog-cli去实现

  1. 安装依赖
 npm install -D conventional-changelog-cli
  1. 配置脚本
npm set-script genlog "conventional-changelog -p angular -i CHANGELOG.md -s -r 0"

然后运行脚本,就会生成如下的文件了。

changelog

使用打包分析

借助webpack-bundle-analyzer实现打包文件大小分析,帮助我们找到优化点和优化方向。

  1. 安装依赖
npm install -D webpack-bundle-analyzer cross-env
  1. 配置vue.config.js
const BundleAnalyzerPlugin = require("webpack-bundle-analyzer").BundleAnalyzerPlugin;
chainWebpack: (config) => {
    if (process.env.use_analyzer) {
      config.plugin("webpack-bundle-analyzer").use(BundleAnalyzerPlugin);
    }
}

3.配置脚本,然后运行脚本就能看到项目的大文件分析了。

npm set-script report "cross-env use_analyzer=true vue-cli-service build"

打包文件分析

移除moment的local文件

其实在我们的项目中是不建议使用moment.js这个库的,因为其实我们大部分的时间处理自己就可以,但是因为项目中使用的ui库又依赖moment.js这个库。如果不优化这个文件会占很大的一部分空间。所以这里提供一个对于项目中使用了moment.js这个库,暂时又不能将它移除的优化方式。

解决方案:vue.config.js中配置忽略

 const webpack = require("webpack");
 ...
 chainWebpack: (config) => {
   config
      .plugin("ignore")
      .use(new webpack.IgnorePlugin(/^\.\/locale$/, /moment$/));
}

可以看一下优化前后对比,文件大小差距还是比较大的。 优化前有很多的local文件,而这部分文件我们通常都不会有使用的需求。所以完全可以忽略掉。 优化前

打包后可以清晰的看到local被移除后,直接减少了一半的空间。 优化后

配置cdn引用

我们可以将项目中一些版本比较固定的库放到cdn中引入,能够加快我们的加载速度,废话不多说,直接上配置文件

配置vue.config.js

根据环境配置需要使用cdn的项以及对应的cdn地址

// cnd加速配置
const externals = {
  vue: "Vue",
  "vue-router": "VueRouter",
  vuex: "Vuex",
};
// CDN外链,会插入到index.html中 具体取决于公司的情况 没有就用免费的
const cdn = {
  // 开发环境
  dev: {
    css: [],
    js: [],
  },
  // 生产环境
  build: {
    // css: ["https://cdn.jsdelivr.net/npm/vant@2.12/lib/index.css"],
    css: [],
    js: [
      // "https://cdn.jsdelivr.net/npm/vue@3.0.0/dist/vue.global.min.js",//这里踩了个坑 在使用vue3.0.0版本后 打包后的项目一直报错Object(...) is not a function 最后通过升级vue版本解决
      "https://cdn.staticfile.org/vue/3.2.26/vue.runtime.global.prod.min.js",
      "https://cdn.staticfile.org/vue-router/4.0.0/vue-router.global.prod.min.js",
      "https://cdn.staticfile.org/vuex/4.0.0/vuex.global.prod.min.js",
    ],
  },
};

configureWebpack: (config) => {
    config.externals = externals; //配置外部拓展
},

/**
 * 添加CDN参数到htmlWebpackPlugin配置中
 */
chainWebpack: (config) => {
    config.plugin("html").tap((args) => {
      if (!IS_DEV) {
        args[0].cdn = cdn.build;
      } else {
        args[0].cdn = cdn.dev;
      }
      return args;
    });
}

配置index.html

<head>
  <meta charset="utf-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <meta name="viewport" content="width=device-width,initial-scale=1.0">
  <link rel="icon" href="<%= BASE_URL %>favicon.ico">
  <!-- <title><%= htmlWebpackPlugin.options.title %></title> -->
  
  <!-- 使用CDN的CSS文件 -->
  <% for (var i in htmlWebpackPlugin.options.cdn&&htmlWebpackPlugin.options.cdn.css) { %>
    <link href="<%= htmlWebpackPlugin.options.cdn.css[i] %>" rel="preload" as="style" />
    <link href="<%= htmlWebpackPlugin.options.cdn.css[i] %>" rel="stylesheet" />
  <% } %>
  <!-- 使用CDN加速的JS文件,配置在vue.config.js下 -->
  <% for (var i in htmlWebpackPlugin.options.cdn&&htmlWebpackPlugin.options.cdn.js) { %>
    <script src="<%= htmlWebpackPlugin.options.cdn.js[i] %>"></script>
  <% } %>

  <!-- <script>Vue.config.productionTip= false </script> -->
</head>

看到这个,就说明大功告成了! image.png