likes
comments
collection
share

Vue3.3+Vite4.x+Typecript+Idux带你从0到1打造企业级项目(3万字)

作者站长头像
站长
· 阅读数 9

@idux是我司是基于vue3.xTypescript开发的开源组件库,它拥有以下特性:

  • Monorepo 管理模式:cdkcomponentspro
  • 开箱即用的高质量组件
  • 全面拥抱 composition api,从源码到文档
  • 完全使用 TypeScript 开发,提供完整的类型定义
  • 灵活的全局配置
  • 深入细节的主题定制能力
  • 国际化语言支持

相关链接:

感兴趣的朋友可以尝试使用,我们会一直维护🌹~

前言

本篇文章介绍如何基于vue3.3+vite4.x+ts+@idux开发一套符合企业级规范的项目,相关技术栈:

  • 包管理模式:pnpm Monorepo
  • 框架:vue3.3
  • 工程化:vite4.x + rollup
  • 语言:ts + tsx
  • 组件库:@idux
  • css:less+postcss
  • 请求库:axios
  • 状态管理:pinia
  • 单侧:vitest
  • 代码规范:eslint + prettier + stylelint
  • 提交规范:husky+lint-staged+commitlint

废话不多说,下面直接开始吧

环境准备

在开始之前,推荐以下开发环境:

  1. vscode
  2. pnpm>=8.x
  3. node>=12.x
  4. chrome浏览器
  5. vue-devtools

初始化

使用pnpm创建项目

pnpm create vite

输入完指令后会提示输入项目的名称,选择vuets之后,项目创建成功: Vue3.3+Vite4.x+Typecript+Idux带你从0到1打造企业级项目(3万字) 进入项目目录,执行pnpm install安装依赖: Vue3.3+Vite4.x+Typecript+Idux带你从0到1打造企业级项目(3万字) pnpm run dev进入项目: Vue3.3+Vite4.x+Typecript+Idux带你从0到1打造企业级项目(3万字) 看到这个界面说明项目初始化成功

CSS工程化

我们把项目中无用的代码删掉,只保留一个App.vue,这样保证我们的项目干净: Vue3.3+Vite4.x+Typecript+Idux带你从0到1打造企业级项目(3万字) 添加less脚本:

Vue3.3+Vite4.x+Typecript+Idux带你从0到1打造企业级项目(3万字) 这个时候会报错,这是很正常的,因为我们还没有装lessVue3.3+Vite4.x+Typecript+Idux带你从0到1打造企业级项目(3万字) 安装less依赖:

pnpm add less

再次启动,样式生效:

Vue3.3+Vite4.x+Typecript+Idux带你从0到1打造企业级项目(3万字)

试一下变量和嵌套样式,一样没有问题: Vue3.3+Vite4.x+Typecript+Idux带你从0到1打造企业级项目(3万字)

如果我们想引入全局的样式文件呢?例如把@color放在style.less文件中

Vue3.3+Vite4.x+Typecript+Idux带你从0到1打造企业级项目(3万字)

解决方案有两种:

1、在style标签中引入style.less文件

Vue3.3+Vite4.x+Typecript+Idux带你从0到1打造企业级项目(3万字)

如果是某个vue组件特定的变量没有问题,但是对于全局性的变量或者样式,总不能每个都通过@import导入一次吧?

2、配置preprocessorOptions

vite为我们提供了preprocessorOptions配置,可以在style标签或者.less文件中自动引入style.less文件:

//vite.config.ts
import { defineConfig, normalizePath } from 'vite'
import vue from '@vitejs/plugin-vue'
// 可以安装@types/node解决类型报错
import path from 'path';

// 用 normalizePath 解决 window 下的路径问题
const variablePath = normalizePath(path.resolve('./src/style.less'));

// https://vitejs.dev/config/
export default defineConfig({
  plugins: [vue()],
  css: {
    preprocessorOptions: {
      less: {
        additionalData: `@import "${variablePath}";`
      }
    }
  }
})

我们安装@types/node解决path模块的类型错误

pnpm add @types/node -D

使用postcss一般用来解析和处理css代码,通常通过postcss.config.js文件来配置postcss,不过vite已经提供了相关配置入口,可以直接在vite.config.ts进行操作,我们安装一个常见的插件,用来解决浏览器兼容性问题,那就是autoprefixer 安装autoprefixer:

pnpm add autoprefixer -D

vite.config.ts中配置:

// vite.config.ts
import autoprefixer from 'autoprefixer';

export default {
  css: {
    postcss: {
      plugins: [
        autoprefixer({
          // 指定目标浏览器
          overrideBrowserslist: ['> 1%', 'last 2 versions']
        })
      ]
    }
  }
}

我们尝试使用一些较新的语法:

.demo {
  &-text {
    color: @color;
    text-decoration: dashed;
  }
}

执行pnpm run build,可以看到产物中,已经在自动在样式前加上了前缀:

Vue3.3+Vite4.x+Typecript+Idux带你从0到1打造企业级项目(3万字)

OK,那css工程化相关的内容就讲到这。

代码规范

1、Eslint

安装eslint

pnpm add eslint -D

执行npx eslint --init初始化

Vue3.3+Vite4.x+Typecript+Idux带你从0到1打造企业级项目(3万字) 步骤最后会问你是否要立即安装以上依赖,这里建议不要,因为默认用的是npm安装,我们更希望用pnpm进行安装

pnpm add @typescript-eslint/eslint-plugin@latest eslint-plugin-vue@latest @typescript-eslint/parser@latest -D

可以看到初始化之后,项目自动新建了.eslintrc.cjs文件

// .eslintrc.cjs
module.exports = {
    "env": {
        "browser": true,
        "es2021": true
    },
    "extends": [
        "eslint:recommended",
        "plugin:@typescript-eslint/recommended",
        "plugin:vue/vue3-essential"
    ],
    "overrides": [
        {
            "env": {
                "node": true
            },
            "files": [
                ".eslintrc.{js,cjs}"
            ],
            "parserOptions": {
                "sourceType": "script"
            }
        }
    ],
    "parserOptions": {
        "ecmaVersion": "latest",
        "parser": "@typescript-eslint/parser",
        "sourceType": "module"
    },
    "plugins": [
        "@typescript-eslint",
        "vue"
    ],
    "rules": {
        "linebreak-style": [
            "error",
            "unix"
        ],
        "quotes": [
            "error",
            "double"
        ],
        "semi": [
            "error",
            "always"
        ]
    }
}

解释一下上面几个关键的内容:

parserOptions是专门对解析器进行能力定制的ecmaVersion: latest表示启用最新的es语法sourceType: module表示使用ES Moduleparser解析器:@typescript-eslint/parser

plugins插件中使用了@typescript-eslint/eslint-plugin(可以简写成@typescript-eslint)来对TS代码规则进行一些拓展,vue插件则专门针对vue代码

