从0搭建封装Vue3 + Vite项目框架,值得学习 — 项目规范,基础配置【前端工程化】

前言

为何自己要搭建封装项目框架呢?

定制化的vue3框架能够提供更加个性化、高效的开发体验,帮助团队更好地掌控整个开发过程,避免了从零开始和重复编写相同的代码框架和基础配置,大大提高开发效率。

本项目是基于vue3 + vite + TypeScript, 对vite 快速创建的框架做的二次封装

封装的切入点主要有:

    • 规范开发代码风格
    • 规范git提交代码
    • Vite的基础配置
    • 引入状态管理库pinia和路由router
    • 封装请求
    • 按需引入Element Plus组件库
    • 配置预处理器scss

github源码地址:github.com/wyx6/vue3Vi... (走过路过给个star)

搭建前准备

前置包管理工具pnpm安装(已安装可跳过)

vue3项目推荐使用pnpm来作为包管理工具。若当前没安装过pnpm,执行下面的命令安装一下。

复制代码
npm install pnpm -g

具体搭建过程

创建vue3+vite框架

方式一:使用 vite 快速创建脚手架

lua 复制代码
pnpm create vite

选择vue

选择TS

接下来根据提示操作即可

方式二:直接创建vue框架

你也可以pnpm create vue@latest安装,这种方式比较方便,可以直接选择创建状态管理库,路由,代码检测规范等,不用自己后期去创建相应的文件

本文选择方式一创建vue3项目,仓库,状态管理库,eslint等等在后面配置,目的是加深记忆

规范代码风格

前置工具介绍

  • husky

对git执行的一些命令,通过对应的hooks钩子触发,它在执行 Git 命令时自动运行一些自定义的脚本

  • lint-staged

用于在 Git 提交之前自动运行 ESLint 的工具。可以帮助你确保代码质量,避免不必要的提交

  • eslint:一个可配置的 JavaScript 检查器

可以帮助你发现并修复 JavaScript 代码中的问题

  • prettier:代码格式化工具

是一个代码格式化插件,对代码的格式进行优化

配置Eslint

ESLint 是一个可配置的 JavaScript 检查器。它可以帮助你发现并修复 JavaScript 代码中的问题。问题可以是任何东西,从潜在的运行时错误,到不遵循最佳实践,再到风格问题。

首先介绍一下需要使用到的插件

  1. pnpm add eslint:安装ESLint本身。
  2. pnpm add eslint-plugin-vue:安装Vue的ESLint插件,用于检查Vue项目的代码规范。
  3. pnpm add @typescript-eslint/eslint-plugin:安装TypeScript的ESLint插件,用于检查TypeScript项目的代码规范。
  4. pnpm add eslint-plugin-prettier:安装Prettier的ESLint插件,用于自动格式化代码。
  5. pnpm add @typescript-eslint/parser:安装TypeScript的解析器,用于解析TypeScript代码。

安装

sql 复制代码
pnpm add eslint -D
pnpm add eslint-plugin-vue -D
pnpm add @typescript-eslint/eslint-plugin -D  
pnpm add eslint-plugin-prettier -D
pnpm add @typescript-eslint/parser -D 

//使得 ESLint 能够正确识别和解析 ES6+ 语法的模块
npm install --save-dev @rushstack/eslint-patch

项目下新建 .eslintrc.cjs

在文件中配置 eslint 校验规则(校验规则可以根据自己需求配置):

perl 复制代码
//使得 ESLint 能够正确识别和解析 ES6+ 语法的模块
require("@rushstack/eslint-patch/modern-module-resolution");

