vike 0.4.259 → 0.4.260

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (83) hide show
  1. package/AGENTS.md +3 -0
  2. package/README.md +2 -0
  3. package/dist/client/runtime-client-routing/renderPageClient.js +1 -1
  4. package/dist/client/shared/getJsonSerializedInHtml.js +1 -10
  5. package/dist/node/api/build.js +6 -6
  6. package/dist/node/api/dev.d.ts +2 -10
  7. package/dist/node/api/dev.js +5 -52
  8. package/dist/node/api/prepareViteApiCall.d.ts +1 -1
  9. package/dist/node/api/prepareViteApiCall.js +2 -2
  10. package/dist/node/api/prerender.js +2 -2
  11. package/dist/node/api/preview.d.ts +2 -2
  12. package/dist/node/api/preview.js +15 -16
  13. package/dist/node/api/{resolveViteConfigFromUser.d.ts → resolveViteConfigUser.d.ts} +5 -5
  14. package/dist/node/api/{resolveViteConfigFromUser.js → resolveViteConfigUser.js} +99 -68
  15. package/dist/node/api/startupLog.d.ts +4 -0
  16. package/dist/node/api/{getStartupLogFirstLine.js → startupLog.js} +15 -1
  17. package/dist/node/api/types.d.ts +9 -0
  18. package/dist/node/cli/entry.js +1 -5
  19. package/dist/node/cli/parseCli.js +25 -11
  20. package/dist/node/createDevMiddleware.js +2 -2
  21. package/dist/node/prerender/runPrerender.d.ts +8 -2
  22. package/dist/node/prerender/runPrerender.js +3 -1
  23. package/dist/node/prerender/runPrerenderEntry.js +2 -2
  24. package/dist/node/vite/index.js +2 -2
  25. package/dist/node/vite/plugins/build/handleAssetsManifest.d.ts +2 -6
  26. package/dist/node/vite/plugins/build/handleAssetsManifest.js +16 -9
  27. package/dist/node/vite/plugins/build/pluginBuildApp.js +10 -5
  28. package/dist/node/vite/plugins/build/pluginBuildConfig.d.ts +6 -1
  29. package/dist/node/vite/plugins/build/pluginBuildConfig.js +13 -11
  30. package/dist/node/vite/plugins/build/pluginDistFileNames.js +1 -6
  31. package/dist/node/vite/plugins/build/pluginModuleBanner.js +2 -4
  32. package/dist/node/vite/plugins/pluginCommon.d.ts +1 -1
  33. package/dist/node/vite/plugins/pluginCommon.js +15 -7
  34. package/dist/node/vite/plugins/pluginDev/optimizeDeps.js +67 -31
  35. package/dist/node/vite/plugins/pluginStripPointerImportAttribute.js +31 -10
  36. package/dist/node/vite/plugins/pluginVirtualFiles/generateVirtualFilePageEntry.js +5 -7
  37. package/dist/node/vite/plugins/pluginVirtualFiles/getConfigValueSourcesRelevant.d.ts +2 -2
  38. package/dist/node/vite/shared/addSsrMiddleware.js +1 -1
  39. package/dist/node/vite/shared/isVite8OrAbove.d.ts +4 -0
  40. package/dist/node/vite/shared/isVite8OrAbove.js +9 -0
  41. package/dist/node/vite/shared/isViteCli.d.ts +9 -4
  42. package/dist/node/vite/shared/isViteCli.js +24 -4
  43. package/dist/node/vite/shared/loggerDev.js +6 -5
  44. package/dist/node/vite/shared/resolveVikeConfigInternal/assertExtensions.d.ts +2 -0
  45. package/dist/node/vite/shared/resolveVikeConfigInternal/assertExtensions.js +6 -5
  46. package/dist/node/vite/shared/resolveVikeConfigInternal/crawlPlusFilePaths.js +1 -0
  47. package/dist/node/vite/shared/resolveVikeConfigInternal/getPlusFilesByLocationId.d.ts +3 -1
  48. package/dist/node/vite/shared/resolveVikeConfigInternal/getPlusFilesByLocationId.js +15 -13
  49. package/dist/node/vite/shared/resolveVikeConfigInternal/metaBuiltIn.d.ts +15 -15
  50. package/dist/node/vite/shared/resolveVikeConfigInternal/metaBuiltIn.js +15 -0
  51. package/dist/node/vite/shared/resolveVikeConfigInternal.d.ts +18 -3
  52. package/dist/node/vite/shared/resolveVikeConfigInternal.js +213 -72
  53. package/dist/server/runtime/globalContext.js +4 -1
  54. package/dist/server/runtime/renderPageServer/createPageContextServer.d.ts +12 -3
  55. package/dist/server/runtime/renderPageServer/html/injectAssets/getHtmlTags.js +2 -3
  56. package/dist/server/runtime/renderPageServer/html/serializeContext.js +5 -10
  57. package/dist/server/runtime/renderPageServer/loadPageConfigsLazyServerSide.d.ts +8 -2
  58. package/dist/server/runtime/renderPageServer/renderPageServerAfterRoute.d.ts +32 -8
  59. package/dist/server/runtime/renderPageServer/renderPageServerAfterRoute.js +1 -1
  60. package/dist/server/runtime/renderPageServer.d.ts +8 -2
  61. package/dist/server/runtime/renderPageServer.js +6 -4
  62. package/dist/shared-server-client/page-configs/serialize/serializeConfigValues.d.ts +3 -3
  63. package/dist/shared-server-client/page-configs/serialize/serializeConfigValues.js +8 -1
  64. package/dist/types/Config.d.ts +34 -6
  65. package/dist/types/PageConfig.d.ts +11 -9
  66. package/dist/types/PageContext.d.ts +8 -3
  67. package/dist/utils/PROJECT_VERSION.d.ts +1 -1
  68. package/dist/utils/PROJECT_VERSION.js +1 -1
  69. package/dist/utils/assert.d.ts +2 -0
  70. package/dist/utils/assert.js +8 -8
  71. package/dist/utils/debug.d.ts +1 -1
  72. package/dist/utils/debug.js +1 -0
  73. package/dist/utils/getViteRPC.js +1 -1
  74. package/dist/utils/objectMap.d.ts +2 -0
  75. package/dist/utils/objectMap.js +4 -0
  76. package/dist/utils/parseNpmPackage.js +4 -1
  77. package/dist/utils/pick.d.ts +1 -1
  78. package/package.json +13 -15
  79. package/dist/node/api/getStartupLogFirstLine.d.ts +0 -7
  80. package/dist/server/__internal/vite.d.ts +0 -2
  81. package/dist/server/__internal/vite.js +0 -2
  82. package/dist/server/runtime/renderPageServer/html/injectAssets/sanitizeJson.d.ts +0 -4
  83. package/dist/server/runtime/renderPageServer/html/injectAssets/sanitizeJson.js +0 -7
