@foldkit/vite-plugin 0.26.1-canary.085b787da0a5 → 0.26.1-canary.6bd9ee8e728c
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 +4 -33
- package/dist/build.d.ts +15 -6
- package/dist/build.d.ts.map +1 -1
- package/dist/build.js +49 -146
- package/dist/foldkitPackages.d.ts +1 -1
- package/dist/foldkitPackages.d.ts.map +1 -1
- package/dist/foldkitPackages.js +2 -2
- package/dist/index.d.ts +3 -11
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +38 -43
- package/dist/ssr.d.ts +0 -6
- package/dist/ssr.d.ts.map +1 -1
- package/dist/ssr.js +9 -45
- package/package.json +3 -3
package/README.md
CHANGED
|
@@ -65,53 +65,24 @@ With this set, the dev server converts HTML page requests to Web `Request` value
|
|
|
65
65
|
|
|
66
66
|
Vite retains ownership of configured proxy routes before Foldkit handles application requests. Vite's `server.cors` option applies to Vite-owned source modules, assets, and HMR. It does not add headers to application responses or answer their preflights. Preflight ownership follows `Access-Control-Request-Method`, so a preflight for an application `POST` reaches `renderPage` even when its path looks like an asset. An `OPTIONS` request without both `Origin` and `Access-Control-Request-Method` is not a preflight and also reaches `renderPage`. Define application CORS in `renderPage`, where development and the deployed host share one policy. Vite's `allowedHosts` check runs before proxy and application handling, including `OPTIONS` and methods the Web `Request` API cannot represent.
|
|
67
67
|
|
|
68
|
-
## Server-rendered documents
|
|
69
|
-
|
|
70
|
-
With `ssr.build`, the client input is a script and the server entry produces the complete HTML document. Configure the script beside the server entry:
|
|
71
|
-
|
|
72
|
-
```typescript
|
|
73
|
-
foldkit({
|
|
74
|
-
ssr: {
|
|
75
|
-
clientEntry: '/src/entry.ts',
|
|
76
|
-
serverEntry: '/src/entry.server.ts',
|
|
77
|
-
build: true,
|
|
78
|
-
},
|
|
79
|
-
})
|
|
80
|
-
```
|
|
81
|
-
|
|
82
|
-
Import CSS from the client script. Export `renderDocument` alongside `renderPage` in the server entry:
|
|
83
|
-
|
|
84
|
-
```typescript
|
|
85
|
-
export const renderDocument = Server.renderDocument
|
|
86
|
-
```
|
|
87
|
-
|
|
88
|
-
`Server.renderDocument(application, assets, options)` assembles the document with the application's title, language, direction, canonical URL, and Open Graph URL. `assets` contains the emitted `entryScript`, ordered `stylesheets`, and `modulePreloads`. The plugin collects static imports and their CSS, leaves lazy imports to Vite's runtime, and supplies the same assets to request-time rendering and prerendering. The default document includes UTF-8 and viewport metadata. Wrap the helper to set a default `lang` or add trusted author-owned `head` markup, such as a favicon. Never interpolate unescaped request data into `head`.
|
|
89
|
-
|
|
90
|
-
The client build starts from a script rather than HTML. `index.html` appears in the client output only when prerendering generates `/`. The build refuses an existing root document copied from `publicDir`, emitted by another plugin, or left by an earlier build when `emptyOutDir` is disabled. Remove the source `index.html` when migrating an SSR build, move its stylesheet links into client imports, and move document tags into `renderDocument`. Build-time `transformIndexHtml` hooks do not run with a script input. In development, Vite transforms the rendered document for HMR and dev HTML hooks. `containerId` belongs only to a template-based custom host and cannot be combined with `clientEntry`.
|
|
91
|
-
|
|
92
|
-
Use a root-relative `clientEntry`, such as `/src/entry.ts`, and an absolute-path or full-URL Vite `base`. Relative bases (`''` and `'./'`) are rejected because their asset URLs would resolve differently on nested routes. `modulePreload` configuration and absolute `experimental.renderBuiltUrl` results apply to the generated document; relative and runtime `renderBuiltUrl` results are rejected.
|
|
93
|
-
|
|
94
|
-
The standalone build plugin takes `foldkitBuild(serverEntry, { clientEntry, ...options })`. Omitting `clientEntry` and `ssr.build` selects the template-based development host for a custom build pipeline. Hosts that own an HTML template can use the lower-level `injectIntoTemplate`, `toResponse`, and `handleRequest(request, { template, renderPage })` APIs.
|
|
95
|
-
|
|
96
68
|
## Foldkit package resolution
|
|
97
69
|
|
|
98
70
|
Each module graph must load one Foldkit copy. The plugin configures Vite for that:
|
|
99
71
|
|
|
100
72
|
- `resolve.dedupe` lists `foldkit`, `@foldkit/ui`, and `@foldkit/devtools`, each only when it resolves from the application root.
|
|
101
|
-
-
|
|
73
|
+
- Server builds and the dev server's server render bundle those three packages, plus every installed package whose `dependencies` or `peerDependencies` include `foldkit` or an `@foldkit/*` package, such as `@foldkit/markdown`. In the dev server, these `ssr.noExternal` packages run through Vite's module runner instead of Node's own import. An explicit `ssr.external` entry still keeps a package external.
|
|
102
74
|
- The plugin finds these packages by crawling from the application's `package.json`. It follows the application's `dependencies` and `devDependencies`, then the `dependencies` of each package it bundles, plus the `devDependencies` of a bundled package that is a private workspace package.
|
|
103
|
-
- The plugin excludes `foldkit` from dependency pre-bundling in every environment, so the build id transform runs on it. It also pre-bundles the Effect entries Foldkit imports in every environment whose optimizer is enabled, for example a Cloudflare Worker environment, so Foldkit and the application share one Effect instance. Applications need no `optimizeDeps` settings for Foldkit.
|
|
104
75
|
|
|
105
|
-
A package the crawl does not reach stays external. For example: a peer the application does not declare, or a package reached only through a package that does not depend on Foldkit. Such a package loads a second Foldkit copy from `node_modules` at runtime. Declare it in the application's `package.json`, or add it to `
|
|
76
|
+
A package the crawl does not reach stays external. For example: a peer the application does not declare, or a package reached only through a package that does not depend on Foldkit. Such a package loads a second Foldkit copy from `node_modules` at runtime. Declare it in the application's `package.json`, or add it to `ssr.noExternal`:
|
|
106
77
|
|
|
107
78
|
```typescript
|
|
108
79
|
export default defineConfig({
|
|
109
80
|
plugins: [foldkit()],
|
|
110
|
-
|
|
81
|
+
ssr: { noExternal: ['foldkit-component-library'] },
|
|
111
82
|
})
|
|
112
83
|
```
|
|
113
84
|
|
|
114
|
-
Vitest copies
|
|
85
|
+
Vitest copies SSR `noExternal` into `server.deps.inline`. A Vitest config that includes `foldkit()` therefore also inlines the crawled packages in tests.
|
|
115
86
|
|
|
116
87
|
## Completed build metadata
|
|
117
88
|
|
package/dist/build.d.ts
CHANGED
|
@@ -15,15 +15,25 @@ export type FoldkitPrerenderOptions = Readonly<{
|
|
|
15
15
|
* from `Request.url`, in which case it should match the published origin.
|
|
16
16
|
*/
|
|
17
17
|
origin?: string;
|
|
18
|
+
/**
|
|
19
|
+
* The `id` of the placeholder element in `index.html` the rendered markup
|
|
20
|
+
* replaces. Defaults to `'root'`, and the aggregate plugin passes whatever
|
|
21
|
+
* `ssr.containerId` names, so a renamed container is renamed once.
|
|
22
|
+
*/
|
|
23
|
+
containerId?: string;
|
|
18
24
|
}>;
|
|
19
25
|
/** How `vite build` builds a server entry and what it generates from it. */
|
|
20
26
|
export type FoldkitBuildOptions = Readonly<{
|
|
21
|
-
/** Root-relative browser script entry resolved by Vite, such as `'/src/entry.ts'`. Import stylesheets from this module. */
|
|
22
|
-
clientEntry: string;
|
|
23
27
|
/** Where the browser build is written. */
|
|
24
28
|
clientOutDir?: string;
|
|
25
29
|
/** Where the server build is written. */
|
|
26
30
|
serverOutDir?: string;
|
|
31
|
+
/**
|
|
32
|
+
* The `id` of the empty container in `index.html` the fetch handler
|
|
33
|
+
* replaces. Defaults to `'root'`. The aggregate plugin copies
|
|
34
|
+
* `ssr.containerId` here.
|
|
35
|
+
*/
|
|
36
|
+
containerId?: string;
|
|
27
37
|
/**
|
|
28
38
|
* Generate static HTML for a set of URLs after both builds. `true` takes the
|
|
29
39
|
* paths from the entry's `prerenderPaths` export.
|
|
@@ -124,9 +134,8 @@ export declare const renderTargetFor: (clientDirectory: string, path: string, or
|
|
|
124
134
|
*
|
|
125
135
|
* Vite builds both environments, so a deployment target that runs `vite build`
|
|
126
136
|
* gets the browser and server bundles. The `fetch` handler and generated pages
|
|
127
|
-
* use the
|
|
128
|
-
*
|
|
129
|
-
* The server bundle's default export is `{ fetch }`.
|
|
137
|
+
* use the HTML emitted by the browser build, but the unrendered template is not
|
|
138
|
+
* published with the assets. The server bundle's default export is `{ fetch }`.
|
|
130
139
|
*/
|
|
131
|
-
export declare const foldkitBuild: (serverEntry: string, options
|
|
140
|
+
export declare const foldkitBuild: (serverEntry: string, options?: FoldkitBuildOptions) => Plugin<FoldkitBuildApi>;
|
|
132
141
|
//# sourceMappingURL=build.d.ts.map
|
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,
|
|
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,7 +1,6 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
4
|
-
import { mkdir, readFile, writeFile } from 'node:fs/promises';
|
|
1
|
+
import { Schema } from 'effect';
|
|
2
|
+
import { randomUUID } from 'node:crypto';
|
|
3
|
+
import { mkdir, writeFile } from 'node:fs/promises';
|
|
5
4
|
import nodePath, { dirname, resolve } from 'node:path';
|
|
6
5
|
import { pathToFileURL } from 'node:url';
|
|
7
6
|
/** Vite module id of the fetch handler Foldkit emits as the server entry. */
|
|
@@ -65,15 +64,12 @@ export const manifestPath = (root, directory, pathApi = nodePath) => {
|
|
|
65
64
|
return related.split(pathApi.sep).join('/');
|
|
66
65
|
};
|
|
67
66
|
const MANIFEST_FILE_NAME = 'foldkit.build.json';
|
|
67
|
+
const TEMPLATE_FILE_NAME = 'index.html';
|
|
68
68
|
const DEFAULT_CLIENT_OUT_DIR = 'dist/client';
|
|
69
69
|
const DEFAULT_SERVER_OUT_DIR = 'dist/server';
|
|
70
70
|
const DEFAULT_PRERENDER_ORIGIN = 'http://localhost';
|
|
71
71
|
const FETCH_CHUNK_NAME = 'fetch';
|
|
72
72
|
const RESOLVED_FETCH_MODULE_ID = `\0${FOLDKIT_FETCH_MODULE_ID}`;
|
|
73
|
-
const CLIENT_MODULE_ID = 'virtual:foldkit/client';
|
|
74
|
-
const RESOLVED_CLIENT_MODULE_ID = `\0${CLIENT_MODULE_ID}`;
|
|
75
|
-
const CLIENT_CHUNK_NAME = 'app';
|
|
76
|
-
const VITE_CONSOLIDATED_CSS_ORIGINAL_NAME = 'style.css';
|
|
77
73
|
// The chunk built from the configured entry, by name rather than by position.
|
|
78
74
|
//
|
|
79
75
|
// An SSR environment can carry more than one input, and prerendering imports
|
|
@@ -172,104 +168,39 @@ const prerenderOptionsFrom = (prerender) => {
|
|
|
172
168
|
}
|
|
173
169
|
return prerender === true ? {} : prerender;
|
|
174
170
|
};
|
|
175
|
-
const
|
|
176
|
-
const
|
|
177
|
-
const entryFile = serverEntryFile(outputs, CLIENT_CHUNK_NAME);
|
|
178
|
-
const chunks = new Map(outputs.flatMap(output => output.type === 'chunk' ? [[output.fileName, output]] : []));
|
|
179
|
-
const visited = new Set();
|
|
180
|
-
const stylesheets = new Set();
|
|
181
|
-
const modulePreloads = new Set();
|
|
182
|
-
const visit = (fileName) => {
|
|
183
|
-
if (visited.has(fileName)) {
|
|
184
|
-
return;
|
|
185
|
-
}
|
|
186
|
-
visited.add(fileName);
|
|
187
|
-
const chunk = chunks.get(fileName);
|
|
188
|
-
if (chunk === undefined) {
|
|
189
|
-
return;
|
|
190
|
-
}
|
|
191
|
-
for (const imported of chunk.imports) {
|
|
192
|
-
visit(imported);
|
|
193
|
-
}
|
|
194
|
-
for (const stylesheet of chunk.viteMetadata?.importedCss ?? []) {
|
|
195
|
-
stylesheets.add(stylesheet);
|
|
196
|
-
}
|
|
197
|
-
if (fileName !== entryFile) {
|
|
198
|
-
modulePreloads.add(fileName);
|
|
199
|
-
}
|
|
200
|
-
};
|
|
201
|
-
visit(entryFile);
|
|
202
|
-
if (!config.build.cssCodeSplit) {
|
|
203
|
-
for (const output of outputs) {
|
|
204
|
-
if (output.type === 'asset' &&
|
|
205
|
-
output.originalFileNames.includes(VITE_CONSOLIDATED_CSS_ORIGINAL_NAME)) {
|
|
206
|
-
stylesheets.add(output.fileName);
|
|
207
|
-
}
|
|
208
|
-
}
|
|
209
|
-
}
|
|
210
|
-
const { modulePreload } = config.build;
|
|
211
|
-
const preloadFiles = modulePreload === false
|
|
212
|
-
? []
|
|
213
|
-
: (modulePreload.resolveDependencies?.(entryFile, [...modulePreloads], {
|
|
214
|
-
hostId: 'index.html',
|
|
215
|
-
hostType: 'html',
|
|
216
|
-
}) ?? [...modulePreloads]);
|
|
217
|
-
const assetUrl = (fileName) => {
|
|
218
|
-
if (/^([a-z]+:)?\/\//.test(fileName)) {
|
|
219
|
-
return fileName;
|
|
220
|
-
}
|
|
221
|
-
const builtUrl = config.experimental.renderBuiltUrl?.(fileName, {
|
|
222
|
-
hostId: 'index.html',
|
|
223
|
-
hostType: 'html',
|
|
224
|
-
type: 'asset',
|
|
225
|
-
ssr: false,
|
|
226
|
-
});
|
|
227
|
-
if (Predicate.isString(builtUrl)) {
|
|
228
|
-
if (!builtUrl.startsWith('/') && !/^https?:\/\//.test(builtUrl)) {
|
|
229
|
-
throw new Error('[foldkit] renderBuiltUrl must return an absolute URL or root-relative URL for document assets.');
|
|
230
|
-
}
|
|
231
|
-
return builtUrl;
|
|
232
|
-
}
|
|
233
|
-
if (builtUrl?.runtime !== undefined || builtUrl?.relative === true) {
|
|
234
|
-
throw new Error('[foldkit] document assets cannot use runtime or relative renderBuiltUrl results.');
|
|
235
|
-
}
|
|
236
|
-
return `${config.base}${encodeURI(fileName).replace(/[?#]/g, encodeURIComponent)}`;
|
|
237
|
-
};
|
|
238
|
-
return Object.freeze({
|
|
239
|
-
entryScript: assetUrl(entryFile),
|
|
240
|
-
stylesheets: Object.freeze([...stylesheets].map(assetUrl)),
|
|
241
|
-
modulePreloads: Object.freeze(preloadFiles.map(assetUrl)),
|
|
242
|
-
});
|
|
243
|
-
};
|
|
244
|
-
const fetchModuleSource = (serverEntry, assets) => {
|
|
171
|
+
const fetchModuleSource = (serverEntry, template, containerId) => {
|
|
172
|
+
const containerLiteral = containerId === undefined ? 'undefined' : JSON.stringify(containerId);
|
|
245
173
|
// NOTE: `export *` re-exports whatever the application entry actually names,
|
|
246
174
|
// so a missing `prerenderPaths` is absent rather than a Vite undefined-import
|
|
247
175
|
// warning.
|
|
248
176
|
return `${[
|
|
249
177
|
`import { handleRequest } from 'foldkit/experimental/server'`,
|
|
250
|
-
`import { renderDocument } from ${JSON.stringify(serverEntry)}`,
|
|
251
178
|
`import * as server from ${JSON.stringify(serverEntry)}`,
|
|
252
179
|
`export * from ${JSON.stringify(serverEntry)}`,
|
|
253
|
-
`const
|
|
254
|
-
`
|
|
255
|
-
`Object.freeze(assets.modulePreloads)`,
|
|
256
|
-
`Object.freeze(assets)`,
|
|
180
|
+
`const template = ${JSON.stringify(template)}`,
|
|
181
|
+
`const containerId = ${containerLiteral}`,
|
|
257
182
|
`export default {`,
|
|
258
183
|
` fetch(request) {`,
|
|
259
184
|
` return handleRequest(request, {`,
|
|
260
185
|
` renderPage: server.renderPage,`,
|
|
261
|
-
`
|
|
186
|
+
` template,`,
|
|
187
|
+
` containerId,`,
|
|
262
188
|
` })`,
|
|
263
189
|
` },`,
|
|
264
190
|
`}`,
|
|
265
191
|
``,
|
|
266
192
|
].join('\n')}`;
|
|
267
193
|
};
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
194
|
+
// The template is what the browser build emitted in this same `vite build`,
|
|
195
|
+
// never a file on disk: `dist/client/index.html` could only be the previous
|
|
196
|
+
// build's shell with its old asset hashes, and the source `index.html` still
|
|
197
|
+
// names `/src/entry.ts`. Either would bundle into a handler that serves a
|
|
198
|
+
// page which cannot hydrate, from a build that reported success.
|
|
199
|
+
const templateForFetchModule = (capturedTemplate) => {
|
|
200
|
+
if (capturedTemplate === undefined) {
|
|
201
|
+
throw new Error(`[foldkit] the browser build has not emitted ${TEMPLATE_FILE_NAME}, so the fetch handler has no template to render into. Build the "client" environment before "ssr", and give the client an HTML entry.`);
|
|
271
202
|
}
|
|
272
|
-
return
|
|
203
|
+
return capturedTemplate;
|
|
273
204
|
};
|
|
274
205
|
/**
|
|
275
206
|
* Builds a Web `fetch` handler alongside the browser build, and generates
|
|
@@ -277,21 +208,21 @@ const assetsForFetchModule = (assets) => {
|
|
|
277
208
|
*
|
|
278
209
|
* Vite builds both environments, so a deployment target that runs `vite build`
|
|
279
210
|
* gets the browser and server bundles. The `fetch` handler and generated pages
|
|
280
|
-
* use the
|
|
281
|
-
*
|
|
282
|
-
* The server bundle's default export is `{ fetch }`.
|
|
211
|
+
* use the HTML emitted by the browser build, but the unrendered template is not
|
|
212
|
+
* published with the assets. The server bundle's default export is `{ fetch }`.
|
|
283
213
|
*/
|
|
284
|
-
export const foldkitBuild = (serverEntry, options) => {
|
|
214
|
+
export const foldkitBuild = (serverEntry, options = {}) => {
|
|
285
215
|
const state = {};
|
|
286
216
|
let metadata;
|
|
287
217
|
const clientOutDir = options.clientOutDir ?? DEFAULT_CLIENT_OUT_DIR;
|
|
288
218
|
const serverOutDir = options.serverOutDir ?? DEFAULT_SERVER_OUT_DIR;
|
|
289
219
|
const prerender = prerenderOptionsFrom(options.prerender ?? false);
|
|
220
|
+
const containerId = prerender?.containerId ?? options.containerId;
|
|
290
221
|
// Prerendering imports the server bundle and runs it in the build process,
|
|
291
222
|
// with the build's own privileges. That module is the application's own code
|
|
292
223
|
// and its dependencies, built from the configured entry, and is trusted on
|
|
293
224
|
// exactly those terms. Nothing here is imported when prerendering is off.
|
|
294
|
-
const generatePages = async (builder, clientDirectory, serverDirectory, entryFileName) => {
|
|
225
|
+
const generatePages = async (builder, template, clientDirectory, serverDirectory, entryFileName) => {
|
|
295
226
|
if (prerender === undefined) {
|
|
296
227
|
return [];
|
|
297
228
|
}
|
|
@@ -311,19 +242,15 @@ export const foldkitBuild = (serverEntry, options) => {
|
|
|
311
242
|
if (paths === undefined) {
|
|
312
243
|
throw new Error(`[foldkit] cannot generate pages: "${entry}" exports no prerenderPaths and the build configured no paths.`);
|
|
313
244
|
}
|
|
314
|
-
|
|
315
|
-
throw new Error(`[foldkit] "${entryFileName}" exports no renderDocument function.`);
|
|
316
|
-
}
|
|
317
|
-
const assets = assetsForFetchModule(state.assets);
|
|
245
|
+
const { injectIntoTemplate } = await import('foldkit/experimental/server');
|
|
318
246
|
for (const path of paths) {
|
|
319
247
|
const { url, file } = renderTargetFor(clientDirectory, path, origin);
|
|
320
248
|
const result = await entry.renderPage(new Request(url));
|
|
321
|
-
const html =
|
|
249
|
+
const html = injectIntoTemplate(template(), renderedApplication(path, result), prerender.containerId === undefined
|
|
250
|
+
? undefined
|
|
251
|
+
: { containerId: prerender.containerId });
|
|
322
252
|
await mkdir(dirname(file), { recursive: true });
|
|
323
253
|
await writeFile(file, html);
|
|
324
|
-
if (path === '/') {
|
|
325
|
-
state.prerenderedRootHash = documentHash(html);
|
|
326
|
-
}
|
|
327
254
|
builder.config.logger.info(` generated ${path}`);
|
|
328
255
|
}
|
|
329
256
|
return paths;
|
|
@@ -337,16 +264,15 @@ export const foldkitBuild = (serverEntry, options) => {
|
|
|
337
264
|
if (state.serverEntryFile === undefined) {
|
|
338
265
|
throw new Error('[foldkit] the server environment produced no entry chunk, so there is nothing to deploy or generate from.');
|
|
339
266
|
}
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
}
|
|
267
|
+
// Read only when a page is actually generated: a build that generates
|
|
268
|
+
// nothing has no use for an HTML entry and must not require one.
|
|
269
|
+
const template = () => {
|
|
270
|
+
if (state.template === undefined) {
|
|
271
|
+
throw new Error(`[foldkit] the browser build emitted no ${TEMPLATE_FILE_NAME} to generate pages from. Prerendering needs an HTML entry.`);
|
|
272
|
+
}
|
|
273
|
+
return state.template;
|
|
274
|
+
};
|
|
275
|
+
const prerendered = await generatePages(builder, template, clientDirectory, serverDirectory, state.serverEntryFile);
|
|
350
276
|
const manifest = FoldkitBuildManifest.make({
|
|
351
277
|
schemaVersion: MANIFEST_SCHEMA_VERSION,
|
|
352
278
|
client: manifestPath(builder.config.root, clientDirectory),
|
|
@@ -385,17 +311,9 @@ export const foldkitBuild = (serverEntry, options) => {
|
|
|
385
311
|
order: 'pre',
|
|
386
312
|
handler() {
|
|
387
313
|
if (this.environment.name === 'client') {
|
|
388
|
-
delete state.
|
|
314
|
+
delete state.template;
|
|
389
315
|
delete state.serverEntryFile;
|
|
390
316
|
metadata = undefined;
|
|
391
|
-
const { input } = this.environment.config.build.rolldownOptions;
|
|
392
|
-
const inputs = Predicate.isString(input)
|
|
393
|
-
? [input]
|
|
394
|
-
: Object.values(input ?? {});
|
|
395
|
-
if (!inputs.includes(CLIENT_MODULE_ID) ||
|
|
396
|
-
inputs.some(entry => /\.html(?:[?#]|$)/.test(entry))) {
|
|
397
|
-
throw new Error('[foldkit] ssr.build owns the browser script input. Configure ssr.clientEntry instead of an HTML input or a replacement client input.');
|
|
398
|
-
}
|
|
399
317
|
}
|
|
400
318
|
else if (this.environment.name === 'ssr') {
|
|
401
319
|
delete state.serverEntryFile;
|
|
@@ -404,27 +322,20 @@ export const foldkitBuild = (serverEntry, options) => {
|
|
|
404
322
|
},
|
|
405
323
|
},
|
|
406
324
|
resolveId(id) {
|
|
407
|
-
if (id === CLIENT_MODULE_ID) {
|
|
408
|
-
return RESOLVED_CLIENT_MODULE_ID;
|
|
409
|
-
}
|
|
410
325
|
if (id === FOLDKIT_FETCH_MODULE_ID) {
|
|
411
326
|
return RESOLVED_FETCH_MODULE_ID;
|
|
412
327
|
}
|
|
413
328
|
return undefined;
|
|
414
329
|
},
|
|
415
330
|
load(id) {
|
|
416
|
-
if (id === RESOLVED_CLIENT_MODULE_ID) {
|
|
417
|
-
const { modulePreload } = this.environment.config.build;
|
|
418
|
-
const polyfill = modulePreload !== false && modulePreload.polyfill
|
|
419
|
-
? "import 'vite/modulepreload-polyfill'\n"
|
|
420
|
-
: '';
|
|
421
|
-
return `${polyfill}import ${JSON.stringify(options.clientEntry)}\n`;
|
|
422
|
-
}
|
|
423
331
|
if (id !== RESOLVED_FETCH_MODULE_ID) {
|
|
424
332
|
return;
|
|
425
333
|
}
|
|
426
|
-
|
|
334
|
+
const template = templateForFetchModule(state.template);
|
|
335
|
+
return fetchModuleSource(serverEntry, template, containerId);
|
|
427
336
|
},
|
|
337
|
+
// NOTE: `order: 'post'` because Vite's own HTML plugin emits `index.html`
|
|
338
|
+
// from a `generateBundle` of its own; post is guaranteed to run after it.
|
|
428
339
|
generateBundle: {
|
|
429
340
|
order: 'post',
|
|
430
341
|
handler(_options, bundle) {
|
|
@@ -435,15 +346,17 @@ export const foldkitBuild = (serverEntry, options) => {
|
|
|
435
346
|
if (this.environment.name !== 'client') {
|
|
436
347
|
return;
|
|
437
348
|
}
|
|
438
|
-
|
|
349
|
+
const html = bundle[TEMPLATE_FILE_NAME];
|
|
350
|
+
if (html === undefined || html.type !== 'asset') {
|
|
351
|
+
return;
|
|
352
|
+
}
|
|
353
|
+
state.template = String(html.source);
|
|
354
|
+
delete bundle[TEMPLATE_FILE_NAME];
|
|
439
355
|
},
|
|
440
356
|
},
|
|
441
357
|
config: userConfig => {
|
|
442
358
|
const client = {
|
|
443
|
-
build: {
|
|
444
|
-
outDir: clientOutDir,
|
|
445
|
-
rolldownOptions: { input: { [CLIENT_CHUNK_NAME]: CLIENT_MODULE_ID } },
|
|
446
|
-
},
|
|
359
|
+
build: { outDir: clientOutDir },
|
|
447
360
|
};
|
|
448
361
|
const ssr = {
|
|
449
362
|
build: {
|
|
@@ -474,16 +387,6 @@ export const foldkitBuild = (serverEntry, options) => {
|
|
|
474
387
|
}
|
|
475
388
|
: { environments: { client, ssr } };
|
|
476
389
|
},
|
|
477
|
-
configResolved(config) {
|
|
478
|
-
if (config.base === '' || config.base === './') {
|
|
479
|
-
throw new Error('[foldkit] ssr.build requires an absolute URL or root-relative base, such as "/" or "/app/". A relative base cannot locate browser assets consistently across server-rendered routes.');
|
|
480
|
-
}
|
|
481
|
-
if (!options.clientEntry?.startsWith('/') ||
|
|
482
|
-
options.clientEntry.startsWith('//') ||
|
|
483
|
-
/\.html(?:[?#]|$)/.test(options.clientEntry)) {
|
|
484
|
-
throw new Error("[foldkit] ssr.build requires clientEntry to name a browser script. Move stylesheet imports into that script and document markup into the server entry's renderDocument export.");
|
|
485
|
-
}
|
|
486
|
-
},
|
|
487
390
|
// The composable finalization point. `order: 'post'` runs this after the
|
|
488
391
|
// config-level orchestrator — the host's, or the default above — no matter
|
|
489
392
|
// where this plugin sits in the plugin list, which a wrapped
|
|
@@ -12,6 +12,6 @@ export declare const isFoldkitSingletonPackageSpecifier: (specifier: string) =>
|
|
|
12
12
|
*/
|
|
13
13
|
export declare const crawlFoldkitPackages: (root: string, isBuild: boolean, viteUserConfig: UserConfig) => Promise<Readonly<{
|
|
14
14
|
dedupe: Array<string>;
|
|
15
|
-
|
|
15
|
+
ssrNoExternal: Array<string>;
|
|
16
16
|
}>>;
|
|
17
17
|
//# sourceMappingURL=foldkitPackages.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"foldkitPackages.d.ts","sourceRoot":"","sources":["../src/foldkitPackages.ts"],"names":[],"mappings":"AAGA,OAAO,EAAE,KAAK,UAAU,EAA0B,MAAM,MAAM,CAAA;AAS9D;;;;GAIG;AACH,eAAO,MAAM,kCAAkC,cAClC,MAAM,KAChB,OAKA,CAAA;AA8CH;;;;GAIG;AACH,eAAO,MAAM,oBAAoB,SACzB,MAAM,WACH,OAAO,kBACA,UAAU,KACzB,OAAO,
|
|
1
|
+
{"version":3,"file":"foldkitPackages.d.ts","sourceRoot":"","sources":["../src/foldkitPackages.ts"],"names":[],"mappings":"AAGA,OAAO,EAAE,KAAK,UAAU,EAA0B,MAAM,MAAM,CAAA;AAS9D;;;;GAIG;AACH,eAAO,MAAM,kCAAkC,cAClC,MAAM,KAChB,OAKA,CAAA;AA8CH;;;;GAIG;AACH,eAAO,MAAM,oBAAoB,SACzB,MAAM,WACH,OAAO,kBACA,UAAU,KACzB,OAAO,CACR,QAAQ,CAAC;IAAE,MAAM,EAAE,KAAK,CAAC,MAAM,CAAC,CAAC;IAAC,aAAa,EAAE,KAAK,CAAC,MAAM,CAAC,CAAA;CAAE,CAAC,CAqClE,CAAA"}
|
package/dist/foldkitPackages.js
CHANGED
|
@@ -58,11 +58,11 @@ export const crawlFoldkitPackages = async (root, isBuild, viteUserConfig) => {
|
|
|
58
58
|
viteUserConfig,
|
|
59
59
|
isSemiFrameworkPkgByJson: dependsOnFoldkit,
|
|
60
60
|
});
|
|
61
|
-
const
|
|
61
|
+
const ssrNoExternal = Array.dedupe([
|
|
62
62
|
...FOLDKIT_SINGLETON_PACKAGES,
|
|
63
63
|
...crawl.ssr.noExternal,
|
|
64
64
|
]);
|
|
65
65
|
const maybeResolvableSingletons = await Promise.all(Array.map(FOLDKIT_SINGLETON_PACKAGES, async (packageName) => pipe(await findDepPkgJsonPath(packageName, crawlRoot), Option.fromUndefinedOr, Option.as(packageName))));
|
|
66
66
|
const dedupe = Array.getSomes(maybeResolvableSingletons);
|
|
67
|
-
return { dedupe,
|
|
67
|
+
return { dedupe, ssrNoExternal };
|
|
68
68
|
};
|
package/dist/index.d.ts
CHANGED
|
@@ -25,10 +25,7 @@ export type FoldkitPluginOptions = Readonly<{
|
|
|
25
25
|
* `ssr.serverEntry`. When `undefined` (the default), the dev server
|
|
26
26
|
* serves the client entry only.
|
|
27
27
|
*/
|
|
28
|
-
ssr?: Omit<FoldkitSsrOptions, 'buildId' | 'quietStandDown'
|
|
29
|
-
/** Root-relative browser script for the server entry's code-rendered document. */
|
|
30
|
-
clientEntry: string;
|
|
31
|
-
containerId?: never;
|
|
28
|
+
ssr?: Omit<FoldkitSsrOptions, 'buildId' | 'quietStandDown'> & Readonly<{
|
|
32
29
|
/**
|
|
33
30
|
* Build a Web `fetch` handler alongside the browser build, and generate
|
|
34
31
|
* static HTML from the server entry, inside this project's own
|
|
@@ -38,13 +35,8 @@ export type FoldkitPluginOptions = Readonly<{
|
|
|
38
35
|
*
|
|
39
36
|
* When this is absent, `vite build` builds the browser bundle only.
|
|
40
37
|
*/
|
|
41
|
-
build?: boolean |
|
|
42
|
-
}
|
|
43
|
-
/** Use a custom template-based development and build pipeline. */
|
|
44
|
-
clientEntry?: never;
|
|
45
|
-
containerId?: string;
|
|
46
|
-
build?: false;
|
|
47
|
-
}>);
|
|
38
|
+
build?: boolean | FoldkitBuildOptions;
|
|
39
|
+
}>;
|
|
48
40
|
/**
|
|
49
41
|
* An explicit identity for the deployment this build belongs to. Foldkit
|
|
50
42
|
* normally generates an opaque identity when one Vite app build coordinates
|
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AA6CA,OAAO,KAAK,
|
|
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,CAyHxE,CAAA"}
|
package/dist/index.js
CHANGED
|
@@ -19,12 +19,12 @@ export { FOLDKIT_FETCH_MODULE_ID, FoldkitBuildManifest, FoldkitBuildMetadata, fo
|
|
|
19
19
|
export { foldkitSsr } from './ssr.js';
|
|
20
20
|
export { foldkitViewIdentity, transformViewIdentity, } from './viewIdentity.js';
|
|
21
21
|
// NOTE: Vite does not scan imports through `foldkit` because the plugin
|
|
22
|
-
// excludes the package from optimization
|
|
23
|
-
//
|
|
24
|
-
//
|
|
25
|
-
//
|
|
26
|
-
//
|
|
27
|
-
//
|
|
22
|
+
// excludes the package from optimization. A consumer can import only Effect
|
|
23
|
+
// subpaths while Foldkit's compiled distribution imports the bare barrel, so
|
|
24
|
+
// include both the barrel and every top-level namespace Foldkit imports. This
|
|
25
|
+
// keeps them in one optimized dependency graph. Over-inclusion is harmless;
|
|
26
|
+
// under-inclusion is the bug. `scripts/check-effect-prebundle.ts` keeps the
|
|
27
|
+
// entries in sync with Foldkit's source and runs in `pnpm check`.
|
|
28
28
|
const FORCE_INCLUDED_EFFECT_ENTRIES = [
|
|
29
29
|
'effect',
|
|
30
30
|
'effect/Array',
|
|
@@ -70,16 +70,6 @@ const FORCE_INCLUDED_EFFECT_ENTRIES = [
|
|
|
70
70
|
'effect/SubscriptionRef',
|
|
71
71
|
'effect/Types',
|
|
72
72
|
];
|
|
73
|
-
// NOTE: Adding includes to an environment whose optimizer is otherwise disabled
|
|
74
|
-
// turns on Vite's explicit optimizer, which would pre-bundle Effect in Vite's
|
|
75
|
-
// default Node `ssr` environment.
|
|
76
|
-
const shouldForceEffectEntries = (name, config) => {
|
|
77
|
-
const consumer = config.consumer ?? (name === 'client' ? 'client' : 'server');
|
|
78
|
-
const isClientEnvironment = consumer === 'client';
|
|
79
|
-
const isDiscoveryEnabled = config.optimizeDeps?.noDiscovery === false;
|
|
80
|
-
const isExplicitOptimizationEnabled = Array.isArrayNonEmpty(config.optimizeDeps?.include ?? []);
|
|
81
|
-
return (isClientEnvironment || isDiscoveryEnabled || isExplicitOptimizationEnabled);
|
|
82
|
-
};
|
|
83
73
|
const Event = Data.taggedEnum();
|
|
84
74
|
const makeState = Effect.gen(function* () {
|
|
85
75
|
const preservedModels = yield* Ref.make(HashMap.empty());
|
|
@@ -484,6 +474,27 @@ const main = (server, events, options) => Effect.gen(function* () {
|
|
|
484
474
|
* an array; Vite flattens nested plugin arrays, so `plugins: [foldkit()]`
|
|
485
475
|
* keeps working.
|
|
486
476
|
*/
|
|
477
|
+
// The container is named once, on `ssr`, and reaches both the dev host and the
|
|
478
|
+
// build from there. A `build.prerender` that names its own wins, so a project
|
|
479
|
+
// that needs them to differ still can.
|
|
480
|
+
const withContainerId = (build, containerId) => {
|
|
481
|
+
const options = build === true ? {} : build;
|
|
482
|
+
if (containerId === undefined) {
|
|
483
|
+
return options;
|
|
484
|
+
}
|
|
485
|
+
const withContainer = { ...options, containerId };
|
|
486
|
+
if (options.prerender === undefined) {
|
|
487
|
+
return withContainer;
|
|
488
|
+
}
|
|
489
|
+
const prerender = options.prerender === true ? {} : options.prerender;
|
|
490
|
+
if (prerender === false) {
|
|
491
|
+
return withContainer;
|
|
492
|
+
}
|
|
493
|
+
return {
|
|
494
|
+
...withContainer,
|
|
495
|
+
prerender: { containerId, ...prerender },
|
|
496
|
+
};
|
|
497
|
+
};
|
|
487
498
|
const relayRegistryLayer = Layer.mergeAll(NodeFileSystem.layer, NodePath.layer, NodeCrypto.layer);
|
|
488
499
|
export const foldkit = (options = {}) => {
|
|
489
500
|
// NOTE: During a Vite restart, old and new servers overlap. Separate queues
|
|
@@ -502,15 +513,11 @@ export const foldkit = (options = {}) => {
|
|
|
502
513
|
const reloadPlugin = {
|
|
503
514
|
name: 'foldkit',
|
|
504
515
|
apply: 'serve',
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
handler: (name, config) => shouldForceEffectEntries(name, config)
|
|
511
|
-
? { optimizeDeps: { include: [...FORCE_INCLUDED_EFFECT_ENTRIES] } }
|
|
512
|
-
: undefined,
|
|
513
|
-
},
|
|
516
|
+
config: () => ({
|
|
517
|
+
optimizeDeps: {
|
|
518
|
+
include: [...FORCE_INCLUDED_EFFECT_ENTRIES],
|
|
519
|
+
},
|
|
520
|
+
}),
|
|
514
521
|
configureServer: server => {
|
|
515
522
|
const events = Effect.runSync(Queue.unbounded());
|
|
516
523
|
// NOTE: The default ConfigProvider snapshots the environment. Create a
|
|
@@ -540,27 +547,24 @@ export const foldkit = (options = {}) => {
|
|
|
540
547
|
config: async (userConfig, { command }) => {
|
|
541
548
|
const foldkitPackages = await crawlFoldkitPackages(userConfig.root ?? process.cwd(), command === 'build', userConfig);
|
|
542
549
|
return {
|
|
550
|
+
optimizeDeps: {
|
|
551
|
+
exclude: ['foldkit'],
|
|
552
|
+
},
|
|
543
553
|
resolve: {
|
|
544
554
|
dedupe: foldkitPackages.dedupe,
|
|
545
|
-
noExternal: foldkitPackages.noExternal,
|
|
546
555
|
},
|
|
547
556
|
ssr: {
|
|
548
|
-
noExternal: foldkitPackages.
|
|
557
|
+
noExternal: foldkitPackages.ssrNoExternal,
|
|
549
558
|
},
|
|
550
559
|
environments: {
|
|
551
560
|
ssr: {
|
|
552
561
|
resolve: {
|
|
553
|
-
noExternal: foldkitPackages.
|
|
562
|
+
noExternal: foldkitPackages.ssrNoExternal,
|
|
554
563
|
},
|
|
555
564
|
},
|
|
556
565
|
},
|
|
557
566
|
};
|
|
558
567
|
},
|
|
559
|
-
configEnvironment: () => ({
|
|
560
|
-
optimizeDeps: {
|
|
561
|
-
exclude: ['foldkit'],
|
|
562
|
-
},
|
|
563
|
-
}),
|
|
564
568
|
};
|
|
565
569
|
const shared = [
|
|
566
570
|
resolutionPlugin,
|
|
@@ -573,9 +577,6 @@ export const foldkit = (options = {}) => {
|
|
|
573
577
|
return shared;
|
|
574
578
|
}
|
|
575
579
|
const { build, ...ssr } = options.ssr;
|
|
576
|
-
if (ssr.clientEntry !== undefined && ssr.containerId !== undefined) {
|
|
577
|
-
throw new Error('[foldkit] containerId belongs to an HTML template. A clientEntry build takes its complete document from renderDocument instead.');
|
|
578
|
-
}
|
|
579
580
|
const servePages = foldkitSsr({
|
|
580
581
|
...ssr,
|
|
581
582
|
...(options.buildId === undefined ? {} : { buildId: options.buildId }),
|
|
@@ -584,15 +585,9 @@ export const foldkit = (options = {}) => {
|
|
|
584
585
|
if (build === undefined || build === false) {
|
|
585
586
|
return [...shared, servePages];
|
|
586
587
|
}
|
|
587
|
-
if (ssr.clientEntry === undefined) {
|
|
588
|
-
throw new Error('[foldkit] ssr.build requires ssr.clientEntry to name the browser script and the server entry to export renderDocument.');
|
|
589
|
-
}
|
|
590
588
|
return [
|
|
591
589
|
...shared,
|
|
592
590
|
servePages,
|
|
593
|
-
foldkitBuild(ssr.serverEntry,
|
|
594
|
-
...(build === true ? {} : build),
|
|
595
|
-
clientEntry: ssr.clientEntry,
|
|
596
|
-
}),
|
|
591
|
+
foldkitBuild(ssr.serverEntry, withContainerId(build, ssr.containerId)),
|
|
597
592
|
];
|
|
598
593
|
};
|
package/dist/ssr.d.ts
CHANGED
|
@@ -8,12 +8,6 @@ export type FoldkitSsrOptions = Readonly<{
|
|
|
8
8
|
* `Promise<EntryResult>`.
|
|
9
9
|
*/
|
|
10
10
|
serverEntry: string;
|
|
11
|
-
/**
|
|
12
|
-
* Root-relative browser script entry for a code-rendered document. The server entry must
|
|
13
|
-
* also export `renderDocument(application, assets)`. When absent, the dev
|
|
14
|
-
* host uses `index.html` for a custom template-based build pipeline.
|
|
15
|
-
*/
|
|
16
|
-
clientEntry?: string;
|
|
17
11
|
/**
|
|
18
12
|
* The `id` of the empty container element in `index.html` the rendered
|
|
19
13
|
* markup replaces. Defaults to `'root'`.
|
package/dist/ssr.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ssr.d.ts","sourceRoot":"","sources":["../src/ssr.ts"],"names":[],"mappings":"AAMA,OAAO,KAAK,EAGV,MAAM,EAGP,MAAM,MAAM,CAAA;AAIb,0EAA0E;AAC1E,MAAM,MAAM,iBAAiB,GAAG,QAAQ,CAAC;IACvC;;;;;OAKG;IACH,WAAW,EAAE,MAAM,CAAA;IACnB
|
|
1
|
+
{"version":3,"file":"ssr.d.ts","sourceRoot":"","sources":["../src/ssr.ts"],"names":[],"mappings":"AAMA,OAAO,KAAK,EAGV,MAAM,EAGP,MAAM,MAAM,CAAA;AAIb,0EAA0E;AAC1E,MAAM,MAAM,iBAAiB,GAAG,QAAQ,CAAC;IACvC;;;;;OAKG;IACH,WAAW,EAAE,MAAM,CAAA;IACnB;;;OAGG;IACH,WAAW,CAAC,EAAE,MAAM,CAAA;IACpB;;;;;;;;;;;;OAYG;IACH,MAAM,CAAC,EAAE,MAAM,CAAA;IACf;;;;;;;OAOG;IACH,OAAO,CAAC,EAAE,MAAM,CAAA;IAChB;;;;OAIG;IACH,cAAc,CAAC,EAAE,OAAO,CAAA;CACzB,CAAC,CAAA;AAosBF;;;;;;;;;;;GAWG;AACH,eAAO,MAAM,UAAU,YAAa,iBAAiB,KAAG,MAgEvD,CAAA"}
|
package/dist/ssr.js
CHANGED
|
@@ -132,32 +132,19 @@ const renderDecision = (nodeRequest, requestUrl) => {
|
|
|
132
132
|
const renderRequest = (server, options, nodeRequest, requestUrl) => Effect.gen(function* () {
|
|
133
133
|
const { pathname, search } = new URL(requestUrl);
|
|
134
134
|
const route = `${pathname}${search}`;
|
|
135
|
+
const rawTemplate = yield* Effect.promise(() => readFile(resolve(server.config.root, 'index.html'), 'utf-8'));
|
|
136
|
+
// NOTE: the first argument tells Vite where the HTML lives, and Vite
|
|
137
|
+
// resolves the template's relative URLs (such as a `./src/entry.ts`
|
|
138
|
+
// script) against it. The template always lives at the site root, so
|
|
139
|
+
// that argument must stay `/index.html` no matter which route is being
|
|
140
|
+
// rendered. The third argument, named `originalUrl` in Vite's signature,
|
|
141
|
+
// carries the route actually being requested.
|
|
142
|
+
const template = yield* Effect.promise(() => server.transformIndexHtml('/index.html', rawTemplate, route));
|
|
135
143
|
const loadedModule = yield* Effect.promise(() => server.ssrLoadModule(options.serverEntry));
|
|
136
144
|
if (!isEntryModule(loadedModule)) {
|
|
137
145
|
return yield* Effect.die(new Error(`[foldkit] '${options.serverEntry}' does not export a renderPage function, so the dev server cannot render pages.`));
|
|
138
146
|
}
|
|
139
147
|
const result = yield* Effect.promise(() => loadedModule.renderPage(toWebRequest(requestUrl, nodeRequest)));
|
|
140
|
-
if (result._tag === 'Responded') {
|
|
141
|
-
return result.response;
|
|
142
|
-
}
|
|
143
|
-
if (options.clientEntry !== undefined) {
|
|
144
|
-
if (!('renderDocument' in loadedModule) ||
|
|
145
|
-
!Predicate.isFunction(loadedModule.renderDocument)) {
|
|
146
|
-
return yield* Effect.die(new Error(`[foldkit] '${options.serverEntry}' must export renderDocument(application, assets) when clientEntry is configured.`));
|
|
147
|
-
}
|
|
148
|
-
const assets = Server.DocumentAssets.make({
|
|
149
|
-
entryScript: options.clientEntry,
|
|
150
|
-
stylesheets: [],
|
|
151
|
-
modulePreloads: [],
|
|
152
|
-
});
|
|
153
|
-
const document = loadedModule.renderDocument(result.application, assets);
|
|
154
|
-
const transformed = yield* Effect.promise(() => server.transformIndexHtml(pathname, document, route));
|
|
155
|
-
return Server.toResponse(() => transformed, result);
|
|
156
|
-
}
|
|
157
|
-
const rawTemplate = yield* Effect.promise(() => readFile(resolve(server.config.root, 'index.html'), 'utf-8'));
|
|
158
|
-
// NOTE: Vite resolves template-relative URLs against the first argument;
|
|
159
|
-
// the template-based host's index lives at the root for nested page requests.
|
|
160
|
-
const template = yield* Effect.promise(() => server.transformIndexHtml('/index.html', rawTemplate, route));
|
|
161
148
|
return Server.toResponse(template, result, options.containerId === undefined
|
|
162
149
|
? {}
|
|
163
150
|
: { containerId: options.containerId });
|
|
@@ -474,32 +461,9 @@ const renderMiddleware = (server, options, requestUrls, stateByRequest) => (node
|
|
|
474
461
|
* responses. Server entry edits take effect without a restart.
|
|
475
462
|
*/
|
|
476
463
|
export const foldkitSsr = (options) => {
|
|
477
|
-
let isDevelopmentHost = false;
|
|
478
464
|
return {
|
|
479
465
|
name: 'foldkit-ssr',
|
|
480
|
-
|
|
481
|
-
if (!isDevelopmentHost || options.clientEntry === undefined) {
|
|
482
|
-
return;
|
|
483
|
-
}
|
|
484
|
-
if (!options.clientEntry.startsWith('/') ||
|
|
485
|
-
options.clientEntry.startsWith('//') ||
|
|
486
|
-
/\.html(?:[?#]|$)/.test(options.clientEntry)) {
|
|
487
|
-
throw new Error('[foldkit] clientEntry must be a root-relative browser script URL, such as "/src/entry.ts".');
|
|
488
|
-
}
|
|
489
|
-
if (options.containerId !== undefined) {
|
|
490
|
-
throw new Error('[foldkit] containerId is only used by the template-based dev host. A code-rendered document owns its application placement.');
|
|
491
|
-
}
|
|
492
|
-
if (config.base === '' || config.base === './') {
|
|
493
|
-
throw new Error('[foldkit] code-rendered documents require an absolute URL or root-relative base so browser assets resolve consistently on nested routes.');
|
|
494
|
-
}
|
|
495
|
-
},
|
|
496
|
-
config: (config, { command, isPreview }) => {
|
|
497
|
-
isDevelopmentHost = command === 'serve' && isPreview !== true;
|
|
498
|
-
if (isDevelopmentHost &&
|
|
499
|
-
options.clientEntry !== undefined &&
|
|
500
|
-
(config.base === '' || config.base === './')) {
|
|
501
|
-
throw new Error('[foldkit] code-rendered documents require an absolute URL or root-relative base so browser assets resolve consistently on nested routes.');
|
|
502
|
-
}
|
|
466
|
+
config: (_config, { command, isPreview }) => {
|
|
503
467
|
const buildId = buildIdForCommand(command, options.buildId);
|
|
504
468
|
return {
|
|
505
469
|
// NOTE: `vite preview` also resolves with command 'serve', but it
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@foldkit/vite-plugin",
|
|
3
|
-
"version": "0.26.1-canary.
|
|
3
|
+
"version": "0.26.1-canary.6bd9ee8e728c",
|
|
4
4
|
"description": "Vite plugin for Foldkit with state-preserving live reload",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.js",
|
|
@@ -17,7 +17,7 @@
|
|
|
17
17
|
],
|
|
18
18
|
"peerDependencies": {
|
|
19
19
|
"effect": "4.0.0",
|
|
20
|
-
"foldkit": "0.166.0-canary.
|
|
20
|
+
"foldkit": "0.166.0-canary.6bd9ee8e728c",
|
|
21
21
|
"vite": "^8.0.0"
|
|
22
22
|
},
|
|
23
23
|
"dependencies": {
|
|
@@ -38,7 +38,7 @@
|
|
|
38
38
|
"vite": "^8.3.1",
|
|
39
39
|
"vite-host": "npm:vite@8.2.1",
|
|
40
40
|
"vitest": "^4.1.11",
|
|
41
|
-
"foldkit": "0.166.0-canary.
|
|
41
|
+
"foldkit": "0.166.0-canary.6bd9ee8e728c"
|
|
42
42
|
},
|
|
43
43
|
"keywords": [
|
|
44
44
|
"vite",
|