likes
comments
collection
share

一文解决你在vite中使用环境变量可能会有的疑惑!

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

本篇文章总结了一下我个人在项目中使用vite环境变量时候遇到的坑,并给出了相应的解决方案,纯纯的干货!

1. 为环境变量添加代码提示

假设在.env文件中有如下环境变量

VITE_PORT = 8086
VITE_API_BASE_URL = /api
VITE_MOCK_URL = /mock/api
VITE_MOCK_ENABLED = false

大家都知道,vite是可以通过import.meta.env使用定义在.env.env.[mode]中的环境变量的,但是我们直接使用的话,会是下面这样的 一文解决你在vite中使用环境变量可能会有的疑惑! 可以看到,我们自定义的环境变量并没有代码提示,根据官方文档,只要在.d.ts文件中定义一个ImportMetaEnv的接口即可,官网的例子是env.d.ts,但其实只要是任何包括在ts.config.jsontypeRoots中的类型声明文件中定义了该接口都是可以的

比如我的ts.config.json中配置如下

{
  "compilerOptions": {
    "types": ["node"],
    "typeRoots": ["./node_modules/@types", "./types"]
  },
  "include": [
    
    "types/*.d.ts"
  ]
}

然后在types目录下我有一个global.d.ts

declare global {
  type Recordable<T = any> = Record<string, T>;
  declare interface ImportMetaEnv {
    VITE_PUBLIC_PATH: string;
    VITE_PORT: number;
    VITE_API_BASE_URL: string;
    VITE_MOCK_ENABLED: boolean;
    VITE_MOCK_URL: string;
  }
}

export {};

在该文件中定义了ImportMetaEnv接口

注意:一定要加上**export {}**,即便没有任何东西要导出也依然要这样做,这样才能将**global.d.ts**视为一个模块去处理,而不是全局文件,同时**declare global**是定义了全局可用的一些东西,这意味着在项目中可以不需要**import**就能够使用到这里面定义的变量、接口等内容

现在再试试import.meta.env.看看是否会有代码提示 一文解决你在vite中使用环境变量可能会有的疑惑! 可以看到确实有了


2. 将vite环境变量转换成正确类型

现在我们的.env文件中有如下环境变量

vite可以通过import.meta.env获取到以VITE为前缀的环境变量,那么我们在main.ts中获取并输出一下环境变量 一文解决你在vite中使用环境变量可能会有的疑惑! 乍一看好像没问题对吧,但是仔细看一下,我们自定义的环境变量中,貌似输出的全都是字符串类型

// src/main.ts
for (const [key, value] of Object.entries(import.meta.env)) {
  console.log(`${key}: ${value} -- typeof ${key} === ${typeof value}`);
}

一文解决你在vite中使用环境变量可能会有的疑惑! 可以看到,除了vite内置的环境变量的类型是正确的以外,我们自定义的环境变量全部被视为string类型了,但实际上我更希望的是VITE_PORTnumber类型,VITE_MOCK_ENABLEDboolean类型,这样使用的时候就可以直接用了,不然每次用都需要显式转换一下类型太麻烦了

于是我们可以写一个工具函数,就叫它useEnv吧,在项目根目录下创建build/index.ts

/**
 * @description 将 vite 的原始环境变量转成正确的类型
 * @param env 原始的 vite 环境变量
 * @returns 转换成正确类型的 vite 环境变量
 */
export const useEnv = (env: Recordable): ImportMetaEnv => {
  const ret: any = {};

  for (const envKey of Object.keys(env)) {
    let envValue = env[envKey];

    // 转成正确的布尔类型
    envValue =
      envValue === 'true' ? true : envValue === 'false' ? false : envValue;

    // VITE_PORT 转成 number
    if (envKey === 'VITE_PORT') {
      envValue = parseInt(envValue);
    }

    ret[envKey] = envValue;
  }

  return ret;
};

思路很简单,无非就是遍历env,遇到VITE_PORTkey就将它的value转成number类型,遇到'true'/'false'字符串,就将它们转成boolean类型的值,其它的值则不需要进行变动

env参数的类型是我自己扩展的一个Recordable类型,也定义在global.d.ts的文件中,先看看它的定义


type Recordable<T = any> = Record<string, T>;

它用于表示对象的key全是string类型,而value是传入的泛型类型,默认是any,这样就排除了keySymbol的情况,因为import.meta.env中其实限制了key就是string类型,在源码中可以看到 一文解决你在vite中使用环境变量可能会有的疑惑! 要想能够使用useEnv函数,还需要在ts.config.json中的include里把它包含进来

{
  "include": [
    "build/*.ts",
  ]
}

由于build是在项目根目录下,而不是在src目录下,为了方便使用到它,最好是给它配置一个路径别名,让我们可以在项目中方便使用

