vike 0.4.259-commit-aa6fa94 → 0.4.259-commit-f6ecfb2

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.
@@ -28,6 +28,7 @@ assertIsNotProductionRuntime();
28
28
  const globalObject = getGlobalObject('getVikeConfig/crawlPlusFilePaths.ts', {
29
29
  gitIsNotUsable: false,
30
30
  });
31
+ // TODO/after-PR-merge rename crawlPlusFilePaths crawlPlusFiles
31
32
  async function crawlPlusFilePaths(userRootDir) {
32
33
  assertPosixPath(userRootDir);
33
34
  assertFilePathAbsoluteFilesystem(userRootDir);
@@ -1,10 +1,11 @@
1
1
  export { getPlusFilesByLocationId };
2
+ export { getPlusFileFromConfigFile };
2
3
  export type { PlusFileValue };
3
4
  export type { PlusFile };
4
5
  export type { PlusFilesByLocationId };
5
6
  import { type LocationId } from './filesystemRouting.js';
6
7
  import { type EsbuildCache } from './transpileAndExecuteFile.js';
7
- import { PointerImportLoaded } from './loadFileAtConfigTime.js';
8
+ import { type ConfigFile, PointerImportLoaded } from './loadFileAtConfigTime.js';
8
9
  import type { FilePathResolved } from '../../../../types/FilePath.js';
9
10
  import '../../assertEnvVite.js';
10
11
  type PlusFile = PlusFileConfig | PlusFileValue;
@@ -40,3 +41,4 @@ type PlusFileValue = PlusFileCommon & {
40
41
  };
41
42
  type PlusFilesByLocationId = Record<LocationId, PlusFile[]>;
42
43
  declare function getPlusFilesByLocationId(userRootDir: string, esbuildCache: EsbuildCache): Promise<PlusFilesByLocationId>;
44
+ declare function getPlusFileFromConfigFile(configFile: ConfigFile, isExtensionConfig: boolean, locationId: LocationId, userRootDir: string): PlusFileConfig;
@@ -1,4 +1,5 @@
1
1
  export { getPlusFilesByLocationId };
2
+ export { getPlusFileFromConfigFile };
2
3
  import { assert } from '../../../../utils/assert.js';
3
4
  import { metaBuiltIn } from './metaBuiltIn.js';
4
5
  import { getLocationId } from './filesystemRouting.js';
@@ -9,6 +10,7 @@ import { resolvePointerImport } from './resolvePointerImport.js';
9
10
  import { getFilePathResolved } from '../getFilePath.js';
10
11
  import { assertExtensionsConventions, assertExtensionsRequire } from './assertExtensions.js';
11
12
  import '../../assertEnvVite.js';
13
+ // TODO/after-PR-merge rename getPlusFilesByLocationId getPlusFiles
12
14
  async function getPlusFilesByLocationId(userRootDir, esbuildCache) {
13
15
  const plusFilePaths = (await crawlPlusFilePaths(userRootDir)).map(({ filePathAbsoluteUserRootDir }) => getFilePathResolved({ filePathAbsoluteUserRootDir, userRootDir }));
14
16
  const plusFilesByLocationId = {};
@@ -103,6 +103,11 @@ const metaBuiltIn = {
103
103
  extends: {
104
104
  env: { config: true },
105
105
  },
106
+ pages: {
107
+ env: { config: true },
108
+ global: true,
109
+ cumulative: true,
110
+ },
106
111
  meta: {
107
112
  env: { config: true },
108
113
  },
@@ -40,11 +40,13 @@ import pc from '@brillout/picocolors';
40
40
  import { getConfigDefinedAt, getDefinedByString, } from '../../../shared-server-client/page-configs/getConfigDefinedAt.js';
41
41
  import { loadPointerImport, loadValueFile } from './resolveVikeConfigInternal/loadFileAtConfigTime.js';
42
42
  import { resolvePointerImport } from './resolveVikeConfigInternal/resolvePointerImport.js';
43
+ import { parsePointerImportData } from './resolveVikeConfigInternal/pointerImports.js';
43
44
  import { getFilePathResolved } from './getFilePath.js';
44
45
  import { getConfigValueBuildTime } from '../../../shared-server-client/page-configs/getConfigValueBuildTime.js';
45
46
  import { resolveGlobalConfigPublic, } from '../../../shared-server-client/page-configs/resolveVikeConfigPublic.js';
46
47
  import { getConfigValuesBase, isJsonValue, } from '../../../shared-server-client/page-configs/serialize/serializeConfigValues.js';
47
- import { getPlusFilesByLocationId, } from './resolveVikeConfigInternal/getPlusFilesByLocationId.js';
48
+ import { getPlusFilesByLocationId, getPlusFileFromConfigFile, } from './resolveVikeConfigInternal/getPlusFilesByLocationId.js';
49
+ import { assertRouteString } from '../../../shared-server-client/route/resolveRouteString.js';
48
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';
@@ -322,35 +324,94 @@ function getPageConfigsBuildTime(configDefinitionsResolved, plusFilesByLocationI
322
324
  assertPageConfigGlobal(pageConfigGlobal, plusFilesByLocationId);
323
325
  const pageConfigs = objectEntries(configDefinitionsResolved.configDefinitionsLocal)
324
326
  .filter(([_locationId, { plusFiles }]) => isDefiningPage(plusFiles))
325
- .map(([locationId, { configDefinitions, plusFilesRelevant }]) => {
326
- const configDefinitionsLocal = configDefinitions;
327
- const configValueSources = {};
328
- objectEntries(configDefinitionsLocal)
329
- .filter(([_configName, configDef]) => configDef.global !== true)
330
- .forEach(([configName, configDef]) => {
331
- const sources = resolveConfigValueSources(configName, configDef, plusFilesRelevant, userRootDir, false, plusFilesByLocationId);
332
- if (sources.length === 0)
333
- return;
334
- configValueSources[configName] = sources;
335
- });
336
- const pageConfigRoute = determineRouteFilesystem(locationId, configValueSources);
337
- applyEffects(configValueSources, configDefinitionsLocal, plusFilesByLocationId);
338
- sortConfigValueSources(configValueSources, locationId);
339
- const pageConfig = {
340
- pageId: locationId,
341
- ...pageConfigRoute,
342
- configDefinitions: configDefinitionsLocal,
343
- plusFiles: plusFilesRelevant,
344
- configValueSources,
345
- };
346
- const configValuesComputed = getComputed(pageConfig);
347
- objectAssign(pageConfig, { configValuesComputed });
348
- checkType(pageConfig);
349
- return pageConfig;
350
- });
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));
351
330
  assertPageConfigs(pageConfigs);
352
331
  return { pageConfigs, pageConfigGlobal };
353
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
+ }
354
415
  function assertPageConfigGlobal(pageConfigGlobal, plusFilesByLocationId) {
355
416
  Object.entries(pageConfigGlobal.configValueSources).forEach(([configName, sources]) => {
356
417
  assertGlobalConfigLocation(configName, sources, plusFilesByLocationId, pageConfigGlobal.configDefinitions);
@@ -397,10 +458,18 @@ function assertGlobalConfigLocation(configName, sources, plusFilesByLocationId,
397
458
  });
398
459
  }
399
460
  function assertPageConfigs(pageConfigs) {
461
+ assertNoDuplicatePageIds(pageConfigs);
400
462
  pageConfigs.forEach((pageConfig) => {
401
463
  assertOnBeforeRenderEnv(pageConfig);
402
464
  });
403
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
+ }
404
473
  function assertOnBeforeRenderEnv(pageConfig) {
405
474
  const onBeforeRenderConfig = pageConfig.configValueSources.onBeforeRender?.[0];
406
475
  if (!onBeforeRenderConfig)
@@ -812,6 +881,9 @@ function isDefiningPage(plusFiles) {
812
881
  function isDefiningPageConfig(configName) {
813
882
  return ['Page', 'route'].includes(configName);
814
883
  }
884
+ function isDefiningProgrammaticPages(plusFiles) {
885
+ return plusFiles.some((plusFile) => getConfigNamesSetByPlusFile(plusFile).includes('pages'));
886
+ }
815
887
  function resolveIsGlobalValue(configDefGlobal, source, plusFilesByLocationId) {
816
888
  assert(source.valueIsLoaded);
817
889
  let isGlobal;
@@ -1218,7 +1290,9 @@ function resolveConfigEnv(configEnv, filePath) {
1218
1290
  /** Whether configs defined in `locationId` apply to every page */
1219
1291
  function isGlobalLocation(locationId, plusFilesByLocationId) {
1220
1292
  const locationIdsPage = objectEntries(plusFilesByLocationId)
1221
- .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))
1222
1296
  .map(([locationId]) => locationId);
1223
1297
  return locationIdsPage.every((locId) => isInherited(locationId, locId));
1224
1298
  }
@@ -56,8 +56,8 @@ type HookName = HookNamePage | HookNameGlobal;
56
56
  type HookNamePage = 'onHydrationEnd' | 'onBeforePrerenderStart' | 'onBeforeRender' | 'onPageTransitionStart' | 'onPageTransitionEnd' | 'onRenderHtml' | 'onRenderClient' | 'guard' | 'data' | 'onData' | 'route';
57
57
  type HookNameGlobal = 'onBeforeRoute' | 'onPrerenderStart' | 'onCreatePageContext' | 'onCreateGlobalContext' | 'onError' | 'onHookCall';
58
58
  type HookNameOldDesign = 'render' | 'prerender' | 'onBeforePrerender';
59
- type ConfigNameBuiltIn = Exclude<keyof ConfigBuiltIn, keyof VikeVitePluginOptions | 'onBeforeRoute' | 'onPrerenderStart' | 'vite' | 'redirects'> | 'prerender' | 'hasServerOnlyHook' | 'isClientRuntimeLoaded' | 'onBeforeRenderEnv' | 'dataEnv' | 'guardEnv' | 'hooksTimeout' | 'clientHooks' | 'middleware' | 'server' | 'vercel';
60
- type ConfigNameBuiltInGlobal = 'onPrerenderStart' | 'onBeforeRoute' | 'prerender' | 'disableAutoFullBuild' | 'includeAssetsImportedByServer' | 'baseAssets' | 'baseServer' | 'redirects' | 'trailingSlash' | 'disableUrlNormalization' | 'vite';
59
+ type ConfigNameBuiltIn = Exclude<keyof ConfigBuiltIn, keyof VikeVitePluginOptions | 'onBeforeRoute' | 'onPrerenderStart' | 'vite' | 'redirects' | 'pages'> | 'prerender' | 'hasServerOnlyHook' | 'isClientRuntimeLoaded' | 'onBeforeRenderEnv' | 'dataEnv' | 'guardEnv' | 'hooksTimeout' | 'clientHooks' | 'middleware' | 'server' | 'vercel';
60
+ type ConfigNameBuiltInGlobal = 'onPrerenderStart' | 'onBeforeRoute' | 'pages' | 'prerender' | 'disableAutoFullBuild' | 'includeAssetsImportedByServer' | 'baseAssets' | 'baseServer' | 'redirects' | 'trailingSlash' | 'disableUrlNormalization' | 'vite';
61
61
  type Config = ConfigBuiltIn & Vike.Config;
62
62
  /** @deprecated This type is deprecated, see:
63
63
  * - https://vike.dev/migration/hook-types
@@ -255,6 +255,12 @@ type ConfigBuiltIn = {
255
255
  * https://vike.dev/route
256
256
  */
257
257
  route?: Route | ImportStringList;
258
+ /**
259
+ * @experimental
260
+ *
261
+ * Programmatically define pages.
262
+ */
263
+ pages?: ConfigPageEntry[];
258
264
  /** Protect page(s), e.g. forbid unauthorized access.
259
265
  *
260
266
  * https://vike.dev/guard
@@ -675,3 +681,10 @@ type ConfigBuiltInResolved = {
675
681
  staticReplace?: StaticReplace[][];
676
682
  };
677
683
  type ConfigMeta = Record<string, ConfigDefinition>;
684
+ type ConfigPageEntry = Omit<Config, 'pages' | 'extends' | 'route'> & {
685
+ /** The page's URL — a Route String or a Route Function.
686
+ *
687
+ * https://vike.dev/route
688
+ */
689
+ route: Route;
690
+ };
@@ -1 +1 @@
1
- export declare const PROJECT_VERSION: "0.4.259-commit-aa6fa94";
1
+ export declare const PROJECT_VERSION: "0.4.259-commit-f6ecfb2";
@@ -1,2 +1,2 @@
1
1
  // Automatically updated by @brillout/release-me
2
- export const PROJECT_VERSION = '0.4.259-commit-aa6fa94';
2
+ export const PROJECT_VERSION = '0.4.259-commit-f6ecfb2';
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "vike",
3
- "version": "0.4.259-commit-aa6fa94",
3
+ "version": "0.4.259-commit-f6ecfb2",
4
4
  "repository": "https://github.com/vikejs/vike",
5
5
  "exports": {
6
6
  "./server": {