@valbuild/shared 0.150.18 → 0.151.0
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 +246 -0
- package/client/dist/valbuild-shared-client.cjs.dev.js +2 -1
- package/client/dist/valbuild-shared-client.cjs.prod.js +2 -1
- package/client/dist/valbuild-shared-client.esm.js +1 -1
- package/dist/declarations/src/internal/ApiRoutes.d.ts +253 -2
- package/dist/declarations/src/internal/content/applyChain.d.ts +124 -0
- package/dist/declarations/src/internal/content/buildRegistry.d.ts +36 -0
- package/dist/declarations/src/internal/content/jsonValuesOps.d.ts +110 -0
- package/dist/declarations/src/internal/content/moduleStore.d.ts +119 -0
- package/dist/declarations/src/internal/content/source.d.ts +71 -0
- package/dist/declarations/src/internal/content/version.d.ts +151 -0
- package/dist/declarations/src/internal/index.d.ts +8 -1
- package/dist/declarations/src/internal/publish/projectSource.d.ts +28 -0
- package/dist/declarations/src/internal/quickPreview.d.ts +17 -1
- package/dist/declarations/src/internal/search/searchIndex.d.ts +34 -3
- package/dist/declarations/src/internal/server/types.d.ts +9 -1
- package/dist/declarations/src/internal/valCanvasProtocol.d.ts +46 -0
- package/dist/declarations/src/internal/valClientHeader.d.ts +36 -0
- package/dist/declarations/src/publish/callJson.d.ts +15 -0
- package/dist/declarations/src/publish/jobClient.d.ts +7 -1
- package/dist/declarations/src/publish/publishClient.d.ts +2 -14
- package/dist/{getSitemapTree-7c848c94.esm.js → getSitemapTree-3c1b9e57.esm.js} +37 -4
- package/dist/{getSitemapTree-98642b57.cjs.dev.js → getSitemapTree-59c5ed5d.cjs.dev.js} +37 -3
- package/dist/{getSitemapTree-97713490.cjs.prod.js → getSitemapTree-6b9024d1.cjs.prod.js} +37 -3
- package/dist/{protocol-d0efb8cd.cjs.dev.js → projectSource-2a58ef15.cjs.dev.js} +221 -0
- package/dist/{protocol-55cfa3cf.esm.js → projectSource-7f0ee83c.esm.js} +216 -1
- package/dist/{protocol-b1055ef0.cjs.prod.js → projectSource-ed6aec39.cjs.prod.js} +221 -0
- package/internal/dist/valbuild-shared-internal.cjs.dev.js +2388 -189
- package/internal/dist/valbuild-shared-internal.cjs.prod.js +2388 -189
- package/internal/dist/valbuild-shared-internal.esm.js +2332 -172
- package/package.json +3 -3
- package/publish/dist/valbuild-shared-publish.cjs.dev.js +48 -155
- package/publish/dist/valbuild-shared-publish.cjs.prod.js +48 -155
- package/publish/dist/valbuild-shared-publish.esm.js +17 -123
- package/dist/declarations/src/internal/server/requestScopedMemo.d.ts +0 -49
|
@@ -20,6 +20,14 @@
|
|
|
20
20
|
* document's `/_app/` requests from the files, and everything else from
|
|
21
21
|
* the network: the HTML is the last build's, rendered by its server, and
|
|
22
22
|
* the new code takes it over in the browser.
|
|
23
|
+
* 4. It takes it over by RENDERING it, not by hydrating it. Hydration keeps
|
|
24
|
+
* the markup the server sent wherever only an attribute differs -- React
|
|
25
|
+
* mends text, never a `className` -- so a change to classes and props,
|
|
26
|
+
* which is most of what the design tab changes, hydrated into the old
|
|
27
|
+
* page and showed nothing. The worker adds a script to the end of that
|
|
28
|
+
* document's `<body>` ({@link QUICK_PREVIEW_RENDER_SCRIPT}) that takes the
|
|
29
|
+
* server's markup out before the app's scripts run, so hydration finds
|
|
30
|
+
* nothing to keep and React renders the page with the new code.
|
|
23
31
|
*
|
|
24
32
|
* What it cannot show, and the tab says so: anything on the server. Server
|
|
25
33
|
* functions, API routes and loaders that run there are the last build's, as
|
|
@@ -34,6 +42,14 @@
|
|
|
34
42
|
export declare const QUICK_PREVIEW_PARAM = "val_quick";
|
|
35
43
|
/** Where Val's API serves the worker: under `/api/val`. */
|
|
36
44
|
export declare const QUICK_PREVIEW_SERVICE_WORKER_PATH = "/quick-preview-sw.js";
|
|
45
|
+
/**
|
|
46
|
+
* What a quick preview's document runs before the app's scripts: the
|
|
47
|
+
* server's markup out of `<body>`, every script left where it is -- the
|
|
48
|
+
* router's dehydrated state and the app's entry are among them. Hydration
|
|
49
|
+
* then finds the body empty and React renders the page, with the new code's
|
|
50
|
+
* attributes and classes, where it would have kept the old ones.
|
|
51
|
+
*/
|
|
52
|
+
export declare const QUICK_PREVIEW_RENDER_SCRIPT: string;
|
|
37
53
|
/** What the page sends the worker to store a preview's files. */
|
|
38
54
|
export declare const QUICK_PREVIEW_STORE_MESSAGE = "val-quick-preview-store";
|
|
39
55
|
/**
|
|
@@ -54,7 +70,7 @@ export declare function isQuickPreviewFiles(value: unknown): value is QuickPrevi
|
|
|
54
70
|
* The worker, as the source it is served as: plain, and small, because it is
|
|
55
71
|
* served as a file of its own (`demoServiceWorker.ts` is the same).
|
|
56
72
|
*/
|
|
57
|
-
export declare const QUICK_PREVIEW_SERVICE_WORKER_SOURCE
|
|
73
|
+
export declare const QUICK_PREVIEW_SERVICE_WORKER_SOURCE: string;
|
|
58
74
|
/**
|
|
59
75
|
* Store a preview's files in the worker, registering it first: what the
|
|
60
76
|
* bridge does when the tab sends `quickPreview`. `swUrl` is the worker's
|
|
@@ -57,6 +57,28 @@ export type SearchIndex = {
|
|
|
57
57
|
* re-indexing one module exists to avoid.
|
|
58
58
|
*/
|
|
59
59
|
docsByModule: Map<ModuleFilePath, Set<string>>;
|
|
60
|
+
/**
|
|
61
|
+
* For a module whose root is a record: each entry's documents, and the
|
|
62
|
+
* value they were made from, by key -- so indexing it again walks only the
|
|
63
|
+
* entries whose value is a different object (see {@link indexModule}).
|
|
64
|
+
*/
|
|
65
|
+
/**
|
|
66
|
+
* What each module was last indexed from, whole: the same source object
|
|
67
|
+
* under a schema that says the same is not walked again. Removing a
|
|
68
|
+
* document from a big index is slow (FlexSearch looks for it everywhere),
|
|
69
|
+
* so indexing an unchanged module again was the cost of a pass.
|
|
70
|
+
*/
|
|
71
|
+
walkedByModule: Map<ModuleFilePath, {
|
|
72
|
+
source: Json;
|
|
73
|
+
schema: string;
|
|
74
|
+
}>;
|
|
75
|
+
entriesByModule: Map<ModuleFilePath, {
|
|
76
|
+
schema: SerializedSchema;
|
|
77
|
+
entries: Map<string, {
|
|
78
|
+
source: Json;
|
|
79
|
+
docs: string[];
|
|
80
|
+
}>;
|
|
81
|
+
}>;
|
|
60
82
|
};
|
|
61
83
|
/** An index with nothing in it. Fill it with {@link indexModule}. */
|
|
62
84
|
export declare function createSearchIndex(): SearchIndex;
|
|
@@ -70,10 +92,19 @@ export declare function createSearchIndex(): SearchIndex;
|
|
|
70
92
|
* `.jsonValues()` entries are indexed only once their content is loaded — an
|
|
71
93
|
* un-loaded entry is an opaque marker that {@link traverseSchemaSource} skips, so
|
|
72
94
|
* the index is naturally PARTIAL and grows as batches land. That is why the search
|
|
73
|
-
* UI loads the entries on the first query and re-indexes
|
|
74
|
-
*
|
|
95
|
+
* UI loads the entries on the first query and re-indexes as they arrive, rather
|
|
96
|
+
* than pretending an empty result set is an answer -- and why a module whose
|
|
97
|
+
* root is a record is re-indexed by entry ({@link indexRecordModule}): a pass
|
|
98
|
+
* per batch walks the batch, not the record.
|
|
99
|
+
*/
|
|
100
|
+
export declare function indexModule(searchIndex: SearchIndex, moduleFilePath: ModuleFilePath, source: Json, schema: SerializedSchema,
|
|
101
|
+
/**
|
|
102
|
+
* Stop by this time (`performance.now()`), for a caller that must not hold
|
|
103
|
+
* the thread: answers `false` when it stopped with entries of a record
|
|
104
|
+
* module still to walk, and the same call again goes on from there. A
|
|
105
|
+
* module that is not a record is walked whole, whatever the time.
|
|
75
106
|
*/
|
|
76
|
-
|
|
107
|
+
until?: number): boolean;
|
|
77
108
|
/**
|
|
78
109
|
* Drop everything indexed for one module.
|
|
79
110
|
*
|
|
@@ -45,7 +45,15 @@ export type ValServerJsonResult<Body extends Json | never = never, Error extends
|
|
|
45
45
|
status: 400;
|
|
46
46
|
json: Error;
|
|
47
47
|
} : never);
|
|
48
|
-
|
|
48
|
+
/**
|
|
49
|
+
* Nothing moved since the ETag the caller sent back: no body, as HTTP
|
|
50
|
+
* requires of a 304.
|
|
51
|
+
*/
|
|
52
|
+
export type ValServerNotModified = {
|
|
53
|
+
status: 304;
|
|
54
|
+
headers?: Record<string, string | undefined>;
|
|
55
|
+
};
|
|
56
|
+
export type ValServerGenericResult = ValServerNotModified | ValServerJsonResult<Json> | ValServerError | ValServerRedirectResult<ValCookiesNames> | ValServerResult<ValCookiesNames, string | ReadableStream<Uint8Array>>;
|
|
49
57
|
export type ValUIRequestHandler = (path: string, url: string) => Promise<ValServerGenericResult>;
|
|
50
58
|
export type ValSession = {
|
|
51
59
|
mode: "unauthorized";
|
|
@@ -93,6 +93,26 @@ export type ValCanvasPageMessage = {
|
|
|
93
93
|
draftMode: boolean;
|
|
94
94
|
/** The URL the frame ended up on, which a redirect may have changed. */
|
|
95
95
|
url: string;
|
|
96
|
+
/**
|
|
97
|
+
* The modules the page holds a draft of: what its server read for this
|
|
98
|
+
* request (`<ValProvider draft>`). The studio sends each of them in its
|
|
99
|
+
* catch-up, pending or not, so a draft that stopped being the draft
|
|
100
|
+
* before the studio was there to say so -- discarded, published -- is
|
|
101
|
+
* replaced rather than kept. Absent from a page of the release before,
|
|
102
|
+
* and from a page that read no draft.
|
|
103
|
+
*/
|
|
104
|
+
holds?: ModuleFilePath[];
|
|
105
|
+
/**
|
|
106
|
+
* The version the page holds each of {@link holds} at, where its draft
|
|
107
|
+
* says (`draftHoldsVersions`): the studio does not send one it would
|
|
108
|
+
* send at the same version -- the same content. Absent from a page of
|
|
109
|
+
* the release before, which is sent everything it holds, as before.
|
|
110
|
+
*/
|
|
111
|
+
holdsVersions?: Partial<Record<ModuleFilePath, {
|
|
112
|
+
schema: string;
|
|
113
|
+
base: string;
|
|
114
|
+
chain: string;
|
|
115
|
+
}>>;
|
|
96
116
|
} | {
|
|
97
117
|
val: typeof VAL_CANVAS_MESSAGE;
|
|
98
118
|
type: "elements";
|
|
@@ -257,8 +277,18 @@ export type ValCanvasStudioMessage = {
|
|
|
257
277
|
/**
|
|
258
278
|
* The whole module source. `unknown` because the page's own types are the
|
|
259
279
|
* ones that give it meaning; nothing in between should be interpreting it.
|
|
280
|
+
*
|
|
281
|
+
* @deprecated A page of this release reads `read`; this is for a page
|
|
282
|
+
* of the release before. Goes in the release after the one that ships
|
|
283
|
+
* `read`, with the bridges' fallback that reads it: see
|
|
284
|
+
* "Release-delayed deletions" in `docs/plans/one-content-model.md`.
|
|
260
285
|
*/
|
|
261
286
|
source: unknown;
|
|
287
|
+
/**
|
|
288
|
+
* The same module as a read (`ModuleRead`), which the page keeps as it
|
|
289
|
+
* is. Optional: a Studio from the release before sends `source` alone.
|
|
290
|
+
*/
|
|
291
|
+
read?: unknown;
|
|
262
292
|
} | {
|
|
263
293
|
/**
|
|
264
294
|
* Everything the studio holds has been sent.
|
|
@@ -276,6 +306,22 @@ export type ValCanvasStudioMessage = {
|
|
|
276
306
|
*/
|
|
277
307
|
val: typeof VAL_CANVAS_MESSAGE;
|
|
278
308
|
type: "sourcesSynced";
|
|
309
|
+
} | {
|
|
310
|
+
/**
|
|
311
|
+
* The server holds the editor's changes up to this version (`seq`, the
|
|
312
|
+
* server's `ProjectVersion.seq`; `null` from a server that reports
|
|
313
|
+
* none). Sent once after `sourcesSynced`, then every time it moves.
|
|
314
|
+
*
|
|
315
|
+
* What the page re-reads its server-rendered content on (a loader, a
|
|
316
|
+
* server component): those only show an edit once the server has it,
|
|
317
|
+
* so a refresh sent when the edit was typed raced the write and came
|
|
318
|
+
* back without it -- which a ten second safety refresh used to catch.
|
|
319
|
+
* A bridge of the release before ignores it, as it ignores any type it
|
|
320
|
+
* does not know.
|
|
321
|
+
*/
|
|
322
|
+
val: typeof VAL_CANVAS_MESSAGE;
|
|
323
|
+
type: "confirmed";
|
|
324
|
+
seq: number | null;
|
|
279
325
|
} | {
|
|
280
326
|
/**
|
|
281
327
|
* What colour to outline the page's content in.
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Which Val package, at which version, sent a request to Val Build.
|
|
3
|
+
*
|
|
4
|
+
* Sent on every request a Val package makes to Val's own services from a
|
|
5
|
+
* server or a CLI -- the content host, `/api/login`, the analytics ingest --
|
|
6
|
+
* so Val Build can tell which versions are still out there and which old
|
|
7
|
+
* endpoints they still call. Without it an endpoint can never be retired with
|
|
8
|
+
* any confidence: a request from a site nobody has upgraded in a year looks
|
|
9
|
+
* exactly like one from yesterday's release.
|
|
10
|
+
*
|
|
11
|
+
* The value is a product token, `@valbuild/server@0.152.0`: the package whose
|
|
12
|
+
* code built the request, because that is what decides which endpoints are
|
|
13
|
+
* called. Not the framework binding or the app: those do not choose URLs.
|
|
14
|
+
*
|
|
15
|
+
* NOT sent from the browser. A custom header on a cross-origin request makes
|
|
16
|
+
* the browser send a CORS preflight, and a host that does not list the header
|
|
17
|
+
* in `Access-Control-Allow-Headers` refuses the request outright -- so adding
|
|
18
|
+
* it there is a change to every such host first.
|
|
19
|
+
*
|
|
20
|
+
* It identifies the code, not the caller: anyone can send any value, so it is
|
|
21
|
+
* for counting, never for deciding what a request may do.
|
|
22
|
+
*/
|
|
23
|
+
export declare const VAL_CLIENT_HEADER = "x-val-client";
|
|
24
|
+
/** Packages that send {@link VAL_CLIENT_HEADER}. */
|
|
25
|
+
export type ValClientPackage = "@valbuild/server" | "@valbuild/cli";
|
|
26
|
+
/**
|
|
27
|
+
* `{ "x-val-client": "@valbuild/server@0.152.0" }`, to spread into a
|
|
28
|
+
* request's headers.
|
|
29
|
+
*
|
|
30
|
+
* A version that could not be read is sent as `unknown` rather than leaving
|
|
31
|
+
* the header out: "a server too broken to know its version" is still a thing
|
|
32
|
+
* worth counting, and it is not the same as "a version older than the header".
|
|
33
|
+
*/
|
|
34
|
+
export declare function valClientHeaders(pkg: ValClientPackage, version: string | null | undefined): {
|
|
35
|
+
[VAL_CLIENT_HEADER]: string;
|
|
36
|
+
};
|
|
@@ -19,3 +19,18 @@ export declare function isTransientPublishError(error: unknown): boolean;
|
|
|
19
19
|
* `StudioPublishError` carrying the answer's own message.
|
|
20
20
|
*/
|
|
21
21
|
export declare function callJson(fetchImpl: typeof fetch, url: string, method: "GET" | "POST", body?: unknown): Promise<unknown>;
|
|
22
|
+
/**
|
|
23
|
+
* One JSON READ through this origin, BY VERSION: `held` is the answer the
|
|
24
|
+
* caller holds, with its `ETag`, which goes as `If-None-Match`. While the
|
|
25
|
+
* answer is still that one (a 304, with no body) it is `held` again;
|
|
26
|
+
* otherwise the new body, `parse`d, with its `ETag` -- `null` when the answer
|
|
27
|
+
* named none, so there is nothing to hold it by. Errors as {@link callJson}
|
|
28
|
+
* throws them.
|
|
29
|
+
*/
|
|
30
|
+
export declare function readJsonByVersion<T>(fetchImpl: typeof fetch, url: string, held: {
|
|
31
|
+
etag: string;
|
|
32
|
+
value: T;
|
|
33
|
+
} | null, parse: (body: unknown) => T): Promise<{
|
|
34
|
+
etag: string | null;
|
|
35
|
+
value: T;
|
|
36
|
+
}>;
|
|
@@ -24,7 +24,13 @@ export type StudioJobClient = {
|
|
|
24
24
|
* failed: `types`, an upgrade's new type errors (Publish anyway on home's
|
|
25
25
|
* upgrade page). Content refuses it over anything else.
|
|
26
26
|
*/
|
|
27
|
-
anyway?: "types"[]
|
|
27
|
+
anyway?: "types"[],
|
|
28
|
+
/**
|
|
29
|
+
* The message of the commit Publish makes first, in a project connected
|
|
30
|
+
* to git (the proposal's branch, before it merges): from the project's
|
|
31
|
+
* `studio.commitMessage` setting. Absent: content's default.
|
|
32
|
+
*/
|
|
33
|
+
commitMessage?: string): Promise<PressResponse>;
|
|
28
34
|
/** Try again: resume a paused queue and press anew. */
|
|
29
35
|
tryAgain(requestId: string, tab: string): Promise<PressResponse>;
|
|
30
36
|
/** Where a press is. */
|
|
@@ -22,8 +22,9 @@
|
|
|
22
22
|
* own authorisation and a cookie sent to a storage host is a cookie leaked to
|
|
23
23
|
* one.
|
|
24
24
|
*/
|
|
25
|
-
import { ArtifactsResponse, BuildTargetResponse, DeclareBody, DeclareResponse, PromoteResponse, StatusResponse, UploadSlot, VerifyResponse } from "../internal/index.js";
|
|
25
|
+
import { ArtifactsResponse, BuildTargetResponse, DeclareBody, DeclareResponse, PromoteResponse, StatusResponse, UploadSlot, VerifyResponse, publishApiOf, type PublishApiAt } from "../internal/index.js";
|
|
26
26
|
export { StudioPublishError, callJson, isTransientPublishError, } from "./callJson.js";
|
|
27
|
+
export { publishApiOf, type PublishApiAt };
|
|
27
28
|
/** An artifact the live build has and this publish re-declares by hash. */
|
|
28
29
|
export type CarriedArtifact = {
|
|
29
30
|
key: string;
|
|
@@ -77,19 +78,6 @@ export type StudioPublishClient = {
|
|
|
77
78
|
* short of the finish.
|
|
78
79
|
*/
|
|
79
80
|
export declare function isExpiredSlot(error: unknown): boolean;
|
|
80
|
-
/**
|
|
81
|
-
* Where content's publish API is reached: through Val's proxy on this origin
|
|
82
|
-
* (`api`, the Studio), or directly (`publishApi`, `<content>/v1`: home's
|
|
83
|
-
* upgrade page, whose `fetchImpl` adds the project token).
|
|
84
|
-
*/
|
|
85
|
-
export type PublishApiAt = {
|
|
86
|
-
/** Val's API root on this origin, e.g. `/api/val`. */
|
|
87
|
-
api: string;
|
|
88
|
-
} | {
|
|
89
|
-
/** Content's publish API itself, e.g. `https://content.val.build/v1`. */
|
|
90
|
-
publishApi: string;
|
|
91
|
-
};
|
|
92
|
-
export declare function publishApiOf(at: PublishApiAt): string;
|
|
93
81
|
export declare function createStudioPublishClient(options: PublishApiAt & {
|
|
94
82
|
fetchImpl?: typeof fetch;
|
|
95
83
|
}): StudioPublishClient;
|
|
@@ -27,6 +27,14 @@ var VAL_AI_SESSION_STORAGE_KEY = "val-ai-session";
|
|
|
27
27
|
* document's `/_app/` requests from the files, and everything else from
|
|
28
28
|
* the network: the HTML is the last build's, rendered by its server, and
|
|
29
29
|
* the new code takes it over in the browser.
|
|
30
|
+
* 4. It takes it over by RENDERING it, not by hydrating it. Hydration keeps
|
|
31
|
+
* the markup the server sent wherever only an attribute differs -- React
|
|
32
|
+
* mends text, never a `className` -- so a change to classes and props,
|
|
33
|
+
* which is most of what the design tab changes, hydrated into the old
|
|
34
|
+
* page and showed nothing. The worker adds a script to the end of that
|
|
35
|
+
* document's `<body>` ({@link QUICK_PREVIEW_RENDER_SCRIPT}) that takes the
|
|
36
|
+
* server's markup out before the app's scripts run, so hydration finds
|
|
37
|
+
* nothing to keep and React renders the page with the new code.
|
|
30
38
|
*
|
|
31
39
|
* What it cannot show, and the tab says so: anything on the server. Server
|
|
32
40
|
* functions, API routes and loaders that run there are the last build's, as
|
|
@@ -44,6 +52,15 @@ var QUICK_PREVIEW_PARAM = "val_quick";
|
|
|
44
52
|
/** Where Val's API serves the worker: under `/api/val`. */
|
|
45
53
|
var QUICK_PREVIEW_SERVICE_WORKER_PATH = "/quick-preview-sw.js";
|
|
46
54
|
|
|
55
|
+
/**
|
|
56
|
+
* What a quick preview's document runs before the app's scripts: the
|
|
57
|
+
* server's markup out of `<body>`, every script left where it is -- the
|
|
58
|
+
* router's dehydrated state and the app's entry are among them. Hydration
|
|
59
|
+
* then finds the body empty and React renders the page, with the new code's
|
|
60
|
+
* attributes and classes, where it would have kept the old ones.
|
|
61
|
+
*/
|
|
62
|
+
var QUICK_PREVIEW_RENDER_SCRIPT = "<script>(function(){var b=document.body;if(!b)return;" + "Array.prototype.slice.call(b.childNodes).forEach(function(n){" + 'if(n.nodeName!=="SCRIPT")b.removeChild(n)})})()</script>';
|
|
63
|
+
|
|
47
64
|
/** What the page sends the worker to store a preview's files. */
|
|
48
65
|
var QUICK_PREVIEW_STORE_MESSAGE = "val-quick-preview-store";
|
|
49
66
|
|
|
@@ -70,7 +87,7 @@ function isQuickPreviewFiles(value) {
|
|
|
70
87
|
* The worker, as the source it is served as: plain, and small, because it is
|
|
71
88
|
* served as a file of its own (`demoServiceWorker.ts` is the same).
|
|
72
89
|
*/
|
|
73
|
-
var QUICK_PREVIEW_SERVICE_WORKER_SOURCE = "\n\"use strict\";\nconst PARAM = \"".concat(QUICK_PREVIEW_PARAM, "\";\nconst CACHE = \"val-quick-preview\";\nconst PREFIX = \"/__val_quick/\";\nconst INDEX = PREFIX + \"index\";\nconst KEEP = 4;\n\nself.addEventListener(\"install\", () => self.skipWaiting());\nself.addEventListener(\"activate\", (event) => {\n event.waitUntil(self.clients.claim());\n});\n\nself.addEventListener(\"message\", (event) => {\n const data = event.data;\n if (!data || data.type !== \"").concat(QUICK_PREVIEW_STORE_MESSAGE, "\") return;\n const port = event.ports && event.ports[0];\n event.waitUntil(\n store(data.id, data.files).then(\n () => port && port.postMessage({ ok: true }),\n (error) =>\n port &&\n port.postMessage({\n ok: false,\n message: String((error && error.message) || error),\n }),\n ),\n );\n});\n\nfunction keyOf(id, path) {\n return PREFIX + id + \"/\" + path;\n}\n\nasync function store(id, files) {\n const cache = await caches.open(CACHE);\n for (const path of Object.keys(files)) {\n const file = files[path];\n const body = file.base64\n ? Uint8Array.from(atob(file.body), (c) => c.charCodeAt(0))\n : file.body;\n await cache.put(\n keyOf(id, path),\n new Response(body, {\n headers: { \"Content-Type\": file.contentType, \"Cache-Control\": \"no-store\" },\n }),\n );\n }\n // The newest few previews are kept; older ones are deleted.\n const index = await cache.match(INDEX);\n const ids = [id].concat(\n (index ? await index.json() : []).filter((other) => other !== id),\n );\n const dropped = ids.slice(KEEP);\n if (dropped.length > 0) {\n for (const request of await cache.keys()) {\n const path = new URL(request.url).pathname;\n if (dropped.some((old) => path.startsWith(PREFIX + old + \"/\"))) {\n await cache.delete(request);\n }\n }\n }\n await cache.put(INDEX, new Response(JSON.stringify(ids.slice(0, KEEP))));\n}\n\n/** Which preview a document is: by its id, or by the address it was opened at. */\nconst byClient = new Map();\nasync function previewOf(clientId) {\n if (!clientId) return null;\n if (byClient.has(clientId)) return byClient.get(clientId);\n const client = await self.clients.get(clientId);\n const id = client ? new URL(client.url).searchParams.get(PARAM) : null;\n byClient.set(clientId, id);\n return id;\n}\n\nself.addEventListener(\"fetch\", (event) => {\n const url = new URL(event.request.url);\n if (url.origin !== self.location.origin) return;\n if (event.request.mode === \"navigate\") {\n if (event.resultingClientId)
|
|
90
|
+
var QUICK_PREVIEW_SERVICE_WORKER_SOURCE = "\n\"use strict\";\nconst PARAM = \"".concat(QUICK_PREVIEW_PARAM, "\";\nconst RENDER = ").concat(JSON.stringify(QUICK_PREVIEW_RENDER_SCRIPT), ";\nconst CACHE = \"val-quick-preview\";\nconst PREFIX = \"/__val_quick/\";\nconst INDEX = PREFIX + \"index\";\nconst KEEP = 4;\n\nself.addEventListener(\"install\", () => self.skipWaiting());\nself.addEventListener(\"activate\", (event) => {\n event.waitUntil(self.clients.claim());\n});\n\nself.addEventListener(\"message\", (event) => {\n const data = event.data;\n if (!data || data.type !== \"").concat(QUICK_PREVIEW_STORE_MESSAGE, "\") return;\n const port = event.ports && event.ports[0];\n event.waitUntil(\n store(data.id, data.files).then(\n () => port && port.postMessage({ ok: true }),\n (error) =>\n port &&\n port.postMessage({\n ok: false,\n message: String((error && error.message) || error),\n }),\n ),\n );\n});\n\nfunction keyOf(id, path) {\n return PREFIX + id + \"/\" + path;\n}\n\nasync function store(id, files) {\n const cache = await caches.open(CACHE);\n for (const path of Object.keys(files)) {\n const file = files[path];\n const body = file.base64\n ? Uint8Array.from(atob(file.body), (c) => c.charCodeAt(0))\n : file.body;\n await cache.put(\n keyOf(id, path),\n new Response(body, {\n headers: { \"Content-Type\": file.contentType, \"Cache-Control\": \"no-store\" },\n }),\n );\n }\n // The newest few previews are kept; older ones are deleted.\n const index = await cache.match(INDEX);\n const ids = [id].concat(\n (index ? await index.json() : []).filter((other) => other !== id),\n );\n const dropped = ids.slice(KEEP);\n if (dropped.length > 0) {\n for (const request of await cache.keys()) {\n const path = new URL(request.url).pathname;\n if (dropped.some((old) => path.startsWith(PREFIX + old + \"/\"))) {\n await cache.delete(request);\n }\n }\n }\n await cache.put(INDEX, new Response(JSON.stringify(ids.slice(0, KEEP))));\n}\n\n/** Which preview a document is: by its id, or by the address it was opened at. */\nconst byClient = new Map();\nasync function previewOf(clientId) {\n if (!clientId) return null;\n if (byClient.has(clientId)) return byClient.get(clientId);\n const client = await self.clients.get(clientId);\n const id = client ? new URL(client.url).searchParams.get(PARAM) : null;\n byClient.set(clientId, id);\n return id;\n}\n\nself.addEventListener(\"fetch\", (event) => {\n const url = new URL(event.request.url);\n if (url.origin !== self.location.origin) return;\n if (event.request.mode === \"navigate\") {\n const id = url.searchParams.get(PARAM);\n if (event.resultingClientId) byClient.set(event.resultingClientId, id);\n if (id) event.respondWith(rendered(event.request));\n return;\n }\n if (!url.pathname.startsWith(\"/_app/\")) return;\n event.respondWith(serve(event, url));\n});\n\n/** A quick preview's document, with the script that has it rendered, not hydrated. */\nasync function rendered(request) {\n const response = await fetch(request);\n const type = response.headers.get(\"Content-Type\") || \"\";\n if (!response.ok || !type.includes(\"text/html\")) return response;\n const html = await response.text();\n const at = html.lastIndexOf(\"</body>\");\n if (at === -1) return new Response(html, response);\n const headers = new Headers(response.headers);\n headers.delete(\"Content-Length\");\n headers.delete(\"Content-Encoding\");\n return new Response(html.slice(0, at) + RENDER + html.slice(at), {\n status: response.status,\n statusText: response.statusText,\n headers,\n });\n}\n\nasync function serve(event, url) {\n const id = await previewOf(event.clientId);\n if (id) {\n const hit = await caches.match(keyOf(id, url.pathname.slice(6)), {\n cacheName: CACHE,\n });\n if (hit) return hit;\n }\n return fetch(event.request);\n}\n");
|
|
74
91
|
|
|
75
92
|
/** How long the worker is given to install and store a preview. */
|
|
76
93
|
var STORE_TIMEOUT_MS = 30000;
|
|
@@ -322,7 +339,23 @@ function isValCanvasPageMessage(data) {
|
|
|
322
339
|
if (_typeof(data) !== "object" || data === null) return false;
|
|
323
340
|
var message = data;
|
|
324
341
|
if (message.val !== VAL_CANVAS_MESSAGE) return false;
|
|
325
|
-
return message.type === "ready" || message.type === "elements" || message.type === "clicked" || message.type === "pinch" || message.type === "zoom" || message.type === "refreshing" || message.type === "elementPickingStopped" || message.type === "elementPicked" && isPickedElement(data.element) || message.type === "location" && isPageLocation(data) || message.type === "pageRead" && typeof data.requestId === "string" || message.type === "pageScreenshot" && typeof data.requestId === "string" || message.type === "quickPreviewStored" && typeof data.id === "string";
|
|
342
|
+
return message.type === "ready" && isModulePathList(data.holds) && isHeldVersions(data.holdsVersions) || message.type === "elements" || message.type === "clicked" || message.type === "pinch" || message.type === "zoom" || message.type === "refreshing" || message.type === "elementPickingStopped" || message.type === "elementPicked" && isPickedElement(data.element) || message.type === "location" && isPageLocation(data) || message.type === "pageRead" && typeof data.requestId === "string" || message.type === "pageScreenshot" && typeof data.requestId === "string" || message.type === "quickPreviewStored" && typeof data.id === "string";
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
/** `holdsVersions` on `ready`: absent, or a version per module path. */
|
|
346
|
+
function isHeldVersions(value) {
|
|
347
|
+
if (value === undefined) return true;
|
|
348
|
+
if (_typeof(value) !== "object" || value === null) return false;
|
|
349
|
+
return Object.values(value).every(function (version) {
|
|
350
|
+
return _typeof(version) === "object" && version !== null && typeof version.schema === "string" && typeof version.base === "string" && typeof version.chain === "string";
|
|
351
|
+
});
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
/** `holds` on `ready`: absent, or a list of module paths. */
|
|
355
|
+
function isModulePathList(value) {
|
|
356
|
+
return value === undefined || Array.isArray(value) && value.every(function (path) {
|
|
357
|
+
return typeof path === "string";
|
|
358
|
+
});
|
|
326
359
|
}
|
|
327
360
|
function isPageLocation(value) {
|
|
328
361
|
var location = value;
|
|
@@ -387,7 +420,7 @@ function isValCanvasStudioMessage(data) {
|
|
|
387
420
|
var read = data;
|
|
388
421
|
return typeof read.requestId === "string" && (read.selector === undefined || typeof read.selector === "string") && (read.maxItems === undefined || typeof read.maxItems === "number");
|
|
389
422
|
}
|
|
390
|
-
return message.type === "rescan" || message.type === "highlight" || message.type === "setPicking" || message.type === "sourceUpdate" || message.type === "sourcesSynced";
|
|
423
|
+
return message.type === "rescan" || message.type === "highlight" || message.type === "setPicking" || message.type === "sourceUpdate" || message.type === "sourcesSynced" || message.type === "confirmed" && (data.seq === null || typeof data.seq === "number");
|
|
391
424
|
}
|
|
392
425
|
|
|
393
426
|
/**
|
|
@@ -1513,4 +1546,4 @@ function getPatternFromModuleFilePath(moduleFilePath, srcFolder) {
|
|
|
1513
1546
|
.replace("/page.val.ts", "").replace("/page.val.js", "");
|
|
1514
1547
|
}
|
|
1515
1548
|
|
|
1516
|
-
export {
|
|
1549
|
+
export { storeQuickPreview as A, withQuickPreviewParam as B, quickPreviewSiteOrigin as C, DEFAULT_CANVAS_SELECTION as D, isDesignTabOrigin as E, parseRoutePattern as F, formatRoutePattern as G, getNextAppRouterSourceFolder as H, getTanStackRouterSourceFolder as I, getPageRouterSourceFolder as J, isTanStackRoutesFolder as K, getPageRouterSitemapTree as L, getPatternFromModuleFilePath as M, QUICK_PREVIEW_PARAM as Q, VAL_CONFIG_SESSION_STORAGE_KEY as V, VAL_THEME_SESSION_STORAGE_KEY as a, VAL_AI_SESSION_STORAGE_KEY as b, VAL_CANVAS_PARAM as c, VAL_CANVAS_MESSAGE as d, DEFAULT_CANVAS_SELECTION_SOFT as e, isValCanvasStudioMessage as f, isValCanvasFrame as g, describeElement as h, isValCanvasPageMessage as i, attachElementPicker as j, createPickerOverlay as k, elementLabel as l, modifierLabel as m, watchPageLocation as n, readPageSnapshot as o, pageLocationOf as p, findBySelector as q, restoreScroll as r, startElementPicking as s, screenshotPage as t, QUICK_PREVIEW_SERVICE_WORKER_PATH as u, QUICK_PREVIEW_RENDER_SCRIPT as v, withValCanvasParam as w, QUICK_PREVIEW_STORE_MESSAGE as x, isQuickPreviewFiles as y, QUICK_PREVIEW_SERVICE_WORKER_SOURCE as z };
|
|
@@ -47,6 +47,14 @@ var VAL_AI_SESSION_STORAGE_KEY = "val-ai-session";
|
|
|
47
47
|
* document's `/_app/` requests from the files, and everything else from
|
|
48
48
|
* the network: the HTML is the last build's, rendered by its server, and
|
|
49
49
|
* the new code takes it over in the browser.
|
|
50
|
+
* 4. It takes it over by RENDERING it, not by hydrating it. Hydration keeps
|
|
51
|
+
* the markup the server sent wherever only an attribute differs -- React
|
|
52
|
+
* mends text, never a `className` -- so a change to classes and props,
|
|
53
|
+
* which is most of what the design tab changes, hydrated into the old
|
|
54
|
+
* page and showed nothing. The worker adds a script to the end of that
|
|
55
|
+
* document's `<body>` ({@link QUICK_PREVIEW_RENDER_SCRIPT}) that takes the
|
|
56
|
+
* server's markup out before the app's scripts run, so hydration finds
|
|
57
|
+
* nothing to keep and React renders the page with the new code.
|
|
50
58
|
*
|
|
51
59
|
* What it cannot show, and the tab says so: anything on the server. Server
|
|
52
60
|
* functions, API routes and loaders that run there are the last build's, as
|
|
@@ -64,6 +72,15 @@ var QUICK_PREVIEW_PARAM = "val_quick";
|
|
|
64
72
|
/** Where Val's API serves the worker: under `/api/val`. */
|
|
65
73
|
var QUICK_PREVIEW_SERVICE_WORKER_PATH = "/quick-preview-sw.js";
|
|
66
74
|
|
|
75
|
+
/**
|
|
76
|
+
* What a quick preview's document runs before the app's scripts: the
|
|
77
|
+
* server's markup out of `<body>`, every script left where it is -- the
|
|
78
|
+
* router's dehydrated state and the app's entry are among them. Hydration
|
|
79
|
+
* then finds the body empty and React renders the page, with the new code's
|
|
80
|
+
* attributes and classes, where it would have kept the old ones.
|
|
81
|
+
*/
|
|
82
|
+
var QUICK_PREVIEW_RENDER_SCRIPT = "<script>(function(){var b=document.body;if(!b)return;" + "Array.prototype.slice.call(b.childNodes).forEach(function(n){" + 'if(n.nodeName!=="SCRIPT")b.removeChild(n)})})()</script>';
|
|
83
|
+
|
|
67
84
|
/** What the page sends the worker to store a preview's files. */
|
|
68
85
|
var QUICK_PREVIEW_STORE_MESSAGE = "val-quick-preview-store";
|
|
69
86
|
|
|
@@ -90,7 +107,7 @@ function isQuickPreviewFiles(value) {
|
|
|
90
107
|
* The worker, as the source it is served as: plain, and small, because it is
|
|
91
108
|
* served as a file of its own (`demoServiceWorker.ts` is the same).
|
|
92
109
|
*/
|
|
93
|
-
var QUICK_PREVIEW_SERVICE_WORKER_SOURCE = "\n\"use strict\";\nconst PARAM = \"".concat(QUICK_PREVIEW_PARAM, "\";\nconst CACHE = \"val-quick-preview\";\nconst PREFIX = \"/__val_quick/\";\nconst INDEX = PREFIX + \"index\";\nconst KEEP = 4;\n\nself.addEventListener(\"install\", () => self.skipWaiting());\nself.addEventListener(\"activate\", (event) => {\n event.waitUntil(self.clients.claim());\n});\n\nself.addEventListener(\"message\", (event) => {\n const data = event.data;\n if (!data || data.type !== \"").concat(QUICK_PREVIEW_STORE_MESSAGE, "\") return;\n const port = event.ports && event.ports[0];\n event.waitUntil(\n store(data.id, data.files).then(\n () => port && port.postMessage({ ok: true }),\n (error) =>\n port &&\n port.postMessage({\n ok: false,\n message: String((error && error.message) || error),\n }),\n ),\n );\n});\n\nfunction keyOf(id, path) {\n return PREFIX + id + \"/\" + path;\n}\n\nasync function store(id, files) {\n const cache = await caches.open(CACHE);\n for (const path of Object.keys(files)) {\n const file = files[path];\n const body = file.base64\n ? Uint8Array.from(atob(file.body), (c) => c.charCodeAt(0))\n : file.body;\n await cache.put(\n keyOf(id, path),\n new Response(body, {\n headers: { \"Content-Type\": file.contentType, \"Cache-Control\": \"no-store\" },\n }),\n );\n }\n // The newest few previews are kept; older ones are deleted.\n const index = await cache.match(INDEX);\n const ids = [id].concat(\n (index ? await index.json() : []).filter((other) => other !== id),\n );\n const dropped = ids.slice(KEEP);\n if (dropped.length > 0) {\n for (const request of await cache.keys()) {\n const path = new URL(request.url).pathname;\n if (dropped.some((old) => path.startsWith(PREFIX + old + \"/\"))) {\n await cache.delete(request);\n }\n }\n }\n await cache.put(INDEX, new Response(JSON.stringify(ids.slice(0, KEEP))));\n}\n\n/** Which preview a document is: by its id, or by the address it was opened at. */\nconst byClient = new Map();\nasync function previewOf(clientId) {\n if (!clientId) return null;\n if (byClient.has(clientId)) return byClient.get(clientId);\n const client = await self.clients.get(clientId);\n const id = client ? new URL(client.url).searchParams.get(PARAM) : null;\n byClient.set(clientId, id);\n return id;\n}\n\nself.addEventListener(\"fetch\", (event) => {\n const url = new URL(event.request.url);\n if (url.origin !== self.location.origin) return;\n if (event.request.mode === \"navigate\") {\n if (event.resultingClientId)
|
|
110
|
+
var QUICK_PREVIEW_SERVICE_WORKER_SOURCE = "\n\"use strict\";\nconst PARAM = \"".concat(QUICK_PREVIEW_PARAM, "\";\nconst RENDER = ").concat(JSON.stringify(QUICK_PREVIEW_RENDER_SCRIPT), ";\nconst CACHE = \"val-quick-preview\";\nconst PREFIX = \"/__val_quick/\";\nconst INDEX = PREFIX + \"index\";\nconst KEEP = 4;\n\nself.addEventListener(\"install\", () => self.skipWaiting());\nself.addEventListener(\"activate\", (event) => {\n event.waitUntil(self.clients.claim());\n});\n\nself.addEventListener(\"message\", (event) => {\n const data = event.data;\n if (!data || data.type !== \"").concat(QUICK_PREVIEW_STORE_MESSAGE, "\") return;\n const port = event.ports && event.ports[0];\n event.waitUntil(\n store(data.id, data.files).then(\n () => port && port.postMessage({ ok: true }),\n (error) =>\n port &&\n port.postMessage({\n ok: false,\n message: String((error && error.message) || error),\n }),\n ),\n );\n});\n\nfunction keyOf(id, path) {\n return PREFIX + id + \"/\" + path;\n}\n\nasync function store(id, files) {\n const cache = await caches.open(CACHE);\n for (const path of Object.keys(files)) {\n const file = files[path];\n const body = file.base64\n ? Uint8Array.from(atob(file.body), (c) => c.charCodeAt(0))\n : file.body;\n await cache.put(\n keyOf(id, path),\n new Response(body, {\n headers: { \"Content-Type\": file.contentType, \"Cache-Control\": \"no-store\" },\n }),\n );\n }\n // The newest few previews are kept; older ones are deleted.\n const index = await cache.match(INDEX);\n const ids = [id].concat(\n (index ? await index.json() : []).filter((other) => other !== id),\n );\n const dropped = ids.slice(KEEP);\n if (dropped.length > 0) {\n for (const request of await cache.keys()) {\n const path = new URL(request.url).pathname;\n if (dropped.some((old) => path.startsWith(PREFIX + old + \"/\"))) {\n await cache.delete(request);\n }\n }\n }\n await cache.put(INDEX, new Response(JSON.stringify(ids.slice(0, KEEP))));\n}\n\n/** Which preview a document is: by its id, or by the address it was opened at. */\nconst byClient = new Map();\nasync function previewOf(clientId) {\n if (!clientId) return null;\n if (byClient.has(clientId)) return byClient.get(clientId);\n const client = await self.clients.get(clientId);\n const id = client ? new URL(client.url).searchParams.get(PARAM) : null;\n byClient.set(clientId, id);\n return id;\n}\n\nself.addEventListener(\"fetch\", (event) => {\n const url = new URL(event.request.url);\n if (url.origin !== self.location.origin) return;\n if (event.request.mode === \"navigate\") {\n const id = url.searchParams.get(PARAM);\n if (event.resultingClientId) byClient.set(event.resultingClientId, id);\n if (id) event.respondWith(rendered(event.request));\n return;\n }\n if (!url.pathname.startsWith(\"/_app/\")) return;\n event.respondWith(serve(event, url));\n});\n\n/** A quick preview's document, with the script that has it rendered, not hydrated. */\nasync function rendered(request) {\n const response = await fetch(request);\n const type = response.headers.get(\"Content-Type\") || \"\";\n if (!response.ok || !type.includes(\"text/html\")) return response;\n const html = await response.text();\n const at = html.lastIndexOf(\"</body>\");\n if (at === -1) return new Response(html, response);\n const headers = new Headers(response.headers);\n headers.delete(\"Content-Length\");\n headers.delete(\"Content-Encoding\");\n return new Response(html.slice(0, at) + RENDER + html.slice(at), {\n status: response.status,\n statusText: response.statusText,\n headers,\n });\n}\n\nasync function serve(event, url) {\n const id = await previewOf(event.clientId);\n if (id) {\n const hit = await caches.match(keyOf(id, url.pathname.slice(6)), {\n cacheName: CACHE,\n });\n if (hit) return hit;\n }\n return fetch(event.request);\n}\n");
|
|
94
111
|
|
|
95
112
|
/** How long the worker is given to install and store a preview. */
|
|
96
113
|
var STORE_TIMEOUT_MS = 30000;
|
|
@@ -342,7 +359,23 @@ function isValCanvasPageMessage(data) {
|
|
|
342
359
|
if (createForOfIteratorHelper._typeof(data) !== "object" || data === null) return false;
|
|
343
360
|
var message = data;
|
|
344
361
|
if (message.val !== VAL_CANVAS_MESSAGE) return false;
|
|
345
|
-
return message.type === "ready" || message.type === "elements" || message.type === "clicked" || message.type === "pinch" || message.type === "zoom" || message.type === "refreshing" || message.type === "elementPickingStopped" || message.type === "elementPicked" && isPickedElement(data.element) || message.type === "location" && isPageLocation(data) || message.type === "pageRead" && typeof data.requestId === "string" || message.type === "pageScreenshot" && typeof data.requestId === "string" || message.type === "quickPreviewStored" && typeof data.id === "string";
|
|
362
|
+
return message.type === "ready" && isModulePathList(data.holds) && isHeldVersions(data.holdsVersions) || message.type === "elements" || message.type === "clicked" || message.type === "pinch" || message.type === "zoom" || message.type === "refreshing" || message.type === "elementPickingStopped" || message.type === "elementPicked" && isPickedElement(data.element) || message.type === "location" && isPageLocation(data) || message.type === "pageRead" && typeof data.requestId === "string" || message.type === "pageScreenshot" && typeof data.requestId === "string" || message.type === "quickPreviewStored" && typeof data.id === "string";
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
/** `holdsVersions` on `ready`: absent, or a version per module path. */
|
|
366
|
+
function isHeldVersions(value) {
|
|
367
|
+
if (value === undefined) return true;
|
|
368
|
+
if (createForOfIteratorHelper._typeof(value) !== "object" || value === null) return false;
|
|
369
|
+
return Object.values(value).every(function (version) {
|
|
370
|
+
return createForOfIteratorHelper._typeof(version) === "object" && version !== null && typeof version.schema === "string" && typeof version.base === "string" && typeof version.chain === "string";
|
|
371
|
+
});
|
|
372
|
+
}
|
|
373
|
+
|
|
374
|
+
/** `holds` on `ready`: absent, or a list of module paths. */
|
|
375
|
+
function isModulePathList(value) {
|
|
376
|
+
return value === undefined || Array.isArray(value) && value.every(function (path) {
|
|
377
|
+
return typeof path === "string";
|
|
378
|
+
});
|
|
346
379
|
}
|
|
347
380
|
function isPageLocation(value) {
|
|
348
381
|
var location = value;
|
|
@@ -407,7 +440,7 @@ function isValCanvasStudioMessage(data) {
|
|
|
407
440
|
var read = data;
|
|
408
441
|
return typeof read.requestId === "string" && (read.selector === undefined || typeof read.selector === "string") && (read.maxItems === undefined || typeof read.maxItems === "number");
|
|
409
442
|
}
|
|
410
|
-
return message.type === "rescan" || message.type === "highlight" || message.type === "setPicking" || message.type === "sourceUpdate" || message.type === "sourcesSynced";
|
|
443
|
+
return message.type === "rescan" || message.type === "highlight" || message.type === "setPicking" || message.type === "sourceUpdate" || message.type === "sourcesSynced" || message.type === "confirmed" && (data.seq === null || typeof data.seq === "number");
|
|
411
444
|
}
|
|
412
445
|
|
|
413
446
|
/**
|
|
@@ -1536,6 +1569,7 @@ function getPatternFromModuleFilePath(moduleFilePath, srcFolder) {
|
|
|
1536
1569
|
exports.DEFAULT_CANVAS_SELECTION = DEFAULT_CANVAS_SELECTION;
|
|
1537
1570
|
exports.DEFAULT_CANVAS_SELECTION_SOFT = DEFAULT_CANVAS_SELECTION_SOFT;
|
|
1538
1571
|
exports.QUICK_PREVIEW_PARAM = QUICK_PREVIEW_PARAM;
|
|
1572
|
+
exports.QUICK_PREVIEW_RENDER_SCRIPT = QUICK_PREVIEW_RENDER_SCRIPT;
|
|
1539
1573
|
exports.QUICK_PREVIEW_SERVICE_WORKER_PATH = QUICK_PREVIEW_SERVICE_WORKER_PATH;
|
|
1540
1574
|
exports.QUICK_PREVIEW_SERVICE_WORKER_SOURCE = QUICK_PREVIEW_SERVICE_WORKER_SOURCE;
|
|
1541
1575
|
exports.QUICK_PREVIEW_STORE_MESSAGE = QUICK_PREVIEW_STORE_MESSAGE;
|
|
@@ -47,6 +47,14 @@ var VAL_AI_SESSION_STORAGE_KEY = "val-ai-session";
|
|
|
47
47
|
* document's `/_app/` requests from the files, and everything else from
|
|
48
48
|
* the network: the HTML is the last build's, rendered by its server, and
|
|
49
49
|
* the new code takes it over in the browser.
|
|
50
|
+
* 4. It takes it over by RENDERING it, not by hydrating it. Hydration keeps
|
|
51
|
+
* the markup the server sent wherever only an attribute differs -- React
|
|
52
|
+
* mends text, never a `className` -- so a change to classes and props,
|
|
53
|
+
* which is most of what the design tab changes, hydrated into the old
|
|
54
|
+
* page and showed nothing. The worker adds a script to the end of that
|
|
55
|
+
* document's `<body>` ({@link QUICK_PREVIEW_RENDER_SCRIPT}) that takes the
|
|
56
|
+
* server's markup out before the app's scripts run, so hydration finds
|
|
57
|
+
* nothing to keep and React renders the page with the new code.
|
|
50
58
|
*
|
|
51
59
|
* What it cannot show, and the tab says so: anything on the server. Server
|
|
52
60
|
* functions, API routes and loaders that run there are the last build's, as
|
|
@@ -64,6 +72,15 @@ var QUICK_PREVIEW_PARAM = "val_quick";
|
|
|
64
72
|
/** Where Val's API serves the worker: under `/api/val`. */
|
|
65
73
|
var QUICK_PREVIEW_SERVICE_WORKER_PATH = "/quick-preview-sw.js";
|
|
66
74
|
|
|
75
|
+
/**
|
|
76
|
+
* What a quick preview's document runs before the app's scripts: the
|
|
77
|
+
* server's markup out of `<body>`, every script left where it is -- the
|
|
78
|
+
* router's dehydrated state and the app's entry are among them. Hydration
|
|
79
|
+
* then finds the body empty and React renders the page, with the new code's
|
|
80
|
+
* attributes and classes, where it would have kept the old ones.
|
|
81
|
+
*/
|
|
82
|
+
var QUICK_PREVIEW_RENDER_SCRIPT = "<script>(function(){var b=document.body;if(!b)return;" + "Array.prototype.slice.call(b.childNodes).forEach(function(n){" + 'if(n.nodeName!=="SCRIPT")b.removeChild(n)})})()</script>';
|
|
83
|
+
|
|
67
84
|
/** What the page sends the worker to store a preview's files. */
|
|
68
85
|
var QUICK_PREVIEW_STORE_MESSAGE = "val-quick-preview-store";
|
|
69
86
|
|
|
@@ -90,7 +107,7 @@ function isQuickPreviewFiles(value) {
|
|
|
90
107
|
* The worker, as the source it is served as: plain, and small, because it is
|
|
91
108
|
* served as a file of its own (`demoServiceWorker.ts` is the same).
|
|
92
109
|
*/
|
|
93
|
-
var QUICK_PREVIEW_SERVICE_WORKER_SOURCE = "\n\"use strict\";\nconst PARAM = \"".concat(QUICK_PREVIEW_PARAM, "\";\nconst CACHE = \"val-quick-preview\";\nconst PREFIX = \"/__val_quick/\";\nconst INDEX = PREFIX + \"index\";\nconst KEEP = 4;\n\nself.addEventListener(\"install\", () => self.skipWaiting());\nself.addEventListener(\"activate\", (event) => {\n event.waitUntil(self.clients.claim());\n});\n\nself.addEventListener(\"message\", (event) => {\n const data = event.data;\n if (!data || data.type !== \"").concat(QUICK_PREVIEW_STORE_MESSAGE, "\") return;\n const port = event.ports && event.ports[0];\n event.waitUntil(\n store(data.id, data.files).then(\n () => port && port.postMessage({ ok: true }),\n (error) =>\n port &&\n port.postMessage({\n ok: false,\n message: String((error && error.message) || error),\n }),\n ),\n );\n});\n\nfunction keyOf(id, path) {\n return PREFIX + id + \"/\" + path;\n}\n\nasync function store(id, files) {\n const cache = await caches.open(CACHE);\n for (const path of Object.keys(files)) {\n const file = files[path];\n const body = file.base64\n ? Uint8Array.from(atob(file.body), (c) => c.charCodeAt(0))\n : file.body;\n await cache.put(\n keyOf(id, path),\n new Response(body, {\n headers: { \"Content-Type\": file.contentType, \"Cache-Control\": \"no-store\" },\n }),\n );\n }\n // The newest few previews are kept; older ones are deleted.\n const index = await cache.match(INDEX);\n const ids = [id].concat(\n (index ? await index.json() : []).filter((other) => other !== id),\n );\n const dropped = ids.slice(KEEP);\n if (dropped.length > 0) {\n for (const request of await cache.keys()) {\n const path = new URL(request.url).pathname;\n if (dropped.some((old) => path.startsWith(PREFIX + old + \"/\"))) {\n await cache.delete(request);\n }\n }\n }\n await cache.put(INDEX, new Response(JSON.stringify(ids.slice(0, KEEP))));\n}\n\n/** Which preview a document is: by its id, or by the address it was opened at. */\nconst byClient = new Map();\nasync function previewOf(clientId) {\n if (!clientId) return null;\n if (byClient.has(clientId)) return byClient.get(clientId);\n const client = await self.clients.get(clientId);\n const id = client ? new URL(client.url).searchParams.get(PARAM) : null;\n byClient.set(clientId, id);\n return id;\n}\n\nself.addEventListener(\"fetch\", (event) => {\n const url = new URL(event.request.url);\n if (url.origin !== self.location.origin) return;\n if (event.request.mode === \"navigate\") {\n if (event.resultingClientId)
|
|
110
|
+
var QUICK_PREVIEW_SERVICE_WORKER_SOURCE = "\n\"use strict\";\nconst PARAM = \"".concat(QUICK_PREVIEW_PARAM, "\";\nconst RENDER = ").concat(JSON.stringify(QUICK_PREVIEW_RENDER_SCRIPT), ";\nconst CACHE = \"val-quick-preview\";\nconst PREFIX = \"/__val_quick/\";\nconst INDEX = PREFIX + \"index\";\nconst KEEP = 4;\n\nself.addEventListener(\"install\", () => self.skipWaiting());\nself.addEventListener(\"activate\", (event) => {\n event.waitUntil(self.clients.claim());\n});\n\nself.addEventListener(\"message\", (event) => {\n const data = event.data;\n if (!data || data.type !== \"").concat(QUICK_PREVIEW_STORE_MESSAGE, "\") return;\n const port = event.ports && event.ports[0];\n event.waitUntil(\n store(data.id, data.files).then(\n () => port && port.postMessage({ ok: true }),\n (error) =>\n port &&\n port.postMessage({\n ok: false,\n message: String((error && error.message) || error),\n }),\n ),\n );\n});\n\nfunction keyOf(id, path) {\n return PREFIX + id + \"/\" + path;\n}\n\nasync function store(id, files) {\n const cache = await caches.open(CACHE);\n for (const path of Object.keys(files)) {\n const file = files[path];\n const body = file.base64\n ? Uint8Array.from(atob(file.body), (c) => c.charCodeAt(0))\n : file.body;\n await cache.put(\n keyOf(id, path),\n new Response(body, {\n headers: { \"Content-Type\": file.contentType, \"Cache-Control\": \"no-store\" },\n }),\n );\n }\n // The newest few previews are kept; older ones are deleted.\n const index = await cache.match(INDEX);\n const ids = [id].concat(\n (index ? await index.json() : []).filter((other) => other !== id),\n );\n const dropped = ids.slice(KEEP);\n if (dropped.length > 0) {\n for (const request of await cache.keys()) {\n const path = new URL(request.url).pathname;\n if (dropped.some((old) => path.startsWith(PREFIX + old + \"/\"))) {\n await cache.delete(request);\n }\n }\n }\n await cache.put(INDEX, new Response(JSON.stringify(ids.slice(0, KEEP))));\n}\n\n/** Which preview a document is: by its id, or by the address it was opened at. */\nconst byClient = new Map();\nasync function previewOf(clientId) {\n if (!clientId) return null;\n if (byClient.has(clientId)) return byClient.get(clientId);\n const client = await self.clients.get(clientId);\n const id = client ? new URL(client.url).searchParams.get(PARAM) : null;\n byClient.set(clientId, id);\n return id;\n}\n\nself.addEventListener(\"fetch\", (event) => {\n const url = new URL(event.request.url);\n if (url.origin !== self.location.origin) return;\n if (event.request.mode === \"navigate\") {\n const id = url.searchParams.get(PARAM);\n if (event.resultingClientId) byClient.set(event.resultingClientId, id);\n if (id) event.respondWith(rendered(event.request));\n return;\n }\n if (!url.pathname.startsWith(\"/_app/\")) return;\n event.respondWith(serve(event, url));\n});\n\n/** A quick preview's document, with the script that has it rendered, not hydrated. */\nasync function rendered(request) {\n const response = await fetch(request);\n const type = response.headers.get(\"Content-Type\") || \"\";\n if (!response.ok || !type.includes(\"text/html\")) return response;\n const html = await response.text();\n const at = html.lastIndexOf(\"</body>\");\n if (at === -1) return new Response(html, response);\n const headers = new Headers(response.headers);\n headers.delete(\"Content-Length\");\n headers.delete(\"Content-Encoding\");\n return new Response(html.slice(0, at) + RENDER + html.slice(at), {\n status: response.status,\n statusText: response.statusText,\n headers,\n });\n}\n\nasync function serve(event, url) {\n const id = await previewOf(event.clientId);\n if (id) {\n const hit = await caches.match(keyOf(id, url.pathname.slice(6)), {\n cacheName: CACHE,\n });\n if (hit) return hit;\n }\n return fetch(event.request);\n}\n");
|
|
94
111
|
|
|
95
112
|
/** How long the worker is given to install and store a preview. */
|
|
96
113
|
var STORE_TIMEOUT_MS = 30000;
|
|
@@ -342,7 +359,23 @@ function isValCanvasPageMessage(data) {
|
|
|
342
359
|
if (createForOfIteratorHelper._typeof(data) !== "object" || data === null) return false;
|
|
343
360
|
var message = data;
|
|
344
361
|
if (message.val !== VAL_CANVAS_MESSAGE) return false;
|
|
345
|
-
return message.type === "ready" || message.type === "elements" || message.type === "clicked" || message.type === "pinch" || message.type === "zoom" || message.type === "refreshing" || message.type === "elementPickingStopped" || message.type === "elementPicked" && isPickedElement(data.element) || message.type === "location" && isPageLocation(data) || message.type === "pageRead" && typeof data.requestId === "string" || message.type === "pageScreenshot" && typeof data.requestId === "string" || message.type === "quickPreviewStored" && typeof data.id === "string";
|
|
362
|
+
return message.type === "ready" && isModulePathList(data.holds) && isHeldVersions(data.holdsVersions) || message.type === "elements" || message.type === "clicked" || message.type === "pinch" || message.type === "zoom" || message.type === "refreshing" || message.type === "elementPickingStopped" || message.type === "elementPicked" && isPickedElement(data.element) || message.type === "location" && isPageLocation(data) || message.type === "pageRead" && typeof data.requestId === "string" || message.type === "pageScreenshot" && typeof data.requestId === "string" || message.type === "quickPreviewStored" && typeof data.id === "string";
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
/** `holdsVersions` on `ready`: absent, or a version per module path. */
|
|
366
|
+
function isHeldVersions(value) {
|
|
367
|
+
if (value === undefined) return true;
|
|
368
|
+
if (createForOfIteratorHelper._typeof(value) !== "object" || value === null) return false;
|
|
369
|
+
return Object.values(value).every(function (version) {
|
|
370
|
+
return createForOfIteratorHelper._typeof(version) === "object" && version !== null && typeof version.schema === "string" && typeof version.base === "string" && typeof version.chain === "string";
|
|
371
|
+
});
|
|
372
|
+
}
|
|
373
|
+
|
|
374
|
+
/** `holds` on `ready`: absent, or a list of module paths. */
|
|
375
|
+
function isModulePathList(value) {
|
|
376
|
+
return value === undefined || Array.isArray(value) && value.every(function (path) {
|
|
377
|
+
return typeof path === "string";
|
|
378
|
+
});
|
|
346
379
|
}
|
|
347
380
|
function isPageLocation(value) {
|
|
348
381
|
var location = value;
|
|
@@ -407,7 +440,7 @@ function isValCanvasStudioMessage(data) {
|
|
|
407
440
|
var read = data;
|
|
408
441
|
return typeof read.requestId === "string" && (read.selector === undefined || typeof read.selector === "string") && (read.maxItems === undefined || typeof read.maxItems === "number");
|
|
409
442
|
}
|
|
410
|
-
return message.type === "rescan" || message.type === "highlight" || message.type === "setPicking" || message.type === "sourceUpdate" || message.type === "sourcesSynced";
|
|
443
|
+
return message.type === "rescan" || message.type === "highlight" || message.type === "setPicking" || message.type === "sourceUpdate" || message.type === "sourcesSynced" || message.type === "confirmed" && (data.seq === null || typeof data.seq === "number");
|
|
411
444
|
}
|
|
412
445
|
|
|
413
446
|
/**
|
|
@@ -1536,6 +1569,7 @@ function getPatternFromModuleFilePath(moduleFilePath, srcFolder) {
|
|
|
1536
1569
|
exports.DEFAULT_CANVAS_SELECTION = DEFAULT_CANVAS_SELECTION;
|
|
1537
1570
|
exports.DEFAULT_CANVAS_SELECTION_SOFT = DEFAULT_CANVAS_SELECTION_SOFT;
|
|
1538
1571
|
exports.QUICK_PREVIEW_PARAM = QUICK_PREVIEW_PARAM;
|
|
1572
|
+
exports.QUICK_PREVIEW_RENDER_SCRIPT = QUICK_PREVIEW_RENDER_SCRIPT;
|
|
1539
1573
|
exports.QUICK_PREVIEW_SERVICE_WORKER_PATH = QUICK_PREVIEW_SERVICE_WORKER_PATH;
|
|
1540
1574
|
exports.QUICK_PREVIEW_SERVICE_WORKER_SOURCE = QUICK_PREVIEW_SERVICE_WORKER_SOURCE;
|
|
1541
1575
|
exports.QUICK_PREVIEW_STORE_MESSAGE = QUICK_PREVIEW_STORE_MESSAGE;
|