UNPKG

vike

Version:

The Framework *You* Control - Next.js & Nuxt alternative for unprecedented flexibility and dependability.

968 lines 60.1 kB
"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