Browse Source

add environment configurations

dev
powersir 1 year ago
parent
commit
4a6e64ac4a
15 changed files with 443 additions and 18 deletions
  1. +8
    -0
      .env
  2. +17
    -0
      .env.develop
  3. +35
    -0
      .env.production
  4. +6
    -0
      build/constant.ts
  5. +9
    -0
      build/getConfigFileName.ts
  6. +47
    -0
      build/script/buildConf.ts
  7. +23
    -0
      build/script/postBuild.ts
  8. +92
    -0
      build/utils.ts
  9. +5
    -1
      package.json
  10. +83
    -0
      src/utils/env.ts
  11. +9
    -0
      src/utils/log.ts
  12. +9
    -2
      tsconfig.json
  13. +26
    -0
      types/config.d.ts
  14. +43
    -0
      types/global.d.ts
  15. +31
    -15
      vite.config.ts

+ 8
- 0
.env View File

@@ -0,0 +1,8 @@
# port
VITE_PORT = 3100

# spa-title
VITE_GLOB_APP_TITLE = Vogocm Admin

# spa shortname
VITE_GLOB_APP_SHORT_NAME = Vogocm

+ 17
- 0
.env.develop View File

@@ -0,0 +1,17 @@
# Whether to open mock
VITE_USE_MOCK = false

# public path
VITE_PUBLIC_PATH = /

# Delete console
VITE_DROP_CONSOLE = false

# Basic interface address SPA
VITE_GLOB_API_URL = https://test.vogocm.com:9697

# File upload address, optional
VITE_GLOB_UPLOAD_URL=/upload

# Interface prefix
VITE_GLOB_API_URL_PREFIX=

+ 35
- 0
.env.production View File

@@ -0,0 +1,35 @@
# Whether to open mock
VITE_USE_MOCK = false

# public path
VITE_PUBLIC_PATH = /

# Delete console
VITE_DROP_CONSOLE = true

# Whether to enable gzip or brotli compression
# Optional: gzip | brotli | none
# If you need multiple forms, you can use `,` to separate
VITE_BUILD_COMPRESS = 'none'

# Whether to delete origin files when using compress, default false
VITE_BUILD_COMPRESS_DELETE_ORIGIN_FILE = false

# Basic interface address SPA
VITE_GLOB_API_URL=

# File upload address, optional
# It can be forwarded by nginx or write the actual address directly
VITE_GLOB_UPLOAD_URL=/upload

# Interface prefix
VITE_GLOB_API_URL_PREFIX=

# Whether to enable image compression
VITE_USE_IMAGEMIN= true

# use pwa
VITE_USE_PWA = false

# Is it compatible with older browsers
VITE_LEGACY = false

+ 6
- 0
build/constant.ts View File

@@ -0,0 +1,6 @@
/**
* The name of the configuration file entered in the production environment
*/
export const GLOB_CONFIG_FILE_NAME = '_app.config.js';

export const OUTPUT_DIR = 'dist';

+ 9
- 0
build/getConfigFileName.ts View File

@@ -0,0 +1,9 @@
/**
* Get the configuration file variable name
* @param env
*/
export const getConfigFileName = (env: Record<string, any>) => {
return `__PRODUCTION__${env.VITE_GLOB_APP_SHORT_NAME || '__APP'}__CONF__`
.toUpperCase()
.replace(/\s/g, '');
};

+ 47
- 0
build/script/buildConf.ts View File

@@ -0,0 +1,47 @@
/**
* Generate additional configuration files when used for packaging. The file can be configured with some global variables, so that it can be changed directly externally without repackaging
*/
import { GLOB_CONFIG_FILE_NAME, OUTPUT_DIR } from '../constant';
import fs, { writeFileSync } from 'fs-extra';
import colors from 'picocolors';

import { getEnvConfig, getRootPath } from '../utils';
import { getConfigFileName } from '../getConfigFileName';

import pkg from '../../package.json';

