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

前言

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

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

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

封装的切入点主要有:

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

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

搭建前准备

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

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

npm install pnpm -g

具体搭建过程

创建vue3+vite框架

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

pnpm create vite

选择vue 

image.png

选择TS 

image.png

接下来根据提示操作即可

image.png

方式二:直接创建vue框架

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

image.png

image.png

本文选择方式一创建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代码。

安装

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

image.png

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

//使得 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 配置
{ "script": { "lint": "eslint src --fix --ext .ts,.tsx,.vue,.js,.jsx", } }
遇到的问题

1705564534243.png

报错的原因

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

解决

关闭命名规则

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

// 关闭名称校验 
'vue/multi-word-component-names': 'off'

配置prettier

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

安装

pnpm add prettier -D

解决 eslint 和 prettier 冲突

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

安装

pnpm add eslint-config-prettier -D

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

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

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

更多prettier配置请戳prettier中文网

项目下新建 .prettierignore

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

node_modules dist
package.json 配置
{ "script": { "prettier": "prettier --write ." } }

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

pnpm lint

pnpm prettier

image.png

image.png

配置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写,接下来我以第一种的形式编写相应钩子

image.png

image.png

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

配置husky

安装

pnpm add husky -D

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

pnpm husky-init

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

#!/usr/bin/env sh
. "$(dirname -- "$0")/_/husky.sh"

npm run lint-staged

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

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

"scripts": { "prepare": "husky install", },

配置lint-staged

安装

pnpm add lint-staged -D

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

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

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

1705498890784.png

规范git提交代码

工具介绍

commitlint:代码提交检测,检测git commit 内容是否符合定义的规范

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

配置commitlint

安装

pnpm add @commitlint/config-conventional @commitlint/cli -D

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

npx husky add .husky/commit-msg

1705491716559.png

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

#!/usr/bin/env sh . "$(dirname -- "$0")/_/husky.sh" npm run commitlint

新建commitlint.config.cjs配置文件

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

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中添加下面的代码

"scripts": { "commitlint": "commitlint --config commitlint.config.cjs -e -V" }

1705334504119.jpg

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

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

测试
git commit -m "改bug"

提交失败

1705499015058.png

 
git commit -m "#fix 修改bug"

提交成功

1705499125929.png

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

commitizen

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

image.png

安装

pnpm add commitizen cz-customizable -D pnpm add commitizen -g

配置package.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文件,这样就不会报错了

1705501317254.png

在文件中添加以下代码

// 自定义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

测试

image.png

配置文件引用别名

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

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

{
  "compilerOptions": {
   ......
    "baseUrl": ".",
    "paths": {
      "@/*":["src/*"]
    }
  },
}

image.png

环境变量配置

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

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

NODE_ENV=development
VITE_APP_URL= 'YOUR WEB URL'
VITE_APP_BASE_API = '/api'

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

NODE_ENV=production
VITE_APP_URL= 'YOUR WEB URL'
VITE_APP_BASE_API = 'YOUR BASE API'

组件中使用:

console.log(import.meta.env.VITE_APP_URL)

配置 package.json:

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

"build:dev": "vite build --mode development",
"build:pro": "vite build --mode production",

axios的二次封装

安装

pnpm add axios

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

1705503587672.png

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

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代码分割机制

image.png

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

image.png

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

图片引用=》Pinia进阶:再谈Pinia函数式(composition API)用法 - 掘金

安装

pnpm add pinia

main.ts文件注册仓库

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文件

添加以下代码:

//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实例就行。

添加以下代码

// 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文件

// 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的代码

//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项目总线执行注册操作:

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做如下配置

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

参考

Pinia进阶:再谈Pinia函数式(composition API)用法 - 掘金

路由router

安装

pnpm add vue-router@4

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

配置

routes.ts中配置

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

index.ts中配置

//通过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 :

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插件实现自动按需引入

image.png

具体配置

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

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

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

