vike
Version:
The Framework *You* Control - Next.js & Nuxt alternative for unprecedented flexibility and dependability.
170 lines (169 loc) • 8.7 kB
JavaScript
export { prepareViteApiCall };
export { getViteRoot };
export { assertViteRoot };
export { normalizeViteRoot };
import { loadConfigFromFile, mergeConfig, resolveConfig } from 'vite';
import { clearContextApiOperation, setContextApiOperation } from './context.js';
import { getVikeConfigInternal, getVikeConfigFromCliOrEnv, setVikeConfigContext, } from '../vite/shared/resolveVikeConfigInternal.js';
import path from 'node:path';
import { assert, assertUsage, getGlobalObject, isObject, pick, toPosixPath } from './utils.js';
import pc from '@brillout/picocolors';
import { clearGlobalContext } from '../runtime/globalContext.js';
import { getEnvVarObject } from '../vite/shared/getEnvVarObject.js';
const globalObject = getGlobalObject('api/prepareViteApiCall.ts', {});
async function prepareViteApiCall(options, operation) {
clear();
setContextApiOperation(operation, options);
const viteConfigFromUserApiOptions = options.viteConfig;
return resolveConfigs(viteConfigFromUserApiOptions, operation);
}
// For subsequent API calls, e.g. calling prerender() after build()
function clear() {
clearContextApiOperation();
clearGlobalContext();
}
async function resolveConfigs(viteConfigFromUserApiOptions, operation) {
const viteInfo = await getViteInfo(viteConfigFromUserApiOptions, operation);
setVikeConfigContext({
userRootDir: viteInfo.root,
isDev: operation === 'dev',
vikeVitePluginOptions: viteInfo.vikeVitePluginOptions,
});
const vikeConfig = await getVikeConfigInternal();
const viteConfigFromUserEnhanced = applyVikeViteConfig(viteInfo.viteConfigFromUserEnhanced, vikeConfig);
const { viteConfigResolved } = await assertViteRoot2(viteInfo.root, viteConfigFromUserEnhanced, operation);
return {
vikeConfig,
viteConfigResolved, // ONLY USE if strictly necessary. (We plan to remove assertViteRoot2() as explained in the comments of that function.)
viteConfigFromUserEnhanced,
};
}
// Apply +vite
// - For example, Vike extensions adding Vite plugins
function applyVikeViteConfig(viteConfigFromUserEnhanced, vikeConfig) {
const viteConfigs = vikeConfig._from.configsCumulative.vite;
if (!viteConfigs)
return viteConfigFromUserEnhanced;
viteConfigs.values.forEach((v) => {
assertUsage(isObject(v.value), `${v.definedAt} should be an object`);
viteConfigFromUserEnhanced = mergeConfig(viteConfigFromUserEnhanced ?? {}, v.value);
assertUsage(!findVikeVitePlugin(v.value), "Using the +vite setting to add Vike's Vite plugin is forbidden");
});
return viteConfigFromUserEnhanced;
}
async function getViteRoot(operation) {
if (!globalObject.root)
await getViteInfo(undefined, operation);
assert(globalObject.root);
return globalObject.root;
}
async function getViteInfo(viteConfigFromUserApiOptions, operation) {
let viteConfigFromUserEnhanced = viteConfigFromUserApiOptions;
// Precedence:
// 1) viteConfigFromUserEnvVar (highest precedence)
// 2) viteConfigFromUserVikeConfig
// 2) viteConfigFromUserApiOptions
// 3) viteConfigFromUserViteFile (lowest precedence)
// Resolve Vike's +mode setting
{
const viteConfigFromUserVikeConfig = pick(getVikeConfigFromCliOrEnv().vikeConfigFromCliOrEnv, ['mode']);
if (Object.keys(viteConfigFromUserVikeConfig).length > 0) {
viteConfigFromUserEnhanced = mergeConfig(viteConfigFromUserEnhanced ?? {}, viteConfigFromUserVikeConfig);
}
}
// Resolve VITE_CONFIG
const viteConfigFromUserEnvVar = getEnvVarObject('VITE_CONFIG');
if (viteConfigFromUserEnvVar) {
viteConfigFromUserEnhanced = mergeConfig(viteConfigFromUserEnhanced ?? {}, viteConfigFromUserEnvVar);
}
// Resolve vite.config.js
const viteConfigFromUserViteFile = await loadViteConfigFile(viteConfigFromUserEnhanced, operation);
// Correct precedence, replicates Vite:
// https://github.com/vitejs/vite/blob/4f5845a3182fc950eb9cd76d7161698383113b18/packages/vite/src/node/config.ts#L1001
const viteConfigResolved = mergeConfig(viteConfigFromUserViteFile ?? {}, viteConfigFromUserEnhanced ?? {});
const root = normalizeViteRoot(viteConfigResolved.root ?? process.cwd());
globalObject.root = root;
// - Find options `vike(options)` set in vite.config.js
// - TODO/next-major: remove
// - Add Vike's Vite plugin if missing
let vikeVitePluginOptions;
const found = findVikeVitePlugin(viteConfigResolved);
if (found) {
vikeVitePluginOptions = found.vikeVitePluginOptions;
}
else {
// Add Vike to plugins if not present.
// Using a dynamic import because the script calling the Vike API may not live in the same place as vite.config.js, thus vike/plugin may resolved to two different node_modules/vike directories.
const { plugin: vikePlugin } = await import('../vite/index.js');
viteConfigFromUserEnhanced = {
...viteConfigFromUserEnhanced,
plugins: [...(viteConfigFromUserEnhanced?.plugins ?? []), vikePlugin()],
};
const res = findVikeVitePlugin(viteConfigFromUserEnhanced);
assert(res);
vikeVitePluginOptions = res.vikeVitePluginOptions;
}
assert(vikeVitePluginOptions);
return { root, vikeVitePluginOptions, viteConfigFromUserEnhanced };
}
function findVikeVitePlugin(viteConfig) {
let vikeVitePluginOptions;
let vikeVitePuginFound = false;
viteConfig?.plugins?.forEach((p) => {
if (p && '_vikeVitePluginOptions' in p) {
vikeVitePuginFound = true;
const options = p._vikeVitePluginOptions;
vikeVitePluginOptions ?? (vikeVitePluginOptions = {});
Object.assign(vikeVitePluginOptions, options);
}
});
if (!vikeVitePuginFound)
return null;
return { vikeVitePluginOptions };
}
// Copied from https://github.com/vitejs/vite/blob/4f5845a3182fc950eb9cd76d7161698383113b18/packages/vite/src/node/config.ts#L961-L1005
async function loadViteConfigFile(viteConfigFromUserApiOptions, operation) {
const [inlineConfig, command, defaultMode, _defaultNodeEnv, isPreview] = getResolveConfigArgs(viteConfigFromUserApiOptions, operation);
let config = inlineConfig;
let mode = inlineConfig.mode || defaultMode;
const configEnv = {
mode,
command,
isSsrBuild: command === 'build' && !!config.build?.ssr,
isPreview,
};
let { configFile } = config;
if (configFile !== false) {
const loadResult = await loadConfigFromFile(configEnv, configFile, config.root, config.logLevel, config.customLogger);
return loadResult?.config;
}
return null;
}
function getResolveConfigArgs(viteConfig = {}, operation) {
const inlineConfig = viteConfig;
const command = operation === 'build' || operation === 'prerender' ? 'build' : 'serve';
const defaultMode = operation === 'dev' ? 'development' : 'production';
const defaultNodeEnv = defaultMode;
const isPreview = operation === 'preview';
return [inlineConfig, command, defaultMode, defaultNodeEnv, isPreview];
}
function normalizeViteRoot(root) {
// `path.resolve(viteConfigFromUserViteFile.configFile, root)` could be more intuitive than `path.resolve(process.cwd(), root)` but we replicate Vite's behavior (`vite.config.js` should follow Vite's API), see:
// https://github.com/vitejs/vite/blob/4f5845a3182fc950eb9cd76d7161698383113b18/packages/vite/src/node/config.ts#L1063
return toPosixPath(
// Equivalent to `path.resolve(process.cwd(), root)`
path.resolve(root));
}
const errMsg = `A Vite plugin is modifying Vite's setting ${pc.cyan('root')} which is forbidden`;
async function assertViteRoot2(root, viteConfigFromUserEnhanced, operation) {
const args = getResolveConfigArgs(viteConfigFromUserEnhanced, operation);
// We can eventually remove this resolveConfig() call (along with removing the whole assertViteRoot2() function which is redundant with the assertViteRoot() function) so that Vike doesn't make any resolveConfig() (except for pre-rendering and preview which is required). But let's keep it for now, just to see whether calling resolveConfig() can be problematic.
const viteConfigResolved = await resolveConfig(...args);
assertUsage(normalizeViteRoot(viteConfigResolved.root) === normalizeViteRoot(root), errMsg);
return { viteConfigResolved };
}
function assertViteRoot(root, config) {
if (globalObject.root)
assert(normalizeViteRoot(globalObject.root) === normalizeViteRoot(root));
assertUsage(normalizeViteRoot(root) === normalizeViteRoot(config.root), errMsg);
}