vike 0.4.179 → 0.4.180-commit-648cd01

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 (54) hide show
  1. package/dist/cjs/node/plugin/plugins/importUserCode/v1-design/getVikeConfig/configDefinitionsBuiltIn.js +3 -0
  2. package/dist/cjs/node/plugin/plugins/importUserCode/v1-design/getVikeConfig/crawlPlusFiles.js +3 -0
  3. package/dist/cjs/node/runtime/html/injectAssets/getHtmlTags.js +46 -15
  4. package/dist/cjs/node/runtime/html/injectAssets/injectHtmlTags.js +3 -2
  5. package/dist/cjs/node/runtime/html/injectAssets.js +18 -3
  6. package/dist/cjs/node/runtime/html/renderHtml.js +12 -11
  7. package/dist/cjs/node/runtime/html/serializePageContextClientSide.js +14 -4
  8. package/dist/cjs/node/runtime/html/stream.js +12 -5
  9. package/dist/cjs/node/runtime/renderPage/executeOnRenderHtmlHook.js +1 -1
  10. package/dist/cjs/node/runtime/renderPage.js +14 -13
  11. package/dist/cjs/node/runtime/utils.js +1 -0
  12. package/dist/cjs/shared/page-configs/serialize/serializeConfigValues.js +4 -0
  13. package/dist/cjs/shared/route/index.js +4 -6
  14. package/dist/cjs/utils/augmentType.js +10 -0
  15. package/dist/cjs/utils/getPropAccessNotation.js +4 -1
  16. package/dist/cjs/utils/objectAssign.js +2 -0
  17. package/dist/cjs/utils/projectInfo.js +1 -1
  18. package/dist/esm/client/client-routing-runtime/createPageContext.d.ts +1 -0
  19. package/dist/esm/client/client-routing-runtime/createPageContext.js +2 -1
  20. package/dist/esm/client/client-routing-runtime/getPageContextFromHooks.d.ts +5 -3
  21. package/dist/esm/client/client-routing-runtime/getPageContextFromHooks.js +22 -27
  22. package/dist/esm/client/client-routing-runtime/renderPageClientSide.js +6 -12
  23. package/dist/esm/client/client-routing-runtime/utils.d.ts +1 -0
  24. package/dist/esm/client/client-routing-runtime/utils.js +1 -0
  25. package/dist/esm/client/shared/getPageContextProxyForUser.js +1 -1
  26. package/dist/esm/node/plugin/plugins/importUserCode/v1-design/getVikeConfig/configDefinitionsBuiltIn.js +3 -0
  27. package/dist/esm/node/plugin/plugins/importUserCode/v1-design/getVikeConfig/crawlPlusFiles.js +3 -0
  28. package/dist/esm/node/runtime/html/injectAssets/getHtmlTags.d.ts +3 -2
  29. package/dist/esm/node/runtime/html/injectAssets/getHtmlTags.js +46 -15
  30. package/dist/esm/node/runtime/html/injectAssets/injectHtmlTags.d.ts +3 -1
  31. package/dist/esm/node/runtime/html/injectAssets/injectHtmlTags.js +2 -1
  32. package/dist/esm/node/runtime/html/injectAssets.d.ts +1 -0
  33. package/dist/esm/node/runtime/html/injectAssets.js +19 -4
  34. package/dist/esm/node/runtime/html/renderHtml.js +12 -11
  35. package/dist/esm/node/runtime/html/serializePageContextClientSide.js +15 -5
  36. package/dist/esm/node/runtime/html/stream.d.ts +2 -1
  37. package/dist/esm/node/runtime/html/stream.js +12 -5
  38. package/dist/esm/node/runtime/renderPage/executeOnRenderHtmlHook.js +1 -1
  39. package/dist/esm/node/runtime/renderPage.js +14 -13
  40. package/dist/esm/node/runtime/utils.d.ts +1 -0
  41. package/dist/esm/node/runtime/utils.js +1 -0
  42. package/dist/esm/shared/page-configs/Config.d.ts +5 -0
  43. package/dist/esm/shared/page-configs/serialize/serializeConfigValues.js +4 -0
  44. package/dist/esm/shared/route/index.d.ts +1 -1
  45. package/dist/esm/shared/route/index.js +4 -6
  46. package/dist/esm/shared/types.d.ts +1 -1
  47. package/dist/esm/utils/augmentType.d.ts +3 -0
  48. package/dist/esm/utils/augmentType.js +7 -0
  49. package/dist/esm/utils/getPropAccessNotation.js +4 -1
  50. package/dist/esm/utils/objectAssign.d.ts +1 -1
  51. package/dist/esm/utils/objectAssign.js +2 -0
  52. package/dist/esm/utils/projectInfo.d.ts +2 -2
  53. package/dist/esm/utils/projectInfo.js +1 -1
  54. package/package.json +4 -4
@@ -27,30 +27,26 @@ function getPageContextFromHooks_serialized() {
27
27
  return pageContextSerialized;
28
28
  }
