@skgo/sveltekit-adapter 0.22.0 → 0.22.1
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.22.
|
|
3
|
+
"version": "0.22.1",
|
|
4
4
|
"description": "SvelteKit adapter for skgo: builds a frontend that a Go binary renders and answers",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"adapter",
|
|
@@ -44,6 +44,7 @@
|
|
|
44
44
|
"skgo-adapter/identity.js",
|
|
45
45
|
"skgo-adapter/polyfill.js",
|
|
46
46
|
"skgo-adapter/prerender-csp.js",
|
|
47
|
+
"skgo-adapter/prerender-modules.js",
|
|
47
48
|
"skgo-adapter/prerender.js",
|
|
48
49
|
"skgo-adapter/prerender.d.ts"
|
|
49
50
|
],
|
|
@@ -64,7 +64,7 @@ export function validateGenerated(parsed) {
|
|
|
64
64
|
);
|
|
65
65
|
}
|
|
66
66
|
}
|
|
67
|
-
return { remotes: parsed.remotes, loads: parsed.loads, actions: parsed.actions, endpoints: parsed.endpoints, prerender: parsed.prerender };
|
|
67
|
+
return { remotes: parsed.remotes, loads: parsed.loads, actions: parsed.actions, endpoints: parsed.endpoints, prerender: parsed.prerender, build: parsed.build };
|
|
68
68
|
}
|
|
69
69
|
|
|
70
70
|
/**
|
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
import { readFileSync } from 'node:fs';
|
|
2
|
+
import { createRequire } from 'node:module';
|
|
3
|
+
import { join, relative } from 'node:path';
|
|
4
|
+
import { pathToFileURL } from 'node:url';
|
|
5
|
+
import { validateGenerated } from './generated.js';
|
|
6
|
+
|
|
7
|
+
// Use the parser shipped with the application's Vite, as the goja environment
|
|
8
|
+
// does. Parsing the original TS/JS declarations preserves Kit's native wrappers,
|
|
9
|
+
// actions, types and module identities; only build callback bodies change.
|
|
10
|
+
const app = createRequire(join(process.cwd(), 'package.json'));
|
|
11
|
+
const { parseSync } = await import(pathToFileURL(app.resolve('vite/rolldown/experimental')).href);
|
|
12
|
+
const header = '// Code generated by skgo. DO NOT EDIT.';
|
|
13
|
+
const helper = '@skgo/sveltekit-adapter/prerender';
|
|
14
|
+
|
|
15
|
+
export function prerenderModules() {
|
|
16
|
+
let root;
|
|
17
|
+
let generated;
|
|
18
|
+
return {
|
|
19
|
+
name: 'skgo-prerender-modules',
|
|
20
|
+
apply: 'build',
|
|
21
|
+
applyToEnvironment: (environment) => environment.name === 'ssr',
|
|
22
|
+
configResolved(config) { root = config.root; },
|
|
23
|
+
buildStart() {
|
|
24
|
+
generated = validateGenerated(JSON.parse(readFileSync(join(root, 'skgo.remotes.json'), 'utf8')));
|
|
25
|
+
if (!generated.build || typeof generated.build !== 'object' || Array.isArray(generated.build)) {
|
|
26
|
+
throw new Error('skgo: missing build declarations; run `go generate ./...`.');
|
|
27
|
+
}
|
|
28
|
+
for (const [module, declaration] of Object.entries(generated.build)) {
|
|
29
|
+
if (!module.startsWith('src/') || module.split('/').some((part) => part === '.' || part === '..') || !/\.(ts|js)$/.test(module) || !declaration || typeof declaration !== 'object' || Array.isArray(declaration)) {
|
|
30
|
+
throw new Error(`skgo: malformed build declaration for ${module}; run \`go generate ./...\`.`);
|
|
31
|
+
}
|
|
32
|
+
const fields = Object.keys(declaration);
|
|
33
|
+
if (fields.length !== 1 || !['source', 'remotes', 'endpoint', 'hook'].includes(fields[0]) ||
|
|
34
|
+
('source' in declaration && (typeof declaration.source !== 'string' || !declaration.source)) ||
|
|
35
|
+
('remotes' in declaration && (!Array.isArray(declaration.remotes) || !declaration.remotes.length)) ||
|
|
36
|
+
('endpoint' in declaration && declaration.endpoint !== true) ||
|
|
37
|
+
('hook' in declaration && declaration.hook !== true)) {
|
|
38
|
+
throw new Error(`skgo: malformed build declaration for ${module}; run \`go generate ./...\`.`);
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
for (const module of generated.loads) {
|
|
42
|
+
if (typeof generated.build[module]?.source !== 'string' || !generated.build[module].source) {
|
|
43
|
+
throw new Error(`skgo: missing Go load source for ${module}; run \`go generate ./...\`.`);
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
},
|
|
47
|
+
load(id) {
|
|
48
|
+
const module = relative(root, id).split('\\').join('/');
|
|
49
|
+
if (!/\.(ts|js)$/.test(module)) return;
|
|
50
|
+
const declaration = generated.build[module];
|
|
51
|
+
// Inspect server declarations even without metadata, so a missing
|
|
52
|
+
// entry cannot quietly execute a generated throwing stub at build time.
|
|
53
|
+
if (!declaration && !/(\.remote|\/\+(page|layout)\.server|\/\+server|\/hooks\.server)\.(ts|js)$/.test(module)) return;
|
|
54
|
+
const code = readFileSync(id, 'utf8');
|
|
55
|
+
if (!code.startsWith(header)) {
|
|
56
|
+
if (declaration) throw new Error(`skgo: build declaration for authored module ${module}`);
|
|
57
|
+
return;
|
|
58
|
+
}
|
|
59
|
+
const parsed = parseSync(id, code);
|
|
60
|
+
if (parsed.errors.length) throw new Error(`skgo: cannot parse generated module ${module}`);
|
|
61
|
+
const exports = new Map();
|
|
62
|
+
for (const node of parsed.program.body) {
|
|
63
|
+
if (node.type !== 'ExportNamedDeclaration' || node.exportKind === 'type') continue;
|
|
64
|
+
for (const item of node.declaration?.declarations ?? []) exports.set(item.id.name, item.init);
|
|
65
|
+
}
|
|
66
|
+
const fail = () => { throw new Error(`skgo: build declarations disagree with ${module}; run \`go generate ./...\`.`); };
|
|
67
|
+
const edits = [];
|
|
68
|
+
let imports = '';
|
|
69
|
+
if (exports.has('load')) {
|
|
70
|
+
if (!declaration?.source || !generated.loads.includes(module)) fail();
|
|
71
|
+
const fn = exports.get('load');
|
|
72
|
+
if (fn?.type !== 'ArrowFunctionExpression' || fn.params.length !== 1) fail();
|
|
73
|
+
const event = fn.params[0].name;
|
|
74
|
+
imports += "import { error as $skgoError, redirect as $skgoRedirect } from '@sveltejs/kit';\n";
|
|
75
|
+
edits.push([fn.body.start, fn.body.end, `{
|
|
76
|
+
const bridge = ${event}.platform?.skgoPrerenderLoad;
|
|
77
|
+
if (!bridge) throw new Error('skgo: prerender Go load bridge unavailable');
|
|
78
|
+
const answer = await bridge(${JSON.stringify(module)}, ${JSON.stringify(declaration.source)}, ${event});
|
|
79
|
+
if (answer.redirect) $skgoRedirect(answer.redirect.status, answer.redirect.location);
|
|
80
|
+
if (answer.error) $skgoError(answer.error.status, answer.error.message);
|
|
81
|
+
if (answer.data === undefined) throw new Error('skgo: prerender Go load returned no data');
|
|
82
|
+
return answer.data;
|
|
83
|
+
}`]);
|
|
84
|
+
} else if (declaration?.source) fail();
|
|
85
|
+
if (/\/\+server\.(ts|js)$/.test(module)) {
|
|
86
|
+
if (declaration?.endpoint !== true) fail();
|
|
87
|
+
for (const fn of exports.values()) {
|
|
88
|
+
if (!fn) fail();
|
|
89
|
+
edits.push([fn.start, fn.end, `(event) => {
|
|
90
|
+
const bridge = event.platform?.skgoPrerenderEndpoint;
|
|
91
|
+
if (!bridge) throw new Error('skgo: prerender Go endpoint bridge unavailable');
|
|
92
|
+
return bridge(event);
|
|
93
|
+
}`]);
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
const remotes = declaration?.remotes ?? [];
|
|
97
|
+
if (!Array.isArray(remotes)) fail();
|
|
98
|
+
const prerenders = [...exports].filter(([, fn]) => fn?.type === 'CallExpression' && fn.callee.name === 'prerender');
|
|
99
|
+
if (prerenders.length !== remotes.length) fail();
|
|
100
|
+
if (remotes.length) imports += "import { getRequestEvent as $skgoEvent } from '$app/server';\n";
|
|
101
|
+
if (remotes.some((fn) => fn.inputs)) imports += `import { remoteInputs as $skgoInputs } from '${helper}';\n`;
|
|
102
|
+
const seen = new Set();
|
|
103
|
+
for (const remote of remotes) {
|
|
104
|
+
const call = exports.get(remote.name);
|
|
105
|
+
if (seen.has(remote.name) || typeof remote.argument !== 'boolean' || typeof remote.inputs !== 'boolean' || call?.callee.name !== 'prerender') fail();
|
|
106
|
+
seen.add(remote.name);
|
|
107
|
+
const callback = call.arguments[remote.argument ? 1 : 0];
|
|
108
|
+
if (call.arguments.length !== (remote.argument ? 2 : 1) || callback?.type !== 'ArrowFunctionExpression' || callback.params.length !== (remote.argument ? 1 : 0)) fail();
|
|
109
|
+
const arg = remote.argument ? callback.params[0].name : 'undefined';
|
|
110
|
+
edits.push([callback.body.start, callback.body.end, `{
|
|
111
|
+
const event = $skgoEvent();
|
|
112
|
+
const bridge = event.platform?.skgoPrerenderRemote;
|
|
113
|
+
if (!bridge) throw new Error('skgo: prerender Go remote bridge unavailable');
|
|
114
|
+
return bridge(${JSON.stringify(module)}, ${JSON.stringify(remote.name)}, ${arg}, event);
|
|
115
|
+
}`]);
|
|
116
|
+
if (remote.inputs) edits.push([callback.end, callback.end, `, { inputs: () => $skgoInputs(${JSON.stringify(module)}, ${JSON.stringify(remote.name)}) }`]);
|
|
117
|
+
}
|
|
118
|
+
if (/\/hooks\.server\.(ts|js)$/.test(module)) {
|
|
119
|
+
if (declaration?.hook !== true || exports.has('handle') || exports.has('handleFetch')) fail();
|
|
120
|
+
return `import { requestHandle, requestFetch } from '${helper}';
|
|
121
|
+
${code}
|
|
122
|
+
export const handle = ({ event, resolve }) => requestHandle(event, resolve);
|
|
123
|
+
export const handleFetch = (input) => requestFetch(input);
|
|
124
|
+
`;
|
|
125
|
+
}
|
|
126
|
+
if (!edits.length) return;
|
|
127
|
+
let result = code;
|
|
128
|
+
for (const [start, end, replacement] of edits.sort((a, b) => b[0] - a[0])) {
|
|
129
|
+
result = result.slice(0, start) + replacement + result.slice(end);
|
|
130
|
+
}
|
|
131
|
+
return imports + result;
|
|
132
|
+
}
|
|
133
|
+
};
|
|
134
|
+
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
/** Internal bridge used by
|
|
1
|
+
/** Internal bridge used by adapter build modules. */
|
|
2
2
|
export function remoteInputs<Input = unknown>(module: string, name: string): Promise<Input[]>;
|
|
3
3
|
|
|
4
|
-
/** Build-only logical request lifecycle used by
|
|
4
|
+
/** Build-only logical request lifecycle used by adapter build hooks. */
|
|
5
5
|
export function requestHandle(event: import('@sveltejs/kit').RequestEvent, resolve: (event: import('@sveltejs/kit').RequestEvent, options?: import('@sveltejs/kit/hooks').ResolveOptions) => Promise<Response>): Promise<Response>;
|
|
6
6
|
export function requestFetch(input: Parameters<import('@sveltejs/kit/hooks').HandleFetch>[0]): Promise<Response>;
|
package/skgo-adapter.js
CHANGED
|
@@ -22,6 +22,7 @@ import {
|
|
|
22
22
|
remoteLoad
|
|
23
23
|
} from './skgo-adapter/prerender.js';
|
|
24
24
|
import { gojaDevEnvironment, gojaDevUnchangedFiles, gojaEnvironment, goEnvironmentValues, nodeTable, SSR_TARGET } from './skgo-adapter/env.js';
|
|
25
|
+
import { prerenderModules } from './skgo-adapter/prerender-modules.js';
|
|
25
26
|
import { identity } from './skgo-adapter/identity.js';
|
|
26
27
|
import { checkEndpoints, validateGenerated } from './skgo-adapter/generated.js';
|
|
27
28
|
import { authorizePrerenderedScripts } from './skgo-adapter/prerender-csp.js';
|
|
@@ -115,7 +116,7 @@ export default function skgo({ out = 'build', precompress = true, prerenderPacka
|
|
|
115
116
|
// compiles the SSR bundle in is also declared in `vite dev`, where Go
|
|
116
117
|
// pulls one transformed module at a time out of it instead. Each plugin
|
|
117
118
|
// states its own `apply`, so only one of them is ever live.
|
|
118
|
-
vite: { plugins: { pre: [prerenderOwnerPlugin], post: [environment.plugin, goja.plugin, gojaDevEnvironment(), gojaDevUnchangedFiles({ out })] } },
|
|
119
|
+
vite: { plugins: { pre: [prerenderOwnerPlugin, prerenderModules()], post: [environment.plugin, goja.plugin, gojaDevEnvironment(), gojaDevUnchangedFiles({ out })] } },
|
|
119
120
|
async adapt(builder) {
|
|
120
121
|
let failure;
|
|
121
122
|
try {
|