多环境配置

caoguanjie2022年8月22日大约 9 分钟约 2711 字

多环境配置

vite的环境变量

  1. env配置文件

项目根目录分别添加 开发、生产和模拟环境配置

  • 开发环境配置:.env.development
# 变量必须以 VITE_ 为前缀才能暴露给外部读取
VITE_APP_TITLE = 'fits-admin'
VITE_APP_PORT = 3000
VITE_APP_BASE_API = '/api'
  • 生产环境配置:.env.production
# 变量必须以 VITE_ 为前缀才能暴露给外部读取
VITE_APP_TITLE = 'fits-admin'
VITE_APP_PORT = 3000
VITE_APP_BASE_API = '/newapi'
  1. 环境变量智能提示

添加环境变量类型声明

// src/ env.d.ts
// 环境变量类型声明
interface ImportMetaEnv {
  VITE_APP_TITLE: string,
  VITE_APP_PORT: string,
  VITE_APP_BASE_API: string
}

interface ImportMeta {
  readonly env: ImportMetaEnv
}

后面在使用自定义环境变量就会有智能提示

图 4

vite的环境配置方案挺好的,但是对于一些复杂的项目,多客户的项目,这种方案存在以下的缺点:

  1. 对于FitsAdmin常用的环境有开发环境、生产环境、106验证环境、105测试环境、QA环境、客户1环境、客户2环境......随着环境的增多,要不断增加.env文件,对项目的维护性不利
  2. 在使用场景中,我们针对不同的客户,想通过环境变量,在预编译的阶段,编译不同的界面,.env显然无法处理这么复杂的场景
  3. .env的文件无法与ts文件做交互,同样无法处理一些复杂的逻辑

综上所述,在FitsAdmin项目中,需要对多环境配置进行重新的规划,可以结合.env的优点,做多环境和复杂情况下的处理

小编以前也写过两篇文章,针对app的多环境配置的解决思路探索,有兴趣的朋友,可以点击下面的链接去了解一下本次多环境配置方案的思路来源,我这次就不多做赘叙了

新环境配置构建过程

配置文件结构

......                        
├── src                                 
│   ├── environment                     # 多环境配置文件夹
│       ├── modules                     # 存放不同的环境变量
│           ├── dev.ts                  # 开发环境的变量、配置项
│           └── prod.ts                 # 生产环境的变量、配置项
│       ├── index.ts                    # 环境配置的入口文件,多环境配置方案的关键代码
│       └── type.ts                     # 预定义的环境变量、相关配置项
......    

使用策略模式

当开发者通过vite的命令行:--mode dev 输入了环境变量的指令之后,我的逻辑判断能根据这个指令,选择出这个环境的相应动作或者策略方案,具体点说:就是能适配出dev这个开发环境变量的具体配置项的路径,

根据之前已经实现过相关功能的文档ionic6的自动化构建和多环境配置open in new window可以知道,在webpack的前端工程项目中,有require.context这个方法能实现这个策略模式,于是乎,我们只需要找到在vite环境底下是否也有相关的api能够达到相同的目的,最终找到了import.meta.globEager

什么是import.meta.globEager

相关信息

官方文档:Glob 导入open in new window

Vite 支持使用特殊的 import.meta.glob 函数从文件系统导入多个模块:

const modules = import.meta.globEager('./modules/*.ts')

以上将会被转译为下面的样子:

// vite 生成的代码
const modules = {
  './modules/dev.ts': () => import('./modules/dev.ts'),
  './modules/prod.ts': () => import('./modules/prod.ts')
}

你可以遍历 modules 对象的 key 值来访问相应的模块:

for (const path in modules) {
  modules[path]().then((mod) => {
    console.log(path, mod)
  })
}

注意

  • 这只是一个 Vite 独有的功能而不是一个 Web 或 ES 标准
  • 该 Glob 模式会被当成导入标识符:必须是相对路径(以 ./ 开头)或绝对路径(以 / 开头,相对于项目根目录解析)
  • 你还需注意,glob 的导入不接受变量,你应直接传递字符串模式。

核心设计思想

  1. 获取到文件夹src/environments/modules里面所有的文件路径,能得到以下的路径数组:
// vite 生成的代码
const modules = {
  './modules/dev.ts': () => import('./modules/dev.ts'),
  './modules/prod.ts': () => import('./modules/prod.ts')
}

  1. 通过正则运算匹配提取关键字devprod作为一个空对象里面的属性,并且通过import的关键词default得到export导出的对象,如下:
const modules = {
  'dev': () => import('./modules/dev.ts').default,
  'prod': () => import('./modules/prod.ts').default
}

// 进一步转化为:
const modules = {
  'dev': () => FitsDefaultSetting,
  'prod': () => FitsDefaultSetting
}

  1. 根据命令行--mode dev的环境变量得到当前环境dev,然后得到对应的配置,把这个配置项放入实体类FitsDefaultSetting中,会得到所有的配置项
const NODE_ENV = process.env.NODE_ENV || 'dev'
const ENV: FitsDefaultSetting = new FitsDefaultSetting(_modules[NODE_ENV])