配置 css 预处理器 scss

安装

pnpm add sass --dev

配置全局 scss 样式文件

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

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

$test-color: red;

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

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

组件中使用

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

.main{ color: $test-color; }

Vite 配置

文件压缩

pnpm add vite-plugin-compression -D
//Gzip文件压缩
import viteCompression from 'vite-plugin-compression'
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
export default defineConfig({
   build: {
    minify: 'terser',
    // 清除所有console和debugger
    terserOptions: {
      compress: {
        drop_console: true,
        drop_debugger: true
      }
    }
  }
})

更多vite常规配置,请看我的另外一篇文章基于vite对vue3项目打包优化经验 【前端性能优化】 - 掘金

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

参考文章:

Vite2 + Vue3 + TypeScript + Pinia 搭建一套企业级的开发脚手架【值得收藏】 - 掘金 Vite2 + Vue3 + TypeScript + Pinia 搭建一套企业级的开发脚手架【值得收藏】

GitHook 工具 —— husky(格式化代码) - 掘金 # GitHook 工具 —— husky(格式化代码)

Pinia进阶:再谈Pinia函数式(composition API)用法 - 掘金 Pinia进阶:再谈Pinia函数式(composition API)用法

后期规划:

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

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

题外话

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

  • 25
    点赞
  • 17
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
以下是使用Vue3 + TypeScript + Vite + Element Plus + Router + Axios搭建前端项目框架的步骤: 1. 首先,确保你已经安装了Node.js和npm。你可以在命令行中运行以下命令来检查它们的版本: ```shell node -v npm -v ``` 2. 创建一个新的项目文件夹,并在该文件夹中打开命令行。 3. 在命令行中运行以下命令来初始化一个新的Vite项目: ```shell npm init vite ``` 在初始化过程中,你需要选择Vue作为模板,选择TypeScript作为语言,并填写项目名称。 4. 进入项目文件夹,并安装所需的依赖: ```shell cd your-project-name npm install ``` 5. 安装Vue Router、Vuex和Axios: ```shell npm install vue-router@next vuex@next axios ``` 6. 在项目文件夹中创建一个新的文件夹,用于存放页面组件和路由配置文件。 7. 在src文件夹中创建一个新的文件夹,用于存放页面组件。 8. 在src文件夹中创建一个新的文件夹,用于存放路由配置文件。 9. 在src/router文件夹中创建一个新的文件,命名为index.ts,并在其中编写路由配置: ```typescript import { createRouter, createWebHistory } from 'vue-router'; import Home from '../views/Home.vue'; const routes = [ { path: '/', name: 'Home', component: Home, }, // 添加其他页面的路由配置 ]; const router = createRouter({ history: createWebHistory(), routes, }); export default router; ``` 10. 在src/main.ts文件中导入并使用Vue Router: ```typescript import { createApp } from 'vue'; import App from './App.vue'; import router from './router'; createApp(App).use(router).mount('#app'); ``` 11. 在src/views文件夹中创建一个新的文件,命名为Home.vue,并在其中编写一个简单的页面组件: ```vue <template> <div> <h1>Welcome to Home Page</h1> </div> </template> <script> export default { name: 'Home', }; </script> ``` 12.src/App.vue文件中添加一个路由出口,用于显示组件: ```vue <template> <div id="app"> <router-view></router-view> </div> </template> <script> export default { name: 'App', }; </script> ``` 13. 在src/main.ts文件中导入并使用Element Plus: ```typescript import { createApp } from 'vue'; import App from './App.vue'; import router from './router'; import ElementPlus from 'element-plus'; import 'element-plus/lib/theme-chalk/index.css'; createApp(App).use(router).use(ElementPlus).mount('#app'); ``` 14. 运行以下命令来启动开发服务器: ```shell npm run dev ``` 15. 打开浏览器,并访问http://localhost:3000,你将看到一个简单的页面,其中包含"Welcome to Home Page"的文本。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值