vike
Version:
The Framework *You* Control - Next.js & Nuxt alternative for unprecedented flexibility and dependability.
968 lines • 60.1 kB
JavaScript
"use strict";
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.getVikeConfigInternal = getVikeConfigInternal;
exports.getVikeConfigInternalOptional = getVikeConfigInternalOptional;
exports.getVikeConfigInternalSync = getVikeConfigInternalSync;
exports.setVikeConfigContext = setVikeConfigContext;
exports.reloadVikeConfig = reloadVikeConfig;
exports.isV1Design = isV1Design;
exports.getConfVal = getConfVal;
exports.getConfigDefinitionOptional = getConfigDefinitionOptional;
exports.getVikeConfigFromCliOrEnv = getVikeConfigFromCliOrEnv;
exports.isOverridden = isOverridden;
exports.getVikeConfig = getVikeConfig;
const utils_js_1 = require("../utils.js");
const configDefinitionsBuiltIn_js_1 = require("./resolveVikeConfigInternal/configDefinitionsBuiltIn.js");
const filesystemRouting_js_1 = require("./resolveVikeConfigInternal/filesystemRouting.js");
const globalContext_js_1 = require("../../runtime/globalContext.js");
const loggerNotProd_js_1 = require("./loggerNotProd.js");
const removeSuperfluousViteLog_js_1 = require("./loggerVite/removeSuperfluousViteLog.js");
const picocolors_1 = __importDefault(require("@brillout/picocolors"));
const getConfigDefinedAt_js_1 = require("../../../shared/page-configs/getConfigDefinedAt.js");
const loadFileAtConfigTime_js_1 = require("./resolveVikeConfigInternal/loadFileAtConfigTime.js");
const resolvePointerImport_js_1 = require("./resolveVikeConfigInternal/resolvePointerImport.js");
const getFilePath_js_1 = require("./getFilePath.js");
const getConfigValueBuildTime_js_1 = require("../../../shared/page-configs/getConfigValueBuildTime.js");
const resolveVikeConfigPublic_js_1 = require("../../../shared/page-configs/resolveVikeConfigPublic.js");
const serializeConfigValues_js_1 = require("../../../shared/page-configs/serialize/serializeConfigValues.js");
const getPlusFilesAll_js_1 = require("./resolveVikeConfigInternal/getPlusFilesAll.js");
const getEnvVarObject_js_1 = require("./getEnvVarObject.js");
const context_js_1 = require("../../api/context.js");
const context_js_2 = require("../../cli/context.js");
const resolvePrerenderConfig_js_1 = require("../../prerender/resolvePrerenderConfig.js");
const getProxyForPublicUsage_js_1 = require("../../../shared/getProxyForPublicUsage.js");
const getVikeConfigError_js_1 = require("../../shared/getVikeConfigError.js");
(0, utils_js_1.assertIsNotProductionRuntime)();
// We can simply use global variables since Vike's config is:
// - global
// - independent of Vite (therefore we don't need to tie Vike's config with Vite's `config` object)
(0, utils_js_1.assertIsSingleModuleInstance)('v1-design/getVikeConfig.ts');
let restartVite = false;
let vikeConfigHasBuildError = null;
let isV1Design_ = null;
let vikeConfigPromise = null;
// TODO/v1-release: remove
let vikeConfigSync = null;
let vikeConfigCtx = null; // Information provided by Vite's `config` and Vike's CLI. We could, if we want or need to, completely remove the dependency on Vite.
let prerenderContext;
function reloadVikeConfig() {
(0, utils_js_1.assert)(vikeConfigCtx);
const { userRootDir, vikeVitePluginOptions } = vikeConfigCtx;
(0, utils_js_1.assert)(vikeVitePluginOptions);
resolveVikeConfigInternal_withErrorHandling(userRootDir, true, vikeVitePluginOptions);
}
async function getVikeConfigInternal(
// I don't remember the logic behind it — neither why we restart Vite's dev server, nor why we sometimes don't.
// TO-DO: eventually rethink all that. Some + settings are expected to influence Vite's config (restarting Vite's dev server is needed) while some don't.
doNotRestartViteOnError = false) {
(0, utils_js_1.assert)(vikeConfigCtx);
const { userRootDir, isDev, vikeVitePluginOptions } = vikeConfigCtx;
const vikeConfig = await getOrResolveVikeConfig(userRootDir, isDev, vikeVitePluginOptions, doNotRestartViteOnError);
return vikeConfig;
}
// TODO/v1-release: remove
function getVikeConfigInternalSync() {
(0, utils_js_1.assert)(vikeConfigSync);
return vikeConfigSync;
}
// TO-DO/eventually: this maybe(/probably?) isn't safe against race conditions upon file changes in development, thus:
// - Like getGlobalContext() and getGlobalContextSync() — make getVikeConfig() async and provide a getVikeConfigSync() while discourage using it
// Public usage
/**
* Get all the information Vike knows about the app in your Vite plugin.
*
* https://vike.dev/getVikeConfig
*/
function getVikeConfig(
// TO-DO/eventually: remove unused arguments (older versions used it and we didn't remove it yet to avoid a TypeScript breaking change)
// - No rush: we can do it later since it's getVikeConfig() is a beta feature as documented at https://vike.dev/getVikeConfig
config) {
const vikeConfig = getVikeConfigInternalSync();
(0, utils_js_1.assertUsage)(vikeConfig, 'getVikeConfig() can only be used when Vite is loaded (i.e. during development or build) — Vite is never loaded in production.');
const vikeConfigPublic = (0, getProxyForPublicUsage_js_1.getProxyForPublicUsage)(vikeConfig, 'vikeConfig');
return vikeConfigPublic;
}
function setVikeConfigContext(vikeConfigCtx_) {
// If the user changes Vite's `config.root` => Vite completely reloads itself => setVikeConfigContext() is called again
vikeConfigCtx = vikeConfigCtx_;
}
async function getOrResolveVikeConfig(userRootDir, isDev, vikeVitePluginOptions, doNotRestartViteOnError) {
if (!vikeConfigPromise) {
resolveVikeConfigInternal_withErrorHandling(userRootDir, isDev, vikeVitePluginOptions, doNotRestartViteOnError);
}
(0, utils_js_1.assert)(vikeConfigPromise);
const vikeConfig = await vikeConfigPromise;
return vikeConfig;
}
async function getVikeConfigInternalOptional() {
if (!vikeConfigPromise)
return null;
const vikeConfig = await vikeConfigPromise;
return vikeConfig;
}
function isV1Design() {
(0, utils_js_1.assert)(typeof isV1Design_ === 'boolean');
return isV1Design_;
}
async function resolveVikeConfigInternal_withErrorHandling(userRootDir, isDev, vikeVitePluginOptions, doNotRestartViteOnError) {
const { promise, resolve, reject } = (0, utils_js_1.genPromise)();
vikeConfigPromise = promise;
const esbuildCache = {
transpileCache: {},
vikeConfigDependencies: new Set(),
};
let hasError = false;
let ret;
let err;
try {
ret = await resolveVikeConfigInternal(userRootDir, vikeVitePluginOptions, esbuildCache);
}
catch (err_) {
hasError = true;
err = err_;
}
// There is a newer call — let the new call supersede the old one.
// We deliberately swallow the intermetidate state (including any potential error) — it's now outdated and has existed only for a very short period of time.
if (vikeConfigPromise !== promise) {
// vikeConfigPromise.then(resolve).catch(reject)
try {
resolve(await vikeConfigPromise);
}
catch (err) {
reject(err);
}
return;
}
if (!hasError) {
(0, utils_js_1.assert)(ret);
(0, utils_js_1.assert)(err === undefined);
const hadError = vikeConfigHasBuildError;
vikeConfigHasBuildError = false;
(0, getVikeConfigError_js_1.setVikeConfigError)({ errorBuild: false });
if (hadError) {
(0, loggerNotProd_js_1.logConfigErrorRecover)();
if (restartVite) {
restartVite = false;
restartViteDevServer();
}
}
resolve(ret);
}
else {
(0, utils_js_1.assert)(ret === undefined);
(0, utils_js_1.assert)(err);
vikeConfigHasBuildError = true;
(0, getVikeConfigError_js_1.setVikeConfigError)({ errorBuild: { err } });
if (!doNotRestartViteOnError)
restartVite = true;
if (!isDev) {
reject(err);
}
else {
(0, loggerNotProd_js_1.logConfigError)(err);
resolve(getVikeConfigDummy(esbuildCache));
}
}
}
async function resolveVikeConfigInternal(userRootDir, vikeVitePluginOptions, esbuildCache) {
const plusFilesAll = await (0, getPlusFilesAll_js_1.getPlusFilesAll)(userRootDir, esbuildCache);
const configDefinitionsResolved = await resolveConfigDefinitions(plusFilesAll, userRootDir, esbuildCache);
const { pageConfigGlobal, pageConfigs } = getPageConfigsBuildTime(configDefinitionsResolved, plusFilesAll, userRootDir);
if (!isV1Design_)
isV1Design_ = pageConfigs.length > 0;
// Backwards compatibility for vike(options) in vite.config.js
temp_interopVikeVitePlugin(pageConfigGlobal, vikeVitePluginOptions, userRootDir);
setCliAndApiOptions(pageConfigGlobal, configDefinitionsResolved);
// global
const pageConfigGlobalValues = getConfigValues(pageConfigGlobal);
const vikeConfigPublicGlobal = (0, resolveVikeConfigPublic_js_1.resolveVikeConfigPublicGlobal)({ pageConfigGlobalValues });
// pages
const vikeConfigPublicPagesEager = (0, utils_js_1.objectFromEntries)(pageConfigs.map((pageConfig) => {
const pageConfigValues = getConfigValues(pageConfig, true);
return (0, resolveVikeConfigPublic_js_1.resolveVikeConfigPublicPageEager)(pageConfigGlobalValues, pageConfig, pageConfigValues);
}));
const prerenderContext = resolvePrerenderContext({
config: vikeConfigPublicGlobal.config,
_from: vikeConfigPublicGlobal._from,
_pageConfigs: pageConfigs,
});
const vikeConfig = {
_pageConfigs: pageConfigs,
_pageConfigGlobal: pageConfigGlobal,
config: vikeConfigPublicGlobal.config,
_from: vikeConfigPublicGlobal._from,
pages: vikeConfigPublicPagesEager,
prerenderContext,
_vikeConfigDependencies: esbuildCache.vikeConfigDependencies,
};
vikeConfigSync = vikeConfig;
return vikeConfig;
}
async function resolveConfigDefinitions(plusFilesAll, userRootDir, esbuildCache) {
const plusFilesAllOrdered = Object.values(plusFilesAll)
.flat()
.sort((plusFile1, plusFile2) => sortAfterInheritanceOrderGlobal(plusFile1, plusFile2, plusFilesAll, null));
const configDefinitionsGlobal = getConfigDefinitions(
// We use `plusFilesAll` in order to allow local Vike extensions to create global configs, and to set the value of global configs such as `+vite` (enabling Vike extensions to add Vite plugins).
plusFilesAllOrdered, (configDef) => !!configDef.global);
await loadCustomConfigBuildTimeFiles(plusFilesAll, configDefinitionsGlobal, userRootDir, esbuildCache);
const configDefinitionsAll = getConfigDefinitions(Object.values(plusFilesAll).flat());
const configNamesKnownAll = Object.keys(configDefinitionsAll);
const configNamesKnownGlobal = Object.keys(configDefinitionsGlobal);
(0, utils_js_1.assert)(configNamesKnownGlobal.every((configName) => configNamesKnownAll.includes(configName)));
const configDefinitionsLocal = {};
await Promise.all((0, utils_js_1.objectEntries)(plusFilesAll).map(async ([locationIdPage, plusFiles]) => {
const plusFilesRelevant = (0, utils_js_1.objectEntries)(plusFilesAll)
.filter(([locationId]) => (0, filesystemRouting_js_1.isInherited)(locationId, locationIdPage))
.map(([, plusFiles]) => plusFiles)
.flat()
.sort((plusFile1, plusFile2) => sortAfterInheritanceOrderPage(plusFile1, plusFile2, locationIdPage, null));
const configDefinitions = getConfigDefinitions(plusFilesRelevant, (configDef) => configDef.global !== true);
await loadCustomConfigBuildTimeFiles(plusFiles, configDefinitions, userRootDir, esbuildCache);
const configNamesKnownLocal = (0, utils_js_1.unique)([...Object.keys(configDefinitions), ...configNamesKnownGlobal]);
(0, utils_js_1.assert)(configNamesKnownLocal.every((configName) => configNamesKnownAll.includes(configName)));
configDefinitionsLocal[locationIdPage] = {
configDefinitions,
plusFiles,
plusFilesRelevant,
configNamesKnownLocal,
};
}));
const configDefinitionsResolved = {
configDefinitionsGlobal,
configDefinitionsLocal,
configDefinitionsAll,
configNamesKnownAll,
configNamesKnownGlobal,
};
assertKnownConfigs(configDefinitionsResolved);
return configDefinitionsResolved;
}
// Load value files (with `env.config===true`) of *custom* configs.
// - The value files of *built-in* configs are already loaded at `getPlusFilesAll()`.
async function loadCustomConfigBuildTimeFiles(plusFiles, configDefinitions, userRootDir, esbuildCache) {
const plusFileList = Object.values(plusFiles).flat(1);
await Promise.all(plusFileList.map(async (plusFile) => {
if (!plusFile.isConfigFile) {
await (0, loadFileAtConfigTime_js_1.loadValueFile)(plusFile, configDefinitions, userRootDir, esbuildCache);
}
else {
await Promise.all(Object.entries(plusFile.pointerImportsByConfigName).map(async ([configName, pointerImport]) => {
await (0, loadFileAtConfigTime_js_1.loadPointerImport)(pointerImport, userRootDir, configName, configDefinitions, esbuildCache);
}));
}
}));
}
function getPageConfigsBuildTime(configDefinitionsResolved, plusFilesAll, userRootDir) {
const pageConfigGlobal = {
configDefinitions: configDefinitionsResolved.configDefinitionsGlobal,
configValueSources: {},
};
(0, utils_js_1.objectEntries)(configDefinitionsResolved.configDefinitionsGlobal).forEach(([configName, configDef]) => {
const sources = resolveConfigValueSources(configName, configDef,
// We use `plusFilesAll` in order to allow local Vike extensions to create global configs, and to set the value of global configs such as `+vite` (enabling Vike extensions to add Vite plugins).
Object.values(plusFilesAll).flat(), userRootDir, true, plusFilesAll);
if (sources.length === 0)
return;
pageConfigGlobal.configValueSources[configName] = sources;
});
applyEffectsMetaEnv(pageConfigGlobal.configValueSources, configDefinitionsResolved.configDefinitionsGlobal);
applyEffectsConfVal(pageConfigGlobal.configValueSources, configDefinitionsResolved.configDefinitionsGlobal, plusFilesAll);
sortConfigValueSources(pageConfigGlobal.configValueSources, null);
assertPageConfigGlobal(pageConfigGlobal, plusFilesAll);
const pageConfigs = (0, utils_js_1.objectEntries)(configDefinitionsResolved.configDefinitionsLocal)
.filter(([_locationId, { plusFiles }]) => isDefiningPage(plusFiles))
.map(([locationId, { configDefinitions, plusFilesRelevant }]) => {
const configDefinitionsLocal = configDefinitions;
const configValueSources = {};
(0, utils_js_1.objectEntries)(configDefinitionsLocal)
.filter(([_configName, configDef]) => configDef.global !== true)
.forEach(([configName, configDef]) => {
const sources = resolveConfigValueSources(configName, configDef, plusFilesRelevant, userRootDir, false, plusFilesAll);
if (sources.length === 0)
return;
configValueSources[configName] = sources;
});
const pageConfigRoute = determineRouteFilesystem(locationId, configValueSources);
applyEffectsMetaEnv(configValueSources, configDefinitionsLocal);
applyEffectsConfVal(configValueSources, configDefinitionsLocal, plusFilesAll);
sortConfigValueSources(configValueSources, locationId);
const configValuesComputed = getComputed(configValueSources, configDefinitionsLocal);
const pageConfig = {
pageId: locationId,
...pageConfigRoute,
configDefinitions: configDefinitionsLocal,
plusFiles: plusFilesRelevant,
configValueSources,
configValuesComputed,
};
return pageConfig;
});
assertPageConfigs(pageConfigs);
return { pageConfigs, pageConfigGlobal };
}
function assertPageConfigGlobal(pageConfigGlobal, plusFilesAll) {
Object.entries(pageConfigGlobal.configValueSources).forEach(([configName, sources]) => {
assertGlobalConfigLocation(configName, sources, plusFilesAll, pageConfigGlobal.configDefinitions);
});
}
function assertGlobalConfigLocation(configName, sources, plusFilesAll, configDefinitionsGlobal) {
// Determine existing global +config.js files
const configFilePathsGlobal = [];
const plusFilesGlobal = Object.values((0, utils_js_1.objectFromEntries)((0, utils_js_1.objectEntries)(plusFilesAll).filter(([locationId]) => isGlobalLocation(locationId, plusFilesAll)))).flat();
plusFilesGlobal
.filter((i) => i.isConfigFile)
.forEach((plusFile) => {
const { filePathAbsoluteUserRootDir } = plusFile.filePath;
if (filePathAbsoluteUserRootDir) {
configFilePathsGlobal.push(filePathAbsoluteUserRootDir);
}
});
// Call assertWarning()
sources.forEach((source) => {
const { plusFile } = source;
// It's `null` when the config is defined by `vike(options)` in vite.config.js
(0, utils_js_1.assert)(plusFile);
const { filePathAbsoluteUserRootDir } = plusFile.filePath;
// Allow local Vike extensions to set global configs (`filePathAbsoluteUserRootDir===null` for Vike extension)
if (!filePathAbsoluteUserRootDir)
return;
(0, utils_js_1.assert)(!plusFile.isExtensionConfig);
if (!isGlobalLocation(source.locationId, plusFilesAll)) {
const configDef = configDefinitionsGlobal[configName];
(0, utils_js_1.assert)(configDef);
const isConditionallyGlobal = (0, utils_js_1.isCallable)(configDef.global);
const errBeg = `${filePathAbsoluteUserRootDir} (which is a local config file) sets the config ${picocolors_1.default.cyan(configName)}`;
const errMid = !isConditionallyGlobal
? "but it's a global config"
: 'to a value that is global';
const what = isConditionallyGlobal ? 'global values' : picocolors_1.default.cyan(configName);
const errEnd = configFilePathsGlobal.length > 0
? `define ${what} at a global config file such as ${(0, utils_js_1.joinEnglish)(configFilePathsGlobal.map(picocolors_1.default.bold), 'or')} instead`
: `create a global config file (e.g. /pages/+config.js) and define ${what} there instead`;
// When updating this error message => also update error message at https://vike.dev/warning/global-config
const errMsg = `${errBeg} ${errMid}: ${errEnd} (https://vike.dev/warning/global-config).`;
(0, utils_js_1.assertWarning)(false, errMsg, { onlyOnce: true });
}
});
}
function assertPageConfigs(pageConfigs) {
pageConfigs.forEach((pageConfig) => {
assertOnBeforeRenderEnv(pageConfig);
});
}
function assertOnBeforeRenderEnv(pageConfig) {
const onBeforeRenderConfig = pageConfig.configValueSources.onBeforeRender?.[0];
if (!onBeforeRenderConfig)
return;
const onBeforeRenderEnv = onBeforeRenderConfig.configEnv;
const isClientRouting = (0, getConfigValueBuildTime_js_1.getConfigValueBuildTime)(pageConfig, 'clientRouting', 'boolean');
// When using Server Routing, loading a onBeforeRender() hook on the client-side hasn't any effect (the Server Routing's client runtime never calls it); it unnecessarily bloats client bundle sizes
(0, utils_js_1.assertUsage)(!(onBeforeRenderEnv.client && !isClientRouting), `Page ${pageConfig.pageId} has an onBeforeRender() hook with env ${picocolors_1.default.cyan(JSON.stringify(onBeforeRenderEnv))} which doesn't make sense because the page is using Server Routing: onBeforeRender() can be run in the client only when using Client Routing.`);
}
function getConfigValues(pageConfig, tolerateMissingValue) {
const configValues = {};
(0, serializeConfigValues_js_1.getConfigValuesBase)(pageConfig, (configEnv) => !!configEnv.config, null).forEach((entry) => {
if (entry.configValueBase.type === 'computed') {
(0, utils_js_1.assert)('value' in entry); // Help TS
const { configValueBase, value, configName } = entry;
configValues[configName] = { ...configValueBase, value };
}
if (entry.configValueBase.type === 'standard') {
(0, utils_js_1.assert)('sourceRelevant' in entry); // Help TS
const { configValueBase, sourceRelevant, configName } = entry;
if (!sourceRelevant.valueIsLoaded) {
if (tolerateMissingValue)
return;
(0, utils_js_1.assert)(false);
}
const { value } = sourceRelevant;
configValues[configName] = { ...configValueBase, value };
}
if (entry.configValueBase.type === 'cumulative') {
(0, utils_js_1.assert)('sourcesRelevant' in entry); // Help TS
const { configValueBase, sourcesRelevant, configName } = entry;
const values = [];
sourcesRelevant.forEach((source) => {
if (!source.valueIsLoaded) {
if (tolerateMissingValue)
return;
(0, utils_js_1.assert)(false);
}
values.push(source.value);
});
if (values.length === 0) {
if (tolerateMissingValue)
return;
(0, utils_js_1.assert)(false);
}
configValues[configName] = { ...configValueBase, value: values };
}
});
return configValues;
}
function temp_interopVikeVitePlugin(pageConfigGlobal, vikeVitePluginOptions, userRootDir) {
(0, utils_js_1.assert)((0, utils_js_1.isObject)(vikeVitePluginOptions));
(0, utils_js_1.assertWarning)(Object.keys(vikeVitePluginOptions).length === 0, `Define Vike settings in +config.js instead of vite.config.js ${picocolors_1.default.underline('https://vike.dev/migration/settings')}`, { onlyOnce: true });
Object.entries(vikeVitePluginOptions).forEach(([configName, value]) => {
var _a;
const sources = ((_a = pageConfigGlobal.configValueSources)[configName] ?? (_a[configName] = []));
sources.push(getSourceNonConfigFile(configName, value, {
...(0, getFilePath_js_1.getFilePathResolved)({
userRootDir,
filePathAbsoluteUserRootDir: '/vite.config.js',
}),
fileExportPathToShowToUser: null,
}));
});
}
function setCliAndApiOptions(pageConfigGlobal, configDefinitionsResolved) {
// Vike API — passed options [lowest precedence]
const apiOperation = (0, context_js_1.getApiOperation)();
if (apiOperation?.options.vikeConfig) {
addSources(apiOperation.options.vikeConfig, { definedBy: 'api', operation: apiOperation.operation }, false);
}
const { configFromCliOptions, configFromEnvVar } = getVikeConfigFromCliOrEnv();
// Vike CLI options
if (configFromCliOptions) {
addSources(configFromCliOptions, { definedBy: 'cli' }, true);
}
// VIKE_CONFIG [highest precedence]
if (configFromEnvVar) {
addSources(configFromEnvVar, { definedBy: 'env' }, false);
}
return;
function addSources(configValues, definedBy, exitOnError) {
Object.entries(configValues).forEach(([configName, value]) => {
var _a;
const sourceName = `The ${(0, getConfigDefinedAt_js_1.getDefinedByString)(definedBy, configName)}`;
assertKnownConfig(configName, configDefinitionsResolved.configNamesKnownGlobal, configDefinitionsResolved, '/', false, sourceName, exitOnError);
const sources = ((_a = pageConfigGlobal.configValueSources)[configName] ?? (_a[configName] = []));
sources.unshift(getSourceNonConfigFile(configName, value, definedBy));
});
}
}
function getVikeConfigFromCliOrEnv() {
const configFromCliOptions = (0, context_js_2.getCliOptions)();
const configFromEnvVar = (0, getEnvVarObject_js_1.getEnvVarObject)('VIKE_CONFIG');
const vikeConfigFromCliOrEnv = {
...configFromCliOptions, // Lower precedence
...configFromEnvVar, // Higher precedence
};
return {
vikeConfigFromCliOrEnv,
configFromCliOptions,
configFromEnvVar,
};
}
function getSourceNonConfigFile(configName, value, definedAt) {
(0, utils_js_1.assert)((0, utils_js_1.includes)((0, utils_js_1.objectKeys)(configDefinitionsBuiltIn_js_1.configDefinitionsBuiltIn), configName));
const configDef = configDefinitionsBuiltIn_js_1.configDefinitionsBuiltIn[configName];
const source = {
valueIsLoaded: true,
value,
configEnv: configDef.env,
definedAt,
locationId: '/',
plusFile: null,
valueIsLoadedWithImport: false,
valueIsDefinedByPlusValueFile: false,
};
return source;
}
function sortConfigValueSources(configValueSources, locationIdPage) {
Object.entries(configValueSources).forEach(([configName, sources]) => {
sources
.sort((source1, source2) => {
if (!source1.plusFile || !source2.plusFile)
return 0;
const isGlobal = !locationIdPage;
if (isGlobal) {
return sortAfterInheritanceOrderGlobal(source1.plusFile, source2.plusFile, null, configName);
}
else {
return sortAfterInheritanceOrderPage(source1.plusFile, source2.plusFile, locationIdPage, configName);
}
})
// TODO/next-major: remove
// Interop with vike(options) in vite.config.js — make it least precedence.
.sort((0, utils_js_1.makeLast)((source) => !source.plusFile));
});
}
function sortAfterInheritanceOrderPage(plusFile1, plusFile2, locationIdPage, configName) {
{
const ret = (0, filesystemRouting_js_1.sortAfterInheritanceOrder)(plusFile1.locationId, plusFile2.locationId, locationIdPage);
if (ret !== 0)
return ret;
(0, utils_js_1.assert)(plusFile1.locationId === plusFile2.locationId);
}
if (configName) {
const ret = sortPlusFilesSameLocationId(plusFile1, plusFile2, configName);
if (ret !== 0)
return ret;
}
return 0;
}
function sortAfterInheritanceOrderGlobal(plusFile1, plusFile2, plusFilesAll, configName) {
if (plusFilesAll) {
const ret = (0, utils_js_1.makeFirst)((plusFile) => isGlobalLocation(plusFile.locationId, plusFilesAll))(plusFile1, plusFile2);
if (ret !== 0)
return ret;
}
{
const ret = (0, utils_js_1.lowerFirst)((plusFile) => plusFile.locationId.split('/').length)(plusFile1, plusFile2);
if (ret !== 0)
return ret;
}
if (plusFile1.locationId !== plusFile2.locationId) {
// Same as `sort()` in `['some', 'string', 'array'].sort()`
return plusFile1.locationId > plusFile2.locationId ? 1 : -1;
}
if (configName) {
(0, utils_js_1.assert)(plusFile1.locationId === plusFile2.locationId);
const ret = sortPlusFilesSameLocationId(plusFile1, plusFile2, configName);
if (ret !== 0)
return ret;
}
return 0;
}
function sortPlusFilesSameLocationId(plusFile1, plusFile2, configName) {
(0, utils_js_1.assert)(plusFile1.locationId === plusFile2.locationId);
(0, utils_js_1.assert)(isDefiningConfig(plusFile1, configName));
(0, utils_js_1.assert)(isDefiningConfig(plusFile2, configName));
// Config set by extensions (lowest precedence)
{
const ret = (0, utils_js_1.makeLast)((plusFile) => !!plusFile.isExtensionConfig)(plusFile1, plusFile2);
if (ret !== 0)
return ret;
}
// Config set by side-export (lower precedence)
{
// - For example `export { frontmatter }` of `.mdx` files.
// - This only considers side-export configs that are already loaded at build-time. (E.g. it actually doesn't consider `export { frontmatter }` of .mdx files since .mdx files are loaded only at runtime.)
const ret = (0, utils_js_1.makeLast)((plusFile) => !plusFile.isConfigFile &&
// Is side-export
plusFile.configName !== configName)(plusFile1, plusFile2);
if (ret !== 0)
return ret;
}
// Config set by +config.js
{
const ret = (0, utils_js_1.makeLast)((plusFile) => plusFile.isConfigFile)(plusFile1, plusFile2);
if (ret !== 0)
return ret;
}
// Config set by +{configName}.js (highest precedence)
// No need to make it deterministic: the overall order is already deterministic, see sortMakeDeterministic() at getPlusFilesAll()
return 0;
}
function resolveConfigValueSources(configName, configDef, plusFilesRelevant, userRootDir, isGlobal, plusFilesAll) {
let sources = plusFilesRelevant
.filter((plusFile) => isDefiningConfig(plusFile, configName))
.map((plusFile) => getConfigValueSource(configName, plusFile, configDef, userRootDir));
// Filter hydrid global-local configs
if (!(0, utils_js_1.isCallable)(configDef.global)) {
// Already filtered
(0, utils_js_1.assert)((configDef.global ?? false) === isGlobal);
}
else {
// We cannot filter earlier
(0, utils_js_1.assert)(configDef.env.config);
sources = sources.filter((source) => {
(0, utils_js_1.assert)(source.configEnv.config);
(0, utils_js_1.assert)(source.valueIsLoaded);
const valueIsGlobal = resolveIsGlobalValue(configDef.global, source, plusFilesAll);
return isGlobal ? valueIsGlobal : !valueIsGlobal;
});
}
return sources;
}
function isDefiningConfig(plusFile, configName) {
return getConfigNamesSetByPlusFile(plusFile).includes(configName);
}
function getConfigValueSource(configName, plusFile, configDef, userRootDir) {
const confVal = getConfVal(plusFile, configName);
(0, utils_js_1.assert)(confVal);
const configValueSourceCommon = {
locationId: plusFile.locationId,
plusFile,
};
const definedAtFilePath_ = {
...plusFile.filePath,
fileExportPathToShowToUser: ['default', configName],
};
// +client.js
if (configDef._valueIsFilePath) {
let definedAtFilePath;
let valueFilePath;
if (plusFile.isConfigFile) {
// Defined over pointer import
(0, utils_js_1.assert)(confVal.valueIsLoaded);
const pointerImport = (0, resolvePointerImport_js_1.resolvePointerImport)(confVal.value, plusFile.filePath, userRootDir, configName);
const configDefinedAt = (0, getConfigDefinedAt_js_1.getConfigDefinedAt)('Config', configName, definedAtFilePath_);
(0, utils_js_1.assertUsage)(pointerImport, `${configDefinedAt} should be an import`);
valueFilePath = pointerImport.fileExportPath.filePathAbsoluteVite;
definedAtFilePath = pointerImport.fileExportPath;
}
else {
// Defined by value file, i.e. +{configName}.js
(0, utils_js_1.assert)(!plusFile.isConfigFile);
valueFilePath = plusFile.filePath.filePathAbsoluteVite;
definedAtFilePath = {
...plusFile.filePath,
fileExportPathToShowToUser: [],
};
}
const configValueSource = {
...configValueSourceCommon,
valueIsLoaded: true,
value: valueFilePath,
valueIsFilePath: true,
configEnv: configDef.env,
valueIsLoadedWithImport: false,
valueIsDefinedByPlusValueFile: false,
definedAt: definedAtFilePath,
};
return configValueSource;
}
// +config.js
if (plusFile.isConfigFile) {
(0, utils_js_1.assert)(confVal.valueIsLoaded);
// Defined over pointer import
const pointerImport = plusFile.pointerImportsByConfigName[configName];
if (pointerImport) {
const value = pointerImport.fileExportValueLoaded
? {
valueIsLoaded: true,
value: pointerImport.fileExportValue,
}
: {
valueIsLoaded: false,
};
const configValueSource = {
...configValueSourceCommon,
...value,
configEnv: resolveConfigEnv(configDef.env, pointerImport.fileExportPath),
valueIsLoadedWithImport: true,
valueIsDefinedByPlusValueFile: false,
definedAt: pointerImport.fileExportPath,
};
return configValueSource;
}
// Defined inside +config.js
const configValueSource = {
...configValueSourceCommon,
valueIsLoaded: true,
value: confVal.value,
configEnv: configDef.env,
valueIsLoadedWithImport: false,
valueIsDefinedByPlusValueFile: false,
definedAt: definedAtFilePath_,
};
return configValueSource;
}
// Defined by value file, i.e. +{configName}.js
if (!plusFile.isConfigFile) {
const configEnvResolved = resolveConfigEnv(configDef.env, plusFile.filePath);
(0, utils_js_1.assert)(confVal.valueIsLoaded === !!configEnvResolved.config);
const configValueSource = {
...configValueSourceCommon,
...confVal,
configEnv: configEnvResolved,
valueIsLoadedWithImport: !confVal.valueIsLoaded || !(0, serializeConfigValues_js_1.isJsonValue)(confVal.value),
valueIsDefinedByPlusValueFile: true,
definedAt: {
...plusFile.filePath,
fileExportPathToShowToUser: configName === plusFile.configName
? []
: // Side-effect config (e.g. `export { frontmatter }` of .md files)
[configName],
},
};
return configValueSource;
}
(0, utils_js_1.assert)(false);
}
function isDefiningPage(plusFiles) {
for (const plusFile of plusFiles) {
const configNames = getConfigNamesSetByPlusFile(plusFile);
if (configNames.some((configName) => isDefiningPageConfig(configName))) {
return true;
}
}
return false;
}
function isDefiningPageConfig(configName) {
return ['Page', 'route'].includes(configName);
}
function resolveIsGlobalValue(configDefGlobal, source, plusFilesAll) {
(0, utils_js_1.assert)(source.valueIsLoaded);
let isGlobal;
if ((0, utils_js_1.isCallable)(configDefGlobal))
isGlobal = configDefGlobal(source.value, {
isGlobalLocation: isGlobalLocation(source.locationId, plusFilesAll),
});
else
isGlobal = configDefGlobal ?? false;
(0, utils_js_1.assert)(typeof isGlobal === 'boolean');
return isGlobal;
}
function getConfigNamesSetByPlusFile(plusFile) {
if (!plusFile.isConfigFile) {
return [plusFile.configName];
}
else {
return Object.keys(plusFile.fileExportsByConfigName);
}
}
function getConfigDefinitions(plusFilesRelevant, filter) {
let configDefinitions = { ...configDefinitionsBuiltIn_js_1.configDefinitionsBuiltIn };
// Add user-land meta configs
plusFilesRelevant
.slice()
.reverse()
.forEach((plusFile) => {
const confVal = getConfVal(plusFile, 'meta');
if (!confVal)
return;
(0, utils_js_1.assert)(confVal.valueIsLoaded);
const meta = confVal.value;
assertMetaUsage(meta, `Config ${picocolors_1.default.cyan('meta')} defined at ${plusFile.filePath.filePathToShowToUser}`);
// Set configDef._userEffectDefinedAtFilePath
Object.entries(meta).forEach(([configName, configDef]) => {
if ('isDefinedByPeerDependency' in configDef)
return;
if (!configDef.effect)
return;
(0, utils_js_1.assert)(plusFile.isConfigFile);
configDef._userEffectDefinedAtFilePath = {
...plusFile.filePath,
fileExportPathToShowToUser: ['default', 'meta', configName, 'effect'],
};
});
(0, utils_js_1.objectEntries)(meta).forEach(([configName, configDefinitionUserLand]) => {
if ('isDefinedByPeerDependency' in configDefinitionUserLand) {
configDefinitionUserLand = {
env: { client: false, server: false, config: false },
...configDefinitionUserLand,
};
}
// User can override an existing config definition
configDefinitions[configName] = {
...configDefinitions[configName],
...configDefinitionUserLand,
};
});
});
if (filter) {
configDefinitions = Object.fromEntries(Object.entries(configDefinitions).filter(([_configName, configDef]) => filter(configDef)));
}
return configDefinitions;
}
function assertMetaUsage(metaVal, metaConfigDefinedAt) {
if (!(0, utils_js_1.isObject)(metaVal)) {
(0, utils_js_1.assert)(metaConfigDefinedAt); // We expect internal effects to return a valid meta value
(0, utils_js_1.assertUsage)(false, `${metaConfigDefinedAt} has an invalid type ${picocolors_1.default.cyan(typeof metaVal)}: it should be an object instead.`);
}
(0, utils_js_1.objectEntries)(metaVal).forEach(([configName, def]) => {
if (!(0, utils_js_1.isObject)(def)) {
(0, utils_js_1.assert)(metaConfigDefinedAt); // We expect internal effects to return a valid meta value
(0, utils_js_1.assertUsage)(false, `${metaConfigDefinedAt} sets ${picocolors_1.default.cyan(`meta.${configName}`)} to a value with an invalid type ${picocolors_1.default.cyan(typeof def)}: it should be an object instead.`);
}
if (def.isDefinedByPeerDependency)
return;
// env
let configEnv;
{
(0, utils_js_1.assert)(metaConfigDefinedAt); // We expect internal effects to return a valid meta value
if (!('env' in def)) {
(0, utils_js_1.assertUsage)(false, `${metaConfigDefinedAt} doesn't set ${picocolors_1.default.cyan(`meta.${configName}.env`)} but it's required.`);
}
configEnv = getConfigEnvValue(def.env, `${metaConfigDefinedAt} sets ${picocolors_1.default.cyan(`meta.${configName}.env`)} to`);
// Overwrite deprecated value with valid value
// TODO/v1-release: remove once support for the deprecated values is removed
if (typeof def.env === 'string')
def.env = configEnv;
}
// effect
if ('effect' in def) {
if (!(0, utils_js_1.hasProp)(def, 'effect', 'function')) {
(0, utils_js_1.assert)(metaConfigDefinedAt); // We expect internal effects to return a valid meta value
(0, utils_js_1.assertUsage)(false, `${metaConfigDefinedAt} sets ${picocolors_1.default.cyan(`meta.${configName}.effect`)} to an invalid type ${picocolors_1.default.cyan(typeof def.effect)}: it should be a function instead`);
}
if (!configEnv.config) {
(0, utils_js_1.assert)(metaConfigDefinedAt); // We expect internal effects to return a valid meta value
(0, utils_js_1.assertUsage)(false, `${metaConfigDefinedAt} sets ${picocolors_1.default.cyan(`meta.${configName}.effect`)} but it's only supported if meta.${configName}.env has ${picocolors_1.default.cyan('{ config: true }')} (but it's ${picocolors_1.default.cyan(JSON.stringify(configEnv))} instead)`);
}
}
});
}
// Test: https://github.com/vikejs/vike/blob/441a37c4c1a3b07bb8f6efb1d1f7be297a53974a/test/playground/vite.config.ts#L39
function applyEffectsConfVal(configValueSources, configDefinitions, plusFilesAll) {
(0, utils_js_1.objectEntries)(configDefinitions).forEach(([configNameEffect, configDefEffect]) => {
const sourceEffect = configValueSources[configNameEffect]?.[0];
if (!sourceEffect)
return;
const effect = runEffect(configNameEffect, configDefEffect, sourceEffect);
if (!effect)
return;
const configModFromEffect = effect;
applyEffectConfVal(configModFromEffect, sourceEffect, configValueSources, configNameEffect, configDefEffect, configDefinitions, plusFilesAll);
});
}
// Test: https://github.com/vikejs/vike/blob/441a37c4c1a3b07bb8f6efb1d1f7be297a53974a/test/playground/pages/config-meta/effect/e2e-test.ts#L16
function applyEffectsMetaEnv(configValueSources, configDefinitions) {
(0, utils_js_1.objectEntries)(configDefinitions).forEach(([configNameEffect, configDefEffect]) => {
const sourceEffect = configValueSources[configNameEffect]?.[0];
if (!sourceEffect)
return;
const effect = runEffect(configNameEffect, configDefEffect, sourceEffect);
if (!effect)
return;
const configModFromEffect = effect;
applyEffectMetaEnv(configModFromEffect, configValueSources, configDefEffect);
});
}
function runEffect(configName, configDef, source) {
if (!configDef.effect)
return null;
// The value needs to be loaded at config time, that's why we only support effect for configs that are config-only for now.
(0, utils_js_1.assertUsage)(configDef.env.config, [
`Cannot add meta.effect to ${picocolors_1.default.cyan(configName)} because its meta.env is ${picocolors_1.default.cyan(JSON.stringify(configDef.env))} but an effect can only be added to a config that has a meta.env with ${picocolors_1.default.cyan('{ config: true }')}.`,
].join(' '));
(0, utils_js_1.assert)(source.valueIsLoaded);
// Call effect
const configModFromEffect = configDef.effect({
configValue: source.value,
configDefinedAt: (0, getConfigDefinedAt_js_1.getConfigDefinedAt)('Config', configName, source.definedAt),
});
if (!configModFromEffect)
return null;
return configModFromEffect;
}
function applyEffectConfVal(configModFromEffect, sourceEffect, configValueSources, configNameEffect, configDefEffect, configDefinitions, plusFilesAll) {
(0, utils_js_1.objectEntries)(configModFromEffect).forEach(([configNameTarget, configValue]) => {
if (configNameTarget === 'meta')
return;
const configDef = configDefinitions[configNameTarget];
(0, utils_js_1.assert)(configDef);
(0, utils_js_1.assert)(configDefEffect._userEffectDefinedAtFilePath);
const configValueSource = {
definedAt: configDefEffect._userEffectDefinedAtFilePath,
plusFile: sourceEffect.plusFile,
locationId: sourceEffect.locationId,
configEnv: configDef.env,
valueIsLoadedWithImport: false,
valueIsDefinedByPlusValueFile: false,
valueIsLoaded: true,
value: configValue,
};
(0, utils_js_1.assert)(sourceEffect.valueIsLoaded);
const isValueGlobalSource = resolveIsGlobalValue(configDefEffect.global, sourceEffect, plusFilesAll);
const isValueGlobalTarget = resolveIsGlobalValue(configDef.global, configValueSource, plusFilesAll);
const isGlobalHumanReadable = (isGlobal) => `${isGlobal ? 'non-' : ''}global`;
// The error message make it sound like it's an inherent limitation, it actually isn't (both ways can make senses).
(0, utils_js_1.assertUsage)(isValueGlobalSource === isValueGlobalTarget, `The configuration ${picocolors_1.default.cyan(configNameEffect)} is set to ${picocolors_1.default.cyan(JSON.stringify(sourceEffect.value))} which is considered ${isGlobalHumanReadable(isValueGlobalSource)}. However, it has a meta.effect that sets the configuration ${picocolors_1.default.cyan(configNameTarget)} to ${picocolors_1.default.cyan(JSON.stringify(configValue))} which is considered ${isGlobalHumanReadable(isValueGlobalTarget)}. This is contradictory: make sure the values are either both non-global or both global.`);
configValueSources[configNameTarget] ?? (configValueSources[configNameTarget] = []);
configValueSources[configNameTarget].push(configValueSource);
});
}
function applyEffectMetaEnv(configModFromEffect, configValueSources, configDefEffect) {
const notSupported = `${picocolors_1.default.cyan('meta.effect')} currently only supports setting the value of a config, or modifying the ${picocolors_1.default.cyan('meta.env')} of a config.`;
(0, utils_js_1.objectEntries)(configModFromEffect).forEach(([configNameTarget, configValue]) => {
if (configNameTarget !== 'meta')
return;
let configDefinedAt;
if (configDefEffect._userEffectDefinedAtFilePath) {
configDefinedAt = (0, getConfigDefinedAt_js_1.getConfigDefinedAt)('Config', configNameTarget, configDefEffect._userEffectDefinedAtFilePath);
}
else {
configDefinedAt = null;
}
assertMetaUsage(configValue, configDefinedAt);
(0, utils_js_1.objectEntries)(configValue).forEach(([configTargetName, configTargetDef]) => {
(0, utils_js_1.assert)(!('isDefinedByPeerDependency' in configTargetDef));
{
const keys = Object.keys(configTargetDef);
(0, utils_js_1.assertUsage)(keys.includes('env'), notSupported);
(0, utils_js_1.assertUsage)(keys.length === 1, notSupported);
}
const envOverridden = configTargetDef.env;
const sources = configValueSources[configTargetName];
sources?.forEach((configValueSource) => {
// Apply effect
configValueSource.configEnv = envOverridden;
});
});
});
}
function getComputed(configValueSources, configDefinitions) {
const configValuesComputed = {};
(0, utils_js_1.objectEntries)(configDefinitions).forEach(([configName, configDef]) => {
if (!configDef._computed)
return;
const value = configDef._computed(configValueSources);
if (value === undefined)
return;
configValuesComputed[configName] = {
value,
configEnv: configDef.env,
};
});
return configValuesComputed;
}
// Show error message upon unknown config
function assertKnownConfigs(configDefinitionsResolved) {
(0, utils_js_1.objectEntries)(configDefinitionsResolved.configDefinitionsLocal).forEach(([_locationId, { configNamesKnownLocal, plusFiles }]) => {
plusFiles.forEach((plusFile) => {
const configNames = getConfigNamesSetByPlusFile(plusFile);
configNames.forEach((configName) => {
const { locationId } = plusFile;
const sourceName = plusFile.filePath.filePathToShowToUser;
assertKnownConfig(configName, configNamesKnownLocal, configDefinitionsResolved, locationId, true, sourceName, false);
});
});
});
}
function assertKnownConfig(configName, configNamesKnownRelevant, configDefinitionsResolved, locationId, isPlusFile, sourceName, exitOnError) {
const { configNamesKnownAll } = configDefinitionsResolved;
if (configNamesKnownRelevant.includes(configName)) {
(0, utils_js_1.assert)(configNamesKnownAll.includes(configName));
return;
}
const configNameColored = picocolors_1.default.cyan(configName);
// Inheritance issue: config is known but isn't defined at `locationId`
if (configNamesKnownAll.includes(configName)) {
(0, utils_js_1.assertUsage)(false, `${sourceName} sets the value of the config ${configNameColored} which is a custom config that is defined with ${picocolors_1.default.underline('https://vike.dev/meta')} at a path that doesn't apply to ${locationId} — see ${picocolors_1.default.underline('https://vike.dev/config#inheritance')}`, { exitOnError });
}
const errMsg = isPlusFile
? `${sourceName} sets an unknown config ${configNameColored}`
: `${sourceName} sets an unknown Vike config, see ${picocolors_1.default.underline('https://vike.dev/cli')} for the list of CLI options`;
(0, utils_js_1.assert)(errMsg.includes(configName));
// Missing vike-{react,vue,solid} installation
{
const ui = ['vike-react', 'vike-vue', 'vike-solid'];
const knownVikeExntensionConfigs = {
description: ui,
favicon: ui,
Head: ui,
Layout: ui,
onCreateApp: ['vike-vue'],
title: ui,
ssr: ui,
stream: ui,
Wrapper: ui,
};
if (configName in knownVikeExntensionConfigs) {
const requiredVikeExtension = (0, utils_js_1.joinEnglish)(knownVikeExntensionConfigs[configName].map((e) => picocolors_1.default.bold(e)), 'or');
const errMsgEnhanced = `${errMsg}. If you want to use the configuration ${configNameColored} documented at ${picocolors_1.default.underline(`https://vike.dev/${configName}`)} then make sure to install ${requiredVikeExtension}. (Alternatively, you can define ${configNameColored} yourself by using ${picocolors_1.default.cyan('meta')}, see ${picocolors_1.default.underline('https://vike.dev/meta')} for more information.)`;
(0, utils_js_1.assertUsage)(false, errMsgEnhanced, { exitOnError });
}
}
// Similarity hint
let configNameSimilar = null;
if (configName === 'page') {
configNameSimila