@@ -9,10 +9,11 @@ export { isV1Design };
9
9
  export { getConfVal };
10
10
  export { getConfigDefinitionOptional };
11
11
  export { getVikeConfigFromCliOrEnv };
12
+ export { EARLY_SETTINGS };
12
13
  export type { VikeConfigInternal };
13
14
  export type { PageConfigBuildTimeBeforeComputed };
14
- import type { PageConfigGlobalBuildTime, PageConfigBuildTime } from '../../../types/PageConfig.js';
15
- import { type ConfigDefinitionsInternal, type ConfigDefinitionInternal } from './resolveVikeConfigInternal/metaBuiltIn.js';
15
+ import type { PageConfigGlobalBuildTime, ConfigEnv, PageConfigBuildTime, DefinedAtFilePath } from '../../../types/PageConfig.js';
16
+ import { type ConfigDefinitionsInternal } from './resolveVikeConfigInternal/metaBuiltIn.js';
16
17
  import { type GlobalConfigPublic } from '../../../shared-server-client/page-configs/resolveVikeConfigPublic.js';
17
18
  import { type PlusFile } from './resolveVikeConfigInternal/getPlusFilesByLocationId.js';
18
19
  import type { PrerenderContextPublic } from '../../prerender/runPrerender.js';
@@ -62,8 +63,22 @@ declare function getVikeConfigFromCliOrEnv(): {
62
63
  configFromCliOptions: import("../../cli/parseCli.js").CliOptions | null;
63
64
  configFromEnvVar: Record<string, unknown> | null;
64
65
  };
66
+ declare const EARLY_SETTINGS: readonly ["root", "mode"];
65
67
  type PageConfigBuildTimeBeforeComputed = Omit<PageConfigBuildTime, 'configValuesComputed'>;
