sjabloon 0.10.0 → 0.12.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/EMBEDDING.md +209 -0
- package/README.md +152 -108
- package/lib/core.js +175 -209
- package/lib/html.d.ts +3 -12
- package/lib/html.js +8 -6
- package/lib/index.d.ts +13 -13
- package/lib/index.js +18 -24
- package/lib/text.d.ts +3 -12
- package/lib/text.js +6 -3
- package/lib/types.d.ts +51 -10
- package/package.json +11 -6
package/lib/index.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
export * from "./types.js";
|
|
2
|
-
import type {
|
|
2
|
+
import type { SjabloonRender, SjabloonTemplate, Token } from "./types.js";
|
|
3
3
|
|
|
4
4
|
/**
|
|
5
5
|
* Compile a template once, render it many times to a token stream.
|
|
@@ -7,21 +7,21 @@ import type { SjabloonFunctions, SjabloonRenderer, SjabloonValues, Token } from
|
|
|
7
7
|
* `{{ expr }}` emits a value token; escaping belongs to whoever consumes the
|
|
8
8
|
* stream, so `{{{ expr }}}` has no meaning here and is a compile-time
|
|
9
9
|
* `SJABLOON_RAW_TAG` error.
|
|
10
|
-
*
|
|
11
|
-
* @see SjabloonRenderer for `names`/`functions`, SjabloonScope for `$` and `@`.
|
|
12
|
-
* @throws {SyntaxError} On malformed tags, unclosed blocks, or bad expressions.
|
|
13
10
|
*/
|
|
14
|
-
export
|
|
15
|
-
|
|
16
|
-
funcs?: SjabloonFunctions,
|
|
17
|
-
opts?: { bound?: Iterable<string> },
|
|
18
|
-
): SjabloonRenderer<Token[]>;
|
|
19
|
-
|
|
20
|
-
/** Compile and render in one go. Shorthand for `template(str, funcs)(values)`. */
|
|
21
|
-
export function render(str: string, values?: SjabloonValues, funcs?: SjabloonFunctions): Token[];
|
|
11
|
+
export const template: SjabloonTemplate<Token[]>;
|
|
12
|
+
export const render: SjabloonRender<Token[]>;
|
|
22
13
|
|
|
23
14
|
/**
|
|
24
15
|
* Join a token stream into the string `sjabloon/text` would have produced:
|
|
25
|
-
* literals verbatim, values
|
|
16
|
+
* literals verbatim, values through `display()`.
|
|
26
17
|
*/
|
|
27
18
|
export function text(tokens: readonly Token[]): string;
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Display text for one interpolated value — the scalar rule `text()` and the
|
|
22
|
+
* string editions share. A valid `Date` renders as ISO 8601 UTC
|
|
23
|
+
* (`toISOString()`), deterministically across machines; an invalid `Date`
|
|
24
|
+
* stays `"Invalid Date"`; nullish displays empty; everything else is
|
|
25
|
+
* `String(value)`.
|
|
26
|
+
*/
|
|
27
|
+
export function display(value: unknown): string;
|
package/lib/index.js
CHANGED
|
@@ -4,20 +4,17 @@
|
|
|
4
4
|
* so nothing here is HTML-aware and `{{{ }}}` has no meaning — `{{ }}` is
|
|
5
5
|
* already raw.
|
|
6
6
|
*/
|
|
7
|
-
import { make } from "./core.js";
|
|
7
|
+
import { display, make } from "./core.js";
|
|
8
8
|
|
|
9
|
-
export { isDiagnostic, relocate } from "./core.js";
|
|
9
|
+
export { display, isDiagnostic, relocate } from "./core.js";
|
|
10
10
|
|
|
11
11
|
export const { template, render } = make([
|
|
12
|
-
// Static text is a compile-time constant:
|
|
13
|
-
// text node rather than allocating
|
|
14
|
-
(text) =>
|
|
15
|
-
(
|
|
16
|
-
acc.push(token)
|
|
17
|
-
|
|
18
|
-
(expr) => (scope, acc) => {
|
|
19
|
-
acc.push({ value: expr(scope) });
|
|
20
|
-
},
|
|
12
|
+
// Static text is a compile-time constant: the default parameter hoists and
|
|
13
|
+
// freezes one token per text node rather than allocating per loop iteration.
|
|
14
|
+
(text, token = Object.freeze({ literal: text })) =>
|
|
15
|
+
(scope, acc) =>
|
|
16
|
+
acc.push(token),
|
|
17
|
+
(expr) => (scope, acc) => acc.push({ value: expr(scope) }),
|
|
21
18
|
0,
|
|
22
19
|
() => /** @type {import('./types.js').Token[]} */ ([]),
|
|
23
20
|
(acc) => acc,
|
|
@@ -25,20 +22,17 @@ export const { template, render } = make([
|
|
|
25
22
|
|
|
26
23
|
/**
|
|
27
24
|
* Join a token stream into the string `sjabloon/text` would have produced:
|
|
28
|
-
* literals verbatim, values
|
|
25
|
+
* literals verbatim, values through `display()` — the one scalar rule, so a
|
|
26
|
+
* `Date` joins as ISO 8601 UTC here exactly as the string editions render it.
|
|
29
27
|
*
|
|
30
28
|
* @param {readonly import('./types.js').Token[]} tokens A render's output.
|
|
31
29
|
* @returns {string} The joined text.
|
|
32
30
|
*/
|
|
33
|
-
export const text = (tokens) =>
|
|
34
|
-
|
|
35
|
-
//
|
|
36
|
-
//
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
// oxlint-disable-next-line typescript/no-base-to-string
|
|
42
|
-
s += t.literal ?? String(t.value ?? "");
|
|
43
|
-
return s;
|
|
44
|
-
};
|
|
31
|
+
export const text = (tokens) =>
|
|
32
|
+
// A literal never stringifies and a nullish value still renders empty.
|
|
33
|
+
// Widened here because each token carries one key or the other, which the
|
|
34
|
+
// public union deliberately does not model.
|
|
35
|
+
/** @type {readonly { literal?: string, value?: unknown }[]} */ (tokens).reduce(
|
|
36
|
+
(s, t) => s + (t.literal ?? display(t.value)),
|
|
37
|
+
"",
|
|
38
|
+
);
|
package/lib/text.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
export * from "./types.js";
|
|
2
|
-
import type {
|
|
2
|
+
import type { SjabloonRender, SjabloonTemplate } from "./types.js";
|
|
3
3
|
|
|
4
4
|
/**
|
|
5
5
|
* Compile a template once, render it many times to a plain string.
|
|
@@ -7,15 +7,6 @@ import type { SjabloonFunctions, SjabloonRenderer, SjabloonValues } from "./type
|
|
|
7
7
|
* `{{ expr }}` interpolates unescaped — escaping belongs at the output edge —
|
|
8
8
|
* so `{{{ expr }}}` has no meaning here and is a compile-time
|
|
9
9
|
* `SJABLOON_RAW_TAG` error.
|
|
10
|
-
*
|
|
11
|
-
* @see SjabloonRenderer for `names`/`functions`, SjabloonScope for `$` and `@`.
|
|
12
|
-
* @throws {SyntaxError} On malformed tags, unclosed blocks, or bad expressions.
|
|
13
10
|
*/
|
|
14
|
-
export
|
|
15
|
-
|
|
16
|
-
funcs?: SjabloonFunctions,
|
|
17
|
-
opts?: { bound?: Iterable<string> },
|
|
18
|
-
): SjabloonRenderer<string>;
|
|
19
|
-
|
|
20
|
-
/** Compile and render in one go. Shorthand for `template(str, funcs)(values)`. */
|
|
21
|
-
export function render(str: string, values?: SjabloonValues, funcs?: SjabloonFunctions): string;
|
|
11
|
+
export const template: SjabloonTemplate<string>;
|
|
12
|
+
export const render: SjabloonRender<string>;
|
package/lib/text.js
CHANGED
|
@@ -6,13 +6,16 @@
|
|
|
6
6
|
* Definitionally `text(template(str)(values))` from the root entry, but built
|
|
7
7
|
* as a string accumulator so casual string users never allocate tokens.
|
|
8
8
|
*/
|
|
9
|
-
import {
|
|
9
|
+
import { display, make } from "./core.js";
|
|
10
10
|
|
|
11
11
|
export { isDiagnostic, relocate } from "./core.js";
|
|
12
12
|
|
|
13
|
+
// The string accumulator is this edition's own shape, so its profile is stated
|
|
14
|
+
// here rather than named in the core: every node is shorter than an import of
|
|
15
|
+
// it, and the core stays the parser alone.
|
|
13
16
|
export const { template, render } = make([
|
|
14
|
-
|
|
15
|
-
(expr) => (scope, acc
|
|
17
|
+
(text) => (scope, acc) => (acc.text += text),
|
|
18
|
+
(expr) => (scope, acc) => (acc.text += display(expr(scope))),
|
|
16
19
|
0,
|
|
17
20
|
() => ({ text: "" }),
|
|
18
21
|
(acc) => acc.text,
|
package/lib/types.d.ts
CHANGED
|
@@ -1,7 +1,12 @@
|
|
|
1
|
-
import type {
|
|
1
|
+
import type { Diagnostic, Relocation } from "waarmerk";
|
|
2
|
+
import type { XprsnErrorCode, XprsnRead } from "xprsn";
|
|
2
3
|
|
|
4
|
+
/**
|
|
5
|
+
* The codes sjabloon itself mints. An expression fault carries xprsn's instead,
|
|
6
|
+
* which is why the diagnostic's `code` is the wider union — the same split
|
|
7
|
+
* padvinder draws between its own codes and treffer's.
|
|
8
|
+
*/
|
|
3
9
|
export type SjabloonErrorCode =
|
|
4
|
-
| XprsnErrorCode
|
|
5
10
|
| "SJABLOON_EACH_SYNTAX"
|
|
6
11
|
| "SJABLOON_BLOCKED_BINDING"
|
|
7
12
|
| "SJABLOON_UNEXPECTED_TAG"
|
|
@@ -16,14 +21,30 @@ export interface SjabloonBlock {
|
|
|
16
21
|
readonly end: number;
|
|
17
22
|
}
|
|
18
23
|
|
|
19
|
-
|
|
20
|
-
|
|
24
|
+
/**
|
|
25
|
+
* The fields and their meanings are waarmerk's; this names the code union they are
|
|
26
|
+
* checked against, narrows the three this module always carries from optional
|
|
27
|
+
* to required, and adds the block context a template fault sits in.
|
|
28
|
+
*
|
|
29
|
+
* `code` is widened with `XprsnErrorCode` because most diagnostics here are
|
|
30
|
+
* xprsn errors translated into template coordinates: a host can still tell a
|
|
31
|
+
* malformed tag from a fault in the expression inside it.
|
|
32
|
+
*/
|
|
33
|
+
export interface SjabloonDiagnostic extends Diagnostic<SjabloonErrorCode | XprsnErrorCode> {
|
|
34
|
+
readonly code: SjabloonErrorCode | XprsnErrorCode;
|
|
21
35
|
readonly start: number;
|
|
22
36
|
readonly end: number;
|
|
23
37
|
readonly blocks: readonly SjabloonBlock[];
|
|
24
38
|
}
|
|
25
39
|
|
|
26
40
|
export type SjabloonValues = Record<string, any>;
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* One root-name read, with its span in the template source — xprsn's read
|
|
44
|
+
* record, forwarded with only its coordinates shifted.
|
|
45
|
+
*/
|
|
46
|
+
export type SjabloonRead = XprsnRead;
|
|
47
|
+
|
|
27
48
|
export type SjabloonFunctions = Record<string, Function>;
|
|
28
49
|
|
|
29
50
|
/**
|
|
@@ -50,8 +71,11 @@ export interface SjabloonScope {
|
|
|
50
71
|
*
|
|
51
72
|
* `names` are the variables the template reads from your values, deduplicated;
|
|
52
73
|
* loop variables the template introduces are not included, and neither is
|
|
53
|
-
* anything the compile's `bound` option declared. `
|
|
54
|
-
*
|
|
74
|
+
* anything the compile's `bound` option declared. `reads` are every root-name
|
|
75
|
+
* read with its span in the template source, in source order — duplicates,
|
|
76
|
+
* anchors, loop variables and bound names kept, so `names` is its free,
|
|
77
|
+
* deduplicated view. `functions` are the registry functions the template
|
|
78
|
+
* calls, deduplicated.
|
|
55
79
|
*
|
|
56
80
|
* `isDiagnostic(error)` recognizes runtime diagnostics thrown through this
|
|
57
81
|
* renderer alone — the per-renderer twin of the module-wide `isDiagnostic`.
|
|
@@ -65,11 +89,31 @@ export interface SjabloonScope {
|
|
|
65
89
|
export interface SjabloonRenderer<T> {
|
|
66
90
|
(values?: SjabloonValues, scope?: SjabloonScope): T;
|
|
67
91
|
names: string[];
|
|
92
|
+
reads: SjabloonRead[];
|
|
68
93
|
functions: string[];
|
|
69
94
|
isDiagnostic(error: unknown): boolean;
|
|
70
95
|
scoped(values: SjabloonValues): T;
|
|
71
96
|
}
|
|
72
97
|
|
|
98
|
+
/**
|
|
99
|
+
* `template` in every edition: compile once, render many times. `T` is what
|
|
100
|
+
* one render returns.
|
|
101
|
+
*
|
|
102
|
+
* @throws {SyntaxError} On malformed tags, unclosed blocks, or bad expressions.
|
|
103
|
+
*/
|
|
104
|
+
export type SjabloonTemplate<T> = (
|
|
105
|
+
str: string,
|
|
106
|
+
funcs?: SjabloonFunctions,
|
|
107
|
+
opts?: { bound?: Iterable<string> },
|
|
108
|
+
) => SjabloonRenderer<T>;
|
|
109
|
+
|
|
110
|
+
/** Compile and render in one go. Shorthand for `template(str, funcs)(values)`. */
|
|
111
|
+
export type SjabloonRender<T> = (
|
|
112
|
+
str: string,
|
|
113
|
+
values?: SjabloonValues,
|
|
114
|
+
funcs?: SjabloonFunctions,
|
|
115
|
+
) => T;
|
|
116
|
+
|
|
73
117
|
/** One static text run of the template, verbatim. */
|
|
74
118
|
export interface LiteralToken {
|
|
75
119
|
literal: string;
|
|
@@ -102,7 +146,4 @@ export function isDiagnostic(error: unknown): error is SjabloonDiagnostic;
|
|
|
102
146
|
*
|
|
103
147
|
* @throws {TypeError} When `diag` is not a sjabloon diagnostic.
|
|
104
148
|
*/
|
|
105
|
-
export function relocate(
|
|
106
|
-
diag: unknown,
|
|
107
|
-
opts?: { prefix?: string; offset?: number },
|
|
108
|
-
): SjabloonDiagnostic;
|
|
149
|
+
export function relocate(diag: unknown, opts?: Relocation): SjabloonDiagnostic;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "sjabloon",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.12.0",
|
|
4
4
|
"description": "Tiny, CSP-safe template engine for JavaScript, powered by xprsn expressions. No eval, no new Function.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"csp",
|
|
@@ -17,7 +17,8 @@
|
|
|
17
17
|
},
|
|
18
18
|
"repository": "getquario/sjabloon",
|
|
19
19
|
"files": [
|
|
20
|
-
"lib"
|
|
20
|
+
"lib",
|
|
21
|
+
"EMBEDDING.md"
|
|
21
22
|
],
|
|
22
23
|
"type": "module",
|
|
23
24
|
"module": "lib/index.js",
|
|
@@ -61,7 +62,8 @@
|
|
|
61
62
|
"test:unit": "c8 --100 --src lib/ node --disallow-code-generation-from-strings --test --test-concurrency=1 test/*.test.js"
|
|
62
63
|
},
|
|
63
64
|
"dependencies": {
|
|
64
|
-
"
|
|
65
|
+
"waarmerk": "^0.1.0",
|
|
66
|
+
"xprsn": "^0.11.1"
|
|
65
67
|
},
|
|
66
68
|
"devDependencies": {
|
|
67
69
|
"@arethetypeswrong/cli": "^0.18.3",
|
|
@@ -83,25 +85,28 @@
|
|
|
83
85
|
"name": "sjabloon",
|
|
84
86
|
"path": "lib/index.js",
|
|
85
87
|
"ignore": [
|
|
88
|
+
"waarmerk",
|
|
86
89
|
"xprsn"
|
|
87
90
|
],
|
|
88
|
-
"limit": "2.
|
|
91
|
+
"limit": "2.35 kB"
|
|
89
92
|
},
|
|
90
93
|
{
|
|
91
94
|
"name": "sjabloon/text",
|
|
92
95
|
"path": "lib/text.js",
|
|
93
96
|
"ignore": [
|
|
97
|
+
"waarmerk",
|
|
94
98
|
"xprsn"
|
|
95
99
|
],
|
|
96
|
-
"limit": "2.
|
|
100
|
+
"limit": "2.35 kB"
|
|
97
101
|
},
|
|
98
102
|
{
|
|
99
103
|
"name": "sjabloon/html",
|
|
100
104
|
"path": "lib/html.js",
|
|
101
105
|
"ignore": [
|
|
106
|
+
"waarmerk",
|
|
102
107
|
"xprsn"
|
|
103
108
|
],
|
|
104
|
-
"limit": "2.
|
|
109
|
+
"limit": "2.4 kB"
|
|
105
110
|
}
|
|
106
111
|
],
|
|
107
112
|
"engines": {
|