实体类FitsDefaultSetting是什么?

  • 考虑到以后环境配置项越来越多,每个环境其实不需要把所有的配置项都进行配置的
  • 实体类FitsDefaultSetting的产生,主要解决当你只配置了项目配置的属性,其他配置项可以不填,由实体类里面的方法初始化默认值。

核心关键代码

为什么要保留.env文件

在实践过程中,我们发现import()和require()都无法在vite.config.ts文件中导入模块,也就意味着,无法动态导入src/environment/modules的环境配置

为什么会出现这种情况呢?

  1. require()和import()都不支持动态变量的字符串,例如**require(src/environment/modules/${mode}.ts)**这种写法
  2. import.meta.globEager这个函数只会在vite环境下有效,而文件vite.config.ts执行的是vite的配置项,还未到vite的构建环境。
  3. 不想因为解决vite.config.ts这个文件的问题,而在index.ts文件中写一大段nodejs的逻辑处理,通过nodejs的方法的确可以做到import.meta.globEager相同的效果。但是目前暂时没发现写这个兼容的必要性,后面可以根据实际场景可以优化该方法。

综上所述,我们通过新建.env的方式,把vite.config.ts需要的变量存放起来使用,下面是具体的使用方法。

export default defineConfig({
  // mode代表是当前的环境变量
  // env[`VITE_APP_PORT_${mode}` == env.VITE_APP_PORT_dev
    server: {
        host: 'localhost', 
        port: Number(env[`VITE_APP_PORT_${mode}`]), 
        open: true, // 启动是否自动打开浏览器
        proxy: {
            '/apis': {
                target: 'http://192.168.32.108:3000/api',
                changeOrigin: true,
                rewrite: (path) => path.replace(/^\/apis/, '')
            }
        }
    },
})

具体使用方法

在页面中,通过解构赋值的方式,获取配置项信息

<script setup lang="ts">
// 路径:src/views/login/index.vue
// API依赖
import ENV from '@/environment/index';

const { project: { title, subTitle }, login: loginSetting } = ENV
</script>

实际环境配置

// 路径:src/environment/modules/dev.ts
import { FitsSetting } from "../type"

const ENV: FitsSetting = {
    project: {
        title: 'FitsAdmin后台管理系统',
        subTitle: 'FITS ADMIN',
        company: '广东丰德科技有限公司',
        version: "1.0.0",
        api_address: 'http://192.168.32.108:3000/mock/78',
        http_timeout: 15000
    },
    system: {
        showSettings: true,
        tagsView: true,
        fixedHeader: false,
        sidebarLogo: true,
        isInsensitivity: false,
        coutDownTime: 30,
        errorLog: true
    },
    login: {
        appScanCode: true,
        smsLogin: true,
        accountLogin: true,
        appDownload: true
    }

}
export default ENV

如果不想配置系统、登录的这两项配置项,可以仅填写项目配置项,如下图所示:

// 路径:src/environment/modules/prod.ts
import { FitsSetting } from "../type"

const ENV: FitsSetting = {
    project: {
        title: 'FitsAdmin后台管理系统',
        subTitle: 'FITS ADMIN PROD',
        company: '广东丰德科技有限公司',
        version: "1.0.1",
        api_address: 'http://192.168.32.108:3000/mock/78',
        http_timeout: 15000
    }
}
export default ENV

这里留下一个优化方向

当我在开发环境配置好了整个项目的全部配置项之后,其他环境能否根据这个齐全的配置进行继承,例如我在上架环境的prod.ts文件,只需要配置项目名称即可,其他属性全部继承dev.ts的配置。

初步的思路是:

  1. FitsSetting属性里面新增一个source来源属性,通过这个属性对对两个文件的配置项进行Object.assign()合并
  2. ENV这个变量是无法存放到store里面的,所以可以考虑把所有的配置通过加密的方式存放到session里面。

配置项设计

FitsSetting属性

属性说明类型可选值默认值
project项目配置FitsProjectSetting必填-
system系统配置FitsSystemSetting非必填-
login登录配置LoginSetting非必填-

FitsProjectSetting项目配置属性

属性说明类型可选值默认值
title网站标题,项目名称string--
subTitle网站副标题, 英文标题string--
company公司名字string--
version版本号string-1.0.0
api_address项目的接口地址string--
http_timeout项目的接口超时时间,也就是接口请求超过多少秒之后,会返回超时状态number-15000,单位:毫秒

FitsSystemSetting系统配置属性

属性说明类型可选值默认值
tagsView是否要需要多页签boolean-true
fixedHeader是否要固定网页的头部boolean-true
sidebarLogo是否显示菜单栏的logoboolean-true
isInsensitivitytoken失效时是否进行无感操作boolean-true
showSettings是否配置默认设置boolean-true
errorLog是否显示错误日志boolean-true
coutDownTime倒计时的时间number-30,单位:秒
showFooterBreadcrumb是否展示底部的面包屑功能boolean-true
formType表单的展示形式,dialog弹窗形式,drawer右边弹出的抽屉形式FormTypedialog / drawerdialog

LoginSetting登录配置属性

属性说明类型可选值默认值
appScanCodeapp扫码登录boolean-true
smsLogin短信登录boolean-true
accountLogin账号登录boolean-true
appDownloadapp下载功能,二维码展示boolean-true
上次编辑于: 2023/7/5 03:00:45
贡献者: caoguanjie
Loading...