vike 0.4.149 → 0.4.150-commit-d0822a3

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 (94) hide show
  1. package/dist/cjs/node/plugin/index.js +0 -2
  2. package/dist/cjs/node/plugin/plugins/autoFullBuild.js +3 -2
  3. package/dist/cjs/node/plugin/plugins/buildConfig.js +17 -36
  4. package/dist/cjs/node/plugin/plugins/commonConfig/assertResolveAlias.js +3 -0
  5. package/dist/cjs/node/plugin/plugins/commonConfig.js +1 -1
  6. package/dist/cjs/node/plugin/plugins/config/resolveExtensions.js +1 -1
  7. package/dist/cjs/node/plugin/plugins/config/stemUtils.js +1 -1
  8. package/dist/cjs/node/plugin/plugins/devConfig/determineFsAllowList.js +1 -1
  9. package/dist/cjs/node/plugin/plugins/importBuild/getVikeManifest.js +38 -0
  10. package/dist/cjs/node/plugin/plugins/importBuild/index.js +57 -19
  11. package/dist/cjs/node/plugin/plugins/importUserCode/index.js +3 -1
  12. package/dist/cjs/node/plugin/plugins/importUserCode/v1-design/getVikeConfig/configDefinitionsBuiltIn.js +7 -0
  13. package/dist/cjs/node/plugin/plugins/importUserCode/v1-design/getVikeConfig/crawlPlusFiles.js +1 -1
  14. package/dist/cjs/node/plugin/utils.js +1 -0
  15. package/dist/cjs/node/runtime/html/serializePageContextClientSide.js +2 -1
  16. package/dist/cjs/node/runtime/html/stream.js +57 -17
  17. package/dist/cjs/node/runtime/index-deprecated.js +3 -2
  18. package/dist/cjs/node/runtime/index.js +3 -2
  19. package/dist/cjs/node/runtime/renderPage/executeOnBeforeRenderAndDataHooks.js +33 -0
  20. package/dist/cjs/node/runtime/renderPage/getHttpResponseBody.js +2 -2
  21. package/dist/cjs/node/runtime/renderPage/getPageAssets/retrieveAssetsDev.js +13 -5
  22. package/dist/cjs/node/runtime/renderPage/getPageAssets/sortPageAssetsForEarlyHintsHeader.js +6 -0
  23. package/dist/cjs/node/runtime/renderPage/getPageAssets.js +1 -1
  24. package/dist/cjs/node/runtime/renderPage/inferMediaType.js +38 -0
  25. package/dist/cjs/node/runtime/renderPage/renderPageAlreadyRouted.js +4 -4
  26. package/dist/cjs/shared/page-configs/assertExports.js +11 -4
  27. package/dist/cjs/shared/route/resolveRedirects.js +2 -10
  28. package/dist/cjs/shared/route/resolveUrlPathname.js +48 -0
  29. package/dist/cjs/utils/debug.js +29 -15
  30. package/dist/cjs/utils/getDependencyPackageJson.js +1 -1
  31. package/dist/cjs/utils/getFilePathAbsolute.js +1 -1
  32. package/dist/cjs/utils/getOutDirs.js +11 -1
  33. package/dist/cjs/utils/injectRollupInputs.js +29 -0
  34. package/dist/cjs/utils/isPlainObject.js +1 -1
  35. package/dist/cjs/utils/projectInfo.js +1 -1
  36. package/dist/cjs/utils/requireResolve.js +1 -1
  37. package/dist/esm/client/client-routing-runtime/getPageContextFromHooks.js +105 -66
  38. package/dist/esm/client/client-routing-runtime/renderPageClientSide.js +1 -1
  39. package/dist/esm/node/plugin/index.js +0 -2
  40. package/dist/esm/node/plugin/plugins/autoFullBuild.js +3 -2
  41. package/dist/esm/node/plugin/plugins/buildConfig.js +17 -36
  42. package/dist/esm/node/plugin/plugins/commonConfig/assertResolveAlias.js +3 -0
  43. package/dist/esm/node/plugin/plugins/commonConfig.js +1 -1
  44. package/dist/esm/node/plugin/plugins/config/resolveExtensions.js +1 -1
  45. package/dist/esm/node/plugin/plugins/config/stemUtils.js +1 -1
  46. package/dist/esm/node/plugin/plugins/devConfig/determineFsAllowList.js +1 -1
  47. package/dist/esm/node/plugin/plugins/importBuild/getVikeManifest.d.ts +5 -0
  48. package/dist/esm/node/plugin/plugins/importBuild/getVikeManifest.js +32 -0
  49. package/dist/esm/node/plugin/plugins/importBuild/index.js +59 -21
  50. package/dist/esm/node/plugin/plugins/importUserCode/index.js +3 -1
  51. package/dist/esm/node/plugin/plugins/importUserCode/v1-design/debug.d.ts +1 -1
  52. package/dist/esm/node/plugin/plugins/importUserCode/v1-design/getVikeConfig/configDefinitionsBuiltIn.js +7 -0
  53. package/dist/esm/node/plugin/plugins/importUserCode/v1-design/getVikeConfig/crawlPlusFiles.js +1 -1
  54. package/dist/esm/node/plugin/utils.d.ts +1 -0
  55. package/dist/esm/node/plugin/utils.js +1 -0
  56. package/dist/esm/node/runtime/html/serializePageContextClientSide.js +2 -1
  57. package/dist/esm/node/runtime/html/stream.js +57 -17
  58. package/dist/esm/node/runtime/index-deprecated.js +3 -2
  59. package/dist/esm/node/runtime/index.js +3 -2
  60. package/dist/esm/node/runtime/renderPage/{executeOnBeforeRenderHook.d.ts → executeOnBeforeRenderAndDataHooks.d.ts} +2 -2
  61. package/dist/esm/node/runtime/renderPage/executeOnBeforeRenderAndDataHooks.js +30 -0
  62. package/dist/esm/node/runtime/renderPage/getHttpResponseBody.js +2 -2
  63. package/dist/esm/node/runtime/renderPage/getPageAssets/retrieveAssetsDev.js +13 -5
  64. package/dist/esm/node/runtime/renderPage/getPageAssets/sortPageAssetsForEarlyHintsHeader.js +6 -0
  65. package/dist/esm/node/runtime/renderPage/getPageAssets.js +1 -1
  66. package/dist/esm/node/runtime/renderPage/inferMediaType.d.ts +2 -2
  67. package/dist/esm/node/runtime/renderPage/inferMediaType.js +38 -0
  68. package/dist/esm/node/runtime/renderPage/renderPageAlreadyRouted.js +4 -4
  69. package/dist/esm/shared/VikeNamespace.d.ts +2 -1
  70. package/dist/esm/shared/page-configs/Config.d.ts +22 -5
  71. package/dist/esm/shared/page-configs/assertExports.js +11 -4
  72. package/dist/esm/shared/route/resolveRedirects.js +2 -10
  73. package/dist/esm/shared/route/resolveUrlPathname.d.ts +12 -0
  74. package/dist/esm/shared/route/resolveUrlPathname.js +45 -0
  75. package/dist/esm/shared/types.d.ts +5 -0
  76. package/dist/esm/types/index.d.ts +1 -1
  77. package/dist/esm/utils/debug.d.ts +2 -2
  78. package/dist/esm/utils/debug.js +29 -15
  79. package/dist/esm/utils/debugGlob.d.ts +1 -1
  80. package/dist/esm/utils/getDependencyPackageJson.js +1 -1
  81. package/dist/esm/utils/getFilePathAbsolute.js +1 -1
  82. package/dist/esm/utils/getOutDirs.js +11 -1
  83. package/dist/esm/utils/injectRollupInputs.d.ts +7 -0
  84. package/dist/esm/utils/injectRollupInputs.js +26 -0
  85. package/dist/esm/utils/isPlainObject.js +1 -1
  86. package/dist/esm/utils/projectInfo.d.ts +2 -2
  87. package/dist/esm/utils/projectInfo.js +1 -1
  88. package/dist/esm/utils/requireResolve.js +1 -1
  89. package/package.json +10 -3
  90. package/dist/cjs/node/plugin/plugins/manifest.js +0 -59
  91. package/dist/cjs/node/runtime/renderPage/executeOnBeforeRenderHook.js +0 -23
  92. package/dist/esm/node/plugin/plugins/manifest.d.ts +0 -3
  93. package/dist/esm/node/plugin/plugins/manifest.js +0 -53
  94. package/dist/esm/node/runtime/renderPage/executeOnBeforeRenderHook.js +0 -20
