一文解决你在vite中使用环境变量可能会有的疑惑!
本篇文章总结了一下我个人在项目中使用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]
中的环境变量的,但是我们直接使用的话,会是下面这样的
可以看到,我们自定义的环境变量并没有代码提示,根据官方文档,只要在
.d.ts
文件中定义一个ImportMetaEnv
的接口即可,官网的例子是env.d.ts
,但其实只要是任何包括在ts.config.json
的typeRoots
中的类型声明文件中定义了该接口都是可以的
比如我的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.
看看是否会有代码提示
可以看到确实有了
2. 将vite环境变量转换成正确类型
现在我们的.env
文件中有如下环境变量
vite
可以通过import.meta.env
获取到以VITE
为前缀的环境变量,那么我们在main.ts
中获取并输出一下环境变量
乍一看好像没问题对吧,但是仔细看一下,我们自定义的环境变量中,貌似输出的全都是字符串类型
// src/main.ts
for (const [key, value] of Object.entries(import.meta.env)) {
console.log(`${key}: ${value} -- typeof ${key} === ${typeof value}`);
}
可以看到,除了
vite
内置的环境变量的类型是正确的以外,我们自定义的环境变量全部被视为string
类型了,但实际上我更希望的是VITE_PORT
是number
类型,VITE_MOCK_ENABLED
是boolean
类型,这样使用的时候就可以直接用了,不然每次用都需要显式转换一下类型太麻烦了
于是我们可以写一个工具函数,就叫它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_PORT
的key
就将它的value
转成number
类型,遇到'true'/'false'
字符串,就将它们转成boolean
类型的值,其它的值则不需要进行变动
env
参数的类型是我自己扩展的一个Recordable
类型,也定义在global.d.ts
的文件中,先看看它的定义
type Recordable<T = any> = Record<string, T>;
它用于表示对象的key
全是string
类型,而value
是传入的泛型类型,默认是any
,这样就排除了key
是Symbol
的情况,因为import.meta.env
中其实限制了key
就是string
类型,在源码中可以看到
要想能够使用
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
中是支持传入两种类型的参数的:
UserConfig
就是脚手架默认生成的以对象的方式导出UserConfigFn
是以函数的方式导出配置
UserConfigFn
的签名如下
export declare type UserConfigFn = (env: ConfigEnv) => UserConfig | Promise<UserConfig>;
ConfigEnv
是参数类型
export declare interface ConfigEnv {
command: 'build' | 'serve';
mode: string;
}
因此我们可以换一种方式使用vite.config.ts
的defineConfig
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
这一原因官方文档中有说明
那么如果我们就是希望在这里用
.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
得到的env
和import.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