ts.config.json

{
  "compilerOptions": {
    "baseUrl": ".",
    "paths": {
      "@/*": ["src/*"],
      "@build/*": ["build/*"],
      "#/*": ["types/"]
    },
  },
  "include": [
    "build/*.ts",
  ]
}

vite.config.ts中也要配置

const resolvePath = (...paths: string[]) => resolve(__dirname, ...paths);

{
  resolve: {
    alias: {
      '@': resolvePath('src'),
      '@build': resolvePath('build'),
      '#': resolvePath('types'),
    },
  }
}

个人建议:如果你使用的是vscode,并且希望在import的时候有路径提示,建议安装Path Intellisense,然后在settings.json中配置一下路径映射

{
  "path-intellisense.mappings": {
    "@": "${workspaceFolder}/src",
    "#": "${workspaceFolder}/types",
    "@build": "${workspaceFolder}/build"
  },
}

3. vite.config.ts的defineConfig的另一种使用方式

首先为了能够在vite.config.ts中运行一些代码逻辑,而不是单纯导出一个对象,我们需要修改一下默认的配置方式,从默认的对象方式变成函数配置的方式,关于这个可以在源码中看到的

defineConfig函数签名

export declare function defineConfig(config: UserConfigExport): UserConfigExport;

UserConfigExport

export declare type UserConfigExport = UserConfig | Promise<UserConfig> | UserConfigFn;

说明配置vite.config.ts的时候,在defineConfig中是支持传入两种类型的参数的:

  1. UserConfig就是脚手架默认生成的以对象的方式导出
  2. UserConfigFn是以函数的方式导出配置

UserConfigFn的签名如下

export declare type UserConfigFn = (env: ConfigEnv) => UserConfig | Promise<UserConfig>;

ConfigEnv是参数类型

export declare interface ConfigEnv {
    command: 'build' | 'serve';
    mode: string;
}

因此我们可以换一种方式使用vite.config.tsdefineConfig

export default defineConfig(({ command, mode }) => {
  // 导出配置对象之前可以进行一些处理

  return {
    resolve: {
      alias: {
        '@': resolvePath('src'),
        '#': resolvePath('types'),
      },
    },
    plugins: [vue()],
  };
});

这样做的好处就是可以在导出配置对象之前进行一些处理,比如根据运行的模式决定要使用什么样的配置项就只能是以函数的方式进行配置才能实现,因为函数的方式可以接受ConfigEnv对象,它会给我们传入运行vite命令时使用的命令模式

export default defineConfig(({ command, mode }) => {
  console.log(command, mode);
  
  return {
    // ...
  }
});

// => serve development

接下来就会立即讨论一下这样做的好处在哪里


4. vite.config.ts中使用环境变量

import.meta.env只能是在项目中使用,在vite.config.ts中是不能直接这样使用的,可以来验证一下,由于前面我们已经将defineConfig改成了函数配置的方式,现在就可以试一下在里面打印import.meta.env

export default defineConfig(({ command, mode }) => {
  console.log(import.meta.env);
});

// => undefined

这一原因官方文档中有说明 一文解决你在vite中使用环境变量可能会有的疑惑! 那么如果我们就是希望在这里用.env和指定模式下的.env.[mode]中的环境变量怎么办呢,官方文档说了,可以用loadEnv函数

export default defineConfig(({ command, mode }) => {
  const env = loadEnv(mode, root);
  console.log(env);
});

// => {
//  VITE_PORT: '8848',
//  VITE_API_BASE_URL: '/dev/api',
//  VITE_MOCK_URL: '/dev/mock/api',
//  VITE_MOCK_ENABLED: 'true'
//}

但是这样子使用得到的环境变量仍然全都是string类型,这时候就可以用上前面写的useEnv函数了,事实上loadEnv得到的envimport.meta.env是一样的,因此可以作为useEnv的参数传入,这样我们就可以得到类型正确的环境变量了!

export default defineConfig(({ command, mode }) => {
  const env = loadEnv(mode, root);
  const trueEnv = useEnv(env);
  console.log(trueEnv);
});

// => {
//  VITE_PORT: 8848,
//  VITE_API_BASE_URL: '/dev/api',
//  VITE_MOCK_URL: '/dev/mock/api',
//  VITE_MOCK_ENABLED: true
//}

然后我们就可以根据生产环境和开发环境下环境变量的不同来定制不同的配置项,这就是使用函数的方式配置vite的好处!

在项目中使用的时候,要想获得类型正确的环境变量类型也是可以用前面封装的函数useEnv的,只是传入的参数从loadEnv()得到的返回值变成了import.meta.env,再也不需要进行强制转换啦

转载自:https://juejin.cn/post/7094940781726826526
评论
请登录