@@ -36,6 +36,9 @@ const configDefinitionsBuiltIn = {
36
36
  guard: {
37
37
  env: { server: true, client: 'if-client-routing' }
38
38
  },
39
+ data: {
40
+ env: { server: true }
41
+ },
39
42
  iKnowThePerformanceRisksOfAsyncRouteFunctions: {
40
43
  env: { server: true, client: 'if-client-routing', eager: true }
41
44
  },
@@ -76,6 +79,10 @@ const configDefinitionsBuiltIn = {
76
79
  env: { client: true },
77
80
  _computed: (configValueSources) => !isConfigSet(configValueSources, 'onBeforeRender') ? null : getConfigEnv(configValueSources, 'onBeforeRender')
78
81
  },
82
+ dataEnv: {
83
+ env: { client: true },
84
+ _computed: (configValueSources) => !isConfigSet(configValueSources, 'data') ? null : getConfigEnv(configValueSources, 'data')
85
+ },
79
86
  hooksTimeout: {
80
87
  env: { server: true, client: true }
81
88
  }
@@ -120,5 +120,5 @@ async function isUsingGit(userRootDir) {
120
120
  async function runCmd(cmd, cwd) {
121
121
  const res = await execA(cmd, { cwd });
122
122
  assert(res.stderr === '');
123
- return res.stdout.toString().trim().split('\n');
123
+ return res.stdout.toString().split('\n').filter(Boolean);
124
124
  }
@@ -18,3 +18,4 @@ export * from '../../utils/getPropAccessNotation.js';
18
18
  export * from '../../utils/mergeCumulativeValues.js';
19
19
  export * from '../../utils/deepEqual.js';
20
20
  export * from '../../utils/assertKeys.js';
21
+ export * from '../../utils/injectRollupInputs.js';
@@ -24,3 +24,4 @@ export * from '../../utils/getPropAccessNotation.js';
24
24
  export * from '../../utils/mergeCumulativeValues.js';
25
25
  export * from '../../utils/deepEqual.js';
26
26
  export * from '../../utils/assertKeys.js';
27
+ export * from '../../utils/injectRollupInputs.js';
@@ -16,7 +16,8 @@ const PASS_TO_CLIENT = [
16
16
  '_abortCaller',
17
17
  */
18
18
  '_pageContextInitHasClientData',
19
- '_pageId'
19
+ '_pageId',
20
+ 'data' // for data() hook
20
21
  ];
21
22
  const PASS_TO_CLIENT_ERROR_PAGE = ['pageProps', 'is404', '_isError'];
22
23
  function serializePageContextClientSide(pageContext) {
@@ -205,7 +205,9 @@ function pipeToStreamWritableNode(htmlRender, writable) {
205
205
  async function processStream(streamOriginal, { injectStringAtBegin, injectStringAtEnd, onErrorWhileStreaming, enableEagerStreaming }) {
206
206
  const buffer = [];
207
207
  let streamOriginalHasStartedEmitting = false;
208
- let streamEnded = false;
208
+ let streamOriginalEnded = false;
209
+ let streamClosed = false;
210
+ let onEndWasCalled = false;
209
211
  let isReadyToWrite = false;
210
212
  let wrapperCreated = false;
211
213
  let shouldFlushStream = false;
@@ -229,6 +231,14 @@ async function processStream(streamOriginal, { injectStringAtBegin, injectString
229
231
  writeStream(injectionBegin); // Adds injectionBegin to buffer
230
232
  flushStream(); // Sets shouldFlushStream to true
231
233
  }
234
+ // We call onStreamEvent() also when the stream ends in order to properly handle the situation when the stream didn't emit any data
235
+ const onStreamDataOrEnd = (cb) => {
236
+ assert(streamOriginalEnded === false);
237
+ streamOriginalHasStartedEmitting = true;
238
+ cb();
239
+ if (wrapperCreated)
240
+ resolvePromise();
241
+ };
232
242
  const { streamWrapper, streamWrapperOperations } = await createStreamWrapper({
233
243
  streamOriginal,
234
244
  onReadyToWrite() {
@@ -246,19 +256,21 @@ async function processStream(streamOriginal, { injectStringAtBegin, injectString
246
256
  }
247
257
  },
248
258
  onData(chunk) {
249
- assert(streamEnded === false);
250
- streamOriginalHasStartedEmitting = true;
251
- writeStream(chunk);
252
- if (wrapperCreated)
253
- resolvePromise();
259
+ onStreamDataOrEnd(() => {
260
+ writeStream(chunk);
261
+ });
254
262
  },
255
- async onEnd() {
263
+ async onEnd(
264
+ // Should we use this `isCancel`? Maybe we can skip `injectStringAtEnd()`?
265
+ isCancel) {
256
266
  try {
267
+ assert(!onEndWasCalled);
268
+ onEndWasCalled = true;
257
269
  debug('stream end');
258
- streamEnded = true;
259
- streamOriginalHasStartedEmitting = true; // In case original stream (stream provided by user) emits no data
260
- if (wrapperCreated)
261
- resolvePromise(); // In case original stream (stream provided by user) emits no data
270
+ // We call onStreamEvent() also here in case the stream didn't emit any data
271
+ onStreamDataOrEnd(() => {
272
+ streamOriginalEnded = true;
273
+ });
262
274
  if (injectStringAtEnd) {
263
275
  const injectEnd = await injectStringAtEnd();
264
276
  writeStream(injectEnd);
@@ -266,10 +278,12 @@ async function processStream(streamOriginal, { injectStringAtBegin, injectString
266
278
  await promiseReadyToWrite; // E.g. if the user calls the pipe wrapper after the original writable has ended
267
279
  assert(isReady());
268
280
  flushBuffer();
281
+ streamClosed = true;
269
282
  debug('stream ended');
270
283
  }
271
284
  catch (err) {
272
- // We should catch and gracefully handle user land errors, as any error thrown here kills the server
285
+ // Ideally, we should catch and gracefully handle user land errors, as any error thrown here kills the server. (I assume that the fact it kills the server is a Node.js bug?)
286
+ // Show "[vike][Bug] You stumbled upon a bug in Vike's source code" to user while printing original error
273
287
  if (!isBug(err)) {
274
288
  console.error(err);
275
289
  assert(false);
@@ -293,6 +307,7 @@ async function processStream(streamOriginal, { injectStringAtBegin, injectString
293
307
  function flushBuffer() {
294
308
  if (!isReady())
295
309
  return;
310
+ assert(!streamClosed);
296
311
  buffer.forEach((chunk) => {
297
312
  streamWrapperOperations.writeChunk(chunk);
298
313
  });
@@ -489,6 +504,17 @@ async function createStreamWrapper({ streamOriginal, onError, onData, onEnd, onF
489
504
  if (isStreamReadableWeb(streamOriginal)) {
490
505
  debug('onRenderHtml() hook returned Web Readable');
491
506
  const readableOriginal = streamOriginal;
507
+ let controllerProxyIsClosed = false;
508
+ let isClosed = false;
509
+ let isCancel = false;
510
+ const closeStream = async () => {
511
+ if (isClosed)
512
+ return;
513
+ isClosed = true;
514
+ await onEnd(isCancel);
515
+ controllerProxy.close();
516
+ controllerProxyIsClosed = true;
517
+ };
492
518
  let controllerProxy;
493
519
  assertReadableStreamConstructor();
494
520
  const readableProxy = new ReadableStream({
@@ -502,16 +528,30 @@ async function createStreamWrapper({ streamOriginal, onError, onData, onEnd, onF
502
528
  controllerProxy.close();
503
529
  },
504
530
  async onEnd() {
505
- await onEnd();
506
- controllerProxy.close();
531
+ await closeStream();
507
532
  }
508
533
  });
534
+ },
535
+ async cancel(...args) {
536
+ isCancel = true;
537
+ await readableOriginal.cancel(...args);
538
+ // If readableOriginal has implemented readableOriginal.cancel() then the onEnd() callback and therfore closeStream() may already have been called at this point
539
+ await closeStream();
509
540
  }
510
541
  });
511
542
  const writeChunk = (chunk) => {
512
- controllerProxy.enqueue(encodeForWebStream(chunk));
513
- if (debug.isEnabled) {
514
- debug('data written (Web Readable)', String(chunk));
543
+ if (
544
+ // If readableOriginal doesn't implement readableOriginal.cancel() then it may still emit data after we close the stream. We therefore need to check whether we closed `controllerProxy`.
545
+ !controllerProxyIsClosed) {
546
+ controllerProxy.enqueue(encodeForWebStream(chunk));
547
+ if (debug.isEnabled) {
548
+ debug('data written (Web Readable)', String(chunk));
549
+ }
550
+ }
551
+ else {
552
+ if (debug.isEnabled) {
553
+ debug('data emitted but not written (Web Readable)', String(chunk));
554
+ }
515
555
  }
516
556
  };
517
557
  // Readables don't have the notion of flushing
@@ -1,5 +1,8 @@
1
1
  // TODO/v1-release: replace this with:
2
2
  // assertUsage(false, "`import { something } from 'vike'` doesn't exist: instead import from 'vike/server', 'vike/client', 'vike/plugin', ...")
3
+ import { isBrowser } from '../../utils/isBrowser.js';
4
+ import { assertUsage } from '../../utils/assert.js';
5
+ assertUsage(!isBrowser(), "It's forbidden to `import { something } from 'vike'` on the client-side: the module 'vike' is a server-only module.", { showStackTrace: true });
3
6
  export * from './index-common.js';
4
7
  export * from '../../types/index-dreprecated.js';
5
8
  import { assertWarning } from './utils.js';
@@ -36,5 +39,3 @@ assertWarning(false, [
36
39
  pc.green(" import type { something } from 'vike/types'"),
37
40
  "Make sure to import renderPage(), escapeInject, html, dangerouslySkipEscape(), pipeWebStream(), pipeNodeStream(), pipeStream(), stampPipe() from 'vike/server'. (Or inspect the error stack below to find the import causing this warning.)"
38
41
  ].join('\n'), { showStackTrace: true, onlyOnce: true });
39
- import { isBrowser, assertUsage } from './utils.js';
40
- assertUsage(!isBrowser(), "It's forbidden to `import { something } from 'vike'` in code loaded in the browser: the module 'vike' is a server-only module.", { showStackTrace: true });
@@ -1,3 +1,4 @@
1
+ import { isBrowser } from '../../utils/isBrowser.js';
2
+ import { assertUsage } from '../../utils/assert.js';
3
+ assertUsage(!isBrowser(), "It's forbidden to `import { something } from 'vike/server'` on the client-side: the module 'vike/server' is a server-only module.", { showStackTrace: true });
1
4
  export * from './index-common.js';
2
- import { isBrowser, assertUsage } from './utils.js';
3
- assertUsage(!isBrowser(), "It's forbidden to `import { something } from 'vike/server'` in code loaded on the client-side: the module 'vike/server' is a server-only module.", { showStackTrace: true });
@@ -1,7 +1,7 @@
1
- export { executeOnBeforeRenderHooks };
1
+ export { executeOnBeforeRenderAndDataHooks };
2
2
  import { type PageContextExports } from '../../../shared/getPageFiles.js';
3
3
  import { type PageContextForUserConsumptionServerSide } from './preparePageContextForUserConsumptionServerSide.js';
4
- declare function executeOnBeforeRenderHooks(pageContext: {
4
+ declare function executeOnBeforeRenderAndDataHooks(pageContext: {
5
5
  _pageId: string;
6
6
  _pageContextAlreadyProvidedByOnPrerenderHook?: true;
7
7
  } & PageContextExports & PageContextForUserConsumptionServerSide): Promise<void>;
@@ -0,0 +1,30 @@
1
+ export { executeOnBeforeRenderAndDataHooks };
2
+ import { getHook } from '../../../shared/hooks/getHook.js';
3
+ import { preparePageContextForUserConsumptionServerSide } from './preparePageContextForUserConsumptionServerSide.js';
4
+ import { executeHook } from '../utils.js';
5
+ import { assertOnBeforeRenderHookReturn } from '../../../shared/assertOnBeforeRenderHookReturn.js';
6
+ async function executeOnBeforeRenderAndDataHooks(pageContext) {
7
+ if (pageContext._pageContextAlreadyProvidedByOnPrerenderHook) {
8
+ return;
9
+ }
10
+ const dataHook = getHook(pageContext, 'data');
11
+ const onBeforeRenderHook = getHook(pageContext, 'onBeforeRender');
12
+ if (!dataHook && !onBeforeRenderHook) {
13
+ return;
14
+ }
15
+ preparePageContextForUserConsumptionServerSide(pageContext);
16
+ if (dataHook) {
17
+ const hookResult = await executeHook(() => dataHook.hookFn(pageContext), dataHook);
18
+ // Note: hookResult can be anything (e.g. an object) and is to be assigned to pageContext.data
19
+ const pageContextFromHook = {
20
+ data: hookResult
21
+ };
22
+ Object.assign(pageContext, pageContextFromHook);
23
+ }
24
+ if (onBeforeRenderHook) {
25
+ const hookResult = await executeHook(() => onBeforeRenderHook.hookFn(pageContext), onBeforeRenderHook);
26
+ assertOnBeforeRenderHookReturn(hookResult, onBeforeRenderHook.hookFilePath);
27
+ const pageContextFromHook = hookResult?.pageContext;
28
+ Object.assign(pageContext, pageContextFromHook);
29
+ }
30
+ }
@@ -71,7 +71,7 @@ function getHttpResponseBodyStreamHandlers(htmlRender, renderHook) {
71
71
  return;
72
72
  }
73
73
  else {
74
- assert(isStreamReadableWeb(htmlRender) || isStreamPipeWeb(htmlRender));
74
+ assert(isStreamReadableNode(htmlRender) || isStreamPipeNode(htmlRender));
75
75
  assertUsage(false, getErrMsgMixingStreamTypes('Web Writable'));
76
76
  }
77
77
  }
@@ -81,7 +81,7 @@ function getHttpResponseBodyStreamHandlers(htmlRender, renderHook) {
81
81
  return;
82
82
  }
83
83
  else {
84
- assert(isStreamReadableNode(htmlRender) || isStreamPipeNode(htmlRender));
84
+ assert(isStreamReadableWeb(htmlRender) || isStreamPipeWeb(htmlRender));
85
85
  assertUsage(false, getErrMsgMixingStreamTypes('Node.js Writable'));
86
86
  }
87
87
  }
@@ -37,13 +37,21 @@ function collectCss(mod, styleUrls, visitedModules, importer) {
37
37
  if (mod.url.startsWith('/')) {
38
38
  styleUrls.add(mod.url);
39
39
  }
40
+ else if (mod.url.startsWith('\0')) {
41
+ // Virtual modules
42
+ // - https://vitejs.dev/guide/api-plugin.html#virtual-modules-convention
43
+ // - I believe some Vite plugins don't respect the \0 virtual module convention. What should we do then?
44
+ // - https://github.com/vikejs/vike/issues/1327
45
+ // - https://github.com/vikejs/vike/commit/3f7b9916dddc84e29e2c20d2b0df7211b6f1acbd
46
+ styleUrls.add(`/@id/${mod.url.substring(1)}`);
47
+ }
40
48
  else {
41
- // Vuetify uses virtual SCSS modules which we skip
42
- // - We skip because `<link rel="stylesheet" type="text/css" href="virtual-module.css">` doesn't work
43
- // - Reproduction: https://github.com/vikejs/vike/issues/479
44
- // - Possible workaround: `<script>import 'virtual-module.css'</script>`
45
- // logModule(mod)
49
+ // Is this useful? Maybe for virtual modules that don't respect the \0 virtual module convention?
50
+ styleUrls.add(`/@id/${mod.url}`);
46
51
  }
52
+ /* Debug:
53
+ logModule(mod)
54
+ //*/
47
55
  }
48
56
  mod.importedModules.forEach((dep) => {
49
57
  collectCss(dep, styleUrls, visitedModules, mod);
@@ -20,6 +20,12 @@ function sortPageAssetsForEarlyHintsHeader(pageAssets) {
20
20
  if (assetType === 'image')
21
21
  return priority;
22
22
  priority--;
23
+ if (assetType === 'video')
24
+ return priority;
25
+ priority--;
26
+ if (assetType === 'audio')
27
+ return priority;
28
+ priority--;
23
29
  // Others
24
30
  if (assetType !== 'script')
25
31
  return priority;
@@ -82,7 +82,7 @@ async function resolveClientEntriesDev(clientEntry, viteDevServer, configVike) {
82
82
  const { createRequire } = (await import_('module')).default;
83
83
  const { dirname } = (await import_('path')).default;
84
84
  const { fileURLToPath } = (await import_('url')).default;
85
- // @ts-ignore Shimed by dist-cjs-fixup.js for CJS build.
85
+ // @ts-ignore Shimmed by dist-cjs-fixup.js for CJS build.
86
86
  const importMetaUrl = import.meta.url;
87
87
  const require_ = createRequire(importMetaUrl);
88
88
  const __dirname_ = dirname(fileURLToPath(importMetaUrl));
@@ -1,7 +1,7 @@
1
1
  export { inferMediaType };
2
2
  export type { MediaType };
3
3
  type MediaType = null | {
4
- assetType: 'image' | 'script' | 'font' | 'style';
5
- mediaType: 'text/javascript' | 'text/css' | 'image/jpeg' | 'image/png' | 'image/webp' | 'image/gif' | 'image/svg+xml' | 'font/ttf' | 'font/woff' | 'font/woff2';
4
+ assetType: 'image' | 'script' | 'font' | 'style' | 'audio' | 'video' | 'document' | 'fetch' | 'track' | 'worker' | 'embed' | 'object';
5
+ mediaType: 'text/javascript' | 'text/css' | 'image/jpeg' | 'image/png' | 'image/webp' | 'image/gif' | 'image/svg+xml' | 'font/ttf' | 'font/woff' | 'font/woff2' | 'video/mp4' | 'video/webm' | 'video/ogg' | 'video/mpeg' | 'video/x-msvideo' | 'video/quicktime' | 'audio/mpeg' | 'audio/wav' | 'audio/ogg' | 'audio/aac' | 'audio/midi' | 'audio/flac';
6
6
  };
7
7
  declare function inferMediaType(href: string): MediaType;
@@ -34,5 +34,43 @@ function inferMediaType(href) {
34
34
  if (href.endsWith('.woff2')) {
35
35
  return { assetType: 'font', mediaType: 'font/woff2' };
36
36
  }
37
+ // Videos
38
+ if (href.endsWith('.mp4')) {
39
+ return { assetType: 'video', mediaType: 'video/mp4' };
40
+ }
41
+ if (href.endsWith('.webm')) {
42
+ return { assetType: 'video', mediaType: 'video/webm' };
43
+ }
44
+ if (href.endsWith('.ogv')) {
45
+ return { assetType: 'video', mediaType: 'video/ogg' };
46
+ }
47
+ if (href.endsWith('.mpeg') || href.endsWith('.mpg')) {
48
+ return { assetType: 'video', mediaType: 'video/mpeg' };
49
+ }
50
+ if (href.endsWith('.avi')) {
51
+ return { assetType: 'video', mediaType: 'video/x-msvideo' };
52
+ }
53
+ if (href.endsWith('.mov') || href.endsWith('.qt')) {
54
+ return { assetType: 'video', mediaType: 'video/quicktime' };
55
+ }
56
+ // Audios
57
+ if (href.endsWith('.mp3')) {
58
+ return { assetType: 'audio', mediaType: 'audio/mpeg' };
59
+ }
60
+ if (href.endsWith('.wav')) {
61
+ return { assetType: 'audio', mediaType: 'audio/wav' };
62
+ }
63
+ if (href.endsWith('.ogg')) {
64
+ return { assetType: 'audio', mediaType: 'audio/ogg' };
65
+ }
66
+ if (href.endsWith('.m4a')) {
67
+ return { assetType: 'audio', mediaType: 'audio/aac' };
68
+ }
69
+ if (href.endsWith('midi') || href.endsWith('.mid')) {
70
+ return { assetType: 'audio', mediaType: 'audio/midi' };
71
+ }
72
+ if (href.endsWith('.flac')) {
73
+ return { assetType: 'audio', mediaType: 'audio/flac' };
74
+ }
37
75
  return null;
38
76
  }
@@ -13,7 +13,7 @@ import { getGlobalContext } from '../globalContext.js';
13
13
  import { createHttpResponseObject, createHttpResponsePageContextJson } from './createHttpResponseObject.js';
14
14
  import { loadPageFilesServerSide } from './loadPageFilesServerSide.js';
15
15
  import { executeOnRenderHtmlHook } from './executeOnRenderHtmlHook.js';
16
- import { executeOnBeforeRenderHooks } from './executeOnBeforeRenderHook.js';
16
+ import { executeOnBeforeRenderAndDataHooks } from './executeOnBeforeRenderAndDataHooks.js';
17
17
  import { logRuntimeError } from './loggerRuntime.js';
18
18
  import { isNewError } from './isNewError.js';
19
19
  import { preparePageContextForUserConsumptionServerSide } from './preparePageContextForUserConsumptionServerSide.js';
@@ -33,11 +33,11 @@ async function renderPageAlreadyRouted(pageContext) {
33
33
  await executeGuardHook(pageContext, (pageContext) => preparePageContextForUserConsumptionServerSide(pageContext));
34
34
  }
35
35
  if (!isError) {
36
- await executeOnBeforeRenderHooks(pageContext);
36
+ await executeOnBeforeRenderAndDataHooks(pageContext);
37
37
  }
38
38
  else {
39
39
  try {
40
- await executeOnBeforeRenderHooks(pageContext);
40
+ await executeOnBeforeRenderAndDataHooks(pageContext);
41
41
  }
42
42
  catch (err) {
43
43
  if (isNewError(err, pageContext.errorWhileRendering)) {
@@ -75,7 +75,7 @@ async function prerenderPage(pageContext) {
75
75
  * - It isn't trivial to implement, as it requires to duplicate / factor out the isAbortError() handling
76
76
  await executeGuardHook(pageContext, (pageContext) => preparePageContextForUserConsumptionServerSide(pageContext))
77
77
  */
78
- await executeOnBeforeRenderHooks(pageContext);
78
+ await executeOnBeforeRenderAndDataHooks(pageContext);
79
79
  const { htmlRender, renderHook } = await executeOnRenderHtmlHook(pageContext);
80
80
  assertUsage(htmlRender !== null, `Cannot pre-render ${pc.cyan(pageContext.urlOriginal)} because the ${renderHook.hookName}() hook defined by ${renderHook.hookFilePath} didn't return an HTML string.`);
81
81
  assert(pageContext.isClientSideNavigation === false);
@@ -15,9 +15,10 @@ declare global {
15
15
  /** Extend and/or refine the `PageContext` type (`import type { PageContext } from 'vike/types'`).
16
16
  *
17
17
  * For example:
18
- * - You can define the type of fetched data, e.g. `PageContext['movies']`.
19
18
  * - You can refine the type of `PageContext['Page']`.
19
+ * - You can define the type of custom `pageContext` values such as `pageContext.user`, see https://vike.dev/pageContext#custom
20
20
  *
21
+ * https://vike.dev/pageContext#typescript
21
22
  */
22
23
  interface PageContext {
23
24
  }
@@ -5,6 +5,8 @@ export type { ConfigMeta };
5
5
  export type { HookName };
6
6
  export type { HookNamePage };
7
7
  export type { HookNameGlobal };
8
+ export type { DataAsync };
9
+ export type { DataSync };
8
10
  export type { GuardAsync };
9
11
  export type { GuardSync };
10
12
  export type { OnBeforePrerenderStartAsync };
@@ -35,11 +37,21 @@ import type { Vike, VikePackages } from '../VikeNamespace.js';
35
37
  import type { HooksTimeoutProvidedByUser } from '../hooks/getHook.js';
36
38
  import type { PageContextClient, PageContextServer } from '../types.js';
37
39
  type HookName = HookNamePage | HookNameGlobal | HookNameOldDesign;
38
- type HookNamePage = 'onHydrationEnd' | 'onBeforePrerenderStart' | 'onBeforeRender' | 'onPageTransitionStart' | 'onPageTransitionEnd' | 'onRenderHtml' | 'onRenderClient' | 'guard';
40
+ type HookNamePage = 'onHydrationEnd' | 'onBeforePrerenderStart' | 'onBeforeRender' | 'onPageTransitionStart' | 'onPageTransitionEnd' | 'onRenderHtml' | 'onRenderClient' | 'guard' | 'data';
39
41
  type HookNameGlobal = 'onBeforePrerender' | 'onBeforeRoute' | 'onPrerenderStart';
40
42
  type HookNameOldDesign = 'render' | 'prerender';
41
- type ConfigNameBuiltIn = Exclude<keyof Config, keyof ConfigVikeUserProvided | 'onBeforeRoute' | 'onPrerenderStart'> | 'prerender' | 'isClientSideRenderable' | 'onBeforeRenderEnv' | 'hooksTimeout';
43
+ type ConfigNameBuiltIn = Exclude<keyof Config, keyof ConfigVikeUserProvided | 'onBeforeRoute' | 'onPrerenderStart'> | 'prerender' | 'isClientSideRenderable' | 'onBeforeRenderEnv' | 'dataEnv' | 'hooksTimeout';
42
44
  type Config = ConfigBuiltIn & Vike.Config & (VikePackages.ConfigVikeReact | VikePackages.ConfigVikeVue | VikePackages.ConfigVikeSolid | VikePackages.ConfigVikeSvelte);
45
+ /** Hook for fetching data.
46
+ *
47
+ * https://vike.dev/data
48
+ */
49
+ type DataAsync<Data> = (pageContext: PageContextServer) => Promise<Data>;
50
+ /** Hook for fetching data.
51
+ *
52
+ * https://vike.dev/data
53
+ */
54
+ type DataSync<Data> = (pageContext: PageContextServer) => Data;
43
55
  /** Protect page(s), e.g. forbid unauthorized access.
44
56
  *
45
57
  * https://vike.dev/guard
@@ -66,14 +78,14 @@ type OnBeforePrerenderStartSync = () => (string | {
66
78
  url: string;
67
79
  pageContext: Partial<Vike.PageContext>;
68
80
  })[];
69
- /** Hook called before the page is rendered, usually for fetching data.
81
+ /** Hook called before the page is rendered.
70
82
  *
71
83
  * https://vike.dev/onBeforeRender
72
84
  */
73
85
  type OnBeforeRenderAsync = (pageContext: PageContextServer) => Promise<{
74
86
  pageContext: Partial<Vike.PageContext>;
75
87
  } | void>;
76
- /** Hook called before the page is rendered, usually for fetching data.
88
+ /** Hook called before the page is rendered.
77
89
  *
78
90
  * https://vike.dev/onBeforeRender
79
91
  */
@@ -234,11 +246,16 @@ type ConfigBuiltIn = {
234
246
  * https://vike.dev/extends
235
247
  */
236
248
  extends?: Config | Config[] | ImportString | ImportString[];
237
- /** Hook called before the page is rendered, usually for fetching data.
249
+ /** Hook called before the page is rendered.
238
250
  *
239
251
  * https://vike.dev/onBeforeRender
240
252
  */
241
253
  onBeforeRender?: OnBeforeRenderAsync | OnBeforeRenderSync | ImportString | null;
254
+ /** Hook for fetching data.
255
+ *
256
+ * https://vike.dev/data
257
+ */
258
+ data?: DataAsync<unknown> | DataSync<unknown> | ImportString | null;
242
259
  /** Determines what pageContext properties are sent to the client-side.
243
260
  *
244
261
  * https://vike.dev/passToClient
@@ -28,13 +28,20 @@ function assertExports(fileExports, filePathToShowToUser, configName) {
28
28
  if (exportsRelevant.length === 1) {
29
29
  return;
30
30
  }
31
- else {
32
- assert(exportsRelevant.length === 0);
33
- let errMsg = `${filePathToShowToUser} doesn't export any value, but it should have a ${pc.cyan('export default')}`;
31
+ const exportDefault = pc.cyan('export default');
32
+ const exportConfigName = pc.cyan(`export { ${configName} }`);
33
+ if (exportsRelevant.length === 0) {
34
+ let errMsg = `${filePathToShowToUser} doesn't export any value, but it should have a ${exportDefault}`;
34
35
  if (configName)
35
- errMsg += ` or ${pc.cyan(`export { ${configName} }`)}`;
36
+ errMsg += ` or ${exportConfigName}`;
36
37
  assertUsage(false, errMsg);
37
38
  }
39
+ else {
40
+ assert(exportsRelevant.length === 2);
41
+ assertWarning(false, `${filePathToShowToUser} remove ${exportConfigName} or ${exportDefault}`, {
42
+ onlyOnce: true
43
+ });
44
+ }
38
45
  }
39
46
  else {
40
47
  // !configName => isConfigFile
@@ -4,6 +4,7 @@ export { resolveRouteStringRedirect };
4
4
  import { assertIsNotBrowser } from '../../utils/assertIsNotBrowser.js';
5
5
  import { isUriWithProtocol } from '../../utils/parseUrl-extras.js';
6
6
  import { assert, assertUsage } from '../utils.js';
7
+ import { resolveUrlPathname } from './resolveUrlPathname.js';
7
8
  import { assertRouteString, resolveRouteString } from './resolveRouteString.js';
8
9
  import pc from '@brillout/picocolors';
9
10
  assertIsNotBrowser(); // Don't bloat the client
@@ -27,16 +28,7 @@ function resolveRouteStringRedirect(urlSource, urlTarget, urlPathname) {
27
28
  const match = resolveRouteString(urlSource, urlPathname);
28
29
  if (!match)
29
30
  return null;
30
- let urlResolved = urlTarget;
31
- Object.entries(match.routeParams).forEach(([key, val]) => {
32
- if (key !== '*') {
33
- key = `@${key}`;
34
- }
35
- urlResolved = urlResolved.replaceAll(key, val);
36
- });
37
- if (!urlResolved.startsWith('mailto:')) {
38
- assertUsage(!urlResolved.includes('@'), 'URL should not contain "@" unless it is a mailto link.');
39
- }
31
+ const urlResolved = resolveUrlPathname(urlTarget, match.routeParams);
40
32
  if (urlResolved === urlPathname)
41
33
  return null;
42
34
  assert(urlResolved.startsWith('/') || isUriWithProtocol(urlResolved));
@@ -0,0 +1,12 @@
1
+ export { resolveUrlPathname };
2
+ /** Given a `routeString` and `routeParams`, resolve `urlPathname`.
3
+ *
4
+ * Basically, the correct implementation of following:
5
+ * ```js
6
+ * let urlPathname = routeString
7
+ * Object.entries(routeParams).forEach(([key, val]) => {
8
+ * urlPathname = urlPathname.replaceAll(key, val)
9
+ * })
10
+ * ```
11
+ */
12
+ declare function resolveUrlPathname(routeString: string, routeParams: Record<string, string>): string;
@@ -0,0 +1,45 @@
1
+ export { resolveUrlPathname };
2
+ import { assertIsNotBrowser } from '../../utils/assertIsNotBrowser.js';
3
+ import { assert, assertUsage } from '../utils.js';
4
+ assertIsNotBrowser(); // Don't bloat the client
5
+ /** Given a `routeString` and `routeParams`, resolve `urlPathname`.
6
+ *
7
+ * Basically, the correct implementation of following:
8
+ * ```js
9
+ * let urlPathname = routeString
10
+ * Object.entries(routeParams).forEach(([key, val]) => {
11
+ * urlPathname = urlPathname.replaceAll(key, val)
12
+ * })
13
+ * ```
14
+ */
15
+ function resolveUrlPathname(routeString, routeParams) {
16
+ let parts = [{ val: routeString, type: 'ROUTE_STRING' }];
17
+ Object.entries(routeParams).forEach(([key, val]) => {
18
+ if (key.startsWith('*')) {
19
+ assert(key === '*' || /\d+/.test(key.slice(1)));
20
+ assertUsage(key === '*', "Resolving URL with multiple globs isn't implemented yet");
21
+ }
22
+ else {
23
+ key = `@${key}`;
24
+ }
25
+ parts = parts
26
+ .map((part) => {
27
+ if (part.type === 'URL') {
28
+ return part;
29
+ }
30
+ else {
31
+ return part.val
32
+ .split(key)
33
+ .map((rest, i) => {
34
+ const partURL = { val, type: 'URL' };
35
+ const partRouteString = { val: rest, type: 'ROUTE_STRING' };
36
+ return i === 0 ? [partRouteString] : [partURL, partRouteString];
37
+ })
38
+ .flat();
39
+ }
40
+ })
41
+ .flat();
42
+ });
43
+ const urlPathname = parts.map((p) => p.val).join('');
44
+ return urlPathname;
45
+ }
@@ -35,6 +35,11 @@ type PageContextBuiltInCommon<Page = [never]> = {
35
35
  * https://vike.dev/route-string
36
36
  */
37
37
  routeParams: Record<string, string>;
38
+ /** The page's data which was fetched using the data() hook.
39
+ *
40
+ * https://vike.dev/data
41
+ */
42
+ data?: unknown;
38
43
  /** The page's configuration values.
39
44
  *
40
45
  * https://vike.dev/config
@@ -6,7 +6,7 @@ export type { PageContextClientWithServerRouting } from '../shared/types.js';
6
6
  export type { PageContextBuiltInServer } from '../shared/types.js';
7
7
  export type { PageContextBuiltInClientWithClientRouting } from '../shared/types.js';
8
8
  export type { PageContextBuiltInClientWithServerRouting } from '../shared/types.js';
9
- export type { Config, ConfigMeta as Meta, GuardAsync, GuardSync, OnBeforePrerenderStartAsync, OnBeforePrerenderStartSync, OnBeforeRenderAsync, OnBeforeRenderSync, OnBeforeRouteAsync, OnBeforeRouteSync, OnHydrationEndAsync, OnHydrationEndSync, OnPageTransitionEndAsync, OnPageTransitionEndSync, OnPageTransitionStartAsync, OnPageTransitionStartSync, OnPrerenderStartAsync, OnPrerenderStartSync, OnRenderClientAsync, OnRenderClientSync, OnRenderHtmlAsync, OnRenderHtmlSync, RouteAsync, RouteSync } from '../shared/page-configs/Config.js';
9
+ export type { Config, ConfigMeta as Meta, DataAsync, DataSync, GuardAsync, GuardSync, OnBeforePrerenderStartAsync, OnBeforePrerenderStartSync, OnBeforeRenderAsync, OnBeforeRenderSync, OnBeforeRouteAsync, OnBeforeRouteSync, OnHydrationEndAsync, OnHydrationEndSync, OnPageTransitionEndAsync, OnPageTransitionEndSync, OnPageTransitionStartAsync, OnPageTransitionStartSync, OnPrerenderStartAsync, OnPrerenderStartSync, OnRenderClientAsync, OnRenderClientSync, OnRenderHtmlAsync, OnRenderHtmlSync, RouteAsync, RouteSync } from '../shared/page-configs/Config.js';
10
10
  export type { ConfigEnv } from '../shared/page-configs/PageConfig.js';
11
11
  export type { ConfigDefinition, ConfigEffect } from '../node/plugin/plugins/importUserCode/v1-design/getVikeConfig/configDefinitionsBuiltIn.js';
12
12
  export type { ConfigEntries } from '../shared/getPageFiles/getExports.js';