66
- declare function getConfigDefinitionOptional(configDefinitions: ConfigDefinitionsInternal, configName: string): ConfigDefinitionInternal | null;
68
+ declare function getConfigDefinitionOptional(configDefinitions: ConfigDefinitionsInternal, configName: string): {
69
+ env: ConfigEnv;
70
+ cumulative?: boolean;
71
+ effect?: import("./resolveVikeConfigInternal/metaBuiltIn.js").ConfigEffect;
72
+ eager?: boolean;
73
+ global?: boolean | ((value: unknown, moreInfo: {
74
+ isGlobalLocation: boolean;
75
+ }) => boolean);
76
+ vite?: boolean;
77
+ _computed?: (pageConfig: PageConfigBuildTimeBeforeComputed) => unknown;
78
+ _valueIsFilePath?: true;
79
+ _userEffectDefinedAtFilePath?: DefinedAtFilePath;
80
+ isDefinedByPeerDependency?: undefined;
81
+ } | null;
67
82
  declare function getConfVal(plusFile: PlusFile, configName: string): null | {
68
83
  value: unknown;
69
84
  valueIsLoaded: true;
@@ -10,14 +10,13 @@ export { isV1Design };
10
10
  export { getConfVal };
11
11
  export { getConfigDefinitionOptional };
12
12
  export { getVikeConfigFromCliOrEnv };
13
+ export { EARLY_SETTINGS };
13
14
  import { deepEqual } from '../../../utils/deepEqual.js';
14
15
  import { assertKeys } from '../../../utils/assertKeys.js';
15
16
  import { assertIsNotProductionRuntime } from '../../../utils/assertSetup.js';
16
17
  import { getMostSimilar } from '../../../utils/getMostSimilar.js';
17
- import { includes } from '../../../utils/includes.js';
18
18
  import { objectEntries } from '../../../utils/objectEntries.js';
19
19
  import { objectFromEntries } from '../../../utils/objectFromEntries.js';
20
- import { objectKeys } from '../../../utils/objectKeys.js';
21
20
  import { makeLast } from '../../../utils/sorter.js';
22
21
  import { assert, assertUsage, assertWarning } from '../../../utils/assert.js';
23
22
  import { checkType } from '../../../utils/checkType.js';
@@ -41,11 +40,14 @@ import pc from '@brillout/picocolors';
41
40
  import { getConfigDefinedAt, getDefinedByString, } from '../../../shared-server-client/page-configs/getConfigDefinedAt.js';
42
41
  import { loadPointerImport, loadValueFile } from './resolveVikeConfigInternal/loadFileAtConfigTime.js';
43
42
  import { resolvePointerImport } from './resolveVikeConfigInternal/resolvePointerImport.js';
43
+ import { parsePointerImportData } from './resolveVikeConfigInternal/pointerImports.js';
44
44
  import { getFilePathResolved } from './getFilePath.js';
45
45
  import { getConfigValueBuildTime } from '../../../shared-server-client/page-configs/getConfigValueBuildTime.js';
46
46
  import { resolveGlobalConfigPublic, } from '../../../shared-server-client/page-configs/resolveVikeConfigPublic.js';
47
47
  import { getConfigValuesBase, isJsonValue, } from '../../../shared-server-client/page-configs/serialize/serializeConfigValues.js';
48
- import { getPlusFilesByLocationId, } from './resolveVikeConfigInternal/getPlusFilesByLocationId.js';
48
+ import { getPlusFilesByLocationId, getPlusFileFromConfigFile, } from './resolveVikeConfigInternal/getPlusFilesByLocationId.js';
49
+ import { assertRouteString } from '../../../shared-server-client/route/resolveRouteString.js';
50
+ import { getExtensionName } from './resolveVikeConfigInternal/assertExtensions.js';
49
51
  import { getEnvVarObject } from './getEnvVarObject.js';
50
52
  import { getVikeApiOperation } from '../../../shared-server-node/api-context.js';
51
53
  import { getCliOptions } from '../../cli/context.js';
@@ -221,7 +223,8 @@ async function resolveVikeConfigInternal(userRootDir, vikeVitePluginOptions, esb
221
223
  globalObject.isV1Design_ = pageConfigs.length > 0;
222
224
  // Backwards compatibility for vike(options) in vite.config.js
223
225
  temp_interopVikeVitePlugin(pageConfigGlobal, vikeVitePluginOptions, userRootDir);
224
- setCliAndApiOptions(pageConfigGlobal, configDefinitionsResolved);
226
+ setCliAndApiOptions(pageConfigGlobal, pageConfigs, configDefinitionsResolved);
227
+ warnEarlySettingsInConfigFile(pageConfigGlobal);
225
228
  const globalConfigPublic = resolveGlobalConfig(pageConfigGlobal, pageConfigs);
226
229
  const prerenderContext = await resolvePrerenderContext({
227
230
  config: globalConfigPublic.config,
@@ -254,8 +257,7 @@ async function resolveConfigDefinitions(plusFilesByLocationId, userRootDir, esbu
254
257
  // We use `plusFilesByLocationId` in order to allow non-global Vike extensions to create global configs, and to set the value of global configs such as `+vite` (enabling Vike extensions to add Vite plugins).
255
258
  plusFilesByLocationIdOrdered, (configDef) => !!configDef.global);
256
259
  await loadCustomConfigBuildTimeFiles(plusFilesByLocationId, configDefinitionsGlobal, userRootDir, esbuildCache);
257
- const configDefinitionsAll = getConfigDefinitions(Object.values(plusFilesByLocationId).flat());
258
- const configNamesKnownAll = Object.keys(configDefinitionsAll);
260
+ const configNamesKnownAll = getConfigNames(Object.values(plusFilesByLocationId).flat());
259
261
  const configNamesKnownGlobal = Object.keys(configDefinitionsGlobal);
260
262
  assert(configNamesKnownGlobal.every((configName) => configNamesKnownAll.includes(configName)));
261
263
  const configDefinitionsLocal = {};
@@ -265,9 +267,13 @@ async function resolveConfigDefinitions(plusFilesByLocationId, userRootDir, esbu
265
267
  .map(([, plusFiles]) => plusFiles)
266
268
  .flat()
267
269
  .sort((plusFile1, plusFile2) => sortAfterInheritanceOrderPage(plusFile1, plusFile2, locationIdPage, null));
268
- const configDefinitions = getConfigDefinitions(plusFilesRelevant, (configDef) => configDef.global !== true);
270
+ const { configDefinitions, peerDependencyConfigNames } = collectConfigDefinitions(plusFilesRelevant, (configDef) => configDef.global !== true);
269
271
  await loadCustomConfigBuildTimeFiles(plusFiles, configDefinitions, userRootDir, esbuildCache);
270
- const configNamesKnownLocal = unique([...Object.keys(configDefinitions), ...configNamesKnownGlobal]);
272
+ const configNamesKnownLocal = unique([
273
+ ...Object.keys(configDefinitions),
274
+ ...peerDependencyConfigNames,
275
+ ...configNamesKnownGlobal,
276
+ ]);
271
277
  assert(configNamesKnownLocal.every((configName) => configNamesKnownAll.includes(configName)));
272
278
  configDefinitionsLocal[locationIdPage] = {
273
279
  configDefinitions,
@@ -279,11 +285,10 @@ async function resolveConfigDefinitions(plusFilesByLocationId, userRootDir, esbu
279
285
  const configDefinitionsResolved = {
280
286
  configDefinitionsGlobal,
281
287
  configDefinitionsLocal,
282
- configDefinitionsAll,
283
288
  configNamesKnownAll,
284
289
  configNamesKnownGlobal,
285
290
  };
286
- assertKnownConfigs(configDefinitionsResolved);
291
+ warnUnknownConfigs(configDefinitionsResolved);
287
292
  return configDefinitionsResolved;
288
293
  }
289
294
  // Load value files (with `env.config===true`) of *custom* configs.
@@ -319,35 +324,94 @@ function getPageConfigsBuildTime(configDefinitionsResolved, plusFilesByLocationI
319
324
  assertPageConfigGlobal(pageConfigGlobal, plusFilesByLocationId);
320
325
  const pageConfigs = objectEntries(configDefinitionsResolved.configDefinitionsLocal)
321
326
  .filter(([_locationId, { plusFiles }]) => isDefiningPage(plusFiles))
322
- .map(([locationId, { configDefinitions, plusFilesRelevant }]) => {
323
- const configDefinitionsLocal = configDefinitions;
324
- const configValueSources = {};
325
- objectEntries(configDefinitionsLocal)
326
- .filter(([_configName, configDef]) => configDef.global !== true)
327
- .forEach(([configName, configDef]) => {
328
- const sources = resolveConfigValueSources(configName, configDef, plusFilesRelevant, userRootDir, false, plusFilesByLocationId);
329
- if (sources.length === 0)
330
- return;
331
- configValueSources[configName] = sources;
332
- });
333
- const pageConfigRoute = determineRouteFilesystem(locationId, configValueSources);
334
- applyEffects(configValueSources, configDefinitionsLocal, plusFilesByLocationId);
335
- sortConfigValueSources(configValueSources, locationId);
336
- const pageConfig = {
337
- pageId: locationId,
338
- ...pageConfigRoute,
339
- configDefinitions: configDefinitionsLocal,
340
- plusFiles: plusFilesRelevant,
341
- configValueSources,
342
- };
343
- const configValuesComputed = getComputed(pageConfig);
344
- objectAssign(pageConfig, { configValuesComputed });
345
- checkType(pageConfig);
346
- return pageConfig;
347
- });
327
+ .map(([locationId, { configDefinitions, plusFilesRelevant }]) => resolvePageConfigBuildTime(locationId, locationId, plusFilesRelevant, configDefinitions, plusFilesByLocationId, userRootDir));
328
+ // Pages defined programmatically via +pages
329
+ pageConfigs.push(...getProgrammaticPageConfigs(configDefinitionsResolved, plusFilesByLocationId, userRootDir));
348
330
  assertPageConfigs(pageConfigs);
349
331
  return { pageConfigs, pageConfigGlobal };
350
332
  }
333
+ function resolvePageConfigBuildTime(pageId, locationId, plusFilesRelevant, configDefinitionsLocal, plusFilesByLocationId, userRootDir) {
334
+ const configValueSources = {};
335
+ objectEntries(configDefinitionsLocal)
336
+ .filter(([_configName, configDef]) => configDef.global !== true)
337
+ .forEach(([configName, configDef]) => {
338
+ const sources = resolveConfigValueSources(configName, configDef, plusFilesRelevant, userRootDir, false, plusFilesByLocationId);
339
+ if (sources.length === 0)
340
+ return;
341
+ configValueSources[configName] = sources;
342
+ });
343
+ const pageConfigRoute = determineRouteFilesystem(locationId, configValueSources);
344
+ applyEffects(configValueSources, configDefinitionsLocal, plusFilesByLocationId);
345
+ sortConfigValueSources(configValueSources, locationId);
346
+ const pageConfig = {
347
+ pageId,
348
+ ...pageConfigRoute,
349
+ configDefinitions: configDefinitionsLocal,
350
+ plusFiles: plusFilesRelevant,
351
+ configValueSources,
352
+ };
353
+ const configValuesComputed = getComputed(pageConfig);
354
+ objectAssign(pageConfig, { configValuesComputed });
355
+ checkType(pageConfig);
356
+ return pageConfig;
357
+ }
358
+ /**
359
+ * Get the pages defined programmatically via +pages
360
+ */
361
+ function getProgrammaticPageConfigs(configDefinitionsResolved, plusFilesByLocationId, userRootDir) {
362
+ const pageConfigs = [];
363
+ const entryIndexByLocationId = {};
364
+ // +pages can be set in a +config.js or +pages.js file — getConfVal() handles both
365
+ const plusFilesDefiningPages = Object.values(plusFilesByLocationId)
366
+ .flat()
367
+ .filter((plusFile) => {
368
+ const confVal = getConfVal(plusFile, 'pages');
369
+ return !!confVal && confVal.valueIsLoaded;
370
+ });
371
+ plusFilesDefiningPages.forEach((plusFileDefiningPages) => {
372
+ const locationIdAnchor = plusFileDefiningPages.locationId;
373
+ const local = configDefinitionsResolved.configDefinitionsLocal[locationIdAnchor];
374
+ assert(local);
375
+ const confVal = getConfVal(plusFileDefiningPages, 'pages');
376
+ assert(confVal?.valueIsLoaded);
377
+ const pages = confVal.value;
378
+ const definedAt = plusFileDefiningPages.filePath.filePathToShowToUser;
379
+ assertUsage(Array.isArray(pages), `${definedAt} sets ${pc.cyan('+pages')} to an invalid value: it should be an array`);
380
+ pages.forEach((entry, i) => {
381
+ const definedAtEntry = `${definedAt} > ${pc.cyan(`pages[${i}]`)}`;
382
+ assertUsage(isObject(entry), `${definedAtEntry} should be an object`);
383
+ assertUsage('route' in entry, `${definedAtEntry} should set ${pc.cyan('+route')}`);
384
+ // A Route Function can't be inlined (a function can't be serialized to the runtime): it must be a pointer import
385
+ assertUsage(!isCallable(entry.route), `${definedAtEntry} sets ${pc.cyan('+route')} to a function, but a Route Function can't be inlined — define the Route Function in a separate file and import it with ${pc.cyan("{ type: 'vike:pointer' }")} instead (so that Vike can load it at runtime)`);
386
+ assertUsage(typeof entry.route === 'string', `${definedAtEntry} should set ${pc.cyan('+route')} to a Route String or Route Function`);
387
+ // A Route String is validated now; a Route Function (pointer import) is validated at runtime.
388
+ if (!parsePointerImportData(entry.route))
389
+ assertRouteString(entry.route, `${definedAtEntry} sets an invalid`);
390
+ assertUsage(!('extends' in entry), `${definedAtEntry} sets ${pc.cyan('+extends')} which isn't supported for programmatically defined pages`);
391
+ const index = entryIndexByLocationId[locationIdAnchor] ?? 0;
392
+ entryIndexByLocationId[locationIdAnchor] = index + 1;
393
+ const base = locationIdAnchor === '/' ? '' : locationIdAnchor;
394
+ // The page's virtual locationId = its inheritance position (a child of the +config.js that defines +pages) so that:
395
+ // - The page's own values (`entry`) only apply to that page
396
+ // - The page inherits as usual
397
+ const locationIdVirtual = `${base}/(+pages)/entry:${index}`;
398
+ // Hack: treat `entry` as the page's own +config.js — getPlusFileFromConfigFile() makes pointer imports (e.g. +Page) resolve to runtime imports.
399
+ const plusFileVirtual = getPlusFileFromConfigFile({ fileExports: { default: entry }, filePath: plusFileDefiningPages.filePath, extendsFilePaths: [] }, false, locationIdVirtual, userRootDir);
400
+ getConfigNamesSetByPlusFile(plusFileVirtual).forEach((configName) => {
401
+ // Warn on unknown configs
402
+ isUnknownConfig(configName, local.configNamesKnownLocal, configDefinitionsResolved, locationIdAnchor, true, definedAtEntry);
403
+ // A global config (e.g. +onBeforeRoute) applies app-wide and can't be set on a single page. (We check `global === true` so conditionally-global configs such as +prerender, whose `global` is a function, are still allowed per-page.)
404
+ const configDefGlobal = configDefinitionsResolved.configDefinitionsGlobal[configName];
405
+ assertUsage(configDefGlobal?.global !== true, `${definedAtEntry} sets the global config ${pc.cyan(`+${configName}`)} which can't be set on a single page — set it at a global config file instead`);
406
+ });
407
+ // The page's own values (`entry`) first (most-specific), then the inherited configs
408
+ const plusFilesRelevant = [plusFileVirtual, ...local.plusFilesRelevant];
409
+ // Perf: resolvePageConfigBuildTime() re-resolves the inherited config per entry — fine for typical +pages array length. But for very large +pages arrays this might be too slow: consider resolving the inherited part once and re-use.
410
+ pageConfigs.push(resolvePageConfigBuildTime(locationIdVirtual, locationIdVirtual, plusFilesRelevant, local.configDefinitions, plusFilesByLocationId, userRootDir));
411
+ });
412
+ });
413
+ return pageConfigs;
414
+ }
351
415
  function assertPageConfigGlobal(pageConfigGlobal, plusFilesByLocationId) {
352
416
  Object.entries(pageConfigGlobal.configValueSources).forEach(([configName, sources]) => {
353
417
  assertGlobalConfigLocation(configName, sources, plusFilesByLocationId, pageConfigGlobal.configDefinitions);
@@ -394,10 +458,18 @@ function assertGlobalConfigLocation(configName, sources, plusFilesByLocationId,
394
458
  });
395
459
  }
396
460
  function assertPageConfigs(pageConfigs) {
461
+ assertNoDuplicatePageIds(pageConfigs);
397
462
  pageConfigs.forEach((pageConfig) => {
398
463
  assertOnBeforeRenderEnv(pageConfig);
399
464
  });
400
465
  }
466
+ function assertNoDuplicatePageIds(pageConfigs) {
467
+ const seen = new Set();
468
+ pageConfigs.forEach(({ pageId }) => {
469
+ assert(!seen.has(pageId));
470
+ seen.add(pageId);
471
+ });
472
+ }
401
473
  function assertOnBeforeRenderEnv(pageConfig) {
402
474
  const onBeforeRenderConfig = pageConfig.configValueSources.onBeforeRender?.[0];
403
475
  if (!onBeforeRenderConfig)
@@ -461,32 +533,50 @@ function temp_interopVikeVitePlugin(pageConfigGlobal, vikeVitePluginOptions, use
461
533
  filePathAbsoluteUserRootDir: '/vite.config.js',
462
534
  }),
463
535
  fileExportPathToShowToUser: null,
464
- }));
536
+ }, pageConfigGlobal.configDefinitions));
465
537
  });