interface CreateConfigParams {
configName: string;
config: any;
configFileName?: string;
}

function createConfig(params: CreateConfigParams) {
const { configName, config, configFileName } = params;
try {
const windowConf = `window.${configName}`;
// Ensure that the variable will not be modified
let configStr = `${windowConf}=${JSON.stringify(config)};`;
configStr += `
Object.freeze(${windowConf});
Object.defineProperty(window, "${configName}", {
configurable: false,
writable: false,
});
`.replace(/\s/g, '');

fs.mkdirp(getRootPath(OUTPUT_DIR));
writeFileSync(getRootPath(`${OUTPUT_DIR}/${configFileName}`), configStr);

console.log(colors.cyan(`✨ [${pkg.name}]`) + ` - configuration file is build successfully:`);
console.log(colors.gray(OUTPUT_DIR + '/' + colors.green(configFileName)) + '\n');
} catch (error) {
console.log(colors.red('configuration file configuration file failed to package:\n' + error));
}
}

export function runBuildConfig() {
const config = getEnvConfig();
const configFileName = getConfigFileName(config);
createConfig({ config, configName: configFileName, configFileName: GLOB_CONFIG_FILE_NAME });
}

+ 23
- 0
build/script/postBuild.ts View File

@@ -0,0 +1,23 @@
// #!/usr/bin/env node

import { runBuildConfig } from './buildConf';
import colors from 'picocolors';

import pkg from '../../package.json';

export const runBuild = async () => {
try {
const argvList = process.argv.splice(2);

// Generate configuration file
if (!argvList.includes('disabled-config')) {
runBuildConfig();
}

console.log(`✨ ${colors.cyan(`[${pkg.name}]`)}` + ' - build successfully!');
} catch (error) {
console.log(colors.red('vite build error:\n' + error));
process.exit(1);
}
};
runBuild();

+ 92
- 0
build/utils.ts View File

@@ -0,0 +1,92 @@
import fs from 'fs';
import path from 'path';
import dotenv from 'dotenv';

export function isDevFn(mode: string): boolean {
return mode === 'development';
}

export function isProdFn(mode: string): boolean {
return mode === 'production';
}

/**
* Whether to generate package preview
*/
export function isReportMode(): boolean {
return process.env.REPORT === 'true';
}

