@skgo/sveltekit-adapter 0.14.0 → 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 +70 -127
- package/skgo-adapter/env.js +173 -6
- package/skgo-adapter/polyfill.js +273 -40
- package/skgo-adapter.js +2 -2
package/package.json
CHANGED
package/skgo-adapter/entry.js
CHANGED
|
@@ -25,6 +25,7 @@ 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
27
|
import { serialize_data } from 'skgo:kit/serialize-data';
|
|
28
|
+
import { create_universal_fetch } from 'skgo:kit/load-data';
|
|
28
29
|
|
|
29
30
|
/**
|
|
30
31
|
* The app's transport hook, installed the way kit installs it
|
|
@@ -77,8 +78,50 @@ function make_state() {
|
|
|
77
78
|
};
|
|
78
79
|
}
|
|
79
80
|
|
|
80
|
-
|
|
81
|
-
|
|
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
|
+
};
|
|
88
|
+
|
|
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
|
+
};
|
|
95
|
+
|
|
96
|
+
// Statuses a real Response cannot carry a body for (runtime/server/constants.js).
|
|
97
|
+
const NULL_BODY = [101, 204, 205, 304];
|
|
98
|
+
|
|
99
|
+
/**
|
|
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.
|
|
104
|
+
*
|
|
105
|
+
* @param {Set<string>} serialized
|
|
106
|
+
*/
|
|
107
|
+
function resolve_options(serialized) {
|
|
108
|
+
return {
|
|
109
|
+
filterSerializedResponseHeaders: (name, value) => serialized.has(name + ':' + value)
|
|
110
|
+
};
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
/**
|
|
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.
|
|
119
|
+
*
|
|
120
|
+
* @param {object} req
|
|
121
|
+
* @param {URL} url
|
|
122
|
+
* @param {Set<string>} serialized
|
|
123
|
+
*/
|
|
124
|
+
function create_fetch(req, url, serialized) {
|
|
82
125
|
return async function (input, init) {
|
|
83
126
|
const request = input instanceof Request ? input : new Request(new URL(input, url).href, init);
|
|
84
127
|
const target = new URL(request.url, url);
|
|
@@ -88,138 +131,35 @@ function create_fetch(req, url) {
|
|
|
88
131
|
method: request.method || 'GET', url: target.href, headers,
|
|
89
132
|
credentials: request.credentials, mode: request.mode
|
|
90
133
|
};
|
|
91
|
-
if (
|
|
92
|
-
|
|
134
|
+
if (request.body !== null) {
|
|
135
|
+
const bytes = new Uint8Array(await request._consume());
|
|
136
|
+
if (bytes.length) envelope.bodyBase64 = bytes_to_base64(bytes);
|
|
137
|
+
}
|
|
93
138
|
const answer = JSON.parse(await globalThis.__skgo_fetch(JSON.stringify(envelope)));
|
|
94
139
|
if (answer.error) throw new TypeError(answer.error);
|
|
95
140
|
const value = answer.response;
|
|
96
|
-
const
|
|
97
|
-
|
|
98
|
-
: value.body ?? '';
|
|
99
|
-
const response = new Response(body, { status: value.status, statusText: value.statusText, headers: value.headers });
|
|
100
|
-
response._serializedHeaders = value.serializedHeaders ?? {};
|
|
101
|
-
return response;
|
|
102
|
-
};
|
|
103
|
-
}
|
|
104
|
-
|
|
105
|
-
/**
|
|
106
|
-
* Kit's universal-load fetch contract (server/page/load_data.js): record bodies
|
|
107
|
-
* when consumed, hash the author's headers/body rather than inherited cookies,
|
|
108
|
-
* and reject access to headers hydration will not receive. The final HTML is
|
|
109
|
-
* emitted by Kit's own serialize_data, including escaping, hashes and TTLs.
|
|
110
|
-
*/
|
|
111
|
-
function create_universal_fetch(event, req, fetched) {
|
|
112
|
-
return async (input, init) => {
|
|
113
|
-
const request_headers = input instanceof Request
|
|
114
|
-
? ([...input.headers].length ? new Headers(input.headers) : undefined)
|
|
115
|
-
: init?.headers;
|
|
116
|
-
const request_body = input instanceof Request ? input._body : init?.body;
|
|
117
|
-
const url = new URL(input instanceof Request ? input.url : input, event.url);
|
|
118
|
-
let response = await event.fetch(input, init);
|
|
119
|
-
if (!same_origin(url, event.url) && (url.protocol === 'http:' || url.protocol === 'https:')) {
|
|
120
|
-
const mode = input instanceof Request ? input.mode : init?.mode ?? 'cors';
|
|
121
|
-
if (mode === 'no-cors') {
|
|
122
|
-
const allowed = response._serializedHeaders;
|
|
123
|
-
response = new Response('', { status: response.status, statusText: response.statusText, headers: response.headers });
|
|
124
|
-
response._serializedHeaders = allowed;
|
|
125
|
-
} else {
|
|
126
|
-
const acao = response.headers.get('access-control-allow-origin');
|
|
127
|
-
if (!acao || (acao !== '*' && acao !== origin_of(event.url))) {
|
|
128
|
-
throw new Error("CORS error: incorrect or missing 'Access-Control-Allow-Origin' header");
|
|
129
|
-
}
|
|
130
|
-
}
|
|
141
|
+
for (const [name, header, allowed] of value.headers ?? []) {
|
|
142
|
+
if (allowed) serialized.add(name + ':' + header);
|
|
131
143
|
}
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
:
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
response_body: body, is_b64, allowed
|
|
142
|
-
});
|
|
143
|
-
};
|
|
144
|
-
const text = async () => {
|
|
145
|
-
const body = await target.text();
|
|
146
|
-
if (body === '' && [101, 204, 205, 304].includes(target.status)) {
|
|
147
|
-
record(undefined, false);
|
|
148
|
-
return undefined;
|
|
149
|
-
}
|
|
150
|
-
record(body, false);
|
|
151
|
-
return body;
|
|
152
|
-
};
|
|
153
|
-
const headers = new Headers(target.headers);
|
|
154
|
-
const get = headers.get.bind(headers);
|
|
155
|
-
if (req.csr) {
|
|
156
|
-
headers.get = (key) => {
|
|
157
|
-
const lower = key.toLowerCase();
|
|
158
|
-
const value = get(lower);
|
|
159
|
-
if (value && !lower.startsWith('x-sveltekit-') && !(lower in allowed)) {
|
|
160
|
-
throw new Error(`Failed to get response header "${lower}" — it must be included by the filterSerializedResponseHeaders option (at ${event.route.id})`);
|
|
161
|
-
}
|
|
162
|
-
return value;
|
|
163
|
-
};
|
|
164
|
-
}
|
|
165
|
-
return new Proxy(target, {
|
|
166
|
-
get(target, key) {
|
|
167
|
-
if (key === 'headers') return headers;
|
|
168
|
-
if (key === 'text') return text;
|
|
169
|
-
if (key === 'json') return async () => { const body = await text(); return body ? JSON.parse(body) : undefined; };
|
|
170
|
-
if (key === 'arrayBuffer') return async () => {
|
|
171
|
-
const buffer = await target.arrayBuffer();
|
|
172
|
-
let binary = '';
|
|
173
|
-
for (const byte of new Uint8Array(buffer)) binary += String.fromCharCode(byte);
|
|
174
|
-
record(btoa(binary), true);
|
|
175
|
-
return buffer;
|
|
176
|
-
};
|
|
177
|
-
if (key === 'clone') return () => wrap(target.clone());
|
|
178
|
-
const value = Reflect.get(target, key, target);
|
|
179
|
-
return typeof value === 'function' ? value.bind(target) : value;
|
|
180
|
-
}
|
|
181
|
-
});
|
|
182
|
-
};
|
|
183
|
-
return wrap(response);
|
|
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
|
+
});
|
|
184
153
|
};
|
|
185
154
|
}
|
|
186
155
|
|
|
187
|
-
|
|
188
|
-
/**
|
|
189
|
-
* Same-origin, spelled out rather than taken from `origin`. The engine's URL is
|
|
190
|
-
* Go's (goja_nodejs, over net/url) and its `origin` is scheme and hostname with
|
|
191
|
-
* the port left out, which would make a page on :8080 and a URL on :9999 look
|
|
192
|
-
* like the same site — and this is the one comparison standing between a
|
|
193
|
-
* cached response and a foreign response. Protocol, hostname and
|
|
194
|
-
* port are each exactly what the standard compares. Go enforces dispatch separately.
|
|
195
|
-
*
|
|
196
|
-
* @param {URL} a
|
|
197
|
-
* @param {URL} b
|
|
198
|
-
*/
|
|
199
|
-
function same_origin(a, b) {
|
|
200
|
-
const port = (url) => url.port || (url.protocol === 'https:' ? '443' : '80');
|
|
201
|
-
return a.protocol === b.protocol && a.hostname === b.hostname && port(a) === port(b);
|
|
202
|
-
}
|
|
203
|
-
|
|
204
|
-
/**
|
|
205
|
-
* What `url.origin` would say if this engine's URL reported it the way the
|
|
206
|
-
* standard does, used for CORS validation.
|
|
207
|
-
*
|
|
208
|
-
* @param {URL} url
|
|
209
|
-
*/
|
|
210
|
-
function origin_of(url) {
|
|
211
|
-
const default_port = (url.protocol === 'http:' && url.port === '80') || (url.protocol === 'https:' && url.port === '443');
|
|
212
|
-
const host = default_port ? url.host.replace(/:(80|443)$/, '') : url.host;
|
|
213
|
-
return host ? url.protocol + '//' + host : 'null';
|
|
214
|
-
}
|
|
215
|
-
|
|
216
156
|
/**
|
|
217
157
|
* A RequestEvent stand-in. run_remote_function spreads it and derives the
|
|
218
158
|
* event a query actually sees, which is where kit makes url, params and
|
|
219
159
|
* route throw. Go owns the real request; the only I/O anything here performs
|
|
220
160
|
* is `fetch`'s in-process call back into Go for one of the app's own routes.
|
|
221
161
|
*/
|
|
222
|
-
function make_event(req, url) {
|
|
162
|
+
function make_event(req, url, serialized) {
|
|
223
163
|
return {
|
|
224
164
|
cookies: {
|
|
225
165
|
get: (name) => (req.cookies ?? {})[name],
|
|
@@ -228,7 +168,7 @@ function make_event(req, url) {
|
|
|
228
168
|
delete: () => {},
|
|
229
169
|
serialize: () => ''
|
|
230
170
|
},
|
|
231
|
-
fetch: create_fetch(req, url),
|
|
171
|
+
fetch: create_fetch(req, url, serialized),
|
|
232
172
|
getClientAddress: () => req.client_address ?? '127.0.0.1',
|
|
233
173
|
locals: {},
|
|
234
174
|
params: req.params ?? {},
|
|
@@ -270,7 +210,7 @@ function node_data(node) {
|
|
|
270
210
|
});
|
|
271
211
|
}
|
|
272
212
|
|
|
273
|
-
async function build_props(req, url, event, state, fetched) {
|
|
213
|
+
async function build_props(req, url, event, state, fetched, serialized) {
|
|
274
214
|
const form = req.form ? devalue.parse(req.form, decoders) : null;
|
|
275
215
|
const page = {
|
|
276
216
|
error: req.error ?? null,
|
|
@@ -313,7 +253,7 @@ async function build_props(req, url, event, state, fetched) {
|
|
|
313
253
|
parent: async () => parent_data,
|
|
314
254
|
depends: () => {},
|
|
315
255
|
untrack: (fn) => fn(),
|
|
316
|
-
fetch: create_universal_fetch(event, req,
|
|
256
|
+
fetch: create_universal_fetch(event, undefined, fetched, req.csr, resolve_options(serialized)),
|
|
317
257
|
setHeaders: () => {
|
|
318
258
|
throw new Error('skgo: universal load setHeaders is not supported during rendering');
|
|
319
259
|
}
|
|
@@ -421,9 +361,10 @@ globalThis.__skgo_render = function (req_json) {
|
|
|
421
361
|
const req = JSON.parse(req_json);
|
|
422
362
|
const url = new URL(req.url);
|
|
423
363
|
const state = make_state();
|
|
424
|
-
const
|
|
364
|
+
const serialized = new Set();
|
|
365
|
+
const event = make_event(req, url, serialized);
|
|
425
366
|
const fetched = [];
|
|
426
|
-
const props_promise = build_props(req, url, event, state, fetched);
|
|
367
|
+
const props_promise = build_props(req, url, event, state, fetched, serialized);
|
|
427
368
|
const promise = props_promise.then((props) => {
|
|
428
369
|
result.status = props.page.status;
|
|
429
370
|
result.error = req.error ?? null;
|
|
@@ -469,7 +410,9 @@ globalThis.__skgo_render = function (req_json) {
|
|
|
469
410
|
(rendered) => {
|
|
470
411
|
result.head = rendered.head;
|
|
471
412
|
result.body = rendered.body;
|
|
472
|
-
result.fetched = fetched
|
|
413
|
+
result.fetched = fetched
|
|
414
|
+
.map((item) => serialize_data(item, resolve_options(serialized).filterSerializedResponseHeaders))
|
|
415
|
+
.join("\n\t\t\t");
|
|
473
416
|
result.done = true;
|
|
474
417
|
},
|
|
475
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.
|
|
@@ -133,6 +133,9 @@ function kitAliases(root) {
|
|
|
133
133
|
'skgo:kit/shared': join(runtime, 'shared.js'),
|
|
134
134
|
'skgo:kit/transport': join(runtime, 'app/internal/transport.js'),
|
|
135
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'),
|
|
136
139
|
'skgo:kit/props': join(runtime, 'props.svelte.js'),
|
|
137
140
|
'skgo:kit/root': join(runtime, 'components/root.svelte'),
|
|
138
141
|
// kit's own `$app/paths` server implementation, which app-paths.js
|
|
@@ -166,8 +169,9 @@ function bare(id) {
|
|
|
166
169
|
return !id.startsWith('.') && !id.startsWith('\0') && !id.startsWith('/') && !isAbsolute(id);
|
|
167
170
|
}
|
|
168
171
|
|
|
169
|
-
|
|
170
|
-
|
|
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/;
|
|
171
175
|
|
|
172
176
|
/**
|
|
173
177
|
* The goja environment: the vite plugin that declares it, and the call that
|
|
@@ -485,7 +489,7 @@ export function nodeTable(root, nodes) {
|
|
|
485
489
|
* @param {string} id
|
|
486
490
|
* @param {{ lowered?: Set<string>, helpers?: string }} options
|
|
487
491
|
*/
|
|
488
|
-
function engineTransform(code, id, { lowered, helpers } = {}) {
|
|
492
|
+
export function engineTransform(code, id, { lowered, helpers } = {}) {
|
|
489
493
|
let out = code;
|
|
490
494
|
// svelte/src/internal/server/crypto.js hides a `node:crypto` import behind
|
|
491
495
|
// a variable so bundlers cannot resolve it. goja rejects the syntax
|
|
@@ -496,7 +500,7 @@ function engineTransform(code, id, { lowered, helpers } = {}) {
|
|
|
496
500
|
'=> Promise.reject(new Error("skgo: no dynamic import in the SSR engine"))'
|
|
497
501
|
);
|
|
498
502
|
}
|
|
499
|
-
if (!
|
|
503
|
+
if (!needsLowering(out, id)) {
|
|
500
504
|
return out === code ? null : { code: out, map: null };
|
|
501
505
|
}
|
|
502
506
|
lowered?.add(id);
|
|
@@ -511,6 +515,30 @@ function engineTransform(code, id, { lowered, helpers } = {}) {
|
|
|
511
515
|
return { code: lower, map: null };
|
|
512
516
|
}
|
|
513
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
|
+
|
|
514
542
|
/**
|
|
515
543
|
* Where oxc's lowering helpers live, resolved the way dev has to resolve them:
|
|
516
544
|
* from vite rather than from the app. Under pnpm `@oxc-project/runtime` is a
|
|
@@ -787,6 +815,13 @@ export function gojaDevEnvironment({ outDir = '.svelte-kit' } = {}) {
|
|
|
787
815
|
// temporary version forever.
|
|
788
816
|
changed.push(file);
|
|
789
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
|
+
|
|
790
825
|
const routeFile = normalize(file).startsWith(normalize(kit.files.routes) + '/');
|
|
791
826
|
if (routeFile && (event === 'add' || event === 'unlink')) manifestDirty = true;
|
|
792
827
|
const name = relative(kit.files.routes, file).split(/[\\/]/).at(-1);
|
|
@@ -958,6 +993,138 @@ export function gojaDevEnvironment({ outDir = '.svelte-kit' } = {}) {
|
|
|
958
993
|
return plugin;
|
|
959
994
|
}
|
|
960
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
|
+
|
|
961
1128
|
const OXC_HELPERS = '@oxc-project/runtime/helpers/';
|
|
962
1129
|
|
|
963
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,8 +38,10 @@ 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
46
|
for (const [k, v] of init) this.append(k, v);
|
|
43
47
|
} else if (init && typeof init === 'object') {
|
|
@@ -49,21 +53,35 @@ 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
87
|
for (const [k, v] of this.entries()) fn.call(thisArg, v, k, this);
|
|
@@ -75,7 +93,10 @@ if (typeof globalThis.Headers === 'undefined') {
|
|
|
75
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
102
|
return this.entries();
|
|
@@ -83,52 +104,264 @@ if (typeof globalThis.Headers === 'undefined') {
|
|
|
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
|
-
|
|
114
|
-
|
|
339
|
+
clone() {
|
|
340
|
+
return new Request(this);
|
|
341
|
+
}
|
|
115
342
|
};
|
|
116
343
|
}
|
|
117
344
|
|
|
118
345
|
if (typeof globalThis.Response === 'undefined') {
|
|
119
|
-
globalThis.Response = class Response {
|
|
346
|
+
globalThis.Response = class Response extends Body {
|
|
120
347
|
constructor(body, init = {}) {
|
|
121
|
-
|
|
348
|
+
super();
|
|
349
|
+
this._source = body ?? null;
|
|
350
|
+
this._used = false;
|
|
122
351
|
this.status = init.status ?? 200;
|
|
123
352
|
this.statusText = init.statusText ?? '';
|
|
124
|
-
this.headers =
|
|
353
|
+
this.headers = new Headers(init.headers);
|
|
125
354
|
this.ok = this.status >= 200 && this.status < 300;
|
|
355
|
+
this.url = '';
|
|
356
|
+
this.redirected = false;
|
|
357
|
+
this.type = 'default';
|
|
126
358
|
}
|
|
127
|
-
async text() { return typeof this._body === 'string' ? this._body : new TextDecoder().decode(this._body); }
|
|
128
|
-
async json() { return JSON.parse(await this.text()); }
|
|
129
|
-
async arrayBuffer() { return (typeof this._body === 'string' ? new TextEncoder().encode(this._body) : new Uint8Array(this._body)).buffer; }
|
|
130
359
|
clone() {
|
|
131
|
-
return new Response(this.
|
|
360
|
+
return new Response(this._cloneSource(), {
|
|
361
|
+
status: this.status,
|
|
362
|
+
statusText: this.statusText,
|
|
363
|
+
headers: this.headers
|
|
364
|
+
});
|
|
132
365
|
}
|
|
133
366
|
};
|
|
134
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
|
|