module.exports = {
  root: true,
  env: {
    browser: true,
    node: true,
    es2021: true,
  },
  parser: 'vue-eslint-parser',
  extends: [
    'eslint:recommended',
    'plugin:vue/vue3-recommended',
    'plugin:@typescript-eslint/recommended',
    'plugin:prettier/recommended',
    // eslint-config-prettier 的缩写
    'prettier',
  ],
  parserOptions: {
    ecmaVersion: 12,
    parser: '@typescript-eslint/parser',
    sourceType: 'module',
    ecmaFeatures: {
      jsx: true,
    },
  },
  // eslint-plugin-vue @typescript-eslint/eslint-plugin eslint-plugin-prettier的缩写
  plugins: ['vue', '@typescript-eslint', 'prettier'],
  rules: {
    '@typescript-eslint/ban-ts-ignore': 'off',
    '@typescript-eslint/no-unused-vars': 'off',
    '@typescript-eslint/explicit-function-return-type': 'off',
    '@typescript-eslint/no-explicit-any': 'off',
    '@typescript-eslint/no-var-requires': 'off',
    '@typescript-eslint/no-empty-function': 'off',
    '@typescript-eslint/no-use-before-define': 'off',
    '@typescript-eslint/ban-ts-comment': 'off',
    '@typescript-eslint/ban-types': 'off',
    '@typescript-eslint/no-non-null-assertion': 'off',
    '@typescript-eslint/explicit-module-boundary-types': 'off',
    'no-var': 'error',
    'prettier/prettier': 'error',
    // 禁止出现console
    'no-console': 'warn',
    // 禁用debugger
    'no-debugger': 'warn',
    // 禁止出现重复的 case 标签
    'no-duplicate-case': 'warn',
    // 禁止出现空语句块
    'no-empty': 'warn',
    // 禁止不必要的括号
    'no-extra-parens': 'off',
    // 禁止对 function 声明重新赋值
    'no-func-assign': 'warn',
    // 禁止在 return、throw、continue 和 break 语句之后出现不可达代码
    'no-unreachable': 'warn',
    // 强制所有控制语句使用一致的括号风格
    curly: 'warn',
    // 要求 switch 语句中有 default 分支
    'default-case': 'warn',
    // 强制尽可能地使用点号
    'dot-notation': 'warn',
    // 要求使用 === 和 !==
    eqeqeq: 'warn',
    // 禁止 if 语句中 return 语句之后有 else 块
    'no-else-return': 'warn',
    // 禁止出现空函数
    'no-empty-function': 'warn',
    // 禁用不必要的嵌套块
    'no-lone-blocks': 'warn',
    // 禁止使用多个空格
    'no-multi-spaces': 'warn',
    // 禁止多次声明同一变量
    'no-redeclare': 'warn',
    // 禁止在 return 语句中使用赋值语句
    'no-return-assign': 'warn',
    // 禁用不必要的 return await
    'no-return-await': 'warn',
    // 禁止自我赋值
    'no-self-assign': 'warn',
    // 禁止自身比较
    'no-self-compare': 'warn',
    // 禁止不必要的 catch 子句
    'no-useless-catch': 'warn',
    // 禁止多余的 return 语句
    'no-useless-return': 'warn',
    // 禁止变量声明与外层作用域的变量同名
    'no-shadow': 'off',
    // 允许delete变量
    'no-delete-var': 'off',
    // 强制数组方括号中使用一致的空格
    'array-bracket-spacing': 'warn',
    // 强制在代码块中使用一致的大括号风格
    'brace-style': 'warn',
    // 强制使用骆驼拼写法命名约定
    camelcase: 'warn',
    // 强制使用一致的缩进
    indent: 'off',
    // 强制在 JSX 属性中一致地使用双引号或单引号
    // 'jsx-quotes': 'warn',
    // 强制可嵌套的块的最大深度4
    'max-depth': 'warn',
    // 强制最大行数 300
    // "max-lines": ["warn", { "max": 1200 }],
    // 强制函数最大代码行数 50
    // 'max-lines-per-function': ['warn', { max: 70 }],
    // 强制函数块最多允许的的语句数量20
    'max-statements': ['warn', 100],
    // 强制回调函数最大嵌套深度
    'max-nested-callbacks': ['warn', 3],
    // 强制函数定义中最多允许的参数数量
    'max-params': ['warn', 3],
    // 强制每一行中所允许的最大语句数量
    'max-statements-per-line': ['warn', { max: 1 }],
    // 要求方法链中每个调用都有一个换行符
    'newline-per-chained-call': ['warn', { ignoreChainWithDepth: 3 }],
    // 禁止 if 作为唯一的语句出现在 else 语句中
    'no-lonely-if': 'warn',
    // 禁止空格和 tab 的混合缩进
    'no-mixed-spaces-and-tabs': 'warn',
    // 禁止出现多行空行
    'no-multiple-empty-lines': 'warn',
    // 禁止出现;
    semi: ['warn', 'never'],
    // 强制在块之前使用一致的空格
    'space-before-blocks': 'warn',
    // 强制在 function的左括号之前使用一致的空格
    // 'space-before-function-paren': ['warn', 'never'],
    // 强制在圆括号内使用一致的空格
    'space-in-parens': 'warn',
    // 要求操作符周围有空格
    'space-infix-ops': 'warn',
    // 强制在一元操作符前后使用一致的空格
    'space-unary-ops': 'warn',
    // 强制在注释中 // 或 /* 使用一致的空格
    // "spaced-comment": "warn",
    // 强制在 switch 的冒号左右有空格
    'switch-colon-spacing': 'warn',
    // 强制箭头函数的箭头前后使用一致的空格
    'arrow-spacing': 'warn',
    'no-var': 'warn',
    'prefer-const': 'warn',
    'prefer-rest-params': 'warn',
    'no-useless-escape': 'warn',
    'no-irregular-whitespace': 'warn',
    'no-prototype-builtins': 'warn',
    'no-fallthrough': 'warn',
    'no-extra-boolean-cast': 'warn',
    'no-case-declarations': 'warn',
    'no-async-promise-executor': 'warn',
  },
  globals: {
    defineProps: 'readonly',
    defineEmits: 'readonly',
    defineExpose: 'readonly',
    withDefaults: 'readonly',
  },
}

