@@ -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": { | |||
"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", | |||
@@ -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, | |||
"baseUrl": "./", | |||
"paths": { | |||
"@/*": ["./src/*"] | |||
"@/*": ["./src/*"], | |||
"/#/*": ["types/*"] | |||
} | |||
}, | |||
"include": ["src"], | |||
"include": [ | |||
"src", | |||
"types/**/*.d.ts", | |||
"types/**/*.ts", | |||
"build/**/*.ts", | |||
"build/**/*.d.ts" | |||
], | |||
"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 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, | |||
} | |||
} | |||
}) | |||
} |