@vesk/adapter 0.0.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/README.md +21 -0
- package/dist/api-function.d.ts +7 -0
- package/dist/api-function.d.ts.map +1 -0
- package/dist/api-function.js +187 -0
- package/dist/client-bundle.d.ts +19 -0
- package/dist/client-bundle.d.ts.map +1 -0
- package/dist/client-bundle.js +491 -0
- package/dist/dev-server.d.ts +4 -0
- package/dist/dev-server.d.ts.map +1 -0
- package/dist/dev-server.js +358 -0
- package/dist/esbuild-fallback.d.ts +3 -0
- package/dist/esbuild-fallback.d.ts.map +1 -0
- package/dist/esbuild-fallback.js +64 -0
- package/dist/hmr.d.ts +7 -0
- package/dist/hmr.d.ts.map +1 -0
- package/dist/hmr.js +411 -0
- package/dist/image-pipeline.d.ts +3 -0
- package/dist/image-pipeline.d.ts.map +1 -0
- package/dist/image-pipeline.js +120 -0
- package/dist/index.d.ts +4 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +291 -0
- package/dist/manifest.d.ts +3 -0
- package/dist/manifest.d.ts.map +1 -0
- package/dist/manifest.js +47 -0
- package/dist/middleware.d.ts +4 -0
- package/dist/middleware.d.ts.map +1 -0
- package/dist/middleware.js +96 -0
- package/dist/package.json +15 -0
- package/dist/platform-deploy.d.ts +18 -0
- package/dist/platform-deploy.d.ts.map +1 -0
- package/dist/platform-deploy.js +354 -0
- package/dist/platform-handler.d.ts +32 -0
- package/dist/platform-handler.d.ts.map +1 -0
- package/dist/platform-handler.js +211 -0
- package/dist/platform-output.d.ts +30 -0
- package/dist/platform-output.d.ts.map +1 -0
- package/dist/platform-output.js +119 -0
- package/dist/platform.d.ts +17 -0
- package/dist/platform.d.ts.map +1 -0
- package/dist/platform.js +35 -0
- package/dist/prod-server.d.ts +5 -0
- package/dist/prod-server.d.ts.map +1 -0
- package/dist/prod-server.js +429 -0
- package/dist/runtime-bundle.d.ts +2 -0
- package/dist/runtime-bundle.d.ts.map +1 -0
- package/dist/runtime-bundle.js +140 -0
- package/dist/seo-audit.d.ts +3 -0
- package/dist/seo-audit.d.ts.map +1 -0
- package/dist/seo-audit.js +169 -0
- package/dist/ssr-function.d.ts +8 -0
- package/dist/ssr-function.d.ts.map +1 -0
- package/dist/ssr-function.js +415 -0
- package/dist/static.d.ts +8 -0
- package/dist/static.d.ts.map +1 -0
- package/dist/static.js +130 -0
- package/dist/types.d.ts +182 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +1 -0
- package/package.json +54 -0
package/dist/hmr.js
ADDED
|
@@ -0,0 +1,411 @@
|
|
|
1
|
+
import { WebSocketServer } from 'ws';
|
|
2
|
+
import { readFileSync, existsSync, writeFileSync } from 'node:fs';
|
|
3
|
+
import { resolve, dirname } from 'node:path';
|
|
4
|
+
import { fileURLToPath } from 'node:url';
|
|
5
|
+
import { compileClient } from '@vesk/compiler/src/client-codegen';
|
|
6
|
+
import { resolveComponentName } from '@vesk/compiler/src/server-codegen';
|
|
7
|
+
import { resolveErrorFile } from '@vesk/adapter/src/ssr-function';
|
|
8
|
+
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
9
|
+
function findRouteForSource(routeTree, sourceDir) {
|
|
10
|
+
for (const node of routeTree) {
|
|
11
|
+
if (node.sourceDir === sourceDir)
|
|
12
|
+
return node;
|
|
13
|
+
if (node.children) {
|
|
14
|
+
const found = findRouteForSource(node.children, sourceDir);
|
|
15
|
+
if (found)
|
|
16
|
+
return found;
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
return null;
|
|
20
|
+
}
|
|
21
|
+
function collectAncestorLayouts(routeTree, sourceDir, chain = []) {
|
|
22
|
+
for (const node of routeTree) {
|
|
23
|
+
if (node.sourceDir === sourceDir)
|
|
24
|
+
return chain;
|
|
25
|
+
if (node.children) {
|
|
26
|
+
const nextChain = node.layout
|
|
27
|
+
? [...chain, { sourceDir: node.sourceDir, layoutCompName: node.layout }]
|
|
28
|
+
: chain;
|
|
29
|
+
const found = collectAncestorLayouts(node.children, sourceDir, nextChain);
|
|
30
|
+
if (found)
|
|
31
|
+
return found;
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
return null;
|
|
35
|
+
}
|
|
36
|
+
function extractComponentAssignments(code) {
|
|
37
|
+
const assignments = [];
|
|
38
|
+
const startRegex = /__components\["(\w+)"\]\s*=\s*/;
|
|
39
|
+
const lines = code.split('\n');
|
|
40
|
+
let i = 0;
|
|
41
|
+
while (i < lines.length) {
|
|
42
|
+
const m = lines[i].match(startRegex);
|
|
43
|
+
if (m) {
|
|
44
|
+
const name = m[1];
|
|
45
|
+
const startIdx = i;
|
|
46
|
+
let braceDepth = 0;
|
|
47
|
+
for (let j = 0; j < lines[i].length; j++) {
|
|
48
|
+
if (lines[i][j] === '{')
|
|
49
|
+
braceDepth++;
|
|
50
|
+
if (lines[i][j] === '}')
|
|
51
|
+
braceDepth--;
|
|
52
|
+
}
|
|
53
|
+
i++;
|
|
54
|
+
while (i < lines.length && braceDepth > 0) {
|
|
55
|
+
for (let j = 0; j < lines[i].length; j++) {
|
|
56
|
+
if (lines[i][j] === '{')
|
|
57
|
+
braceDepth++;
|
|
58
|
+
if (lines[i][j] === '}')
|
|
59
|
+
braceDepth--;
|
|
60
|
+
}
|
|
61
|
+
i++;
|
|
62
|
+
}
|
|
63
|
+
const fullAssignment = lines.slice(startIdx, i).join('\n');
|
|
64
|
+
assignments.push({ name, raw: fullAssignment });
|
|
65
|
+
}
|
|
66
|
+
else {
|
|
67
|
+
i++;
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
return assignments;
|
|
71
|
+
}
|
|
72
|
+
function extractSourceDir(filename) {
|
|
73
|
+
if (filename === 'page.vsk')
|
|
74
|
+
return '';
|
|
75
|
+
if (filename.endsWith('/page.vsk'))
|
|
76
|
+
return filename.slice(0, -'/page.vsk'.length);
|
|
77
|
+
if (filename === 'layout.vsk')
|
|
78
|
+
return '';
|
|
79
|
+
if (filename.endsWith('/layout.vsk'))
|
|
80
|
+
return filename.slice(0, -'/layout.vsk'.length);
|
|
81
|
+
return null;
|
|
82
|
+
}
|
|
83
|
+
function escapeSource(src) {
|
|
84
|
+
return src.replace(/\\/g, '\\\\').replace(/`/g, '\\`').replace(/\$/g, '\\$');
|
|
85
|
+
}
|
|
86
|
+
function extractCompName(src) {
|
|
87
|
+
return resolveComponentName(src);
|
|
88
|
+
}
|
|
89
|
+
function routeName(segments) {
|
|
90
|
+
const parts = segments.filter(Boolean).map(s => {
|
|
91
|
+
if (s.startsWith(':'))
|
|
92
|
+
return s.slice(1) || 'param';
|
|
93
|
+
return s;
|
|
94
|
+
});
|
|
95
|
+
return parts.join('_') || 'index';
|
|
96
|
+
}
|
|
97
|
+
function buildParamExtraction(node, urlParts) {
|
|
98
|
+
const parts = [];
|
|
99
|
+
let partIdx = Math.max(0, urlParts.length - 1);
|
|
100
|
+
function walk(n) {
|
|
101
|
+
if (n.fullPath === '/') {
|
|
102
|
+
for (const child of (n.children || []))
|
|
103
|
+
walk(child);
|
|
104
|
+
return;
|
|
105
|
+
}
|
|
106
|
+
if (n.isGroup) {
|
|
107
|
+
for (const child of (n.children || []))
|
|
108
|
+
walk(child);
|
|
109
|
+
return;
|
|
110
|
+
}
|
|
111
|
+
if (partIdx >= urlParts.length)
|
|
112
|
+
return;
|
|
113
|
+
if (n.isCatchAll) {
|
|
114
|
+
const paramName = n.path.startsWith(':') ? n.path.slice(1) : 'slug';
|
|
115
|
+
parts.push(`${JSON.stringify(paramName)}: urlParts.slice(${partIdx}).join('/')`);
|
|
116
|
+
partIdx = urlParts.length;
|
|
117
|
+
return;
|
|
118
|
+
}
|
|
119
|
+
if (n.isDynamic) {
|
|
120
|
+
const paramName = n.path.startsWith(':') ? n.path.slice(1) : 'param';
|
|
121
|
+
parts.push(`${JSON.stringify(paramName)}: urlParts[${partIdx}]`);
|
|
122
|
+
partIdx++;
|
|
123
|
+
for (const child of (n.children || []))
|
|
124
|
+
walk(child);
|
|
125
|
+
return;
|
|
126
|
+
}
|
|
127
|
+
if (n.path === urlParts[partIdx]) {
|
|
128
|
+
partIdx++;
|
|
129
|
+
for (const child of (n.children || []))
|
|
130
|
+
walk(child);
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
walk(node);
|
|
134
|
+
return parts;
|
|
135
|
+
}
|
|
136
|
+
function regenerateSsrFunction(routeNode, appDir, outDir, componentMap, options) {
|
|
137
|
+
const ancestorLayouts = options?.ancestorLayouts || [];
|
|
138
|
+
const pagePath = resolve(appDir, routeNode.sourceDir, 'page.vsk');
|
|
139
|
+
const layoutPath = resolve(appDir, routeNode.sourceDir, 'layout.vsk');
|
|
140
|
+
const tailwindPath = resolve(outDir, 'static', '_tailwind.css');
|
|
141
|
+
const globalCssPath = resolve(appDir, '..', 'src', 'global.css');
|
|
142
|
+
const altCssPath = resolve(appDir, '..', 'src', 'app.css');
|
|
143
|
+
const hasGlobalCss = existsSync(globalCssPath) || existsSync(altCssPath);
|
|
144
|
+
const hasTailwind = existsSync(tailwindPath) && readFileSync(tailwindPath, 'utf-8').trim().length > 0;
|
|
145
|
+
const cssUrls = [];
|
|
146
|
+
if (hasTailwind)
|
|
147
|
+
cssUrls.push('/_vesk/static/_tailwind.css');
|
|
148
|
+
if (hasGlobalCss)
|
|
149
|
+
cssUrls.push('/_vesk/static/global.css');
|
|
150
|
+
const cssOption = cssUrls.length > 0 ? `, cssUrls: ${JSON.stringify(cssUrls)}` : '';
|
|
151
|
+
const parts = routeNode.fullPath.split('/').filter(Boolean);
|
|
152
|
+
const name = routeName(parts);
|
|
153
|
+
const funcDir = resolve(outDir, 'server', 'functions');
|
|
154
|
+
const funcPath = resolve(funcDir, `${name}.js`);
|
|
155
|
+
const hasLayout = !!routeNode.layout;
|
|
156
|
+
const hasAncestorLayout = ancestorLayouts.length > 0;
|
|
157
|
+
const pageSrc = readFileSync(pagePath, 'utf-8');
|
|
158
|
+
const pageComp = extractCompName(pageSrc) || 'Page';
|
|
159
|
+
const errorPath = resolveErrorFile(routeNode.sourceDir, appDir);
|
|
160
|
+
const errorSrc = errorPath ? readFileSync(errorPath, 'utf-8') : null;
|
|
161
|
+
const errorComp = errorPath ? (extractCompName(errorSrc) || 'Error') : null;
|
|
162
|
+
const errorVars = errorPath
|
|
163
|
+
? `const _errorSrc = \`${escapeSource(errorSrc)}\`;\nconst _errorComp = ${JSON.stringify(errorComp)};\nconst _errorPath = ${JSON.stringify(errorPath)};\nconst _errorCompiled = (() => { try { setVskHydrate(true); return compileFile(_errorSrc, { sourcePath: _errorPath }); } catch { return null; } finally { setVskHydrate(false); } })();\n`
|
|
164
|
+
: 'const _errorSrc = null;\nconst _errorComp = null;\nconst _errorPath = null;\nconst _errorCompiled = null;\n';
|
|
165
|
+
const clientScriptOption = ', clientScriptUrl: "/_vesk/static/client.js"';
|
|
166
|
+
const dataScriptOption = ', externalDataScript: storeDataScriptGlobal';
|
|
167
|
+
let src = '';
|
|
168
|
+
if (hasLayout) {
|
|
169
|
+
const layoutSrc = readFileSync(layoutPath, 'utf-8');
|
|
170
|
+
const layoutComp = extractCompName(layoutSrc) || 'Layout';
|
|
171
|
+
src = `const _layoutSrc = \`${escapeSource(layoutSrc)}\`;\nconst _pageSrc = \`${escapeSource(pageSrc)}\`;\n`;
|
|
172
|
+
src += `const _layoutComp = ${JSON.stringify(layoutComp)};\nconst _pageComp = ${JSON.stringify(pageComp)};\n`;
|
|
173
|
+
src += `const _layoutPath = ${JSON.stringify(layoutPath)};\nconst _pagePath = ${JSON.stringify(pagePath)};\n`;
|
|
174
|
+
src += `const _layoutCompiled = (() => { try { setVskHydrate(true); return compileFile(_layoutSrc, { sourcePath: _layoutPath }); } catch { return null; } finally { setVskHydrate(false); } })();\n`;
|
|
175
|
+
src += `const _pageCompiled = (() => { try { setVskHydrate(true); return compileFile(_pageSrc, { sourcePath: _pagePath }); } catch { return null; } finally { setVskHydrate(false); } })();\n`;
|
|
176
|
+
src += errorVars;
|
|
177
|
+
}
|
|
178
|
+
else if (hasAncestorLayout) {
|
|
179
|
+
const outerLayout = ancestorLayouts[0];
|
|
180
|
+
const outerLayoutPath = resolve(appDir, outerLayout.sourceDir, 'layout.vsk');
|
|
181
|
+
const outerLayoutSrc = readFileSync(outerLayoutPath, 'utf-8');
|
|
182
|
+
const outerLayoutComp = extractCompName(outerLayoutSrc) || 'Layout';
|
|
183
|
+
src = `const _pageSrc = \`${escapeSource(pageSrc)}\`;\n`;
|
|
184
|
+
src += `const _pageComp = ${JSON.stringify(pageComp)};\n`;
|
|
185
|
+
src += `const _layoutSrc = \`${escapeSource(outerLayoutSrc)}\`;\n`;
|
|
186
|
+
src += `const _layoutComp = ${JSON.stringify(outerLayoutComp)};\n`;
|
|
187
|
+
src += `const _layoutPath = ${JSON.stringify(outerLayoutPath)};\nconst _pagePath = ${JSON.stringify(pagePath)};\n`;
|
|
188
|
+
src += `const _layoutCompiled = (() => { try { setVskHydrate(true); return compileFile(_layoutSrc, { sourcePath: _layoutPath }); } catch { return null; } finally { setVskHydrate(false); } })();\n`;
|
|
189
|
+
src += `const _pageCompiled = (() => { try { setVskHydrate(true); return compileFile(_pageSrc, { sourcePath: _pagePath }); } catch { return null; } finally { setVskHydrate(false); } })();\n`;
|
|
190
|
+
src += errorVars;
|
|
191
|
+
}
|
|
192
|
+
else {
|
|
193
|
+
src = `const _src = \`${escapeSource(pageSrc)}\`;\nconst _comp = ${JSON.stringify(pageComp)};\n`;
|
|
194
|
+
src += `const _srcPath = ${JSON.stringify(pagePath)};\n`;
|
|
195
|
+
src += `const _srcCompiled = (() => { try { setVskHydrate(true); return compileFile(_src, { sourcePath: _srcPath }); } catch { return null; } finally { setVskHydrate(false); } })();\n`;
|
|
196
|
+
src += errorVars;
|
|
197
|
+
}
|
|
198
|
+
const urlParts = routeNode.fullPath.split('/').filter(Boolean);
|
|
199
|
+
const paramExprs = buildParamExtraction(routeNode, urlParts);
|
|
200
|
+
const paramsCode = paramExprs.length > 0 ? `const params = { ${paramExprs.join(', ')} };\n` : 'const params = {};\n';
|
|
201
|
+
let registryCode = '';
|
|
202
|
+
const compRegEntries = [];
|
|
203
|
+
const compMap = componentMap || new Map();
|
|
204
|
+
for (const [compName, compPath] of compMap) {
|
|
205
|
+
const compSrc = readFileSync(compPath, 'utf-8');
|
|
206
|
+
const escapedSrc = escapeSource(compSrc);
|
|
207
|
+
compRegEntries.push(` registry.set(${JSON.stringify(compName)}, async (props, __registry, __vesk) => {\n const _src = \`${escapedSrc}\`;\n const _comp = ${JSON.stringify(compName)};\n const _compiled = (() => { try { setVskHydrate(true); return compileFile(_src, { sourcePath: ${JSON.stringify(compPath)} }); } catch { return null; } finally { setVskHydrate(false); } })();\n const result = await renderPage(_src, _comp, props, __registry, { hydrate: true, cached: _compiled, sourcePath: ${JSON.stringify(compPath)} });\n return result.body;\n })`);
|
|
208
|
+
}
|
|
209
|
+
if (compRegEntries.length > 0) {
|
|
210
|
+
registryCode = `const __componentRegistry = new Map();\n{\n${compRegEntries.join('\n')}\n}\n`;
|
|
211
|
+
}
|
|
212
|
+
else {
|
|
213
|
+
registryCode = 'const __componentRegistry = new Map();\n';
|
|
214
|
+
}
|
|
215
|
+
let renderCode;
|
|
216
|
+
if (hasLayout) {
|
|
217
|
+
renderCode = [
|
|
218
|
+
' let page;',
|
|
219
|
+
' let caughtError = null;',
|
|
220
|
+
' try {',
|
|
221
|
+
' page = await renderPage(_pageSrc, _pageComp, { params }, __componentRegistry, { hydrate: true, cached: _pageCompiled, sourcePath: _pagePath });',
|
|
222
|
+
' } catch (err) {',
|
|
223
|
+
" if (err && (err.name === 'NotFoundError' || err.name === 'Redirect')) throw err;",
|
|
224
|
+
' caughtError = err;',
|
|
225
|
+
" const message = err && typeof err === 'object' && 'message' in err ? String(err.message) : String(err);",
|
|
226
|
+
" const stack = err && typeof err === 'object' && 'stack' in err ? String(err.stack) : '';",
|
|
227
|
+
" page = { body: await __renderErrorBody({ params, statusCode: 500, error: message, stack, url: url.href }), head: '' };",
|
|
228
|
+
' }',
|
|
229
|
+
' const html = await renderFullPage(_layoutSrc, _layoutComp, { params, children: (caughtError ? \'<!--vesk-ssr-error:\' + (caughtError && typeof caughtError === \'object\' && \'message\' in caughtError ? encodeURIComponent(String(caughtError.message)) : \'\') + \'-->\' : \'\') + page.body }, __componentRegistry, { hydrate: true, cached: _layoutCompiled' + cssOption + clientScriptOption + dataScriptOption + ', pageHead: page.head, sourcePath: _layoutPath });',
|
|
230
|
+
" return new Response(html, { headers: { 'Content-Type': 'text/html' }, status: caughtError ? 500 : 200 });",
|
|
231
|
+
].join('\n');
|
|
232
|
+
}
|
|
233
|
+
else if (hasAncestorLayout) {
|
|
234
|
+
renderCode = [
|
|
235
|
+
' let page;',
|
|
236
|
+
' let caughtError = null;',
|
|
237
|
+
' try {',
|
|
238
|
+
' page = await renderPage(_pageSrc, _pageComp, { params }, __componentRegistry, { hydrate: true, cached: _pageCompiled, sourcePath: _pagePath });',
|
|
239
|
+
' } catch (err) {',
|
|
240
|
+
" if (err && (err.name === 'NotFoundError' || err.name === 'Redirect')) throw err;",
|
|
241
|
+
' caughtError = err;',
|
|
242
|
+
" const message = err && typeof err === 'object' && 'message' in err ? String(err.message) : String(err);",
|
|
243
|
+
" const stack = err && typeof err === 'object' && 'stack' in err ? String(err.stack) : '';",
|
|
244
|
+
" page = { body: await __renderErrorBody({ params, statusCode: 500, error: message, stack, url: url.href }), head: '' };",
|
|
245
|
+
' }',
|
|
246
|
+
' const html = await renderFullPage(_layoutSrc, _layoutComp, { params, children: (caughtError ? \'<!--vesk-ssr-error:\' + (caughtError && typeof caughtError === \'object\' && \'message\' in caughtError ? encodeURIComponent(String(caughtError.message)) : \'\') + \'-->\' : \'\') + page.body }, __componentRegistry, { hydrate: true, cached: _layoutCompiled' + cssOption + clientScriptOption + dataScriptOption + ', pageHead: page.head, sourcePath: _layoutPath });',
|
|
247
|
+
" return new Response(html, { headers: { 'Content-Type': 'text/html' }, status: caughtError ? 500 : 200 });",
|
|
248
|
+
].join('\n');
|
|
249
|
+
}
|
|
250
|
+
else {
|
|
251
|
+
renderCode = [
|
|
252
|
+
' let stream;',
|
|
253
|
+
' try {',
|
|
254
|
+
' stream = renderPageStream(_src, _comp, { params }, __componentRegistry, { hydrate: true, cached: _srcCompiled' + cssOption + clientScriptOption + dataScriptOption + ", sourcePath: _srcPath });",
|
|
255
|
+
' } catch (err) {',
|
|
256
|
+
' if (err && (err.name === \'NotFoundError\' || err.name === \'Redirect\')) throw err;',
|
|
257
|
+
' if (!_errorSrc) throw err;',
|
|
258
|
+
" const message = err && typeof err === 'object' && 'message' in err ? String(err.message) : String(err);",
|
|
259
|
+
" const stack = err && typeof err === 'object' && 'stack' in err ? String(err.stack) : '';",
|
|
260
|
+
" const html = await renderFullPage(_errorSrc, _errorComp, { params, statusCode: 500, error: message, stack, url: url.href }, __componentRegistry, { hydrate: true, cached: _errorCompiled" + cssOption + clientScriptOption + dataScriptOption + ', sourcePath: _errorPath });',
|
|
261
|
+
" return new Response(html, { headers: { 'Content-Type': 'text/html' }, status: 500 });",
|
|
262
|
+
' }',
|
|
263
|
+
' return new Response(new ReadableStream({',
|
|
264
|
+
' async start(controller) {',
|
|
265
|
+
' const enc = new TextEncoder();',
|
|
266
|
+
' for await (const chunk of stream) {',
|
|
267
|
+
' controller.enqueue(enc.encode(chunk));',
|
|
268
|
+
' }',
|
|
269
|
+
' controller.close();',
|
|
270
|
+
' },',
|
|
271
|
+
" }), { headers: { 'Content-Type': 'text/html' } });",
|
|
272
|
+
].join('\n');
|
|
273
|
+
}
|
|
274
|
+
const errorBodyFnCode = [
|
|
275
|
+
'async function __renderErrorBody(props) {',
|
|
276
|
+
' if (!_errorSrc) throw props.error || new Error("Internal Server Error");',
|
|
277
|
+
' try {',
|
|
278
|
+
' const result = await renderPage(_errorSrc, _errorComp, props, __componentRegistry, { hydrate: true, cached: _errorCompiled, sourcePath: _errorPath });',
|
|
279
|
+
' return result.body;',
|
|
280
|
+
' } catch {',
|
|
281
|
+
" return '<h1>500 \\u2014 Internal Server Error</h1>';",
|
|
282
|
+
' }',
|
|
283
|
+
'}',
|
|
284
|
+
].join('\n');
|
|
285
|
+
const funcCode = [
|
|
286
|
+
"import { renderFullPage, renderPageStream, renderPage, compileFile, setVskHydrate, storeDataScriptGlobal } from '../runtime.js';",
|
|
287
|
+
'', registryCode, src, '',
|
|
288
|
+
errorBodyFnCode,
|
|
289
|
+
'',
|
|
290
|
+
'export async function handle(request) {',
|
|
291
|
+
' const url = new URL(request.url);',
|
|
292
|
+
" const urlParts = url.pathname.split('/').filter(Boolean);",
|
|
293
|
+
` ${paramsCode}`,
|
|
294
|
+
renderCode,
|
|
295
|
+
'}',
|
|
296
|
+
].join('\n');
|
|
297
|
+
writeFileSync(funcPath, funcCode, 'utf-8');
|
|
298
|
+
}
|
|
299
|
+
export function createHmrServer(httpServer, appDir, devDir, componentMap) {
|
|
300
|
+
const wss = new WebSocketServer({ server: httpServer, path: '/_vesk/hmr' });
|
|
301
|
+
const clients = new Set();
|
|
302
|
+
wss.on('connection', (ws) => {
|
|
303
|
+
clients.add(ws);
|
|
304
|
+
ws.on('close', () => clients.delete(ws));
|
|
305
|
+
ws.on('error', () => clients.delete(ws));
|
|
306
|
+
});
|
|
307
|
+
function broadcast(type, data) {
|
|
308
|
+
const msg = JSON.stringify({ type, ...data });
|
|
309
|
+
for (const ws of clients) {
|
|
310
|
+
try {
|
|
311
|
+
ws.send(msg);
|
|
312
|
+
}
|
|
313
|
+
catch {
|
|
314
|
+
clients.delete(ws);
|
|
315
|
+
}
|
|
316
|
+
}
|
|
317
|
+
}
|
|
318
|
+
async function handleFileChange(filename, doFullBuild, routeTree) {
|
|
319
|
+
if (!filename)
|
|
320
|
+
return;
|
|
321
|
+
broadcast('compiling');
|
|
322
|
+
if (filename.endsWith('.vsk')) {
|
|
323
|
+
const sourceDir = extractSourceDir(filename);
|
|
324
|
+
const fullPath = resolve(appDir, filename);
|
|
325
|
+
if (!existsSync(fullPath))
|
|
326
|
+
return;
|
|
327
|
+
try {
|
|
328
|
+
const start = Date.now();
|
|
329
|
+
const src = readFileSync(fullPath, 'utf-8');
|
|
330
|
+
const code = compileClient(src, null, { forceClient: true });
|
|
331
|
+
const assignments = extractComponentAssignments(code);
|
|
332
|
+
if (assignments.length > 0) {
|
|
333
|
+
const components = {};
|
|
334
|
+
const fnSources = {};
|
|
335
|
+
for (const { name, raw } of assignments) {
|
|
336
|
+
components[name] = true;
|
|
337
|
+
fnSources[name] = raw;
|
|
338
|
+
}
|
|
339
|
+
broadcast('update', {
|
|
340
|
+
components,
|
|
341
|
+
fnSources,
|
|
342
|
+
time: Date.now() - start,
|
|
343
|
+
});
|
|
344
|
+
}
|
|
345
|
+
if (sourceDir !== null) {
|
|
346
|
+
const routeNode = findRouteForSource(routeTree, sourceDir);
|
|
347
|
+
if (routeNode) {
|
|
348
|
+
const ancestorLayouts = collectAncestorLayouts(routeTree, sourceDir);
|
|
349
|
+
regenerateSsrFunction(routeNode, appDir, devDir, componentMap, { ancestorLayouts: ancestorLayouts || [] });
|
|
350
|
+
}
|
|
351
|
+
}
|
|
352
|
+
console.error(`vesk hmr: ${assignments.map(a => a.name).join(', ')} (${Date.now() - start}ms)`);
|
|
353
|
+
}
|
|
354
|
+
catch (e) {
|
|
355
|
+
const message = e instanceof Error ? e.message : String(e);
|
|
356
|
+
broadcast('error', { message, file: filename });
|
|
357
|
+
console.error(`vesk hmr: error — ${message}`);
|
|
358
|
+
}
|
|
359
|
+
return;
|
|
360
|
+
}
|
|
361
|
+
if (filename.includes('/api/') && (filename.endsWith('.ts') || filename.endsWith('.js'))) {
|
|
362
|
+
const start = Date.now();
|
|
363
|
+
try {
|
|
364
|
+
await doFullBuild();
|
|
365
|
+
broadcast('reload', { reason: `API: ${filename}`, time: Date.now() - start });
|
|
366
|
+
}
|
|
367
|
+
catch (e) {
|
|
368
|
+
const message = e instanceof Error ? e.message : String(e);
|
|
369
|
+
broadcast('error', { message, file: filename });
|
|
370
|
+
}
|
|
371
|
+
return;
|
|
372
|
+
}
|
|
373
|
+
if (filename === 'middleware.ts' || filename.endsWith('/middleware.ts')) {
|
|
374
|
+
const start = Date.now();
|
|
375
|
+
try {
|
|
376
|
+
await doFullBuild();
|
|
377
|
+
broadcast('reload', { reason: `Middleware: ${filename}`, time: Date.now() - start });
|
|
378
|
+
console.error(`vesk hmr: middleware ${filename} rebuilt (${Date.now() - start}ms)`);
|
|
379
|
+
}
|
|
380
|
+
catch (e) {
|
|
381
|
+
const message = e instanceof Error ? e.message : String(e);
|
|
382
|
+
broadcast('error', { message, file: filename });
|
|
383
|
+
}
|
|
384
|
+
return;
|
|
385
|
+
}
|
|
386
|
+
if (filename === 'vesk.config.ts' || filename === 'vesk.config.js' ||
|
|
387
|
+
filename === 'tsconfig.json' || filename === 'package.json') {
|
|
388
|
+
const start = Date.now();
|
|
389
|
+
try {
|
|
390
|
+
await doFullBuild();
|
|
391
|
+
broadcast('reload', { reason: `Config: ${filename}`, time: Date.now() - start });
|
|
392
|
+
console.error(`vesk hmr: ${filename} rebuilt (${Date.now() - start}ms)`);
|
|
393
|
+
}
|
|
394
|
+
catch (e) {
|
|
395
|
+
const message = e instanceof Error ? e.message : String(e);
|
|
396
|
+
broadcast('error', { message, file: filename });
|
|
397
|
+
}
|
|
398
|
+
return;
|
|
399
|
+
}
|
|
400
|
+
const start = Date.now();
|
|
401
|
+
try {
|
|
402
|
+
await doFullBuild();
|
|
403
|
+
broadcast('reload', { reason: `${filename} changed`, time: Date.now() - start });
|
|
404
|
+
}
|
|
405
|
+
catch (e) {
|
|
406
|
+
const message = e instanceof Error ? e.message : String(e);
|
|
407
|
+
broadcast('error', { message, file: filename });
|
|
408
|
+
}
|
|
409
|
+
}
|
|
410
|
+
return { broadcast, handleFileChange };
|
|
411
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"image-pipeline.d.ts","sourceRoot":"","sources":["../src/image-pipeline.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAY,WAAW,EAAE,MAAM,yBAAyB,CAAC;AAsFrE,wBAAsB,cAAc,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,WAAW,EAAE,CAAC,CAgD3F"}
|
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
import { mkdirSync, writeFileSync, readFileSync, existsSync, readdirSync, statSync } from 'node:fs';
|
|
2
|
+
import { resolve, extname, dirname } from 'node:path';
|
|
3
|
+
const SUPPORTED = new Set(['.jpg', '.jpeg', '.png', '.webp', '.avif', '.tiff']);
|
|
4
|
+
const OUTPUT_WIDTHS = [640, 768, 1024, 1280, 1536];
|
|
5
|
+
const FORMATS = ['webp', 'avif'];
|
|
6
|
+
let sharpFn = null;
|
|
7
|
+
try {
|
|
8
|
+
const mod = await import('sharp');
|
|
9
|
+
sharpFn = mod.default;
|
|
10
|
+
}
|
|
11
|
+
catch {
|
|
12
|
+
// sharp not available — fall through to copy-only
|
|
13
|
+
}
|
|
14
|
+
async function processImage(srcPath, outDir, baseName) {
|
|
15
|
+
const image = sharpFn ? sharpFn(srcPath) : null;
|
|
16
|
+
if (!image) {
|
|
17
|
+
const original = readFileSync(srcPath);
|
|
18
|
+
for (const w of OUTPUT_WIDTHS) {
|
|
19
|
+
const outputPath = resolve(outDir, `${baseName}-${w}w`);
|
|
20
|
+
mkdirSync(dirname(outputPath), { recursive: true });
|
|
21
|
+
writeFileSync(outputPath, original);
|
|
22
|
+
}
|
|
23
|
+
return [];
|
|
24
|
+
}
|
|
25
|
+
const meta = await image.metadata();
|
|
26
|
+
const originalWidth = meta.width || 1920;
|
|
27
|
+
const generated = [];
|
|
28
|
+
for (const w of OUTPUT_WIDTHS) {
|
|
29
|
+
if (w > originalWidth)
|
|
30
|
+
continue;
|
|
31
|
+
const resized = image.clone().resize({ width: w, withoutEnlargement: true });
|
|
32
|
+
const base = `${baseName}-${w}w`;
|
|
33
|
+
const jpgPath = resolve(outDir, `${base}${extname(srcPath)}`);
|
|
34
|
+
mkdirSync(dirname(jpgPath), { recursive: true });
|
|
35
|
+
await resized.toFile(jpgPath);
|
|
36
|
+
generated.push(jpgPath);
|
|
37
|
+
for (const fmt of FORMATS) {
|
|
38
|
+
const fmtPath = resolve(outDir, `${base}.${fmt}`);
|
|
39
|
+
await resized.toFormat(fmt, { quality: 80 }).toFile(fmtPath);
|
|
40
|
+
generated.push(fmtPath);
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
return generated;
|
|
44
|
+
}
|
|
45
|
+
function collectImageRefs(appDir) {
|
|
46
|
+
const refs = [];
|
|
47
|
+
function walk(dir) {
|
|
48
|
+
let entries;
|
|
49
|
+
try {
|
|
50
|
+
entries = readdirSync(dir);
|
|
51
|
+
}
|
|
52
|
+
catch {
|
|
53
|
+
return;
|
|
54
|
+
}
|
|
55
|
+
for (const entry of entries) {
|
|
56
|
+
const full = resolve(dir, entry);
|
|
57
|
+
const st = statSync(full);
|
|
58
|
+
if (st.isDirectory()) {
|
|
59
|
+
if (entry.startsWith('.'))
|
|
60
|
+
continue;
|
|
61
|
+
walk(full);
|
|
62
|
+
}
|
|
63
|
+
else if (entry === 'page.vsk') {
|
|
64
|
+
const src = readFileSync(full, 'utf-8');
|
|
65
|
+
const imgRegex = /<Image\s+src=["']([^"']+)["']/g;
|
|
66
|
+
let m;
|
|
67
|
+
while ((m = imgRegex.exec(src)) !== null) {
|
|
68
|
+
refs.push({ source: full, src: m[1] });
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
walk(appDir);
|
|
74
|
+
return refs;
|
|
75
|
+
}
|
|
76
|
+
export async function optimizeImages(appDir, outDir) {
|
|
77
|
+
const imageOutDir = resolve(outDir, 'static', 'images');
|
|
78
|
+
mkdirSync(imageOutDir, { recursive: true });
|
|
79
|
+
const refs = collectImageRefs(appDir);
|
|
80
|
+
if (refs.length === 0) {
|
|
81
|
+
console.error('vesk images: no <Image> refs found');
|
|
82
|
+
return [];
|
|
83
|
+
}
|
|
84
|
+
const results = [];
|
|
85
|
+
for (const ref of refs) {
|
|
86
|
+
const possiblePaths = [
|
|
87
|
+
resolve(appDir, ref.src),
|
|
88
|
+
resolve(appDir, '..', 'public', ref.src.replace(/^\//, '')),
|
|
89
|
+
resolve(appDir, '..', 'src', ref.src.replace(/^\//, '')),
|
|
90
|
+
resolve(outDir, 'static', 'public', ref.src.replace(/^\//, '')),
|
|
91
|
+
];
|
|
92
|
+
let srcPath = null;
|
|
93
|
+
for (const p of possiblePaths) {
|
|
94
|
+
if (existsSync(p)) {
|
|
95
|
+
srcPath = p;
|
|
96
|
+
break;
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
if (!srcPath) {
|
|
100
|
+
console.error(`vesk images: not found — ${ref.src} (referenced by ${ref.source})`);
|
|
101
|
+
continue;
|
|
102
|
+
}
|
|
103
|
+
const ext = extname(srcPath).toLowerCase();
|
|
104
|
+
if (!SUPPORTED.has(ext)) {
|
|
105
|
+
console.error(`vesk images: unsupported format — ${ref.src} (${ext})`);
|
|
106
|
+
continue;
|
|
107
|
+
}
|
|
108
|
+
const baseName = ref.src.replace(/^\//, '').replace(extname(ref.src), '');
|
|
109
|
+
const files = await processImage(srcPath, imageOutDir, baseName);
|
|
110
|
+
results.push({ src: ref.src, baseName, files, widths: OUTPUT_WIDTHS });
|
|
111
|
+
console.error(`vesk images: ${ref.src} → ${files.length} variants`);
|
|
112
|
+
}
|
|
113
|
+
if (sharpFn) {
|
|
114
|
+
console.error(`vesk images: sharp pipeline — ${results.length} images processed`);
|
|
115
|
+
}
|
|
116
|
+
else {
|
|
117
|
+
console.error('vesk images: sharp not available — originals copied (install sharp for resizing)');
|
|
118
|
+
}
|
|
119
|
+
return results;
|
|
120
|
+
}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
import type { BuildOptions, BuildResult } from '@vesk/adapter/src/types';
|
|
2
|
+
export declare function build(appDir: string, options?: BuildOptions): Promise<BuildResult | undefined>;
|
|
3
|
+
export { startProdServer } from '@vesk/adapter/src/prod-server';
|
|
4
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAYA,OAAO,KAAK,EACe,YAAY,EAAE,WAAW,EAEnD,MAAM,yBAAyB,CAAC;AAgBjC,wBAAsB,KAAK,CAAC,MAAM,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,YAAY,GAAG,OAAO,CAAC,WAAW,GAAG,SAAS,CAAC,CAwSpG;AAED,OAAO,EAAE,eAAe,EAAE,MAAM,+BAA+B,CAAC"}
|