更多配置规则请戳Eslint中文网

项目下新建 .eslintignore

配置eslint 忽略检查文件 (根据需要添加)

复制代码
node_modules
dist

package.json 配置

json 复制代码
{
  "script": {
    "lint": "eslint src --fix --ext .ts,.tsx,.vue,.js,.jsx",
  }
}

遇到的问题

报错的原因:

在组件命名的时候未按照 ESLint 的官方代码规范进行命名,根据 ESLint 官方代码风格指南,除了根组件(App.vue)以外,其他自定义组件命名要使用大驼峰命名方式或者用"-"连接单词进行命名;

解决

关闭命名规则

找到 .eslintrc.cjs 文件在 rules 后面加上这段代码就可解决

arduino 复制代码
// 关闭名称校验
    'vue/multi-word-component-names': 'off'

配置prettier

Prettier是一个代码格式化插件,对代码的格式进行优化,例如,它可以自动调整代码中的缩进、空格和换行等细节,确保代码风格的一致性。

安装

csharp 复制代码
pnpm add prettier -D

解决 eslint 和 prettier 冲突

解决 ESLint 中的样式规范和 prettier 的冲突,以 prettier 的样式规范为准,使 ESLint 中的样式规范自动失效

安装

arduino 复制代码
pnpm add eslint-config-prettier -D

在项目下新建 .prettierrc.json文件

配置 prettier 规则(可以根据自己需求配置):

json 复制代码
{
  "singleQuote": true,  // 使用单引号
  "semi": false,  // 不使用分号
  "bracketSpacing": true,  // 在对象,数组括号与文字之间加空格 
  "htmlWhitespaceSensitivity": "ignore",  // 对html的空格不敏感
  "endOfLine": "auto",  // 行结尾统一样式,保持现有的行尾
  "trailingComma": "none", // 对象,数组等末尾不加逗号
  "tabWidth": 2 //  水平缩进的空格数为2
}

更多prettier配置请戳prettier中文网

项目下新建 .prettierignore

配置忽略文件 (根据需要添加)

复制代码
node_modules
dist

package.json 配置

json 复制代码
{
  "script": {
    "prettier": "prettier --write ."
  }
}

配置完后,可以执行以下命令看看效果:

复制代码
pnpm lint

pnpm prettier

配置husky + lint-staged

介绍

husky 是一个为 git 客户端增加 hook 的工具。安装后,它会自动在仓库中的 .git/ 目录下增加相应的钩子;比如 pre-commit 钩子就会在你执行 git commit 的触发。

