@foldkit/vite-plugin 0.24.0-canary.eb11872b6977 → 0.24.0-canary.ff73466cfc55
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/README.md +12 -15
- package/dist/build.d.ts.map +1 -1
- package/dist/build.js +4 -1
- package/dist/buildToken.d.ts +6 -14
- package/dist/buildToken.d.ts.map +1 -1
- package/dist/buildToken.js +153 -64
- package/dist/foldkitPackages.d.ts +12 -0
- package/dist/foldkitPackages.d.ts.map +1 -0
- package/dist/foldkitPackages.js +37 -0
- package/dist/index.d.ts +13 -17
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +28 -38
- package/package.json +11 -10
package/README.md
CHANGED
|
@@ -119,34 +119,31 @@ Server-rendered HTML carries the deployment id, and the client bundle carries it
|
|
|
119
119
|
|
|
120
120
|
Nothing moves, so no custom element reconnects and no frame reloads. The containment blocks native page interaction; it is not a script or global-event sandbox. A client already running in an open tab is not rechecked when a deployment lands because the comparison happens only when a client boots against a page.
|
|
121
121
|
|
|
122
|
-
|
|
122
|
+
When one Vite app build produces the client and server artifacts, the plugin generates an opaque id and compiles it into Foldkit in both. The entries need no build-id wiring:
|
|
123
123
|
|
|
124
124
|
```typescript
|
|
125
|
-
|
|
125
|
+
// src/entry.server.ts
|
|
126
|
+
Server.renderToString(config, { flags })
|
|
127
|
+
|
|
128
|
+
// src/entry.ts
|
|
129
|
+
Runtime.hydrate(application)
|
|
126
130
|
```
|
|
127
131
|
|
|
128
|
-
|
|
132
|
+
Use the `buildId` option or `FOLDKIT_BUILD_ID` as an explicit override when client and server build in separate jobs, or when the id should name a deployment in another system:
|
|
129
133
|
|
|
130
134
|
```typescript
|
|
131
|
-
|
|
132
|
-
Server.renderToString(config, {
|
|
133
|
-
flags,
|
|
134
|
-
buildId: import.meta.env.FOLDKIT_BUILD_ID,
|
|
135
|
-
})
|
|
136
|
-
|
|
137
|
-
// src/entry.ts
|
|
138
|
-
Runtime.hydrate(application, { buildId: import.meta.env.FOLDKIT_BUILD_ID })
|
|
135
|
+
plugins: [foldkit({ buildId: process.env.DEPLOYMENT_ID })]
|
|
139
136
|
```
|
|
140
137
|
|
|
141
|
-
|
|
138
|
+
Three things have to be true:
|
|
142
139
|
|
|
143
140
|
- The id appears in the HTML every visitor receives, so it must never contain a secret.
|
|
144
141
|
- Two deployments must never share an id.
|
|
145
|
-
-
|
|
142
|
+
- Separate build jobs must receive the same explicit override.
|
|
146
143
|
|
|
147
|
-
A hydratable render
|
|
144
|
+
A hydratable render with neither a compiled nor explicit id fails with `MissingBuildId`. The dev server generates an opaque id for its own client and server transforms.
|
|
148
145
|
|
|
149
|
-
The standalone `foldkitSsr({ serverEntry, buildId })` export
|
|
146
|
+
The standalone `foldkitSsr({ serverEntry, buildId })` export retains explicit build-id support for separately orchestrated integrations. The aggregate `foldkit({ ssr })` plugin owns the automatic path.
|
|
150
147
|
|
|
151
148
|
## DevTools overlay
|
|
152
149
|
|
package/dist/build.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"build.d.ts","sourceRoot":"","sources":["../src/build.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,MAAM,QAAQ,CAAA;
|
|
1
|
+
{"version":3,"file":"build.d.ts","sourceRoot":"","sources":["../src/build.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,MAAM,QAAQ,CAAA;AAI/B,OAAO,QAA8B,MAAM,WAAW,CAAA;AAEtD,OAAO,KAAK,EAGV,MAAM,EAEP,MAAM,MAAM,CAAA;AAEb,mEAAmE;AACnE,MAAM,MAAM,uBAAuB,GAAG,QAAQ,CAAC;IAC7C;;;OAGG;IACH,KAAK,CAAC,EAAE,aAAa,CAAC,MAAM,CAAC,CAAA;IAC7B;;;;;OAKG;IACH,MAAM,CAAC,EAAE,MAAM,CAAA;IACf;;;;OAIG;IACH,WAAW,CAAC,EAAE,MAAM,CAAA;CACrB,CAAC,CAAA;AAEF,4EAA4E;AAC5E,MAAM,MAAM,mBAAmB,GAAG,QAAQ,CAAC;IACzC,0CAA0C;IAC1C,YAAY,CAAC,EAAE,MAAM,CAAA;IACrB,yCAAyC;IACzC,YAAY,CAAC,EAAE,MAAM,CAAA;IACrB;;;;OAIG;IACH,WAAW,CAAC,EAAE,MAAM,CAAA;IACpB;;;OAGG;IACH,SAAS,CAAC,EAAE,OAAO,GAAG,uBAAuB,CAAA;CAC9C,CAAC,CAAA;AAEF,6EAA6E;AAC7E,eAAO,MAAM,uBAAuB,0BAA0B,CAAA;AAE9D;;;;;;GAMG;AACH,eAAO,MAAM,oBAAoB;IAC/B;;;;OAIG;;IAEH;;;;OAIG;;IAEH,+EAA+E;;IAE/E,2DAA2D;;IAE3D,6EAA6E;;EAE7E,CAAA;AAEF;;;;;GAKG;AACH,MAAM,MAAM,oBAAoB,GAAG,OAAO,oBAAoB,CAAC,IAAI,CAAA;AAEnE,iFAAiF;AACjF,eAAO,MAAM,oBAAoB;IAC/B,+CAA+C;;IAE/C,kDAAkD;;IAElD,iDAAiD;;IAEjD,kDAAkD;;IAElD,0DAA0D;;QAtC1D;;;;WAIG;;QAEH;;;;WAIG;;QAEH,+EAA+E;;QAE/E,2DAA2D;;QAE3D,6EAA6E;;;EAwB7E,CAAA;AAEF,sEAAsE;AACtE,MAAM,MAAM,oBAAoB,GAAG,OAAO,oBAAoB,CAAC,IAAI,CAAA;AAEnE,2DAA2D;AAC3D,MAAM,MAAM,eAAe,GAAG,QAAQ,CAAC;IACrC,2CAA2C;IAC3C,WAAW,EAAE,MAAM,CAAA;IACnB,uDAAuD;IACvD,aAAa,EAAE,OAAO,uBAAuB,CAAA;IAC7C;;;;OAIG;IACH,gBAAgB,EAAE,MAAM,oBAAoB,CAAA;CAC7C,CAAC,CAAA;AAcF,eAAO,MAAM,YAAY,SACjB,MAAM,aACD,MAAM,YACR,OAAO,QAAQ,KACvB,MAQF,CAAA;AAyFD,eAAO,MAAM,eAAe,oBACT,MAAM,QACjB,MAAM,UACJ,MAAM,YACL,OAAO,QAAQ,KACvB,QAAQ,CAAC;IAAE,GAAG,EAAE,GAAG,CAAC;IAAC,IAAI,EAAE,MAAM,CAAA;CAAE,CA0CrC,CAAA;AAsFD;;;;;;;;GAQG;AACH,eAAO,MAAM,YAAY,gBACV,MAAM,YACV,mBAAmB,KAC3B,MAAM,CAAC,eAAe,CAqPxB,CAAA"}
|
package/dist/build.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { Schema } from 'effect';
|
|
2
|
+
import { randomUUID } from 'node:crypto';
|
|
2
3
|
import { mkdir, writeFile } from 'node:fs/promises';
|
|
3
4
|
import nodePath, { dirname, resolve } from 'node:path';
|
|
4
5
|
import { pathToFileURL } from 'node:url';
|
|
@@ -231,7 +232,9 @@ export const foldkitBuild = (serverEntry, options = {}) => {
|
|
|
231
232
|
if (!entryFile.startsWith(`${contained}${nodePath.sep}`)) {
|
|
232
233
|
throw new Error(`[foldkit] the server entry "${entryFileName}" resolves outside the server build at "${contained}".`);
|
|
233
234
|
}
|
|
234
|
-
const
|
|
235
|
+
const entryUrl = pathToFileURL(entryFile);
|
|
236
|
+
entryUrl.searchParams.set('foldkit-build', randomUUID());
|
|
237
|
+
const entry = await import(entryUrl.href);
|
|
235
238
|
if (typeof entry.renderPage !== 'function') {
|
|
236
239
|
throw new Error(`[foldkit] "${entryFileName}" exports no renderPage function, so there is nothing to generate pages with.`);
|
|
237
240
|
}
|
package/dist/buildToken.d.ts
CHANGED
|
@@ -1,26 +1,18 @@
|
|
|
1
1
|
import type { Plugin } from 'vite';
|
|
2
|
-
/** The build id
|
|
3
|
-
*
|
|
2
|
+
/** The build id explicitly supplied through plugin configuration or the
|
|
3
|
+
* environment, or `undefined` when neither supplied a nonempty value.
|
|
4
4
|
*
|
|
5
5
|
* @internal Exported for tests.
|
|
6
6
|
*/
|
|
7
7
|
export declare const resolveBuildId: (configured?: string) => string | undefined;
|
|
8
|
-
/** The
|
|
9
|
-
* when a build was given no id and must refuse to render a hydratable page.
|
|
8
|
+
/** The id a standalone plugin compiles for one Vite command.
|
|
10
9
|
*
|
|
11
10
|
* @internal Exported for tests.
|
|
12
11
|
*/
|
|
13
12
|
export declare const buildIdForCommand: (command: 'build' | 'serve', configured?: string) => string | undefined;
|
|
14
|
-
/**
|
|
15
|
-
* Compiles the deployment's build id into application code as
|
|
16
|
-
* `import.meta.env.FOLDKIT_BUILD_ID`, for the client entry and the server entry
|
|
17
|
-
* to hand to `Runtime.hydrate` and `renderToString`.
|
|
13
|
+
/** Compiles one build identity into application entries and Foldkit itself.
|
|
18
14
|
*
|
|
19
|
-
*
|
|
20
|
-
* compiles nothing when it was given neither, so a hydratable render fails with
|
|
21
|
-
* `MissingBuildId` rather than serving a page hydration cannot place.
|
|
22
|
-
* Development serves a fixed id instead because one live source session
|
|
23
|
-
* supplies both transforms and has no deployment identity to derive.
|
|
15
|
+
* @internal
|
|
24
16
|
*/
|
|
25
|
-
export declare const foldkitBuildToken: (buildId?: string) => Plugin
|
|
17
|
+
export declare const foldkitBuildToken: (buildId?: string, verifyFrameworkIdentity?: boolean) => Array<Plugin>;
|
|
26
18
|
//# sourceMappingURL=buildToken.d.ts.map
|
package/dist/buildToken.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"buildToken.d.ts","sourceRoot":"","sources":["../src/buildToken.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"buildToken.d.ts","sourceRoot":"","sources":["../src/buildToken.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,MAAM,EAA+B,MAAM,MAAM,CAAA;AAoC/D;;;;GAIG;AACH,eAAO,MAAM,cAAc,gBAAiB,MAAM,KAAG,MAAM,GAAG,SAQ7D,CAAA;AAED;;;GAGG;AACH,eAAO,MAAM,iBAAiB,YACnB,OAAO,GAAG,OAAO,eACb,MAAM,KAClB,MAAM,GAAG,SAMX,CAAA;AA0ID;;;GAGG;AACH,eAAO,MAAM,iBAAiB,aAClB,MAAM,wCAEf,KAAK,CAAC,MAAM,CA+Dd,CAAA"}
|
package/dist/buildToken.js
CHANGED
|
@@ -1,38 +1,28 @@
|
|
|
1
|
+
import MagicString from 'magic-string';
|
|
2
|
+
import { randomUUID } from 'node:crypto';
|
|
3
|
+
import { isFoldkitSingletonPackageSpecifier } from './foldkitPackages.js';
|
|
1
4
|
// The build id names the deployment a page came from. The server stamps it on
|
|
2
5
|
// the rendered root, the client carries it, and hydration refuses a page whose
|
|
3
6
|
// id is not its own before it adopts any DOM.
|
|
4
7
|
//
|
|
5
|
-
//
|
|
6
|
-
//
|
|
7
|
-
//
|
|
8
|
-
//
|
|
9
|
-
//
|
|
10
|
-
// wins on the element, so a stale page's `<input name="email">` can otherwise be
|
|
11
|
-
// adopted for a new build's `<input name="ssn">`, carrying what a visitor typed
|
|
12
|
-
// into a field that submits under a different name.
|
|
8
|
+
// The aggregate plugin generates one opaque id when a Vite app build contains
|
|
9
|
+
// both the client and ssr environments. The id lives on the ViteBuilder's
|
|
10
|
+
// ResolvedConfig object, so concurrent builders cannot overwrite one another
|
|
11
|
+
// and a later builder in the same process gets a fresh value. Separately
|
|
12
|
+
// invoked builds have no shared session and must receive an explicit value.
|
|
13
13
|
//
|
|
14
|
-
//
|
|
15
|
-
//
|
|
16
|
-
//
|
|
17
|
-
//
|
|
18
|
-
//
|
|
19
|
-
//
|
|
20
|
-
// in the project turns a value published in HTML into an oracle for the secrets
|
|
21
|
-
// among them. A value the deployment already has (a commit, a release tag, a
|
|
22
|
-
// container digest) has none of those problems.
|
|
23
|
-
//
|
|
24
|
-
// This plugin only compiles the id into application code. Foldkit itself is an
|
|
25
|
-
// ordinary dependency that Vite externalizes from a server build, where a
|
|
26
|
-
// compile-time define never reaches it, so the id is handed to `renderToString`
|
|
27
|
-
// and `Runtime.hydrate` explicitly rather than read from inside the framework.
|
|
28
|
-
//
|
|
29
|
-
// Development is compiled an id too. The dev SSR host renders through
|
|
30
|
-
// `renderToString` like any other, and a hydratable render refuses to run
|
|
31
|
-
// without one, so leaving development unnamed would fail every dev page
|
|
32
|
-
// request.
|
|
14
|
+
// Foldkit's own build-token module contains a placeholder call. This transform
|
|
15
|
+
// replaces that call in both artifacts, which lets Runtime.hydrate and
|
|
16
|
+
// renderToString consume the value without application forwarding. Installed
|
|
17
|
+
// Foldkit must therefore participate in the server build; the aggregate plugin
|
|
18
|
+
// configures that boundary and the post-build check refuses an artifact that
|
|
19
|
+
// still imports Foldkit externally.
|
|
33
20
|
const BUILD_ID_ENVIRONMENT_VARIABLE = 'FOLDKIT_BUILD_ID';
|
|
34
|
-
|
|
35
|
-
|
|
21
|
+
const FRAMEWORK_BUILD_ID_PLACEHOLDER = 'foldkitBuildIdPlaceholder()';
|
|
22
|
+
const buildIdentitySessions = new WeakMap();
|
|
23
|
+
const developmentBuildIds = new WeakMap();
|
|
24
|
+
/** The build id explicitly supplied through plugin configuration or the
|
|
25
|
+
* environment, or `undefined` when neither supplied a nonempty value.
|
|
36
26
|
*
|
|
37
27
|
* @internal Exported for tests.
|
|
38
28
|
*/
|
|
@@ -45,19 +35,7 @@ export const resolveBuildId = (configured) => {
|
|
|
45
35
|
? fromEnvironment
|
|
46
36
|
: undefined;
|
|
47
37
|
};
|
|
48
|
-
|
|
49
|
-
// one live source session supplies both the server and client transforms rather
|
|
50
|
-
// than producing independently deployable artifacts. A value that moved would
|
|
51
|
-
// only make the dev server disagree with the tab already open against it. A
|
|
52
|
-
// hydratable render still requires an id, so development has to be given one
|
|
53
|
-
// rather than left without.
|
|
54
|
-
//
|
|
55
|
-
// It is exactly wrong for a build, which is why a build with no id is refused
|
|
56
|
-
// rather than defaulted: two deployments sharing an id is the case the id
|
|
57
|
-
// exists to catch.
|
|
58
|
-
const DEVELOPMENT_BUILD_ID = 'development';
|
|
59
|
-
/** The value `import.meta.env.FOLDKIT_BUILD_ID` compiles to, or `undefined`
|
|
60
|
-
* when a build was given no id and must refuse to render a hydratable page.
|
|
38
|
+
/** The id a standalone plugin compiles for one Vite command.
|
|
61
39
|
*
|
|
62
40
|
* @internal Exported for tests.
|
|
63
41
|
*/
|
|
@@ -66,29 +44,140 @@ export const buildIdForCommand = (command, configured) => {
|
|
|
66
44
|
if (resolved !== undefined) {
|
|
67
45
|
return resolved;
|
|
68
46
|
}
|
|
69
|
-
return command === 'serve' ?
|
|
47
|
+
return command === 'serve' ? 'development' : undefined;
|
|
48
|
+
};
|
|
49
|
+
const hasCoordinatedArtifacts = (builder) => builder.environments['client'] !== undefined &&
|
|
50
|
+
builder.environments['ssr'] !== undefined;
|
|
51
|
+
const beginBuildIdentity = (builder, configuredBuildId, verifyFrameworkIdentity) => {
|
|
52
|
+
const isCoordinated = hasCoordinatedArtifacts(builder);
|
|
53
|
+
const buildId = configuredBuildId ?? (isCoordinated ? randomUUID() : undefined);
|
|
54
|
+
const session = {
|
|
55
|
+
buildId,
|
|
56
|
+
externalizedEnvironments: new Set(),
|
|
57
|
+
verifyFrameworkIdentity,
|
|
58
|
+
};
|
|
59
|
+
buildIdentitySessions.set(builder.config, session);
|
|
60
|
+
for (const environment of Object.values(builder.environments)) {
|
|
61
|
+
buildIdentitySessions.set(environment.config, session);
|
|
62
|
+
buildIdentitySessions.set(environment.getTopLevelConfig(), session);
|
|
63
|
+
}
|
|
64
|
+
};
|
|
65
|
+
const buildIdForConfig = (config, configuredBuildId) => {
|
|
66
|
+
if (config.command === 'build') {
|
|
67
|
+
return buildIdentitySessions.get(config)?.buildId ?? configuredBuildId;
|
|
68
|
+
}
|
|
69
|
+
const existing = developmentBuildIds.get(config);
|
|
70
|
+
if (existing !== undefined) {
|
|
71
|
+
return existing;
|
|
72
|
+
}
|
|
73
|
+
const fresh = configuredBuildId ?? randomUUID();
|
|
74
|
+
developmentBuildIds.set(config, fresh);
|
|
75
|
+
return fresh;
|
|
76
|
+
};
|
|
77
|
+
const replaceAll = (source, code, search, replacement) => {
|
|
78
|
+
let didReplace = false;
|
|
79
|
+
let fromIndex = 0;
|
|
80
|
+
while (fromIndex < code.length) {
|
|
81
|
+
const index = code.indexOf(search, fromIndex);
|
|
82
|
+
if (index === -1) {
|
|
83
|
+
return didReplace;
|
|
84
|
+
}
|
|
85
|
+
source.overwrite(index, index + search.length, replacement);
|
|
86
|
+
didReplace = true;
|
|
87
|
+
fromIndex = index + search.length;
|
|
88
|
+
}
|
|
89
|
+
return didReplace;
|
|
70
90
|
};
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
91
|
+
const isFoldkitBuildTokenModule = (id) => {
|
|
92
|
+
const fileName = (id.split('?', 1)[0] ?? '').replaceAll('\\', '/');
|
|
93
|
+
return (fileName.endsWith('/foldkit/src/buildToken.ts') ||
|
|
94
|
+
fileName.endsWith('/foldkit/dist/buildToken.js'));
|
|
95
|
+
};
|
|
96
|
+
const hasExternalFoldkitSingletonImport = (imports, dynamicImports) => [...imports, ...dynamicImports].some(isFoldkitSingletonPackageSpecifier);
|
|
97
|
+
const transformBuildIdentity = (code, id, config, configuredBuildId) => {
|
|
98
|
+
if (!isFoldkitBuildTokenModule(id) ||
|
|
99
|
+
!code.includes(FRAMEWORK_BUILD_ID_PLACEHOLDER)) {
|
|
100
|
+
return undefined;
|
|
101
|
+
}
|
|
102
|
+
const buildId = buildIdForConfig(config, configuredBuildId);
|
|
103
|
+
const replacement = buildId === undefined ? 'undefined' : JSON.stringify(buildId);
|
|
104
|
+
const transformed = new MagicString(code);
|
|
105
|
+
const transformedFramework = replaceAll(transformed, code, FRAMEWORK_BUILD_ID_PLACEHOLDER, replacement);
|
|
106
|
+
if (!transformedFramework) {
|
|
107
|
+
return undefined;
|
|
108
|
+
}
|
|
109
|
+
return {
|
|
110
|
+
code: transformed.toString(),
|
|
111
|
+
map: transformed.generateMap({ hires: 'boundary', source: id }),
|
|
112
|
+
};
|
|
113
|
+
};
|
|
114
|
+
const verifyBuildIdentity = (builder) => {
|
|
115
|
+
const session = buildIdentitySessions.get(builder.config);
|
|
116
|
+
if (session === undefined || !session.verifyFrameworkIdentity) {
|
|
117
|
+
return;
|
|
118
|
+
}
|
|
119
|
+
const externalized = [...session.externalizedEnvironments];
|
|
120
|
+
if (externalized.length === 0) {
|
|
121
|
+
return;
|
|
122
|
+
}
|
|
123
|
+
throw new Error('[foldkit] A Foldkit singleton package was externalized from the ' +
|
|
124
|
+
`${externalized.join(' and ')} ` +
|
|
125
|
+
`${externalized.length === 1 ? 'artifact' : 'artifacts'}, so it can ` +
|
|
126
|
+
'load a framework copy whose hydration build identity was not compiled. ' +
|
|
127
|
+
'Remove Foldkit packages from explicit SSR or Rolldown externalization ' +
|
|
128
|
+
'settings and let @foldkit/vite-plugin bundle them.');
|
|
129
|
+
};
|
|
130
|
+
/** Compiles one build identity into application entries and Foldkit itself.
|
|
75
131
|
*
|
|
76
|
-
*
|
|
77
|
-
* compiles nothing when it was given neither, so a hydratable render fails with
|
|
78
|
-
* `MissingBuildId` rather than serving a page hydration cannot place.
|
|
79
|
-
* Development serves a fixed id instead because one live source session
|
|
80
|
-
* supplies both transforms and has no deployment identity to derive.
|
|
132
|
+
* @internal
|
|
81
133
|
*/
|
|
82
|
-
export const foldkitBuildToken = (buildId) =>
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
:
|
|
89
|
-
|
|
90
|
-
|
|
134
|
+
export const foldkitBuildToken = (buildId, verifyFrameworkIdentity = true) => {
|
|
135
|
+
const configuredBuildId = resolveBuildId(buildId);
|
|
136
|
+
return [
|
|
137
|
+
{
|
|
138
|
+
name: 'foldkit:build-token',
|
|
139
|
+
enforce: 'pre',
|
|
140
|
+
sharedDuringBuild: true,
|
|
141
|
+
config: (_config, { command }) => {
|
|
142
|
+
const legacyBuildId = configuredBuildId ?? (command === 'serve' ? 'development' : undefined);
|
|
143
|
+
return legacyBuildId === undefined
|
|
144
|
+
? {}
|
|
145
|
+
: {
|
|
146
|
+
define: {
|
|
147
|
+
'import.meta.env.FOLDKIT_BUILD_ID': JSON.stringify(legacyBuildId),
|
|
148
|
+
},
|
|
149
|
+
};
|
|
150
|
+
},
|
|
151
|
+
buildApp: {
|
|
152
|
+
order: 'pre',
|
|
153
|
+
async handler(builder) {
|
|
154
|
+
beginBuildIdentity(builder, configuredBuildId, verifyFrameworkIdentity);
|
|
91
155
|
},
|
|
92
|
-
}
|
|
93
|
-
|
|
94
|
-
|
|
156
|
+
},
|
|
157
|
+
transform(code, id) {
|
|
158
|
+
const config = this.environment.getTopLevelConfig();
|
|
159
|
+
return transformBuildIdentity(code, id, config, configuredBuildId);
|
|
160
|
+
},
|
|
161
|
+
generateBundle(_options, bundle) {
|
|
162
|
+
const isExternalized = Object.values(bundle).some(output => output.type === 'chunk' &&
|
|
163
|
+
hasExternalFoldkitSingletonImport(output.imports, output.dynamicImports));
|
|
164
|
+
if (!isExternalized) {
|
|
165
|
+
return;
|
|
166
|
+
}
|
|
167
|
+
buildIdentitySessions
|
|
168
|
+
.get(this.environment.getTopLevelConfig())
|
|
169
|
+
?.externalizedEnvironments.add(this.environment.name);
|
|
170
|
+
},
|
|
171
|
+
},
|
|
172
|
+
{
|
|
173
|
+
name: 'foldkit:verify-build-token',
|
|
174
|
+
sharedDuringBuild: true,
|
|
175
|
+
buildApp: {
|
|
176
|
+
order: 'post',
|
|
177
|
+
async handler(builder) {
|
|
178
|
+
verifyBuildIdentity(builder);
|
|
179
|
+
},
|
|
180
|
+
},
|
|
181
|
+
},
|
|
182
|
+
];
|
|
183
|
+
};
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
/** Tests whether an import refers to a package that must share Foldkit's
|
|
2
|
+
* runtime instance.
|
|
3
|
+
*
|
|
4
|
+
* @internal
|
|
5
|
+
*/
|
|
6
|
+
export declare const isFoldkitSingletonPackageSpecifier: (specifier: string) => boolean;
|
|
7
|
+
/** Resolves the installed packages that must share Foldkit's runtime instance.
|
|
8
|
+
*
|
|
9
|
+
* @internal
|
|
10
|
+
*/
|
|
11
|
+
export declare const resolveInstalledFoldkitPackages: (root: string) => Array<string>;
|
|
12
|
+
//# sourceMappingURL=foldkitPackages.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"foldkitPackages.d.ts","sourceRoot":"","sources":["../src/foldkitPackages.ts"],"names":[],"mappings":"AAUA;;;;GAIG;AACH,eAAO,MAAM,kCAAkC,cAClC,MAAM,KAChB,OAKA,CAAA;AAEH;;;GAGG;AACH,eAAO,MAAM,+BAA+B,SACpC,MAAM,KACX,KAAK,CAAC,MAAM,CAmBd,CAAA"}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import { Array, Predicate } from 'effect';
|
|
2
|
+
import { createRequire } from 'node:module';
|
|
3
|
+
import { resolve } from 'node:path';
|
|
4
|
+
const FOLDKIT_SINGLETON_PACKAGES = [
|
|
5
|
+
'foldkit',
|
|
6
|
+
'@foldkit/ui',
|
|
7
|
+
'@foldkit/devtools',
|
|
8
|
+
];
|
|
9
|
+
/** Tests whether an import refers to a package that must share Foldkit's
|
|
10
|
+
* runtime instance.
|
|
11
|
+
*
|
|
12
|
+
* @internal
|
|
13
|
+
*/
|
|
14
|
+
export const isFoldkitSingletonPackageSpecifier = (specifier) => Array.some(FOLDKIT_SINGLETON_PACKAGES, packageName => specifier === packageName || specifier.startsWith(`${packageName}/`));
|
|
15
|
+
/** Resolves the installed packages that must share Foldkit's runtime instance.
|
|
16
|
+
*
|
|
17
|
+
* @internal
|
|
18
|
+
*/
|
|
19
|
+
export const resolveInstalledFoldkitPackages = (root) => {
|
|
20
|
+
// NOTE: Vite can supply a relative root before it resolves the config, while
|
|
21
|
+
// createRequire requires an absolute path.
|
|
22
|
+
const requireFromRoot = createRequire(resolve(root, 'noop.js'));
|
|
23
|
+
return Array.filter(FOLDKIT_SINGLETON_PACKAGES, packageName => {
|
|
24
|
+
try {
|
|
25
|
+
requireFromRoot.resolve(packageName);
|
|
26
|
+
return true;
|
|
27
|
+
}
|
|
28
|
+
catch (error) {
|
|
29
|
+
// NOTE: optional ESM-only packages can throw
|
|
30
|
+
// ERR_PACKAGE_PATH_NOT_EXPORTED even when installed. Only
|
|
31
|
+
// MODULE_NOT_FOUND proves this consumer does not have the package.
|
|
32
|
+
return !(error instanceof Error &&
|
|
33
|
+
Predicate.hasProperty(error, 'code') &&
|
|
34
|
+
error.code === 'MODULE_NOT_FOUND');
|
|
35
|
+
}
|
|
36
|
+
});
|
|
37
|
+
};
|
package/dist/index.d.ts
CHANGED
|
@@ -38,25 +38,21 @@ export type FoldkitPluginOptions = Readonly<{
|
|
|
38
38
|
build?: boolean | FoldkitBuildOptions;
|
|
39
39
|
}>;
|
|
40
40
|
/**
|
|
41
|
-
*
|
|
42
|
-
*
|
|
43
|
-
*
|
|
44
|
-
*
|
|
45
|
-
*
|
|
46
|
-
* body marked `inert`.
|
|
41
|
+
* An explicit identity for the deployment this build belongs to. Foldkit
|
|
42
|
+
* normally generates an opaque identity when one Vite app build coordinates
|
|
43
|
+
* the client and server artifacts, then compiles it into the framework in
|
|
44
|
+
* both. Hydration compares that value against the id the server stamped and
|
|
45
|
+
* refuses a page from another deployment before adopting its DOM.
|
|
47
46
|
*
|
|
48
|
-
*
|
|
49
|
-
*
|
|
50
|
-
*
|
|
51
|
-
*
|
|
47
|
+
* Set this when the client and server are built separately, or when the id
|
|
48
|
+
* should name a deployment in another system. The `FOLDKIT_BUILD_ID`
|
|
49
|
+
* environment variable supplies the same override when this option is
|
|
50
|
+
* absent. Give every artifact the same value. It is published in the page,
|
|
51
|
+
* so it must not be a secret.
|
|
52
52
|
*
|
|
53
|
-
*
|
|
54
|
-
*
|
|
55
|
-
*
|
|
56
|
-
* timestamp — gives the browser bundle and the server bundle different ids
|
|
57
|
-
* within one build, and every page of that deployment is then refused at
|
|
58
|
-
* hydration. Read it from the environment, or store a generated fallback
|
|
59
|
-
* back into the environment so later reads resolve the same id.
|
|
53
|
+
* Reusing an override across deployments makes stale pages appear current.
|
|
54
|
+
* Use a value that changes whenever the deployment's rendering inputs can
|
|
55
|
+
* change.
|
|
60
56
|
*/
|
|
61
57
|
buildId?: string;
|
|
62
58
|
}>;
|
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AA6CA,OAAO,KAAK,EAEV,MAAM,EAIP,MAAM,MAAM,CAAA;AAOb,OAAO,EAAE,KAAK,mBAAmB,EAAgB,MAAM,YAAY,CAAA;AAKnE,OAAO,EAAE,KAAK,iBAAiB,EAAc,MAAM,UAAU,CAAA;AAG7D,OAAO,EAAE,KAAK,eAAe,EAAE,kBAAkB,EAAE,MAAM,gBAAgB,CAAA;AACzE,OAAO,EACL,uBAAuB,EACvB,oBAAoB,EACpB,oBAAoB,EACpB,KAAK,eAAe,EACpB,KAAK,mBAAmB,EACxB,KAAK,uBAAuB,EAC5B,YAAY,GACb,MAAM,YAAY,CAAA;AACnB,OAAO,EAAE,KAAK,iBAAiB,EAAE,UAAU,EAAE,MAAM,UAAU,CAAA;AAC7D,OAAO,EACL,KAAK,2BAA2B,EAChC,mBAAmB,EACnB,qBAAqB,GACtB,MAAM,mBAAmB,CAAA;AAE1B,6CAA6C;AAC7C,MAAM,MAAM,oBAAoB,GAAG,QAAQ,CAAC;IAC1C;;;;;;;;OAQG;IACH,eAAe,CAAC,EAAE,MAAM,GAAG,KAAK,CAAA;IAChC;;;;;;;OAOG;IACH,GAAG,CAAC,EAAE,IAAI,CAAC,iBAAiB,EAAE,SAAS,GAAG,gBAAgB,CAAC,GACzD,QAAQ,CAAC;QACP;;;;;;;;WAQG;QACH,KAAK,CAAC,EAAE,OAAO,GAAG,mBAAmB,CAAA;KACtC,CAAC,CAAA;IACJ;;;;;;;;;;;;;;;;OAgBG;IACH,OAAO,CAAC,EAAE,MAAM,CAAA;CACjB,CAAC,CAAA;AAs6BF,eAAO,MAAM,OAAO,aAAa,oBAAoB,KAAQ,KAAK,CAAC,MAAM,CAuHxE,CAAA"}
|
package/dist/index.js
CHANGED
|
@@ -3,8 +3,6 @@ import { Event as DevToolsEvent, EventFrame, RELAY_RECORD_VERSION, RequestFrame,
|
|
|
3
3
|
import { PreserveModelMessage, RequestModelMessage, RestoreModelMessage, } from 'foldkit/model-preservation';
|
|
4
4
|
import { timingSafeEqual } from 'node:crypto';
|
|
5
5
|
import { createServer as createHttpServer, } from 'node:http';
|
|
6
|
-
import { createRequire } from 'node:module';
|
|
7
|
-
import { resolve } from 'node:path';
|
|
8
6
|
import { WebSocketServer } from 'ws';
|
|
9
7
|
import * as NodeCrypto from '@effect/platform-node/NodeCrypto';
|
|
10
8
|
import * as NodeFileSystem from '@effect/platform-node/NodeFileSystem';
|
|
@@ -12,6 +10,7 @@ import * as NodePath from '@effect/platform-node/NodePath';
|
|
|
12
10
|
import { foldkitBuild } from './build.js';
|
|
13
11
|
import { foldkitBuildToken } from './buildToken.js';
|
|
14
12
|
import { devToolsOverlayPlugin } from './devToolsOverlay.js';
|
|
13
|
+
import { resolveInstalledFoldkitPackages } from './foldkitPackages.js';
|
|
15
14
|
import { publishRelayRecord, retireRelayRecord } from './relayRegistry.js';
|
|
16
15
|
import { foldkitSsr } from './ssr.js';
|
|
17
16
|
import { foldkitViewIdentity } from './viewIdentity.js';
|
|
@@ -71,37 +70,6 @@ const FORCE_INCLUDED_EFFECT_NAMESPACES = [
|
|
|
71
70
|
'effect/SubscriptionRef',
|
|
72
71
|
'effect/Types',
|
|
73
72
|
];
|
|
74
|
-
// NOTE: a duplicate `foldkit` instance is its own hazard. If a bundler
|
|
75
|
-
// resolves `foldkit` (or a foldkit-consuming package like `@foldkit/ui`) to
|
|
76
|
-
// more than one copy, the copies get distinct Schema and tagged-message
|
|
77
|
-
// identities (decode and tag matching fail across the boundary) and separate
|
|
78
|
-
// module-level singleton state. `resolve.dedupe` (below) collapses every
|
|
79
|
-
// installed Foldkit package to one resolved copy.
|
|
80
|
-
const FOLDKIT_SINGLETON_PACKAGES = [
|
|
81
|
-
'foldkit',
|
|
82
|
-
'@foldkit/ui',
|
|
83
|
-
'@foldkit/devtools',
|
|
84
|
-
];
|
|
85
|
-
// NOTE: `@foldkit/ui` and `@foldkit/devtools` are optional, so dedupe only
|
|
86
|
-
// the ones the consumer installed. An installed ESM package resolves to
|
|
87
|
-
// ERR_PACKAGE_PATH_NOT_EXPORTED rather than succeeding, so a missing package
|
|
88
|
-
// is signalled only by MODULE_NOT_FOUND.
|
|
89
|
-
const resolveInstalledFoldkitPackages = (root) => {
|
|
90
|
-
// NOTE: `root` (Vite's `config.root`) can be relative at config-hook time,
|
|
91
|
-
// and createRequire requires an absolute path; `resolve` normalizes it.
|
|
92
|
-
const requireFromRoot = createRequire(resolve(root, 'noop.js'));
|
|
93
|
-
return Array.filter(FOLDKIT_SINGLETON_PACKAGES, packageName => {
|
|
94
|
-
try {
|
|
95
|
-
requireFromRoot.resolve(packageName);
|
|
96
|
-
return true;
|
|
97
|
-
}
|
|
98
|
-
catch (error) {
|
|
99
|
-
return !(error instanceof Error &&
|
|
100
|
-
Predicate.hasProperty(error, 'code') &&
|
|
101
|
-
error.code === 'MODULE_NOT_FOUND');
|
|
102
|
-
}
|
|
103
|
-
});
|
|
104
|
-
};
|
|
105
73
|
const Event = Data.taggedEnum();
|
|
106
74
|
const makeState = Effect.gen(function* () {
|
|
107
75
|
const preservedModels = yield* Ref.make(HashMap.empty());
|
|
@@ -545,13 +513,10 @@ export const foldkit = (options = {}) => {
|
|
|
545
513
|
const reloadPlugin = {
|
|
546
514
|
name: 'foldkit',
|
|
547
515
|
apply: 'serve',
|
|
548
|
-
config:
|
|
516
|
+
config: () => ({
|
|
549
517
|
optimizeDeps: {
|
|
550
518
|
include: [...FORCE_INCLUDED_EFFECT_NAMESPACES],
|
|
551
519
|
},
|
|
552
|
-
resolve: {
|
|
553
|
-
dedupe: resolveInstalledFoldkitPackages(userConfig.root ?? process.cwd()),
|
|
554
|
-
},
|
|
555
520
|
}),
|
|
556
521
|
configureServer: server => {
|
|
557
522
|
const events = Effect.runSync(Queue.unbounded());
|
|
@@ -577,8 +542,33 @@ export const foldkit = (options = {}) => {
|
|
|
577
542
|
return [];
|
|
578
543
|
},
|
|
579
544
|
};
|
|
545
|
+
const resolutionPlugin = {
|
|
546
|
+
name: 'foldkit:resolution',
|
|
547
|
+
config: userConfig => {
|
|
548
|
+
const singletonPackages = resolveInstalledFoldkitPackages(userConfig.root ?? process.cwd());
|
|
549
|
+
return {
|
|
550
|
+
optimizeDeps: {
|
|
551
|
+
exclude: ['foldkit'],
|
|
552
|
+
},
|
|
553
|
+
resolve: {
|
|
554
|
+
dedupe: singletonPackages,
|
|
555
|
+
},
|
|
556
|
+
ssr: {
|
|
557
|
+
noExternal: singletonPackages,
|
|
558
|
+
},
|
|
559
|
+
environments: {
|
|
560
|
+
ssr: {
|
|
561
|
+
resolve: {
|
|
562
|
+
noExternal: singletonPackages,
|
|
563
|
+
},
|
|
564
|
+
},
|
|
565
|
+
},
|
|
566
|
+
};
|
|
567
|
+
},
|
|
568
|
+
};
|
|
580
569
|
const shared = [
|
|
581
|
-
|
|
570
|
+
resolutionPlugin,
|
|
571
|
+
...foldkitBuildToken(options.buildId),
|
|
582
572
|
foldkitViewIdentity(),
|
|
583
573
|
devToolsOverlayPlugin(),
|
|
584
574
|
reloadPlugin,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@foldkit/vite-plugin",
|
|
3
|
-
"version": "0.24.0-canary.
|
|
3
|
+
"version": "0.24.0-canary.ff73466cfc55",
|
|
4
4
|
"description": "Vite plugin for Foldkit with state-preserving live reload",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.js",
|
|
@@ -16,27 +16,28 @@
|
|
|
16
16
|
"dist"
|
|
17
17
|
],
|
|
18
18
|
"peerDependencies": {
|
|
19
|
-
"effect": "4.0.0-rc.
|
|
20
|
-
"foldkit": "0.163.0-canary.
|
|
19
|
+
"effect": "4.0.0-rc.117",
|
|
20
|
+
"foldkit": "0.163.0-canary.ff73466cfc55",
|
|
21
21
|
"vite": "^8.0.0"
|
|
22
22
|
},
|
|
23
23
|
"dependencies": {
|
|
24
|
-
"@effect/platform-node": "4.0.0-rc.
|
|
25
|
-
"
|
|
24
|
+
"@effect/platform-node": "4.0.0-rc.117",
|
|
25
|
+
"@effect/platform-node-shared": "4.0.0-rc.117",
|
|
26
|
+
"magic-string": "^1.4.2",
|
|
26
27
|
"ws": "^8.21.3"
|
|
27
28
|
},
|
|
28
29
|
"devDependencies": {
|
|
29
|
-
"@types/node": "
|
|
30
|
+
"@types/node": "26.4.0",
|
|
30
31
|
"@types/ws": "^8.18.1",
|
|
31
32
|
"@vitejs/plugin-basic-ssl": "^2.3.0",
|
|
32
|
-
"effect": "4.0.0-rc.
|
|
33
|
-
"happy-dom": "^20.
|
|
33
|
+
"effect": "4.0.0-rc.117",
|
|
34
|
+
"happy-dom": "^20.14.5",
|
|
34
35
|
"rimraf": "^6.1.3",
|
|
35
36
|
"typescript": "^7.0.2",
|
|
36
|
-
"vite": "^8.
|
|
37
|
+
"vite": "^8.3.1",
|
|
37
38
|
"vite-host": "npm:vite@8.2.1",
|
|
38
39
|
"vitest": "^4.1.11",
|
|
39
|
-
"foldkit": "0.163.0-canary.
|
|
40
|
+
"foldkit": "0.163.0-canary.ff73466cfc55"
|
|
40
41
|
},
|
|
41
42
|
"keywords": [
|
|
42
43
|
"vite",
|