// Read all environment variable configuration files to process.env
export function wrapperEnv(envConf: Recordable): ViteEnv {
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);
}
if (envName === 'VITE_PROXY' && realName) {
try {
realName = JSON.parse(realName.replace(/'/g, '"'));
} catch (error) {
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;
}

/**
* 获取当前环境下生效的配置文件名
*/
function getConfFiles() {
const script = process.env.npm_lifecycle_script;
const reg = new RegExp('--mode ([a-z_\\d]+)');
const result = reg.exec(script as string) as any;
if (result) {
const mode = result[1] as string;
return ['.env', `.env.${mode}`];
}
return ['.env', '.env.production'];
}

/**
* Get the environment variables starting with the specified prefix
* @param match prefix
* @param confFiles ext
*/
export function getEnvConfig(match = 'VITE_GLOB_', confFiles = getConfFiles()) {
let envConfig = {};
confFiles.forEach((item) => {
try {
const env = dotenv.parse(fs.readFileSync(path.resolve(process.cwd(), item)));
envConfig = { ...envConfig, ...env };
} catch (e) {
console.error(`Error in parsing ${item}`, e);
}
});
const reg = new RegExp(`^(${match})`);
Object.keys(envConfig).forEach((key) => {
if (!reg.test(key)) {
Reflect.deleteProperty(envConfig, key);
}
});
return envConfig;
}

/**
* Get user root directory
* @param dir file path
*/
export function getRootPath(...dir: string[]) {
return path.resolve(process.cwd(), ...dir);
}

+ 5
- 1
package.json View File

@@ -6,7 +6,7 @@
"scripts": {
"start": "vite",
"dev": "vite",
"build": "tsc && vite build",
"build": "cross-env NODE_ENV=production vite build && esno ./build/script/postBuild.ts",
"lint": "eslint . --ext ts,tsx --report-unused-disable-directives --max-warnings 0",
"preview": "vite preview"
},
@@ -37,6 +37,7 @@
"zustand": "^4.3.8"
},
"devDependencies": {
"@types/fs-extra": "^11.0.1",
"@types/node-rsa": "^1.1.1",
"@types/nprogress": "^0.2.0",
"@types/react": "^18.2.15",
@@ -45,10 +46,13 @@
"@typescript-eslint/parser": "^6.0.0",
"@vitejs/plugin-react": "^4.0.3",
"autoprefixer": "^10.4.14",
"dotenv": "^16.3.1",
"eslint": "^8.45.0",
"eslint-plugin-react-hooks": "^4.6.0",
"eslint-plugin-react-refresh": "^0.4.3",
"fs-extra": "^11.1.1",
"lint-staged": "^13.2.2",
"picocolors": "^1.0.0",
"postcss": "^8.4.23",
"typescript": "^5.2",
"vite": "^4.4.5",


+ 83
- 0
src/utils/env.ts View File

@@ -0,0 +1,83 @@
import type { GlobEnvConfig } from '/#/config';

import { warn } from '@/utils/log';
import pkg from '../../package.json';
import { getConfigFileName } from '../../build/getConfigFileName';

export function getCommonStoragePrefix() {
const { VITE_GLOB_APP_SHORT_NAME } = getAppEnvConfig();
return `${VITE_GLOB_APP_SHORT_NAME}__${getEnv()}`.toUpperCase();
}

// Generate cache key according to version
export function getStorageShortName() {
return `${getCommonStoragePrefix()}${`__${pkg.version}`}__`.toUpperCase();
}

export function getAppEnvConfig() {
const ENV_NAME = getConfigFileName(import.meta.env);

const ENV = (import.meta.env.DEV
? // Get the global configuration (the configuration will be extracted independently when packaging)
(import.meta.env as unknown as GlobEnvConfig)
: window[ENV_NAME as any]) as unknown as GlobEnvConfig;

const {
VITE_GLOB_APP_TITLE,
VITE_GLOB_API_URL,
VITE_GLOB_APP_SHORT_NAME,
VITE_GLOB_API_URL_PREFIX,
VITE_GLOB_UPLOAD_URL,
} = ENV;

if (!/^[a-zA-Z\_]*$/.test(VITE_GLOB_APP_SHORT_NAME)) {
warn(
`VITE_GLOB_APP_SHORT_NAME Variables can only be characters/underscores, please modify in the environment variables and re-running.`,
);
}

return {
VITE_GLOB_APP_TITLE,
VITE_GLOB_API_URL,
VITE_GLOB_APP_SHORT_NAME,
VITE_GLOB_API_URL_PREFIX,
VITE_GLOB_UPLOAD_URL,
};
}

/**
* @description: Development mode
*/
export const devMode = 'development';

/**
* @description: Production mode
*/
export const prodMode = 'production';

/**
* @description: Get environment variables
* @returns:
* @example:
*/
export function getEnv(): string {
return import.meta.env.MODE;
}

/**
* @description: Is it a development mode
* @returns:
* @example:
*/
export function isDevMode(): boolean {
return import.meta.env.DEV;
}

/**
* @description: Is it a production mode
* @returns:
* @example:
*/
export function isProdMode(): boolean {
return import.meta.env.PROD;
}

+ 9
- 0
src/utils/log.ts View File

@@ -0,0 +1,9 @@
const projectName = import.meta.env.VITE_GLOB_APP_TITLE;

export function warn(message: string) {
console.warn(`[${projectName} warn]:${message}`);
}

export function error(message: string) {
throw new Error(`[${projectName} error]:${message}`);
}

+ 9
- 2
tsconfig.json View File

@@ -21,9 +21,16 @@
"noFallthroughCasesInSwitch": true,
"baseUrl": "./",
"paths": {
"@/*": ["./src/*"]
"@/*": ["./src/*"],
"/#/*": ["types/*"]
}
},
"include": ["src"],
"include": [
"src",
"types/**/*.d.ts",
"types/**/*.ts",
"build/**/*.ts",
"build/**/*.d.ts"
],
"references": [{ "path": "./tsconfig.node.json" }]
}

+ 26
- 0
types/config.d.ts View File

@@ -0,0 +1,26 @@

export interface GlobConfig {
// Site title
title: string;
// Service interface url
apiUrl: string;
// Upload url
uploadUrl?: string;
// Service interface url prefix
urlPrefix?: string;
// Project abbreviation
shortName: string;
}

export interface GlobEnvConfig {
// Site title
VITE_GLOB_APP_TITLE: string;
// Service interface url
VITE_GLOB_API_URL: string;
// Service interface url prefix
VITE_GLOB_API_URL_PREFIX?: string;
// Project abbreviation
VITE_GLOB_APP_SHORT_NAME: string;
// Upload url
VITE_GLOB_UPLOAD_URL?: string;
}

+ 43
- 0
types/global.d.ts View File

@@ -0,0 +1,43 @@
export type Writable<T> = {
-readonly [P in keyof T]: T[P];
};

declare type Nullable<T> = T | null;
declare type NonNullable<T> = T extends null | undefined ? never : T;
export declare type Recordable<T = any> = Record<string, T>;
declare type ReadonlyRecordable<T = any> = {
readonly [key: string]: T;
};
declare type Indexable<T = any> = {
[key: string]: T;
};
declare type DeepPartial<T> = {
[P in keyof T]?: DeepPartial<T[P]>;
};
declare type TimeoutHandle = ReturnType<typeof setTimeout>;
declare type IntervalHandle = ReturnType<typeof setInterval>;

declare interface ChangeEvent extends Event {
target: HTMLInputElement;
}

interface ImportMetaEnv extends ViteEnv {
__: unknown;
}

export declare interface ViteEnv {
VITE_PORT: number;
VITE_USE_MOCK: boolean;
VITE_USE_PWA: boolean;
VITE_PUBLIC_PATH: string;
VITE_PROXY: [string, string][];
VITE_GLOB_APP_TITLE: string;
VITE_GLOB_APP_SHORT_NAME: string;
VITE_USE_CDN: boolean;
VITE_DROP_CONSOLE: boolean;
VITE_BUILD_COMPRESS: 'gzip' | 'brotli' | 'none';
VITE_BUILD_COMPRESS_DELETE_ORIGIN_FILE: boolean;
VITE_LEGACY: boolean;
VITE_USE_IMAGEMIN: boolean;
VITE_GENERATE_UI: string;
}

+ 31
- 15
vite.config.ts View File

@@ -1,21 +1,37 @@
import {defineConfig} from 'vite';
import type { UserConfig, ConfigEnv } from 'vite';
import { loadEnv } from 'vite';
import { wrapperEnv } from './build/utils';
import react from '@vitejs/plugin-react';
import WindiCSS from 'vite-plugin-windicss';

// https://vitejs.dev/config/
export default defineConfig({
base: '/',
plugins: [
react(),
WindiCSS()
],
resolve: {
alias: {
'@': '/src/',
export default ({ command, mode }: ConfigEnv): UserConfig => {

const root = process.cwd();
const env = loadEnv(mode, root);
const viteEnv = wrapperEnv(env);
const { VITE_PORT } = viteEnv;

return {
base: '/',
plugins: [
react(),
WindiCSS()
],
resolve: {
alias: {
'@/': '/src/',
'/#/': '/types/'
},
},
server: {
https: true,
host: true,
port: VITE_PORT,
},
},
build: {
manifest: true,
sourcemap: true,
build: {
manifest: true,
sourcemap: true,
}
}
})
}

Loading…
Cancel
Save