常用hooks介绍

  1. pre-commit:钩子在提交信息前运行命令。
  2. prepare-commit-msg:钩子在启动提交信息编辑器之前,默认信息被创建之后运行。
  3. commit-msg:这个钩子在 git commit 和 git merge 命令触发,会传递一个参数,该参数为存放当前 commit 消息的临时文件路径。 如果该钩子脚本以非零值退出,Git 将放弃提交, 因此,可以用来在提交通过前验证项目状态或提交信息。
  4. post-commit:钩子在整个提交过程完成后运行

钩子有两种编写形式,可以在.husky文件编写相应的钩子文件也可以在package.json写,接下来我以第一种的形式编写相应钩子

lint-staged:用于在 Git 提交之前自动运行 ESLint 的工具。可以帮助你确保代码质量,避免不必要的提交

配置husky

安装

csharp 复制代码
pnpm add husky -D

初始化husky配置,在根目录会有.husky配置文件,里面有初始化配置pre-commit

csharp 复制代码
pnpm husky-init  

在.husky下的pre-commit配置文件中添加以下信息

bash 复制代码
#!/usr/bin/env sh
. "$(dirname -- "$0")/_/husky.sh"

npm run lint-staged

配置完后如果git commit到仓库时就会触发pre-commit钩子,然后执行npm run lint-staged

安装成功后在 package.json 中配置:

json 复制代码
 "scripts": {
    "prepare": "husky install",
  },

配置lint-staged

安装

csharp 复制代码
pnpm add lint-staged -D

安装成功后在 package.json 中配置:

json 复制代码
 "scripts": {
    "lint-staged": "lint-staged",
  },
 "lint-staged": {
   "*.{js,jsx,vue,ts,tsx}": [
     "pnpm lint",
     "pnpm prettier",
     "git add"
   ]
 }

配置完后,当提交代码到仓库时,就会自动执行lint和prettier检查代码规范了

规范git提交代码

工具介绍

commitlint:代码提交检测,检测git commit 内容是否符合定义的规范
commitizen:用于自动化提交信息的工具,帮助开发者在每次提交代码时自动生成符合规范的提交信息,使用Commitizen可以使得团队的提交信息更加统一和规范。

配置commitlint

安装

sql 复制代码
pnpm add @commitlint/config-conventional @commitlint/cli -D

在.husky配置文件中,添加commit-msg钩子

sql 复制代码
npx husky add .husky/commit-msg 

在commit-msg配置文件中添加以下信息

bash 复制代码
#!/usr/bin/env sh
. "$(dirname -- "$0")/_/husky.sh"

npm run commitlint

新建commitlint.config.cjs配置文件

在配置文件添加下面的代码:

java 复制代码
module.exports = {
  extends: ['@commitlint/config-conventional'],
  // 校验规则
  rules: {
    'type-enum': [
      2,
      'always',
      [
        'feat',
        'fix',
        'docs',
        'style',
        'refactor',
        'perf',
        'test',
        'chore',
        'revert',
        'build',
      ],
    ],
    'type-case': [0],
    'type-empty': [0],
    'scope-empty': [0],
    'scope-case': [0],
    'subject-full-stop': [0, 'never'],
    'subject-case': [0, 'never'],
    'header-max-length': [0, 'always', 72],
  },
}

在package.json中配置

在scrips中添加下面的代码

json 复制代码
"scripts": {
    "commitlint": "commitlint --config commitlint.config.cjs -e -V"
}

按照上面配置完成后,现在当我们填写commit信息的时候,前面就需要带着下面的subject

arduino 复制代码
'feat',//新特性、新功能
'fix',//修改bug
'docs',//文档修改
'style',//代码格式修改, 注意不是 css 修改
'refactor',//代码重构
'perf',//优化相关,比如提升性能、体验
'test',//测试用例修改
'chore',//其他修改, 比如改变构建流程、或者增加依赖库、工具等
'revert',//回滚到上一个版本
'build',//编译相关的修改,例如发布版本、对项目构建或者依赖的改动

测试

sql 复制代码
git commit -m "改bug"

提交失败

sql 复制代码
git commit -m "#fix 修改bug"

提交成功

注意 :当我们 commit 提交信息时,必须是 git commit -m 'fix: xxx' 符合类型的才可以,需要注意的是类型的后面需要用英文的 :,并且冒号后面是需要空一格的,这个是不能省略的

