@@ -0,0 +1,8 @@ | |||||
# port | |||||
VITE_PORT = 3100 | |||||
# spa-title | |||||
VITE_GLOB_APP_TITLE = Vogocm Admin | |||||
# spa shortname | |||||
VITE_GLOB_APP_SHORT_NAME = Vogocm |
@@ -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= |
@@ -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 |
@@ -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'; |
@@ -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, ''); | |||||
}; |
@@ -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 }); | |||||
} |
@@ -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(); |
@@ -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); | |||||
} |
@@ -6,7 +6,7 @@ | |||||
"scripts": { | "scripts": { | ||||
"start": "vite", | "start": "vite", | ||||
"dev": "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", | "lint": "eslint . --ext ts,tsx --report-unused-disable-directives --max-warnings 0", | ||||
"preview": "vite preview" | "preview": "vite preview" | ||||
}, | }, | ||||
@@ -37,6 +37,7 @@ | |||||
"zustand": "^4.3.8" | "zustand": "^4.3.8" | ||||
}, | }, | ||||
"devDependencies": { | "devDependencies": { | ||||
"@types/fs-extra": "^11.0.1", | |||||
"@types/node-rsa": "^1.1.1", | "@types/node-rsa": "^1.1.1", | ||||
"@types/nprogress": "^0.2.0", | "@types/nprogress": "^0.2.0", | ||||
"@types/react": "^18.2.15", | "@types/react": "^18.2.15", | ||||
@@ -45,10 +46,13 @@ | |||||
"@typescript-eslint/parser": "^6.0.0", | "@typescript-eslint/parser": "^6.0.0", | ||||
"@vitejs/plugin-react": "^4.0.3", | "@vitejs/plugin-react": "^4.0.3", | ||||
"autoprefixer": "^10.4.14", | "autoprefixer": "^10.4.14", | ||||
"dotenv": "^16.3.1", | |||||
"eslint": "^8.45.0", | "eslint": "^8.45.0", | ||||
"eslint-plugin-react-hooks": "^4.6.0", | "eslint-plugin-react-hooks": "^4.6.0", | ||||
"eslint-plugin-react-refresh": "^0.4.3", | "eslint-plugin-react-refresh": "^0.4.3", | ||||
"fs-extra": "^11.1.1", | |||||
"lint-staged": "^13.2.2", | "lint-staged": "^13.2.2", | ||||
"picocolors": "^1.0.0", | |||||
"postcss": "^8.4.23", | "postcss": "^8.4.23", | ||||
"typescript": "^5.2", | "typescript": "^5.2", | ||||
"vite": "^4.4.5", | "vite": "^4.4.5", | ||||
@@ -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; | |||||
} |
@@ -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}`); | |||||
} |
@@ -21,9 +21,16 @@ | |||||
"noFallthroughCasesInSwitch": true, | "noFallthroughCasesInSwitch": true, | ||||
"baseUrl": "./", | "baseUrl": "./", | ||||
"paths": { | "paths": { | ||||
"@/*": ["./src/*"] | |||||
"@/*": ["./src/*"], | |||||
"/#/*": ["types/*"] | |||||
} | } | ||||
}, | }, | ||||
"include": ["src"], | |||||
"include": [ | |||||
"src", | |||||
"types/**/*.d.ts", | |||||
"types/**/*.ts", | |||||
"build/**/*.ts", | |||||
"build/**/*.d.ts" | |||||
], | |||||
"references": [{ "path": "./tsconfig.node.json" }] | "references": [{ "path": "./tsconfig.node.json" }] | ||||
} | } |
@@ -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; | |||||
} |
@@ -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; | |||||
} |
@@ -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 react from '@vitejs/plugin-react'; | ||||
import WindiCSS from 'vite-plugin-windicss'; | import WindiCSS from 'vite-plugin-windicss'; | ||||
// https://vitejs.dev/config/ | // 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, | |||||
} | |||||
} | } | ||||
}) | |||||
} |