(七)Vue3-huohuo-admin 开发配置(Vite配置)

1,145 阅读10分钟

本文已参与「新人创作礼」活动,一起开启掘金创作之路

本篇主要围绕我们项目根目录下的vite.config.ts文件进行,看看我们一般的项目该如何进行配置。

Vite 配置

**遇到问题时,建议首先阅读一遍官方文档。**这里建议先阅读一遍Vite官方中文文档中的配置 Vite配置文件部分内容,以帮助您更好的了解vite.config.js这个文件应该如何配置。

配置文件

配置文件出口

当以命令行方式运行vite时,Vite会自动解析 项目根目录 下名为vite.config.ts的文件。

最基础的配置文件是这样的:

// vite.config.ts
export default {
  // 配置选项
};

我们使用Vite创建的目前是这样的:

import { defineConfig } from "vite";
import vue from "@vitejs/plugin-vue";

// https://vitejs.dev/config/
export default defineConfig({
  plugins: [vue()],
});

defineConfig,顾名思义,定义配置。我们可以看到vite.config.ts文件暴露出了一个接口 defineConfig,这个接口函数接收一个对象参数,这个对象里面就包含了我们各种各样的vite 配置项

当然了,我们最好还是去Vite源码中看看这个defineConfig 函数是什么样的。

image-20220510163917940

可以看到,其实就是一个接收UserConfigExport类型参数的函数,然后返回这个类型的参数,所以我们改造一下:

import { UserConfigExport } from "vite";

// vite.config.ts
export default (): UserConfigExport => {
  // 导出配置对象之前可以进行一些处理
  return {
    // 配置项
  };
};