commitizen

配置commitizen后,帮助开发者在每次提交代码 时自动生成符合规范的提交信息,提示每个subjet的含义

安装

csharp 复制代码
pnpm add  commitizen cz-customizable -D
pnpm add  commitizen -g

配置package.json

json 复制代码
"scripts": {
  "commit": "git-cz",
}
// 这里自定义commitizen,使用git-cz执行git commit命令
  "config": {
    "commitizen": {
      "path": "./node_modules/cz-customizable"
    },
    "cz-customizable": {
      "config": "./.cz-config.cjs"
    }
  }

在根目录创建的.cz-config.cjs文件

看了别人的版本是创建.cz-config.cjs文件,但会报错,提示我改为cjs文件,这样就不会报错了

在文件中添加以下代码

css 复制代码
// 自定义commit提示内容
module.exports = {
  types: [
    { value: 'feat', name: 'feat:     新功能' },
    { value: 'fix', name: 'fix:      修复' },
    { value: 'docs', name: 'docs:     文档变更' },
    { value: 'style', name: 'style:    代码格式(不影响代码运行的变动)' },
    {
      value: 'refactor',
      name: 'refactor: 重构(既不是增加feature,也不是修复bug)'
    },
    { value: 'perf', name: 'perf:     性能优化' },
    { value: 'test', name: 'test:     增加测试' },
    { value: 'chore', name: 'chore:    构建过程或辅助工具的变动' },
    { value: 'revert', name: 'revert:   回退' },
    { value: 'build', name: 'build:    打包' }
  ],
  // override the messages, defaults are as follows
  messages: {
    type: '请选择提交类型:',
    // scope: '请输入文件修改范围(可选):',
    // used if allowCustomScopes is true
    customScope: '请输入修改范围(可选):',
    subject: '请简要描述提交(必填):',
    body: '请输入详细描述(可选,待优化去除,跳过即可):',
    // breaking: 'List any BREAKING CHANGES (optional):\n',
    footer: '请输入要关闭的issue(待优化去除,跳过即可):',
    confirmCommit: '确认使用以上信息提交?(y/n/e/h)'
  },
  allowCustomScopes: true,
  // allowBreakingChanges: ['feat', 'fix'],
  skipQuestions: ['body', 'footer'],
  // limit subject length, commitlint默认是72
  subjectLimit: 72
}

配置完成后,后面的提交,使用pnpm commit或者git cz来代替git commit

测试

配置文件引用别名

修改 vite.config.ts 文件配置:

javascript 复制代码
import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'
import path from 'path'

export default defineConfig({
  plugins: [vue()],
  resolve: {
    alias: {
      '@': path.resolve(__dirname, 'src'),
    },
  },
})

修改 tsconfig.json

json 复制代码
{
  "compilerOptions": {
   ......
    "baseUrl": ".",
    "paths": {
      "@/*":["src/*"]
    }
  },
}

环境变量配置

vite 提供了两种模式:具有开发服务器的开发模式(development)和生产模式(production)

项目根目录新建:.env.development ,添加以下代码:

ini 复制代码
NODE_ENV=development
VITE_APP_URL= 'YOUR WEB URL'
VITE_APP_BASE_API = '/api'

项目根目录新建:.env.production ,添加以下代码:

ini 复制代码
NODE_ENV=production
VITE_APP_URL= 'YOUR WEB URL'
VITE_APP_BASE_API = 'YOUR BASE API'

组件中使用:

arduino 复制代码
console.log(import.meta.env.VITE_APP_URL)

配置 package.json:

打包区分开发环境和生产环境

json 复制代码
"build:dev": "vite build --mode development",
"build:pro": "vite build --mode production",

axios的二次封装

安装

csharp 复制代码
pnpm add axios

utils 下新增 http 文件夹,在http 文件夹下新增index.ts文件

在index.ts文件中添加以下代码

typescript 复制代码
import axios, { AxiosRequestConfig } from 'axios'

// 创建axios的一个实例
const instance = axios.create({
  baseURL: String(import.meta.env.VITE_APP_BASE_API),
  timeout: 5000, // 设置超时
  headers: {
    'Content-Type': 'application/json;charset=UTF-8;'
  }
})

