vike 0.4.179 → 0.4.180
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.
- package/dist/cjs/node/plugin/plugins/importUserCode/v1-design/getVikeConfig/configDefinitionsBuiltIn.js +3 -0
- package/dist/cjs/node/runtime/html/injectAssets/getHtmlTags.js +46 -15
- package/dist/cjs/node/runtime/html/injectAssets/injectHtmlTags.js +3 -2
- package/dist/cjs/node/runtime/html/injectAssets.js +18 -3
- package/dist/cjs/node/runtime/html/renderHtml.js +12 -11
- package/dist/cjs/node/runtime/html/serializePageContextClientSide.js +14 -4
- package/dist/cjs/node/runtime/html/stream.js +12 -5
- package/dist/cjs/node/runtime/renderPage/executeOnRenderHtmlHook.js +1 -1
- package/dist/cjs/node/runtime/renderPage.js +14 -13
- package/dist/cjs/node/runtime/utils.js +1 -0
- package/dist/cjs/shared/page-configs/serialize/serializeConfigValues.js +4 -0
- package/dist/cjs/shared/route/index.js +4 -6
- package/dist/cjs/utils/augmentType.js +10 -0
- package/dist/cjs/utils/getPropAccessNotation.js +4 -1
- package/dist/cjs/utils/objectAssign.js +2 -0
- package/dist/cjs/utils/projectInfo.js +1 -1
- package/dist/esm/client/client-routing-runtime/createPageContext.d.ts +1 -0
- package/dist/esm/client/client-routing-runtime/createPageContext.js +2 -1
- package/dist/esm/client/client-routing-runtime/getPageContextFromHooks.d.ts +5 -3
- package/dist/esm/client/client-routing-runtime/getPageContextFromHooks.js +22 -27
- package/dist/esm/client/client-routing-runtime/renderPageClientSide.js +6 -12
- package/dist/esm/client/client-routing-runtime/utils.d.ts +1 -0
- package/dist/esm/client/client-routing-runtime/utils.js +1 -0
- package/dist/esm/client/shared/getPageContextProxyForUser.js +1 -1
- package/dist/esm/node/plugin/plugins/importUserCode/v1-design/getVikeConfig/configDefinitionsBuiltIn.js +3 -0
- package/dist/esm/node/runtime/html/injectAssets/getHtmlTags.d.ts +3 -2
- package/dist/esm/node/runtime/html/injectAssets/getHtmlTags.js +46 -15
- package/dist/esm/node/runtime/html/injectAssets/injectHtmlTags.d.ts +3 -1
- package/dist/esm/node/runtime/html/injectAssets/injectHtmlTags.js +2 -1
- package/dist/esm/node/runtime/html/injectAssets.d.ts +1 -0
- package/dist/esm/node/runtime/html/injectAssets.js +19 -4
- package/dist/esm/node/runtime/html/renderHtml.js +12 -11
- package/dist/esm/node/runtime/html/serializePageContextClientSide.js +15 -5
- package/dist/esm/node/runtime/html/stream.d.ts +2 -1
- package/dist/esm/node/runtime/html/stream.js +12 -5
- package/dist/esm/node/runtime/renderPage/executeOnRenderHtmlHook.js +1 -1
- package/dist/esm/node/runtime/renderPage.js +14 -13
- package/dist/esm/node/runtime/utils.d.ts +1 -0
- package/dist/esm/node/runtime/utils.js +1 -0
- package/dist/esm/shared/page-configs/Config.d.ts +5 -0
- package/dist/esm/shared/page-configs/serialize/serializeConfigValues.js +4 -0
- package/dist/esm/shared/route/index.d.ts +1 -1
- package/dist/esm/shared/route/index.js +4 -6
- package/dist/esm/shared/types.d.ts +1 -1
- package/dist/esm/utils/augmentType.d.ts +3 -0
- package/dist/esm/utils/augmentType.js +7 -0
- package/dist/esm/utils/getPropAccessNotation.js +4 -1
- package/dist/esm/utils/objectAssign.d.ts +1 -1
- package/dist/esm/utils/objectAssign.js +2 -0
- package/dist/esm/utils/projectInfo.d.ts +2 -2
- package/dist/esm/utils/projectInfo.js +1 -1
- 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
|
-
|
|
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
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
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
|
|
43
|
+
return pageContext;
|
|
45
44
|
}
|
|
46
45
|
async function getPageContextFromHooks_isNotHydration(pageContext, isErrorPage) {
|
|
47
|
-
|
|
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(
|
|
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(
|
|
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(
|
|
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,
|
|
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,
|
|
97
|
-
|
|
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(
|
|
99
|
+
objectAssign(pageContext, {
|
|
105
100
|
_hasPageContextFromServer: hasPageContextFromServer
|
|
106
101
|
});
|
|
107
|
-
return {
|
|
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
|
|
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
|
|
135
|
+
let pageContextAugmented;
|
|
136
136
|
try {
|
|
137
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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) {
|
|
@@ -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}
|
|
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
|
}
|
|
@@ -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:
|
|
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
|
-
//
|
|
77
|
-
//
|
|
78
|
-
//
|
|
79
|
-
//
|
|
80
|
-
//
|
|
81
|
-
//
|
|
82
|
-
//
|
|
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
|
-
|
|
87
|
-
//
|
|
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
|
-
|
|
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:
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
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,
|
|
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
|
|
46
|
-
const
|
|
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}
|
|
55
|
-
`Make sure
|
|
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
|
|
232
|
-
writeStream(
|
|
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
|
|
277
|
-
writeStream(
|
|
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
|
|
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) {
|