@skgo/sveltekit-adapter 0.28.2 → 0.32.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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@skgo/sveltekit-adapter",
3
- "version": "0.28.2",
3
+ "version": "0.32.0",
4
4
  "description": "SvelteKit adapter for skgo: builds a frontend that a Go binary renders and answers",
5
5
  "keywords": [
6
6
  "adapter",
@@ -11,6 +11,7 @@ export function protectEnvironmentFiles({ root, kitOut, output, base = '/', toke
11
11
  const files = [
12
12
  resolve(root, '.svelte-kit/skgo-env-values.js'),
13
13
  resolve(root, '.svelte-kit/skgo-env-runtime.json'),
14
+ resolve(root, '.svelte-kit/skgo-dev-supervisor.json'),
14
15
  resolve(root, kitOut, 'generated/build/env/config.js'),
15
16
  resolve(root, kitOut, 'generated/dev/env/config.js'),
16
17
  resolve(root, output, 'env.json')
@@ -29,7 +29,7 @@ import { execFileSync } from 'node:child_process';
29
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
- import { dirname, isAbsolute, join, relative, resolve } from 'node:path';
32
+ import { dirname, isAbsolute, join, posix as posixPath, relative, resolve } from 'node:path';
33
33
  import { fileURLToPath, pathToFileURL } from 'node:url';
34
34
 
35
35
  /**
@@ -1014,8 +1014,8 @@ export function gojaDevEnvironment({ outDir = '.svelte-kit' } = {}) {
1014
1014
  * way. Kit's own dev server has no answer to it: vite turns each report into a
1015
1015
  * hot update or a full page reload, and a reload throws away what a visitor
1016
1016
  * had typed and aborts the documents Go was rendering for them. So the dev
1017
- * server remembers the content it last loaded or was told about, and lets
1018
- * through only the reports whose content differs.
1017
+ * server initializes its baseline from the first load, then advances it when
1018
+ * the watcher reports a change, and lets through reports whose content differs.
1019
1019
  *
1020
1020
  * Content means the file's bytes, not the text vite decoded from them: two
1021
1021
  * different invalid byte sequences decode to the same replacement characters,
@@ -1040,7 +1040,7 @@ export function gojaDevEnvironment({ outDir = '.svelte-kit' } = {}) {
1040
1040
  * @returns {import('vite').Plugin}
1041
1041
  */
1042
1042
  export function gojaDevUnchangedFiles({ out = 'build' } = {}) {
1043
- /** What vite last loaded for a file, or last reported changed: its byte digest. */
1043
+ /** The initial load or last delivered watcher content: its byte digest. */
1044
1044
  const known = new Map();
1045
1045
  const digest = (/** @type {Uint8Array} */ bytes) => createHash('sha256').update(bytes).digest('base64');
1046
1046
  const read = (/** @type {string} */ file) => {
@@ -1050,8 +1050,12 @@ export function gojaDevUnchangedFiles({ out = 'build' } = {}) {
1050
1050
  return null;
1051
1051
  }
1052
1052
  };
1053
- /** Remember a file by the bytes vite decoded `code` from, if they are the ones on disk now. */
1053
+ /** Initialize a file from the bytes vite decoded, without consuming pending watcher changes. */
1054
1054
  const remember = (/** @type {string} */ file, /** @type {string} */ code) => {
1055
+ // A module request can transform new bytes while awaitWriteFinish is
1056
+ // still settling their event. Only watcher delivery advances this baseline
1057
+ // after initialization, or that real change would be suppressed.
1058
+ if (known.has(posix(file))) return;
1055
1059
  try {
1056
1060
  const bytes = readFileSync(file);
1057
1061
  if (bytes.toString('utf8') === code) known.set(posix(file), digest(bytes));
@@ -1264,6 +1268,55 @@ function errorText(e) {
1264
1268
  // Values are validated by Go once before Kit loads its environment declaration.
1265
1269
  // This must be a real file: Kit imports env.ts in a separate Vite server with
1266
1270
  // configFile:false, where this adapter's virtual modules do not exist.
1271
+ // The Go supervisor publishes readiness through Vite's existing HMR protocol.
1272
+ // This is adapter glue: the browser keeps using Vite's own client and overlay.
1273
+ export function goSupervisorReload() {
1274
+ return {
1275
+ name: 'skgo-supervisor-reload',
1276
+ configureServer(server) {
1277
+ const token = randomBytes(32).toString('hex');
1278
+ const file = join(server.config.root, '.svelte-kit/skgo-dev-supervisor.json');
1279
+ // Vite's client imports @vite/env. Import analysis rewrites that to
1280
+ // its installed env.mjs, either under root or through /@fs/. Publish
1281
+ // only this exact bootstrap dependency, never an application prefix.
1282
+ const envFile = app.resolve('vite/dist/client/env.mjs').replaceAll('\\', '/');
1283
+ const root = server.config.root.replaceAll('\\', '/').replace(/\/$/, '');
1284
+ const envURL = envFile.startsWith(root + '/') ? envFile.slice(root.length) : posixPath.join('/@fs/', envFile);
1285
+ const clientFiles = [server.config.base.replace(/\/$/, '') + envURL];
1286
+ mkdirSync(dirname(file), { recursive: true });
1287
+ writeFileSync(file, JSON.stringify({ token, base: server.config.base, clientFiles }), { mode: 0o600 });
1288
+ chmodSync(file, 0o600);
1289
+ let failure;
1290
+ server.ws.on('vite:client:connect', (_, client) => {
1291
+ if (failure) client.send(failure);
1292
+ });
1293
+ server.middlewares.use((req, res, next) => {
1294
+ if ((req.url || '').split('?')[0] !== '/__skgo_dev/supervisor') return next();
1295
+ if (req.method !== 'POST' || req.headers['x-skgo-dev-token'] !== token) {
1296
+ res.statusCode = 403; res.end('Forbidden'); return;
1297
+ }
1298
+ let body = '', size = 0;
1299
+ req.setEncoding('utf8');
1300
+ req.on('data', chunk => {
1301
+ size += Buffer.byteLength(chunk);
1302
+ if (size <= 1048576) body += chunk;
1303
+ });
1304
+ req.on('end', () => {
1305
+ let message;
1306
+ try { message = JSON.parse(body); } catch {}
1307
+ if (size > 1048576 || !message || typeof message.diagnostic !== 'string') {
1308
+ res.statusCode = 400; res.end('Invalid supervisor status'); return;
1309
+ }
1310
+ failure = message.diagnostic ? { type: 'error', err: { message: message.diagnostic, stack: '', plugin: 'skgo' } } : undefined;
1311
+ // Kit also omits path for full-reload: app.html is not the page URL.
1312
+ server.ws.send(failure || { type: 'full-reload' });
1313
+ res.statusCode = 204; res.end();
1314
+ });
1315
+ });
1316
+ }
1317
+ };
1318
+ }
1319
+
1267
1320
  export function goEnvironmentValues(output = 'build') {
1268
1321
  let token = '';
1269
1322
  let kitOut = '.svelte-kit';
package/skgo-adapter.js CHANGED
@@ -21,7 +21,7 @@ import {
21
21
  remoteEndpoint,
22
22
  remoteLoad
23
23
  } from './skgo-adapter/prerender.js';
24
- import { gojaDevEnvironment, gojaDevUnchangedFiles, gojaEnvironment, goEnvironmentValues, nodeTable, SSR_TARGET } from './skgo-adapter/env.js';
24
+ import { gojaDevEnvironment, gojaDevUnchangedFiles, gojaEnvironment, goEnvironmentValues, goSupervisorReload, nodeTable, SSR_TARGET } from './skgo-adapter/env.js';
25
25
  import { prerenderModules } from './skgo-adapter/prerender-modules.js';
26
26
  import { identity } from './skgo-adapter/identity.js';
27
27
  import { checkEndpoints, validateGenerated } from './skgo-adapter/generated.js';
@@ -116,7 +116,7 @@ export default function skgo({ out = 'build', precompress = true, prerenderPacka
116
116
  // compiles the SSR bundle in is also declared in `vite dev`, where Go
117
117
  // pulls one transformed module at a time out of it instead. Each plugin
118
118
  // states its own `apply`, so only one of them is ever live.
119
- vite: { plugins: { pre: [prerenderOwnerPlugin, prerenderModules()], post: [environment.plugin, goja.plugin, gojaDevEnvironment(), gojaDevUnchangedFiles({ out })] } },
119
+ vite: { plugins: { pre: [prerenderOwnerPlugin, prerenderModules()], post: [environment.plugin, goSupervisorReload(), goja.plugin, gojaDevEnvironment(), gojaDevUnchangedFiles({ out })] } },
120
120
  async adapt(builder) {
121
121
  let failure;
122
122
  try {