//显示loading,可以用于封装加载动画
export const showLoading = () => {}

//隐藏loading,可以用于封装取消加载动画
export const hideLoading = () => {
}

// 请求拦截器
instance.interceptors.request.use(
  (config: any) => {
    showLoading()
    const token = window.localStorage.getItem('token')
    //判断token是否存在
    if (token) {
      config.headers.token = token
    }

    // 若请求方式为post,则将data参数转为JSON字符串
    if (config.method === 'POST') {
      config.data = JSON.stringify(config.data)
    }
    return config
  },
  (error: any) =>
    // 错误处理逻辑
    Promise.reject(error)
)

// 响应拦截器
instance.interceptors.response.use(
  (response: any) => {
    hideLoading()
    return response
  },
  (error: { response: { status: any } }) => {
    // 响应错误
    if (error.response && error.response.status) {
      const status = error.response.status
      //对status进行判断,这里可以用策略模式进行改进
     switch (status) {
      case 401:
        message = 'TOKEN过期'
        break
      case 403:
        message = '无权访问'
        break
      case 404:
        message = '请求地址错误'
        break
      case 500:
        message = '服务器出现问题'
        break
      default:
        message = '网络出现问题'
        break
    }
      return Promise.reject(error)
    }
    return Promise.reject(error)
  }
)

export default async <T = any>(config: AxiosRequestConfig) => {
  const res = await instance(config)
  return res.data as T
}

上面是自己封装的,比较基础,可能有很多没有考虑到的可以参考这篇文章

状态管理库 pinia

pinia让Vue Store拥有Composition API方式的状态管理库,能同时支持Option API 和setup Composition API开发模式,并兼容Typescript写法

Pinia与Vuex代码分割机制

Vuex的代码分割: 打包时,vuex会把所有store合并打包,当页面用到Vuex某个仓库模块时,所有仓库模块会一起打包,其实只需要其中1个store,但其他无关的store也被打包进来,影响前端性能,当然这也可以通过一些办法解决

Pinia的代码分割: 打包时,Pinia会检查引用依赖,当页面用到哪个 store,打包就只会引入相应的模块,其它仓库模块不会一起打包。这也是pinia的优势

图片引用=》juejin.cn/post/708903...

安装

csharp 复制代码
pnpm add pinia

在main.ts文件注册仓库

javascript 复制代码
import { createApp } from 'vue'
import App from './App.vue'
import { createPinia } from 'pinia'
const app = createApp(App)
const pinia = createPinia()
app.use(pinia)
app.mount('#app')

创建第一个仓库

在store文件夹下创建counter.ts文件

添加以下代码:

javascript 复制代码
//src/store/counter.ts
import { ref, computed } from 'vue'
import { defineStore } from 'pinia'

export const useCounterStore = defineStore('counter', () => {
  const count = ref(0)
  const doubleCount = computed(() => count.value * 2)

  const increment= () => {
    count.value++
  }
  
  const decrement= () => {
    count.value--
  }

  return { count, doubleCount, increment,decrement }
})

创建pinia总入口

在src/store目录下创建入口index.ts文件,其中包含一个注册函数registerStore(),其作用是把所有的store都提前注册在appStore上,在任何组件要使用pinia时,只要import appStore进来,取对应的store实例就行。

添加以下代码

typescript 复制代码
// src/store/index.ts
import { useCounterStore } from './counter';
//还可以引入其它仓库模块

export interface IAppStore {
  useCounter: ReturnType<typeof useCounterStore>;
}

const appStore: IAppStore = {} as IAppStore;

/**
 * 注册app状态库
 */
export const registerStore = () => {
  appStore.useCounter = useCounterStore();
};

export default appStore;

composition API 模式下不支持某些内置方法,如$reset(),解决方式是重写一下reset方法。

在src/utils创建storeTools.ts文件

typescript 复制代码
// src/utils/storeTools
// Pinia store基础集成方法
import type { IAppStore } from '@/store'

/**
 * 重构$reset()
 * @desc 因为setup模式编程不支持reset方法,这里要手动重构
 * @param appStore
 */
