@vesk/compiler 0.1.14 → 0.2.9
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/dist/api-routes.d.ts +6 -1
- package/dist/api-routes.d.ts.map +1 -1
- package/dist/api-routes.js +52 -7
- package/dist/client-codegen.d.ts +15 -0
- package/dist/client-codegen.d.ts.map +1 -1
- package/dist/client-codegen.js +84 -11
- package/dist/config.d.ts +2 -0
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js +25 -0
- package/dist/errors.d.ts.map +1 -1
- package/dist/errors.js +33 -4
- package/dist/ir-generator.d.ts.map +1 -1
- package/dist/ir-generator.js +208 -9
- package/dist/md-inline.d.ts +17 -0
- package/dist/md-inline.d.ts.map +1 -0
- package/dist/md-inline.js +149 -0
- package/dist/middleware.d.ts.map +1 -1
- package/dist/middleware.js +42 -7
- package/dist/module-imports.d.ts +51 -0
- package/dist/module-imports.d.ts.map +1 -0
- package/dist/module-imports.js +715 -0
- package/dist/router.d.ts +2 -0
- package/dist/router.d.ts.map +1 -1
- package/dist/router.js +48 -3
- package/dist/server-codegen.d.ts +1 -1
- package/dist/server-codegen.d.ts.map +1 -1
- package/dist/server-codegen.js +1 -1
- package/dist/server-jsgen.d.ts.map +1 -1
- package/dist/server-jsgen.js +9 -4
- package/dist/server-render.d.ts.map +1 -1
- package/dist/server-render.js +30 -10
- package/dist/server-utils.d.ts +31 -0
- package/dist/server-utils.d.ts.map +1 -1
- package/dist/server-utils.js +478 -52
- package/dist/tokens.d.ts.map +1 -1
- package/dist/tokens.js +24 -1
- package/dist/typecheck.d.ts +2 -2
- package/dist/typecheck.d.ts.map +1 -1
- package/dist/typecheck.js +71 -27
- package/dist/types.d.ts +4 -129
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js +1 -0
- package/dist/vesk-plugin.d.ts +3 -0
- package/dist/vesk-plugin.d.ts.map +1 -1
- package/dist/vesk-plugin.js +518 -1
- package/dist/vsk-imports.d.ts.map +1 -1
- package/dist/vsk-imports.js +3 -1
- package/dist/vsk-tsx.d.ts.map +1 -1
- package/dist/vsk-tsx.js +27 -5
- package/package.json +2 -1
package/dist/api-routes.d.ts
CHANGED
|
@@ -22,9 +22,14 @@ export type BuildWebRequestInput = {
|
|
|
22
22
|
* `json()`/`text()` are patched accordingly. A `cookies` getter is attached
|
|
23
23
|
* (NextRequest-compatible).
|
|
24
24
|
*/
|
|
25
|
-
export declare function buildWebRequest(nodeReq: BuildWebRequestInput, url: string, body?: BodyInit | null
|
|
25
|
+
export declare function buildWebRequest(nodeReq: BuildWebRequestInput, url: string, body?: BodyInit | null, options?: {
|
|
26
|
+
maxBodyBytes?: number;
|
|
27
|
+
}): Request & {
|
|
26
28
|
cookies: Record<string, string>;
|
|
27
29
|
query: Record<string, string>;
|
|
28
30
|
};
|
|
31
|
+
export declare function tooLargeError(max: number): Error & {
|
|
32
|
+
status: number;
|
|
33
|
+
};
|
|
29
34
|
export declare function executeApiRoute(filePath: string, method: string, request: Request, params?: Record<string, string>, locals?: Record<string, unknown>, devCache?: DevCache): Promise<Response | null>;
|
|
30
35
|
//# sourceMappingURL=api-routes.d.ts.map
|
package/dist/api-routes.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"api-routes.d.ts","sourceRoot":"","sources":["../src/api-routes.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,0BAA0B,CAAC;
|
|
1
|
+
{"version":3,"file":"api-routes.d.ts","sourceRoot":"","sources":["../src/api-routes.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,0BAA0B,CAAC;AAW7D,wBAAgB,aAAa,CAAC,MAAM,EAAE,MAAM,GAAG,YAAY,EAAE,CAG5D;AA+ED,wBAAgB,WAAW,CAAC,IAAI,EAAE,YAAY,EAAE,EAAE,QAAQ,EAAE,MAAM,GAAG;IAAE,IAAI,EAAE,YAAY,CAAC;IAAC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAA;CAAE,GAAG,IAAI,CA0DjI;AAED,wBAAgB,YAAY,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAWhE;AAED,MAAM,WAAW,QAAS,SAAQ,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC;CAAG;AAExD,MAAM,MAAM,oBAAoB,GAAG;IACjC,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,EAAE,GAAG,MAAM,GAAG,SAAS,CAAC,CAAC;IACjE,MAAM,CAAC,EAAE;QAAE,aAAa,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC;IACpC,CAAC,MAAM,CAAC,aAAa,CAAC,IAAI,qBAAqB,CAAC,MAAM,GAAG,UAAU,GAAG,MAAM,CAAC,CAAC;CAC/E,CAAC;AAEF;;;;;;GAMG;AACH,wBAAgB,eAAe,CAC7B,OAAO,EAAE,oBAAoB,EAC7B,GAAG,EAAE,MAAM,EACX,IAAI,CAAC,EAAE,QAAQ,GAAG,IAAI,EACtB,OAAO,CAAC,EAAE;IAAE,YAAY,CAAC,EAAE,MAAM,CAAA;CAAE,GAClC,OAAO,GAAG;IAAE,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAAC,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAA;CAAE,CA4C9E;AAED,wBAAgB,aAAa,CAAC,GAAG,EAAE,MAAM,GAAG,KAAK,GAAG;IAAE,MAAM,EAAE,MAAM,CAAA;CAAE,CAIrE;AAED,wBAAsB,eAAe,CACnC,QAAQ,EAAE,MAAM,EAChB,MAAM,EAAE,MAAM,EACd,OAAO,EAAE,OAAO,EAChB,MAAM,GAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAM,EACnC,MAAM,GAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAM,EACpC,QAAQ,CAAC,EAAE,QAAQ,GAClB,OAAO,CAAC,QAAQ,GAAG,IAAI,CAAC,CA0J1B"}
|
package/dist/api-routes.js
CHANGED
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
import { readdirSync, statSync, existsSync } from 'fs';
|
|
2
2
|
import { join } from 'path';
|
|
3
|
+
import { collapseSlashes } from '@vesk/compiler/src/router';
|
|
4
|
+
import { assertSameOrigin, DEFAULT_MAX_BODY_BYTES } from '@vesk/compiler/src/server-utils';
|
|
3
5
|
function basename(p) {
|
|
4
6
|
const idx = p.lastIndexOf('/');
|
|
5
7
|
return idx === -1 ? p : p.slice(idx + 1);
|
|
@@ -69,7 +71,7 @@ function scanApiDir(rootDir, dir, parentPath) {
|
|
|
69
71
|
: (parentPath || '/');
|
|
70
72
|
const node = {
|
|
71
73
|
path: seg,
|
|
72
|
-
fullPath: fullPath
|
|
74
|
+
fullPath: collapseSlashes(fullPath) || '/',
|
|
73
75
|
isDynamic,
|
|
74
76
|
isCatchAll,
|
|
75
77
|
filePath: hasRoute && routeFileName ? join(dir, routeFileName) : null,
|
|
@@ -95,7 +97,14 @@ function scanApiDir(rootDir, dir, parentPath) {
|
|
|
95
97
|
return nodes;
|
|
96
98
|
}
|
|
97
99
|
export function matchApiUrl(tree, pathname) {
|
|
98
|
-
|
|
100
|
+
let normalized = pathname.split('?')[0].split('#')[0];
|
|
101
|
+
// strip a leading /api segment (exact or followed by /) without regex
|
|
102
|
+
if (normalized === '/api') {
|
|
103
|
+
normalized = '/';
|
|
104
|
+
}
|
|
105
|
+
else if (normalized.startsWith('/api/') && !normalized.startsWith('//')) {
|
|
106
|
+
normalized = normalized.slice(4);
|
|
107
|
+
}
|
|
99
108
|
const parts = normalized.split('/').filter(Boolean);
|
|
100
109
|
const params = {};
|
|
101
110
|
function matchNodes(nodes, partIndex) {
|
|
@@ -169,7 +178,7 @@ export function parseCookies(str) {
|
|
|
169
178
|
* `json()`/`text()` are patched accordingly. A `cookies` getter is attached
|
|
170
179
|
* (NextRequest-compatible).
|
|
171
180
|
*/
|
|
172
|
-
export function buildWebRequest(nodeReq, url, body) {
|
|
181
|
+
export function buildWebRequest(nodeReq, url, body, options) {
|
|
173
182
|
const parsedUrl = new URL(url, `http://${nodeReq.headers?.host || 'localhost'}`);
|
|
174
183
|
const method = nodeReq.method || 'GET';
|
|
175
184
|
const headers = {};
|
|
@@ -183,8 +192,15 @@ export function buildWebRequest(nodeReq, url, body) {
|
|
|
183
192
|
if (_bodyBuffer)
|
|
184
193
|
return _bodyBuffer;
|
|
185
194
|
const chunks = [];
|
|
186
|
-
|
|
187
|
-
|
|
195
|
+
let total = 0;
|
|
196
|
+
const max = options?.maxBodyBytes ?? DEFAULT_MAX_BODY_BYTES;
|
|
197
|
+
for await (const chunk of nodeReq) {
|
|
198
|
+
const bytes = typeof chunk === 'string' ? Buffer.from(chunk) : chunk;
|
|
199
|
+
total += bytes.byteLength;
|
|
200
|
+
if (total > max)
|
|
201
|
+
throw tooLargeError(max);
|
|
202
|
+
chunks.push(bytes);
|
|
203
|
+
}
|
|
188
204
|
_bodyBuffer = Buffer.concat(chunks);
|
|
189
205
|
return _bodyBuffer;
|
|
190
206
|
};
|
|
@@ -210,6 +226,11 @@ export function buildWebRequest(nodeReq, url, body) {
|
|
|
210
226
|
Object.defineProperty(webRequest, 'query', { get: () => query, enumerable: true });
|
|
211
227
|
return webRequest;
|
|
212
228
|
}
|
|
229
|
+
export function tooLargeError(max) {
|
|
230
|
+
const err = new Error(`Request body exceeds limit (${max} bytes)`);
|
|
231
|
+
err.status = 413;
|
|
232
|
+
return err;
|
|
233
|
+
}
|
|
213
234
|
export async function executeApiRoute(filePath, method, request, params = {}, locals = {}, devCache) {
|
|
214
235
|
let mod;
|
|
215
236
|
try {
|
|
@@ -224,11 +245,33 @@ export async function executeApiRoute(filePath, method, request, params = {}, lo
|
|
|
224
245
|
}
|
|
225
246
|
}
|
|
226
247
|
catch (e) {
|
|
227
|
-
|
|
248
|
+
// details only in dev (devCache present); prod gets a generic message
|
|
249
|
+
const body = devCache
|
|
250
|
+
? { error: 'Failed to load route module', details: e.message }
|
|
251
|
+
: { error: 'Failed to load route module' };
|
|
252
|
+
return new Response(JSON.stringify(body), {
|
|
228
253
|
status: 500, headers: { 'Content-Type': 'application/json' },
|
|
229
254
|
});
|
|
230
255
|
}
|
|
231
256
|
const routeConfig = (mod.config || {});
|
|
257
|
+
// Default CSRF defense for mutating API calls: same-origin check unless the
|
|
258
|
+
// route opts out via `config.csrf = false`.
|
|
259
|
+
{
|
|
260
|
+
const methodUpper = method.toUpperCase();
|
|
261
|
+
if (routeConfig.csrf !== false && methodUpper !== 'GET' && methodUpper !== 'HEAD' && methodUpper !== 'OPTIONS') {
|
|
262
|
+
try {
|
|
263
|
+
assertSameOrigin({
|
|
264
|
+
method: methodUpper,
|
|
265
|
+
headers: Object.fromEntries([...request.headers.entries()].map(([k, v]) => [k, Array.isArray(v) ? v.join(', ') : v])),
|
|
266
|
+
});
|
|
267
|
+
}
|
|
268
|
+
catch {
|
|
269
|
+
return new Response(JSON.stringify({ error: 'Cross-origin request blocked' }), {
|
|
270
|
+
status: 403, headers: { 'Content-Type': 'application/json' },
|
|
271
|
+
});
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
}
|
|
232
275
|
const handler = mod[method];
|
|
233
276
|
if (!handler) {
|
|
234
277
|
if (method === 'OPTIONS') {
|
|
@@ -326,7 +369,9 @@ export async function executeApiRoute(filePath, method, request, params = {}, lo
|
|
|
326
369
|
status: 404, headers: { 'Content-Type': 'application/json' },
|
|
327
370
|
});
|
|
328
371
|
}
|
|
329
|
-
|
|
372
|
+
// never leak internal error messages in production responses
|
|
373
|
+
const body = devCache ? { error: err.message } : { error: 'Internal Server Error' };
|
|
374
|
+
return new Response(JSON.stringify(body), {
|
|
330
375
|
status: 500, headers: { 'Content-Type': 'application/json' },
|
|
331
376
|
});
|
|
332
377
|
}
|
package/dist/client-codegen.d.ts
CHANGED
|
@@ -14,6 +14,21 @@ export declare function compileClient(source: string, _componentName: string | n
|
|
|
14
14
|
forceClient?: boolean;
|
|
15
15
|
hydrate?: boolean;
|
|
16
16
|
includeTopLevel?: boolean;
|
|
17
|
+
sourcePath?: string;
|
|
18
|
+
mdRoots?: string[];
|
|
17
19
|
}): string;
|
|
20
|
+
/**
|
|
21
|
+
* Compiles a component source in BOTH client modes (plain + hydrate)
|
|
22
|
+
* sharing a single parse/IR pass. Used by the dev-server hot path, where
|
|
23
|
+
* every edit otherwise pays the full acorn+TS parse twice. Also returns
|
|
24
|
+
* the resolved component name using the exact selection order of
|
|
25
|
+
* `resolveComponentName`, so callers don't need a second full parse just
|
|
26
|
+
* to learn the name.
|
|
27
|
+
*/
|
|
28
|
+
export declare function compileClientBoth(source: string, _componentName: string | null): {
|
|
29
|
+
comp: string;
|
|
30
|
+
hyd: string;
|
|
31
|
+
name: string | null;
|
|
32
|
+
};
|
|
18
33
|
export { compileClient as compile, isStaticIR };
|
|
19
34
|
//# sourceMappingURL=client-codegen.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"client-codegen.d.ts","sourceRoot":"","sources":["../src/client-codegen.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"client-codegen.d.ts","sourceRoot":"","sources":["../src/client-codegen.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,MAAM,EAAuB,MAAM,uBAAuB,CAAC;AACzE,OAAO,EAGL,cAAc,EAQd,gBAAgB,EAOjB,MAAM,uBAAuB,CAAC;AAC/B,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,uBAAuB,CAAC;AAwCxD,MAAM,WAAW,WAAW;IAC1B,QAAQ,EAAE,MAAM,CAAC;IACjB,IAAI,EAAE,SAAS,GAAG,MAAM,CAAC;CAC1B;AAED,wBAAgB,qBAAqB,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAI1D;AAoBD,wBAAgB,gBAAgB,CAAC,MAAM,EAAE,UAAU,GAAG,gBAAgB,GAAG,cAAc,EAAE,OAAO,EAAE,GAAG,CAAC,MAAM,EAAE,WAAW,CAAC,GAAG,MAAM,CAwGlI;AAID,wBAAgB,mBAAmB,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG,GAAG,CAAC,MAAM,EAAE,WAAW,CAAC,CAU5E;AAqjCD,iBAAS,UAAU,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG,OAAO,CAe3C;AA2HD,wBAAgB,UAAU,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,CAO9C;AAoED,wBAAgB,aAAa,CAAC,MAAM,EAAE,MAAM,EAAE,cAAc,EAAE,MAAM,GAAG,IAAI,EAAE,OAAO,GAAE;IAAE,WAAW,CAAC,EAAE,OAAO,CAAC;IAAC,OAAO,CAAC,EAAE,OAAO,CAAC;IAAC,eAAe,CAAC,EAAE,OAAO,CAAC;IAAC,UAAU,CAAC,EAAE,MAAM,CAAC;IAAC,OAAO,CAAC,EAAE,MAAM,EAAE,CAAA;CAAO,GAAG,MAAM,CAOnN;AAED;;;;;;;GAOG;AACH,wBAAgB,iBAAiB,CAAC,MAAM,EAAE,MAAM,EAAE,cAAc,EAAE,MAAM,GAAG,IAAI,GAAG;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,GAAG,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,GAAG,IAAI,CAAA;CAAE,CAqBnI;AAED,OAAO,EAAE,aAAa,IAAI,OAAO,EAAE,UAAU,EAAE,CAAC"}
|
package/dist/client-codegen.js
CHANGED
|
@@ -1,12 +1,15 @@
|
|
|
1
1
|
import { walk } from 'zimmerframe';
|
|
2
2
|
import { print } from 'esrap';
|
|
3
3
|
import ts from 'esrap/languages/ts';
|
|
4
|
+
import tsx from 'esrap/languages/tsx';
|
|
4
5
|
import { StaticNode, TextNode, DynamicBinding, OpaqueDynamicRegion, MapRegion, WhileLoop, SwitchBlock, TryCatch, ForLoop, TrackDecl, RuntimeStatement, ComponentRef, ComponentCall, ServerBlock, ClientBlock, HeadBlock, SlotNode, } from '@vesk/compiler/src/ir';
|
|
5
6
|
import { parse } from '@vesk/compiler/src/parser';
|
|
6
7
|
import { generateIR } from '@vesk/compiler/src/ir-generator';
|
|
7
8
|
import { transformTopLevelForActions } from '@vesk/compiler/src/actions';
|
|
8
9
|
import { extractRuntimeNames } from '@vesk/compiler/src/server-utils';
|
|
10
|
+
import { importBindingPairs } from '@vesk/compiler/src/module-imports';
|
|
9
11
|
import { stripTrackGeneric } from '@vesk/compiler/src/scan';
|
|
12
|
+
import { inlineMdImportsFrom } from '@vesk/compiler/src/md-inline';
|
|
10
13
|
import { stripTsTypes, hasTsSyntax } from '@vesk/compiler/src/strip-ts';
|
|
11
14
|
function memberExpr(object, property) {
|
|
12
15
|
return {
|
|
@@ -44,6 +47,28 @@ export function semicolonizeStatement(code) {
|
|
|
44
47
|
return code;
|
|
45
48
|
return code + ';';
|
|
46
49
|
}
|
|
50
|
+
function containsJsx(node, depth = 0) {
|
|
51
|
+
if (!node || typeof node !== 'object' || depth > 40)
|
|
52
|
+
return false;
|
|
53
|
+
if (Array.isArray(node)) {
|
|
54
|
+
for (const item of node) {
|
|
55
|
+
if (containsJsx(item, depth + 1))
|
|
56
|
+
return true;
|
|
57
|
+
}
|
|
58
|
+
return false;
|
|
59
|
+
}
|
|
60
|
+
if (typeof node.type === 'string' && (node.type === 'JSXElement' || node.type === 'JSXFragment'))
|
|
61
|
+
return true;
|
|
62
|
+
for (const key of Object.keys(node)) {
|
|
63
|
+
const v = node[key];
|
|
64
|
+
if (v && typeof v === 'object' && containsJsx(v, depth + 1))
|
|
65
|
+
return true;
|
|
66
|
+
}
|
|
67
|
+
return false;
|
|
68
|
+
}
|
|
69
|
+
function printAst(ast) {
|
|
70
|
+
return print(ast, containsJsx(ast) ? tsx() : ts()).code;
|
|
71
|
+
}
|
|
47
72
|
export function transformTracked(irNode, tracked) {
|
|
48
73
|
const ast = irNode.ast;
|
|
49
74
|
if (!ast)
|
|
@@ -51,7 +76,7 @@ export function transformTracked(irNode, tracked) {
|
|
|
51
76
|
if (tracked.size === 0) {
|
|
52
77
|
if (!hasTsSyntax(ast))
|
|
53
78
|
return irNode.raw;
|
|
54
|
-
return
|
|
79
|
+
return printAst(stripTsTypes(ast));
|
|
55
80
|
}
|
|
56
81
|
const stripped = stripTsTypes(ast);
|
|
57
82
|
const transformed = walk(stripped, tracked, {
|
|
@@ -139,7 +164,7 @@ export function transformTracked(irNode, tracked) {
|
|
|
139
164
|
return context.next();
|
|
140
165
|
},
|
|
141
166
|
});
|
|
142
|
-
return print(transformed, ts()).code;
|
|
167
|
+
return print(transformed, containsJsx(transformed) ? tsx() : ts()).code;
|
|
143
168
|
}
|
|
144
169
|
export function collectTrackedNames(body) {
|
|
145
170
|
const names = new Map();
|
|
@@ -1458,15 +1483,13 @@ function hasKeyedMap(nodes) {
|
|
|
1458
1483
|
}
|
|
1459
1484
|
export function escapeHtml(str) {
|
|
1460
1485
|
return str
|
|
1461
|
-
.
|
|
1462
|
-
.
|
|
1463
|
-
.
|
|
1464
|
-
.
|
|
1465
|
-
.
|
|
1486
|
+
.split('&').join('&')
|
|
1487
|
+
.split('<').join('<')
|
|
1488
|
+
.split('>').join('>')
|
|
1489
|
+
.split('"').join('"')
|
|
1490
|
+
.split("'").join(''');
|
|
1466
1491
|
}
|
|
1467
|
-
|
|
1468
|
-
const ast = parse(source);
|
|
1469
|
-
const ir = generateIR(ast, source);
|
|
1492
|
+
function emitClientFromIR(ir, options) {
|
|
1470
1493
|
const needsClient = ir.components.some((c) => c.isClient || !isStaticComponent(c));
|
|
1471
1494
|
if (!options.forceClient && !needsClient) {
|
|
1472
1495
|
return '';
|
|
@@ -1510,7 +1533,16 @@ export function compileClient(source, _componentName, options = {}) {
|
|
|
1510
1533
|
runtimeNames.push(name);
|
|
1511
1534
|
}
|
|
1512
1535
|
}
|
|
1513
|
-
|
|
1536
|
+
// A name the file already binds via its own imports (e.g. a local module that
|
|
1537
|
+
// exports its own `effect`) shadows the runtime binding — never inject a
|
|
1538
|
+
// duplicate `import { x } from '@vesk/runtime'`. Mirrors SSR, so the two agree.
|
|
1539
|
+
const boundLocally = new Set();
|
|
1540
|
+
for (const imp of ir.imports) {
|
|
1541
|
+
for (const pair of importBindingPairs(imp))
|
|
1542
|
+
boundLocally.add(pair.local);
|
|
1543
|
+
}
|
|
1544
|
+
const shadowedRuntimeNames = runtimeNames.filter(n => !boundLocally.has(n));
|
|
1545
|
+
const runtimeImport = `import { ${shadowedRuntimeNames.length > 0 ? shadowedRuntimeNames.join(', ') : 'destroy_block'} } from '@vesk/runtime';`;
|
|
1514
1546
|
const moduleCode = `
|
|
1515
1547
|
${runtimeImport}
|
|
1516
1548
|
${importLines}
|
|
@@ -1520,4 +1552,45 @@ ${exportCode}
|
|
|
1520
1552
|
`;
|
|
1521
1553
|
return moduleCode.trim();
|
|
1522
1554
|
}
|
|
1555
|
+
export function compileClient(source, _componentName, options = {}) {
|
|
1556
|
+
if (options.sourcePath) {
|
|
1557
|
+
source = inlineMdImportsFrom(source, options.sourcePath, options.mdRoots || []);
|
|
1558
|
+
}
|
|
1559
|
+
const ast = parse(source);
|
|
1560
|
+
const ir = generateIR(ast, source);
|
|
1561
|
+
return emitClientFromIR(ir, options);
|
|
1562
|
+
}
|
|
1563
|
+
/**
|
|
1564
|
+
* Compiles a component source in BOTH client modes (plain + hydrate)
|
|
1565
|
+
* sharing a single parse/IR pass. Used by the dev-server hot path, where
|
|
1566
|
+
* every edit otherwise pays the full acorn+TS parse twice. Also returns
|
|
1567
|
+
* the resolved component name using the exact selection order of
|
|
1568
|
+
* `resolveComponentName`, so callers don't need a second full parse just
|
|
1569
|
+
* to learn the name.
|
|
1570
|
+
*/
|
|
1571
|
+
export function compileClientBoth(source, _componentName) {
|
|
1572
|
+
const ast = parse(source);
|
|
1573
|
+
// Downstream type-stripping mutates AST nodes in place (stripTsTypes),
|
|
1574
|
+
// so each emit mode needs its own tree. Cloning is far cheaper than the
|
|
1575
|
+
// second full acorn+TS parse this replaces.
|
|
1576
|
+
const hydAst = structuredClone(ast);
|
|
1577
|
+
const ir = generateIR(ast, source);
|
|
1578
|
+
const irHyd = generateIR(hydAst, source);
|
|
1579
|
+
const defaultComp = ir.components.find((c) => c.defaultExport);
|
|
1580
|
+
let name = null;
|
|
1581
|
+
if (defaultComp)
|
|
1582
|
+
name = defaultComp.name;
|
|
1583
|
+
else if (ir.components.length > 0)
|
|
1584
|
+
name = ir.components[0].name;
|
|
1585
|
+
else {
|
|
1586
|
+
const exportedComp = ir.components.find((c) => c.exported);
|
|
1587
|
+
if (exportedComp)
|
|
1588
|
+
name = exportedComp.name;
|
|
1589
|
+
}
|
|
1590
|
+
return {
|
|
1591
|
+
comp: emitClientFromIR(ir, { forceClient: true }),
|
|
1592
|
+
hyd: emitClientFromIR(irHyd, { forceClient: true, hydrate: true, includeTopLevel: false }),
|
|
1593
|
+
name,
|
|
1594
|
+
};
|
|
1595
|
+
}
|
|
1523
1596
|
export { compileClient as compile, isStaticIR };
|
package/dist/config.d.ts
CHANGED
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
import type { VeskConfig, VeskPlugin, VeskSecurity } from '@vesk/compiler/src/types';
|
|
2
|
+
/** Tags allowed by default when md.html = 'allowlist' and no allowTags given. */
|
|
3
|
+
export declare const MD_DEFAULT_ALLOW_TAGS: string[];
|
|
2
4
|
export declare function preset(name: string, overrides?: VeskSecurity): VeskSecurity;
|
|
3
5
|
export declare function definePlugin<P extends VeskPlugin>(plugin: P): P;
|
|
4
6
|
export declare function defineConfig(config: VeskConfig): VeskConfig;
|
package/dist/config.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"config.d.ts","sourceRoot":"","sources":["../src/config.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAE,UAAU,EAAE,YAAY,
|
|
1
|
+
{"version":3,"file":"config.d.ts","sourceRoot":"","sources":["../src/config.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAE,UAAU,EAAE,YAAY,EAAgC,MAAM,0BAA0B,CAAC;AAGnH,iFAAiF;AACjF,eAAO,MAAM,qBAAqB,UAIjC,CAAC;AA6CF,wBAAgB,MAAM,CAAC,IAAI,EAAE,MAAM,EAAE,SAAS,GAAE,YAAiB,GAAG,YAAY,CAY/E;AAED,wBAAgB,YAAY,CAAC,CAAC,SAAS,UAAU,EAAE,MAAM,EAAE,CAAC,GAAG,CAAC,CAQ/D;AAED,wBAAgB,YAAY,CAAC,MAAM,EAAE,UAAU,GAAG,UAAU,CAuB3D;AAED,wBAAgB,cAAc,CAAC,MAAM,EAAE,UAAU,GAAG,UAAU,CAkC7D"}
|
package/dist/config.js
CHANGED
|
@@ -1,4 +1,26 @@
|
|
|
1
1
|
import { VeskError } from '@vesk/compiler/src/errors';
|
|
2
|
+
/** Tags allowed by default when md.html = 'allowlist' and no allowTags given. */
|
|
3
|
+
export const MD_DEFAULT_ALLOW_TAGS = [
|
|
4
|
+
'a', 'abbr', 'b', 'bdi', 'bdo', 'br', 'cite', 'code', 'data', 'del', 'dfn', 'em',
|
|
5
|
+
'i', 'ins', 'kbd', 'mark', 'q', 'rp', 'rt', 'ruby', 's', 'samp', 'small', 'span',
|
|
6
|
+
'strong', 'sub', 'sup', 'time', 'u', 'var', 'wbr',
|
|
7
|
+
];
|
|
8
|
+
const MD_HTML_MODES = ['escape', 'allow', 'allowlist'];
|
|
9
|
+
function normalizeMdConfig(md) {
|
|
10
|
+
if (!md || typeof md !== 'object')
|
|
11
|
+
return md;
|
|
12
|
+
if (md.html !== undefined && !MD_HTML_MODES.includes(md.html)) {
|
|
13
|
+
throw VeskError.configError(`Unknown md.html mode: "${md.html}".`, MD_HTML_MODES);
|
|
14
|
+
}
|
|
15
|
+
const out = { ...md };
|
|
16
|
+
if (out.allowTags) {
|
|
17
|
+
if (!Array.isArray(out.allowTags)) {
|
|
18
|
+
throw VeskError.configError('md.allowTags must be an array of tag names.', []);
|
|
19
|
+
}
|
|
20
|
+
out.allowTags = out.allowTags.map((t) => String(t).toLowerCase().replace(/[^a-z0-9-]/g, '')).filter(Boolean);
|
|
21
|
+
}
|
|
22
|
+
return out;
|
|
23
|
+
}
|
|
2
24
|
const SECURITY_PRESETS = {
|
|
3
25
|
strict: {
|
|
4
26
|
autoEscape: true,
|
|
@@ -71,6 +93,9 @@ export function defineConfig(config) {
|
|
|
71
93
|
sec.redactLogs = true;
|
|
72
94
|
if (config.routeDataCache === undefined)
|
|
73
95
|
config.routeDataCache = 0;
|
|
96
|
+
const md = normalizeMdConfig(config.md);
|
|
97
|
+
if (md !== undefined)
|
|
98
|
+
config.md = md;
|
|
74
99
|
return config;
|
|
75
100
|
}
|
|
76
101
|
export function validateConfig(config) {
|
package/dist/errors.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"errors.d.ts","sourceRoot":"","sources":["../src/errors.ts"],"names":[],"mappings":"AAAA,wBAAgB,WAAW,CAAC,CAAC,EAAE,MAAM,EAAE,CAAC,EAAE,MAAM,GAAG,MAAM,CAoBxD;AAED,wBAAgB,UAAU,CAAC,IAAI,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,EAAE,EAAE,WAAW,GAAE,MAAU,GAAG,MAAM,GAAG,IAAI,CAWrG;
|
|
1
|
+
{"version":3,"file":"errors.d.ts","sourceRoot":"","sources":["../src/errors.ts"],"names":[],"mappings":"AAAA,wBAAgB,WAAW,CAAC,CAAC,EAAE,MAAM,EAAE,CAAC,EAAE,MAAM,GAAG,MAAM,CAoBxD;AAED,wBAAgB,UAAU,CAAC,IAAI,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,EAAE,EAAE,WAAW,GAAE,MAAU,GAAG,MAAM,GAAG,IAAI,CAWrG;AAqCD,MAAM,WAAW,gBAAgB;IAC/B,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,WAAW,CAAC,EAAE,MAAM,EAAE,CAAC;IACvB,SAAS,CAAC,EAAE,MAAM,EAAE,CAAC;IACrB,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC;CACxB;AAgBD,qBAAa,SAAU,SAAQ,KAAK;IAClC,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,MAAM,CAAC;IACf,WAAW,EAAE,MAAM,EAAE,CAAC;IACtB,SAAS,EAAE,MAAM,EAAE,CAAC;IACpB,GAAG,EAAE,MAAM,CAAC;IACZ,IAAI,CAAC,EAAE,MAAM,CAAC;gBAEF,OAAO,EAAE,MAAM,EAAE,IAAI,GAAE,gBAAqB;IAYxD,MAAM,CAAC,QAAQ,CAAC,IAAI,EAAE,MAAM,EAAE,UAAU,GAAE,MAAM,EAAO,EAAE,OAAO,GAAE,gBAAqB,GAAG,SAAS;IA6BnG,MAAM,CAAC,SAAS,CAAC,OAAO,GAAE,gBAAqB,GAAG,SAAS;IAmB3D,MAAM,CAAC,mBAAmB,CAAC,QAAQ,EAAE,MAAM,EAAE,OAAO,GAAE,gBAAqB,GAAG,SAAS;IAkBvF,MAAM,CAAC,mBAAmB,CAAC,QAAQ,EAAE,MAAM,EAAE,OAAO,GAAE,gBAAqB,GAAG,SAAS;IAkBvF,MAAM,CAAC,iBAAiB,CAAC,IAAI,EAAE,MAAM,EAAE,SAAS,GAAE,MAAM,EAAO,EAAE,OAAO,GAAE,gBAAqB,GAAG,SAAS;IAO3G,MAAM,CAAC,WAAW,CAAC,GAAG,EAAE,MAAM,EAAE,YAAY,GAAE,MAAM,EAAO,EAAE,OAAO,GAAE,gBAAqB,GAAG,SAAS;IAWvG,MAAM,CAAC,sBAAsB,CAAC,UAAU,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,EAAE,OAAO,GAAE,gBAAqB,GAAG,SAAS;IAkB/G,QAAQ,IAAI,MAAM;IAgBlB,MAAM,IAAI,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC;CAclC"}
|
package/dist/errors.js
CHANGED
|
@@ -31,19 +31,48 @@ export function didYouMean(name, candidates, maxDistance = 3) {
|
|
|
31
31
|
return best;
|
|
32
32
|
}
|
|
33
33
|
function extractLineColumn(message) {
|
|
34
|
-
const lineMatch = message.match(/(?:line|at\s+line)\s*(\d+)/i);
|
|
35
|
-
const colMatch = message.match(/(?:column|col)\s*(\d+)/i);
|
|
36
34
|
return {
|
|
37
|
-
line:
|
|
38
|
-
column:
|
|
35
|
+
line: findKeywordNumber(message, ['at line', 'line']),
|
|
36
|
+
column: findKeywordNumber(message, ['column', 'col']),
|
|
39
37
|
};
|
|
40
38
|
}
|
|
39
|
+
/**
|
|
40
|
+
* Finds `<keyword><whitespace>*<digits>` in `message`, trying longer keyword
|
|
41
|
+
* variants first so 'column' is not matched as 'col' + 'umn'. Char-scan only.
|
|
42
|
+
*/
|
|
43
|
+
function findKeywordNumber(message, keywords) {
|
|
44
|
+
const lower = message.toLowerCase();
|
|
45
|
+
for (const kw of keywords) {
|
|
46
|
+
let from = 0;
|
|
47
|
+
while (from <= lower.length - kw.length) {
|
|
48
|
+
const idx = lower.indexOf(kw, from);
|
|
49
|
+
if (idx === -1)
|
|
50
|
+
break;
|
|
51
|
+
// word boundary before the keyword
|
|
52
|
+
const before = idx > 0 ? lower[idx - 1] : ' ';
|
|
53
|
+
if (!(before >= 'a' && before <= 'z')) {
|
|
54
|
+
let j = idx + kw.length;
|
|
55
|
+
while (j < message.length && (message[j] === ' ' || message[j] === '\t'))
|
|
56
|
+
j++;
|
|
57
|
+
if (message[j] >= '0' && message[j] <= '9') {
|
|
58
|
+
let end = j;
|
|
59
|
+
while (end < message.length && message[end] >= '0' && message[end] <= '9')
|
|
60
|
+
end++;
|
|
61
|
+
return parseInt(message.slice(j, end));
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
from = idx + 1;
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
return 0;
|
|
68
|
+
}
|
|
41
69
|
const VESK_BUILTINS = [
|
|
42
70
|
'useFetch', 'useRouter', 'useParams', 'usePathname', 'useSearchParams',
|
|
43
71
|
'useNavigate', 'useHead', 'useTitle',
|
|
44
72
|
'Form', 'Field', 'Link', 'NavLink', 'Outlet',
|
|
45
73
|
'Image', 'Portal',
|
|
46
74
|
'Experiment',
|
|
75
|
+
'LoadingIndicator', 'useLoadingIndicator',
|
|
47
76
|
'required', 'email', 'minLength', 'maxLength', 'pattern', 'custom',
|
|
48
77
|
'track', 'get', 'set', 'derived', 'effect', 'batch', 'untrack',
|
|
49
78
|
'cookies', 'headers', 'locals',
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ir-generator.d.ts","sourceRoot":"","sources":["../src/ir-generator.ts"],"names":[],"mappings":"AACA,OAAO,EACL,MAAM,EAoBP,MAAM,uBAAuB,CAAC;
|
|
1
|
+
{"version":3,"file":"ir-generator.d.ts","sourceRoot":"","sources":["../src/ir-generator.ts"],"names":[],"mappings":"AACA,OAAO,EACL,MAAM,EAoBP,MAAM,uBAAuB,CAAC;AAgJ/B,wBAAgB,YAAY,CAAC,MAAM,EAAE,KAAK,CAAC;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,IAAI,CAAC,EAAE,MAAM,CAAC;IAAC,IAAI,CAAC,EAAE,GAAG,CAAC;IAAC,cAAc,CAAC,EAAE,GAAG,CAAC;IAAC,UAAU,CAAC,EAAE,GAAG,EAAE,CAAA;CAAE,CAAC,GAAG,SAAS,GAAG,IAAI,EAAE,MAAM,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAkCnL;AAuxBD,wBAAgB,UAAU,CAAC,GAAG,EAAE,GAAG,EAAE,MAAM,EAAE,MAAM,GAAG,MAAM,CAiO3D"}
|