@vidact/start 0.2.0-beta.6 → 0.2.0-beta.8
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/CHANGELOG.md +22 -0
- package/README.md +20 -5
- package/dist/client.d.ts.map +1 -1
- package/dist/client.js +104 -9
- package/dist/client.js.map +1 -1
- package/dist/link-client.js +3 -1
- package/dist/link-client.js.map +1 -1
- package/dist/server.d.ts +12 -1
- package/dist/server.d.ts.map +1 -1
- package/dist/server.js +72 -9
- package/dist/server.js.map +1 -1
- package/package.json +3 -3
- package/src/client.ts +158 -13
- package/src/link-client.ts +3 -2
- package/src/server.ts +98 -14
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,27 @@
|
|
|
1
1
|
# @vidact/start
|
|
2
2
|
|
|
3
|
+
## 0.2.0-beta.8
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- Updated dependencies [e480658]
|
|
8
|
+
- @vidact/runtime@0.2.0-beta.8
|
|
9
|
+
- @vidact/vite@0.2.0-beta.8
|
|
10
|
+
|
|
11
|
+
## 0.2.0-beta.7
|
|
12
|
+
|
|
13
|
+
### Patch Changes
|
|
14
|
+
|
|
15
|
+
- 5d2dd5d: Preserve destination fragments through successful client navigation and redirects.
|
|
16
|
+
- 977b90f: Retain unchanged Start route owners and update their loader, parameter, URL, and child props during client navigation.
|
|
17
|
+
- d35d98f: Stream the Start document shell before pending application resources and support streaming custom document shells.
|
|
18
|
+
- 8ce4ca8: Separate cached document and client-navigation responses with a Vary header.
|
|
19
|
+
- Updated dependencies [ed60a1c]
|
|
20
|
+
- Updated dependencies [977b90f]
|
|
21
|
+
- Updated dependencies [9581420]
|
|
22
|
+
- @vidact/runtime@0.2.0-beta.7
|
|
23
|
+
- @vidact/vite@0.2.0-beta.7
|
|
24
|
+
|
|
3
25
|
## 0.2.0-beta.6
|
|
4
26
|
|
|
5
27
|
### Patch Changes
|
package/README.md
CHANGED
|
@@ -45,8 +45,8 @@ application. Route components must be named functions so the Vidact compiler
|
|
|
45
45
|
can identify and lower them.
|
|
46
46
|
|
|
47
47
|
`Link` renders an ordinary anchor during SSR. After hydration, unmodified clicks
|
|
48
|
-
on same-origin Start routes request a server loader snapshot,
|
|
49
|
-
route
|
|
48
|
+
on same-origin Start routes request a server loader snapshot, update the retained
|
|
49
|
+
route chain, and update browser history without loading another document.
|
|
50
50
|
External URLs, downloads, modified clicks, `target` links, hash-only links, and
|
|
51
51
|
links with `reloadDocument` retain native browser behavior. Use `replace` to
|
|
52
52
|
replace the current history entry.
|
|
@@ -60,6 +60,21 @@ replace the current history entry.
|
|
|
60
60
|
`hydrateStart()` returns a `StartClient` whose `navigate()` method provides the
|
|
61
61
|
same behavior for programmatic navigation.
|
|
62
62
|
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
63
|
+
Document HTML and client-navigation snapshots share the same route URL. Start
|
|
64
|
+
marks both successful representations with `Vary: x-vidact-start-navigation`,
|
|
65
|
+
so HTTP caches that honor `Vary` keep them separate. Deployments must preserve
|
|
66
|
+
that response header when adding their own caching rules.
|
|
67
|
+
|
|
68
|
+
The built-in document streams its opening shell as soon as loaders settle. The
|
|
69
|
+
marker-complete application follows after its async resources settle, then the
|
|
70
|
+
snapshot and client script close the document. `renderDocumentShell` customizes
|
|
71
|
+
the strings before and after the application while preserving this behavior.
|
|
72
|
+
The older `renderDocument` callback still receives one `applicationHtml` string
|
|
73
|
+
and therefore buffers the application before returning a response.
|
|
74
|
+
|
|
75
|
+
Start retains the longest common prefix of component-bearing route entries.
|
|
76
|
+
Those components keep their DOM, local state, refs, and effects while their
|
|
77
|
+
`loaderData`, `params`, `requestUrl`, and `children` props update. The first
|
|
78
|
+
changed route entry and all descendants are staged as a new owned suffix, then
|
|
79
|
+
the previous suffix is disposed. Routes without a component do not establish a
|
|
80
|
+
DOM owner.
|
package/dist/client.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"client.d.ts","names":[],"sources":["../src/client.ts"],"mappings":";;;
|
|
1
|
+
{"version":3,"file":"client.d.ts","names":[],"sources":["../src/client.ts"],"mappings":";;;UAgCiB;WACN,eAAe,WAAW;WAC1B,UAAU;;;;;;WAMV,sBAAsB;WACtB,OAAO;WACP;WACA;WACA;;UAGM;WACN;WACA;;UAGM,oBAAoB;WAC1B,WAAW,aAAa,KAAK,UAAU,yBAAyB;;iBAyBrD,aAAa,SAAS,sBAAsB,QAAQ"}
|
package/dist/client.js
CHANGED
|
@@ -1,10 +1,11 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { loadRouteMatches, matchRoutes } from "./router.js";
|
|
2
2
|
import { VIDACT_START_NAVIGATION_HEADER, decodeStartSnapshot } from "./snapshot.js";
|
|
3
|
-
import { cloneRenderable, compiledRoot, createCompiledScope } from "@vidact/runtime";
|
|
4
|
-
import {
|
|
3
|
+
import { binding, cloneRenderable, compiledRoot, createCompiledScope, createCompiledState, createComponentRenderable, source } from "@vidact/runtime";
|
|
4
|
+
import { hydrateRoot } from "@vidact/runtime/hydrate";
|
|
5
5
|
//#region src/client.ts
|
|
6
6
|
const DEFAULT_ROOT_ID = "vidact-start-root";
|
|
7
7
|
const DEFAULT_SNAPSHOT_ID = "vidact-start-snapshot";
|
|
8
|
+
const routeComponentIdentities = /* @__PURE__ */ new WeakMap();
|
|
8
9
|
async function hydrateStart(options) {
|
|
9
10
|
const snapshot = decodeStartSnapshot(options.snapshot ?? readSnapshot(options.snapshotId ?? DEFAULT_SNAPSHOT_ID));
|
|
10
11
|
const initialUrl = new URL(snapshot.pathname, window.location.href);
|
|
@@ -14,7 +15,8 @@ async function hydrateStart(options) {
|
|
|
14
15
|
const loaded = await loadRouteMatches(matches, request, snapshot.loaderData);
|
|
15
16
|
const host = options.root ?? document.querySelector(`#${CSS.escape(options.rootId ?? DEFAULT_ROOT_ID)}`);
|
|
16
17
|
if (host === null) throw new Error("Vidact Start hydration root is missing");
|
|
17
|
-
|
|
18
|
+
let application = createClientApplication(loaded, request.url);
|
|
19
|
+
const root = hydrateRoot(host, application.application, { onRecoverableError: options.onRecoverableError ?? ((error) => console.warn("[vidact/start] hydration recovered by re-rendering:", error)) });
|
|
18
20
|
const fetchNavigation = options.fetch ?? window.fetch.bind(window);
|
|
19
21
|
let navigation = 0;
|
|
20
22
|
let navigationController;
|
|
@@ -50,6 +52,7 @@ async function hydrateStart(options) {
|
|
|
50
52
|
}
|
|
51
53
|
const nextSnapshot = decodeStartSnapshot(await response.text());
|
|
52
54
|
const nextUrl = new URL(nextSnapshot.pathname, target.origin);
|
|
55
|
+
if (nextUrl.hash === "") nextUrl.hash = target.hash;
|
|
53
56
|
const snapshotMatches = matchRoutes(options.manifest, nextUrl.pathname);
|
|
54
57
|
if (snapshotMatches.length === 0) {
|
|
55
58
|
navigateDocument(target, historyMode === "replace");
|
|
@@ -58,7 +61,12 @@ async function hydrateStart(options) {
|
|
|
58
61
|
const nextRequest = new Request(nextUrl);
|
|
59
62
|
const nextLoaded = await loadRouteMatches(snapshotMatches, nextRequest, nextSnapshot.loaderData);
|
|
60
63
|
if (attempt !== navigation) return false;
|
|
61
|
-
|
|
64
|
+
const nextState = createClientRouteState(nextLoaded, nextRequest.url);
|
|
65
|
+
if (sharedRouteComponentCount(application.state, nextState) === 0) {
|
|
66
|
+
const replacement = createClientApplication(nextLoaded, nextRequest.url);
|
|
67
|
+
root.replace(replacement.application);
|
|
68
|
+
application = replacement;
|
|
69
|
+
} else application.update(nextState);
|
|
62
70
|
updateHistory(nextUrl, historyMode);
|
|
63
71
|
if (scroll) scrollToLocation(nextUrl);
|
|
64
72
|
return true;
|
|
@@ -97,12 +105,99 @@ async function hydrateStart(options) {
|
|
|
97
105
|
};
|
|
98
106
|
}
|
|
99
107
|
function createClientApplication(matches, requestUrl) {
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
108
|
+
let currentState = createClientRouteState(matches, requestUrl);
|
|
109
|
+
let updateState;
|
|
110
|
+
return {
|
|
111
|
+
application: () => {
|
|
112
|
+
const scope = createCompiledScope();
|
|
113
|
+
const routeSource = source(0);
|
|
114
|
+
const state = createCompiledState(scope, routeSource, currentState);
|
|
115
|
+
updateState = (nextState) => {
|
|
116
|
+
const previousState = currentState;
|
|
117
|
+
try {
|
|
118
|
+
state.set(nextState);
|
|
119
|
+
currentState = nextState;
|
|
120
|
+
} catch (error) {
|
|
121
|
+
try {
|
|
122
|
+
state.set(previousState);
|
|
123
|
+
} catch {}
|
|
124
|
+
currentState = previousState;
|
|
125
|
+
throw error;
|
|
126
|
+
}
|
|
127
|
+
};
|
|
128
|
+
const route = createRetainedRouteRenderable(scope, routeSource, state.get, 0);
|
|
129
|
+
return compiledRoot(scope, () => cloneRenderable(route));
|
|
130
|
+
},
|
|
131
|
+
get state() {
|
|
132
|
+
return currentState;
|
|
133
|
+
},
|
|
134
|
+
update(nextState) {
|
|
135
|
+
if (updateState === void 0) throw new Error("cannot update a Vidact Start application before it mounts");
|
|
136
|
+
updateState(nextState);
|
|
137
|
+
}
|
|
138
|
+
};
|
|
139
|
+
}
|
|
140
|
+
function createClientRouteState(matches, requestUrl) {
|
|
141
|
+
return {
|
|
142
|
+
components: matches.flatMap((match) => {
|
|
143
|
+
const component = match.definition.options.component;
|
|
144
|
+
return component === void 0 ? [] : [{
|
|
145
|
+
component,
|
|
146
|
+
entry: match.entry,
|
|
147
|
+
loaderData: match.loaderData,
|
|
148
|
+
params: match.params
|
|
149
|
+
}];
|
|
150
|
+
}),
|
|
151
|
+
requestUrl
|
|
104
152
|
};
|
|
105
153
|
}
|
|
154
|
+
function createRetainedRouteRenderable(scope, routeSource, readState, index) {
|
|
155
|
+
const initial = readState().components[index];
|
|
156
|
+
if (initial === void 0) return void 0;
|
|
157
|
+
const identity = routeComponentIdentity(initial.entry, initial.component);
|
|
158
|
+
let lastProps = routeComponentProps(scope, routeSource, readState, index);
|
|
159
|
+
const input = binding(scope, routeSource, () => {
|
|
160
|
+
const current = readState().components[index];
|
|
161
|
+
if (current === void 0 || routeComponentIdentity(current.entry, current.component) !== identity) return lastProps;
|
|
162
|
+
lastProps = routeComponentProps(scope, routeSource, readState, index);
|
|
163
|
+
return lastProps;
|
|
164
|
+
});
|
|
165
|
+
return createComponentRenderable(initial.component, input, identity);
|
|
166
|
+
}
|
|
167
|
+
function routeComponentProps(scope, routeSource, readState, index) {
|
|
168
|
+
const state = readState();
|
|
169
|
+
const match = state.components[index];
|
|
170
|
+
return {
|
|
171
|
+
children: createRetainedRouteRenderable(scope, routeSource, readState, index + 1),
|
|
172
|
+
loaderData: match.loaderData,
|
|
173
|
+
params: match.params,
|
|
174
|
+
requestUrl: state.requestUrl
|
|
175
|
+
};
|
|
176
|
+
}
|
|
177
|
+
function sharedRouteComponentCount(left, right) {
|
|
178
|
+
const length = Math.min(left.components.length, right.components.length);
|
|
179
|
+
let index = 0;
|
|
180
|
+
while (index < length) {
|
|
181
|
+
const previous = left.components[index];
|
|
182
|
+
const next = right.components[index];
|
|
183
|
+
if (previous.entry !== next.entry || previous.component !== next.component) break;
|
|
184
|
+
index += 1;
|
|
185
|
+
}
|
|
186
|
+
return index;
|
|
187
|
+
}
|
|
188
|
+
function routeComponentIdentity(entry, component) {
|
|
189
|
+
let componentIdentities = routeComponentIdentities.get(entry);
|
|
190
|
+
if (componentIdentities === void 0) {
|
|
191
|
+
componentIdentities = /* @__PURE__ */ new WeakMap();
|
|
192
|
+
routeComponentIdentities.set(entry, componentIdentities);
|
|
193
|
+
}
|
|
194
|
+
let identity = componentIdentities.get(component);
|
|
195
|
+
if (identity === void 0) {
|
|
196
|
+
identity = {};
|
|
197
|
+
componentIdentities.set(component, identity);
|
|
198
|
+
}
|
|
199
|
+
return identity;
|
|
200
|
+
}
|
|
106
201
|
function linkForEvent(event) {
|
|
107
202
|
if (event.defaultPrevented || event.button !== 0 || event.metaKey || event.ctrlKey || event.shiftKey || event.altKey) return;
|
|
108
203
|
const target = event.target;
|
package/dist/client.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"client.js","names":[],"sources":["../src/client.ts"],"mappings":";;;;;
|
|
1
|
+
{"version":3,"file":"client.js","names":[],"sources":["../src/client.ts"],"mappings":"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"}
|
package/dist/link-client.js
CHANGED
|
@@ -1,8 +1,10 @@
|
|
|
1
1
|
import { anchorProps } from "./link-contract.js";
|
|
2
|
+
import { compiledRoot, createNarrowCompiledScope } from "@vidact/runtime";
|
|
2
3
|
import { createElement } from "@vidact/runtime/hydrate";
|
|
3
4
|
//#region src/link-client.ts
|
|
4
5
|
function Link(props) {
|
|
5
|
-
|
|
6
|
+
const scope = createNarrowCompiledScope();
|
|
7
|
+
return compiledRoot(scope, () => createElement("a", anchorProps(props)));
|
|
6
8
|
}
|
|
7
9
|
//#endregion
|
|
8
10
|
export { Link };
|
package/dist/link-client.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"link-client.js","names":[],"sources":["../src/link-client.ts"],"mappings":"
|
|
1
|
+
{"version":3,"file":"link-client.js","names":[],"sources":["../src/link-client.ts"],"mappings":";;;;AAKA,SAAgB,KAAK,OAAuC;CAC1D,MAAM,QAAQ,0BAA0B;CACxC,OAAO,aAAa,aAAa,cAAc,KAAK,YAAY,KAAK,CAAC,CAAC;AACzE"}
|
package/dist/server.d.ts
CHANGED
|
@@ -7,15 +7,26 @@ interface StartDocumentContext {
|
|
|
7
7
|
readonly snapshot: string;
|
|
8
8
|
readonly snapshotId: string;
|
|
9
9
|
}
|
|
10
|
+
interface StartDocumentShellContext {
|
|
11
|
+
readonly clientEntry: string | undefined;
|
|
12
|
+
readonly rootId: string;
|
|
13
|
+
readonly snapshot: string;
|
|
14
|
+
readonly snapshotId: string;
|
|
15
|
+
}
|
|
16
|
+
interface StartDocumentShell {
|
|
17
|
+
readonly afterApplication: string;
|
|
18
|
+
readonly beforeApplication: string;
|
|
19
|
+
}
|
|
10
20
|
interface StartHandlerOptions {
|
|
11
21
|
readonly clientEntry?: string;
|
|
12
22
|
readonly manifest: RouteManifest;
|
|
13
23
|
readonly notFound?: (request: Request) => Response | Promise<Response>;
|
|
14
24
|
readonly renderDocument?: (context: StartDocumentContext) => string | Promise<string>;
|
|
25
|
+
readonly renderDocumentShell?: (context: StartDocumentShellContext) => StartDocumentShell | Promise<StartDocumentShell>;
|
|
15
26
|
readonly rootId?: string;
|
|
16
27
|
readonly snapshotId?: string;
|
|
17
28
|
}
|
|
18
29
|
declare function createStartHandler(options: StartHandlerOptions): (request: Request) => Promise<Response>;
|
|
19
30
|
//#endregion
|
|
20
|
-
export { type StartComponent, StartDocumentContext, StartHandlerOptions, createStartHandler };
|
|
31
|
+
export { type StartComponent, StartDocumentContext, StartDocumentShell, StartDocumentShellContext, StartHandlerOptions, createStartHandler };
|
|
21
32
|
//# sourceMappingURL=server.d.ts.map
|
package/dist/server.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"server.d.ts","names":[],"sources":["../src/server.ts"],"mappings":";;UAkCiB;WACN;WACA;WACA;WACA;WACA;;UAGM;WACN;WACA,UAAU;WACV,YAAY,SAAS,YAAY,WAAW,QAAQ;WACpD,kBAAkB,SAAS,kCAAkC;WAC7D;WACA;;iBAGK,mBACd,SAAS,uBACP,SAAS,YAAY,QAAQ"}
|
|
1
|
+
{"version":3,"file":"server.d.ts","names":[],"sources":["../src/server.ts"],"mappings":";;UAkCiB;WACN;WACA;WACA;WACA;WACA;;UAGM;WACN;WACA;WACA;WACA;;UAGM;WACN;WACA;;UAGM;WACN;WACA,UAAU;WACV,YAAY,SAAS,YAAY,WAAW,QAAQ;WACpD,kBAAkB,SAAS,kCAAkC;WAC7D,uBACP,SAAS,8BACN,qBAAqB,QAAQ;WACzB;WACA;;iBAGK,mBACd,SAAS,uBACP,SAAS,YAAY,QAAQ"}
|
package/dist/server.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { composeRouteMatches, matchRoutes, resolveRouteMatches, routeLoaderData, runRouteLoaders } from "./router.js";
|
|
2
|
-
import { VIDACT_START_PROTOCOL, VIDACT_START_SNAPSHOT_MEDIA_TYPE, encodeStartSnapshot } from "./snapshot.js";
|
|
2
|
+
import { VIDACT_START_NAVIGATION_HEADER, VIDACT_START_PROTOCOL, VIDACT_START_SNAPSHOT_MEDIA_TYPE, encodeStartSnapshot } from "./snapshot.js";
|
|
3
3
|
import { createElement } from "@vidact/runtime/server";
|
|
4
4
|
import { renderToReadableStream } from "@vidact/runtime/framework/server";
|
|
5
5
|
//#region src/server.ts
|
|
@@ -15,6 +15,7 @@ function StartApplication({ children }) {
|
|
|
15
15
|
const DEFAULT_ROOT_ID = "vidact-start-root";
|
|
16
16
|
const DEFAULT_SNAPSHOT_ID = "vidact-start-snapshot";
|
|
17
17
|
function createStartHandler(options) {
|
|
18
|
+
if (options.renderDocument !== void 0 && options.renderDocumentShell !== void 0) throw new TypeError("renderDocument and renderDocumentShell cannot be used together");
|
|
18
19
|
const rootId = options.rootId ?? DEFAULT_ROOT_ID;
|
|
19
20
|
const snapshotId = options.snapshotId ?? DEFAULT_SNAPSHOT_ID;
|
|
20
21
|
return async (request) => {
|
|
@@ -48,20 +49,38 @@ function createStartHandler(options) {
|
|
|
48
49
|
pathname: `${url.pathname}${url.search}`,
|
|
49
50
|
loaderData: routeLoaderData(loaded)
|
|
50
51
|
}));
|
|
51
|
-
if (request.headers.get("x-vidact-start-navigation") === "1") return new Response(request.method === "HEAD" ? null : snapshot, { headers:
|
|
52
|
+
if (request.headers.get("x-vidact-start-navigation") === "1") return new Response(request.method === "HEAD" ? null : snapshot, { headers: representationHeaders(`${VIDACT_START_SNAPSHOT_MEDIA_TYPE}; charset=utf-8`) });
|
|
53
|
+
const headers = representationHeaders("text/html; charset=utf-8");
|
|
54
|
+
if (request.method === "HEAD") return new Response(null, { headers });
|
|
52
55
|
const application = composeRouteMatches(loaded, (component, props) => createElement(component, props), request.url);
|
|
53
|
-
const stream = await renderToReadableStream(() => createElement(StartApplication, { children: application }), {
|
|
54
|
-
|
|
55
|
-
|
|
56
|
+
const stream = await renderToReadableStream(() => createElement(StartApplication, { children: application }), {
|
|
57
|
+
identifierPrefix: "start-",
|
|
58
|
+
signal: request.signal
|
|
59
|
+
});
|
|
60
|
+
stream.allReady.catch(() => void 0);
|
|
61
|
+
const shellContext = {
|
|
56
62
|
clientEntry: options.clientEntry,
|
|
57
63
|
rootId,
|
|
58
64
|
snapshot,
|
|
59
65
|
snapshotId
|
|
60
66
|
};
|
|
61
|
-
|
|
62
|
-
|
|
67
|
+
if (options.renderDocument !== void 0) {
|
|
68
|
+
const applicationHtml = await new Response(stream).text();
|
|
69
|
+
const html = await options.renderDocument({
|
|
70
|
+
...shellContext,
|
|
71
|
+
applicationHtml
|
|
72
|
+
});
|
|
73
|
+
return new Response(html, { headers });
|
|
74
|
+
}
|
|
75
|
+
const shell = options.renderDocumentShell === void 0 ? defaultDocumentShell(shellContext) : validateDocumentShell(await options.renderDocumentShell(shellContext));
|
|
76
|
+
return new Response(streamDocument(shell, stream), { headers });
|
|
63
77
|
};
|
|
64
78
|
}
|
|
79
|
+
function representationHeaders(contentType) {
|
|
80
|
+
const headers = new Headers({ "content-type": contentType });
|
|
81
|
+
headers.append("vary", VIDACT_START_NAVIGATION_HEADER);
|
|
82
|
+
return headers;
|
|
83
|
+
}
|
|
65
84
|
function responseForRequest(request, response) {
|
|
66
85
|
return request.method === "HEAD" ? new Response(null, response) : response;
|
|
67
86
|
}
|
|
@@ -93,9 +112,53 @@ function allowedMethods(matches, canRender) {
|
|
|
93
112
|
function methodRank(method) {
|
|
94
113
|
return method === "GET" ? 0 : method === "HEAD" ? 1 : 2;
|
|
95
114
|
}
|
|
96
|
-
function
|
|
115
|
+
function defaultDocumentShell(context) {
|
|
97
116
|
const clientScript = context.clientEntry === void 0 ? "" : `<script type="module" src="${escapeAttribute(context.clientEntry)}"><\/script>`;
|
|
98
|
-
return
|
|
117
|
+
return {
|
|
118
|
+
beforeApplication: `<!doctype html><html><head><meta charset="UTF-8"><meta name="viewport" content="width=device-width, initial-scale=1.0"></head><body><div id="${escapeAttribute(context.rootId)}">`,
|
|
119
|
+
afterApplication: `</div><script id="${escapeAttribute(context.snapshotId)}" type="application/json">${context.snapshot}<\/script>${clientScript}</body></html>`
|
|
120
|
+
};
|
|
121
|
+
}
|
|
122
|
+
function validateDocumentShell(value) {
|
|
123
|
+
if (typeof value !== "object" || value === null || typeof value.beforeApplication !== "string" || typeof value.afterApplication !== "string") throw new TypeError("renderDocumentShell must return beforeApplication and afterApplication");
|
|
124
|
+
return value;
|
|
125
|
+
}
|
|
126
|
+
function streamDocument(shell, application) {
|
|
127
|
+
const encoder = new TextEncoder();
|
|
128
|
+
const before = encoder.encode(shell.beforeApplication);
|
|
129
|
+
const after = encoder.encode(shell.afterApplication);
|
|
130
|
+
const reader = application.getReader();
|
|
131
|
+
let phase = "before";
|
|
132
|
+
return new ReadableStream({
|
|
133
|
+
async pull(controller) {
|
|
134
|
+
try {
|
|
135
|
+
if (phase === "before") {
|
|
136
|
+
phase = "application";
|
|
137
|
+
if (before.length > 0) controller.enqueue(before);
|
|
138
|
+
return;
|
|
139
|
+
}
|
|
140
|
+
if (phase === "application") {
|
|
141
|
+
const result = await reader.read();
|
|
142
|
+
if (!result.done) {
|
|
143
|
+
controller.enqueue(result.value);
|
|
144
|
+
return;
|
|
145
|
+
}
|
|
146
|
+
phase = "after";
|
|
147
|
+
}
|
|
148
|
+
if (phase === "after") {
|
|
149
|
+
phase = "done";
|
|
150
|
+
if (after.length > 0) controller.enqueue(after);
|
|
151
|
+
return;
|
|
152
|
+
}
|
|
153
|
+
controller.close();
|
|
154
|
+
} catch (error) {
|
|
155
|
+
controller.error(error);
|
|
156
|
+
}
|
|
157
|
+
},
|
|
158
|
+
cancel(reason) {
|
|
159
|
+
return reader.cancel(reason);
|
|
160
|
+
}
|
|
161
|
+
});
|
|
99
162
|
}
|
|
100
163
|
function escapeAttribute(value) {
|
|
101
164
|
return value.replaceAll("&", "&").replaceAll("\"", """).replaceAll("<", "<").replaceAll(">", ">");
|
package/dist/server.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"server.js","names":[],"sources":["../src/server.ts"],"mappings":";;;;;;;;;;;AA2BA,SAAS,iBAAiB,EAAE,YAA8D;CACxF,OAAO;AACT;AAEA,MAAM,kBAAkB;AACxB,MAAM,sBAAsB;
|
|
1
|
+
{"version":3,"file":"server.js","names":[],"sources":["../src/server.ts"],"mappings":";;;;;;;;;;;AA2BA,SAAS,iBAAiB,EAAE,YAA8D;CACxF,OAAO;AACT;AAEA,MAAM,kBAAkB;AACxB,MAAM,sBAAsB;AAkC5B,SAAgB,mBACd,SACyC;CACzC,IAAI,QAAQ,mBAAmB,KAAA,KAAa,QAAQ,wBAAwB,KAAA,GAC1E,MAAM,IAAI,UAAU,gEAAgE;CAEtF,MAAM,SAAS,QAAQ,UAAU;CACjC,MAAM,aAAa,QAAQ,cAAc;CACzC,OAAO,OAAO,YAAY;EACxB,MAAM,MAAM,IAAI,IAAI,QAAQ,GAAG;EAC/B,MAAM,UAAU,YAAY,QAAQ,UAAU,IAAI,QAAQ;EAC1D,IAAI,QAAQ,WAAW,GACrB,OAAO,QAAQ,aAAa,KAAA,IACxB,IAAI,SAAS,aAAa,EAAE,QAAQ,IAAI,CAAC,IACzC,MAAM,QAAQ,SAAS,OAAO;EAEpC,MAAM,WAAW,MAAM,oBAAoB,OAAO;EAClD,MAAM,WAAW,gBAAgB,UAAU,QAAQ,MAAM;EACzD,IAAI,aAAa,KAAA,GAKf,OAAO,mBAAmB,SAAS,MAJZ,SAAS;GAC9B,QAAQ,SAAS,GAAG,EAAE,CAAC,CAAE;GACzB;EACF,CAAC,CAC0C;EAE7C,MAAM,YAAY,SAAS,MAAM,UAAU,MAAM,WAAW,QAAQ,cAAc,KAAA,CAAS;EAC3F,IAAK,QAAQ,WAAW,SAAS,QAAQ,WAAW,UAAW,CAAC,WAAW;GACzE,MAAM,QAAQ,eAAe,UAAU,SAAS;GAChD,IAAI,MAAM,WAAW,GACnB,OAAO,QAAQ,aAAa,KAAA,IACxB,IAAI,SAAS,aAAa,EAAE,QAAQ,IAAI,CAAC,IACzC,MAAM,QAAQ,SAAS,OAAO;GAEpC,OAAO,IAAI,SAAS,sBAAsB;IACxC,QAAQ;IACR,SAAS,EAAE,OAAO,MAAM,KAAK,IAAI,EAAE;GACrC,CAAC;EACH;EACA,IAAI;EACJ,IAAI;GACF,SAAS,MAAM,gBAAgB,UAAU,OAAO;EAClD,SAAS,OAAO;GACd,IAAI,WAAW,KAAK,GAAG,OAAO,mBAAmB,SAAS,KAAK;GAC/D,MAAM;EACR;EACA,MAAM,WAAW,iBACf,oBAAoB;GAClB,UAAU;GACV,UAAU,GAAG,IAAI,WAAW,IAAI;GAChC,YAAY,gBAAgB,MAAM;EACpC,CAAC,CACH;EACA,IAAI,QAAQ,QAAQ,IAAA,2BAAkC,MAAM,KAC1D,OAAO,IAAI,SAAS,QAAQ,WAAW,SAAS,OAAO,UAAU,EAC/D,SAAS,sBAAsB,GAAG,iCAAiC,gBAAgB,EACrF,CAAC;EAEH,MAAM,UAAU,sBAAsB,0BAA0B;EAChE,IAAI,QAAQ,WAAW,QAAQ,OAAO,IAAI,SAAS,MAAM,EAAE,QAAQ,CAAC;EAEpE,MAAM,cAAc,oBAClB,SACC,WAAW,UACV,cAAc,WAAyC,KAAK,GAC9D,QAAQ,GACV;EACA,MAAM,SAAS,MAAM,6BACb,cAAc,kBAAkB,EAAE,UAAU,YAAY,CAAC,GAC/D;GAAE,kBAAkB;GAAU,QAAQ,QAAQ;EAAO,CACvD;EACA,OAAY,SAAS,YAAY,KAAA,CAAS;EAC1C,MAAM,eAAe;GACnB,aAAa,QAAQ;GACrB;GACA;GACA;EACF;EACA,IAAI,QAAQ,mBAAmB,KAAA,GAAW;GACxC,MAAM,kBAAkB,MAAM,IAAI,SAAS,MAAM,CAAC,CAAC,KAAK;GACxD,MAAM,OAAO,MAAM,QAAQ,eAAe;IAAE,GAAG;IAAc;GAAgB,CAAC;GAC9E,OAAO,IAAI,SAAS,MAAM,EAAE,QAAQ,CAAC;EACvC;EACA,MAAM,QACJ,QAAQ,wBAAwB,KAAA,IAC5B,qBAAqB,YAAY,IACjC,sBAAsB,MAAM,QAAQ,oBAAoB,YAAY,CAAC;EAC3E,OAAO,IAAI,SAAS,eAAe,OAAO,MAAM,GAAG,EAAE,QAAQ,CAAC;CAChE;AACF;AAEA,SAAS,sBAAsB,aAA8B;CAC3D,MAAM,UAAU,IAAI,QAAQ,EAAE,gBAAgB,YAAY,CAAC;CAC3D,QAAQ,OAAO,QAAQ,8BAA8B;CACrD,OAAO;AACT;AAEA,SAAS,mBAAmB,SAAkB,UAA8B;CAC1E,OAAO,QAAQ,WAAW,SAAS,IAAI,SAAS,MAAM,QAAQ,IAAI;AACpE;AAEA,SAAS,WAAW,OAAmC;CACrD,IAAI,iBAAiB,UAAU,OAAO;CACtC,IACE,OAAO,UAAU,YACjB,UAAU,QACV,OAAO,UAAU,eAAe,KAAK,OAAO,OAAO,WAAW,KAC9D,OAAO,UAAU,SAAS,KAAK,KAAK,MAAM,qBAE1C,OAAO;CAET,IAAI;EACF,MAAM,YAAY;EAClB,OACE,OAAO,UAAU,gBAAgB,cACjC,OAAO,UAAU,UAAU,cAC3B,OAAO,UAAU,SAAS,QAAQ,cAClC,OAAO,UAAU,UAAU,MAAM;CAErC,QAAQ;EACN,OAAO;CACT;AACF;AAEA,SAAS,gBACP,SACA,QACgC;CAChC,MAAM,WAAW,QAAQ,GAAG,EAAE,CAAC,EAAE,WAAW,QAAQ,QAAQ;CAC5D,OAAO,WAAW,OAAO,YAAY,OAAO,WAAW,SAAS,UAAU,MAAM,KAAA;AAClF;AAEA,SAAS,eACP,SACA,WACmB;CACnB,MAAM,0BAAU,IAAI,IAAY;CAChC,IAAI,WAAW;EACb,QAAQ,IAAI,KAAK;EACjB,QAAQ,IAAI,MAAM;CACpB;CACA,MAAM,WAAW,QAAQ,GAAG,EAAE,CAAC,EAAE,WAAW,QAAQ,QAAQ;CAC5D,KAAK,MAAM,UAAU,OAAO,KAAK,YAAY,CAAC,CAAC,GAAG,QAAQ,IAAI,OAAO,YAAY,CAAC;CAClF,IAAI,QAAQ,IAAI,KAAK,GAAG,QAAQ,IAAI,MAAM;CAC1C,OAAO,CAAC,GAAG,OAAO,CAAC,CAAC,UACjB,MAAM,UAAU,WAAW,IAAI,IAAI,WAAW,KAAK,KAAK,KAAK,cAAc,KAAK,CACnF;AACF;AAEA,SAAS,WAAW,QAAwB;CAC1C,OAAO,WAAW,QAAQ,IAAI,WAAW,SAAS,IAAI;AACxD;AAEA,SAAS,qBAAqB,SAAwD;CACpF,MAAM,eACJ,QAAQ,gBAAgB,KAAA,IACpB,KACA,8BAA8B,gBAAgB,QAAQ,WAAW,EAAE;CACzE,OAAO;EACL,mBAAmB,gJAAgJ,gBAAgB,QAAQ,MAAM,EAAE;EACnM,kBAAkB,qBAAqB,gBAAgB,QAAQ,UAAU,EAAE,4BAA4B,QAAQ,SAAS,YAAW,aAAa;CAClJ;AACF;AAEA,SAAS,sBAAsB,OAA+C;CAC5E,IACE,OAAO,UAAU,YACjB,UAAU,QACV,OAAO,MAAM,sBAAsB,YACnC,OAAO,MAAM,qBAAqB,UAElC,MAAM,IAAI,UAAU,wEAAwE;CAE9F,OAAO;AACT;AAEA,SAAS,eACP,OACA,aAC4B;CAC5B,MAAM,UAAU,IAAI,YAAY;CAChC,MAAM,SAAS,QAAQ,OAAO,MAAM,iBAAiB;CACrD,MAAM,QAAQ,QAAQ,OAAO,MAAM,gBAAgB;CACnD,MAAM,SAAS,YAAY,UAAU;CACrC,IAAI,QAAqD;CACzD,OAAO,IAAI,eAA2B;EACpC,MAAM,KAAK,YAAY;GACrB,IAAI;IACF,IAAI,UAAU,UAAU;KACtB,QAAQ;KACR,IAAI,OAAO,SAAS,GAAG,WAAW,QAAQ,MAAM;KAChD;IACF;IACA,IAAI,UAAU,eAAe;KAC3B,MAAM,SAAS,MAAM,OAAO,KAAK;KACjC,IAAI,CAAC,OAAO,MAAM;MAChB,WAAW,QAAQ,OAAO,KAAK;MAC/B;KACF;KACA,QAAQ;IACV;IACA,IAAI,UAAU,SAAS;KACrB,QAAQ;KACR,IAAI,MAAM,SAAS,GAAG,WAAW,QAAQ,KAAK;KAC9C;IACF;IACA,WAAW,MAAM;GACnB,SAAS,OAAO;IACd,WAAW,MAAM,KAAK;GACxB;EACF;EACA,OAAO,QAAQ;GACb,OAAO,OAAO,OAAO,MAAM;EAC7B;CACF,CAAC;AACH;AAEA,SAAS,gBAAgB,OAAuB;CAC9C,OAAO,MACJ,WAAW,KAAK,OAAO,CAAC,CACxB,WAAW,MAAK,QAAQ,CAAC,CACzB,WAAW,KAAK,MAAM,CAAC,CACvB,WAAW,KAAK,MAAM;AAC3B;AAEA,SAAS,iBAAiB,OAAuB;CAC/C,OAAO,MAAM,WAAW,KAAK,SAAS,CAAC,CAAC,WAAW,KAAK,SAAS,CAAC,CAAC,WAAW,KAAK,SAAS;AAC9F"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@vidact/start",
|
|
3
|
-
"version": "0.2.0-beta.
|
|
3
|
+
"version": "0.2.0-beta.8",
|
|
4
4
|
"description": "Full-stack application framework primitives for Vidact",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"full-stack",
|
|
@@ -53,8 +53,8 @@
|
|
|
53
53
|
"access": "public"
|
|
54
54
|
},
|
|
55
55
|
"dependencies": {
|
|
56
|
-
"@vidact/
|
|
57
|
-
"@vidact/
|
|
56
|
+
"@vidact/vite": "0.2.0-beta.8",
|
|
57
|
+
"@vidact/runtime": "0.2.0-beta.8"
|
|
58
58
|
},
|
|
59
59
|
"devDependencies": {
|
|
60
60
|
"@types/node": "24.13.3",
|
package/src/client.ts
CHANGED
|
@@ -1,18 +1,24 @@
|
|
|
1
1
|
import {
|
|
2
|
+
binding,
|
|
2
3
|
cloneRenderable,
|
|
3
4
|
compiledRoot,
|
|
5
|
+
createComponentRenderable,
|
|
4
6
|
createCompiledScope,
|
|
7
|
+
createCompiledState,
|
|
8
|
+
source,
|
|
5
9
|
type CompiledComponentResult,
|
|
10
|
+
type CompiledRenderValue,
|
|
6
11
|
type CompiledRoot,
|
|
7
12
|
} from '@vidact/runtime'
|
|
8
|
-
import {
|
|
13
|
+
import { hydrateRoot } from '@vidact/runtime/hydrate'
|
|
9
14
|
|
|
10
15
|
import {
|
|
11
|
-
composeRouteMatches,
|
|
12
16
|
loadRouteMatches,
|
|
13
17
|
matchRoutes,
|
|
14
18
|
type LoadedRouteMatch,
|
|
19
|
+
type RouteComponentProps,
|
|
15
20
|
type RouteManifest,
|
|
21
|
+
type RouteManifestEntry,
|
|
16
22
|
type StartComponent,
|
|
17
23
|
} from './router.ts'
|
|
18
24
|
import {
|
|
@@ -50,6 +56,26 @@ export interface StartClient extends CompiledRoot {
|
|
|
50
56
|
|
|
51
57
|
type HistoryMode = 'none' | 'push' | 'replace'
|
|
52
58
|
|
|
59
|
+
interface ClientRouteState {
|
|
60
|
+
readonly components: readonly ClientRouteComponent[]
|
|
61
|
+
readonly requestUrl: string
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
interface ClientRouteComponent {
|
|
65
|
+
readonly component: StartComponent
|
|
66
|
+
readonly entry: RouteManifestEntry
|
|
67
|
+
readonly loaderData: LoadedRouteMatch['loaderData']
|
|
68
|
+
readonly params: LoadedRouteMatch['params']
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
interface RetainedClientApplication {
|
|
72
|
+
readonly application: () => CompiledComponentResult
|
|
73
|
+
readonly state: ClientRouteState
|
|
74
|
+
readonly update: (state: ClientRouteState) => void
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
const routeComponentIdentities = new WeakMap<RouteManifestEntry, WeakMap<StartComponent, object>>()
|
|
78
|
+
|
|
53
79
|
export async function hydrateStart(options: HydrateStartOptions): Promise<StartClient> {
|
|
54
80
|
const snapshot = decodeStartSnapshot(
|
|
55
81
|
options.snapshot ?? readSnapshot(options.snapshotId ?? DEFAULT_SNAPSHOT_ID),
|
|
@@ -63,7 +89,8 @@ export async function hydrateStart(options: HydrateStartOptions): Promise<StartC
|
|
|
63
89
|
options.root ?? document.querySelector(`#${CSS.escape(options.rootId ?? DEFAULT_ROOT_ID)}`)
|
|
64
90
|
if (host === null) throw new Error('Vidact Start hydration root is missing')
|
|
65
91
|
|
|
66
|
-
|
|
92
|
+
let application = createClientApplication(loaded, request.url)
|
|
93
|
+
const root = hydrateRoot(host, application.application, {
|
|
67
94
|
onRecoverableError:
|
|
68
95
|
options.onRecoverableError ??
|
|
69
96
|
((error) => console.warn('[vidact/start] hydration recovered by re-rendering:', error)),
|
|
@@ -123,6 +150,9 @@ export async function hydrateStart(options: HydrateStartOptions): Promise<StartC
|
|
|
123
150
|
|
|
124
151
|
const nextSnapshot = decodeStartSnapshot(await response.text())
|
|
125
152
|
const nextUrl = new URL(nextSnapshot.pathname, target.origin)
|
|
153
|
+
// Fragments never reach the server request. Preserve the caller's fragment unless
|
|
154
|
+
// the snapshot explicitly supplies one, including when the server redirects the path.
|
|
155
|
+
if (nextUrl.hash === '') nextUrl.hash = target.hash
|
|
126
156
|
const snapshotMatches = matchRoutes(options.manifest, nextUrl.pathname)
|
|
127
157
|
if (snapshotMatches.length === 0) {
|
|
128
158
|
navigateDocument(target, historyMode === 'replace')
|
|
@@ -136,7 +166,14 @@ export async function hydrateStart(options: HydrateStartOptions): Promise<StartC
|
|
|
136
166
|
)
|
|
137
167
|
if (attempt !== navigation) return false
|
|
138
168
|
|
|
139
|
-
|
|
169
|
+
const nextState = createClientRouteState(nextLoaded, nextRequest.url)
|
|
170
|
+
if (sharedRouteComponentCount(application.state, nextState) === 0) {
|
|
171
|
+
const replacement = createClientApplication(nextLoaded, nextRequest.url)
|
|
172
|
+
root.replace(replacement.application)
|
|
173
|
+
application = replacement
|
|
174
|
+
} else {
|
|
175
|
+
application.update(nextState)
|
|
176
|
+
}
|
|
140
177
|
updateHistory(nextUrl, historyMode)
|
|
141
178
|
if (scroll) scrollToLocation(nextUrl)
|
|
142
179
|
return true
|
|
@@ -192,16 +229,124 @@ export async function hydrateStart(options: HydrateStartOptions): Promise<StartC
|
|
|
192
229
|
function createClientApplication(
|
|
193
230
|
matches: readonly LoadedRouteMatch[],
|
|
194
231
|
requestUrl: string,
|
|
195
|
-
):
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
232
|
+
): RetainedClientApplication {
|
|
233
|
+
const initialState = createClientRouteState(matches, requestUrl)
|
|
234
|
+
let currentState = initialState
|
|
235
|
+
let updateState: ((state: ClientRouteState) => void) | undefined
|
|
236
|
+
return {
|
|
237
|
+
application: () => {
|
|
238
|
+
const scope = createCompiledScope()
|
|
239
|
+
const routeSource = source(0)
|
|
240
|
+
const state = createCompiledState(scope, routeSource, currentState)
|
|
241
|
+
updateState = (nextState) => {
|
|
242
|
+
const previousState = currentState
|
|
243
|
+
try {
|
|
244
|
+
state.set(nextState)
|
|
245
|
+
currentState = nextState
|
|
246
|
+
} catch (error) {
|
|
247
|
+
try {
|
|
248
|
+
state.set(previousState)
|
|
249
|
+
} catch {
|
|
250
|
+
// Preserve the navigation failure that triggered the rollback.
|
|
251
|
+
}
|
|
252
|
+
currentState = previousState
|
|
253
|
+
throw error
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
const route = createRetainedRouteRenderable(scope, routeSource, state.get, 0)
|
|
257
|
+
return compiledRoot(scope, () => cloneRenderable(route))
|
|
258
|
+
},
|
|
259
|
+
get state() {
|
|
260
|
+
return currentState
|
|
261
|
+
},
|
|
262
|
+
update(nextState) {
|
|
263
|
+
if (updateState === undefined) {
|
|
264
|
+
throw new Error('cannot update a Vidact Start application before it mounts')
|
|
265
|
+
}
|
|
266
|
+
updateState(nextState)
|
|
267
|
+
},
|
|
268
|
+
}
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
function createClientRouteState(
|
|
272
|
+
matches: readonly LoadedRouteMatch[],
|
|
273
|
+
requestUrl: string,
|
|
274
|
+
): ClientRouteState {
|
|
275
|
+
return {
|
|
276
|
+
components: matches.flatMap((match) => {
|
|
277
|
+
const component = match.definition.options.component
|
|
278
|
+
return component === undefined
|
|
279
|
+
? []
|
|
280
|
+
: [{ component, entry: match.entry, loaderData: match.loaderData, params: match.params }]
|
|
281
|
+
}),
|
|
282
|
+
requestUrl,
|
|
283
|
+
}
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
function createRetainedRouteRenderable(
|
|
287
|
+
scope: ReturnType<typeof createCompiledScope>,
|
|
288
|
+
routeSource: ReturnType<typeof source>,
|
|
289
|
+
readState: () => ClientRouteState,
|
|
290
|
+
index: number,
|
|
291
|
+
): CompiledRenderValue {
|
|
292
|
+
const initial = readState().components[index]
|
|
293
|
+
if (initial === undefined) return undefined
|
|
294
|
+
const identity = routeComponentIdentity(initial.entry, initial.component)
|
|
295
|
+
let lastProps = routeComponentProps(scope, routeSource, readState, index)
|
|
296
|
+
const input = binding(scope, routeSource, () => {
|
|
297
|
+
const current = readState().components[index]
|
|
298
|
+
if (
|
|
299
|
+
current === undefined ||
|
|
300
|
+
routeComponentIdentity(current.entry, current.component) !== identity
|
|
301
|
+
) {
|
|
302
|
+
return lastProps
|
|
303
|
+
}
|
|
304
|
+
lastProps = routeComponentProps(scope, routeSource, readState, index)
|
|
305
|
+
return lastProps
|
|
306
|
+
})
|
|
307
|
+
return createComponentRenderable(initial.component as never, input, identity)
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
function routeComponentProps(
|
|
311
|
+
scope: ReturnType<typeof createCompiledScope>,
|
|
312
|
+
routeSource: ReturnType<typeof source>,
|
|
313
|
+
readState: () => ClientRouteState,
|
|
314
|
+
index: number,
|
|
315
|
+
): RouteComponentProps {
|
|
316
|
+
const state = readState()
|
|
317
|
+
const match = state.components[index]!
|
|
318
|
+
return {
|
|
319
|
+
children: createRetainedRouteRenderable(scope, routeSource, readState, index + 1),
|
|
320
|
+
loaderData: match.loaderData,
|
|
321
|
+
params: match.params,
|
|
322
|
+
requestUrl: state.requestUrl,
|
|
323
|
+
}
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
function sharedRouteComponentCount(left: ClientRouteState, right: ClientRouteState): number {
|
|
327
|
+
const length = Math.min(left.components.length, right.components.length)
|
|
328
|
+
let index = 0
|
|
329
|
+
while (index < length) {
|
|
330
|
+
const previous = left.components[index]!
|
|
331
|
+
const next = right.components[index]!
|
|
332
|
+
if (previous.entry !== next.entry || previous.component !== next.component) break
|
|
333
|
+
index += 1
|
|
334
|
+
}
|
|
335
|
+
return index
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
function routeComponentIdentity(entry: RouteManifestEntry, component: StartComponent): object {
|
|
339
|
+
let componentIdentities = routeComponentIdentities.get(entry)
|
|
340
|
+
if (componentIdentities === undefined) {
|
|
341
|
+
componentIdentities = new WeakMap()
|
|
342
|
+
routeComponentIdentities.set(entry, componentIdentities)
|
|
343
|
+
}
|
|
344
|
+
let identity = componentIdentities.get(component)
|
|
345
|
+
if (identity === undefined) {
|
|
346
|
+
identity = {}
|
|
347
|
+
componentIdentities.set(component, identity)
|
|
204
348
|
}
|
|
349
|
+
return identity
|
|
205
350
|
}
|
|
206
351
|
|
|
207
352
|
function linkForEvent(event: MouseEvent): HTMLAnchorElement | undefined {
|
package/src/link-client.ts
CHANGED
|
@@ -1,10 +1,11 @@
|
|
|
1
|
-
import type
|
|
1
|
+
import { compiledRoot, createNarrowCompiledScope, type CompiledRenderValue } from '@vidact/runtime'
|
|
2
2
|
import { createElement } from '@vidact/runtime/hydrate'
|
|
3
3
|
|
|
4
4
|
import { anchorProps, type LinkProps } from './link-contract.ts'
|
|
5
5
|
|
|
6
6
|
export function Link(props: LinkProps): CompiledRenderValue {
|
|
7
|
-
|
|
7
|
+
const scope = createNarrowCompiledScope()
|
|
8
|
+
return compiledRoot(scope, () => createElement('a', anchorProps(props)))
|
|
8
9
|
}
|
|
9
10
|
|
|
10
11
|
export type { LinkProps }
|
package/src/server.ts
CHANGED
|
@@ -40,11 +40,26 @@ export interface StartDocumentContext {
|
|
|
40
40
|
readonly snapshotId: string
|
|
41
41
|
}
|
|
42
42
|
|
|
43
|
+
export interface StartDocumentShellContext {
|
|
44
|
+
readonly clientEntry: string | undefined
|
|
45
|
+
readonly rootId: string
|
|
46
|
+
readonly snapshot: string
|
|
47
|
+
readonly snapshotId: string
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
export interface StartDocumentShell {
|
|
51
|
+
readonly afterApplication: string
|
|
52
|
+
readonly beforeApplication: string
|
|
53
|
+
}
|
|
54
|
+
|
|
43
55
|
export interface StartHandlerOptions {
|
|
44
56
|
readonly clientEntry?: string
|
|
45
57
|
readonly manifest: RouteManifest
|
|
46
58
|
readonly notFound?: (request: Request) => Response | Promise<Response>
|
|
47
59
|
readonly renderDocument?: (context: StartDocumentContext) => string | Promise<string>
|
|
60
|
+
readonly renderDocumentShell?: (
|
|
61
|
+
context: StartDocumentShellContext,
|
|
62
|
+
) => StartDocumentShell | Promise<StartDocumentShell>
|
|
48
63
|
readonly rootId?: string
|
|
49
64
|
readonly snapshotId?: string
|
|
50
65
|
}
|
|
@@ -52,6 +67,9 @@ export interface StartHandlerOptions {
|
|
|
52
67
|
export function createStartHandler(
|
|
53
68
|
options: StartHandlerOptions,
|
|
54
69
|
): (request: Request) => Promise<Response> {
|
|
70
|
+
if (options.renderDocument !== undefined && options.renderDocumentShell !== undefined) {
|
|
71
|
+
throw new TypeError('renderDocument and renderDocumentShell cannot be used together')
|
|
72
|
+
}
|
|
55
73
|
const rootId = options.rootId ?? DEFAULT_ROOT_ID
|
|
56
74
|
const snapshotId = options.snapshotId ?? DEFAULT_SNAPSHOT_ID
|
|
57
75
|
return async (request) => {
|
|
@@ -100,9 +118,11 @@ export function createStartHandler(
|
|
|
100
118
|
)
|
|
101
119
|
if (request.headers.get(VIDACT_START_NAVIGATION_HEADER) === '1') {
|
|
102
120
|
return new Response(request.method === 'HEAD' ? null : snapshot, {
|
|
103
|
-
headers:
|
|
121
|
+
headers: representationHeaders(`${VIDACT_START_SNAPSHOT_MEDIA_TYPE}; charset=utf-8`),
|
|
104
122
|
})
|
|
105
123
|
}
|
|
124
|
+
const headers = representationHeaders('text/html; charset=utf-8')
|
|
125
|
+
if (request.method === 'HEAD') return new Response(null, { headers })
|
|
106
126
|
|
|
107
127
|
const application = composeRouteMatches(
|
|
108
128
|
loaded,
|
|
@@ -112,26 +132,34 @@ export function createStartHandler(
|
|
|
112
132
|
) as ServerChild
|
|
113
133
|
const stream = await renderToReadableStream(
|
|
114
134
|
() => createElement(StartApplication, { children: application }) as ServerChild,
|
|
115
|
-
{ identifierPrefix: 'start-' },
|
|
135
|
+
{ identifierPrefix: 'start-', signal: request.signal },
|
|
116
136
|
)
|
|
117
|
-
|
|
118
|
-
const
|
|
119
|
-
applicationHtml,
|
|
137
|
+
void stream.allReady.catch(() => undefined)
|
|
138
|
+
const shellContext = {
|
|
120
139
|
clientEntry: options.clientEntry,
|
|
121
140
|
rootId,
|
|
122
141
|
snapshot,
|
|
123
142
|
snapshotId,
|
|
124
143
|
}
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
144
|
+
if (options.renderDocument !== undefined) {
|
|
145
|
+
const applicationHtml = await new Response(stream).text()
|
|
146
|
+
const html = await options.renderDocument({ ...shellContext, applicationHtml })
|
|
147
|
+
return new Response(html, { headers })
|
|
148
|
+
}
|
|
149
|
+
const shell =
|
|
150
|
+
options.renderDocumentShell === undefined
|
|
151
|
+
? defaultDocumentShell(shellContext)
|
|
152
|
+
: validateDocumentShell(await options.renderDocumentShell(shellContext))
|
|
153
|
+
return new Response(streamDocument(shell, stream), { headers })
|
|
132
154
|
}
|
|
133
155
|
}
|
|
134
156
|
|
|
157
|
+
function representationHeaders(contentType: string): Headers {
|
|
158
|
+
const headers = new Headers({ 'content-type': contentType })
|
|
159
|
+
headers.append('vary', VIDACT_START_NAVIGATION_HEADER)
|
|
160
|
+
return headers
|
|
161
|
+
}
|
|
162
|
+
|
|
135
163
|
function responseForRequest(request: Request, response: Response): Response {
|
|
136
164
|
return request.method === 'HEAD' ? new Response(null, response) : response
|
|
137
165
|
}
|
|
@@ -188,12 +216,68 @@ function methodRank(method: string): number {
|
|
|
188
216
|
return method === 'GET' ? 0 : method === 'HEAD' ? 1 : 2
|
|
189
217
|
}
|
|
190
218
|
|
|
191
|
-
function
|
|
219
|
+
function defaultDocumentShell(context: StartDocumentShellContext): StartDocumentShell {
|
|
192
220
|
const clientScript =
|
|
193
221
|
context.clientEntry === undefined
|
|
194
222
|
? ''
|
|
195
223
|
: `<script type="module" src="${escapeAttribute(context.clientEntry)}"></script>`
|
|
196
|
-
return
|
|
224
|
+
return {
|
|
225
|
+
beforeApplication: `<!doctype html><html><head><meta charset="UTF-8"><meta name="viewport" content="width=device-width, initial-scale=1.0"></head><body><div id="${escapeAttribute(context.rootId)}">`,
|
|
226
|
+
afterApplication: `</div><script id="${escapeAttribute(context.snapshotId)}" type="application/json">${context.snapshot}</script>${clientScript}</body></html>`,
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
function validateDocumentShell(value: StartDocumentShell): StartDocumentShell {
|
|
231
|
+
if (
|
|
232
|
+
typeof value !== 'object' ||
|
|
233
|
+
value === null ||
|
|
234
|
+
typeof value.beforeApplication !== 'string' ||
|
|
235
|
+
typeof value.afterApplication !== 'string'
|
|
236
|
+
) {
|
|
237
|
+
throw new TypeError('renderDocumentShell must return beforeApplication and afterApplication')
|
|
238
|
+
}
|
|
239
|
+
return value
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
function streamDocument(
|
|
243
|
+
shell: StartDocumentShell,
|
|
244
|
+
application: ReadableStream<Uint8Array>,
|
|
245
|
+
): ReadableStream<Uint8Array> {
|
|
246
|
+
const encoder = new TextEncoder()
|
|
247
|
+
const before = encoder.encode(shell.beforeApplication)
|
|
248
|
+
const after = encoder.encode(shell.afterApplication)
|
|
249
|
+
const reader = application.getReader()
|
|
250
|
+
let phase: 'after' | 'application' | 'before' | 'done' = 'before'
|
|
251
|
+
return new ReadableStream<Uint8Array>({
|
|
252
|
+
async pull(controller) {
|
|
253
|
+
try {
|
|
254
|
+
if (phase === 'before') {
|
|
255
|
+
phase = 'application'
|
|
256
|
+
if (before.length > 0) controller.enqueue(before)
|
|
257
|
+
return
|
|
258
|
+
}
|
|
259
|
+
if (phase === 'application') {
|
|
260
|
+
const result = await reader.read()
|
|
261
|
+
if (!result.done) {
|
|
262
|
+
controller.enqueue(result.value)
|
|
263
|
+
return
|
|
264
|
+
}
|
|
265
|
+
phase = 'after'
|
|
266
|
+
}
|
|
267
|
+
if (phase === 'after') {
|
|
268
|
+
phase = 'done'
|
|
269
|
+
if (after.length > 0) controller.enqueue(after)
|
|
270
|
+
return
|
|
271
|
+
}
|
|
272
|
+
controller.close()
|
|
273
|
+
} catch (error) {
|
|
274
|
+
controller.error(error)
|
|
275
|
+
}
|
|
276
|
+
},
|
|
277
|
+
cancel(reason) {
|
|
278
|
+
return reader.cancel(reason)
|
|
279
|
+
},
|
|
280
|
+
})
|
|
197
281
|
}
|
|
198
282
|
|
|
199
283
|
function escapeAttribute(value: string): string {
|