@skgo/sveltekit-adapter 0.2.7
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/LICENSE +21 -0
- package/README.md +39 -0
- package/index.d.ts +26 -0
- package/package.json +42 -0
- package/skgo-adapter/app-paths.js +40 -0
- package/skgo-adapter/app-server.js +152 -0
- package/skgo-adapter/entry.js +420 -0
- package/skgo-adapter/env.js +1044 -0
- package/skgo-adapter/identity.js +75 -0
- package/skgo-adapter/polyfill.js +157 -0
- package/skgo-adapter.js +793 -0
|
@@ -0,0 +1,1044 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The SSR bundle, built as a fourth environment of kit's own Vite build.
|
|
3
|
+
*
|
|
4
|
+
* skgo used to bundle the engine's JavaScript itself, with esbuild, which
|
|
5
|
+
* meant reimplementing the parts of kit's build that a server environment
|
|
6
|
+
* already does: the Svelte server compile, the TypeScript strip, the `$app/*`
|
|
7
|
+
* alias table, the twenty-three `__SVELTEKIT_*` defines and the epilogue that
|
|
8
|
+
* gives every remote function the `<hash>/<name>` id the browser addresses it
|
|
9
|
+
* by. Every one of those was a second answer to a question kit had already
|
|
10
|
+
* answered, and each could drift from kit's answer without saying so.
|
|
11
|
+
*
|
|
12
|
+
* Declaring an environment instead means kit compiles the engine's bundle the
|
|
13
|
+
* same way it compiles its own server: this file adds no compile step and no
|
|
14
|
+
* alias for anything `$app/*` names. What is left is the one thing skgo has to
|
|
15
|
+
* change — what `$app/server` means — and it stays a source-level substitution,
|
|
16
|
+
* which is only possible before kit's graph is linked.
|
|
17
|
+
*
|
|
18
|
+
* Kit's remote plugin emits one entry chunk per `.remote` module into every
|
|
19
|
+
* server environment (`exports/vite/index.js`, the `emitFile` at 686), which
|
|
20
|
+
* makes this environment a code-splitting build, and rolldown refuses `iife`
|
|
21
|
+
* for those. So the environment emits esm and a second `rolldown()` call over
|
|
22
|
+
* its entry chunk folds it into the one script Go evaluates. Kit's remote
|
|
23
|
+
* entries can share application modules with that entry, so the fold follows
|
|
24
|
+
* every imported emitted chunk while leaving unrelated entries behind.
|
|
25
|
+
*/
|
|
26
|
+
|
|
27
|
+
import { createRequire } from 'node:module';
|
|
28
|
+
import { existsSync, readdirSync, readFileSync, realpathSync, rmSync, statSync } from 'node:fs';
|
|
29
|
+
import { dirname, isAbsolute, join, relative, resolve } from 'node:path';
|
|
30
|
+
import { fileURLToPath, pathToFileURL } from 'node:url';
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* Rolldown, resolved from the app rather than from this package.
|
|
34
|
+
*
|
|
35
|
+
* A plain `import ... from 'vite/rolldown'` resolves beside this file, and this
|
|
36
|
+
* file is in `node_modules/@skgo/sveltekit-adapter`, whose own dependencies are not the
|
|
37
|
+
* app's. Two of them would be fatal even where both exist: kit checks its dev
|
|
38
|
+
* SSR environment with an `instanceof` against the project's resolved vite, so
|
|
39
|
+
* a build driven by a second copy carries classes from a different module realm
|
|
40
|
+
* and kit does not recognise them. The one rolldown that may build this app is
|
|
41
|
+
* the one the app's vite ships, and the app is where it is asked for — the same
|
|
42
|
+
* way kit's own internals are located, below.
|
|
43
|
+
*
|
|
44
|
+
* `process.cwd()` is the vite root: kit's build runs there, and the adapter
|
|
45
|
+
* already reads `skgo.remotes.json` out of it.
|
|
46
|
+
*/
|
|
47
|
+
const app = createRequire(join(process.cwd(), 'package.json'));
|
|
48
|
+
|
|
49
|
+
/** @param {string} specifier */
|
|
50
|
+
async function fromApp(specifier) {
|
|
51
|
+
try {
|
|
52
|
+
return await import(pathToFileURL(app.resolve(specifier)).href);
|
|
53
|
+
} catch (cause) {
|
|
54
|
+
throw new Error(
|
|
55
|
+
`skgo: the adapter builds the SSR bundle with the app's own vite, and ${specifier} ` +
|
|
56
|
+
`could not be resolved from ${process.cwd()}. Run the install first.`,
|
|
57
|
+
{ cause }
|
|
58
|
+
);
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
const { rolldown } = await fromApp('vite/rolldown');
|
|
63
|
+
const { transformSync } = await fromApp('vite/rolldown/experimental');
|
|
64
|
+
// `fetchModule` operates on the app's own `DevEnvironment`, so it has to be
|
|
65
|
+
// the app's copy for the same reason rolldown is: a second module realm's
|
|
66
|
+
// vite does not recognise this one's environments.
|
|
67
|
+
const { fetchModule, isCSSRequest } = await fromApp('vite');
|
|
68
|
+
|
|
69
|
+
// These are the same two Kit functions its own dev server calls in
|
|
70
|
+
// `update_manifest`: one derives the route/node graph from the authored tree,
|
|
71
|
+
// and the other writes the generated client modules that graph describes.
|
|
72
|
+
// The adapter reaches the app's installed Kit by absolute path because these
|
|
73
|
+
// internals are intentionally not package exports.
|
|
74
|
+
const kitRoot = realpathSync(join(process.cwd(), 'node_modules/@sveltejs/kit'));
|
|
75
|
+
const createManifestData = (
|
|
76
|
+
await import(pathToFileURL(join(kitRoot, 'src/core/sync/create_manifest_data/index.js')).href)
|
|
77
|
+
).default;
|
|
78
|
+
const kitSync = await import(pathToFileURL(join(kitRoot, 'src/core/sync/sync.js')).href);
|
|
79
|
+
|
|
80
|
+
/**
|
|
81
|
+
* The runtime JavaScript, as files on disk beside this one — the way kit's own
|
|
82
|
+
* adapters carry theirs (`adapter-node`, `adapter-vercel` and the rest locate a
|
|
83
|
+
* `files/` directory from `import.meta.url`). They used to be `String.raw`
|
|
84
|
+
* constants inside the adapter, which no editor, formatter or type checker
|
|
85
|
+
* could see into.
|
|
86
|
+
*/
|
|
87
|
+
const here = dirname(fileURLToPath(import.meta.url));
|
|
88
|
+
const ENTRY = join(here, 'entry.js');
|
|
89
|
+
const APP_SERVER = join(here, 'app-server.js');
|
|
90
|
+
const APP_PATHS = join(here, 'app-paths.js');
|
|
91
|
+
const POLYFILL = join(here, 'polyfill.js');
|
|
92
|
+
|
|
93
|
+
/**
|
|
94
|
+
* The target the environment is compiled to. es2022 keeps native private class
|
|
95
|
+
* fields; downlevelling them rewrites Svelte's server `Renderer` into WeakMap
|
|
96
|
+
* lookups and costs between 2x and 7x, measured. Anything lower is refused —
|
|
97
|
+
* the modules the engine's parser actually rejects are lowered one at a time
|
|
98
|
+
* below instead.
|
|
99
|
+
*/
|
|
100
|
+
export const SSR_TARGET = 'es2022';
|
|
101
|
+
|
|
102
|
+
/**
|
|
103
|
+
* The namespace the environment's own virtual modules live in. `\0` is
|
|
104
|
+
* rollup's convention for an id no other plugin should touch.
|
|
105
|
+
*/
|
|
106
|
+
const PREFIX = '\0skgo:';
|
|
107
|
+
|
|
108
|
+
/**
|
|
109
|
+
* The kit internals the entry reaches that no `$app/*` specifier names. They
|
|
110
|
+
* are absolute paths, so the bundle holds one instance of each — one installed
|
|
111
|
+
* transport, one devalue.
|
|
112
|
+
*
|
|
113
|
+
* realpath, because pnpm links the package: a module reached through the link
|
|
114
|
+
* looks for kit's own dependencies (devalue, cookie) beside the link instead of
|
|
115
|
+
* beside kit, and the app's node_modules has no such directory. Vite realpaths
|
|
116
|
+
* what its own resolver returns; an id handed back from `resolveId` is taken as
|
|
117
|
+
* given.
|
|
118
|
+
*
|
|
119
|
+
* @param {string} root the vite root
|
|
120
|
+
* @returns {Record<string, string>}
|
|
121
|
+
*/
|
|
122
|
+
function kitAliases(root) {
|
|
123
|
+
const kit = realpathSync(join(root, 'node_modules/@sveltejs/kit'));
|
|
124
|
+
const runtime = join(kit, 'src/runtime');
|
|
125
|
+
return {
|
|
126
|
+
// kit's real `$app/server`, which app-server.js re-exports everything
|
|
127
|
+
// it does not itself replace out of.
|
|
128
|
+
'skgo:kit/app-server': join(runtime, 'app/server/index.js'),
|
|
129
|
+
'skgo:kit/remote': join(runtime, 'app/server/remote/index.js'),
|
|
130
|
+
'skgo:kit/shared': join(runtime, 'shared.js'),
|
|
131
|
+
'skgo:kit/transport': join(runtime, 'app/internal/transport.js'),
|
|
132
|
+
'skgo:kit/props': join(runtime, 'props.svelte.js'),
|
|
133
|
+
'skgo:kit/root': join(runtime, 'components/root.svelte'),
|
|
134
|
+
// kit's own `$app/paths` server implementation, which app-paths.js
|
|
135
|
+
// re-exports `resolve` and `asset` out of, and the pathname decoder kit's
|
|
136
|
+
// own `match` prepares its argument with.
|
|
137
|
+
'skgo:kit/paths-server': join(runtime, 'app/paths/server.js'),
|
|
138
|
+
'skgo:kit/url': join(kit, 'src/utils/url.js'),
|
|
139
|
+
// devalue resolved from kit rather than from the app: kit is what
|
|
140
|
+
// depends on it, and under pnpm the app's own node_modules has no such
|
|
141
|
+
// directory. The entry needs it directly for the one thing kit's
|
|
142
|
+
// `parse` cannot do — read a promise placeholder back.
|
|
143
|
+
'skgo:devalue': createRequire(join(kit, 'package.json')).resolve('devalue')
|
|
144
|
+
};
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
/**
|
|
148
|
+
* Whether a module is one of this package's own runtime files.
|
|
149
|
+
*
|
|
150
|
+
* @param {string} file
|
|
151
|
+
*/
|
|
152
|
+
function ours(file) {
|
|
153
|
+
const rel = relative(here, file);
|
|
154
|
+
return rel !== '' && !rel.startsWith('..') && !isAbsolute(rel);
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
/** Whether a specifier names a package rather than a path or a virtual module.
|
|
158
|
+
*
|
|
159
|
+
* @param {string} id
|
|
160
|
+
*/
|
|
161
|
+
function bare(id) {
|
|
162
|
+
return !id.startsWith('.') && !id.startsWith('\0') && !id.startsWith('/') && !isAbsolute(id);
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
/** The constructs goja's parser rejects, and the only reason to lower a module. */
|
|
166
|
+
const UNPARSEABLE = /for\s+await\s*\(|async\s+function\s*\*|async\s*\*/;
|
|
167
|
+
|
|
168
|
+
/**
|
|
169
|
+
* The goja environment: the vite plugin that declares it, and the call that
|
|
170
|
+
* builds it and folds the result.
|
|
171
|
+
*
|
|
172
|
+
* They are one object because the plugin has to be handed to kit at
|
|
173
|
+
* construction time — kit reads `adapter.vite.plugins.post` while it is
|
|
174
|
+
* building its config — and the build cannot start until `adapt()` runs, which
|
|
175
|
+
* is the first moment the node table is known.
|
|
176
|
+
*
|
|
177
|
+
*/
|
|
178
|
+
export function gojaEnvironment() {
|
|
179
|
+
/** filled in by build(), read by the plugin's virtual modules */
|
|
180
|
+
const state = {
|
|
181
|
+
nodes: '',
|
|
182
|
+
hooks: '',
|
|
183
|
+
// vite's ViteBuilder. Its own types do not name it in a form a JSDoc
|
|
184
|
+
// annotation here can reach.
|
|
185
|
+
/** @type {any} */
|
|
186
|
+
builder: null
|
|
187
|
+
};
|
|
188
|
+
/** @type {Set<string>} the modules that had to be lowered, for the log */
|
|
189
|
+
const lowered = new Set();
|
|
190
|
+
|
|
191
|
+
const root = process.cwd();
|
|
192
|
+
const aliases = kitAliases(root);
|
|
193
|
+
|
|
194
|
+
/** @type {Record<string, () => string>} */
|
|
195
|
+
const sources = {
|
|
196
|
+
// The node table the bundle renders through and the app's universal
|
|
197
|
+
// hooks, both of which only exist once kit's build has finished.
|
|
198
|
+
'skgo:nodes': () => state.nodes,
|
|
199
|
+
'skgo:hooks': () => state.hooks,
|
|
200
|
+
// esm-env's DEV/BROWSER are vite conditions kit's server build resolves
|
|
201
|
+
// through package exports; this environment states them outright.
|
|
202
|
+
'skgo:esm-env': () => 'export const DEV = false; export const BROWSER = false;',
|
|
203
|
+
// `get_hooks` carries `reroute` and nothing else the engine needs; Go
|
|
204
|
+
// does the routing.
|
|
205
|
+
'skgo:generated': () => 'export const get_hooks = () => ({});',
|
|
206
|
+
// Neither kit nor Svelte can reach AsyncLocalStorage here, and neither
|
|
207
|
+
// needs to: the webcontainer flag in the banner selects Svelte's own
|
|
208
|
+
// supported fallback — a module-global render context and a serialised
|
|
209
|
+
// render queue — and flips the matching flag in kit.
|
|
210
|
+
'skgo:missing': () =>
|
|
211
|
+
'throw new Error("skgo: node:async_hooks is unavailable in the SSR engine");'
|
|
212
|
+
};
|
|
213
|
+
|
|
214
|
+
/** @type {import('vite').Plugin} */
|
|
215
|
+
const plugin = {
|
|
216
|
+
name: 'skgo-goja-environment',
|
|
217
|
+
apply: 'build',
|
|
218
|
+
|
|
219
|
+
/**
|
|
220
|
+
* The environment itself. A server consumer, like kit's own `ssr`, so
|
|
221
|
+
* that kit's `#app/*` imports resolve to their server variants; esm
|
|
222
|
+
* because kit's remote plugin makes it a code-splitting build; nothing
|
|
223
|
+
* external, because the engine has no module loader to reach anything
|
|
224
|
+
* with.
|
|
225
|
+
*/
|
|
226
|
+
config() {
|
|
227
|
+
return {
|
|
228
|
+
environments: {
|
|
229
|
+
goja: {
|
|
230
|
+
consumer: 'server',
|
|
231
|
+
resolve: {
|
|
232
|
+
noExternal: true,
|
|
233
|
+
external: [],
|
|
234
|
+
// A server consumer gets Node builtins by default. This one
|
|
235
|
+
// executes in goja and has none: leaving them external can
|
|
236
|
+
// produce a successful build whose IIFE calls an undefined
|
|
237
|
+
// `node_module` global before the first render.
|
|
238
|
+
builtins: [],
|
|
239
|
+
// Deliberately neither `node` nor `browser`. Kit and Svelte's
|
|
240
|
+
// server entry points use their default exports, while an app
|
|
241
|
+
// dependency such as `yaml` can choose its portable default
|
|
242
|
+
// instead of a Node-only conditional export.
|
|
243
|
+
conditions: ['production', 'module', 'import', 'default']
|
|
244
|
+
},
|
|
245
|
+
build: {
|
|
246
|
+
outDir: GOJA_OUT,
|
|
247
|
+
emptyOutDir: false,
|
|
248
|
+
copyPublicDir: false,
|
|
249
|
+
manifest: false,
|
|
250
|
+
ssrEmitAssets: false,
|
|
251
|
+
emitAssets: false,
|
|
252
|
+
cssCodeSplit: false,
|
|
253
|
+
minify: false,
|
|
254
|
+
sourcemap: false,
|
|
255
|
+
target: SSR_TARGET,
|
|
256
|
+
rolldownOptions: {
|
|
257
|
+
// Vite otherwise gives every server consumer the Node
|
|
258
|
+
// platform, which may synthesize `createRequire` while
|
|
259
|
+
// linking a chunk shared with Kit's remote entry. The engine
|
|
260
|
+
// is a neutral JavaScript host, not Node.
|
|
261
|
+
platform: 'neutral',
|
|
262
|
+
input: { bundle: ENTRY },
|
|
263
|
+
preserveEntrySignatures: false,
|
|
264
|
+
output: {
|
|
265
|
+
format: 'esm',
|
|
266
|
+
entryFileNames: 'bundle.js',
|
|
267
|
+
assetFileNames: 'assets/[name][extname]',
|
|
268
|
+
codeSplitting: false
|
|
269
|
+
}
|
|
270
|
+
}
|
|
271
|
+
}
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
};
|
|
275
|
+
},
|
|
276
|
+
|
|
277
|
+
// Vite hands every buildApp hook its builder, and kit's adapter hook
|
|
278
|
+
// runs last (`order: 'post'`), so the builder is in hand by the time
|
|
279
|
+
// adapt() asks for the environment to be built.
|
|
280
|
+
buildApp: {
|
|
281
|
+
order: 'pre',
|
|
282
|
+
async handler(builder) {
|
|
283
|
+
state.builder = builder;
|
|
284
|
+
}
|
|
285
|
+
},
|
|
286
|
+
|
|
287
|
+
// Everything below is this environment's and no other's: kit's `ssr`
|
|
288
|
+
// build keeps kit's own `$app/server`, and its client build is
|
|
289
|
+
// untouched.
|
|
290
|
+
applyToEnvironment(environment) {
|
|
291
|
+
return environment.name === 'goja';
|
|
292
|
+
},
|
|
293
|
+
|
|
294
|
+
resolveId: {
|
|
295
|
+
order: 'pre',
|
|
296
|
+
async handler(id, importer) {
|
|
297
|
+
if (id in sources) return PREFIX + id;
|
|
298
|
+
if (id.startsWith(PREFIX)) return id;
|
|
299
|
+
if (id in aliases) return aliases[id];
|
|
300
|
+
// The two substitutions. Kit aliases `$app/server` and
|
|
301
|
+
// `$app/paths` to its own runtime modules; this gets there first,
|
|
302
|
+
// and only here.
|
|
303
|
+
if (id === '$app/server') return APP_SERVER;
|
|
304
|
+
if (id === '$app/paths') return APP_PATHS;
|
|
305
|
+
if (id === 'esm-env') return PREFIX + 'skgo:esm-env';
|
|
306
|
+
if (id === '<sveltekit:generated>') return PREFIX + 'skgo:generated';
|
|
307
|
+
if (id === 'node:async_hooks' || id === 'async_hooks') return PREFIX + 'skgo:missing';
|
|
308
|
+
// A bare specifier in one of this package's own runtime files —
|
|
309
|
+
// `svelte/server`, `@sveltejs/kit/internal/server` — names a
|
|
310
|
+
// package the app depends on and this one does not. Resolving it
|
|
311
|
+
// beside these files looks in node_modules/@skgo/sveltekit-adapter and
|
|
312
|
+
// finds nothing; the app is where kit and Svelte are installed,
|
|
313
|
+
// and it is the copy of each that the rest of this build already
|
|
314
|
+
// uses. So the app asks on the file's behalf, through vite's own
|
|
315
|
+
// resolver, which is the only thing here that knows the
|
|
316
|
+
// conditions this environment resolves under.
|
|
317
|
+
if (importer && ours(importer) && bare(id)) {
|
|
318
|
+
return this.resolve(id, join(root, 'package.json'), { skipSelf: true });
|
|
319
|
+
}
|
|
320
|
+
return null;
|
|
321
|
+
}
|
|
322
|
+
},
|
|
323
|
+
|
|
324
|
+
load: {
|
|
325
|
+
order: 'pre',
|
|
326
|
+
handler(id) {
|
|
327
|
+
if (!id.startsWith(PREFIX)) return null;
|
|
328
|
+
const make = sources[id.slice(PREFIX.length)];
|
|
329
|
+
if (!make) throw new Error('skgo: no virtual module ' + id.slice(PREFIX.length));
|
|
330
|
+
return make();
|
|
331
|
+
}
|
|
332
|
+
},
|
|
333
|
+
|
|
334
|
+
/**
|
|
335
|
+
* Per-module lowering, and the one module that cannot be bundled at
|
|
336
|
+
* all. See engineTransform.
|
|
337
|
+
*/
|
|
338
|
+
transform: {
|
|
339
|
+
order: 'post',
|
|
340
|
+
handler(code, id) {
|
|
341
|
+
return engineTransform(code, id, { lowered });
|
|
342
|
+
}
|
|
343
|
+
}
|
|
344
|
+
};
|
|
345
|
+
|
|
346
|
+
/**
|
|
347
|
+
* Builds the environment and folds it into the one script Go evaluates.
|
|
348
|
+
*
|
|
349
|
+
* The fold is a second `rolldown()` over the environment's own entry chunk.
|
|
350
|
+
* It reaches that chunk and everything it imports, including chunks shared
|
|
351
|
+
* with entries emitted by kit's remote plugin. Unrelated entry chunks are
|
|
352
|
+
* left behind, and `iife` is legal because the fold itself has one entry.
|
|
353
|
+
*
|
|
354
|
+
* The polyfill is the fold's banner rather than a module of the bundle: in
|
|
355
|
+
* the folded file kit's shared chunk evaluates before the entry body, so a
|
|
356
|
+
* polyfill module would arrive after `new URL(...)` at kit's module scope.
|
|
357
|
+
*
|
|
358
|
+
* @param {{ nodes: string, hooks: string, log?: (message: string) => void }} generated
|
|
359
|
+
* @param {string} outfile
|
|
360
|
+
*/
|
|
361
|
+
async function build({ nodes, hooks, log = () => {} }, outfile) {
|
|
362
|
+
if (!state.builder) {
|
|
363
|
+
throw new Error(
|
|
364
|
+
"skgo: vite never handed the adapter its builder, so the goja environment cannot be built. The adapter's `vite.plugins.post` hook did not reach kit."
|
|
365
|
+
);
|
|
366
|
+
}
|
|
367
|
+
state.nodes = nodes;
|
|
368
|
+
state.hooks = hooks;
|
|
369
|
+
|
|
370
|
+
const environment = state.builder.environments.goja;
|
|
371
|
+
if (!environment) {
|
|
372
|
+
throw new Error(
|
|
373
|
+
'skgo: vite built no `goja` environment. The adapter declares one in a `config` hook; this build of vite did not take it.'
|
|
374
|
+
);
|
|
375
|
+
}
|
|
376
|
+
|
|
377
|
+
await state.builder.build(environment);
|
|
378
|
+
if (lowered.size) {
|
|
379
|
+
log(`skgo: lowered to es2017 for the engine's parser: ${lowered.size} module(s)`);
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
const chunk = resolve(root, environment.config.build.outDir, 'bundle.js');
|
|
383
|
+
if (!existsSync(chunk)) {
|
|
384
|
+
throw new Error(`skgo: the goja environment emitted no ${chunk}`);
|
|
385
|
+
}
|
|
386
|
+
|
|
387
|
+
const bundle = await rolldown({
|
|
388
|
+
input: chunk,
|
|
389
|
+
platform: 'neutral',
|
|
390
|
+
// `import.meta` cannot survive into an iife, and one reaches this
|
|
391
|
+
// graph: kit's `constants.js` exports `SRC_ROOT = import.meta.dirname`
|
|
392
|
+
// for the benefit of its own prerenderer, which does not run here.
|
|
393
|
+
// Saying so is what the previous build did too — esbuild's
|
|
394
|
+
// `supported: { 'import-meta': false }` rewrote it silently. Naming
|
|
395
|
+
// it keeps rolldown from warning about a rewrite that is the point.
|
|
396
|
+
transform: { define: { 'import.meta': '{}' } }
|
|
397
|
+
});
|
|
398
|
+
try {
|
|
399
|
+
await bundle.write({
|
|
400
|
+
dir: dirname(resolve(root, outfile)),
|
|
401
|
+
entryFileNames: basenameOf(outfile),
|
|
402
|
+
format: 'iife',
|
|
403
|
+
codeSplitting: false,
|
|
404
|
+
banner: readFileSync(POLYFILL, 'utf-8')
|
|
405
|
+
});
|
|
406
|
+
} finally {
|
|
407
|
+
await bundle.close();
|
|
408
|
+
}
|
|
409
|
+
rmSync(resolve(root, environment.config.build.outDir), { force: true, recursive: true });
|
|
410
|
+
}
|
|
411
|
+
|
|
412
|
+
return { plugin, build };
|
|
413
|
+
}
|
|
414
|
+
|
|
415
|
+
/**
|
|
416
|
+
* Where the environment's own output goes before it is folded. It is removed
|
|
417
|
+
* once the fold has read it; nothing downstream ever sees it.
|
|
418
|
+
*/
|
|
419
|
+
const GOJA_OUT = 'build/.goja';
|
|
420
|
+
|
|
421
|
+
/** @param {string} file */
|
|
422
|
+
function basenameOf(file) {
|
|
423
|
+
return file.slice(file.lastIndexOf('/') + 1);
|
|
424
|
+
}
|
|
425
|
+
|
|
426
|
+
/**
|
|
427
|
+
* The node table the bundle renders through: one static import per component
|
|
428
|
+
* kit's build produced, in the manifest's own node order.
|
|
429
|
+
*
|
|
430
|
+
* @param {string} root
|
|
431
|
+
* @param {Array<{ component: string | null }>} nodes
|
|
432
|
+
*/
|
|
433
|
+
export function nodeTable(root, nodes) {
|
|
434
|
+
/** @type {string[]} */
|
|
435
|
+
const imports = [];
|
|
436
|
+
/** @type {string[]} */
|
|
437
|
+
const table = [];
|
|
438
|
+
nodes.forEach((node, i) => {
|
|
439
|
+
if (!node.component) {
|
|
440
|
+
table.push('undefined');
|
|
441
|
+
return;
|
|
442
|
+
}
|
|
443
|
+
imports.push(`import N${i} from ${JSON.stringify(join(root, node.component))};`);
|
|
444
|
+
table.push(`N${i}`);
|
|
445
|
+
});
|
|
446
|
+
return `${imports.join('\n')}\nexport const components = [${table.join(', ')}];\n`;
|
|
447
|
+
}
|
|
448
|
+
|
|
449
|
+
/**
|
|
450
|
+
* The engine's per-module transform, shared by the build and by dev.
|
|
451
|
+
*
|
|
452
|
+
* oxc's `target` is a single coarse ES version with no esbuild-style
|
|
453
|
+
* per-feature `supported` map, so lowering `for await` and async generators
|
|
454
|
+
* for the whole environment would also downlevel every private class field —
|
|
455
|
+
* the 2x-to-7x SSR_TARGET exists to avoid. Only the handful of modules that
|
|
456
|
+
* carry the two constructs goja's parser rejects are lowered, and none of them
|
|
457
|
+
* is under `svelte/src/internal/server/`.
|
|
458
|
+
*
|
|
459
|
+
* `helpers` is only passed in dev. A module lowered to es2017 imports
|
|
460
|
+
* `@oxc-project/runtime/helpers/...` by bare specifier, and this transform runs
|
|
461
|
+
* after vite's import analysis, so nothing rewrites it: the module runner would
|
|
462
|
+
* ask `fetchModule` for that specifier with an importer inside `svelte/`, and
|
|
463
|
+
* under pnpm the package is a dependency of vite's own core rather than of the
|
|
464
|
+
* app, so it cannot be resolved from there. The build never sees this, because
|
|
465
|
+
* resolution there starts from the bundler. Naming the file outright is what
|
|
466
|
+
* keeps dev working.
|
|
467
|
+
*
|
|
468
|
+
* @param {string} code
|
|
469
|
+
* @param {string} id
|
|
470
|
+
* @param {{ lowered?: Set<string>, helpers?: string }} options
|
|
471
|
+
*/
|
|
472
|
+
function engineTransform(code, id, { lowered, helpers } = {}) {
|
|
473
|
+
let out = code;
|
|
474
|
+
// svelte/src/internal/server/crypto.js hides a `node:crypto` import behind
|
|
475
|
+
// a variable so bundlers cannot resolve it. goja rejects the syntax
|
|
476
|
+
// whether or not the branch is reached.
|
|
477
|
+
if (out.includes('obfuscated_import')) {
|
|
478
|
+
out = out.replace(
|
|
479
|
+
/=>\s*import\(([\s\S]*?)\)/,
|
|
480
|
+
'=> Promise.reject(new Error("skgo: no dynamic import in the SSR engine"))'
|
|
481
|
+
);
|
|
482
|
+
}
|
|
483
|
+
if (!UNPARSEABLE.test(out)) {
|
|
484
|
+
return out === code ? null : { code: out, map: null };
|
|
485
|
+
}
|
|
486
|
+
lowered?.add(id);
|
|
487
|
+
const result = transformSync(id.replace(/\0/g, '_'), out, { target: 'es2017' });
|
|
488
|
+
let lower = result.code;
|
|
489
|
+
if (helpers) {
|
|
490
|
+
lower = lower.replace(
|
|
491
|
+
/(['"`])@oxc-project\/runtime\/helpers\/([A-Za-z0-9_$]+)\1/g,
|
|
492
|
+
(_m, quote, name) => quote + join(helpers, 'src/helpers/esm', name + '.js') + quote
|
|
493
|
+
);
|
|
494
|
+
}
|
|
495
|
+
return { code: lower, map: null };
|
|
496
|
+
}
|
|
497
|
+
|
|
498
|
+
/**
|
|
499
|
+
* Where oxc's lowering helpers live, resolved the way dev has to resolve them:
|
|
500
|
+
* from vite rather than from the app. Under pnpm `@oxc-project/runtime` is a
|
|
501
|
+
* dependency of vite's core and the app's own node_modules has no such
|
|
502
|
+
* directory.
|
|
503
|
+
*
|
|
504
|
+
* @param {string} root the vite root
|
|
505
|
+
*/
|
|
506
|
+
function oxcHelpers(root) {
|
|
507
|
+
// Not the module-level `fromApp`, which imports; this resolves a path.
|
|
508
|
+
const fromRoot = createRequire(join(root, 'package.json'));
|
|
509
|
+
const fromVite = createRequire(fromRoot.resolve('vite/package.json'));
|
|
510
|
+
return dirname(fromVite.resolve('@oxc-project/runtime/package.json'));
|
|
511
|
+
}
|
|
512
|
+
|
|
513
|
+
/**
|
|
514
|
+
* The paths in the document kit's own dev server writes, which are not the
|
|
515
|
+
* paths a build writes: kit's client entry is served straight out of the
|
|
516
|
+
* installed package over `/@fs`, its app module out of the generated dev tree,
|
|
517
|
+
* and neither has any import, stylesheet or font beside it
|
|
518
|
+
* (`exports/vite/dev/index.js`, the `_.client` of the dev manifest). The boot
|
|
519
|
+
* global is `__sveltekit_dev` rather than `__sveltekit_<hash>`
|
|
520
|
+
* (`core/utils.js`, `get_global_name`).
|
|
521
|
+
*
|
|
522
|
+
* @param {string} root the vite root
|
|
523
|
+
* @param {string} outDir kit's `outDir`
|
|
524
|
+
*/
|
|
525
|
+
function devClient(root, outDir) {
|
|
526
|
+
const kit = realpathSync(join(root, 'node_modules/@sveltejs/kit'));
|
|
527
|
+
const runtime = join(kit, 'src/runtime');
|
|
528
|
+
// kit's own get_runtime_base: a runtime inside the project is addressed by
|
|
529
|
+
// a root-relative path, and one outside it — which is what pnpm's link
|
|
530
|
+
// farm gives — over `/@fs`.
|
|
531
|
+
const start = runtime.startsWith(root + '/')
|
|
532
|
+
? '/' + posix(runtime.slice(root.length + 1)) + '/client/entry.js'
|
|
533
|
+
: '/@fs' + posix(runtime) + '/client/entry.js';
|
|
534
|
+
return {
|
|
535
|
+
start,
|
|
536
|
+
app: '/@fs' + posix(join(outDir, 'generated/dev/client/app.js')),
|
|
537
|
+
imports: [],
|
|
538
|
+
stylesheets: [],
|
|
539
|
+
fonts: [],
|
|
540
|
+
usesEnvDynamicPublic: true
|
|
541
|
+
};
|
|
542
|
+
}
|
|
543
|
+
|
|
544
|
+
/** @param {string} p */
|
|
545
|
+
function posix(p) {
|
|
546
|
+
return p.replace(/\\/g, '/');
|
|
547
|
+
}
|
|
548
|
+
|
|
549
|
+
/**
|
|
550
|
+
* The node table in kit's own dev numbering.
|
|
551
|
+
*
|
|
552
|
+
* `create_manifest_data` is Kit's route scanner and `nodes` is its ordered
|
|
553
|
+
* output. Reading that snapshot directly matters: the generated numbered files
|
|
554
|
+
* are written one at a time and old high-numbered files can remain briefly
|
|
555
|
+
* after a route is removed, so enumerating that directory can combine two
|
|
556
|
+
* different route graphs. A node with no component leaves a hole, exactly as
|
|
557
|
+
* the built table does.
|
|
558
|
+
*
|
|
559
|
+
* @param {any} manifest Kit's current manifest data
|
|
560
|
+
* @param {string} root Vite's project root
|
|
561
|
+
*/
|
|
562
|
+
function devNodeTable(manifest, root) {
|
|
563
|
+
if (!manifest) throw new Error('skgo: the live Kit manifest is not ready');
|
|
564
|
+
/** @type {string[]} */
|
|
565
|
+
const imports = [];
|
|
566
|
+
/** @type {string[]} */
|
|
567
|
+
const table = [];
|
|
568
|
+
for (const [index, node] of manifest.nodes.entries()) {
|
|
569
|
+
if (!node.component) {
|
|
570
|
+
table.push('undefined');
|
|
571
|
+
continue;
|
|
572
|
+
}
|
|
573
|
+
imports.push(`import N${index} from ${JSON.stringify(resolve(root, node.component))};`);
|
|
574
|
+
table.push(`N${index}`);
|
|
575
|
+
}
|
|
576
|
+
return `${imports.join('\n')}\nexport const components = [${table.join(', ')}];\n`;
|
|
577
|
+
}
|
|
578
|
+
|
|
579
|
+
/**
|
|
580
|
+
* The engine's environment in `vite dev`.
|
|
581
|
+
*
|
|
582
|
+
* `vite dev` never runs an adapter — kit reaches `adapt()` only from the
|
|
583
|
+
* `apply: 'build'` plugin that finalises a build — but it does hand the
|
|
584
|
+
* adapter's `vite.plugins.post` to vite unconditionally
|
|
585
|
+
* (`exports/vite/index.js`, the returned plugin array). So the same `goja`
|
|
586
|
+
* environment the build compiles can be declared in dev, where there is no
|
|
587
|
+
* bundle at all, and Go can pull one transformed module at a time out of it
|
|
588
|
+
* over vite's own `fetchModule`.
|
|
589
|
+
*
|
|
590
|
+
* Nothing else changes. The entry, the `$app/server` substitution, the kit
|
|
591
|
+
* aliases and the per-module lowering are the build's; only where the modules
|
|
592
|
+
* come from is different, and three things dev states differently from a
|
|
593
|
+
* build:
|
|
594
|
+
*
|
|
595
|
+
* - `esm-env`'s `DEV` is **true**. `vite-plugin-svelte` compiles components
|
|
596
|
+
* with Svelte's dev instrumentation in serve, and that instrumentation
|
|
597
|
+
* reads bookkeeping Svelte's runtime only keeps when `DEV` is true
|
|
598
|
+
* (`svelte/src/internal/server/context.js`, `push`). Saying false gives a
|
|
599
|
+
* component compiled one way a runtime built the other, and the render
|
|
600
|
+
* dies in `push_element`.
|
|
601
|
+
* - the node table is kit's dev numbering rather than the build's.
|
|
602
|
+
* - lowered modules name oxc's helper files outright. See engineTransform.
|
|
603
|
+
*
|
|
604
|
+
* @param {{ outDir?: string }} [options]
|
|
605
|
+
*/
|
|
606
|
+
export function gojaDevEnvironment({ outDir = '.svelte-kit' } = {}) {
|
|
607
|
+
const root = process.cwd();
|
|
608
|
+
const out = resolve(root, outDir);
|
|
609
|
+
const aliases = kitAliases(root);
|
|
610
|
+
const helpers = oxcHelpers(root);
|
|
611
|
+
let manifestData;
|
|
612
|
+
|
|
613
|
+
/** @type {Record<string, () => string>} */
|
|
614
|
+
const sources = {
|
|
615
|
+
'skgo:nodes': () => devNodeTable(manifestData, root),
|
|
616
|
+
'skgo:hooks': () => hooksModule(root),
|
|
617
|
+
'skgo:esm-env': () => 'export const DEV = true; export const BROWSER = false;',
|
|
618
|
+
'skgo:generated': () => 'export const get_hooks = () => ({});',
|
|
619
|
+
'skgo:missing': () =>
|
|
620
|
+
'throw new Error("skgo: node:async_hooks is unavailable in the SSR engine");'
|
|
621
|
+
};
|
|
622
|
+
|
|
623
|
+
// Every file the dev server has seen change, oldest first, and the cursor
|
|
624
|
+
// Go reads them with. It is filled from `hotUpdate` rather than from the
|
|
625
|
+
// watcher because `hotUpdate` runs after vite has already invalidated its
|
|
626
|
+
// own module graph for the file — a listener on the raw watcher races that,
|
|
627
|
+
// and the module Go re-fetched would be the one it already had.
|
|
628
|
+
/** @type {string[]} */
|
|
629
|
+
const changed = [];
|
|
630
|
+
let manifestVersion = 0;
|
|
631
|
+
let manifestDirty = true;
|
|
632
|
+
|
|
633
|
+
/** @type {import('vite').Plugin} */
|
|
634
|
+
const plugin = {
|
|
635
|
+
name: 'skgo-goja-dev-environment',
|
|
636
|
+
apply: 'serve',
|
|
637
|
+
|
|
638
|
+
config() {
|
|
639
|
+
return {
|
|
640
|
+
environments: {
|
|
641
|
+
goja: {
|
|
642
|
+
consumer: 'server',
|
|
643
|
+
resolve: {
|
|
644
|
+
noExternal: true,
|
|
645
|
+
external: [],
|
|
646
|
+
// The dev module runner executes in the same neutral, no-builtins
|
|
647
|
+
// host as the production bundle. Keeping its resolver identical
|
|
648
|
+
// prevents a package from changing implementation between modes.
|
|
649
|
+
builtins: [],
|
|
650
|
+
conditions: ['production', 'module', 'import', 'default']
|
|
651
|
+
}
|
|
652
|
+
}
|
|
653
|
+
}
|
|
654
|
+
};
|
|
655
|
+
},
|
|
656
|
+
|
|
657
|
+
configureServer(server) {
|
|
658
|
+
const environment = server.environments.goja;
|
|
659
|
+
if (!environment) {
|
|
660
|
+
throw new Error(
|
|
661
|
+
'skgo: vite created no `goja` environment for the dev server. The adapter declares one in a `config` hook; this build of vite did not take it.'
|
|
662
|
+
);
|
|
663
|
+
}
|
|
664
|
+
|
|
665
|
+
const setup = server.config.plugins.find(
|
|
666
|
+
(candidate) => candidate.name === 'vite-plugin-sveltekit-setup'
|
|
667
|
+
);
|
|
668
|
+
const kit = setup?.api?.options;
|
|
669
|
+
if (!kit) {
|
|
670
|
+
throw new Error(
|
|
671
|
+
'skgo: Kit exposed no validated options on vite-plugin-sveltekit-setup; the dev manifest cannot mirror what Kit is serving.'
|
|
672
|
+
);
|
|
673
|
+
}
|
|
674
|
+
|
|
675
|
+
const generated = JSON.parse(readFileSync(join(root, 'skgo.remotes.json'), 'utf-8'));
|
|
676
|
+
const endpointMethods = generated.endpoints ?? {};
|
|
677
|
+
|
|
678
|
+
const refreshManifest = () => {
|
|
679
|
+
if (!manifestDirty && manifestData) return manifestData;
|
|
680
|
+
manifestData = createManifestData(kit, root);
|
|
681
|
+
// A skgo page request does not pass through Kit's SSR middleware, so
|
|
682
|
+
// it cannot rely on Kit's first request to write these modules. This
|
|
683
|
+
// is Kit's own sync.create call with the same arguments its dev
|
|
684
|
+
// update_manifest uses.
|
|
685
|
+
kitSync.create(kit, root, manifestData, false);
|
|
686
|
+
// Invalidate only after the complete manifest snapshot is installed.
|
|
687
|
+
// A watcher fires before this point and would make Vite cache a node
|
|
688
|
+
// table generated from the preceding snapshot.
|
|
689
|
+
const module = environment.moduleGraph.getModuleById(PREFIX + 'skgo:nodes');
|
|
690
|
+
if (module) environment.moduleGraph.invalidateModule(module);
|
|
691
|
+
changed.push(NODE_TABLE_URL);
|
|
692
|
+
manifestDirty = false;
|
|
693
|
+
manifestVersion++;
|
|
694
|
+
return manifestData;
|
|
695
|
+
};
|
|
696
|
+
|
|
697
|
+
const devManifest = () => {
|
|
698
|
+
const data = refreshManifest();
|
|
699
|
+
return {
|
|
700
|
+
version: manifestVersion,
|
|
701
|
+
nodes: data.nodes.map((node) => node.server ?? ''),
|
|
702
|
+
routes: data.routes
|
|
703
|
+
.filter((route) => route.page || route.endpoint)
|
|
704
|
+
.map((route) => ({
|
|
705
|
+
id: route.id,
|
|
706
|
+
pattern: route.pattern.source,
|
|
707
|
+
params: route.params,
|
|
708
|
+
endpoint: route.endpoint
|
|
709
|
+
? { methods: endpointMethods[route.id] ?? [] }
|
|
710
|
+
: null,
|
|
711
|
+
page: route.page
|
|
712
|
+
? {
|
|
713
|
+
layouts: Array.from(
|
|
714
|
+
{ length: route.page.layouts.length },
|
|
715
|
+
(_, i) => route.page.layouts[i] ?? -1
|
|
716
|
+
),
|
|
717
|
+
errors: Array.from(
|
|
718
|
+
{ length: route.page.errors.length },
|
|
719
|
+
(_, i) => route.page.errors[i] ?? -1
|
|
720
|
+
),
|
|
721
|
+
leaf: route.page.leaf
|
|
722
|
+
}
|
|
723
|
+
: null
|
|
724
|
+
})),
|
|
725
|
+
ssr: {
|
|
726
|
+
assets: kit.paths.assets,
|
|
727
|
+
relative: kit.paths.relative,
|
|
728
|
+
csp: kit.csp,
|
|
729
|
+
nodes: data.nodes.map((node, index) => ({
|
|
730
|
+
index,
|
|
731
|
+
component: !!node.component,
|
|
732
|
+
ssr: node.page_options?.ssr ?? null,
|
|
733
|
+
csr: node.page_options?.csr ?? null
|
|
734
|
+
}))
|
|
735
|
+
},
|
|
736
|
+
template: readFileSync(kit.files.appTemplate, 'utf-8'),
|
|
737
|
+
errorTemplate: readDevErrorTemplate(kit)
|
|
738
|
+
};
|
|
739
|
+
};
|
|
740
|
+
|
|
741
|
+
// Do this before Go can observe `/info`. It closes the clean-worktree
|
|
742
|
+
// race where the server was listening but Kit had not yet received a
|
|
743
|
+
// page request and therefore had not written its dev client/node tree.
|
|
744
|
+
refreshManifest();
|
|
745
|
+
|
|
746
|
+
// A route being added or removed changes Kit's node numbering. Record
|
|
747
|
+
// the virtual table itself, not the generated client files Kit happens
|
|
748
|
+
// to rewrite: those files are not an atomic snapshot and old numbered
|
|
749
|
+
// files can remain while the tree is settling.
|
|
750
|
+
server.watcher.on('all', (event, file) => {
|
|
751
|
+
// `hotUpdate` is not dispatched for a custom environment until it
|
|
752
|
+
// has its own HMR transport. Go is that environment's consumer, so
|
|
753
|
+
// record and invalidate its graph directly from Vite's watcher.
|
|
754
|
+
// Generated client files are browser inputs and, more importantly,
|
|
755
|
+
// are rewritten by refreshManifest itself; feeding them back here
|
|
756
|
+
// creates a change storm with no Go module to invalidate.
|
|
757
|
+
if (normalize(file).startsWith(normalize(out) + '/')) return;
|
|
758
|
+
for (const module of environment.moduleGraph.getModulesByFile(file) ?? []) {
|
|
759
|
+
environment.moduleGraph.invalidateModule(module);
|
|
760
|
+
}
|
|
761
|
+
// Repeated edits are distinct cursor positions. In particular, an
|
|
762
|
+
// editor replacing text and a test restoring it can be consecutive
|
|
763
|
+
// events for the same path; collapsing them strands runtimes on the
|
|
764
|
+
// temporary version forever.
|
|
765
|
+
changed.push(file);
|
|
766
|
+
|
|
767
|
+
const routeFile = normalize(file).startsWith(normalize(kit.files.routes) + '/');
|
|
768
|
+
if (routeFile && (event === 'add' || event === 'unlink')) manifestDirty = true;
|
|
769
|
+
const name = relative(kit.files.routes, file).split(/[\\/]/).at(-1);
|
|
770
|
+
if (routeFile && /^\+(?:page|layout)(?:\.server)?\.[jt]s$/.test(name ?? '')) {
|
|
771
|
+
manifestDirty = true;
|
|
772
|
+
}
|
|
773
|
+
});
|
|
774
|
+
|
|
775
|
+
// What Go needs to know before it can ask for anything: which
|
|
776
|
+
// module is the render entry, and what a dev document boots.
|
|
777
|
+
server.middlewares.use('/__skgo_dev/info', (_req, res) => {
|
|
778
|
+
try {
|
|
779
|
+
json(res, 200, {
|
|
780
|
+
entry: ENTRY,
|
|
781
|
+
client: devClient(root, out),
|
|
782
|
+
globalName: '__sveltekit_dev',
|
|
783
|
+
manifest: devManifest()
|
|
784
|
+
});
|
|
785
|
+
} catch (e) {
|
|
786
|
+
json(res, 500, { error: errorText(e) });
|
|
787
|
+
}
|
|
788
|
+
});
|
|
789
|
+
|
|
790
|
+
// Kit's dev renderer inlines every CSS dependency of the nodes in the
|
|
791
|
+
// rendered branch. Go asks after it has evaluated those nodes, so the
|
|
792
|
+
// goja environment's module graph contains the same dependency edges
|
|
793
|
+
// Kit's renderer would walk.
|
|
794
|
+
server.middlewares.use('/__skgo_dev/styles', async (req, res) => {
|
|
795
|
+
let body = '';
|
|
796
|
+
for await (const chunk of req) body += chunk;
|
|
797
|
+
try {
|
|
798
|
+
const { nodes = [] } = JSON.parse(body || '{}');
|
|
799
|
+
const data = refreshManifest();
|
|
800
|
+
const deps = new Set();
|
|
801
|
+
for (const index of nodes) {
|
|
802
|
+
const node = data.nodes[index];
|
|
803
|
+
if (!node) continue;
|
|
804
|
+
for (const file of [node.component, node.universal]) {
|
|
805
|
+
if (!file) continue;
|
|
806
|
+
const id = resolve(root, file);
|
|
807
|
+
const module =
|
|
808
|
+
environment.moduleGraph.getModuleById(id) ??
|
|
809
|
+
(await environment.moduleGraph.getModuleByUrl('/' + posix(file)));
|
|
810
|
+
if (module) await findDevDeps(environment, module, deps);
|
|
811
|
+
}
|
|
812
|
+
}
|
|
813
|
+
|
|
814
|
+
const styles = [];
|
|
815
|
+
const runner = server.environments.ssr.runner;
|
|
816
|
+
for (const dep of deps) {
|
|
817
|
+
if (!isCSSRequest(dep.url) || VITE_CSS_QUERY.test(dep.url)) continue;
|
|
818
|
+
const inline = dep.url.includes('?')
|
|
819
|
+
? dep.url.replace('?', '?inline&')
|
|
820
|
+
: dep.url + '?inline';
|
|
821
|
+
try {
|
|
822
|
+
styles.push((await runner.import(inline)).default);
|
|
823
|
+
} catch {
|
|
824
|
+
// Kit deliberately ignores a CSS module that vanished from the
|
|
825
|
+
// graph between traversal and import (typically a dynamic import).
|
|
826
|
+
}
|
|
827
|
+
}
|
|
828
|
+
json(res, 200, { styles });
|
|
829
|
+
} catch (e) {
|
|
830
|
+
json(res, 500, { error: errorText(e) });
|
|
831
|
+
}
|
|
832
|
+
});
|
|
833
|
+
|
|
834
|
+
// One transformed module, exactly as vite's own module runner would
|
|
835
|
+
// receive it (`ssr/fetchModule.ts`): the SSR transform's output and
|
|
836
|
+
// the file it came from.
|
|
837
|
+
server.middlewares.use('/__skgo_dev/module', async (req, res) => {
|
|
838
|
+
let body = '';
|
|
839
|
+
for await (const chunk of req) body += chunk;
|
|
840
|
+
try {
|
|
841
|
+
const { url, importer } = JSON.parse(body || '{}');
|
|
842
|
+
json(
|
|
843
|
+
res,
|
|
844
|
+
200,
|
|
845
|
+
await fetchModule(server.environments.goja, url, importer, {
|
|
846
|
+
inlineSourceMap: false
|
|
847
|
+
})
|
|
848
|
+
);
|
|
849
|
+
} catch (e) {
|
|
850
|
+
json(res, 500, { error: errorText(e) });
|
|
851
|
+
}
|
|
852
|
+
});
|
|
853
|
+
|
|
854
|
+
// What has changed since Go last asked. Go drops exactly the
|
|
855
|
+
// modules these files reach and re-imports the entry; everything
|
|
856
|
+
// else in its module cache is still the one the last render used.
|
|
857
|
+
server.middlewares.use('/__skgo_dev/changed', (req, res) => {
|
|
858
|
+
const since = Number(new URL(req.url ?? '/', 'http://skgo').searchParams.get('since'));
|
|
859
|
+
const from = Number.isInteger(since) && since >= 0 ? since : 0;
|
|
860
|
+
json(res, 200, {
|
|
861
|
+
version: changed.length,
|
|
862
|
+
// A cursor from before this server started is a Go process
|
|
863
|
+
// that outlived a vite restart. It cannot know what it
|
|
864
|
+
// missed, so it is told to start over.
|
|
865
|
+
reset: from > changed.length,
|
|
866
|
+
files: from > changed.length ? [] : changed.slice(from)
|
|
867
|
+
});
|
|
868
|
+
});
|
|
869
|
+
},
|
|
870
|
+
|
|
871
|
+
applyToEnvironment(environment) {
|
|
872
|
+
return environment.name === 'goja';
|
|
873
|
+
},
|
|
874
|
+
|
|
875
|
+
// Runs for this environment after vite has invalidated its module graph
|
|
876
|
+
// for the file (`handleHMRUpdate`), which is the ordering the cursor
|
|
877
|
+
// above depends on.
|
|
878
|
+
hotUpdate({ file }) {
|
|
879
|
+
const name = relative(kit.files.routes, file).split(/[\\/]/).at(-1);
|
|
880
|
+
if (/^\+(?:page|layout)(?:\.server)?\.[jt]s$/.test(name ?? '')) {
|
|
881
|
+
manifestDirty = true;
|
|
882
|
+
}
|
|
883
|
+
return undefined;
|
|
884
|
+
},
|
|
885
|
+
|
|
886
|
+
resolveId: {
|
|
887
|
+
order: 'pre',
|
|
888
|
+
async handler(id, importer) {
|
|
889
|
+
if (id in sources) return PREFIX + id;
|
|
890
|
+
if (id.startsWith(PREFIX)) return id;
|
|
891
|
+
if (id in aliases) return aliases[id];
|
|
892
|
+
if (id === '$app/server') return APP_SERVER;
|
|
893
|
+
if (id === '$app/paths') return APP_PATHS;
|
|
894
|
+
if (id === 'esm-env') return PREFIX + 'skgo:esm-env';
|
|
895
|
+
if (id === '<sveltekit:generated>') return PREFIX + 'skgo:generated';
|
|
896
|
+
if (id === 'node:async_hooks' || id === 'async_hooks') return PREFIX + 'skgo:missing';
|
|
897
|
+
if (id.startsWith(OXC_HELPERS)) {
|
|
898
|
+
return join(helpers, 'src/helpers/esm', id.slice(OXC_HELPERS.length) + '.js');
|
|
899
|
+
}
|
|
900
|
+
// The same question the build's environment answers, and for the
|
|
901
|
+
// same reason: this package's own runtime files name packages
|
|
902
|
+
// the app depends on and this one does not, and they are read
|
|
903
|
+
// from wherever `@skgo/sveltekit-adapter` is installed rather than from
|
|
904
|
+
// inside the app. The app asks on their behalf.
|
|
905
|
+
if (importer && ours(importer) && bare(id)) {
|
|
906
|
+
return this.resolve(id, join(root, 'package.json'), { skipSelf: true });
|
|
907
|
+
}
|
|
908
|
+
return null;
|
|
909
|
+
}
|
|
910
|
+
},
|
|
911
|
+
|
|
912
|
+
load: {
|
|
913
|
+
order: 'pre',
|
|
914
|
+
handler(id) {
|
|
915
|
+
if (!id.startsWith(PREFIX)) return null;
|
|
916
|
+
const make = sources[id.slice(PREFIX.length)];
|
|
917
|
+
if (!make) throw new Error('skgo: no virtual module ' + id.slice(PREFIX.length));
|
|
918
|
+
return make();
|
|
919
|
+
}
|
|
920
|
+
},
|
|
921
|
+
|
|
922
|
+
transform: {
|
|
923
|
+
order: 'post',
|
|
924
|
+
handler(code, id) {
|
|
925
|
+
return engineTransform(code, id, { helpers });
|
|
926
|
+
}
|
|
927
|
+
}
|
|
928
|
+
};
|
|
929
|
+
|
|
930
|
+
return plugin;
|
|
931
|
+
}
|
|
932
|
+
|
|
933
|
+
const OXC_HELPERS = '@oxc-project/runtime/helpers/';
|
|
934
|
+
|
|
935
|
+
/**
|
|
936
|
+
* The URL vite gives the node table, which is how Go names it when it drops it.
|
|
937
|
+
* A virtual module has no file for a change to be attributed to, so the change
|
|
938
|
+
* log carries this instead.
|
|
939
|
+
*/
|
|
940
|
+
const NODE_TABLE_URL = '/@id/__x00__skgo:skgo:nodes';
|
|
941
|
+
const VITE_CSS_QUERY = /(?:\?|&)(?:raw|url|inline)(?:&|$)/;
|
|
942
|
+
|
|
943
|
+
/**
|
|
944
|
+
* Kit's dev `find_deps`, with the environment made explicit. Its own copy is
|
|
945
|
+
* closed over the `ssr` environment; skgo's component modules were evaluated
|
|
946
|
+
* in `goja`, so that is the graph whose transform-result URLs are authoritative.
|
|
947
|
+
*
|
|
948
|
+
* @param {import('vite').DevEnvironment} environment
|
|
949
|
+
* @param {import('vite').EnvironmentModuleNode} node
|
|
950
|
+
* @param {Set<import('vite').EnvironmentModuleNode>} deps
|
|
951
|
+
*/
|
|
952
|
+
async function findDevDeps(environment, node, deps) {
|
|
953
|
+
const branches = [];
|
|
954
|
+
const add = async (dependency) => {
|
|
955
|
+
if (deps.has(dependency)) return;
|
|
956
|
+
deps.add(dependency);
|
|
957
|
+
await findDevDeps(environment, dependency, deps);
|
|
958
|
+
};
|
|
959
|
+
const addByURL = async (url) => {
|
|
960
|
+
const dependency = await environment.moduleGraph.getModuleByUrl(url);
|
|
961
|
+
if (dependency) await add(dependency);
|
|
962
|
+
};
|
|
963
|
+
|
|
964
|
+
if (node.transformResult) {
|
|
965
|
+
for (const url of node.transformResult.deps ?? []) branches.push(addByURL(url));
|
|
966
|
+
for (const url of node.transformResult.dynamicDeps ?? []) branches.push(addByURL(url));
|
|
967
|
+
} else {
|
|
968
|
+
for (const dependency of node.importedModules) branches.push(add(dependency));
|
|
969
|
+
}
|
|
970
|
+
await Promise.all(branches);
|
|
971
|
+
}
|
|
972
|
+
|
|
973
|
+
/** @param {any} kit Kit's validated config */
|
|
974
|
+
function readDevErrorTemplate(kit) {
|
|
975
|
+
try {
|
|
976
|
+
return readFileSync(kit.files.errorTemplate, 'utf-8');
|
|
977
|
+
} catch {
|
|
978
|
+
return readFileSync(join(kitRoot, 'src/core/config/default-error.html'), 'utf-8');
|
|
979
|
+
}
|
|
980
|
+
}
|
|
981
|
+
|
|
982
|
+
/** @param {string} p */
|
|
983
|
+
function normalize(p) {
|
|
984
|
+
return posix(p);
|
|
985
|
+
}
|
|
986
|
+
|
|
987
|
+
/**
|
|
988
|
+
* The module the engine reads the app's `transport` hook out of, in dev. The
|
|
989
|
+
* build's equivalent is written by the adapter from `builder.config`; here the
|
|
990
|
+
* file is found the way kit's own `resolve_entry` finds it.
|
|
991
|
+
*
|
|
992
|
+
* @param {string} root the vite root
|
|
993
|
+
*/
|
|
994
|
+
function hooksModule(root) {
|
|
995
|
+
const file = resolveEntry(join(root, 'src/hooks'));
|
|
996
|
+
if (!file) return 'export const transport = {};';
|
|
997
|
+
return (
|
|
998
|
+
`import * as hooks from ${JSON.stringify(file)};\n` +
|
|
999
|
+
'export const transport = hooks.transport ?? {};\n'
|
|
1000
|
+
);
|
|
1001
|
+
}
|
|
1002
|
+
|
|
1003
|
+
/**
|
|
1004
|
+
* kit's `resolve_entry` (packages/kit/src/utils/filesystem.js): an
|
|
1005
|
+
* extensionless path becomes the file that is actually there.
|
|
1006
|
+
*
|
|
1007
|
+
* @param {string} entry
|
|
1008
|
+
* @returns {string | null}
|
|
1009
|
+
*/
|
|
1010
|
+
function resolveEntry(entry) {
|
|
1011
|
+
if (existsSync(entry)) {
|
|
1012
|
+
if (statSync(entry).isFile()) return entry;
|
|
1013
|
+
const index = join(entry, 'index');
|
|
1014
|
+
if (existsSync(index + '.js') || existsSync(index + '.ts')) return resolveEntry(index);
|
|
1015
|
+
}
|
|
1016
|
+
|
|
1017
|
+
const dir = dirname(entry);
|
|
1018
|
+
if (existsSync(dir)) {
|
|
1019
|
+
const base = entry.slice(entry.lastIndexOf('/') + 1);
|
|
1020
|
+
const found = readdirSync(dir).find(
|
|
1021
|
+
(file) => file.replace(/\.(js|ts)$/, '') === base && statSync(join(dir, file)).isFile()
|
|
1022
|
+
);
|
|
1023
|
+
if (found) return join(dir, found);
|
|
1024
|
+
}
|
|
1025
|
+
|
|
1026
|
+
return null;
|
|
1027
|
+
}
|
|
1028
|
+
|
|
1029
|
+
/**
|
|
1030
|
+
* @param {import('node:http').ServerResponse} res
|
|
1031
|
+
* @param {number} status
|
|
1032
|
+
* @param {unknown} body
|
|
1033
|
+
*/
|
|
1034
|
+
function json(res, status, body) {
|
|
1035
|
+
res.statusCode = status;
|
|
1036
|
+
res.setHeader('content-type', 'application/json');
|
|
1037
|
+
res.end(JSON.stringify(body));
|
|
1038
|
+
}
|
|
1039
|
+
|
|
1040
|
+
/** @param {unknown} e */
|
|
1041
|
+
function errorText(e) {
|
|
1042
|
+
if (e instanceof Error) return e.stack ?? e.message;
|
|
1043
|
+
return String(e);
|
|
1044
|
+
}
|