@b9g/crank 0.7.5 → 0.7.7
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 +15 -9
- package/_svg.cjs +94 -0
- package/_svg.cjs.map +1 -0
- package/_svg.d.ts +1 -0
- package/_svg.js +92 -0
- package/_svg.js.map +1 -0
- package/async.cjs +2 -1
- package/async.cjs.map +1 -1
- package/async.js +2 -1
- package/async.js.map +1 -1
- package/crank.cjs +193 -16
- package/crank.cjs.map +1 -1
- package/crank.d.ts +1 -0
- package/crank.js +193 -16
- package/crank.js.map +1 -1
- package/dom.cjs +326 -258
- package/dom.cjs.map +1 -1
- package/dom.js +326 -258
- package/dom.js.map +1 -1
- package/html.cjs +35 -5
- package/html.cjs.map +1 -1
- package/html.d.ts +2 -2
- package/html.js +35 -5
- package/html.js.map +1 -1
- package/jsx-tag.cjs +86 -21
- package/jsx-tag.cjs.map +1 -1
- package/jsx-tag.d.ts +41 -0
- package/jsx-tag.js +86 -22
- package/jsx-tag.js.map +1 -1
- package/package.json +1 -2
- package/standalone.cjs +7 -0
- package/standalone.cjs.map +1 -1
- package/standalone.d.ts +3 -1
- package/standalone.js +2 -0
- package/standalone.js.map +1 -1
- package/umd.js +648 -285
- package/umd.js.map +1 -1
package/jsx-tag.d.ts
CHANGED
|
@@ -2,3 +2,44 @@ import type { Element } from "./crank.js";
|
|
|
2
2
|
export declare function jsx(spans: TemplateStringsArray, ...expressions: Array<unknown>): Element;
|
|
3
3
|
/** Alias for `jsx` template tag. */
|
|
4
4
|
export declare const html: typeof jsx;
|
|
5
|
+
export interface ParseElement {
|
|
6
|
+
type: "element";
|
|
7
|
+
open: ParseTag;
|
|
8
|
+
close: ParseTag | null;
|
|
9
|
+
props: Array<ParseProp | ParseValue>;
|
|
10
|
+
children: Array<ParseElement | ParseValue>;
|
|
11
|
+
}
|
|
12
|
+
export interface ParseValue {
|
|
13
|
+
type: "value";
|
|
14
|
+
value: any;
|
|
15
|
+
}
|
|
16
|
+
export interface ParseTag {
|
|
17
|
+
type: "tag";
|
|
18
|
+
slash: string;
|
|
19
|
+
value: any;
|
|
20
|
+
spanIndex?: number;
|
|
21
|
+
charIndex?: number;
|
|
22
|
+
}
|
|
23
|
+
export interface ParseProp {
|
|
24
|
+
type: "prop";
|
|
25
|
+
name: string;
|
|
26
|
+
value: ParseValue | ParsePropString;
|
|
27
|
+
}
|
|
28
|
+
export interface ParsePropString {
|
|
29
|
+
type: "propString";
|
|
30
|
+
parts: Array<string | ParseValue>;
|
|
31
|
+
}
|
|
32
|
+
export interface ParseError {
|
|
33
|
+
type: "error";
|
|
34
|
+
message: string;
|
|
35
|
+
value: any;
|
|
36
|
+
spanIndex?: number;
|
|
37
|
+
charIndex?: number;
|
|
38
|
+
}
|
|
39
|
+
export type ExpressionTarget = ParseValue | ParseTag | ParseProp | ParseError;
|
|
40
|
+
export interface ParseResult {
|
|
41
|
+
element: ParseElement;
|
|
42
|
+
targets: Array<ExpressionTarget | null>;
|
|
43
|
+
spans: ArrayLike<string>;
|
|
44
|
+
}
|
|
45
|
+
export declare function parse(spans: ArrayLike<string>): ParseResult;
|
package/jsx-tag.js
CHANGED
|
@@ -7,7 +7,17 @@ function jsx(spans, ...expressions) {
|
|
|
7
7
|
let parseResult = cache.get(key);
|
|
8
8
|
if (parseResult == null) {
|
|
9
9
|
parseResult = parse(spans.raw);
|
|
10
|
-
|
|
10
|
+
let hasError = false;
|
|
11
|
+
for (let i = 0; i < parseResult.targets.length; i++) {
|
|
12
|
+
const t = parseResult.targets[i];
|
|
13
|
+
if (t && t.type === "error") {
|
|
14
|
+
hasError = true;
|
|
15
|
+
break;
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
if (!hasError) {
|
|
19
|
+
cache.set(key, parseResult);
|
|
20
|
+
}
|
|
11
21
|
}
|
|
12
22
|
const { element, targets } = parseResult;
|
|
13
23
|
for (let i = 0; i < expressions.length; i++) {
|
|
@@ -15,12 +25,15 @@ function jsx(spans, ...expressions) {
|
|
|
15
25
|
const target = targets[i];
|
|
16
26
|
if (target) {
|
|
17
27
|
if (target.type === "error") {
|
|
18
|
-
|
|
28
|
+
const msg = target.message.replace("${}", formatTagForError(exp));
|
|
29
|
+
throw new SyntaxError(target.spanIndex != null && target.charIndex != null
|
|
30
|
+
? formatSyntaxError(msg, spans.raw, target.spanIndex, target.charIndex)
|
|
31
|
+
: msg);
|
|
19
32
|
}
|
|
20
33
|
target.value = exp;
|
|
21
34
|
}
|
|
22
35
|
}
|
|
23
|
-
return build(element);
|
|
36
|
+
return build(element, parseResult.spans);
|
|
24
37
|
}
|
|
25
38
|
/** Alias for `jsx` template tag. */
|
|
26
39
|
const html = jsx;
|
|
@@ -112,16 +125,20 @@ function parse(spans) {
|
|
|
112
125
|
type: "tag",
|
|
113
126
|
slash: closingSlash,
|
|
114
127
|
value: tagName,
|
|
128
|
+
spanIndex: s,
|
|
129
|
+
charIndex: match.index,
|
|
115
130
|
};
|
|
116
131
|
if (!stack.length) {
|
|
117
132
|
if (end !== span.length) {
|
|
118
|
-
throw new SyntaxError(`Unmatched closing tag "${tagName}"
|
|
133
|
+
throw new SyntaxError(formatSyntaxError(`Unmatched closing tag "${tagName}"`, spans, s, match.index));
|
|
119
134
|
}
|
|
120
135
|
// ERROR EXPRESSION
|
|
121
136
|
expressionTarget = {
|
|
122
137
|
type: "error",
|
|
123
138
|
message: "Unmatched closing tag ${}",
|
|
124
139
|
value: null,
|
|
140
|
+
spanIndex: s,
|
|
141
|
+
charIndex: match.index,
|
|
125
142
|
};
|
|
126
143
|
}
|
|
127
144
|
else {
|
|
@@ -140,6 +157,8 @@ function parse(spans) {
|
|
|
140
157
|
type: "tag",
|
|
141
158
|
slash: "",
|
|
142
159
|
value: tagName,
|
|
160
|
+
spanIndex: s,
|
|
161
|
+
charIndex: match.index,
|
|
143
162
|
},
|
|
144
163
|
close: null,
|
|
145
164
|
props: [],
|
|
@@ -174,7 +193,7 @@ function parse(spans) {
|
|
|
174
193
|
if (match) {
|
|
175
194
|
const [, tagEnd, spread, name, equals, string] = match;
|
|
176
195
|
if (i < match.index) {
|
|
177
|
-
throw new SyntaxError(`Unexpected text \`${span.slice(i, match.index).trim()}
|
|
196
|
+
throw new SyntaxError(formatSyntaxError(`Unexpected text \`${span.slice(i, match.index).trim()}\``, spans, s, i));
|
|
178
197
|
}
|
|
179
198
|
if (tagEnd) {
|
|
180
199
|
if (tagEnd[0] === "/") {
|
|
@@ -193,7 +212,7 @@ function parse(spans) {
|
|
|
193
212
|
// SPREAD PROP EXPRESSION
|
|
194
213
|
expressionTarget = value;
|
|
195
214
|
if (!(expressing && end === span.length)) {
|
|
196
|
-
throw new SyntaxError('Expression expected after "..."');
|
|
215
|
+
throw new SyntaxError(formatSyntaxError('Expression expected after "..."', spans, s, match.index));
|
|
197
216
|
}
|
|
198
217
|
}
|
|
199
218
|
else if (name) {
|
|
@@ -203,14 +222,14 @@ function parse(spans) {
|
|
|
203
222
|
value = { type: "value", value: true };
|
|
204
223
|
}
|
|
205
224
|
else if (end < span.length) {
|
|
206
|
-
throw new SyntaxError(`Unexpected text \`${span.slice(end, end + 20)}
|
|
225
|
+
throw new SyntaxError(formatSyntaxError(`Unexpected text \`${span.slice(end, end + 20)}\``, spans, s, end));
|
|
207
226
|
}
|
|
208
227
|
else {
|
|
209
228
|
value = { type: "value", value: null };
|
|
210
229
|
// PROP EXPRESSION
|
|
211
230
|
expressionTarget = value;
|
|
212
231
|
if (!(expressing && end === span.length)) {
|
|
213
|
-
throw new SyntaxError(`Expression expected for prop "${name}"
|
|
232
|
+
throw new SyntaxError(formatSyntaxError(`Expression expected for prop "${name}"`, spans, s, match.index));
|
|
214
233
|
}
|
|
215
234
|
}
|
|
216
235
|
}
|
|
@@ -236,10 +255,10 @@ function parse(spans) {
|
|
|
236
255
|
else {
|
|
237
256
|
if (!expressing) {
|
|
238
257
|
if (i === span.length) {
|
|
239
|
-
throw new SyntaxError(`Expected props but reached end of document
|
|
258
|
+
throw new SyntaxError(formatSyntaxError(`Expected props but reached end of document`, spans, s, i));
|
|
240
259
|
}
|
|
241
260
|
else {
|
|
242
|
-
throw new SyntaxError(`Unexpected text \`${span.slice(i, i + 20).trim()}
|
|
261
|
+
throw new SyntaxError(formatSyntaxError(`Unexpected text \`${span.slice(i, i + 20).trim()}\``, spans, s, i));
|
|
243
262
|
}
|
|
244
263
|
}
|
|
245
264
|
// Unexpected expression errors are handled in the outer loop.
|
|
@@ -254,13 +273,13 @@ function parse(spans) {
|
|
|
254
273
|
// We're in a closing tag and looking for the >.
|
|
255
274
|
if (match) {
|
|
256
275
|
if (i < match.index) {
|
|
257
|
-
throw new SyntaxError(`Unexpected text \`${span.slice(i, match.index).trim()}
|
|
276
|
+
throw new SyntaxError(formatSyntaxError(`Unexpected text \`${span.slice(i, match.index).trim()}\``, spans, s, i));
|
|
258
277
|
}
|
|
259
278
|
matcher = CHILDREN_RE;
|
|
260
279
|
}
|
|
261
280
|
else {
|
|
262
281
|
if (!expressing) {
|
|
263
|
-
throw new SyntaxError(`Unexpected text \`${span.slice(i, i + 20).trim()}
|
|
282
|
+
throw new SyntaxError(formatSyntaxError(`Unexpected text \`${span.slice(i, i + 20).trim()}\``, spans, s, i));
|
|
264
283
|
}
|
|
265
284
|
}
|
|
266
285
|
break;
|
|
@@ -276,7 +295,7 @@ function parse(spans) {
|
|
|
276
295
|
}
|
|
277
296
|
else {
|
|
278
297
|
if (!expressing) {
|
|
279
|
-
throw new SyntaxError(`Missing \`${matcher === CLOSING_SINGLE_QUOTE_RE ? "'" : '"'}
|
|
298
|
+
throw new SyntaxError(formatSyntaxError(`Missing \`${matcher === CLOSING_SINGLE_QUOTE_RE ? "'" : '"'}\``, spans, s, i));
|
|
280
299
|
}
|
|
281
300
|
}
|
|
282
301
|
break;
|
|
@@ -287,7 +306,7 @@ function parse(spans) {
|
|
|
287
306
|
}
|
|
288
307
|
else {
|
|
289
308
|
if (!expressing) {
|
|
290
|
-
throw new SyntaxError("Expected `-->` but reached end of template");
|
|
309
|
+
throw new SyntaxError(formatSyntaxError("Expected `-->` but reached end of template", spans, s, i));
|
|
291
310
|
}
|
|
292
311
|
}
|
|
293
312
|
break;
|
|
@@ -321,40 +340,45 @@ function parse(spans) {
|
|
|
321
340
|
targets.push(null);
|
|
322
341
|
break;
|
|
323
342
|
default:
|
|
324
|
-
throw new SyntaxError("Unexpected expression");
|
|
343
|
+
throw new SyntaxError(formatSyntaxError("Unexpected expression", spans, s, spans[s].length));
|
|
325
344
|
}
|
|
326
345
|
}
|
|
327
346
|
else if (expressionTarget) {
|
|
328
|
-
throw new SyntaxError("Expression expected");
|
|
347
|
+
throw new SyntaxError(formatSyntaxError("Expression expected", spans, s, spans[s].length));
|
|
329
348
|
}
|
|
330
349
|
lineStart = false;
|
|
331
350
|
}
|
|
332
351
|
if (stack.length) {
|
|
333
352
|
const ti = targets.indexOf(element.open);
|
|
334
353
|
if (ti === -1) {
|
|
335
|
-
throw new SyntaxError(`Unmatched opening tag "${element.open.value}"
|
|
354
|
+
throw new SyntaxError(formatSyntaxError(`Unmatched opening tag "${element.open.value}"`, spans, element.open.spanIndex ?? 0, element.open.charIndex ?? 0));
|
|
336
355
|
}
|
|
337
356
|
targets[ti] = {
|
|
338
357
|
type: "error",
|
|
339
358
|
message: "Unmatched opening tag ${}",
|
|
340
359
|
value: null,
|
|
360
|
+
spanIndex: element.open.spanIndex,
|
|
361
|
+
charIndex: element.open.charIndex,
|
|
341
362
|
};
|
|
342
363
|
}
|
|
343
364
|
if (element.children.length === 1 && element.children[0].type === "element") {
|
|
344
365
|
element = element.children[0];
|
|
345
366
|
}
|
|
346
|
-
return { element, targets };
|
|
367
|
+
return { element, targets, spans };
|
|
347
368
|
}
|
|
348
|
-
function build(parsed) {
|
|
369
|
+
function build(parsed, spans) {
|
|
349
370
|
if (parsed.close !== null &&
|
|
350
371
|
parsed.close.slash !== "//" &&
|
|
351
372
|
parsed.open.value !== parsed.close.value) {
|
|
352
|
-
|
|
373
|
+
const msg = `Unmatched closing tag ${formatTagForError(parsed.close.value)}, expected ${formatTagForError(parsed.open.value)}`;
|
|
374
|
+
throw new SyntaxError(spans && parsed.close.spanIndex != null && parsed.close.charIndex != null
|
|
375
|
+
? formatSyntaxError(msg, spans, parsed.close.spanIndex, parsed.close.charIndex)
|
|
376
|
+
: msg);
|
|
353
377
|
}
|
|
354
378
|
const children = [];
|
|
355
379
|
for (let i = 0; i < parsed.children.length; i++) {
|
|
356
380
|
const child = parsed.children[i];
|
|
357
|
-
children.push(child.type === "element" ? build(child) : child.value);
|
|
381
|
+
children.push(child.type === "element" ? build(child, spans) : child.value);
|
|
358
382
|
}
|
|
359
383
|
let props = parsed.props.length ? {} : null;
|
|
360
384
|
for (let i = 0; i < parsed.props.length; i++) {
|
|
@@ -425,6 +449,46 @@ function formatTagForError(tag) {
|
|
|
425
449
|
? `"${tag}"`
|
|
426
450
|
: JSON.stringify(tag);
|
|
427
451
|
}
|
|
452
|
+
function formatSyntaxError(message, spans, spanIndex, charIndex) {
|
|
453
|
+
// Reconstruct full template source with ${} placeholders
|
|
454
|
+
let source = spans[0];
|
|
455
|
+
for (let i = 1; i < spans.length; i++) {
|
|
456
|
+
source += "${}" + spans[i];
|
|
457
|
+
}
|
|
458
|
+
// Compute absolute offset
|
|
459
|
+
let offset = 0;
|
|
460
|
+
for (let i = 0; i < spanIndex; i++) {
|
|
461
|
+
offset += spans[i].length + 3; // 3 = "${}".length
|
|
462
|
+
}
|
|
463
|
+
offset += charIndex;
|
|
464
|
+
// Split into lines and find line/column
|
|
465
|
+
const lines = source.split(/\n/);
|
|
466
|
+
let line = 0;
|
|
467
|
+
let col = offset;
|
|
468
|
+
for (let i = 0; i < lines.length; i++) {
|
|
469
|
+
if (col <= lines[i].length) {
|
|
470
|
+
line = i;
|
|
471
|
+
break;
|
|
472
|
+
}
|
|
473
|
+
col -= lines[i].length + 1; // +1 for the newline
|
|
474
|
+
}
|
|
475
|
+
// Build context lines
|
|
476
|
+
let result = `${message}\n\n`;
|
|
477
|
+
const start = Math.max(0, line - 1);
|
|
478
|
+
const end = Math.min(lines.length - 1, line + 1);
|
|
479
|
+
const gutterWidth = String(end + 1).length;
|
|
480
|
+
for (let i = start; i <= end; i++) {
|
|
481
|
+
const num = String(i + 1).padStart(gutterWidth);
|
|
482
|
+
if (i === line) {
|
|
483
|
+
result += `> ${num} | ${lines[i]}\n`;
|
|
484
|
+
result += ` ${" ".repeat(gutterWidth)} | ${" ".repeat(col)}^\n`;
|
|
485
|
+
}
|
|
486
|
+
else {
|
|
487
|
+
result += ` ${num} | ${lines[i]}\n`;
|
|
488
|
+
}
|
|
489
|
+
}
|
|
490
|
+
return result.trimEnd();
|
|
491
|
+
}
|
|
428
492
|
|
|
429
|
-
export { html, jsx };
|
|
493
|
+
export { html, jsx, parse };
|
|
430
494
|
//# sourceMappingURL=jsx-tag.js.map
|