注意点:

  • 观察上面最基础的配置文件,我们是暴露出一个object类型
  • 上图中我们可以了解到,配置项的内容都来自于UserConfigExport类型,它抛出object类型的配置数据(UserConfig),或者一个函数UserConfigFn(实际上仍然抛出UserConfig
  • 一般来说,我们针对不同的环境(env)会有不同的配置项,需要用到UserConfigFn,所以我们选择最终返回的数据类型符合UserConfigExport

环境配置

如果配置文件需要基于(dev/servebuild)命令或者不同的模式(开发、预发布 、生产等)来决定配置选项,则可以选择导出如下一个函数。继续关注源码:

image-20220510170217501

import { UserConfigExport, ConfigEnv } from "vite";

// vite.config.ts
export default ({ command, mode }: ConfigEnv): UserConfigExport => {
  // 导出配置对象之前可以进行一些处理
  return {
    // 配置项
  };
};

注:在Vite的 API 中,在开发环境下command的值为serve(在 CLI 中,vite devvite servevite的别名),而在生产环境下为buildvite build

环境变量

Vite默认是不加载.env文件的,因为这些文件需要在执行完Vite配置后才能确定加载哪一个。所以当你的配置项中需要用到.env文件中的环境变量时,可以使用Vite导出的 loadEnv 函数来加载指定的.env文件。

import { UserConfigExport, ConfigEnv, loadEnv } from "vite";

// 根据当前工作目录中的 `mode` 加载 .env 文件
const root: string = process.cwd();

export default ({ command, mode }: ConfigEnv): UserConfigExport => {
  const env = loadEnv(mode, root);
  return {
    // 配置项
  };
};

Bug:这样我们拿到的环境变量(env),全部都是string类型,这样对吗?

image-20220510172625783

比如,我们看看.env.development文件中的环境变量

image-20220510172503361

我们的VITE_PORTnumber类型!!!实际上还有可能是boolean等其他类型,所以我们需要做类型转换,封装一个Vite环境变量类型转换函数。

build 文件夹

通常我们会在项目根目录下建立一个build文件夹,主要作用是帮助我们进行项目的配置与构建。

在这里我们先在build下建立一个工具函数文件utils.ts,写入我们封装的转换函数:

// Read all environment variable configuration files to process.env
export function wrapperEnv(envConf: Recordable): ViteEnv {
  // 这里的默认值的key根据ViteEnv类型来设定
  const ret: ViteEnv = {
    VITE_PORT: 7755,
    VITE_PUBLIC_PATH: "",
    VITE_PROXY_DOMAIN: "",
    VITE_PROXY_DOMAIN_REAL: "",
    VITE_ROUTER_HISTORY: "",
    VITE_LEGACY: false,
  };
  // 当然你也可以简单点这么做
  // const ret: any = {};
  for (const envName of Object.keys(envConf)) {
    let realName = envConf[envName].replace(/\\n/g, "\n");
    realName =
      realName === "true" ? true : realName === "false" ? false : realName;

    if (envName === "VITE_PORT") {
      realName = Number(realName);
    }
    ret[envName] = realName;
    if (typeof realName === "string") {
      process.env[envName] = realName;
    } else if (typeof realName === "object") {
      process.env[envName] = JSON.stringify(realName);
    }
  }
  return ret;
}

**!!!!!!**写到这里,出现了一个 Bug(当时也卡了我很久)。上面这个文件里 TS 会提示 RecordableViteEnv类型不存在。

为什么呢,我不是已经在global.d.ts中全局声明这两个类型了吗(当时我也查阅了几个参考项目,发现这一部分配置都是一样的,没有问题)?

先聚焦这个与 Bug 可能相关的几个文件

  • 罪魁祸首 build/utils.ts
  • 类型声明文件 global.d.ts
  • ts 配置文件 tsconfig.json
  • 需要引入 utils.ts 中方法的文件 vite.config.ts

试探现象:

  • 操作 1:注意这个时候,我们是没有在vite.config.ts中引入utils.ts中的wrapperEnv方法的。引入一下,你会发现,报错消失了。
  • 操作 2:这两个类型我们确实已经全局声明了,但是 TS 还是找不到。别忘了我们还有个 ts 配置文件,里面有个include选项,只有里面包含的文件才能检索到我们自定义的 TS 类型。注意到里面已经includevite.config.ts,结合操作 1。是不是相当于utils.ts中的那个方法模块也被include了?所以,我们直接把build文件include一下,发现,报错消失!!!

image-20220511114038275

所以你可以选择把build文件也include进来(省事),否则请记得及时将对应模块引入进include里的文件。

继续配置我们的环境变量

import { wrapperEnv } from "./build/utils";
import { UserConfigExport, ConfigEnv, loadEnv } from "vite";

// 当前执行node命令时的项目根目录
const root: string = process.cwd();

export default ({ command, mode }: ConfigEnv): UserConfigExport => {
  // 根据当前工作目录中的 `mode` 加载 .env 文件
  const env = loadEnv(mode, root);
  // env中所有值都是string类型,处理需要兼容其他类型的情况。并提取需要的环境变量
  const {
    VITE_PORT,
    VITE_LEGACY,
    VITE_PUBLIC_PATH,
    VITE_PROXY_DOMAIN,
    VITE_PROXY_DOMAIN_REAL,
  } = wrapperEnv(env);
  return {
    // 配置项
  };
};

配置选项

在官网我们可以看到,配置选项包含了共享配置、开发服务器选项、构建选项、预览选项、依赖优化选项、SSR 选项和 Worker 选项。

这里列出部分项目中使用到的选项,大家可以根据自己的需要进行配置。

base

开发或生产环境服务的公共基础路径,从.env文件中获取,本项目中为环境变量VITE_PUBLIC_PATH(string 类型)。

root

项目根目录,默认为node执行时的process.cwd()

const root: string = process.cwd();

resolve

一般我们使用resolve.alias,来配置我们文件系统路径的别名。

import { resolve } from "path"; // 引入resolve路径解析方法

// 路径查找
const pathResolve = (dir: string): string => {
  return resolve(__dirname, ".", dir);
};
// 设置别名
const alias: Record<string, string> = {
  "/@": pathResolve("src"),
  "@build": pathResolve("build"),
};

export default ({ command, mode }: ConfigEnv): UserConfigExport => {
  // ...
  return {
    // ...
    resolve: {
      alias,
    },
  };
};

注:使用别名一定要用绝对路径,否则可能无法被正常解析。

server

开发服务器选项,一般来说我们会配置如下四种选项

https:是否开启 https

https: false

port指定开发服务器端口

注:如果端口已经被使用,Vite会自动尝试下一个可用的端口,所以这可能不是开发服务器最终监听的实际端口

port: VITE_PORT

host指定服务器监听的 IP 地址

注:设置为 0.0.0.0 或者 true 将监听所有地址

host: "0.0.0.0"

proxy自定义跨域代理

参考规则

本地开发环境通过代理实现跨域,生产环境由nginx转发或者后台开启cors进行处理。同样的,我们新建一个专门配置跨域的文件build/vite/proxy.ts,封装创建跨域的方法。

export function createProxy(prefix, target) {
  return target.length > 0
    ? {
        [prefix]: {
          target: target, // 用Url模块解析的Url字符串
          // ws: true, //  如果你想代理websockets
          changeOrigin: true, // 将主机头的来源更改为目标URL
          rewrite: (path: string) => regExps(path, prefix),
        },
      }
    : null;
}

// 处理值为RegExp的情况
const regExps = (value: string, reg: string): string => {
  return value.replace(new RegExp(reg, "g"), "");
};

vite.config.ts中配置,VITE_PROXY_DOMAIN为开发环境代理,VITE_PROXY_DOMAIN_REAL为代理的后端地址

server: {
  // ...
  proxy: createProxy(VITE_PROXY_DOMAIN, VITE_PROXY_DOMAIN_REAL);
}

plugins

关于插件配置,可阅读官网

plugins选项接收一个数组,平时我们会使用到非常多的插件,都放在这里会显得很臃肿且不利于维护,所以建议创建专门的文件(build/vite/plugins)进行管理,在plugins文件夹下新建插件的.ts文件,及总出口index.ts文件。

这里以@vitejs/plugin-legacy处理老版本浏览器兼容问的的插件举例。

image-20220511154844005

legacy.ts

import legacy from "@vitejs/plugin-legacy";

export function configLegacyPlugin(VITE_LEGACY) {
  return VITE_LEGACY
    ? legacy({
        targets: ["ie >= 11"],
        additionalLegacyPolyfills: ["regenerator-runtime/runtime"],
      })
    : null;
}

index.ts

import { PluginOption } from "vite";
import vue from "@vitejs/plugin-vue";

// 引入其他需要做额外配置的插件
import { configLegacyPlugin } from "./legacy";

export function createVitePlugins(VITE_LEGACY) {
  const vitePlugins: (PluginOption | PluginOption[])[] = [
    // 直接引入的配置
    vue(),
  ];
  // 需要做额外配置的插件
  vitePlugins.push(configLegacyPlugin(VITE_LEGACY));

  return vitePlugins;
}

最后引入

image-20220511155147525

build

构建这一块的内容就比较多了,可以根据官网进行配置。这里给出本项目的配置示例:

    build: {
      sourcemap: false, // 根据需要看是否创建源地图文件,false构建更快哦
      brotliSize: false, // 不构建brotli压缩大小报告,可提升构建速度
      chunkSizeWarningLimit: 4000 // 消除打包大小超过500kb(默认值)警告
    },

optimizeDeps

依赖优化选项。有时候某些依赖项不需要进行预构建,我们做强制排除操作。不过通常我们使用这个选项下的include项,帮助我们强制预构建不在node_modules中的包。

这里提一个问题:Vite首次打开界面,界面加载很慢。不是说好的Vite速度很快吗,怎么还慢起来了?

Vite 的快

首先我们来说Vite的快,快在哪里——项目的启动(注意这里不是指我们的页面首页加载完毕)。

启动完毕也就是下面这种状态:

image-20220516115213609

为什么快呢,因为Vite启动时并不会像Webpack一样对所有代码进行编译、打包、压缩,许多工作都在打开页面后交给浏览器来处理了。

Vite 的慢

打开网页时,如果你项目的依赖比较多,就会发现第一次加载是真滴慢呐 ~ 因为这个时候,Vite需要动态解析依赖、动态打包、动态引入,浏览器需要加载当前页面用到的所有文件, 而第一次加载是没有缓存的。

所以影响这里的主要点就是(Vite已在进行相关开发):依赖与构建浏览器请求过多

我们可以做什么来为Vite提速呢?

  • 代码分割,按需加载
  • 使用 http2
  • optimizeDeps

配置 optimizeDeps 可以让Vite在启动的时候就对某些资源进行预打包,这样就能避免后续的动态打包等行为。

举例:

    optimizeDeps: {
      include: ["pinia", "vue-i18n", "lodash-es", "@vueuse/core"],
      exclude: []
    },

define

定义全局常量替换方式。其中每项在开发环境下会被定义在全局,而在构建时被静态替换。

注:它不经过任何语法分析,直接替换文本。所以建议只对CONSTANTS使用

这里我们需要做一个配置,以解决生产环境打包会报的错误(INTLIFY_PROD_DEVTOOLS is not defined

define: {
  __INTLIFY_PROD_DEVTOOLS__: false
}

最后看一下全部 vite.config.ts 代码

import { resolve } from "path";
import { wrapperEnv } from "./build/utils";
import { UserConfigExport, ConfigEnv, loadEnv } from "vite";
import { createProxy } from "./build/vite/proxy";
import { createVitePlugins } from "./build/vite/plugins";

// 当前执行node命令时文件夹的地址(工作目录)
const root: string = process.cwd();

// 路径查找
const pathResolve = (dir: string): string => {
  return resolve(__dirname, ".", dir);
};
// 设置别名
const alias: Record<string, string> = {
  "/@": pathResolve("src"),
  "@build": pathResolve("build"),
};

export default ({ mode }: ConfigEnv): UserConfigExport => {
  // 根据当前工作目录中的 `mode` 加载 .env 文件
  const env = loadEnv(mode, root);
  // env中所有值都是string类型,处理需要兼容其他类型的情况。并提取需要的环境变量
  const {
    VITE_PORT,
    VITE_LEGACY,
    VITE_PUBLIC_PATH,
    VITE_PROXY_DOMAIN,
    VITE_PROXY_DOMAIN_REAL,
  } = wrapperEnv(env);
  return {
    base: VITE_PUBLIC_PATH,
    root,
    resolve: {
      alias,
    },
    server: {
      https: false,
      port: VITE_PORT,
      host: "0.0.0.0",
      proxy: createProxy(VITE_PROXY_DOMAIN, VITE_PROXY_DOMAIN_REAL),
    },
    plugins: createVitePlugins(VITE_LEGACY),
    optimizeDeps: {
      include: [],
      exclude: [],
    },
    build: {
      sourcemap: false,
      brotliSize: false,
      chunkSizeWarningLimit: 4000,
    },
    define: {
      __INTLIFY_PROD_DEVTOOLS__: false,
    },
  };
};

好啦,关于Vite的基本配置就到这里啦,后面还有更多,继续加油!!!