export const initResetFun = (appStore: IAppStore) => {
  // 遍历 appStore 中的所有项。
  Object.values(appStore).forEach((item) => {
    // 创建一个空对象 initState 用于存储初始状态。
    const initState = {} as Record<string, any>

    // 遍历 item 的 $state 对象的所有条目。
    Object.entries(item.$state).forEach((item) => {
      // 将每个状态的初始值存储到 initState 对象中。
      initState[item[0]] = item[1]
    })

    // 为每个 store 项定义一个 reset 方法。
    item.reset = () => {
      // 遍历 $state 对象的所有状态。
      Object.keys(item.$state).forEach((state) => {
        // 将每个状态重置为其初始值。
        item.$state[state] = initState[state]
      })
    }
  })
}

src/store/index.ts

修改index.ts的代码

typescript 复制代码
//src/store/index.ts
//导入counter.ts
import { useCounterStore } from "./counter";
import { initResetFun } from '@/utils/storeTools'

export interface IAppStore {
    useCounter: ReturnType<typeof useCounterStore>;
    // 其他store...
}

const appStore:IAppStore = {} as IAppStore;

/**
 * 注册app状态库
*/
export const registerStore = () => {
    appStore.useCounter = useCounterStore();
    // 其他store...

    //重写reset方法
    initResetFun(appStore);
}

export default appStore;

统一注册

在src/main.ts项目总线执行注册操作:

javascript 复制代码
import { createApp } from 'vue';
import App from './App.vue';
import { createPinia } from 'pinia';
import { registerStore } from '@/store';

const app = createApp(App);

app.use(createPinia());
// 注册pinia状态管理库
registerStore();

app.mount('#app');

打包解耦

为了让appStore实例与项目解耦,在构建时要把appStore抽取到公共chunk,在vite.config.ts做如下配置

