perf: 프로필 프런트엔드 자산을 커밋 단위로 공유한다
정적 운영 빌드에서 프로필별 base와 API 설정을 런타임 JSON으로 분리하고, 공용 Vite 번들과 sourcemap을 한 번만 생성·게시한다. Preview와 기존 정적 artifact의 전환 호환 경계는 유지한다.
This commit is contained in:
@@ -1,6 +1,7 @@
|
||||
import { onBeforeUnmount, onMounted } from 'vue';
|
||||
import { createDeploymentVersionChecker } from '../config/deploymentVersion';
|
||||
import { useGameFeedback } from './useGameFeedback';
|
||||
import { gameFrontendRuntimeConfig } from '../config/runtimeConfig';
|
||||
|
||||
const pollIntervalMs = 60_000;
|
||||
const noticeMessage = '새 버전이 준비되었습니다. 새로고침하면 변경사항이 반영됩니다.';
|
||||
@@ -14,8 +15,8 @@ const resolveSessionStorage = (): Pick<Storage, 'getItem' | 'setItem'> | undefin
|
||||
};
|
||||
|
||||
export const useDeploymentVersionNotice = (): void => {
|
||||
const currentCommitSha = import.meta.env.VITE_BUILD_COMMIT_SHA?.trim() ?? '';
|
||||
const versionUrl = `${import.meta.env.BASE_URL}deployment-version.json`;
|
||||
const currentCommitSha = gameFrontendRuntimeConfig.buildCommitSha;
|
||||
const versionUrl = `${gameFrontendRuntimeConfig.appBasePath}deployment-version.json`;
|
||||
const { info: showInfoToast } = useGameFeedback();
|
||||
const checker = createDeploymentVersionChecker({
|
||||
currentCommitSha,
|
||||
|
||||
@@ -0,0 +1,73 @@
|
||||
export const GAME_FRONTEND_RUNTIME_CONFIG_ID = 'sammo-runtime-config';
|
||||
|
||||
export interface GameFrontendRuntimeConfig {
|
||||
version: 1;
|
||||
profile?: string;
|
||||
profileName?: string;
|
||||
appBasePath: string;
|
||||
gameApiUrl: string;
|
||||
gameSseUrl: string;
|
||||
gatewayApiUrl: string;
|
||||
gatewayWebUrl: string;
|
||||
buildCommitSha: string;
|
||||
assetReleaseId?: string;
|
||||
}
|
||||
|
||||
interface RuntimeConfigEnvironment {
|
||||
VITE_APP_BASE_PATH?: string;
|
||||
VITE_GAME_API_URL?: string;
|
||||
VITE_GAME_SSE_URL?: string;
|
||||
VITE_GAME_PROFILE?: string;
|
||||
VITE_GATEWAY_API_URL?: string;
|
||||
VITE_GATEWAY_WEB_URL?: string;
|
||||
VITE_BUILD_COMMIT_SHA?: string;
|
||||
}
|
||||
|
||||
const normalizeBasePath = (value: string | undefined): string => {
|
||||
const normalized = value?.trim().replace(/^\/+|\/+$/gu, '') ?? '';
|
||||
return normalized ? `/${normalized}/` : '/';
|
||||
};
|
||||
|
||||
const nonEmpty = (value: unknown): string | undefined =>
|
||||
typeof value === 'string' && value.trim() ? value.trim() : undefined;
|
||||
|
||||
const readEmbeddedConfig = (documentValue: Pick<Document, 'getElementById'> | undefined): Record<string, unknown> => {
|
||||
const source = documentValue?.getElementById(GAME_FRONTEND_RUNTIME_CONFIG_ID)?.textContent?.trim();
|
||||
if (!source) return {};
|
||||
try {
|
||||
const parsed: unknown = JSON.parse(source);
|
||||
return parsed !== null && typeof parsed === 'object' && !Array.isArray(parsed)
|
||||
? (parsed as Record<string, unknown>)
|
||||
: {};
|
||||
} catch {
|
||||
return {};
|
||||
}
|
||||
};
|
||||
|
||||
export const resolveGameFrontendRuntimeConfig = (
|
||||
documentValue: Pick<Document, 'getElementById'> | undefined,
|
||||
env: RuntimeConfigEnvironment
|
||||
): GameFrontendRuntimeConfig => {
|
||||
const embedded = readEmbeddedConfig(documentValue);
|
||||
const appBasePath = normalizeBasePath(nonEmpty(embedded.appBasePath) ?? env.VITE_APP_BASE_PATH);
|
||||
const profile = nonEmpty(embedded.profile) ?? nonEmpty(env.VITE_GAME_PROFILE);
|
||||
const profileName = nonEmpty(embedded.profileName);
|
||||
const assetReleaseId = nonEmpty(embedded.assetReleaseId);
|
||||
return Object.freeze({
|
||||
version: 1,
|
||||
...(profile ? { profile } : {}),
|
||||
...(profileName ? { profileName } : {}),
|
||||
appBasePath,
|
||||
gameApiUrl: nonEmpty(embedded.gameApiUrl) ?? nonEmpty(env.VITE_GAME_API_URL) ?? `${appBasePath}api/trpc`,
|
||||
gameSseUrl: nonEmpty(embedded.gameSseUrl) ?? nonEmpty(env.VITE_GAME_SSE_URL) ?? `${appBasePath}api/events`,
|
||||
gatewayApiUrl: nonEmpty(embedded.gatewayApiUrl) ?? nonEmpty(env.VITE_GATEWAY_API_URL) ?? '/gateway/api/trpc',
|
||||
gatewayWebUrl: nonEmpty(embedded.gatewayWebUrl) ?? nonEmpty(env.VITE_GATEWAY_WEB_URL) ?? '/gateway/',
|
||||
buildCommitSha: nonEmpty(embedded.buildCommitSha) ?? nonEmpty(env.VITE_BUILD_COMMIT_SHA) ?? 'unknown',
|
||||
...(assetReleaseId ? { assetReleaseId } : {}),
|
||||
});
|
||||
};
|
||||
|
||||
export const gameFrontendRuntimeConfig = resolveGameFrontendRuntimeConfig(
|
||||
typeof document === 'undefined' ? undefined : document,
|
||||
import.meta.env ?? {}
|
||||
);
|
||||
Vendored
+1
@@ -8,6 +8,7 @@ declare module '*.vue' {
|
||||
|
||||
interface ImportMetaEnv {
|
||||
readonly VITE_APP_BASE_PATH?: string;
|
||||
readonly VITE_ASSET_BASE_PATH?: string;
|
||||
readonly VITE_GATEWAY_API_URL?: string;
|
||||
readonly VITE_GAME_API_URL?: string;
|
||||
readonly VITE_GAME_SSE_URL?: string;
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { createRouter, createWebHistory, type RouteRecordRaw } from 'vue-router';
|
||||
import { gameFrontendRuntimeConfig } from '../config/runtimeConfig';
|
||||
import { useSessionStore } from '../stores/session';
|
||||
import { trpc } from '../utils/trpc';
|
||||
|
||||
@@ -384,7 +385,7 @@ const routes = [
|
||||
] satisfies RouteRecordRaw[];
|
||||
|
||||
const router = createRouter({
|
||||
history: createWebHistory(import.meta.env.BASE_URL),
|
||||
history: createWebHistory(gameFrontendRuntimeConfig.appBasePath),
|
||||
routes,
|
||||
});
|
||||
|
||||
|
||||
@@ -22,6 +22,7 @@ import { createBroadcastTabCoordinator, type BroadcastTabCoordinator } from '../
|
||||
import { resolveWithReadModelSnapshotFallback } from '../utils/readModelDeltaRecovery';
|
||||
import { createRealtimeRequestOptions } from '../utils/realtimeAccessGrant';
|
||||
import { markGameServerContact } from '../utils/gameServerActivity';
|
||||
import { gameFrontendRuntimeConfig } from '../config/runtimeConfig';
|
||||
|
||||
const REALTIME_FULL_REFRESH_MIN_INTERVAL_MS = 5_000;
|
||||
|
||||
@@ -1026,7 +1027,7 @@ export const useMainDashboardStore = defineStore('mainDashboard', () => {
|
||||
const isAccessToken = (token: string | null): boolean => Boolean(token?.startsWith('ga_'));
|
||||
|
||||
const buildRealtimeUrl = (token: string): string => {
|
||||
const base = import.meta.env.VITE_GAME_SSE_URL ?? '/events';
|
||||
const base = gameFrontendRuntimeConfig.gameSseUrl;
|
||||
const url = new URL(base, window.location.origin);
|
||||
url.searchParams.set('token', token);
|
||||
return url.toString();
|
||||
|
||||
@@ -2,6 +2,7 @@ import { defineStore } from 'pinia';
|
||||
import { takeGameSessionTransfer, type GameSessionTransfer } from '@sammo-ts/common/auth/gameSessionTransfer';
|
||||
import { gatewayTrpc } from '../utils/gatewayTrpc';
|
||||
import { trpc as gameTrpc } from '../utils/trpc';
|
||||
import { gameFrontendRuntimeConfig } from '../config/runtimeConfig';
|
||||
|
||||
export type SessionStatus = 'unknown' | 'public' | 'authed' | 'general';
|
||||
|
||||
@@ -222,7 +223,7 @@ export const useSessionStore = defineStore('session', {
|
||||
this.setGameToken(storedGameToken);
|
||||
}
|
||||
|
||||
const storedProfile = this.profile ?? readStorage(PROFILE_KEY) ?? import.meta.env.VITE_GAME_PROFILE;
|
||||
const storedProfile = this.profile ?? readStorage(PROFILE_KEY) ?? gameFrontendRuntimeConfig.profile;
|
||||
if (storedProfile && storedProfile !== this.profile) {
|
||||
this.setProfile(storedProfile);
|
||||
}
|
||||
|
||||
@@ -8,6 +8,7 @@ import { createRealtimeRequestOptions } from '../utils/realtimeAccessGrant';
|
||||
import { structurallyShare } from '../utils/structuralShare';
|
||||
import { trpc } from '../utils/trpc';
|
||||
import { useSessionStore } from './session';
|
||||
import { gameFrontendRuntimeConfig } from '../config/runtimeConfig';
|
||||
|
||||
type Snapshot = Awaited<ReturnType<typeof trpc.tournament.getSnapshot.query>>;
|
||||
type BettingSummary = Awaited<ReturnType<typeof trpc.tournament.getBettingSummary.query>>;
|
||||
@@ -136,7 +137,7 @@ export const useTournamentPagesStore = defineStore('tournamentPages', () => {
|
||||
return session.gameToken && isAccessToken(session.gameToken) ? session.gameToken : null;
|
||||
};
|
||||
const buildRealtimeUrl = (token: string): string => {
|
||||
const base = import.meta.env.VITE_GAME_SSE_URL ?? '/events';
|
||||
const base = gameFrontendRuntimeConfig.gameSseUrl;
|
||||
const url = new URL(base, window.location.origin);
|
||||
url.searchParams.set('token', token);
|
||||
url.searchParams.set('scope', 'tournament');
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { trpcJsonBodyHttpClientOptions } from '@sammo-ts/common/http/trpcTransport';
|
||||
import { createTRPCProxyClient, httpBatchLink } from '@trpc/client';
|
||||
import type { AppRouter } from '@sammo-ts/gateway-api';
|
||||
import { gameFrontendRuntimeConfig } from '../config/runtimeConfig';
|
||||
|
||||
const getSessionToken = (): string | null => {
|
||||
if (typeof window === 'undefined') {
|
||||
@@ -13,7 +14,7 @@ const getSessionToken = (): string | null => {
|
||||
export const gatewayTrpc = createTRPCProxyClient<AppRouter>({
|
||||
links: [
|
||||
httpBatchLink({
|
||||
url: import.meta.env.VITE_GATEWAY_API_URL ?? '/api/trpc',
|
||||
url: gameFrontendRuntimeConfig.gatewayApiUrl,
|
||||
...trpcJsonBodyHttpClientOptions,
|
||||
headers() {
|
||||
const token = getSessionToken();
|
||||
|
||||
@@ -4,6 +4,7 @@ import {
|
||||
DEFAULT_USER_ICON_PUBLIC_URL,
|
||||
externalizeLegacyImageUrl,
|
||||
} from './imageAssets.ts';
|
||||
import { gameFrontendRuntimeConfig } from '../config/runtimeConfig.ts';
|
||||
|
||||
export const DEFAULT_GENERAL_ICON_URL = `${configuredSharedIconPublicUrl()}/default.jpg`;
|
||||
export const DEFAULT_GATEWAY_USER_ICON_BASE_URL = DEFAULT_USER_ICON_PUBLIC_URL;
|
||||
@@ -68,7 +69,7 @@ export const resolveMessageGeneralIconUrl = (
|
||||
if (normalized.startsWith('/') || /^https?:\/\//iu.test(normalized)) {
|
||||
return normalized;
|
||||
}
|
||||
return `${import.meta.env.BASE_URL}${normalized.replace(/^\/+/u, '')}`;
|
||||
return `${gameFrontendRuntimeConfig.appBasePath}${normalized.replace(/^\/+/u, '')}`;
|
||||
};
|
||||
|
||||
export const useDefaultGeneralIcon = (event: Event): void => {
|
||||
|
||||
@@ -2,6 +2,7 @@ import { trpcJsonBodyHttpClientOptions } from '@sammo-ts/common/http/trpcTranspo
|
||||
import { REALTIME_ACCESS_GRANT_HEADER } from '@sammo-ts/common/realtime/types';
|
||||
import { createTRPCProxyClient, httpBatchLink } from '@trpc/client';
|
||||
import type { AppRouter } from '@sammo-ts/game-api';
|
||||
import { gameFrontendRuntimeConfig } from '../config/runtimeConfig';
|
||||
import { resolveBatchRealtimeAccessGrant } from './realtimeAccessGrant';
|
||||
import { markGameServerContact } from './gameServerActivity';
|
||||
|
||||
@@ -16,7 +17,7 @@ const getGameToken = (): string | null => {
|
||||
export const trpc = createTRPCProxyClient<AppRouter>({
|
||||
links: [
|
||||
httpBatchLink({
|
||||
url: import.meta.env.VITE_GAME_API_URL ?? '/api/trpc',
|
||||
url: gameFrontendRuntimeConfig.gameApiUrl,
|
||||
...trpcJsonBodyHttpClientOptions,
|
||||
async fetch(input, init) {
|
||||
const result = await globalThis.fetch(input, init);
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
<script setup lang="ts">
|
||||
import { onMounted } from 'vue';
|
||||
import { gameFrontendRuntimeConfig } from '../config/runtimeConfig';
|
||||
|
||||
const gatewayUrl = import.meta.env.VITE_GATEWAY_WEB_URL || '/gateway/';
|
||||
const gatewayUrl = gameFrontendRuntimeConfig.gatewayWebUrl;
|
||||
|
||||
onMounted(() => {
|
||||
window.location.replace(gatewayUrl);
|
||||
|
||||
@@ -30,6 +30,7 @@ import { useMainDashboardStore } from '../stores/mainDashboard';
|
||||
import { useGameFeedback } from '../composables/useGameFeedback';
|
||||
import { trpc } from '../utils/trpc';
|
||||
import type { CommandPatternEntry } from '../components/command/types';
|
||||
import { gameFrontendRuntimeConfig } from '../config/runtimeConfig';
|
||||
import {
|
||||
loadMobileMainPanelOrder,
|
||||
MOBILE_MAIN_PANEL_ORDER_CHANGED_EVENT,
|
||||
@@ -45,9 +46,9 @@ const isMobile = useMediaQuery('(max-width: 939.98px)');
|
||||
const npcMode = ref(0);
|
||||
const globalNavigation = ref<MainNavigationEntry[]>(defaultGlobalNavigation);
|
||||
const versionDialog = ref<HTMLDialogElement | null>(null);
|
||||
const buildCommitSha = import.meta.env.VITE_BUILD_COMMIT_SHA?.trim() || 'unknown';
|
||||
const buildCommitSha = gameFrontendRuntimeConfig.buildCommitSha;
|
||||
const mobilePanelOrder = ref(loadMobileMainPanelOrder());
|
||||
const navigationUrl = (import.meta.env.VITE_GATEWAY_API_URL ?? '/api/trpc').replace(/\/trpc\/?$/u, '/navigation');
|
||||
const navigationUrl = gameFrontendRuntimeConfig.gatewayApiUrl.replace(/\/trpc\/?$/u, '/navigation');
|
||||
|
||||
const reloadMobilePanelOrder = () => {
|
||||
mobilePanelOrder.value = loadMobileMainPanelOrder();
|
||||
@@ -188,7 +189,7 @@ const requestManualRefresh = () => {
|
||||
};
|
||||
|
||||
const moveLobby = () => {
|
||||
window.location.replace(import.meta.env.VITE_GATEWAY_WEB_URL?.trim() || '/gateway/');
|
||||
window.location.replace(gameFrontendRuntimeConfig.gatewayWebUrl);
|
||||
};
|
||||
|
||||
const moveQuick = (item: QuickNavigationItem) => {
|
||||
|
||||
@@ -9,6 +9,7 @@ import { useSessionStore } from '../stores/session';
|
||||
import { resolveGeneralIconUrl, useDefaultGeneralIcon } from '../utils/generalIcon';
|
||||
import GeneralInformationPanel from '../components/main/GeneralInformationPanel.vue';
|
||||
import { useGameFeedback } from '../composables/useGameFeedback';
|
||||
import { gameFrontendRuntimeConfig } from '../config/runtimeConfig';
|
||||
|
||||
const PENDING_DIE_ON_PRESTART_KEY = 'sam.pending.dieOnPrestart';
|
||||
const { error: showErrorToast, showDialog } = useGameFeedback();
|
||||
@@ -308,7 +309,7 @@ const dieOnPrestart = async () => {
|
||||
await trpc.general.dieOnPrestart.mutate({ clientRequestId });
|
||||
window.sessionStorage.removeItem(PENDING_DIE_ON_PRESTART_KEY);
|
||||
session.leaveGame();
|
||||
window.location.replace(import.meta.env.VITE_GATEWAY_WEB_URL?.trim() || '/gateway/');
|
||||
window.location.replace(gameFrontendRuntimeConfig.gatewayWebUrl);
|
||||
} catch (cause) {
|
||||
const code = asRecord(asRecord(cause).data).code;
|
||||
if (code !== 'TIMEOUT') {
|
||||
|
||||
@@ -0,0 +1,75 @@
|
||||
import assert from 'node:assert/strict';
|
||||
import { describe, it } from 'node:test';
|
||||
|
||||
import { resolveGameFrontendRuntimeConfig } from '../src/config/runtimeConfig.ts';
|
||||
|
||||
const documentWithConfig = (value: string | null): Pick<Document, 'getElementById'> => ({
|
||||
getElementById: () => (value === null ? null : ({ textContent: value } as HTMLElement)),
|
||||
});
|
||||
|
||||
void describe('game frontend runtime config', () => {
|
||||
void it('prefers the embedded profile wrapper config over build-time fallbacks', () => {
|
||||
const config = resolveGameFrontendRuntimeConfig(
|
||||
documentWithConfig(
|
||||
JSON.stringify({
|
||||
version: 1,
|
||||
profile: 'pya',
|
||||
profileName: 'pya:default',
|
||||
appBasePath: '/pya',
|
||||
gameApiUrl: '/pya/api/trpc',
|
||||
gameSseUrl: '/pya/api/events',
|
||||
gatewayApiUrl: '/gateway/api/trpc',
|
||||
gatewayWebUrl: '/gateway/',
|
||||
buildCommitSha: 'a'.repeat(40),
|
||||
assetReleaseId: `${'a'.repeat(40)}-${'b'.repeat(16)}`,
|
||||
})
|
||||
),
|
||||
{
|
||||
VITE_APP_BASE_PATH: '/che',
|
||||
VITE_GAME_API_URL: '/che/api/trpc',
|
||||
VITE_GAME_SSE_URL: '/che/api/events',
|
||||
VITE_GAME_PROFILE: 'che',
|
||||
VITE_BUILD_COMMIT_SHA: 'c'.repeat(40),
|
||||
}
|
||||
);
|
||||
|
||||
assert.equal(config.profile, 'pya');
|
||||
assert.equal(config.appBasePath, '/pya/');
|
||||
assert.equal(config.gameApiUrl, '/pya/api/trpc');
|
||||
assert.equal(config.gameSseUrl, '/pya/api/events');
|
||||
assert.equal(config.buildCommitSha, 'a'.repeat(40));
|
||||
assert.equal(config.assetReleaseId, `${'a'.repeat(40)}-${'b'.repeat(16)}`);
|
||||
});
|
||||
|
||||
void it('keeps preview and local development compatible through Vite environment fallbacks', () => {
|
||||
const config = resolveGameFrontendRuntimeConfig(documentWithConfig(null), {
|
||||
VITE_APP_BASE_PATH: '/hwe',
|
||||
VITE_GAME_API_URL: '/hwe/api/trpc',
|
||||
VITE_GAME_SSE_URL: '/hwe/api/events',
|
||||
VITE_GAME_PROFILE: 'hwe',
|
||||
VITE_GATEWAY_API_URL: '/gateway/api/trpc',
|
||||
VITE_GATEWAY_WEB_URL: '/gateway/',
|
||||
VITE_BUILD_COMMIT_SHA: 'd'.repeat(40),
|
||||
});
|
||||
|
||||
assert.deepEqual(config, {
|
||||
version: 1,
|
||||
profile: 'hwe',
|
||||
appBasePath: '/hwe/',
|
||||
gameApiUrl: '/hwe/api/trpc',
|
||||
gameSseUrl: '/hwe/api/events',
|
||||
gatewayApiUrl: '/gateway/api/trpc',
|
||||
gatewayWebUrl: '/gateway/',
|
||||
buildCommitSha: 'd'.repeat(40),
|
||||
});
|
||||
});
|
||||
|
||||
void it('falls back safely when the embedded script is malformed', () => {
|
||||
const config = resolveGameFrontendRuntimeConfig(documentWithConfig('{'), {});
|
||||
|
||||
assert.equal(config.appBasePath, '/');
|
||||
assert.equal(config.gameApiUrl, '/api/trpc');
|
||||
assert.equal(config.gameSseUrl, '/api/events');
|
||||
assert.equal(config.buildCommitSha, 'unknown');
|
||||
});
|
||||
});
|
||||
@@ -29,6 +29,31 @@ void describe('game frontend Vite config', () => {
|
||||
assert.equal(loaded?.config.build?.sourcemap, true);
|
||||
});
|
||||
|
||||
void it('allows a profile-neutral relative asset base without changing the application base fallback', async () => {
|
||||
const previousAssetBasePath = process.env.VITE_ASSET_BASE_PATH;
|
||||
const previousAppBasePath = process.env.VITE_APP_BASE_PATH;
|
||||
process.env.VITE_ASSET_BASE_PATH = './';
|
||||
process.env.VITE_APP_BASE_PATH = '/che';
|
||||
try {
|
||||
const configPath = path.resolve(import.meta.dirname, '../vite.config.ts');
|
||||
const loaded = await loadConfigFromFile(
|
||||
{ command: 'build', mode: 'production' },
|
||||
configPath,
|
||||
path.dirname(configPath),
|
||||
undefined,
|
||||
undefined,
|
||||
'runner'
|
||||
);
|
||||
|
||||
assert.equal(loaded?.config.base, './');
|
||||
} finally {
|
||||
if (previousAssetBasePath === undefined) delete process.env.VITE_ASSET_BASE_PATH;
|
||||
else process.env.VITE_ASSET_BASE_PATH = previousAssetBasePath;
|
||||
if (previousAppBasePath === undefined) delete process.env.VITE_APP_BASE_PATH;
|
||||
else process.env.VITE_APP_BASE_PATH = previousAppBasePath;
|
||||
}
|
||||
});
|
||||
|
||||
void it('uses the deployment-pinned full commit SHA as the displayed build version', async () => {
|
||||
const commitSha = 'ABCDEF0123456789ABCDEF0123456789ABCDEF01';
|
||||
const previousCommitSha = process.env.VITE_BUILD_COMMIT_SHA;
|
||||
|
||||
@@ -38,6 +38,9 @@ export const createDeploymentVersionPlugin = (buildCommitSha: string): Plugin =>
|
||||
|
||||
const normalizeBasePath = (value: string | undefined): string => {
|
||||
const pathValue = (value ?? '/').trim();
|
||||
if (pathValue === './') {
|
||||
return './';
|
||||
}
|
||||
if (!pathValue || pathValue === '/') {
|
||||
return '/';
|
||||
}
|
||||
@@ -61,7 +64,7 @@ export default defineConfig(({ mode }) => {
|
||||
const env = mergeViteEnv(loadEnv(mode, process.cwd(), ''), process.env);
|
||||
const buildCommitSha = resolveBuildCommitSha(env.VITE_BUILD_COMMIT_SHA, path.resolve(import.meta.dirname, '../..'));
|
||||
return {
|
||||
base: normalizeBasePath(env.VITE_APP_BASE_PATH),
|
||||
base: normalizeBasePath(env.VITE_ASSET_BASE_PATH ?? env.VITE_APP_BASE_PATH),
|
||||
plugins: [vue(), tailwindcss(), createDeploymentVersionPlugin(buildCommitSha)],
|
||||
define: {
|
||||
'import.meta.env.VITE_BUILD_COMMIT_SHA': JSON.stringify(buildCommitSha),
|
||||
|
||||
Reference in New Issue
Block a user