466
538
  }
467
- function setCliAndApiOptions(pageConfigGlobal, configDefinitionsResolved) {
539
+ function setCliAndApiOptions(pageConfigGlobal, pageConfigs, configDefinitionsResolved) {
468
540
  // Vike API — passed options [lowest precedence]
469
541
  const vikeApiOperation = getVikeApiOperation();
470
542
  if (vikeApiOperation?.options.vikeConfig) {
471
- addSources(vikeApiOperation.options.vikeConfig, { definedBy: 'api', operation: vikeApiOperation.operation }, false);
543
+ addSources(vikeApiOperation.options.vikeConfig, {
544
+ definedBy: 'api',
545
+ operation: vikeApiOperation.operation,
546
+ });
472
547
  }
473
548
  const { configFromCliOptions, configFromEnvVar } = getVikeConfigFromCliOrEnv();
474
549
  // Vike CLI options
475
550
  if (configFromCliOptions) {
476
- addSources(configFromCliOptions, { definedBy: 'cli' }, true);
551
+ addSources(configFromCliOptions, { definedBy: 'cli' });
477
552
  }
478
553
  // VIKE_CONFIG [highest precedence]
479
554
  if (configFromEnvVar) {
480
- addSources(configFromEnvVar, { definedBy: 'env' }, false);
555
+ addSources(configFromEnvVar, { definedBy: 'env' });
481
556
  }
482
557
  return;
483
- function addSources(configValues, definedBy, exitOnError) {
558
+ function addSources(configValues, definedBy) {
484
559
  Object.entries(configValues).forEach(([configName, value]) => {
485
560
  var _a;
486
561
  const sourceName = `The ${getDefinedByString(definedBy, configName)}`;
487
- assertKnownConfig(configName, configDefinitionsResolved.configNamesKnownGlobal, configDefinitionsResolved, '/', false, sourceName, exitOnError);
488
- const sources = ((_a = pageConfigGlobal.configValueSources)[configName] ?? (_a[configName] = []));
489
- sources.unshift(getSourceNonConfigFile(configName, value, definedBy));
562
+ const isUnknown = isUnknownConfig(configName, configDefinitionsResolved.configNamesKnownAll, configDefinitionsResolved, '/', false, sourceName);
563
+ if (isUnknown)
564
+ return;
565
+ if (configName in pageConfigGlobal.configDefinitions) {
566
+ const sources = ((_a = pageConfigGlobal.configValueSources)[configName] ?? (_a[configName] = []));
567
+ const source = getSourceNonConfigFile(configName, value, definedBy, pageConfigGlobal.configDefinitions);
568
+ sources.unshift(source);
569
+ return;
570
+ }
571
+ // Non-global config: inject into every page config that knows about it (highest precedence)
572
+ pageConfigs.forEach((pageConfig) => {
573
+ var _a;
574
+ if (!(configName in pageConfig.configDefinitions))
575
+ return;
576
+ const sources = ((_a = pageConfig.configValueSources)[configName] ?? (_a[configName] = []));
577
+ const source = getSourceNonConfigFile(configName, value, definedBy, pageConfig.configDefinitions);
578
+ sources.unshift(source);
579
+ });
490
580
  });
491
581
  }
492
582
  }
@@ -503,9 +593,9 @@ function getVikeConfigFromCliOrEnv() {
503
593
  configFromEnvVar,
504
594
  };
505
595
  }
506
- function getSourceNonConfigFile(configName, value, definedAt) {
507
- assert(includes(objectKeys(metaBuiltIn), configName));
508
- const configDef = metaBuiltIn[configName];
596
+ function getSourceNonConfigFile(configName, value, definedAt, configDefinitionsGlobal) {
597
+ const configDef = configDefinitionsGlobal[configName] ?? metaBuiltIn[configName];
598
+ assert(configDef);
509
599
  const source = {
510
600
  valueIsLoaded: true,
511
601
  value,
@@ -518,6 +608,30 @@ function getSourceNonConfigFile(configName, value, definedAt) {
518
608
  };
519
609
  return source;
520
610
  }
611
+ // Settings that must be resolved early before Vike crawls +config.js files:
612
+ // - They can't be defined inside +config.js files.
613
+ // - They must be set early (via Vike's CLI/API options or VIKE_CONFIG), and read early before Vike's main config resolution.
614
+ const EARLY_SETTINGS = [
615
+ // +root determines where Vike looks for +config.js files (so it can't be defined inside +config.js itself)
616
+ 'root',
617
+ // +mode affects which vite.config.js environment is loaded
618
+ 'mode',
619
+ ];
620
+ function warnEarlySettingsInConfigFile(pageConfigGlobal) {
621
+ for (const configName of EARLY_SETTINGS) {
622
+ const sources = pageConfigGlobal.configValueSources[configName];
623
+ if (!sources)
624
+ continue;
625
+ for (const source of sources) {
626
+ if (!source.plusFile) {
627
+ // `plusFile === null` => source comes from Vike CLI argument, Vike API option, or VIKE_CONFIG
628
+ continue;
629
+ }
630
+ const configDefinedAt = getConfigDefinedAt('Config', configName, source.definedAt);
631
+ assertWarning(false, `${configDefinedAt} has no effect: it can't be set from a ${pc.cyan('+')} file — see ${pc.underline(`https://vike.dev/${configName}`)}`, { onlyOnce: true });
632
+ }
633
+ }
634
+ }
521
635
  function sortConfigValueSources(configValueSources, locationIdPage) {
522
636
  Object.entries(configValueSources).forEach(([configName, sources]) => {
523
637
  sources
@@ -605,9 +719,10 @@ function sortPlusFilesSameLocationId(plusFile1, plusFile2, configName) {
605
719
  return 0;
606
720
  }
607
721
  function resolveConfigValueSources(configName, configDef, plusFilesRelevant, userRootDir, isGlobal, plusFilesByLocationId) {
608
- let sources = plusFilesRelevant
609
- .filter((plusFile) => isDefiningConfig(plusFile, configName))
610
- .flatMap((plusFile) => getConfigValueSources(configName, plusFile, configDef, userRootDir));
722
+ let plusFilesConfig = plusFilesRelevant.filter((plusFile) => isDefiningConfig(plusFile, configName));
723
+ // Make Vike extension installation idempotent. (Don't cumulate configs twice of an extension installed twice.) Since `plusFilesRelevant` is ordered by inheritance the occurrence closest to the page's locationId is the one kept.
724
+ plusFilesConfig = dedupeExtensions(plusFilesConfig);
725
+ let sources = plusFilesConfig.flatMap((plusFile) => getConfigValueSources(configName, plusFile, configDef, userRootDir));
611
726
  // Filter hydrid global-local configs
612
727
  if (!isCallable(configDef.global)) {
613
728
  // Already filtered
@@ -628,6 +743,20 @@ function resolveConfigValueSources(configName, configDef, plusFilesRelevant, use
628
743
  function isDefiningConfig(plusFile, configName) {
629
744
  return getConfigNamesSetByPlusFile(plusFile).includes(configName);
630
745
  }
746
+ function dedupeExtensions(plusFiles) {
747
+ const seen = new Set();
748
+ return plusFiles.filter((plusFile) => {
749
+ if (!plusFile.isConfigFile || !plusFile.isExtensionConfig)
750
+ return true;
751
+ const name = getExtensionName(plusFile);
752
+ // The extension's `name` is guaranteed by assertExtensionsConventions()
753
+ assert(name);
754
+ if (seen.has(name))
755
+ return false;
756
+ seen.add(name);
757
+ return true;
758
+ });
759
+ }
631
760
  function getConfigValueSources(configName, plusFile, configDef, userRootDir) {
632
761
  const confVal = getConfVal(plusFile, configName);
633
762
  assert(confVal);
@@ -752,6 +881,9 @@ function isDefiningPage(plusFiles) {
752
881
  function isDefiningPageConfig(configName) {
753
882
  return ['Page', 'route'].includes(configName);
754
883
  }
884
+ function isDefiningProgrammaticPages(plusFiles) {
885
+ return plusFiles.some((plusFile) => getConfigNamesSetByPlusFile(plusFile).includes('pages'));
886
+ }
755
887
  function resolveIsGlobalValue(configDefGlobal, source, plusFilesByLocationId) {
756
888
  assert(source.valueIsLoaded);
757
889
  let isGlobal;
@@ -772,8 +904,16 @@ function getConfigNamesSetByPlusFile(plusFile) {
772
904
  return Object.keys(plusFile.fileExportsByConfigName);
773
905
  }
774
906
  }
907
+ function getConfigNames(plusFilesRelevant, filter) {
908
+ const { configDefinitions, peerDependencyConfigNames } = collectConfigDefinitions(plusFilesRelevant, filter);
909
+ return [...Object.keys(configDefinitions), ...peerDependencyConfigNames];
910
+ }
775
911
  function getConfigDefinitions(plusFilesRelevant, filter) {
912
+ return collectConfigDefinitions(plusFilesRelevant, filter).configDefinitions;
913
+ }
914
+ function collectConfigDefinitions(plusFilesRelevant, filter) {
776
915
  let configDefinitions = { ...metaBuiltIn };
916
+ const peerDependencyConfigNames = new Set();
777
917
  // Add user-land meta configs
778
918
  plusFilesRelevant
779
919
  .slice()
@@ -799,14 +939,9 @@ function getConfigDefinitions(plusFilesRelevant, filter) {
799
939
  });
800
940
  objectEntries(meta).forEach(([configName, configDefinitionUserLand]) => {
801
941
  if ('isDefinedByPeerDependency' in configDefinitionUserLand) {
802
- /* vike-server@1.0.24 wrongfully sets `stream: { env: { config: true }, isDefinedByPeerDependency: true }`
803
- assert(deepEqual(Object.keys(configDefinitionUserLand), ['isDefinedByPeerDependency']))
804
- //*/
805
- if (!configDefinitions[configName]) {
806
- configDefinitions[configName] = {
807
- env: { client: false, server: false, config: false },
808
- };
809
- }
942
+ assert(deepEqual(Object.keys(configDefinitionUserLand), ['isDefinedByPeerDependency']));
943
+ if (!configDefinitions[configName])
944
+ peerDependencyConfigNames.add(configName);
810
945
  return;
811
946
  }
812
947
  // User can override an existing config definition
@@ -814,12 +949,13 @@ function getConfigDefinitions(plusFilesRelevant, filter) {
814
949
  ...configDefinitions[configName],
815
950
  ...configDefinitionUserLand,
816
951
  };
952
+ peerDependencyConfigNames.delete(configName);
817
953
  });
818
954
  });
819
955
  if (filter) {
820
956
  configDefinitions = Object.fromEntries(Object.entries(configDefinitions).filter(([_configName, configDef]) => filter(configDef)));
821
957
  }
822
- return configDefinitions;
958
+ return { configDefinitions, peerDependencyConfigNames };
823
959
  }
824
960
  function assertMetaUsage(metaVal, metaConfigDefinedAt) {
825
961
  if (!isObject(metaVal)) {
@@ -969,33 +1105,36 @@ function getComputed(pageConfig) {
969
1105
  });
970
1106
  return configValuesComputed;
971
1107
  }
972
- // Show error message upon unknown config
973
- function assertKnownConfigs(configDefinitionsResolved) {
1108
+ function warnUnknownConfigs(configDefinitionsResolved) {
974
1109
  objectEntries(configDefinitionsResolved.configDefinitionsLocal).forEach(([_locationId, { configNamesKnownLocal, plusFiles }]) => {
975
1110
  plusFiles.forEach((plusFile) => {
976
1111
  const configNames = getConfigNamesSetByPlusFile(plusFile);
977
1112
  configNames.forEach((configName) => {
978
1113
  const { locationId } = plusFile;
979
1114
  const sourceName = plusFile.filePath.filePathToShowToUser;
980
- assertKnownConfig(configName, configNamesKnownLocal, configDefinitionsResolved, locationId, true, sourceName, false);
1115
+ isUnknownConfig(configName, configNamesKnownLocal, configDefinitionsResolved, locationId, true, sourceName);
981
1116
  });
982
1117
  });
983
1118
  });
984
1119
  }
985
- function assertKnownConfig(configName, configNamesKnownRelevant, configDefinitionsResolved, locationId, isPlusFile, sourceName, exitOnError) {
1120
+ function isUnknownConfig(configName, configNamesKnownRelevant, configDefinitionsResolved, locationId, isPlusFile, sourceName) {
986
1121
  const { configNamesKnownAll } = configDefinitionsResolved;
987
1122
  if (configNamesKnownRelevant.includes(configName)) {
988
1123
  assert(configNamesKnownAll.includes(configName));
989
- return;
1124
+ return false;
990
1125
  }
991
1126
  const configNameColored = pc.cyan(configName);
1127
+ const warn = (msg) => {
1128
+ assertWarning(false, msg, { onlyOnce: true });
1129
+ return true;
1130
+ };
992
1131
  // Inheritance issue: config is known but isn't defined at `locationId`
993
1132
  if (configNamesKnownAll.includes(configName)) {
994
- assertUsage(false, `${sourceName} sets the value of the config ${configNameColored} which is a custom config that is defined with ${pc.underline('https://vike.dev/meta')} at a path that doesn't apply to ${locationId} — see ${pc.underline('https://vike.dev/config#inheritance')}`, { exitOnError });
1133
+ return warn(`${sourceName} sets the value of the config ${configNameColored} which is a custom config that is defined with ${pc.underline('https://vike.dev/meta')} at a path that doesn't apply to ${locationId} — see ${pc.underline('https://vike.dev/config#inheritance')}`);
995
1134
  }
996
1135
  const errMsg = isPlusFile
997
- ? `${sourceName} sets an unknown config ${configNameColored}`
998
- : `${sourceName} sets an unknown Vike config, see ${pc.underline('https://vike.dev/cli')} for the list of CLI options`;
1136
+ ? `${sourceName} sets an unknown config ${configNameColored} — for the list of built-in configs, see ${pc.underline('https://vike.dev/settings')} and ${pc.underline('https://vike.dev/hooks')}`
1137
+ : `${sourceName} sets an unknown Vike config see ${pc.underline('https://vike.dev/cli')} for the list of CLI options`;
999
1138
  assert(errMsg.includes(configName));
1000
1139
  // Missing vike-{react,vue,solid} installation
1001
1140
  {
@@ -1014,7 +1153,7 @@ function assertKnownConfig(configName, configNamesKnownRelevant, configDefinitio
1014
1153
  if (configName in knownVikeExntensionConfigs) {
1015
1154
  const requiredVikeExtension = joinEnglish(knownVikeExntensionConfigs[configName].map((e) => pc.bold(e)), 'or');
1016
1155
  const errMsgEnhanced = `${errMsg}. If you want to use the configuration ${configNameColored} documented at ${pc.underline(`https://vike.dev/${configName}`)} then make sure to install ${requiredVikeExtension}. (Alternatively, you can define ${configNameColored} yourself by using ${pc.cyan('meta')}, see ${pc.underline('https://vike.dev/meta')} for more information.)`;
1017
- assertUsage(false, errMsgEnhanced, { exitOnError });
1156
+ return warn(errMsgEnhanced);
1018
1157
  }
1019
1158
  }
1020
1159
  // Similarity hint
@@ -1031,9 +1170,9 @@ function assertKnownConfig(configName, configNamesKnownRelevant, configDefinitio
1031
1170
  if (configName === 'page') {
1032
1171
  errMsgEnhanced += ` (The name of the config ${pc.cyan('Page')} starts with a capital letter ${pc.cyan('P')} because it defines a UI component: a ubiquitous JavaScript convention is that the name of UI components start with a capital letter.)`;
1033
1172
  }
1034
- assertUsage(false, errMsgEnhanced, { exitOnError });
1173
+ return warn(errMsgEnhanced);
1035
1174
  }
1036
- assertUsage(false, errMsg, { exitOnError });
1175
+ return warn(errMsg);
1037
1176
  }
1038
1177
  function determineRouteFilesystem(locationId, configValueSources) {
1039
1178
  const configName = 'filesystemRoutingRoot';
@@ -1151,7 +1290,9 @@ function resolveConfigEnv(configEnv, filePath) {
1151
1290
  /** Whether configs defined in `locationId` apply to every page */
1152
1291
  function isGlobalLocation(locationId, plusFilesByLocationId) {
1153
1292
  const locationIdsPage = objectEntries(plusFilesByLocationId)
1154
- .filter(([_locationId, plusFiles]) => isDefiningPage(plusFiles))
1293
+ .filter(([_locationId, plusFiles]) => isDefiningPage(plusFiles) ||
1294
+ // Also add locationId if it defines +pages — programmatic pages are anchored there, so a config that doesn't cover them isn't global.
1295
+ isDefiningProgrammaticPages(plusFiles))
1155
1296
  .map(([locationId]) => locationId);
1156
1297
  return locationIdsPage.every((locId) => isInherited(locationId, locId));
1157
1298
  }
@@ -454,9 +454,12 @@ async function createGlobalContext(virtualFileExportsGlobalEntry) {
454
454
  debug('createGlobalContext() - done [sync]');
455
455
  // We define an early globalContext version synchronously, so that getGlobalContextSync() can be called early.
456
456
  // - Required by vike-vercel
457
- assert(globalObject.globalContext);
457
+ const globalContextIsSetEarly = !!globalObject.globalContext;
458
+ assertWarning(globalContextIsSetEarly, "globalContext isn't set early", { onlyOnce: false });
458
459
  const globalContext = await globalContextPromise;
459
460
  debug('createGlobalContext() - done [async]');
461
+ assert(globalContextIsSetEarly); // We deliberately assert after the `await` (otherwise it swallows promise rejections such as an assert() failing synchronously inside `async createGlobalContextShared()`).
462
+ assert(globalObject.globalContext);
460
463
  assertV1Design(
461
464
  // pageConfigs is PageConfigRuntime[] but assertV1Design() requires PageConfigBuildTime[]
462
465
  globalContext._pageConfigs.length > 0, globalContext._pageFilesAll);
@@ -24,7 +24,10 @@ declare function createPageContextServer(pageContextInit: PageContextInitInterna
24
24
  isPrerendering: boolean;
25
25
  _requestId: number;
26
26
  } & PageContextInit & {
27
- _reqDev?: import("http").IncomingMessage;
27
+ _nodeDev?: {
28
+ req: import("http").IncomingMessage;
29
+ res: import("http").ServerResponse;
30
+ };
28
31
  _reqWeb?: Request;
29
32
  } & {
30
33
  _globalContext: ({
@@ -100,7 +103,10 @@ declare function createPageContextServer(pageContextInit: PageContextInitInterna
100
103
  _baseServer: string;
101
104
  _baseAssets: string;
102
105
  _pageContextInit: PageContextInit & {
103
- _reqDev?: import("http").IncomingMessage;
106
+ _nodeDev?: {
107
+ req: import("http").IncomingMessage;
108
+ res: import("http").ServerResponse;
109
+ };
104
110
  _reqWeb?: Request;
105
111
  } & {
106
112
  headers?: Record<string, unknown>;
@@ -132,6 +138,9 @@ declare function createPageContextServerWithoutGlobalContext(pageContextInit: Pa
132
138
  isPrerendering: boolean;
133
139
  _requestId: number;
134
140
  } & PageContextInit & {
135
- _reqDev?: import("http").IncomingMessage;
141
+ _nodeDev?: {
142
+ req: import("http").IncomingMessage;
143
+ res: import("http").ServerResponse;
144
+ };
136
145
  _reqWeb?: Request;
137
146
  };
@@ -3,7 +3,6 @@ import { assert, assertWarning, assertUsage } from '../../../../../utils/assert.
3
3
  import { freezePartial } from '../../../../../utils/freezePartial.js';
4
4
  import { isObject } from '../../../../../utils/isObject.js';
5
5
  import { getGlobalContextClientSerialized, getPageContextClientSerialized, } from '../serializeContext.js';
6
- import { sanitizeJson } from './sanitizeJson.js';
7
6
  import { inferAssetTag, inferPreloadTag } from './inferHtmlTags.js';
8
7
  import { mergeScriptTags } from './mergeScriptTags.js';
9
8
  import { getPageConfig } from '../../../../../shared-server-client/page-configs/helpers.js';
@@ -186,7 +185,7 @@ function mergeScriptEntries(pageAssets, viteDevScript, pageContext) {
186
185
  return scriptEntry;
187
186
  }
188
187
  function getPageContextJsonScriptTag(pageContext) {
189
- const pageContextClientSerialized = sanitizeJson(getPageContextClientSerialized(pageContext, true));
188
+ const pageContextClientSerialized = getPageContextClientSerialized(pageContext, true);
190
189
  const nonceAttr = inferNonceAttr(pageContext);
191
190
  const htmlTag = `<script id="${htmlElementId_pageContext}" type="application/json"${nonceAttr}>${pageContextClientSerialized}</script>`;
192
191
  // Used by contra.com https://github.com/gajus
@@ -195,7 +194,7 @@ function getPageContextJsonScriptTag(pageContext) {
195
194
  return htmlTag;
196
195
  }
197
196
  function getGlobalContextJsonScriptTag(pageContext) {
198
- const globalContextClientSerialized = sanitizeJson(getGlobalContextClientSerialized(pageContext, true));
197
+ const globalContextClientSerialized = getGlobalContextClientSerialized(pageContext, true);
199
198
  const nonceAttr = inferNonceAttr(pageContext);
200
199
  const htmlTag = `<script id="${htmlElementId_globalContext}" type="application/json"${nonceAttr}>${globalContextClientSerialized}</script>`;
201
200
  return htmlTag;
@@ -115,16 +115,11 @@ function serializeValue(value, isHtmlJsonScript, varName) {
115
115
  return stringify(value, {
116
116
  forbidReactElements: true,
117
117
  valueName: varName,
118
- // Prevent Google from crawling URLs in JSON:
119
- // - https://github.com/vikejs/vike/pull/2603
120
- // - https://github.com/brillout/json-serializer/blob/38edbb9945de4938da1e65d6285ce1dd123a45ef/test/main.spec.ts#L44-L95
121
- replacer: !isHtmlJsonScript
122
- ? undefined
123
- : (_key, value) => {
124
- if (typeof value === 'string') {
125
- return { replacement: value.replaceAll('/', '\\/'), resolved: false };
126
- }
127
- },
118
+ htmlScriptSafe: {
119
+ // Could be set to `isHtmlJsonScript` but we always use `htmlScriptSafe.escapeScripts` to be extra safe
120
+ escapeScripts: true,
121
+ escapeURLs: isHtmlJsonScript,
122
+ },
128
123
  });
129
124
  }
130
125
  function getPassToClientPageContext(pageContext) {