javascript 复制代码
export default defineConfig({
  ...
  build: {
    rollupOptions: {
      output: {
        manualChunks(id) {
          // 将pinia的全局库实例打包进vendor,避免和页面一起打包造成资源重复引入
          if (id.includes(path.resolve(__dirname, '/src/store/index.ts'))) {
            return 'vendor'
          }
        }
      }
    }
  },

参考

juejin.cn/post/708903...

路由router

安装

sql 复制代码
pnpm add vue-router@4

在 src 文件下新增 router 文件夹 => index.ts 文件和routes.ts文件

配置

在routes.ts中配置

javascript 复制代码
//对外暴露配置路由
export const routes = [
  {
    path: '/',
    name: 'Login',
    component: () => import('@/pages/user/index.vue'), // 注意这里要带上 文件后缀.vue
    meta:{}
  },
]

在index.ts中配置

javascript 复制代码
//通过vue-router插件实现模板路由配置
import { createRouter, createWebHashHistory } from 'vue-router'
import { routes } from './routes'
//创建路由器
const router = createRouter({
  //路由模式根据需求选择
  history: createWebHashHistory(),
  routes: routes,
  //滚动行为
  scrollBehavior() {
    return {
      left: 0,
      top: 0,
    }
  },
})
export default router

路由模式根据需求选择

createWebHashHistory hash 路由 使用location.hash 通过hashchange事件监听url变化

createWebHistory 普通路由 使用history 通过popstate事件监听url变化

createMemoryHistory 服务端渲染使用

修改入口文件 mian.ts :

javascript 复制代码
import { createApp } from 'vue'
import App from './App.vue'
import router from './router/index'

const app = createApp(App)
app.use(router)
app.mount('#app')

更多路由配置信息可以查看 vue-router 官方文档:

按需引入Element Plus组件库

关于组件库这部分,有很多很好的组件库,我这里用的是Element Plus组件库

实现自动按需引入

利用unplugin-vue-components插件实现组件自动按需导入, 利用unplugin-auto-import插件实现自动按需引入 利用unplugin-vue-components插件实现组件自动按需导入, 利用unplugin-auto-import插件实现自动按需引入

具体配置

arduino 复制代码
pnpm add element-plus

pnpm add -D unplugin-vue-components unplugin-auto-import

在vite.config.ts中加入以下代码

javascript 复制代码
// 按需自动引入导入element plus。
import AutoImport from 'unplugin-auto-import/vite'
import Components from 'unplugin-vue-components/vite'
import { ElementPlusResolver } from 'unplugin-vue-components/resolvers'
css 复制代码
export default defineConfig({
  plugins: [
    AutoImport({
      resolvers: [ElementPlusResolver()],
    }),
    Components({
      resolvers: [ElementPlusResolver()],
    }),
  ],
})

配置 css 预处理器 scss

安装

sql 复制代码
pnpm add sass --dev

配置全局 scss 样式文件

在 src/assets 下新增 style 文件夹,用于存放全局样式文件

新建 main.scss, 设置一个用于测试的颜色变量 :

css 复制代码
$test-color: red;

将这个全局样式文件全局注入到项目,配置 vite.config.js

css 复制代码
css:{
    preprocessorOptions:{
      scss:{
        additionalData:'@import "@/assets/style/mian.scss";'
      }
    }
  },

组件中使用

不需要任何引入,可以直接使用全局scss定义的变量

css 复制代码
.main{
  color: $test-color;
}

Vite 配置

文件压缩

csharp 复制代码
pnpm add vite-plugin-compression -D
javascript 复制代码
//Gzip文件压缩
import viteCompression from 'vite-plugin-compression'
php 复制代码
export default defineConfig({
  plugins: [
     //开启Gzip压缩
    viteCompression({
      verbose: true, // 是否在控制台中输出压缩结果
      disable: false,
      threshold: 1024, // 如果体积大于阈值,将被压缩,单位为b,体积过小时请不要压缩,以免适得其反
      algorithm: 'gzip', // 压缩算法,可选['gzip',' brotliccompress ','deflate ','deflateRaw']
      ext: '.gz',
      deleteOriginFile: true // 源文件压缩后是否删除(我为了看压缩后的效果,先选择了true)
    })
  ],
})

terser 压缩和去除console+debugger

npm i terser -D

php 复制代码
export default defineConfig({
   build: {
    minify: 'terser',
    // 清除所有console和debugger
    terserOptions: {
      compress: {
        drop_console: true,
        drop_debugger: true
      }
    }
  }
})

更多vite常规配置,请看我的另外一篇文章juejin.cn/post/731546...

也可以查看vite官网,了解更多知识

参考文章:

juejin.cn/post/703674... Vite2 + Vue3 + TypeScript + Pinia 搭建一套企业级的开发脚手架【值得收藏】

juejin.cn/post/694720... # GitHook 工具 ------ husky(格式化代码)

juejin.cn/post/708903... Pinia进阶:再谈Pinia函数式(composition API)用法

后期规划:

会继续学习,在这个封装基础上进行加强,包括但不限于以下:

  • Pinia数据持久化
  • 自动化生成项目基本模版
  • 封装脚手架
  • 多入口打包
  • 路由动画的封装
  • viewport 适配方案
  • ......

题外话

如果这篇文章对于你有一些帮助,可以给个👍和star吗,小希与你一起进步,一起努力,加油!

相关推荐
前端双越老师10 分钟前
程序员 1v1 访谈系列第二期:很焦虑但又不知道干啥
前端·ai编程·全栈
雪芽蓝域zzs17 分钟前
Webstorm打开uniapp uvue文件报红处理方案
vue.js
广州华水科技24 分钟前
北斗形变监测传感器在大坝安全监测中的应用与优势分析
前端
孔令飞31 分钟前
如何学习Go
前端
HUIBUR科技35 分钟前
AI 中枢,面向企业场景构建的 AI 智能体
前端·数据库·人工智能
随便写个软件1 小时前
【新版本】EasyExplorer资源管理器 3.12.0——地址栏重要更新
运维·服务器·前端·everything·资源管理器
Jack N1 小时前
【Strapi 系列之二】Collection / Single / Component 怎么选?Strapi 字段大全一篇讲透
前端·javascript·strapi
hiahiahia1231 小时前
Agent Tool 执行失败怎么办
服务器·前端
Java后端的Ai之路1 小时前
09-React组件生命周期
前端·react.js·前端框架
撸串-研究生1 小时前
面向景区的综合服务管理平台设计——以稻城亚丁景区为例【附源码】
java·开发语言·vue.js·前后端分离·稻城亚丁景区