29
29
  async function getPageContextFromHooks_isHydration(pageContext) {
30
- const pageContextFromHooks = {
30
+ objectAssign(pageContext, {
31
31
  isHydration: true,
32
32
  _hasPageContextFromClient: false,
33
33
  _hasPageContextFromServer: true
34
- };
34
+ });
35
35
  for (const hookName of ['data', 'onBeforeRender']) {
36
- const pageContextForHook = {};
37
- objectAssign(pageContextForHook, pageContext);
38
- objectAssign(pageContextForHook, pageContextFromHooks);
39
- if (hookClientOnlyExists(hookName, pageContextForHook)) {
40
- const pageContextFromHook = await executeHookClientSide(hookName, pageContextForHook);
41
- Object.assign(pageContextFromHooks, pageContextFromHook);
36
+ if (hookClientOnlyExists(hookName, pageContext)) {
37
+ const pageContextFromHook = await executeHookClientSide(hookName, pageContext);
38
+ if (pageContextFromHook)
39
+ assert(!('urlOriginal' in pageContextFromHook));
40
+ Object.assign(pageContext, pageContextFromHook);
42
41
  }
43
42
  }
44
- return pageContextFromHooks;
43
+ return pageContext;
45
44
  }
46
45
  async function getPageContextFromHooks_isNotHydration(pageContext, isErrorPage) {
47
- const pageContextFromHooks = {
46
+ objectAssign(pageContext, {
48
47
  isHydration: false,
49
48
  _hasPageContextFromClient: false
50
- };
51
- const pageContextForCondition = {};
52
- objectAssign(pageContextForCondition, pageContext);
53
- objectAssign(pageContextForCondition, pageContextFromHooks);
49
+ });
54
50
  let hasPageContextFromServer = false;
55
51
  // If pageContextInit has some client data or if one of the hooks guard(), data() or onBeforeRender() is server-side
56
52
  // only, then we need to fetch pageContext from the server.
@@ -59,7 +55,7 @@ async function getPageContextFromHooks_isNotHydration(pageContext, isErrorPage)
59
55
  // For the error page, we cannot fetch pageContext from the server because the pageContext JSON request is based on the URL
60
56
  !isErrorPage &&
61
57
  // true if pageContextInit has some client data or at least one of the data() and onBeforeRender() hooks is server-side only:
62
- (await hasPageContextServer(pageContextForCondition))) {
58
+ (await hasPageContextServer(pageContext))) {
63
59
  const res = await fetchPageContextFromServer(pageContext);
64
60
  if ('is404ServerSideRouted' in res)
65
61
  return { is404ServerSideRouted: true };
@@ -68,43 +64,42 @@ async function getPageContextFromHooks_isNotHydration(pageContext, isErrorPage)
68
64
  // Already handled
69
65
  assert(!(isServerSideError in pageContextFromServer));
70
66
  assert(!('serverSideError' in pageContextFromServer));
71
- objectAssign(pageContextFromHooks, pageContextFromServer);
67
+ objectAssign(pageContext, pageContextFromServer);
72
68
  }
69
+ objectAssign(pageContext, { _hasPageContextFromServer: hasPageContextFromServer });
73
70
  // At this point, we need to call the client-side guard(), data() and onBeforeRender() hooks, if they exist on client
74
71
  // env. However if we have fetched pageContext from the server, some of them might have run already on the
75
72
  // server-side, so we run only the client-only ones in this case.
76
73
  // Note: for the error page, we also execute the client-side data() and onBeforeRender() hooks, but maybe we
77
74
  // shouldn't? The server-side does it as well (but maybe it shouldn't).
78
75
  for (const hookName of ['guard', 'data', 'onBeforeRender']) {
79
- const pageContextForHook = {};
80
- objectAssign(pageContextForHook, { _hasPageContextFromServer: hasPageContextFromServer });
81
- objectAssign(pageContextForHook, pageContext);
82
- objectAssign(pageContextForHook, pageContextFromHooks);
83
76
  if (hookName === 'guard') {
84
77
  if (!isErrorPage &&
85
78
  // We don't need to call guard() on the client-side if we fetch pageContext from the server side. (Because the `${url}.pageContext.json` HTTP request will already trigger the routing and guard() hook on the server-side.)
86
79
  !hasPageContextFromServer) {
87
80
  // Should we really call the guard() hook on the client-side? Shouldn't we make the guard() hook a server-side
88
81
  // only hook? Or maybe make its env configurable like data() and onBeforeRender()?
89
- await executeGuardHook(pageContextForHook, (pageContext) => preparePageContextForUserConsumptionClientSide(pageContext, true));
82
+ await executeGuardHook(pageContext, (pageContext) => preparePageContextForUserConsumptionClientSide(pageContext, true));
90
83
  }
91
84
  }
92
85
  else {
93
86
  assert(hookName === 'data' || hookName === 'onBeforeRender');
94
- if (hookClientOnlyExists(hookName, pageContextForHook) || !hasPageContextFromServer) {
87
+ if (hookClientOnlyExists(hookName, pageContext) || !hasPageContextFromServer) {
95
88
  // This won't do anything if no hook has been defined or if the hook's env.client is false.
96
- const pageContextFromHook = await executeHookClientSide(hookName, pageContextForHook);
97
- objectAssign(pageContextFromHooks, pageContextFromHook);
89
+ const pageContextFromHook = await executeHookClientSide(hookName, pageContext);
90
+ if (pageContextFromHook)
91
+ assert(!('urlOriginal' in pageContextFromHook));
92
+ Object.assign(pageContext, pageContextFromHook);
98
93
  }
99
94
  else {
100
95
  assert(hasPageContextFromServer);
101
96
  }
102
97
  }
103
98
  }
104
- objectAssign(pageContextFromHooks, {
99
+ objectAssign(pageContext, {
105
100
  _hasPageContextFromServer: hasPageContextFromServer
106
101
  });
107
- return { pageContextFromHooks };
102
+ return { pageContextAugmented: pageContext };
108
103
  }
109
104
  async function executeHookClientSide(hookName, pageContext) {
110
105
  const hook = getHook(pageContext, hookName);
@@ -226,7 +221,7 @@ async function fetchPageContextFromServer(pageContext) {
226
221
  }
227
222
  // Is there a reason for having two different properties? Can't we use only one property? I guess/think the isServerSideError property was an attempt (a bad idea really) for rendering the error page even though an error occured on the server-side (which is a bad idea because the added complexity is non-negligible while the added value is minuscule since the error page usually doesn't have any (meaningful / server-side) hooks).
228
223
  if ('serverSideError' in pageContextFromServer || isServerSideError in pageContextFromServer) {
229
- throw getProjectError(`pageContext couldn't be fetched from server: an error occurred on the server-side (see your server logs)`);
224
+ throw getProjectError(`pageContext couldn't be fetched because an error occurred on the server-side`);
230
225
  }
231
226
  assert(hasProp(pageContextFromServer, '_pageId', 'string'));
232
227
  processPageContextFromServer(pageContextFromServer);
@@ -1,7 +1,7 @@
1
1
  export { renderPageClientSide };
2
2
  export { getRenderCount };
3
3
  export { disableClientRouting };
4
- import { assert, getCurrentUrl, isSameErrorMessage, objectAssign, serverSideRouteTo, getGlobalObject, executeHook, hasProp } from './utils.js';
4
+ import { assert, getCurrentUrl, isSameErrorMessage, objectAssign, serverSideRouteTo, getGlobalObject, executeHook, hasProp, augmentType } from './utils.js';
5
5
  import { getPageContextFromHooks_isHydration, getPageContextFromHooks_isNotHydration, getPageContextFromHooks_serialized } from './getPageContextFromHooks.js';
6
6
  import { createPageContext } from './createPageContext.js';
7
7
  import { addLinkPrefetchHandlers } from './prefetch.js';
@@ -132,9 +132,9 @@ async function renderPageClientSide(renderArgs) {
132
132
  // Get pageContext from hooks (fetched from server, and/or directly called on the client-side)
133
133
  if (isHydrationRender) {
134
134
  assert(hasProp(pageContext, '_hasPageContextFromServer', 'true'));
135
- let pageContextFromHooks;
135
+ let pageContextAugmented;
136
136
  try {
137
- pageContextFromHooks = await getPageContextFromHooks_isHydration(pageContext);
137
+ pageContextAugmented = await getPageContextFromHooks_isHydration(pageContext);
138
138
  }
139
139
  catch (err) {
140
140
  await onError(err);
@@ -142,8 +142,7 @@ async function renderPageClientSide(renderArgs) {
142
142
  }
143
143
  if (isRenderOutdated())
144
144
  return;
145
- assert(!('urlOriginal' in pageContextFromHooks));
146
- objectAssign(pageContext, pageContextFromHooks);
145
+ augmentType(pageContext, pageContextAugmented);
147
146
  // Render page view
148
147
  await renderPageView(pageContext);
149
148
  }
@@ -160,9 +159,7 @@ async function renderPageClientSide(renderArgs) {
160
159
  return;
161
160
  if ('is404ServerSideRouted' in res)
162
161
  return;
163
- const pageContextFromHooks = res.pageContextFromHooks;
164
- assert(!('urlOriginal' in pageContextFromHooks));
165
- objectAssign(pageContext, pageContextFromHooks);
162
+ augmentType(pageContext, res.pageContextAugmented);
166
163
  // Render page view
167
164
  await renderPageView(pageContext);
168
165
  }
@@ -295,10 +292,7 @@ async function renderPageClientSide(renderArgs) {
295
292
  return;
296
293
  if ('is404ServerSideRouted' in res)
297
294
  return;
298
- const pageContextFromHooks = res.pageContextFromHooks;
299
- assert(pageContextFromHooks);
300
- assert(!('urlOriginal' in pageContextFromHooks));
301
- objectAssign(pageContext, pageContextFromHooks);
295
+ augmentType(pageContext, res.pageContextAugmented);
302
296
  await renderPageView(pageContext, args);
303
297
  }
304
298
  async function renderPageView(pageContext, isErrorPage) {
@@ -22,3 +22,4 @@ export * from '../../utils/throttle.js';
22
22
  export * from '../../utils/assertRoutingType.js';
23
23
  export * from '../../utils/onPageVisibilityChange.js';
24
24
  export * from '../../utils/isExternalLink.js';
25
+ export * from '../../utils/augmentType.js';
@@ -28,3 +28,4 @@ export * from '../../utils/throttle.js';
28
28
  export * from '../../utils/assertRoutingType.js';
29
29
  export * from '../../utils/onPageVisibilityChange.js';
30
30
  export * from '../../utils/isExternalLink.js';
31
+ export * from '../../utils/augmentType.js';
@@ -14,7 +14,7 @@ function getPageContextProxyForUser(pageContext) {
14
14
  get(_, prop) {
15
15
  const val = pageContext[prop];
16
16
  const propName = getPropAccessNotation(prop);
17
- assertUsage(val !== notSerializable, `pageContext${propName} couldn't be serialized and, therefore, is missing on the client-side. Check the server logs for more information.`);
17
+ assertUsage(val !== notSerializable, `Can't access pageContext${propName} on the client side. Because it can't be serialized, see server logs.`);
18
18
  passToClientHint(pageContext, prop, propName);
19
19
  return val;
20
20
  }
@@ -104,6 +104,9 @@ const configDefinitionsBuiltIn = {
104
104
  cacheControl: {
105
105
  env: { server: true }
106
106
  },
107
+ injectScriptsAt: {
108
+ env: { server: true }
109
+ },
107
110
  name: {
108
111
  env: { config: true }
109
112
  },
@@ -153,6 +153,9 @@ async function fastGlob(userRootDir, outDirRelativeFromUserRootDir) {
153
153
  cwd: userRootDir,
154
154
  dot: false
155
155
  });
156
+ // Make build deterministic, in order to get a stable generated hash for dist/client/assets/entries/entry-client-routing.${hash}.js
157
+ // https://github.com/vikejs/vike/pull/1750
158
+ files.sort();
156
159
  return files;
157
160
  }
158
161
  // Same as getIgnoreAsFilterFn() but as glob pattern
@@ -22,10 +22,11 @@ type InjectFilterEntry = {
22
22
  isEntry: boolean;
23
23
  inject: PreloadFilterInject;
24
24
  };
25
+ type Position = 'HTML_BEGIN' | 'HTML_END' | 'STREAM';
25
26
  type HtmlTag = {
26
27
  htmlTag: string | (() => string);
27
- position: 'HTML_BEGIN' | 'HTML_END' | 'STREAM';
28
+ position: Position;
28
29
  };
29
30
  declare function getHtmlTags(pageContext: {
30
31
  _isStream: boolean;
31
- } & PageContextInjectAssets, streamFromReactStreamingPackage: null | StreamFromReactStreamingPackage, injectFilter: PreloadFilter, pageAssets: PageAsset[], viteDevScript: string): HtmlTag[];
32
+ } & PageContextInjectAssets, streamFromReactStreamingPackage: null | StreamFromReactStreamingPackage, injectFilter: PreloadFilter, pageAssets: PageAsset[], viteDevScript: string, isStream: boolean): HtmlTag[];
@@ -4,13 +4,17 @@ import { serializePageContextClientSide } from '../serializePageContextClientSid
4
4
  import { sanitizeJson } from './sanitizeJson.js';
5
5
  import { inferAssetTag, inferPreloadTag } from './inferHtmlTags.js';
6
6
  import { mergeScriptTags } from './mergeScriptTags.js';
7
+ import { getPageConfig } from '../../../../shared/page-configs/helpers.js';
8
+ import { getConfigValueRuntime } from '../../../../shared/page-configs/getConfigValue.js';
7
9
  import { getGlobalContext } from '../../globalContext.js';
8
10
  import pc from '@brillout/picocolors';
11
+ import { getConfigDefinedAt } from '../../../../shared/page-configs/getConfigDefinedAt.js';
9
12
  const stamp = '__injectFilterEntry';
10
- function getHtmlTags(pageContext, streamFromReactStreamingPackage, injectFilter, pageAssets, viteDevScript) {
13
+ function getHtmlTags(pageContext, streamFromReactStreamingPackage, injectFilter, pageAssets, viteDevScript, isStream) {
11
14
  assert([true, false].includes(pageContext._isHtmlOnly));
12
15
  const isHtmlOnly = pageContext._isHtmlOnly;
13
16
  const { isProduction } = getGlobalContext();
17
+ const injectScriptsAt = getInjectScriptsAt(pageContext._pageId, pageContext._pageConfigs);
14
18
  const injectFilterEntries = pageAssets
15
19
  .filter((asset) => {
16
20
  if (asset.isEntry && asset.assetType === 'script') {
@@ -73,28 +77,39 @@ function getHtmlTags(pageContext, streamFromReactStreamingPackage, injectFilter,
73
77
  // ==========
74
78
  // JavaScript
75
79
  // ==========
76
- // In order to avoid the race-condition depicted in https://github.com/vikejs/vike/issues/567
77
- // 1. <script id="vike_pageContext" type="application/json"> must appear before the entry <script> (which loads Vike's client runtime).
78
- // 2. <script id="vike_pageContext" type="application/json"> can't be async nor defer.
79
- // Additionally:
80
- // 3. the entry <script> can't be defer, otherwise progressive hydration while SSR streaming won't work.
81
- // 4. the entry <script> should be towards the end of the HTML as performance-wise it's more interesting to parse
82
- // <div id="page-view"> before running the entry <script> which initiates the hydration.
83
- // See https://github.com/vikejs/vike/pull/1271
80
+ // - The pageContext JSON should be fully sent before Vike's client runtime starts executing.
81
+ // - Otherwise, race condition "SyntaxError: Unterminated string in JSON": https://github.com/vikejs/vike/issues/567
82
+ // - <script id="vike_pageContext" type="application/json"> must appear before the entry <script> (which loads Vike's client runtime).
83
+ // - <script id="vike_pageContext" type="application/json"> can't be async nor defer.
84
+ // - The entry <script> can't be defer, otherwise progressive hydration while SSR streaming won't work.
85
+ // - The entry <script> should be towards the end of the HTML as performance-wise it's more interesting to parse <div id="page-view"> before running the entry <script> which initiates the hydration.
86
+ // - But with HTML streaming, in order to support [Progressive Rendering](https://vike.dev/streaming#progressive-rendering), the entry <script> should be injected early instead.
84
87
  const positionJavaScriptEntry = (() => {
88
+ if (injectScriptsAt !== null) {
89
+ if (pageContext._pageContextPromise) {
90
+ assertWarning(injectScriptsAt === 'HTML_END' || !isStream, `You're setting injectScriptsAt to ${pc.code(JSON.stringify(injectScriptsAt))} while using HTML streaming with a pageContext promise (https://vike.dev/streaming#initial-data-after-stream-end) which is contradictory: the pageContext promise is skipped.`, { onlyOnce: true });
91
+ }
92
+ return injectScriptsAt;
93
+ }
85
94
  if (pageContext._pageContextPromise) {
86
- assertWarning(!streamFromReactStreamingPackage, "[getHtmlTags()] We recommend against using streaming and a pageContext promise at the same time, because progressive hydration won't work.", { onlyOnce: true });
87
- // If there is a pageContext._pageContextPromise (which is resolved after the stream has ended) then the pageContext JSON data needs to await for it: https://vike.dev/streaming#initial-data-after-stream-end
95
+ // - If there is a pageContext._pageContextPromise then <script id="vike_pageContext" type="application/json"> needs to await for it.
96
+ // - pageContext._pageContextPromise is typically resolved only after the page's components are rendered and the stream ended.
97
+ // - https://vike.dev/streaming#initial-data-after-stream-end
88
98
  return 'HTML_END';
89
99
  }
90
100
  if (streamFromReactStreamingPackage && !streamFromReactStreamingPackage.hasStreamEnded()) {
91
- // If there is a stream then, in order to support progressive hydration, inject the JavaScript during the stream after React(/Vue/Solid/...) resolved the first suspense boundary
101
+ // If there is a stream then, in order to support progressive hydration, inject the JavaScript during the stream after React(/Vue/Solid/...) resolved the first suspense boundary.
92
102
  return 'STREAM';
93
103
  }
94
- else {
95
- return 'HTML_END';
96
- }
104
+ return 'HTML_END';
97
105
  })();
106
+ if (pageContext._pageContextPromise && streamFromReactStreamingPackage) {
107
+ // - Should we show this warning for Solid as well? Solid seems to also support progressive rendering.
108
+ // - https://github.com/vikejs/vike-solid/issues/95
109
+ // - Vue doesn't seem to support progressive rendering yet.
110
+ // - https://github.com/vikejs/vike-vue/issues/85
111
+ assertWarning(false, "We recommend against using HTML streaming and a pageContext promise (https://vike.dev/streaming#initial-data-after-stream-end) at the same time, because progressive hydration (https://vike.dev/streaming#progressive-rendering) won't work.", { onlyOnce: true });
112
+ }
98
113
  // <script id="vike_pageContext" type="application/json">
99
114
  if (!isHtmlOnly) {
100
115
  htmlTags.push({
@@ -189,3 +204,19 @@ function checkForWarnings(injectFilterEntries) {
189
204
  }
190
205
  });
191
206
  }
207
+ function getInjectScriptsAt(pageId, pageConfigs) {
208
+ if (pageConfigs.length === 0)
209
+ return null; // only support V1 design
210
+ const pageConfig = getPageConfig(pageId, pageConfigs);
211
+ const configValue = getConfigValueRuntime(pageConfig, 'injectScriptsAt');
212
+ if (!configValue)
213
+ return null;
214
+ const injectScriptsAt = configValue.value;
215
+ assert(configValue.definedAtData);
216
+ const configDefinedAt = getConfigDefinedAt('Config', 'injectScriptsAt', configValue.definedAtData);
217
+ assertUsage(injectScriptsAt === null ||
218
+ injectScriptsAt === 'HTML_BEGIN' ||
219
+ injectScriptsAt === 'HTML_END' ||
220
+ injectScriptsAt === 'STREAM', `${configDefinedAt} has an invalid value`);
221
+ return injectScriptsAt;
222
+ }
@@ -1,13 +1,15 @@
1
1
  export { injectHtmlTags };
2
2
  export { injectHtmlTagsUsingStream };
3
3
  export { createHtmlHeadIfMissing };
4
+ export { joinHtmlTags };
4
5
  export { injectAtOpeningTag };
5
6
  export { injectAtClosingTag };
6
7
  import type { StreamFromReactStreamingPackage } from '../stream/react-streaming.js';
7
8
  import type { HtmlTag } from './getHtmlTags.js';
8
9
  type Position = 'HTML_BEGIN' | 'HTML_END';
9
10
  declare function injectHtmlTags(htmlString: string, htmlTags: HtmlTag[], position: Position): string;
10
- declare function injectHtmlTagsUsingStream(htmlTags: HtmlTag[], streamFromReactStreamingPackage: null | StreamFromReactStreamingPackage): void;
11
+ declare function injectHtmlTagsUsingStream(htmlTags: HtmlTag[], streamFromReactStreamingPackage: StreamFromReactStreamingPackage): void;
12
+ declare function joinHtmlTags(htmlTags: HtmlTag[]): string;
11
13
  declare function injectAtOpeningTag(tag: 'head' | 'html' | '!doctype', htmlString: string, htmlFragment: string): string;
12
14
  declare function injectAtClosingTag(tag: 'body' | 'html', htmlString: string, htmlFragment: string): string;
13
15
  declare function createHtmlHeadIfMissing(htmlString: string): string;
@@ -2,6 +2,7 @@
2
2
  export { injectHtmlTags };
3
3
  export { injectHtmlTagsUsingStream };
4
4
  export { createHtmlHeadIfMissing };
5
+ export { joinHtmlTags };
5
6
  // Only needed for unit tests
6
7
  export { injectAtOpeningTag };
7
8
  export { injectAtClosingTag };
@@ -13,10 +14,10 @@ function injectHtmlTags(htmlString, htmlTags, position) {
13
14
  }
14
15
  return htmlString;
15
16
  }
17
+ // Is it worth it? Should we remove this? https://github.com/vikejs/vike/pull/1740#issuecomment-2230540892
16
18
  function injectHtmlTagsUsingStream(htmlTags, streamFromReactStreamingPackage) {
17
19
  const htmlFragment = joinHtmlTags(htmlTags.filter((h) => h.position === 'STREAM'));
18
20
  if (htmlFragment) {
19
- assert(streamFromReactStreamingPackage);
20
21
  assert(!streamFromReactStreamingPackage.hasStreamEnded());
21
22
  streamFromReactStreamingPackage.injectToStream(htmlFragment, { flush: true });
22
23
  }
@@ -29,6 +29,7 @@ declare function injectHtmlTagsToStream(pageContext: PageContextInjectAssets & {
29
29
  _isStream: true;
30
30
  }, streamFromReactStreamingPackage: null | StreamFromReactStreamingPackage, injectFilter: PreloadFilter): {
31
31
  injectAtStreamBegin: (htmlPartsBegin: HtmlPart[]) => Promise<string>;
32
+ injectAtStreamAfterFirstChunk: () => null | string;
32
33
  injectAtStreamEnd: (htmlPartsEnd: HtmlPart[]) => Promise<string>;
33
34
  };
34
35
  type PageContextPromise = null | Promise<unknown> | (() => void | Promise<unknown>);
@@ -2,13 +2,13 @@ export { injectHtmlTagsToString };
2
2
  export { injectHtmlTagsToStream };
3
3
  import { assert, isCallable, isPromise } from '../utils.js';
4
4
  import { assertPageContextProvidedByUser } from '../../../shared/assertPageContextProvidedByUser.js';
5
- import { injectHtmlTags, createHtmlHeadIfMissing, injectHtmlTagsUsingStream } from './injectAssets/injectHtmlTags.js';
5
+ import { joinHtmlTags, injectHtmlTags, createHtmlHeadIfMissing, injectHtmlTagsUsingStream } from './injectAssets/injectHtmlTags.js';
6
6
  import { getHtmlTags } from './injectAssets/getHtmlTags.js';
7
7
  import { getViteDevScript } from './injectAssets/getViteDevScript.js';
8
8
  async function injectHtmlTagsToString(htmlParts, pageContext, injectFilter) {
9
9
  const pageAssets = await pageContext.__getPageAssets();
10
10
  const viteDevScript = await getViteDevScript();
11
- const htmlTags = getHtmlTags(pageContext, null, injectFilter, pageAssets, viteDevScript);
11
+ const htmlTags = getHtmlTags(pageContext, null, injectFilter, pageAssets, viteDevScript, false);
12
12
  let htmlString = htmlPartsToString(htmlParts, pageAssets);
13
13
  htmlString = injectToHtmlBegin(htmlString, htmlTags);
14
14
  htmlString = injectToHtmlEnd(htmlString, htmlTags);
@@ -19,17 +19,32 @@ function injectHtmlTagsToStream(pageContext, streamFromReactStreamingPackage, in
19
19
  let htmlTags;
20
20
  return {
21
21
  injectAtStreamBegin,
22
+ injectAtStreamAfterFirstChunk,
22
23
  injectAtStreamEnd
23
24
  };
24
25
  async function injectAtStreamBegin(htmlPartsBegin) {
25
26
  const pageAssets = await pageContext.__getPageAssets();
26
27
  const viteDevScript = await getViteDevScript();
27
- htmlTags = getHtmlTags(pageContext, streamFromReactStreamingPackage, injectFilter, pageAssets, viteDevScript);
28
+ htmlTags = getHtmlTags(pageContext, streamFromReactStreamingPackage, injectFilter, pageAssets, viteDevScript, true);
28
29
  let htmlBegin = htmlPartsToString(htmlPartsBegin, pageAssets);
29
30
  htmlBegin = injectToHtmlBegin(htmlBegin, htmlTags);
30
- injectHtmlTagsUsingStream(htmlTags, streamFromReactStreamingPackage);
31
+ if (streamFromReactStreamingPackage) {
32
+ injectHtmlTagsUsingStream(htmlTags, streamFromReactStreamingPackage);
33
+ }
31
34
  return htmlBegin;
32
35
  }
36
+ // Is it worth it? Should we remove this? https://github.com/vikejs/vike/pull/1740#issuecomment-2230540892
37
+ function injectAtStreamAfterFirstChunk() {
38
+ // React has its own stream injection mechanism, see injectHtmlTagsUsingStream()
39
+ if (streamFromReactStreamingPackage)
40
+ return null;
41
+ assert(htmlTags);
42
+ const tags = htmlTags.filter((h) => h.position === 'STREAM');
43
+ if (tags.length === 0)
44
+ return null;
45
+ const htmlFragment = joinHtmlTags(tags);
46
+ return htmlFragment;
47
+ }
33
48
  async function injectAtStreamEnd(htmlPartsEnd) {
34
49
  assert(htmlTags);
35
50
  await resolvePageContextPromise(pageContext);
@@ -52,7 +52,7 @@ async function renderDocumentHtml(documentHtml, pageContext, onErrorWhileStreami
52
52
  assert(false);
53
53
  }
54
54
  async function renderHtmlStream(streamOriginal, injectString, pageContext, onErrorWhileStreaming, injectFilter) {
55
- const opts = {
55
+ const processStreamOptions = {
56
56
  onErrorWhileStreaming,
57
57
  enableEagerStreaming: pageContext.enableEagerStreaming
58
58
  };
@@ -61,17 +61,18 @@ async function renderHtmlStream(streamOriginal, injectString, pageContext, onErr
61
61
  if (isStreamFromReactStreamingPackage(streamOriginal) && !streamOriginal.disabled) {
62
62
  streamFromReactStreamingPackage = streamOriginal;
63
63
  }
64
- const { injectAtStreamBegin, injectAtStreamEnd } = injectHtmlTagsToStream(pageContext, streamFromReactStreamingPackage, injectFilter);
65
- objectAssign(opts, {
66
- injectStringAtBegin: async () => {
67
- return await injectAtStreamBegin(injectString.htmlPartsBegin);
68
- },
69
- injectStringAtEnd: async () => {
70
- return await injectAtStreamEnd(injectString.htmlPartsEnd);
71
- }
72
- });
64
+ const { injectAtStreamBegin, injectAtStreamAfterFirstChunk, injectAtStreamEnd } = injectHtmlTagsToStream(pageContext, streamFromReactStreamingPackage, injectFilter);
65
+ processStreamOptions.injectStringAtBegin = async () => {
66
+ return await injectAtStreamBegin(injectString.htmlPartsBegin);
67
+ };
68
+ processStreamOptions.injectStringAtEnd = async () => {
69
+ return await injectAtStreamEnd(injectString.htmlPartsEnd);
70
+ };
71
+ processStreamOptions.injectStringAfterFirstChunk = () => {
72
+ return injectAtStreamAfterFirstChunk();
73
+ };
73
74
  }
74
- const streamWrapper = await processStream(streamOriginal, opts);
75
+ const streamWrapper = await processStream(streamOriginal, processStreamOptions);
75
76
  return streamWrapper;
76
77
  }
77
78
  function isTemplateWrapped(something) {
@@ -1,7 +1,7 @@
1
1
  export { serializePageContextClientSide };
2
2
  export { serializePageContextAbort };
3
3
  import { stringify, isJsonSerializerError } from '@brillout/json-serializer/stringify';
4
- import { assert, assertWarning, hasProp, unique } from '../utils.js';
4
+ import { assert, assertUsage, assertWarning, getPropAccessNotation, hasProp, unique } from '../utils.js';
5
5
  import { isErrorPage } from '../../../shared/error-page.js';
6
6
  import { addIs404ToPageProps } from '../../../shared/addIs404ToPageProps.js';
7
7
  import pc from '@brillout/picocolors';
@@ -42,17 +42,27 @@ function serializePageContextClientSide(pageContext) {
42
42
  let hasWarned = false;
43
43
  const propsNonSerializable = [];
44
44
  passToClient.forEach((prop) => {
45
- const propName = JSON.stringify(prop);
46
- const varName = h(`pageContext[${propName}]`);
45
+ const propName1 = getPropAccessNotation(prop);
46
+ const propName2 = JSON.stringify(prop);
47
+ const varName = `pageContext${propName1}`;
47
48
  try {
48
49
  serialize(pageContext[prop], varName);
49
50
  }
50
51
  catch (err) {
51
52
  hasWarned = true;
52
53
  propsNonSerializable.push(prop);
54
+ // useConfig() wrong usage
55
+ if (prop === '_configFromHook') {
56
+ let pathString = '';
57
+ if (isJsonSerializerError(err)) {
58
+ pathString = err.pathString;
59
+ }
60
+ assertUsage(false, `Cannot serialize config ${h(pathString)} set by useConfig(), see https://vike.dev/useConfig#serialization-error`);
61
+ }
62
+ // Non-serializable pageContext set by the user
53
63
  let msg = [
54
- `${varName} cannot be serialized and, therefore, cannot be passed to the client.`,
55
- `Make sure that ${varName} is serializable, or remove ${h(propName)} from ${h('passToClient')}.`
64
+ `${h(varName)} can't be serialized and, therefore, can't be passed to the client side.`,
65
+ `Make sure ${h(varName)} is serializable, or remove ${h(propName2)} from ${h('passToClient')}.`
56
66
  ].join(' ');
57
67
  if (isJsonSerializerError(err)) {
58
68
  msg = `${msg} Serialization error: ${err.messageCore}.`;
@@ -51,8 +51,9 @@ declare function getStreamReadableNode(htmlRender: HtmlRender): Promise<null | S
51
51
  declare function getStreamReadableWeb(htmlRender: HtmlRender): null | StreamReadableWeb;
52
52
  declare function pipeToStreamWritableWeb(htmlRender: HtmlRender, writable: StreamWritableWeb): boolean;
53
53
  declare function pipeToStreamWritableNode(htmlRender: HtmlRender, writable: StreamWritableNode): boolean;
54
- declare function processStream(streamOriginal: StreamProviderAny, { injectStringAtBegin, injectStringAtEnd, onErrorWhileStreaming, enableEagerStreaming }: {
54
+ declare function processStream(streamOriginal: StreamProviderAny, { injectStringAtBegin, injectStringAfterFirstChunk, injectStringAtEnd, onErrorWhileStreaming, enableEagerStreaming }: {
55
55
  injectStringAtBegin?: () => Promise<string>;
56
+ injectStringAfterFirstChunk?: () => string | null;
56
57
  injectStringAtEnd?: () => Promise<string>;
57
58
  onErrorWhileStreaming: (err: unknown) => void;
58
59
  enableEagerStreaming?: boolean;
@@ -203,7 +203,7 @@ function pipeToStreamWritableNode(htmlRender, writable) {
203
203
  checkType(htmlRender);
204
204
  assert(false);
205
205
  }
206
- async function processStream(streamOriginal, { injectStringAtBegin, injectStringAtEnd, onErrorWhileStreaming, enableEagerStreaming }) {
206
+ async function processStream(streamOriginal, { injectStringAtBegin, injectStringAfterFirstChunk, injectStringAtEnd, onErrorWhileStreaming, enableEagerStreaming }) {
207
207
  const buffer = [];
208
208
  let streamOriginalHasStartedEmitting = false;
209
209
  let streamOriginalEnded = false;
@@ -215,6 +215,7 @@ async function processStream(streamOriginal, { injectStringAtBegin, injectString
215
215
  let resolve;
216
216
  let reject;
217
217
  let promiseHasResolved = false;
218
+ let injectStringAfterFirstChunk_done = false;
218
219
  const streamWrapperPromise = new Promise((resolve_, reject_) => {
219
220
  resolve = (streamWrapper) => {
220
221
  promiseHasResolved = true;
@@ -228,8 +229,8 @@ async function processStream(streamOriginal, { injectStringAtBegin, injectString
228
229
  let resolveReadyToWrite;
229
230
  const promiseReadyToWrite = new Promise((r) => (resolveReadyToWrite = r));
230
231
  if (injectStringAtBegin) {
231
- const injectionBegin = await injectStringAtBegin();
232
- writeStream(injectionBegin); // Adds injectionBegin to buffer
232
+ const injectedChunk = await injectStringAtBegin();
233
+ writeStream(injectedChunk); // Adds injectedChunk to buffer
233
234
  flushStream(); // Sets shouldFlushStream to true
234
235
  }
235
236
  // We call onStreamEvent() also when the stream ends in order to properly handle the situation when the stream didn't emit any data
@@ -259,6 +260,12 @@ async function processStream(streamOriginal, { injectStringAtBegin, injectString
259
260
  onData(chunk) {
260
261
  onStreamDataOrEnd(() => {
261
262
  writeStream(chunk);
263
+ if (injectStringAfterFirstChunk && !injectStringAfterFirstChunk_done) {
264
+ const injectedChunk = injectStringAfterFirstChunk();
265
+ if (injectedChunk !== null)
266
+ writeStream(injectedChunk);
267
+ injectStringAfterFirstChunk_done = true;
268
+ }
262
269
  });
263
270
  },
264
271
  async onEnd(
@@ -273,8 +280,8 @@ async function processStream(streamOriginal, { injectStringAtBegin, injectString
273
280
  streamOriginalEnded = true;
274
281
  });
275
282
  if (injectStringAtEnd) {
276
- const injectEnd = await injectStringAtEnd();
277
- writeStream(injectEnd);
283
+ const injectedChunk = await injectStringAtEnd();
284
+ writeStream(injectedChunk);
278
285
  }
279
286
  await promiseReadyToWrite; // E.g. if the user calls the pipe wrapper after the original writable has ended
280
287
  assert(isReady());
@@ -23,7 +23,7 @@ async function executeOnRenderHtmlHook(pageContext) {
23
23
  const onErrorWhileStreaming = (err) => {
24
24
  // Should the stream inject the following?
25
25
  // ```
26
- // <script>console.error("An error occurred on the server while streaming the app to HTML. Check the server logs for more information.")</script>
26
+ // <script>console.error("An error occurred on the server side while streaming the page to HTML, see server logs.")</script>
27
27
  // ```
28
28
  logRuntimeError(err, pageContext._httpRequestId);
29
29
  if (!pageContext.errorWhileRendering) {