2、Prettier

搞定eslint之后,我们需要安装Prettier来做代码格式化,虽然eslint也可以做这件事,但是术业有专攻,eslint主要优势在于代码的风格检查并给出提示,所以企业中常常使用Eslint+Prettier的组合来约束我们的代码规范

安装prettier

pnpm add prettier -D

在根目录新建.prettierrc.cjs配置文件,并填写如下配置内容:

// .prettierrc.cjs
module.exports = {
  printWidth: 80, //一行的字符数,如果超过会进行换行,默认为80
  tabWidth: 2, // 一个 tab 代表几个空格数,默认为 2 个
  useTabs: false, //是否使用 tab 进行缩进,默认为false,表示用空格进行缩减
  singleQuote: true, // 字符串是否使用单引号,默认为 false
  semi: true, // 行尾是否使用分号,默认为true
  trailingComma: "none", // 是否使用尾逗号
  bracketSpacing: true // 对象大括号直接是否有空格,默认为 true

接下来需要把Prettier集成到现有的Eslint工具中,安装这两个包

pnpm i eslint-config-prettier eslint-plugin-prettier -D

其中eslint-config-prettier用来覆盖 ESLint 本身的规则配置,而eslint-plugin-prettier则是用于让 Prettier 来接管eslint --fix即修复代码的能力。在 .eslintrc.js 配置文件中接入 prettier 的相关工具链

// .eslintrc.cjs
module.exports = {
    "env": {
        "browser": true,
        "es2021": true
    },
    "extends": [
        "eslint:recommended",
        "plugin:@typescript-eslint/recommended",
        "plugin:vue/vue3-essential",
        // 1. 接入 prettier 的规则
        "prettier",
        "plugin:prettier/recommended"
    ],
    "overrides": [
        {
            "env": {
                "node": true
            },
            "files": [
                ".eslintrc.{js,cjs}"
            ],
            "parserOptions": {
                "sourceType": "script"
            }
        }
    ],
    "parserOptions": {
        "ecmaVersion": "latest",
        "parser": "@typescript-eslint/parser",
        "sourceType": "module"
    },
    "plugins": [
        "@typescript-eslint",
        "vue",
        // 2. 加入 prettier 的 eslint 插件
        "prettier"
    ],
    "rules": {
        // 3. 注意要加上这一句,开启 prettier 自动修复的功能
        "prettier/prettier": "error",
        "linebreak-style": [
            "error",
            "unix"
        ],
        "quotes": [
            "error",
            "double"
        ],
        "semi": [
            "error",
            "always"
        ]
    }
}

package.json文件中定义脚本

// package.json
{
  "scripts": {
    // 省略已有 script
    "lint:script": "eslint --ext .js,.ts,.vue --fix --quiet ./src",
  }
}

然后我们执行命令试一下效果

pnpm run lint:script

Vue3.3+Vite4.x+Typecript+Idux带你从0到1打造企业级项目(3万字)

可以看到,校验已经生效了,根据报错提示不能使用单引号,这是因为我们在.prettierrc.cjs中配置了允许单引号,而初始化eslint时选择了双引号。我们修改需要一下规则

// .eslintrc.cjs
{
    "rules": {
        "quotes": [
            "error",
            "single"
        ],
    }
}

再次运行命令,此时校验通过,不再报错

Vue3.3+Vite4.x+Typecript+Idux带你从0到1打造企业级项目(3万字)

关于如何配置rules,可以参考Eslint rules文档,这里不过多赘述。 不过每次执行这个命令未免会有些繁琐,我们可以在VSCode中安装ESLintPrettier这两个插件,并且在设置区中开启Format On Save:

Vue3.3+Vite4.x+Typecript+Idux带你从0到1打造企业级项目(3万字)

Vue3.3+Vite4.x+Typecript+Idux带你从0到1打造企业级项目(3万字)

我们修改main.ts中的单引号变成双引号

Vue3.3+Vite4.x+Typecript+Idux带你从0到1打造企业级项目(3万字)

此时按Ctrl+S保存,自动格式化成单引号,则说明Vscode插件生效了,它会根据我们的.eslintrc.cjs.prettierrc.cjs来自动格式化代码

Vue3.3+Vite4.x+Typecript+Idux带你从0到1打造企业级项目(3万字)

有一些同学说Ctrl+S无法自动格式化,可以在settings.json配置文件中加上这个配置: "editor.codeActionsOnSave": { "source.fixAll.eslint": true }

除了安装编辑器插件,我们也可以通过 Vite 插件的方式在开发阶段进行 ESLint 扫描,以命令行的方式展示出代码中的规范问题,并能够直接定位到原文件。

pnpm add vite-plugin-eslint -D

vite.config.ts中配置

// vite.config.ts
import viteEslint from 'vite-plugin-eslint';

{
  plugins: [
    viteEslint(),
  ]
}

3、Stylelint

Stylelint是专门对样式代码规范检查的,安装相关的依赖

pnpm add stylelint stylelint-prettier stylelint-config-prettier stylelint-config-recess-order stylelint-config-standard stylelint-config-standard-less -D

在根目录下创建.stylelintrc.cjs

// .stylelintrc.js
module.exports = {
  // 注册 stylelint 的 prettier 插件
  plugins: ['stylelint-prettier'],
  // 继承一系列规则集合
  extends: [
    // standard 规则集合
    'stylelint-config-standard',
    // standard 规则集合的 less 版本
    'stylelint-config-standard-less',
    // 样式属性顺序规则
    'stylelint-config-recess-order',
    // 接入 Prettier 规则
    'stylelint-config-prettier',
    'stylelint-prettier/recommended'
  ],
  // 配置 rules
  rules: {
    // 开启 Prettier 自动格式化功能
    'prettier/prettier': true
  }
};

然后在package.json文件中添加对应的脚本

// package.json
{
  "scripts": {
    // 整合 lint 命令
    "lint": "npm run lint:script && npm run lint:style",
    // stylelint 命令
    "lint:style": "stylelint --fix \"src/**/*.{css,less,vue}\""
  }
}

执行pnpm run lint:style会有一个警告提示

Vue3.3+Vite4.x+Typecript+Idux带你从0到1打造企业级项目(3万字)

这是因为stylelint默认只会对.css文件进行校验,但是我们还想校验.vue文件中<style lang="less"></style>的样式代码的话,需要额外处理

安装支持vue的插件

pnpm add stylelint-config-recommended-vue postcss-html postcss-less -D

.stylelintrc.cjs中配置

// .stylelintrc.cjs
{
    extends: [
        //忽略其他
        // vue 规则
        'stylelint-config-recommended-vue'
    ],
    overrides: [
        {
          files: ['**/*.{html,vue}'],
          customSyntax: 'postcss-html'
        },
        {
          files: ['**/*.less'],
          customSyntax: 'postcss-less'
        }
    ]
}

再次执行pnpn run lint:style,此时不再报错或警告

Vue3.3+Vite4.x+Typecript+Idux带你从0到1打造企业级项目(3万字)

eslint类似,stylelint也有相同得vite插件

pnpm add vite-plugin-stylelint -D

vite.config.ts中添加

// vite.config.ts
import viteStylelint from 'vite-plugin-stylelint';

{
  plugins: [
    // 省略其它插件
    viteStylelint(),
  ]
}

vite.config.ts添加的eslintprettier插件可以保证我们在dev或者build都会去校验代码

eslintstylelint都可以添加ignore文件用于忽略某个目录或者文件的校验,在根目录下新建.eslintignore.stylelintignore

// .eslintignore
node_modules
dist
public
publish
pnpm-lock.yaml
// .stylelintignore
node_modules
dist
public
publish
pnpm-lock.yaml

这里根据你们具体的项目情况填写即可

最后,输入pnpm run lint可以同时校验jscss了,那么代码规范的内容就讲到这。

Vue3.3+Vite4.x+Typecript+Idux带你从0到1打造企业级项目(3万字)

正常来说,Ctrl+S是可以自动格式化样式代码的,如果不生效,和eslint的操作一样,可以在settings.json配置文件中加上这个配置:

"editor.codeActionsOnSave": { "source.fixAll.stylelint": true }

OK,我们代码规范的内容就讲到这。

提交规范

上面提到的代码规范只能说是在开发阶段提前暴露问题,并不能保证把不规范的代码带到线上,因此我们需要在代码推送到远端时就要对代码进行校验,如果不符合规范,则不允许提交。社区中已经有了对应的工具——Husky来完成这件事情

1、初始化git仓库

首先需要先初始化一个git仓库,由于之前我把vite默认创建的.git目录给删掉了,所以重新再手动初始化一次

git init

2、使用husky来校验提交的代码

安装依赖

pnpm add husky -D

初始化npx husky install,并将 husky install作为项目启动前脚本

// package.json
{
  "scripts": {
    // 会在安装 npm 依赖后自动执行
    "prepare": "husky install"
  }
}

Vue3.3+Vite4.x+Typecript+Idux带你从0到1打造企业级项目(3万字)

添加 husky 钩子,在终端执行如下命令

npx husky add .husky/pre-commit "npm run lint"

Vue3.3+Vite4.x+Typecript+Idux带你从0到1打造企业级项目(3万字)

现在,当你执行 git commit 的时候,会首先执行 npm run lint脚本,通过lint检查后才会正式提交代码记录。不过呢,这样操作会对我们整个项目进行全量检测,也就是说,即使没有任何修改,也会走一次lint校验,这是没有必要的,而lint-staged就是用来解决上述全量扫描问题的,可以实现只对存入暂存区的文件进行lint检查,大大提高了提交代码的效率。

3、使用lint-staged来扫描暂存区的代码

pnpm add lint-staged -D

然后在package.json中添加如下配置

// package.json
{
  "script": {
     //省略其它
     "lint-staged": "lint-staged"
  },
  "lint-staged": {
    "**/*.{vue,js,ts}": [
      "eslint --fix"
    ],
    "**/*.{vue,css,less}": [
      "stylelint --fix
    ]
  }
}

接下来我们需要在husky中应用lint-stage,回到.husky/pre-commit脚本中,将原来的npm run lint换成如下脚本

npx --no-install -- lint-staged

如此一来,我们便实现了提交代码时的增量lint检查

4、使用commitlint来规范化git commit信息

项目中规范commit信息也是非常有必要的,规范的 commit 信息能够方便团队协作和问题定位

安装依赖

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

项目根目录下新建commitlint.config.cjs

// commitlint.config.cjs
module.exports = {
  extends: ["@commitlint/config-conventional"]
};

@commitlint/config-conventional规定了commit信息的一般有两个部分:typesubject

// type 指提交的类型
// subject 指提交的摘要信息
<type>: <subject>

常用的 type 值包括如下:

  • feat: 添加新功能。
  • fix: 修复 Bug。
  • chore: 一些不影响功能的更改。
  • docs: 专指文档的修改。
  • perf: 性能方面的优化。
  • refactor: 代码重构。
  • test: 添加一些测试代码等等。

commitlint的功能集成到husky的钩子中

npx husky add .husky/commit-msg "npx --no-install commitlint --edit $1"

会发现,在.husky目录中会多一个commit-msg文件

Vue3.3+Vite4.x+Typecript+Idux带你从0到1打造企业级项目(3万字)

我们尝试一下提交文件,输入一个不符合commitlint规范的信息,可以提示我们需要按照规范输入 Vue3.3+Vite4.x+Typecript+Idux带你从0到1打造企业级项目(3万字)

需要输入正确的commit后可以提交成功

Vue3.3+Vite4.x+Typecript+Idux带你从0到1打造企业级项目(3万字)

OK,我们提交规范的内容就讲到这。

处理静态资源

这一小节主要介绍如何在项目引入静态资源,例如图片、视频等。拿图片来说

在模板中引入

<template>
    <div>
        <img src="./assets/imgs/vite.svg"/>
    </div>
</template>

style中引入

.logo-img {
    background: url('./assets/imgs/vite.svg') no-repeat;
}

以上相对路径的方式,我们更多是通过配置别名,这样不仅可以在script中作为资源引入,更方便了我们的书写,不然你会看到很多地狱路径

background: url('../../../../../assets/imgs/vite.svg')

1、在vite中配置别名

// vite.config.ts
import path from 'path';

{
  resolve: {
    // 别名配置
    alias: {
      '@assets': path.join(__dirname, './src/assets')
    }
  }
}

script中引入

// App.vue
<template>
  <div class="demo">
    <div class="demo-text">hello idux</div>
    <img :src="logoImg" alt="logo" width="300" />
  </div>
</template>

<script setup lang="ts">
import logoImg from '@assets/imgs/vite.svg';
</script>

<style lang="less" scoped>
.demo {
  &-text {
    color: @color;
    text-decoration: dashed;
  }
}
</style>

Vue3.3+Vite4.x+Typecript+Idux带你从0到1打造企业级项目(3万字)

style中同样使用别名,注意这里就不能继续用img标签了

// App.vue
<template>
  <div class="demo">
    <div class="demo-text">hello idux</div>
    <div class="logo-img"></div>
  </div>
</template>

<style lang="less" scoped>
.demo {
  &-text {
    color: @color;
    text-decoration: dashed;
  }

  .logo-img {
    width: 300px;
    height: 300px;
    background: url('@assets/imgs/vite.svg') no-repeat;
    background-size: cover;
  }
}
</style>

效果是一样的,这里就不放图了

2、SVG组件方式加载

上面案例用到了svg,业界有很多关于svg的最佳实践,这里介绍一种,把svg作为组件的方式引入。社区中已经有了对应的插件支持

pnpm add vite-svg-loader -D

vite中添加插件

// vite.config.ts
import svgLoader from 'vite-svg-loader';

{
  plugins: [
    // 其它插件省略
    svgLoader()
  ]
}

直接作为组件引入

<template>
  <div class="demo">
    <div class="demo-text">hello idux</div>
    <logoImg />
  </div>
</template>

<script setup lang="ts">
import logoImg from '@assets/imgs/vite.svg';
</script>

<style lang="less" scoped>
.demo {
  &-text {
    color: @color;
    text-decoration: dashed;
  }
}
</style>

可以看到,目前组件渲染出来的就是一个svg标签

Vue3.3+Vite4.x+Typecript+Idux带你从0到1打造企业级项目(3万字)

3、生产环境处理

在前面的内容中,我们围绕着如何加载静态资源这个问题,在 vite 中进行具体的编码实践,相信对于 vite 中各种静态资源的使用你已经比较熟悉了。但另一方面,在生产环境下,我们又面临着一些新的问题。

  • 部署域名怎么配置?
  • 资源打包成单文件还是作为 Base64 格式内联?
  • 图片太大了怎么压缩?
3.1、自定义部署域名

一般在我们访问线上的站点时,站点里面一些静态资源的地址都包含了相应域名的前缀,如:

<video src="https://idux-cdn.sangfor.com.cn/medias/home-banner.mp4" autoplay loop>您的浏览器不支持 video 标签。</video>

以上面这个地址例子,https://idux-cdn.sangfor.com.cn是 CDN 地址前缀,/medias/home-banner.mp4则是我们开发阶段使用的路径。那么,我们是不是需要在上线前把图片先上传到 CDN,然后将代码中的地址手动替换成线上地址呢?这样就太麻烦了!

在 Vite 中我们可以有更加自动化的方式来实现地址的替换,只需要在配置文件中指定base参数即可:

// vite.config.ts
// 是否为生产环境,在生产环境一般会注入 NODE_ENV 这个环境变量,见下面的环境变量文件配置
const isProduction = process.env.NODE_ENV === 'production';
// 填入项目的 CDN 域名地址
const CDN_URL = 'https://idux-cdn.sangfor.com.cn';

{
  base: isProduction ? CDN_URL : '/'
}

根目录下新增.env.development.env.production

// .env.development
NODE_ENV=development

// .env.production
NODE_ENV=production

顾名思义,即分别在开发环境和生产环境注入一些环境变量,这里为了区分不同环境我们加上了NODE_ENV,你也可以根据需要添加别的环境变量。

打包的时候 vite 会自动将这些环境变量替换为相应的字符串。

在项目中引入资源

<template>
  <div class="demo">
    <div class="demo-text">hello idux</div>
    <logoImg />
    <div>
      <video width="500" :src="bannerVideo" autoplay loop>
        您的浏览器不支持 video 标签。
      </video>
    </div>
  </div>
</template>

<script setup lang="ts">
import logoImg from '@assets/imgs/vite.svg';
import bannerVideo from '@assets/medias/home-banner.mp4';
</script>

<style lang="less" scoped>
.demo {
  &-text {
    color: @color;
    text-decoration: dashed;
  }
}
</style>

可以正常展示 Vue3.3+Vite4.x+Typecript+Idux带你从0到1打造企业级项目(3万字)

我们执行pnpm run build,可以看到产物中已经自动加上了CDN地址前缀了

Vue3.3+Vite4.x+Typecript+Idux带你从0到1打造企业级项目(3万字)

当然,HTML 中的一些 JSCSS 资源链接也一起加上了 CDN 地址前缀

Vue3.3+Vite4.x+Typecript+Idux带你从0到1打造企业级项目(3万字) 除了CDN之外,有时候可能项目中的某些图片需要存放到另外的存储服务,一种直接的方案是将完整地址写死到 src属性 中,如:

<img src="https://my-image-cdn.com/logo.png">

这样做显然是不太优雅的,我们可以通过定义环境变量的方式来解决这个问题,在项目根目录新增.env文件:

// .env
VITE_IMG_BASE_URL=https://idux.site

开发环境优先级: .env.development > .env

生产环境优先级: .env.production > .env

然后进入 src/vite-env.d.ts增加类型声明:

/// <reference types="vite/client" />

interface ImportMetaEnv {
  // 自定义的环境变量
  readonly VITE_IMG_BASE_URL: string;
}

interface ImportMeta {
  readonly env: ImportMetaEnv;
}

如果某个环境变量要在 vite 中通过 import.meta.env 访问,那么它必须以VITE_开头,如VITE_IMG_BASE_URL

我们在项目中增加一个img

<template>
  <div class="demo">
    <div class="demo-text">hello idux</div>
    <logoImg />
    <div>
      <video width="500" :src="bannerVideo" autoplay loop>
        您的浏览器不支持 video 标签。
      </video>
    </div>
    <img :src="iduxLogoUrl" alt="idux logo" width="200" />
  </div>
</template>

<script setup lang="ts">
import logoImg from '@assets/imgs/vite.svg';
import bannerVideo from '@assets/medias/home-banner.mp4';
// 通过import.meta.env获取env环境变量
const iduxLogoUrl = `${import.meta.env.VITE_IMG_BASE_URL}/icons/logo.svg`;
</script>

<style lang="less" scoped>
.demo {
  &-text {
    color: @color;
    text-decoration: dashed;
  }
}
</style>

可以看到,img路径已经成功被替换成env中的路径

Vue3.3+Vite4.x+Typecript+Idux带你从0到1打造企业级项目(3万字)

至此,我们就解决了生成环境下域名替换的问题。

3.2、单文件or内联

vite 中内置的优化方案是下面这样的:

  • 如果静态资源体积 >= 4KB,则提取成单独的文件
  • 如果静态资源体积 < 4KB,则作为 base64 格式的字符串内联

上述的4 KB即为提取成单文件的临界值,当然,这个临界值你可以通过build.assetsInlineLimit自行配置,如下代码所示:

// vite.config.ts
{
  build: {
    // 8 KB
    assetsInlineLimit: 8 * 1024
  }
}

svg 格式的文件不受这个临时值的影响,始终会打包成单独的文件

3.3、图片压缩

社区中已经有了成熟的图片压缩方案:vite-plugin-imagemin

安装依赖

pnpm add vite-plugin-imagemin -D

如果安装不上可以参考这篇文章:blog.csdn.net/qq_43806604…

然后在vite.config.ts中配置

//vite.config.ts
import viteImagemin from 'vite-plugin-imagemin';
{
  plugins: [
    // 忽略前面的插件
    viteImagemin({
      // 无损压缩配置,无损压缩下图片质量不会变差
      optipng: {
        optimizationLevel: 7
      },
      // 有损压缩配置,有损压缩下图片质量可能会变差
      pngquant: {
        quality: [0.8, 0.9],
      },
      // svg 优化
      svgo: {
        plugins: [
          {
            name: 'removeViewBox'
          },
          {
            name: 'removeEmptyAttrs',
            active: false
          }
        ]
      }
    })
  ]
}

为了测试我特意找了张.png格式的图片

Vue3.3+Vite4.x+Typecript+Idux带你从0到1打造企业级项目(3万字)

Vue3.3+Vite4.x+Typecript+Idux带你从0到1打造企业级项目(3万字)

这张图片大小是10.4KB,按照之前配置的应该会打包成单文件

使用之前vite-plugin-imagemin进行压缩之前:

Vue3.3+Vite4.x+Typecript+Idux带你从0到1打造企业级项目(3万字)

使用之前vite-plugin-imagemin进行压缩之后:

Vue3.3+Vite4.x+Typecript+Idux带你从0到1打造企业级项目(3万字)

可以看到效果非常明显,这对我们的性能有很大提升

OK,我们处理静态资源的内容就讲到这。

TSX

写过组件库的都知道,tsx在语法上会比较有优势,我们把它接入进来

安装依赖

pnpm add @vitejs/plugin-vue-jsx -D

使用插件

// vite.config.ts
import vueJsx from '@vitejs/plugin-vue-jsx';

{
    "plugins": [
        //其他配置
        vueJsx()
    ]
}

补充lint命令支持后缀

{
    "script": {
        "lint:script": "eslint --ext .js,.ts,.tsx,.vue --fix --quiet ./src",
    }
}

编写组件需要以.tsx结尾

// src/componens/Demo.tsx
import { defineComponent } from 'vue';

export default defineComponent({
  setup() {
    return () => <div>hello idux</div>;
  }
});

Vue3.3+Vite4.x+Typecript+Idux带你从0到1打造企业级项目(3万字)

OK, 这里可以顺利执行。

Pinia状态管理

大型项目中一般有状态管理的需求,我们首当其冲的使用:pinia

安装依赖

pnpm add pinia pinia-plugin-persist

插件pinia-plugin-persist支持数据持久化(将store的数据缓存到storage中)

创建别名

// vite.config.ts

{
    resolve: {
        alias: {
          // 其他配置
          '@store/*': path.resolve(__dirname, './src/store')
        }
    }
}

配置tsconfig.json防止类型报错

{
    "compilerOptions": {
        // 其他配置
        "types": [
          "pinia-plugin-persist"
        ],
        "baseUrl": ".",
        "paths": {
          "@store/*": ["./src/store/*"]
        },
        "strict": false,
    }
}

创建store实例

// src/store/index.ts
import { createPinia } from 'pinia';
import { App } from 'vue';
import piniaPersist from 'pinia-plugin-persist'; //数据持久化

const store = createPinia();

const install = (app: App): void => {
  store.use(piniaPersist);

  app.use(store);
};
export default { install };

注册store

// src/main.ts
import { createApp } from 'vue';
import store from './store';
import App from './App.vue';

createApp(App).use(store).mount('#app');

创建useCountStore

// src/store/use_count_store.ts
import { defineStore } from 'pinia';
import { ref } from 'vue';

export const useCountStore = defineStore('count', () => {
  const count = ref(0);
  const increment = () => {
    count.value++;
  };
  return {
    count,
    increment
  };
});

src/components文件夹下创建Demo.vue并在App.vue中引入,用来测试状态是否共享

// App.vue
<template>
  <div class="demo">
    <div class="demo-text">hello idux</div>
    <br />
    <logoImg />
    <br />
    <video width="500" :src="bannerVideo" autoplay loop>
      您的浏览器不支持 video 标签。
    </video>
    <br />
    <img :src="iduxLogoUrl" alt="idux logo" width="200" />
    <br />
    <img src="@assets/imgs/vue3.png" alt="vue3.x" />
    <br />
    <div>
      App Count: {{ count }}
      <button @click="increment">新增</button>
    </div>
    <br />
    <DemoCmp />
  </div>
</template>

<script setup lang="ts">
import logoImg from '@assets/imgs/vite.svg';
import bannerVideo from '@assets/medias/home-banner.mp4';
import { useCountStore } from '@store/use_count_store';
import { storeToRefs } from 'pinia';
import DemoCmp from './components/Demo.vue';
// 通过import.meta.env获取env环境变量
const iduxLogoUrl = `${import.meta.env.VITE_IMG_BASE_URL}/icons/logo.svg`;

const store = useCountStore();
const { increment } = store;
const { count } = storeToRefs(store);
</script>

<style lang="less" scoped>
.demo {
  &-text {
    color: @color;
    text-decoration: dashed;
  }
}
</style>

// Demo.vue
<template>
  Demo Count: {{ count }}
  <button @click="increment">新增</button>
</template>

<script setup lang="ts">
import { useCountStore } from '@store/use_count_store';
import { storeToRefs } from 'pinia';
const store = useCountStore();
const { increment } = store;
const { count } = storeToRefs(store);
</script>

可以看到Demo.vue组件会有一个报错:组件文件名必须驼峰命名,解决方案:

  • 修改组件名为驼峰格式,例如:DemoComponent.vue或者配置name: DemoComponent
  • 配置eslint规则,增加:vue/multi-word-component-names: off的规则

执行项目,可以看到状态已经同步,配合vue-devtools效果更佳

Vue3.3+Vite4.x+Typecript+Idux带你从0到1打造企业级项目(3万字)

pinia持久化的配置这里就不过多介绍了,网上已经有很多详细的介绍方案。

OK,pinia的内容就讲到这。

Vue-Router

接下来,我们要接入router路由

安装依赖

pnpm add vue-router@latest

创建路由实例

// src/router/index.ts
import type { App } from 'vue';
import { createRouter, createWebHashHistory } from 'vue-router';
import routes from './routes'

const router = createRouter({
  routes,
  history: createWebHashHistory()
});

const install = (app: App): void => {
  app.use(router);
};

export default { install };

创建路由,这里我们随意创建个首页和登录页的路由作为案例

// src/router/routes.ts
import type { RouteRecordRaw } from 'vue-router';

const routes: RouteRecordRaw[] = [
  {
    path: '/',
    redirect: '/home'
  },
  {
    path: '/home',
    component: () => import('@views/Home/Index.vue'), //在vite.config.ts中配置@views别名
    meta: {
      title: '首页'
    }
  },
  {
    path: '/login',
    component: () => import('@views/Login/Index.vue'),
    meta: {
      title: '登录'
    }
  }
];

export default routes;

创建HomeLogin组件

// src/components/Home/Index.vue
<template>
  <div>首页</div>
</template>

// src/components/Login/Index.vue
<template>
  <div>登录页</div>
</template>

注册路由

// src/main.ts
import { createApp } from 'vue';
import store from './store';
import router from './router';
import App from './App.vue';

createApp(App).use(store).use(router).mount('#app');

修改tsconfig.json防止类型报错

{
    "compilerOptions": {
        // 其他配置
        "moduleResolution": "node"
    }
}

App.vue添加<RouterView />组件,并添加跳转按钮测试

<template>
  <div class="demo">
    <div class="demo-text">hello idux</div>
    <br />
    <logoImg />
    <br />
    <video width="500" :src="bannerVideo" autoplay loop>
      您的浏览器不支持 video 标签。
    </video>
    <br />
    <img :src="iduxLogoUrl" alt="idux logo" width="200" />
    <br />
    <img src="@assets/imgs/vue3.png" alt="vue3.x" />
    <br />
    <div>
      App Count: {{ count }}
      <button @click="increment">新增</button>
    </div>
    <br />
    <DemoCmp />
    <br />
    <div style="margin-top: 30px">
      测试路由
      <button @click="goHome">去首页</button>
      <button @click="goLogin">去登录页</button>
      <RouterView />
    </div>
  </div>
</template>

<script setup lang="ts">
import { RouterView, useRouter } from 'vue-router';
import logoImg from '@assets/imgs/vite.svg';
import bannerVideo from '@assets/medias/home-banner.mp4';
import { useCountStore } from '@store/use_count_store';
import { storeToRefs } from 'pinia';
import DemoCmp from './components/DemoComponents.vue';
// 通过import.meta.env获取env环境变量
const iduxLogoUrl = `${import.meta.env.VITE_IMG_BASE_URL}/icons/logo.svg`;

const store = useCountStore();
const { increment } = store;
const { count } = storeToRefs(store);

const router = useRouter();

const goHome = () => {
  router.push({
    path: '/home'
  });
};
const goLogin = () => {
  router.push({
    path: '/login'
  });
};
</script>

<style lang="less" scoped>
.demo {
  &-text {
    color: @color;
    text-decoration: dashed;
  }
}
</style>

运行项目看一下效果,可以看到已经可以实现路由的跳转了 Vue3.3+Vite4.x+Typecript+Idux带你从0到1打造企业级项目(3万字)

OK, 关于路由的介绍就到这里。

Idux组件库

前期基础工作都准备的差不多了,我们可以开始接入组件库了

1、初始化Idux

安装依赖

pnpm add @idux/cdk @idux/components @idux/pro

引入idux

// src/plugins/idux.ts
import type { App } from 'vue';

import "@idux/components/default.full.css"; 
import "@idux/pro/default.css";

// 如果不需要 reset 全局样式和滚动条样式,移除下面 2 行代码
import '@idux/components/style/core/reset.default.css';
import '@idux/components/style/core/reset-scroll.default.css';

import IduxCdk from '@idux/cdk';
import IduxComponents from '@idux/components';
import IduxPro from '@idux/pro';

import { createGlobalConfig } from '@idux/components/config';

// 动态加载图标:不会被打包,可以减小包体积,需要加载的时候时候 http 请求加载
const loadIconDynamically = (iconName: string) => {
  return fetch(`/idux-icons/${iconName}.svg`).then((res) => res.text());
};

const globalConfig = createGlobalConfig({
  // 默认为中文,可以打开注释设置为其他语言
  // locale: enUS,
  icon: { loadIconDynamically }
});

const install = (app: App): void => {
  app.use(IduxCdk).use(IduxComponents).use(IduxPro).use(globalConfig);
};

export default { install };

动态加载图标,需要安装vite-plugin-static-copy

pnpm add vite-plugin-static-copy -D

使用插件

// vite.config.ts
import { viteStaticCopy } from 'vite-plugin-static-copy';

{
    "plugins": [
        //其他配置
        viteStaticCopy({
          targets: [
            {
              src: './node_modules/@idux/components/icon/assets/*.svg',
              dest: 'idux-icons'
            }
          ]
        })
    ]
}

这样打包的时候,svg就作为单独的资源文件放在idux-icons目录,而不是打包进js文件中

Vue3.3+Vite4.x+Typecript+Idux带你从0到1打造企业级项目(3万字)

导出idux

// src/plugins/index.ts
import idux from './idux';

export { idux };

注册idux

// src/main.ts
import { createApp } from 'vue';
import store from './store';
import { idux } from './plugins';
import router from './router';
import App from './App.vue';

createApp(App).use(store).use(router).use(idux).mount('#app');

处理组件类型,创建types目录并新建idux.d.ts,同时把原来的vite-env.d.ts移动到该目录下

// idux.d.ts
/// <reference types="@idux/cdk/types" />
/// <reference types="@idux/components/types" />
/// <reference types="@idux/pro/types" />

Vue3.3+Vite4.x+Typecript+Idux带你从0到1打造企业级项目(3万字)

2、创建IduxProvider组件和utils

IduxProvider方便我们全局注入provider,可以使用idux提供的hooks函数

// src/components/idux-provider/IduxProvider.vue
<template>
  <IxDrawerProvider ref="drawerProviderRef">
    <IxNotificationProvider>
      <IxModalProvider ref="modalProviderRef">
        <IxMessageProvider>
          <IduxProviderRegister></IduxProviderRegister>
          <slot></slot>
        </IxMessageProvider>
      </IxModalProvider>
    </IxNotificationProvider>
  </IxDrawerProvider>
</template>

<script setup lang="ts">
import { type DrawerProviderInstance } from '@idux/components/drawer';
import { type ModalProviderInstance } from '@idux/components/modal';

import { onMounted, ref } from 'vue';
import { useRouter } from 'vue-router';
import IduxProviderRegister from './IduxProviderRegister.vue';

const drawerProviderRef = ref<DrawerProviderInstance>();
const modalProviderRef = ref<ModalProviderInstance>();

const router = useRouter();

onMounted(() => {
  // 每次路由切换时销毁当前的抽屉和弹窗(仅对通过 useDrawer/useModal 创建的生效)
  router.afterEach(() => {
    drawerProviderRef.value!.destroyAll();
    modalProviderRef.value!.destroyAll();
  });
});
</script>
<script setup lang="ts">
// src/components/idux-provider/IduxProviderRegister.vue
import { useDrawer } from '@idux/components/drawer';
import { useMessage } from '@idux/components/message';
import { useModal } from '@idux/components/modal';
import { useNotification } from '@idux/components/notification';

import { registerProviders } from '@utils'; //需要在vite.config.ts中配置@utils别名

const drawer = useDrawer();
const notification = useNotification();
const modal = useModal();
const message = useMessage();

registerProviders({ drawer, notification, modal, message });
</script>

<!-- eslint-disable-next-line vue/valid-template-root -->
<template></template>

因为上面的useDrawerhooks需要在setup中才能使用,所以我们可以把实例存在全局的变量中,这样在ts文件也可以使用

// src/utils/iduxProviders.ts
import { DrawerProviderRef } from '@idux/components/drawer';
import { NotificationProviderRef } from '@idux/components/notification';
import { ModalProviderRef } from '@idux/components/modal';
import { MessageProviderRef } from '@idux/components/message';

let Drawer: DrawerProviderRef | undefined;
let Notification: NotificationProviderRef | undefined;
let Modal: ModalProviderRef | undefined;
let Message: MessageProviderRef | undefined;

// 方便在 ts 中直接调用
export function registerProviders(option: {
  drawer: DrawerProviderRef;
  notification: NotificationProviderRef;
  modal: ModalProviderRef;
  message: MessageProviderRef;
}): void {
  Drawer = option.drawer;
  Notification = option.notification;
  Modal = option.modal;
  Message = option.message;
}

export { Drawer, Notification, Modal, Message };

导出组件utils

// src/components/idux-provider/index.ts
import IduxProvider from './IduxProvider.vue';

export { IduxProvider };
// src/utils/index.ts
export * from './iduxProviders';

App.vue中引入 我们把之前的测试代码全都删掉,保持项目的干净,并引入IduxProvider组件

<template>
  <IduxProvider>
    <router-view />
  </IduxProvider>
</template>

<script setup lang="ts">
//需要在vite.config.ts中配置@componrnts别名
import { IduxProvider } from '@components/idux-provider'; 
import { RouterView } from 'vue-router';
</script>

我们去Home组件中引入idux组件,试一下效果

// src/components/Home/Index.vue
<template>
  <div>首页</div>
  <IxButton mode="primary">Hello Idux</IxButton>
</template>

<script setup lang="ts">
import { defineComponent } from 'vue';
import { IxButton } from '@idux/components/button';
</script>

Vue3.3+Vite4.x+Typecript+Idux带你从0到1打造企业级项目(3万字)

可以看到已经可以引入组件了,我们再试试useMessagehooks的使用

// src/components/Home/Index.vue
<template>
  <div>首页</div>
  <IxButton mode="primary">Hello Idux</IxButton>
</template>

<script setup lang="ts">
import { IxButton } from '@idux/components/button';
import { useMessage } from '@idux/components/message';

const { info, success, warning, error, loading } = useMessage();
info('hello idux');
success('hello idux');
warning('hello idux');
error('hello idux');
loading('hello idux');
</script>

Vue3.3+Vite4.x+Typecript+Idux带你从0到1打造企业级项目(3万字)

可以成功使用了,我们在试试直接在ts文件中使用

// src/components/Home/hooks.ts
import { Message } from '@utils';

const useIduxMessage = (): void => {
  Message?.success('hello, idux');
};

export { useIduxMessage };

Home组件引入

<template>
  <div>首页</div>
  <IxButton mode="primary">Hello Idux</IxButton>
</template>

<script setup lang="ts">
import { IxButton } from '@idux/components/button';
import { useIduxMessage } from './hooks';

useIduxMessage();
</script>

Vue3.3+Vite4.x+Typecript+Idux带你从0到1打造企业级项目(3万字)

看一下效果,也是可以提示的

setup中可以使用useMessagehooks函数,因为这是依赖于provider/inject的注入,这就是IduxProvider组件的作用,而在ts文件中使用的话会报错,所以我们通过挂载全局变量的方式使用,达到同样的效果

3、通过unplugin-vue-components按需加载

在上面例子中,我们可以看到组件是通过手动引入的方式

import { IxButton } from '@idux/components/button';

如果使用的组件一多,那需要写特别多的import。而且我们前面注册idux时,直接use了整个组件库,为了降低打包的文件体积,我们使用按需加载的方式使用组件

安装依赖

pnpm add unplugin-vue-components -D

添加插件

// vite.config.ts
import { IduxResolver } from 'unplugin-vue-components/resolvers';
import Components from 'unplugin-vue-components/vite';

{
    "plugins": [
        //其他配置
        Components({
          resolvers: [
            // 可以通过指定 `importStyle` 来按需加载 css 或 less 代码, 也支持不同的主题
            IduxResolver({ importStyle: 'css', importStyleTheme: 'default' })
          ],
          dts: false //不生成d.ts文件
        })
    ]
}

移除注册代码

// src/plugins/idux.ts
// 移除
- import "@idux/components/default.full.css"; 
- import "@idux/pro/default.css";
// 新增
+ import '@idux/cdk/index.css'
// 移除
- import IduxCdk from "@idux/cdk"; 
- import IduxComponents from "@idux/components"; 
- import IduxPro from "@idux/pro";

const install = (app: App): void => { 
    // 移除
    - app.use(IduxCdk).use(IduxComponents).use(IduxPro).use(globalConfig); 
    // 新增
    + app.use(globalConfig) 
};

完整代码如下

// src/plugins/idux.ts
import type { App } from 'vue';

import '@idux/cdk/index.css';
// 如果不需要 reset 全局样式和滚动条样式,移除下面 2 行代码
import '@idux/components/style/core/reset.default.css';
import '@idux/components/style/core/reset-scroll.default.css';

import { createGlobalConfig } from '@idux/components/config';
import {
  IDUX_ICON_DEPENDENCIES,
  addIconDefinitions
} from '@idux/components/icon';
// import { enUS } from "@idux/components/locales";

// 静态加载: `IDUX_ICON_DEPENDENCIES` 是 `@idux` 的部分组件默认所使用到图标,建议在此时静态引入。
addIconDefinitions(IDUX_ICON_DEPENDENCIES);

// 动态加载:不会被打包,可以减小包体积,需要加载的时候时候 http 请求加载
// 注意:请确认图标的 svg 资源被正确放入到 `public/idux-icons` 目录中
const loadIconDynamically = (iconName: string) => {
  return fetch(`/idux-icons/${iconName}.svg`).then((res) => res.text());
};

const globalConfig = createGlobalConfig({
  // 默认为中文,可以打开注释设置为其他语言
  // locale: enUS,
  icon: { loadIconDynamically }
});

const install = (app: App): void => {
  app.use(globalConfig);
};

export default { install };

首页组件也移除import的相关代码

<template>
  <div>首页</div>
  <IxButton mode="primary">Hello Idux</IxButton>
</template>

<script setup lang="ts">
import { useIduxMessage } from './hooks';

useIduxMessage();
</script>

Vue3.3+Vite4.x+Typecript+Idux带你从0到1打造企业级项目(3万字)

可以看到,已经可以成功实现按需加载了

OK,idux组件库的内容就讲到这。

Axios

接下来,我们准备接入axios,用来发请求 安装依赖

pnpm add axios

业界有很多二次封装axios的案例,这里就不费尽口舌了,仅介绍一些整体框架和思路

添加VITE_BASE_API_URL环境变量

// .env
VITE_IMG_BASE_URL=https://idux.site
VITE_BASE_API_URL=http://xxxxx //填写你要请求的url地址

添加到类型中

// src/types/vite.env.d.ts

/// <reference types="vite/client" />

interface ImportMetaEnv {
  // 自定义的环境变量
  readonly VITE_IMG_BASE_URL: string;
  readonly VITE_BASE_API_URL: string;
}

interface ImportMeta {
  readonly env: ImportMetaEnv;
}

添加showErrorMessage方法,当请求错误时用来提示

// src/utils/requeset.ts

// 请求错误时消息提示
import type { AxiosError } from 'axios';
import { Message } from './iduxProviders';

const showErrorMessgae = (error: AxiosError): void => {
  const { response } = error;
  if (!response) {
    return;
  }
  const { status } = response;
  switch (status) {
    case 401:
      Message?.warning('账号没有权限,无法访问资源');
      break;
    case 403:
      Message?.warning('请求的资源无法访问');
      break;
    case 404:
      Message?.error('请求的资源不存在');
      break;
    case 500:
      Message?.error('网络错误,请重试');
      break;
    default:
      Message?.error('网络错误,请重试');
      break;
  }
};

export { showErrorMessgae };

创建axios实例

// src/request/index.ts
import axios, {
  type AxiosRequestConfig,
  type AxiosError,
  type AxiosResponse
} from 'axios';
import { requestInterceptors, responseiInterceptors } from './interceptors';

const request = axios.create({
  baseURL: import.meta.env.VITE_BASE_API_URL,
  timeout: 1000 * 10 //10s超时
});

// 请求拦截器
requestInterceptors?.forEach((interceptors) => {
  request.interceptors.request.use(
    (config: AxiosRequestConfig) => {
      return interceptors?.resolve(config);
    },
    (error: AxiosError) => {
      return interceptors?.reject(error);
    }
  );
});

// 响应拦截器
responseiInterceptors?.forEach((interceptors) => {
  request.interceptors.response.use(
    (response: AxiosResponse) => {
      return interceptors?.resolve(response);
    },
    (error: AxiosError) => {
      return interceptors?.reject(error);
    }
  );
});

export default request;

创建errorStatus拦截器,专门处理错误状态码的

// src/request/interceptors/response/errorStatus.ts

// 专门处理失败状态码的拦截器
import type { AxiosError } from 'axios';
import { showErrorMessgae } from '@utils';

const errorStatusReject = (error: AxiosError): Promise<AxiosError> => {
  showErrorMessgae(error);
  return Promise.reject(error);
};

export default errorStatusReject;

整体层级结构

Vue3.3+Vite4.x+Typecript+Idux带你从0到1打造企业级项目(3万字)

创建mock

// src/components/Home/mock.json

{
  "data": {
    "list": [
      {
        "key": 1,
        "name": "张三",
        "age": 18
      },
      {
        "key": 2,
        "name": "李四",
        "age": 30
      }
    ]
  },
  "success": true
}

首页组件中发请求

// src/components/Home/Index.vue
<template>
  <div>首页</div>
  <IxButton mode="primary" @click="fetch">Hello Idux</IxButton>
</template>

<script setup lang="ts">
import { useIduxMessage } from './hooks';
import request from '@/request';

useIduxMessage();

const fetch = () => {
  request.get('./mock.json').then((res) => {
    console.log(res);
  });
};
</script>

Vue3.3+Vite4.x+Typecript+Idux带你从0到1打造企业级项目(3万字)

可以看到,已经可以成功提示错误消息了,但是为什么会404呢?我们需要设置代理等,社区有成熟的mock方案,那就是vite-plugin-mock

安装依赖

pnpm add vite-plugin-mock@2.9.8 mockjs -D

vite-plugin-mock已经升级到3.0版本,但是好像会报错,所以建议安装2.9.8

根目录下创建mock文件夹和demo.js

// mock/demo.js
export default [
  {
    url: '/api/demo',
    method: 'get',
    response: () => {
      return {
        data: {
          'list|0-10': [
            {
              key: '@id',
              name: '@cname',
              age: '@integer(18, 60)'
            }
          ],
          total: '@integer(0, 100)'
        },
        success: true
      };
    }
  }
];

使用插件

// vite.config.ts
import { viteMockServe } from 'vite-plugin-mock';
{
    "plugins": [
        //其他配置
        viteMockServe({
          mockPath: 'mock',
          localEnabled: !isProduction, //生产环境下不启用
          watchFiles: true
        }),
    ]
}

修改请求路径

// src/components/Home/Index.vue
<template>
  <div>首页</div>
  <IxButton mode="primary" @click="fetch">Hello Idux</IxButton>
</template>

<script setup lang="ts">
import { useIduxMessage } from './hooks';
import request from '@/request';

useIduxMessage();

const fetch = () => {
  request.get('/api/demo').then((res) => {
    console.log(res);
  });
};
</script>

Vue3.3+Vite4.x+Typecript+Idux带你从0到1打造企业级项目(3万字)

可以看到,已经可以成功请求mock

模拟403,也可以正确提示

Vue3.3+Vite4.x+Typecript+Idux带你从0到1打造企业级项目(3万字)

但是,这里还有个小问题,我们每次请求都去引入一次request

import request from '@/request';

那这样每次都会重复去创建一次实例

const request = axios.create()

export default request

这个过程似乎是没有必要的,提供个思路,我们同样可以放在全局变量上,只初始化一次,每次引入request的全局变量即可

// src/request/index.ts
import type { AxiosInstance } from 'axios';

let request: AxiosInstance;

const initAxios = (curRequest: AxiosInstance) => {
  request = curRequest;
};

export { request, initAxios };

main.ts中注入

// src/main.ts
import { createApp } from 'vue';
import store from './store';
import { idux } from './plugins';
import router from './router';
import request from './request/create'; //修改原来的request.ts变为create.ts
import { initAxios } from './request';
import App from './App.vue';

initAxios(request);

createApp(App).use(store).use(router).use(idux).mount('#app');

请求时直接引入即可,避免多次创建

import { request } from '@/request';

OK, axios相关的内容就讲到这。

使用Vitest进行测试

安装依赖

pnpm add vitest -D

我们直接使用官方的案例

Vue3.3+Vite4.x+Typecript+Idux带你从0到1打造企业级项目(3万字)

添加命令

// package.json
{
    "script": {
        //其他配置
        "test": "vitest"
    }
}

运行pnpm run test

Vue3.3+Vite4.x+Typecript+Idux带你从0到1打造企业级项目(3万字)

看到输出结果说明已经成功接入vitest

总结

经过上面的流程,我们已经把整个架构都搭好了,可以根据自己需求继续完善即可。

我们专门建立了@idux的企微群,如果有任何组件库相关的疑问都可以扫描进群询问

Vue3.3+Vite4.x+Typecript+Idux带你从0到1打造企业级项目(3万字)

项目代码位置:github.com/fengxiaodon…