@skgo/sveltekit-adapter 0.13.1 → 0.15.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +1 -1
- package/skgo-adapter/entry.js +77 -81
- package/skgo-adapter/env.js +174 -6
- package/skgo-adapter/polyfill.js +279 -45
- package/skgo-adapter.js +2 -2
package/package.json
CHANGED
package/skgo-adapter/entry.js
CHANGED
|
@@ -24,6 +24,8 @@ import * as devalue from 'skgo:devalue';
|
|
|
24
24
|
import { decoders, init_transport, parse } from 'skgo:kit/transport';
|
|
25
25
|
import { components, universalLoads } from 'skgo:nodes';
|
|
26
26
|
import { transport } from 'skgo:hooks';
|
|
27
|
+
import { serialize_data } from 'skgo:kit/serialize-data';
|
|
28
|
+
import { create_universal_fetch } from 'skgo:kit/load-data';
|
|
27
29
|
|
|
28
30
|
/**
|
|
29
31
|
* The app's transport hook, installed the way kit installs it
|
|
@@ -76,89 +78,79 @@ function make_state() {
|
|
|
76
78
|
};
|
|
77
79
|
}
|
|
78
80
|
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
* the shape a remote function's call is, and a cross-origin one is refused: a
|
|
87
|
-
* thrown TypeError, before anything is dispatched. (Kit's own version also
|
|
88
|
-
* forwards the page's own Authorization header on a same-origin request; this
|
|
89
|
-
* one does not, because the render's own request never reaches the engine
|
|
90
|
-
* with its headers — only its cookies, in req.cookies.)
|
|
91
|
-
*
|
|
92
|
-
* @param {any} req
|
|
93
|
-
* @param {URL} url
|
|
94
|
-
*/
|
|
95
|
-
function create_fetch(req, url) {
|
|
96
|
-
return async function (input, init) {
|
|
97
|
-
const request = input instanceof Request ? input : new Request(input, init);
|
|
98
|
-
const target = new URL(request.url, url);
|
|
99
|
-
|
|
100
|
-
if (!same_origin(target, url)) {
|
|
101
|
-
throw new TypeError(
|
|
102
|
-
"skgo: event.fetch only reaches this app's own routes during server-side rendering (" +
|
|
103
|
-
origin_of(target) +
|
|
104
|
-
' is not ' +
|
|
105
|
-
origin_of(url) +
|
|
106
|
-
')'
|
|
107
|
-
);
|
|
108
|
-
}
|
|
109
|
-
|
|
110
|
-
const credentials = init?.credentials ?? request.credentials ?? 'same-origin';
|
|
111
|
-
if (credentials !== 'omit') {
|
|
112
|
-
const cookie = Object.entries(req.cookies ?? {})
|
|
113
|
-
.map(([name, value]) => name + '=' + value)
|
|
114
|
-
.join('; ');
|
|
115
|
-
if (cookie) request.headers.set('cookie', cookie);
|
|
116
|
-
}
|
|
117
|
-
|
|
118
|
-
const headers = {};
|
|
119
|
-
request.headers.forEach((value, name) => {
|
|
120
|
-
headers[name] = value;
|
|
121
|
-
});
|
|
122
|
-
|
|
123
|
-
const envelope = { method: request.method || 'GET', url: target.href, headers };
|
|
124
|
-
if (typeof request._body === 'string') envelope.body = request._body;
|
|
81
|
+
const bytes_to_base64 = (bytes) => {
|
|
82
|
+
let binary = '';
|
|
83
|
+
for (let i = 0; i < bytes.length; i += 0x8000) {
|
|
84
|
+
binary += String.fromCharCode.apply(null, bytes.subarray(i, i + 0x8000));
|
|
85
|
+
}
|
|
86
|
+
return btoa(binary);
|
|
87
|
+
};
|
|
125
88
|
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
89
|
+
const base64_to_bytes = (encoded) => {
|
|
90
|
+
const binary = atob(encoded);
|
|
91
|
+
const bytes = new Uint8Array(binary.length);
|
|
92
|
+
for (let i = 0; i < binary.length; i++) bytes[i] = binary.charCodeAt(i);
|
|
93
|
+
return bytes;
|
|
94
|
+
};
|
|
129
95
|
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
status: answer.response.status,
|
|
133
|
-
headers: answer.response.headers
|
|
134
|
-
})
|
|
135
|
-
);
|
|
136
|
-
};
|
|
137
|
-
}
|
|
96
|
+
// Statuses a real Response cannot carry a body for (runtime/server/constants.js).
|
|
97
|
+
const NULL_BODY = [101, 204, 205, 304];
|
|
138
98
|
|
|
139
99
|
/**
|
|
140
|
-
*
|
|
141
|
-
*
|
|
142
|
-
*
|
|
143
|
-
*
|
|
144
|
-
* cross-origin fetch and Go's own handler for the path. Protocol, hostname and
|
|
145
|
-
* port are each exactly what the standard compares.
|
|
100
|
+
* Every response header a fetch answered with, and whether the app's
|
|
101
|
+
* `filterSerializedResponseHeaders` lets Go replay it into the document. Go
|
|
102
|
+
* evaluates that filter, so the one kit hands its serializer answers from what
|
|
103
|
+
* Go said: the same name and value always get the same answer.
|
|
146
104
|
*
|
|
147
|
-
* @param {
|
|
148
|
-
* @param {URL} b
|
|
105
|
+
* @param {Set<string>} serialized
|
|
149
106
|
*/
|
|
150
|
-
function
|
|
151
|
-
return
|
|
107
|
+
function resolve_options(serialized) {
|
|
108
|
+
return {
|
|
109
|
+
filterSerializedResponseHeaders: (name, value) => serialized.has(name + ':' + value)
|
|
110
|
+
};
|
|
152
111
|
}
|
|
153
112
|
|
|
154
113
|
/**
|
|
155
|
-
*
|
|
156
|
-
*
|
|
114
|
+
* Kit's `event.fetch` (runtime/server/fetch.js) with its whole body — the
|
|
115
|
+
* handleFetch hook, credential inheritance, internal dispatch, cookie
|
|
116
|
+
* write-back — done by Go. What stays here is what the engine owns: turning
|
|
117
|
+
* what a component passed into a Request, and the bytes Go answers with into a
|
|
118
|
+
* Response.
|
|
157
119
|
*
|
|
120
|
+
* @param {object} req
|
|
158
121
|
* @param {URL} url
|
|
122
|
+
* @param {Set<string>} serialized
|
|
159
123
|
*/
|
|
160
|
-
function
|
|
161
|
-
return
|
|
124
|
+
function create_fetch(req, url, serialized) {
|
|
125
|
+
return async function (input, init) {
|
|
126
|
+
const request = input instanceof Request ? input : new Request(new URL(input, url).href, init);
|
|
127
|
+
const target = new URL(request.url, url);
|
|
128
|
+
const headers = {};
|
|
129
|
+
request.headers.forEach((value, name) => { headers[name] = value; });
|
|
130
|
+
const envelope = {
|
|
131
|
+
method: request.method || 'GET', url: target.href, headers,
|
|
132
|
+
credentials: request.credentials, mode: request.mode
|
|
133
|
+
};
|
|
134
|
+
if (request.body !== null) {
|
|
135
|
+
const bytes = new Uint8Array(await request._consume());
|
|
136
|
+
if (bytes.length) envelope.bodyBase64 = bytes_to_base64(bytes);
|
|
137
|
+
}
|
|
138
|
+
const answer = JSON.parse(await globalThis.__skgo_fetch(JSON.stringify(envelope)));
|
|
139
|
+
if (answer.error) throw new TypeError(answer.error);
|
|
140
|
+
const value = answer.response;
|
|
141
|
+
for (const [name, header, allowed] of value.headers ?? []) {
|
|
142
|
+
if (allowed) serialized.add(name + ':' + header);
|
|
143
|
+
}
|
|
144
|
+
let body = null;
|
|
145
|
+
if (!NULL_BODY.includes(value.status) && request.method !== 'HEAD') {
|
|
146
|
+
body = value.bodyBase64 ? base64_to_bytes(value.bodyBase64) : value.body ?? '';
|
|
147
|
+
}
|
|
148
|
+
return new Response(body, {
|
|
149
|
+
status: value.status,
|
|
150
|
+
statusText: value.statusText,
|
|
151
|
+
headers: (value.headers ?? []).map(([name, header]) => [name, header])
|
|
152
|
+
});
|
|
153
|
+
};
|
|
162
154
|
}
|
|
163
155
|
|
|
164
156
|
/**
|
|
@@ -167,7 +159,7 @@ function origin_of(url) {
|
|
|
167
159
|
* route throw. Go owns the real request; the only I/O anything here performs
|
|
168
160
|
* is `fetch`'s in-process call back into Go for one of the app's own routes.
|
|
169
161
|
*/
|
|
170
|
-
function make_event(req, url) {
|
|
162
|
+
function make_event(req, url, serialized) {
|
|
171
163
|
return {
|
|
172
164
|
cookies: {
|
|
173
165
|
get: (name) => (req.cookies ?? {})[name],
|
|
@@ -176,12 +168,12 @@ function make_event(req, url) {
|
|
|
176
168
|
delete: () => {},
|
|
177
169
|
serialize: () => ''
|
|
178
170
|
},
|
|
179
|
-
fetch: create_fetch(req, url),
|
|
171
|
+
fetch: create_fetch(req, url, serialized),
|
|
180
172
|
getClientAddress: () => req.client_address ?? '127.0.0.1',
|
|
181
173
|
locals: {},
|
|
182
174
|
params: req.params ?? {},
|
|
183
175
|
platform: undefined,
|
|
184
|
-
request: { headers:
|
|
176
|
+
request: { headers: new Headers(req.headers), method: req.method ?? 'GET' },
|
|
185
177
|
route: { id: req.route_id ?? null },
|
|
186
178
|
setHeaders: () => {},
|
|
187
179
|
url,
|
|
@@ -218,7 +210,7 @@ function node_data(node) {
|
|
|
218
210
|
});
|
|
219
211
|
}
|
|
220
212
|
|
|
221
|
-
async function build_props(req, url, event, state) {
|
|
213
|
+
async function build_props(req, url, event, state, fetched, serialized) {
|
|
222
214
|
const form = req.form ? devalue.parse(req.form, decoders) : null;
|
|
223
215
|
const page = {
|
|
224
216
|
error: req.error ?? null,
|
|
@@ -261,9 +253,7 @@ async function build_props(req, url, event, state) {
|
|
|
261
253
|
parent: async () => parent_data,
|
|
262
254
|
depends: () => {},
|
|
263
255
|
untrack: (fn) => fn(),
|
|
264
|
-
fetch: (
|
|
265
|
-
throw new Error('skgo: universal load fetch is not supported during rendering');
|
|
266
|
-
},
|
|
256
|
+
fetch: create_universal_fetch(event, undefined, fetched, req.csr, resolve_options(serialized)),
|
|
267
257
|
setHeaders: () => {
|
|
268
258
|
throw new Error('skgo: universal load setHeaders is not supported during rendering');
|
|
269
259
|
}
|
|
@@ -363,15 +353,18 @@ globalThis.__skgo_render = function (req_json) {
|
|
|
363
353
|
status: 200,
|
|
364
354
|
error: null,
|
|
365
355
|
head: '',
|
|
366
|
-
body: ''
|
|
356
|
+
body: '',
|
|
357
|
+
fetched: ''
|
|
367
358
|
};
|
|
368
359
|
|
|
369
360
|
try {
|
|
370
361
|
const req = JSON.parse(req_json);
|
|
371
362
|
const url = new URL(req.url);
|
|
372
363
|
const state = make_state();
|
|
373
|
-
const
|
|
374
|
-
const
|
|
364
|
+
const serialized = new Set();
|
|
365
|
+
const event = make_event(req, url, serialized);
|
|
366
|
+
const fetched = [];
|
|
367
|
+
const props_promise = build_props(req, url, event, state, fetched, serialized);
|
|
375
368
|
const promise = props_promise.then((props) => {
|
|
376
369
|
result.status = props.page.status;
|
|
377
370
|
result.error = req.error ?? null;
|
|
@@ -417,6 +410,9 @@ globalThis.__skgo_render = function (req_json) {
|
|
|
417
410
|
(rendered) => {
|
|
418
411
|
result.head = rendered.head;
|
|
419
412
|
result.body = rendered.body;
|
|
413
|
+
result.fetched = fetched
|
|
414
|
+
.map((item) => serialize_data(item, resolve_options(serialized).filterSerializedResponseHeaders))
|
|
415
|
+
.join("\n\t\t\t");
|
|
420
416
|
result.done = true;
|
|
421
417
|
},
|
|
422
418
|
(err) => {
|
package/skgo-adapter/env.js
CHANGED
|
@@ -26,7 +26,7 @@
|
|
|
26
26
|
|
|
27
27
|
import { createRequire } from 'node:module';
|
|
28
28
|
import { execFileSync } from 'node:child_process';
|
|
29
|
-
import { randomBytes } from 'node:crypto';
|
|
29
|
+
import { createHash, randomBytes } from 'node:crypto';
|
|
30
30
|
import { protectEnvironmentFiles } from './env-private.js';
|
|
31
31
|
import { existsSync, readdirSync, readFileSync, realpathSync, rmSync, statSync, mkdirSync, writeFileSync, chmodSync } from 'node:fs';
|
|
32
32
|
import { dirname, isAbsolute, join, relative, resolve } from 'node:path';
|
|
@@ -63,7 +63,7 @@ async function fromApp(specifier) {
|
|
|
63
63
|
}
|
|
64
64
|
|
|
65
65
|
const { rolldown } = await fromApp('vite/rolldown');
|
|
66
|
-
const { transformSync } = await fromApp('vite/rolldown/experimental');
|
|
66
|
+
const { parseSync, transformSync } = await fromApp('vite/rolldown/experimental');
|
|
67
67
|
// `fetchModule` operates on the app's own `DevEnvironment`, so it has to be
|
|
68
68
|
// the app's copy for the same reason rolldown is: a second module realm's
|
|
69
69
|
// vite does not recognise this one's environments.
|
|
@@ -132,6 +132,10 @@ function kitAliases(root) {
|
|
|
132
132
|
'skgo:kit/remote': join(runtime, 'app/server/remote/index.js'),
|
|
133
133
|
'skgo:kit/shared': join(runtime, 'shared.js'),
|
|
134
134
|
'skgo:kit/transport': join(runtime, 'app/internal/transport.js'),
|
|
135
|
+
'skgo:kit/serialize-data': join(runtime, 'server/page/serialize_data.js'),
|
|
136
|
+
// kit's own universal fetch, which the entry hands every universal load
|
|
137
|
+
// rather than keeping a second copy of its CORS, replay and header rules.
|
|
138
|
+
'skgo:kit/load-data': join(runtime, 'server/page/load_data.js'),
|
|
135
139
|
'skgo:kit/props': join(runtime, 'props.svelte.js'),
|
|
136
140
|
'skgo:kit/root': join(runtime, 'components/root.svelte'),
|
|
137
141
|
// kit's own `$app/paths` server implementation, which app-paths.js
|
|
@@ -165,8 +169,9 @@ function bare(id) {
|
|
|
165
169
|
return !id.startsWith('.') && !id.startsWith('\0') && !id.startsWith('/') && !isAbsolute(id);
|
|
166
170
|
}
|
|
167
171
|
|
|
168
|
-
|
|
169
|
-
|
|
172
|
+
// A conservative prefilter: comments may separate an unsupported construct's
|
|
173
|
+
// tokens. The syntax tree, not their spelling or surrounding text, decides.
|
|
174
|
+
const MAY_NEED_LOWERING = /\b(?:async|await)\b/;
|
|
170
175
|
|
|
171
176
|
/**
|
|
172
177
|
* The goja environment: the vite plugin that declares it, and the call that
|
|
@@ -484,7 +489,7 @@ export function nodeTable(root, nodes) {
|
|
|
484
489
|
* @param {string} id
|
|
485
490
|
* @param {{ lowered?: Set<string>, helpers?: string }} options
|
|
486
491
|
*/
|
|
487
|
-
function engineTransform(code, id, { lowered, helpers } = {}) {
|
|
492
|
+
export function engineTransform(code, id, { lowered, helpers } = {}) {
|
|
488
493
|
let out = code;
|
|
489
494
|
// svelte/src/internal/server/crypto.js hides a `node:crypto` import behind
|
|
490
495
|
// a variable so bundlers cannot resolve it. goja rejects the syntax
|
|
@@ -495,7 +500,7 @@ function engineTransform(code, id, { lowered, helpers } = {}) {
|
|
|
495
500
|
'=> Promise.reject(new Error("skgo: no dynamic import in the SSR engine"))'
|
|
496
501
|
);
|
|
497
502
|
}
|
|
498
|
-
if (!
|
|
503
|
+
if (!needsLowering(out, id)) {
|
|
499
504
|
return out === code ? null : { code: out, map: null };
|
|
500
505
|
}
|
|
501
506
|
lowered?.add(id);
|
|
@@ -510,6 +515,30 @@ function engineTransform(code, id, { lowered, helpers } = {}) {
|
|
|
510
515
|
return { code: lower, map: null };
|
|
511
516
|
}
|
|
512
517
|
|
|
518
|
+
// A text match is only a cheap prefilter. Svelte's renderer has a JSDoc
|
|
519
|
+
// parameter named `render_async` immediately before `*/`, which looks like
|
|
520
|
+
// an async generator to the old regexp. Lowering that comment rewrote all of its
|
|
521
|
+
// private fields to WeakMaps and retained whole render trees in the engine.
|
|
522
|
+
function needsLowering(code, id) {
|
|
523
|
+
if (!MAY_NEED_LOWERING.test(code)) return false;
|
|
524
|
+
const nodes = [parseSync(id.replace(/\0/g, '_'), code).program];
|
|
525
|
+
while (nodes.length) {
|
|
526
|
+
const node = nodes.pop();
|
|
527
|
+
if (!node || typeof node !== 'object') continue;
|
|
528
|
+
if (node.type === 'ForOfStatement' && node.await) return true;
|
|
529
|
+
if ((node.type === 'FunctionDeclaration' || node.type === 'FunctionExpression') &&
|
|
530
|
+
node.async && node.generator) return true;
|
|
531
|
+
for (const value of Object.values(node)) {
|
|
532
|
+
if (value && typeof value === 'object') {
|
|
533
|
+
if (Array.isArray(value)) {
|
|
534
|
+
for (const child of value) nodes.push(child);
|
|
535
|
+
} else nodes.push(value);
|
|
536
|
+
}
|
|
537
|
+
}
|
|
538
|
+
}
|
|
539
|
+
return false;
|
|
540
|
+
}
|
|
541
|
+
|
|
513
542
|
/**
|
|
514
543
|
* Where oxc's lowering helpers live, resolved the way dev has to resolve them:
|
|
515
544
|
* from vite rather than from the app. Under pnpm `@oxc-project/runtime` is a
|
|
@@ -786,6 +815,13 @@ export function gojaDevEnvironment({ outDir = '.svelte-kit' } = {}) {
|
|
|
786
815
|
// temporary version forever.
|
|
787
816
|
changed.push(file);
|
|
788
817
|
|
|
818
|
+
// The document templates are read afresh for every `/info`, but Go
|
|
819
|
+
// installs them only when the version moves. Nothing else moves it
|
|
820
|
+
// for a template, which is not a route and has no module to drop.
|
|
821
|
+
if ([kit.files.appTemplate, kit.files.errorTemplate].some((t) => normalize(t) === normalize(file))) {
|
|
822
|
+
manifestVersion++;
|
|
823
|
+
}
|
|
824
|
+
|
|
789
825
|
const routeFile = normalize(file).startsWith(normalize(kit.files.routes) + '/');
|
|
790
826
|
if (routeFile && (event === 'add' || event === 'unlink')) manifestDirty = true;
|
|
791
827
|
const name = relative(kit.files.routes, file).split(/[\\/]/).at(-1);
|
|
@@ -957,6 +993,138 @@ export function gojaDevEnvironment({ outDir = '.svelte-kit' } = {}) {
|
|
|
957
993
|
return plugin;
|
|
958
994
|
}
|
|
959
995
|
|
|
996
|
+
/**
|
|
997
|
+
* A watcher event for a file whose bytes are the ones vite already has is not an edit.
|
|
998
|
+
*
|
|
999
|
+
* chokidar reports a file nobody has read since it was written as changed on
|
|
1000
|
+
* any filesystem event, metadata-only ones included, and something on a
|
|
1001
|
+
* developer's machine (a sync client, a scanner) touches generated files that
|
|
1002
|
+
* way. Kit's own dev server has no answer to it: vite turns each report into a
|
|
1003
|
+
* hot update or a full page reload, and a reload throws away what a visitor
|
|
1004
|
+
* had typed and aborts the documents Go was rendering for them. So the dev
|
|
1005
|
+
* server remembers the content it last loaded or was told about, and lets
|
|
1006
|
+
* through only the reports whose content differs.
|
|
1007
|
+
*
|
|
1008
|
+
* Content means the file's bytes, not the text vite decoded from them: two
|
|
1009
|
+
* different invalid byte sequences decode to the same replacement characters,
|
|
1010
|
+
* and an edit between them is still an edit. What vite loaded is therefore
|
|
1011
|
+
* remembered only when the file's bytes decode to exactly it; a file a plugin
|
|
1012
|
+
* generated or rewrote on the way in has no such bytes, and its first report
|
|
1013
|
+
* is let through.
|
|
1014
|
+
*
|
|
1015
|
+
* The adapter's own output directory is not an input at all. It is where the
|
|
1016
|
+
* build writes the documents Go embeds, vite holds no module for any of them,
|
|
1017
|
+
* and vite answers a report for an html file it holds no module for by
|
|
1018
|
+
* reloading every open page. It is excluded from the watcher the way kit
|
|
1019
|
+
* excludes its own `.svelte-kit` output (kit/exports/vite/index.js, the
|
|
1020
|
+
* `server.watch.ignored` it sets), so nothing written there is ever reported.
|
|
1021
|
+
*
|
|
1022
|
+
* chokidar emits one report twice, `change` and then `all`, and the first
|
|
1023
|
+
* verdict stands for both. A `change` emitted from inside a `change` listener
|
|
1024
|
+
* is a plugin's own (vite-plugin-svelte re-emits it for a preprocessor's
|
|
1025
|
+
* dependants, whose bytes are unchanged); those always pass.
|
|
1026
|
+
*
|
|
1027
|
+
* @param {{ out?: string }} [options] the adapter's output directory, relative to the vite root
|
|
1028
|
+
* @returns {import('vite').Plugin}
|
|
1029
|
+
*/
|
|
1030
|
+
export function gojaDevUnchangedFiles({ out = 'build' } = {}) {
|
|
1031
|
+
/** What vite last loaded for a file, or last reported changed: its byte digest. */
|
|
1032
|
+
const known = new Map();
|
|
1033
|
+
const digest = (/** @type {Uint8Array} */ bytes) => createHash('sha256').update(bytes).digest('base64');
|
|
1034
|
+
const read = (/** @type {string} */ file) => {
|
|
1035
|
+
try {
|
|
1036
|
+
return digest(readFileSync(file));
|
|
1037
|
+
} catch {
|
|
1038
|
+
return null;
|
|
1039
|
+
}
|
|
1040
|
+
};
|
|
1041
|
+
/** Remember a file by the bytes vite decoded `code` from, if they are the ones on disk now. */
|
|
1042
|
+
const remember = (/** @type {string} */ file, /** @type {string} */ code) => {
|
|
1043
|
+
try {
|
|
1044
|
+
const bytes = readFileSync(file);
|
|
1045
|
+
if (bytes.toString('utf8') === code) known.set(posix(file), digest(bytes));
|
|
1046
|
+
else known.delete(posix(file));
|
|
1047
|
+
} catch {
|
|
1048
|
+
known.delete(posix(file));
|
|
1049
|
+
}
|
|
1050
|
+
};
|
|
1051
|
+
|
|
1052
|
+
return {
|
|
1053
|
+
name: 'skgo-dev-unchanged-files',
|
|
1054
|
+
apply: 'serve',
|
|
1055
|
+
|
|
1056
|
+
config(config) {
|
|
1057
|
+
const emitted = posix(resolve(config.root ?? process.cwd(), out));
|
|
1058
|
+
return { server: { watch: { ignored: [emitted, `${emitted}/**`] } } };
|
|
1059
|
+
},
|
|
1060
|
+
|
|
1061
|
+
configureServer(server) {
|
|
1062
|
+
const watcher = server.watcher;
|
|
1063
|
+
const emit = watcher.emit;
|
|
1064
|
+
let depth = 0;
|
|
1065
|
+
/** @type {{ file: string, same: boolean } | null} */
|
|
1066
|
+
let verdict = null;
|
|
1067
|
+
/** @type {(file: string) => boolean} */
|
|
1068
|
+
const unchanged = (file) => {
|
|
1069
|
+
const key = posix(file);
|
|
1070
|
+
const now = read(file);
|
|
1071
|
+
const same = now !== null && known.get(key) === now;
|
|
1072
|
+
if (now !== null) known.set(key, now);
|
|
1073
|
+
return same;
|
|
1074
|
+
};
|
|
1075
|
+
// Kit validates templates synchronously from its watcher. An in-place
|
|
1076
|
+
// save can expose truncated bytes; report that validation error but
|
|
1077
|
+
// keep the watcher alive so the completed save can regenerate Kit.
|
|
1078
|
+
const notify = (receiver, event, args, file) => {
|
|
1079
|
+
try {
|
|
1080
|
+
return emit.call(receiver, event, ...args);
|
|
1081
|
+
} catch (error) {
|
|
1082
|
+
const authored = typeof file === 'string' ? relative(server.config.root, file) : null;
|
|
1083
|
+
if (!(error instanceof Error) || authored === null ||
|
|
1084
|
+
!['%sveltekit.head%', '%sveltekit.body%'].some(tag => error.message === `${authored} is missing ${tag}`)) throw error;
|
|
1085
|
+
known.delete(posix(file));
|
|
1086
|
+
server.config.logger.error(error.stack ?? error.message);
|
|
1087
|
+
server.ws.send({ type: 'error', err: { message: error.message, stack: error.stack ?? '', plugin: 'skgo-dev-unchanged-files' } });
|
|
1088
|
+
return false;
|
|
1089
|
+
}
|
|
1090
|
+
};
|
|
1091
|
+
watcher.emit = function (event, ...args) {
|
|
1092
|
+
const all = event === 'all';
|
|
1093
|
+
const kind = all ? args[0] : event;
|
|
1094
|
+
const file = all ? args[1] : args[0];
|
|
1095
|
+
if (typeof file === 'string' && kind === 'add' && !all) {
|
|
1096
|
+
const now = read(file);
|
|
1097
|
+
if (now !== null) known.set(posix(file), now);
|
|
1098
|
+
} else if (typeof file === 'string' && kind === 'change') {
|
|
1099
|
+
if (all) {
|
|
1100
|
+
const reported = verdict && verdict.file === file ? verdict : null;
|
|
1101
|
+
verdict = null;
|
|
1102
|
+
if (reported ? reported.same : unchanged(file)) return false;
|
|
1103
|
+
} else if (depth === 0) {
|
|
1104
|
+
verdict = { file, same: unchanged(file) };
|
|
1105
|
+
if (verdict.same) return false;
|
|
1106
|
+
}
|
|
1107
|
+
}
|
|
1108
|
+
if (all) return notify(this, event, args, file);
|
|
1109
|
+
depth++;
|
|
1110
|
+
try {
|
|
1111
|
+
return notify(this, event, args, file);
|
|
1112
|
+
} finally {
|
|
1113
|
+
depth--;
|
|
1114
|
+
}
|
|
1115
|
+
};
|
|
1116
|
+
},
|
|
1117
|
+
|
|
1118
|
+
transform: {
|
|
1119
|
+
order: 'pre',
|
|
1120
|
+
handler(code, id) {
|
|
1121
|
+
if (!id.includes('?') && !id.startsWith('\0') && isAbsolute(id)) remember(id, code);
|
|
1122
|
+
return null;
|
|
1123
|
+
}
|
|
1124
|
+
}
|
|
1125
|
+
};
|
|
1126
|
+
}
|
|
1127
|
+
|
|
960
1128
|
const OXC_HELPERS = '@oxc-project/runtime/helpers/';
|
|
961
1129
|
|
|
962
1130
|
/**
|
package/skgo-adapter/polyfill.js
CHANGED
|
@@ -25,8 +25,10 @@ globalThis.process = { versions: { webcontainer: 'skgo' } };
|
|
|
25
25
|
// (exports/internal/shared.js), which builds one to reject a location that
|
|
26
26
|
// could not survive an HTTP header — and does it inside a try/catch, so an
|
|
27
27
|
// absent Headers would be reported as an invalid location rather than as a
|
|
28
|
-
// missing global.
|
|
29
|
-
//
|
|
28
|
+
// missing global. It is also what a render's fetch response carries, so it
|
|
29
|
+
// follows the Fetch standard where kit's universal fetch can see it: names are
|
|
30
|
+
// lowercased and iterate sorted, a repeated name is joined with ", ", and
|
|
31
|
+
// Set-Cookie alone is kept as a list (getSetCookie) and iterates once per value.
|
|
30
32
|
if (typeof globalThis.Headers === 'undefined') {
|
|
31
33
|
// RFC 9110 field-name and field-value rules, which is all the real
|
|
32
34
|
// constructor checks that matters here.
|
|
@@ -36,12 +38,14 @@ if (typeof globalThis.Headers === 'undefined') {
|
|
|
36
38
|
globalThis.Headers = class Headers {
|
|
37
39
|
constructor(init) {
|
|
38
40
|
this._ = new Map();
|
|
41
|
+
this._cookies = [];
|
|
39
42
|
if (init instanceof Headers) {
|
|
40
43
|
for (const [k, v] of init._) this._.set(k, v);
|
|
44
|
+
this._cookies = init._cookies.slice();
|
|
41
45
|
} else if (Array.isArray(init)) {
|
|
42
|
-
for (const [k, v] of init) this.
|
|
46
|
+
for (const [k, v] of init) this.append(k, v);
|
|
43
47
|
} else if (init && typeof init === 'object') {
|
|
44
|
-
for (const k of Object.keys(init)) this.
|
|
48
|
+
for (const k of Object.keys(init)) this.append(k, init[k]);
|
|
45
49
|
}
|
|
46
50
|
}
|
|
47
51
|
set(name, value) {
|
|
@@ -49,85 +53,315 @@ if (typeof globalThis.Headers === 'undefined') {
|
|
|
49
53
|
const v = String(value).trim();
|
|
50
54
|
if (!NAME.test(n)) throw new TypeError('Invalid header name: ' + n);
|
|
51
55
|
if (BAD_VALUE.test(v)) throw new TypeError('Invalid header value');
|
|
52
|
-
|
|
56
|
+
const k = n.toLowerCase();
|
|
57
|
+
if (k === 'set-cookie') this._cookies = [v];
|
|
58
|
+
else this._.set(k, v);
|
|
53
59
|
}
|
|
54
60
|
append(name, value) {
|
|
55
|
-
const
|
|
56
|
-
|
|
61
|
+
const n = String(name);
|
|
62
|
+
const v = String(value).trim();
|
|
63
|
+
if (!NAME.test(n)) throw new TypeError('Invalid header name: ' + n);
|
|
64
|
+
if (BAD_VALUE.test(v)) throw new TypeError('Invalid header value');
|
|
65
|
+
const k = n.toLowerCase();
|
|
66
|
+
if (k === 'set-cookie') this._cookies.push(v);
|
|
67
|
+
else this._.set(k, this._.has(k) ? this._.get(k) + ', ' + v : v);
|
|
57
68
|
}
|
|
58
69
|
get(name) {
|
|
59
70
|
const k = String(name).toLowerCase();
|
|
71
|
+
if (k === 'set-cookie') return this._cookies.length ? this._cookies.join(', ') : null;
|
|
60
72
|
return this._.has(k) ? this._.get(k) : null;
|
|
61
73
|
}
|
|
74
|
+
getSetCookie() {
|
|
75
|
+
return this._cookies.slice();
|
|
76
|
+
}
|
|
62
77
|
has(name) {
|
|
63
|
-
|
|
78
|
+
const k = String(name).toLowerCase();
|
|
79
|
+
return k === 'set-cookie' ? this._cookies.length > 0 : this._.has(k);
|
|
64
80
|
}
|
|
65
81
|
delete(name) {
|
|
66
|
-
|
|
82
|
+
const k = String(name).toLowerCase();
|
|
83
|
+
if (k === 'set-cookie') this._cookies = [];
|
|
84
|
+
else this._.delete(k);
|
|
67
85
|
}
|
|
68
86
|
forEach(fn, thisArg) {
|
|
69
|
-
for (const [k, v] of this.
|
|
87
|
+
for (const [k, v] of this.entries()) fn.call(thisArg, v, k, this);
|
|
70
88
|
}
|
|
71
89
|
keys() {
|
|
72
|
-
return this.
|
|
90
|
+
return Array.from(this.entries(), ([k]) => k)[Symbol.iterator]();
|
|
73
91
|
}
|
|
74
92
|
values() {
|
|
75
|
-
return this.
|
|
93
|
+
return Array.from(this.entries(), ([, v]) => v)[Symbol.iterator]();
|
|
76
94
|
}
|
|
77
95
|
entries() {
|
|
78
|
-
|
|
96
|
+
const all = Array.from(this._);
|
|
97
|
+
for (const cookie of this._cookies) all.push(['set-cookie', cookie]);
|
|
98
|
+
// A stable sort keeps the Set-Cookie values in the order they arrived.
|
|
99
|
+
return all.sort(([a], [b]) => a < b ? -1 : a > b ? 1 : 0)[Symbol.iterator]();
|
|
79
100
|
}
|
|
80
101
|
[Symbol.iterator]() {
|
|
81
|
-
return this.
|
|
102
|
+
return this.entries();
|
|
82
103
|
}
|
|
83
104
|
};
|
|
84
105
|
}
|
|
85
106
|
|
|
86
|
-
//
|
|
87
|
-
// `
|
|
88
|
-
//
|
|
89
|
-
//
|
|
90
|
-
//
|
|
91
|
-
//
|
|
92
|
-
|
|
107
|
+
// ReadableStream is what a Response body is on the server too: kit's universal
|
|
108
|
+
// fetch tees `response.body` to record what a load read, and builds a Response
|
|
109
|
+
// over a stream to turn that copy into bytes. This is the part of the standard
|
|
110
|
+
// that path touches — a default reader, tee and cancel — over chunks that are
|
|
111
|
+
// already in memory, because Go hands a render a finished body and the engine
|
|
112
|
+
// holds no socket to stream from.
|
|
113
|
+
if (typeof globalThis.ReadableStream === 'undefined') {
|
|
114
|
+
class ReadableStreamDefaultReader {
|
|
115
|
+
constructor(stream) {
|
|
116
|
+
if (stream._locked) throw new TypeError('ReadableStream is locked');
|
|
117
|
+
stream._locked = true;
|
|
118
|
+
this._stream = stream;
|
|
119
|
+
}
|
|
120
|
+
read() {
|
|
121
|
+
return this._stream._read();
|
|
122
|
+
}
|
|
123
|
+
releaseLock() {
|
|
124
|
+
this._stream._locked = false;
|
|
125
|
+
}
|
|
126
|
+
cancel(reason) {
|
|
127
|
+
return this._stream.cancel(reason, true);
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
globalThis.ReadableStream = class ReadableStream {
|
|
132
|
+
constructor(source = {}) {
|
|
133
|
+
this._source = source;
|
|
134
|
+
this._queue = [];
|
|
135
|
+
this._state = 'readable';
|
|
136
|
+
this._error = undefined;
|
|
137
|
+
this._waiters = [];
|
|
138
|
+
this._locked = false;
|
|
139
|
+
this._disturbed = false;
|
|
140
|
+
const wake = () => this._waiters.splice(0).forEach((resolve) => resolve());
|
|
141
|
+
this._controller = {
|
|
142
|
+
enqueue: (chunk) => {
|
|
143
|
+
if (this._state !== 'readable') throw new TypeError('The stream is not readable');
|
|
144
|
+
this._queue.push(chunk);
|
|
145
|
+
wake();
|
|
146
|
+
},
|
|
147
|
+
close: () => {
|
|
148
|
+
if (this._state === 'readable') this._state = 'closed';
|
|
149
|
+
wake();
|
|
150
|
+
},
|
|
151
|
+
error: (reason) => {
|
|
152
|
+
if (this._state === 'readable') {
|
|
153
|
+
this._state = 'errored';
|
|
154
|
+
this._error = reason;
|
|
155
|
+
this._queue = [];
|
|
156
|
+
}
|
|
157
|
+
wake();
|
|
158
|
+
},
|
|
159
|
+
desiredSize: 1
|
|
160
|
+
};
|
|
161
|
+
this._started = Promise.resolve().then(() => source.start?.(this._controller));
|
|
162
|
+
}
|
|
163
|
+
get locked() {
|
|
164
|
+
return this._locked;
|
|
165
|
+
}
|
|
166
|
+
getReader() {
|
|
167
|
+
return new ReadableStreamDefaultReader(this);
|
|
168
|
+
}
|
|
169
|
+
async _read() {
|
|
170
|
+
this._disturbed = true;
|
|
171
|
+
await this._started;
|
|
172
|
+
for (;;) {
|
|
173
|
+
if (this._queue.length) return { value: this._queue.shift(), done: false };
|
|
174
|
+
if (this._state === 'closed') return { value: undefined, done: true };
|
|
175
|
+
if (this._state === 'errored') throw this._error;
|
|
176
|
+
if (this._source.pull) {
|
|
177
|
+
await this._source.pull(this._controller);
|
|
178
|
+
if (this._queue.length || this._state !== 'readable') continue;
|
|
179
|
+
}
|
|
180
|
+
await new Promise((resolve) => this._waiters.push(resolve));
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
async cancel(reason, viaReader = false) {
|
|
184
|
+
if (this._locked && !viaReader) throw new TypeError('ReadableStream is locked');
|
|
185
|
+
this._disturbed = true;
|
|
186
|
+
if (this._state === 'readable') {
|
|
187
|
+
this._state = 'closed';
|
|
188
|
+
this._queue = [];
|
|
189
|
+
this._waiters.splice(0).forEach((resolve) => resolve());
|
|
190
|
+
await this._source.cancel?.(reason);
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
tee() {
|
|
194
|
+
const reader = this.getReader();
|
|
195
|
+
const branches = [];
|
|
196
|
+
let chain = Promise.resolve();
|
|
197
|
+
const pump = () => (chain = chain.then(async () => {
|
|
198
|
+
const result = await reader.read().catch((error) => ({ error }));
|
|
199
|
+
if (result.error !== undefined) branches.forEach((branch) => branch._controller.error(result.error));
|
|
200
|
+
else if (result.done) branches.forEach((branch) => branch._controller.close());
|
|
201
|
+
else branches.forEach((branch) => branch._state === 'readable' && branch._controller.enqueue(result.value));
|
|
202
|
+
}));
|
|
203
|
+
for (let i = 0; i < 2; i++) branches.push(new ReadableStream({ pull: pump }));
|
|
204
|
+
return branches;
|
|
205
|
+
}
|
|
206
|
+
[Symbol.asyncIterator]() {
|
|
207
|
+
const reader = this.getReader();
|
|
208
|
+
return { next: () => reader.read(), [Symbol.asyncIterator]() { return this; } };
|
|
209
|
+
}
|
|
210
|
+
};
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
// What a body of any of the kinds `Request` and `Response` take comes to as
|
|
214
|
+
// bytes: text, an ArrayBuffer or a view over one, URLSearchParams (which is
|
|
215
|
+
// text), or a ReadableStream of those.
|
|
216
|
+
const body_bytes = async (body) => {
|
|
217
|
+
if (body === null || body === undefined) return new Uint8Array(0);
|
|
218
|
+
if (typeof body === 'string') return new TextEncoder().encode(body);
|
|
219
|
+
if (typeof URLSearchParams !== 'undefined' && body instanceof URLSearchParams) {
|
|
220
|
+
return new TextEncoder().encode(body.toString());
|
|
221
|
+
}
|
|
222
|
+
if (body instanceof ArrayBuffer) return new Uint8Array(body.slice(0));
|
|
223
|
+
if (ArrayBuffer.isView(body)) {
|
|
224
|
+
return new Uint8Array(body.buffer.slice(body.byteOffset, body.byteOffset + body.byteLength));
|
|
225
|
+
}
|
|
226
|
+
if (body instanceof ReadableStream) {
|
|
227
|
+
const chunks = [];
|
|
228
|
+
let length = 0;
|
|
229
|
+
const reader = body.getReader();
|
|
230
|
+
for (;;) {
|
|
231
|
+
const { value, done } = await reader.read();
|
|
232
|
+
if (done) break;
|
|
233
|
+
const bytes = await body_bytes(value);
|
|
234
|
+
chunks.push(bytes);
|
|
235
|
+
length += bytes.length;
|
|
236
|
+
}
|
|
237
|
+
const out = new Uint8Array(length);
|
|
238
|
+
let at = 0;
|
|
239
|
+
for (const chunk of chunks) {
|
|
240
|
+
out.set(chunk, at);
|
|
241
|
+
at += chunk.length;
|
|
242
|
+
}
|
|
243
|
+
return out;
|
|
244
|
+
}
|
|
245
|
+
return new TextEncoder().encode(String(body));
|
|
246
|
+
};
|
|
247
|
+
globalThis.__skgo_body_bytes = body_bytes;
|
|
248
|
+
|
|
249
|
+
// The part of Request and Response that is the same on both: one body, readable
|
|
250
|
+
// once, as text, JSON, bytes or a stream.
|
|
251
|
+
const Body = class {
|
|
252
|
+
get bodyUsed() {
|
|
253
|
+
return this._used || (this._stream !== undefined && this._stream._disturbed);
|
|
254
|
+
}
|
|
255
|
+
get body() {
|
|
256
|
+
if (this._source === null || this._source === undefined) return null;
|
|
257
|
+
if (this._stream === undefined) {
|
|
258
|
+
this._stream = this._source instanceof ReadableStream
|
|
259
|
+
? this._source
|
|
260
|
+
: new ReadableStream({
|
|
261
|
+
start: async (controller) => {
|
|
262
|
+
const bytes = await body_bytes(this._source);
|
|
263
|
+
if (bytes.length) controller.enqueue(bytes);
|
|
264
|
+
controller.close();
|
|
265
|
+
}
|
|
266
|
+
});
|
|
267
|
+
}
|
|
268
|
+
return this._stream;
|
|
269
|
+
}
|
|
270
|
+
async _consume() {
|
|
271
|
+
if (this.bodyUsed) throw new TypeError('Body is unusable: Body has already been read');
|
|
272
|
+
if (this._source === null || this._source === undefined) {
|
|
273
|
+
this._used = true;
|
|
274
|
+
return new Uint8Array(0);
|
|
275
|
+
}
|
|
276
|
+
if (this._stream === undefined && !(this._source instanceof ReadableStream)) {
|
|
277
|
+
this._used = true;
|
|
278
|
+
return body_bytes(this._source);
|
|
279
|
+
}
|
|
280
|
+
const stream = this.body;
|
|
281
|
+
this._used = true;
|
|
282
|
+
return body_bytes(stream);
|
|
283
|
+
}
|
|
284
|
+
async text() {
|
|
285
|
+
return new TextDecoder().decode(await this._consume());
|
|
286
|
+
}
|
|
287
|
+
async json() {
|
|
288
|
+
return JSON.parse(await this.text());
|
|
289
|
+
}
|
|
290
|
+
async arrayBuffer() {
|
|
291
|
+
const bytes = await this._consume();
|
|
292
|
+
return bytes.buffer.slice(bytes.byteOffset, bytes.byteOffset + bytes.byteLength);
|
|
293
|
+
}
|
|
294
|
+
_cloneSource() {
|
|
295
|
+
if (this.bodyUsed) throw new TypeError('Body is unusable: Body has already been read');
|
|
296
|
+
if (this._source === null || this._source === undefined) return null;
|
|
297
|
+
if (this._source instanceof ReadableStream || this._stream !== undefined) {
|
|
298
|
+
const [kept, copy] = this.body.tee();
|
|
299
|
+
this._stream = kept;
|
|
300
|
+
return copy;
|
|
301
|
+
}
|
|
302
|
+
return this._source;
|
|
303
|
+
}
|
|
304
|
+
};
|
|
305
|
+
|
|
306
|
+
// Request and Response back a render's `event.fetch`. Kit's own
|
|
307
|
+
// `normalize_fetch_input` (runtime/server/fetch.js) turns whatever a component
|
|
308
|
+
// passed into a real Request before deciding what to do with it, and the
|
|
309
|
+
// answer a render's fetch gets back has to be a real Response — `await (await
|
|
310
|
+
// event.fetch(...)).json()` is what a page actually writes. Nothing here sends
|
|
311
|
+
// bytes anywhere: building one is bookkeeping, and the one call that leaves
|
|
312
|
+
// the engine is `__skgo_fetch` itself.
|
|
93
313
|
if (typeof globalThis.Request === 'undefined') {
|
|
94
|
-
globalThis.Request = class Request {
|
|
314
|
+
globalThis.Request = class Request extends Body {
|
|
95
315
|
constructor(input, init = {}) {
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
316
|
+
super();
|
|
317
|
+
const from = input instanceof Request ? input : null;
|
|
318
|
+
this.url = from ? from.url : String(input);
|
|
319
|
+
this.method = String(init.method ?? from?.method ?? 'GET').toUpperCase();
|
|
320
|
+
this.headers = new Headers(init.headers ?? from?.headers);
|
|
321
|
+
this.credentials = init.credentials ?? from?.credentials ?? 'same-origin';
|
|
322
|
+
this.mode = init.mode ?? from?.mode ?? 'cors';
|
|
323
|
+
this.cache = init.cache ?? from?.cache ?? 'default';
|
|
324
|
+
this.signal = init.signal ?? from?.signal ?? undefined;
|
|
325
|
+
this._used = false;
|
|
326
|
+
const own = init.body !== undefined && init.body !== null;
|
|
327
|
+
this._source = own ? init.body : from ? from._cloneSource() : null;
|
|
328
|
+
if (this._source !== null && (this.method === 'GET' || this.method === 'HEAD')) {
|
|
329
|
+
throw new TypeError('Request with GET/HEAD method cannot have body.');
|
|
330
|
+
}
|
|
331
|
+
if (own && !this.headers.has('content-type')) {
|
|
332
|
+
if (typeof init.body === 'string') {
|
|
333
|
+
this.headers.set('content-type', 'text/plain;charset=UTF-8');
|
|
334
|
+
} else if (typeof URLSearchParams !== 'undefined' && init.body instanceof URLSearchParams) {
|
|
335
|
+
this.headers.set('content-type', 'application/x-www-form-urlencoded;charset=UTF-8');
|
|
336
|
+
}
|
|
110
337
|
}
|
|
111
338
|
}
|
|
112
|
-
|
|
113
|
-
|
|
339
|
+
clone() {
|
|
340
|
+
return new Request(this);
|
|
341
|
+
}
|
|
114
342
|
};
|
|
115
343
|
}
|
|
116
344
|
|
|
117
345
|
if (typeof globalThis.Response === 'undefined') {
|
|
118
|
-
globalThis.Response = class Response {
|
|
346
|
+
globalThis.Response = class Response extends Body {
|
|
119
347
|
constructor(body, init = {}) {
|
|
120
|
-
|
|
348
|
+
super();
|
|
349
|
+
this._source = body ?? null;
|
|
350
|
+
this._used = false;
|
|
121
351
|
this.status = init.status ?? 200;
|
|
122
352
|
this.statusText = init.statusText ?? '';
|
|
123
|
-
this.headers =
|
|
353
|
+
this.headers = new Headers(init.headers);
|
|
124
354
|
this.ok = this.status >= 200 && this.status < 300;
|
|
355
|
+
this.url = '';
|
|
356
|
+
this.redirected = false;
|
|
357
|
+
this.type = 'default';
|
|
125
358
|
}
|
|
126
|
-
async text() { return this._body; }
|
|
127
|
-
async json() { return JSON.parse(this._body); }
|
|
128
|
-
async arrayBuffer() { return new TextEncoder().encode(this._body).buffer; }
|
|
129
359
|
clone() {
|
|
130
|
-
return new Response(this.
|
|
360
|
+
return new Response(this._cloneSource(), {
|
|
361
|
+
status: this.status,
|
|
362
|
+
statusText: this.statusText,
|
|
363
|
+
headers: this.headers
|
|
364
|
+
});
|
|
131
365
|
}
|
|
132
366
|
};
|
|
133
367
|
}
|
package/skgo-adapter.js
CHANGED
|
@@ -14,7 +14,7 @@ import { createRequire } from 'node:module';
|
|
|
14
14
|
import { tmpdir } from 'node:os';
|
|
15
15
|
import { basename, dirname, join, relative, resolve } from 'node:path';
|
|
16
16
|
import { pathToFileURL } from 'node:url';
|
|
17
|
-
import { gojaDevEnvironment, gojaEnvironment, goEnvironmentValues, nodeTable, SSR_TARGET } from './skgo-adapter/env.js';
|
|
17
|
+
import { gojaDevEnvironment, gojaDevUnchangedFiles, gojaEnvironment, goEnvironmentValues, nodeTable, SSR_TARGET } from './skgo-adapter/env.js';
|
|
18
18
|
import { identity } from './skgo-adapter/identity.js';
|
|
19
19
|
import { checkEndpoints, validateGenerated } from './skgo-adapter/generated.js';
|
|
20
20
|
import { authorizePrerenderedScripts } from './skgo-adapter/prerender-csp.js';
|
|
@@ -188,7 +188,7 @@ export default function skgo({ out = 'build', precompress = true } = {}) {
|
|
|
188
188
|
// compiles the SSR bundle in is also declared in `vite dev`, where Go
|
|
189
189
|
// pulls one transformed module at a time out of it instead. Each plugin
|
|
190
190
|
// states its own `apply`, so only one of them is ever live.
|
|
191
|
-
vite: { plugins: { post: [environment.plugin, goja.plugin, gojaDevEnvironment()] } },
|
|
191
|
+
vite: { plugins: { post: [environment.plugin, goja.plugin, gojaDevEnvironment(), gojaDevUnchangedFiles({ out })] } },
|
|
192
192
|
async adapt(builder) {
|
|
193
193
|
rmSync(out, { force: true, recursive: true });
|
|
194
194
|
|