mancha 0.16.7 → 0.17.3

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.
Files changed (85) hide show
  1. package/.github/workflows/ci.yml +8 -7
  2. package/README.md +88 -25
  3. package/dist/browser.d.ts +42 -3
  4. package/dist/browser.js +1 -1
  5. package/dist/browser.js.map +1 -1
  6. package/dist/css_gen_utils.js +323 -412
  7. package/dist/css_gen_utils.js.map +1 -1
  8. package/dist/dome.d.ts +2 -2
  9. package/dist/dome.js +17 -0
  10. package/dist/dome.js.map +1 -1
  11. package/dist/expressions/ast.d.ts +79 -0
  12. package/dist/expressions/ast.js +6 -0
  13. package/dist/expressions/ast.js.map +1 -0
  14. package/dist/expressions/ast.test.d.ts +1 -0
  15. package/dist/expressions/ast.test.js +122 -0
  16. package/dist/expressions/ast.test.js.map +1 -0
  17. package/dist/expressions/ast_factory.d.ts +37 -0
  18. package/dist/expressions/ast_factory.js +119 -0
  19. package/dist/expressions/ast_factory.js.map +1 -0
  20. package/dist/expressions/ast_factory.test.d.ts +1 -0
  21. package/dist/expressions/ast_factory.test.js +88 -0
  22. package/dist/expressions/ast_factory.test.js.map +1 -0
  23. package/dist/expressions/constants.d.ts +6 -0
  24. package/dist/expressions/constants.js +72 -0
  25. package/dist/expressions/constants.js.map +1 -0
  26. package/dist/expressions/constants.test.d.ts +1 -0
  27. package/dist/expressions/constants.test.js +84 -0
  28. package/dist/expressions/constants.test.js.map +1 -0
  29. package/dist/expressions/eval.d.ts +101 -0
  30. package/dist/expressions/eval.js +375 -0
  31. package/dist/expressions/eval.js.map +1 -0
  32. package/dist/expressions/eval.test.d.ts +1 -0
  33. package/dist/expressions/eval.test.js +141 -0
  34. package/dist/expressions/eval.test.js.map +1 -0
  35. package/dist/expressions/expressions.test.d.ts +6 -0
  36. package/dist/expressions/expressions.test.js +7 -0
  37. package/dist/expressions/expressions.test.js.map +1 -0
  38. package/dist/expressions/index.d.ts +6 -0
  39. package/dist/expressions/index.js +7 -0
  40. package/dist/expressions/index.js.map +1 -0
  41. package/dist/expressions/parser.d.ts +32 -0
  42. package/dist/expressions/parser.js +341 -0
  43. package/dist/expressions/parser.js.map +1 -0
  44. package/dist/expressions/parser.test.d.ts +1 -0
  45. package/dist/expressions/parser.test.js +176 -0
  46. package/dist/expressions/parser.test.js.map +1 -0
  47. package/dist/expressions/tokenizer.d.ts +49 -0
  48. package/dist/expressions/tokenizer.js +253 -0
  49. package/dist/expressions/tokenizer.js.map +1 -0
  50. package/dist/expressions/tokenizer.test.d.ts +1 -0
  51. package/dist/expressions/tokenizer.test.js +99 -0
  52. package/dist/expressions/tokenizer.test.js.map +1 -0
  53. package/dist/index.d.ts +2 -0
  54. package/dist/index.js +1 -0
  55. package/dist/index.js.map +1 -1
  56. package/dist/interfaces.d.ts +1 -1
  57. package/dist/mancha.d.ts +1 -1
  58. package/dist/mancha.js +1 -1
  59. package/dist/mancha.js.map +1 -1
  60. package/dist/plugins.js +94 -23
  61. package/dist/plugins.js.map +1 -1
  62. package/dist/renderer.d.ts +5 -2
  63. package/dist/renderer.js +6 -2
  64. package/dist/renderer.js.map +1 -1
  65. package/dist/safe_browser.d.ts +4 -3
  66. package/dist/safe_browser.js +1 -1
  67. package/dist/safe_browser.js.map +1 -1
  68. package/dist/store.d.ts +59 -13
  69. package/dist/store.js +112 -38
  70. package/dist/store.js.map +1 -1
  71. package/dist/test_utils.d.ts +2 -0
  72. package/dist/test_utils.js +14 -1
  73. package/dist/test_utils.js.map +1 -1
  74. package/dist/trusted_attributes.js +2 -0
  75. package/dist/trusted_attributes.js.map +1 -1
  76. package/dist/type_checker.d.ts +1 -0
  77. package/dist/type_checker.js +113 -41
  78. package/dist/type_checker.js.map +1 -1
  79. package/dist/worker.d.ts +2 -0
  80. package/dist/worker.js +1 -0
  81. package/dist/worker.js.map +1 -1
  82. package/docs/quickstart.md +200 -0
  83. package/gulpfile.js +5 -1
  84. package/package.json +19 -6
  85. package/tsec_exemptions.json +1 -1
@@ -10,7 +10,7 @@ on:
10
10
  permissions:
11
11
  contents: read
12
12
  pages: write
13
- id-token: write
13
+ id-token: write # Required for GitHub Pages and npm OIDC trusted publishing
14
14
 
15
15
  concurrency:
16
16
  group: "pages"
@@ -27,7 +27,7 @@ jobs:
27
27
  - name: Setup Node.js
28
28
  uses: actions/setup-node@v4
29
29
  with:
30
- node-version: '22'
30
+ node-version: '24'
31
31
  registry-url: 'https://registry.npmjs.org'
32
32
 
33
33
  - name: Install dependencies
@@ -77,6 +77,9 @@ jobs:
77
77
  runs-on: ubuntu-latest
78
78
  name: Publish to NPM
79
79
  if: startsWith(github.ref, 'refs/tags/v')
80
+ permissions:
81
+ contents: read
82
+ id-token: write # Required for npm OIDC trusted publishing
80
83
  steps:
81
84
  - name: Checkout code
82
85
  uses: actions/checkout@v4
@@ -84,7 +87,7 @@ jobs:
84
87
  - name: Setup Node.js
85
88
  uses: actions/setup-node@v4
86
89
  with:
87
- node-version: '22'
90
+ node-version: '24'
88
91
  registry-url: 'https://registry.npmjs.org'
89
92
 
90
93
  - name: Install dependencies
@@ -96,7 +99,5 @@ jobs:
96
99
  name: build-artifacts
97
100
  path: dist/
98
101
 
99
- - name: Publish to NPM
100
- run: npm publish
101
- env:
102
- NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
102
+ - name: Publish to NPM with provenance
103
+ run: npm publish --provenance --access public
package/README.md CHANGED
@@ -47,7 +47,7 @@ None of them have all the key features that make `mancha` unique:
47
47
  | Feature | mancha | Svelte | React.js | Vue.js | petite-vue | Alpine.js |
48
48
  | --------------------- | ------ | ------ | -------- | ------ | ---------- | --------- |
49
49
  | Simple to learn | ✔️ | ❌ | ❌ | ❌ | ✔️ | ✔️ |
50
- | < 15kb compressed | ✔️ | | ❌ | ❌ | ✔️ | ❌ |
50
+ | < 16kb compressed | ✔️ | ✔️ | ❌ | ❌ | ✔️ | ❌ |
51
51
  | Custom web components | ✔️ | ✔️ | ✔️ | ✔️ | ❌ | ❌ |
52
52
  | Client-side rendering | ✔️ | ❌ | ❌ | ✔️ | ✔️ | ✔️ |
53
53
  | Server-side rendering | ✔️ | ✔️ | ✔️ | ✔️ | ❌ | ❌ |
@@ -157,6 +157,17 @@ element tag or attributes match a specific criteria. Here's the list of attribut
157
157
  ```html
158
158
  <video :prop:src="buildSrc()"></video>
159
159
  ```
160
+ - `:render` links an element to a JavaScript ES module for initialization
161
+ ```html
162
+ <canvas :render="./chart-init.js"></canvas>
163
+ ```
164
+ The module's default export is called with the element and renderer:
165
+ ```js
166
+ // chart-init.js
167
+ export default function (elem, renderer) {
168
+ new Chart(elem, { type: "bar" });
169
+ }
170
+ ```
160
171
  - `{{ value }}` replaces `value` in text nodes
161
172
  ```html
162
173
  <button :data="{label: 'Click Me'}">{{ label }}</button>
@@ -177,8 +188,9 @@ element tag or attributes match a specific criteria. Here's the list of attribut
177
188
  ## Evaluation
178
189
 
179
190
  To avoid violation of Content Security Policy (CSP) that forbids the use of `eval()`, `Mancha`
180
- evaluates all expressions using [`jexpr`][jexpr]. This means that only simple expressions are
181
- allowed, and spaces must be used to separate different expression tokens. For example:
191
+ evaluates all expressions using a safe expression parser. This means that only simple expressions are
192
+ allowed, but it supports many modern JavaScript features, including optional chaining, the spread
193
+ operator, and arrow functions. For example:
182
194
 
183
195
  ```html
184
196
  <!-- Valid expression: string concatenation -->
@@ -186,6 +198,21 @@ allowed, and spaces must be used to separate different expression tokens. For ex
186
198
  <p :text="'you are number ' + pos + ' in the queue'"></p>
187
199
  </body>
188
200
 
201
+ <!-- Valid expression: optional chaining -->
202
+ <body :data="{ user: null }">
203
+ <p :text="user?.name ?? 'Anonymous'"></p>
204
+ </body>
205
+
206
+ <!-- Valid expression: spread operator -->
207
+ <body :data="{ list: [1, 2], extra: 3 }">
208
+ <div :for="item in [...list, extra]">{{ item }}</div>
209
+ </body>
210
+
211
+ <!-- Valid expression: arrow functions (e.g. in map) -->
212
+ <body :data="{ items: [1, 2, 3] }">
213
+ <div :for="n in items.map((x) => x * 2)">{{ n }}</div>
214
+ </body>
215
+
189
216
  <!-- Valid expression: boolean logic -->
190
217
  <body :data="{ pos: 1, finished: false }">
191
218
  <p :show="pos >= 1 && !finished">you are number {{ pos }} in the queue</p>
@@ -215,24 +242,13 @@ allowed, and spaces must be used to separate different expression tokens. For ex
215
242
  <button :on:click="pos = pos + 1">Click to get there faster</button>
216
243
  </body>
217
244
 
218
- <!-- Invalid expression: missing spaces -->
219
- <body :data="{ pos: 1 }">
220
- <p :text="'you are number '+pos+' in the queue'"></p>
221
- </body>
222
-
223
245
  <!-- Invalid expression: multiple statements -->
224
246
  <button :on:click="console.log('yes'); answer = 'no'"></button>
225
247
 
226
- <!-- Invalid expression: function definition -->
227
- <body :data="{ foo: () => 'yes' }">
248
+ <!-- Invalid expression: function definition (top-level) -->
249
+ <body :data="{ foo: function() { return 'yes'; } }">
228
250
  <p :text="foo()"></p>
229
251
  </body>
230
-
231
- <!-- Invalid expression: complex assignment -->
232
- <body :data="{ pos: 1 }">
233
- <p :text="'you are number ' + pos + ' in the queue'"></p>
234
- <button :on:click="pos++">Click to get there faster</button>
235
- </body>
236
252
  ```
237
253
 
238
254
  ## Variable Scoping
@@ -245,7 +261,7 @@ illustrated with an example:
245
261
  <!-- Hello, stranger -->
246
262
  <h1>Hello, {{ name }}</h1>
247
263
 
248
- <!-- undefined -->
264
+ <!-- Initially "undefined", but reactive to later changes -->
249
265
  <span>{{ message }}</span>
250
266
 
251
267
  <!-- How are you, danger? The secret message is "secret" and the key is "1234" -->
@@ -258,8 +274,10 @@ illustrated with an example:
258
274
  By default, the target root element is the `body` tag. So, any variables defined in the body's
259
275
  `:data` attribute are available to the main renderer.
260
276
 
261
- In the example above, the variable `message` is only available to the `<p>` tag and all elements
262
- under that tag, if any. Since the variables are not accessible via the global object, you'll need
277
+ In the example above, the `<span>` references `message` which is not defined in the body's `:data`.
278
+ This auto-initializes `message` to `undefined` and attaches an observer, so setting `$.message`
279
+ later will update the `<span>` content. The `<p>` tag has its own local `message` variable which
280
+ shadows any parent value. Since the variables are not accessible via the global object, you'll need
263
281
  to retrieve the renderer from the element's properties:
264
282
 
265
283
  ```js
@@ -270,8 +288,9 @@ await $.mount(document.body);
270
288
  // This modifies the `name` variable in all the renderer contexts.
271
289
  $.name = "world";
272
290
 
273
- // This has no effect in the output, because the content of the `<p>` tag is
274
- // bound to its local variable and `message` was undefined at rendering time.
291
+ // This updates the `<span>` content to "bandit" because `message` was
292
+ // auto-initialized when the template referenced it. However, the `<p>` tag
293
+ // still shows "secret" because it has its own local `message` variable.
275
294
  $.message = "bandit";
276
295
 
277
296
  // We extract the subrenderer from the element's properties. Only elements
@@ -284,7 +303,7 @@ subrenderer.$.message = "banana";
284
303
 
285
304
  When accessing variables, `mancha` searches the current renderer first, then the parent, the
286
305
  parent's parent, and so forth until the root renderer is reached. If the requested variable is not
287
- found in the current renderer or any of the ancestor renderers, then `null` is returned:
306
+ found in the current renderer or any of the ancestor renderers, then `undefined` is returned:
288
307
 
289
308
  ```html
290
309
  <body :data="{ name: 'stranger' }">
@@ -293,6 +312,52 @@ found in the current renderer or any of the ancestor renderers, then `null` is r
293
312
  </body>
294
313
  ```
295
314
 
315
+ ### Reactive Undefined Variables
316
+
317
+ When a variable is referenced in a template expression but not yet defined, `mancha` automatically
318
+ initializes it to `undefined` and attaches an observer. This means you can set the variable later
319
+ using the same renderer (or a subrenderer) and the template will reactively update:
320
+
321
+ ```html
322
+ <body>
323
+ <!-- Initially shows "undefined", but updates reactively when `message` is set -->
324
+ <p>{{ message }}</p>
325
+ </body>
326
+ <script type="module">
327
+ const { $ } = Mancha;
328
+ await $.mount(document.body);
329
+
330
+ // The template initially renders with `message` as undefined.
331
+ // Setting it now will trigger a reactive update.
332
+ $.message = "Hello, World!";
333
+ </script>
334
+ ```
335
+
336
+ This behavior is particularly useful with the `:render` attribute, where a JavaScript module can
337
+ set variables that are already referenced in the template:
338
+
339
+ ```html
340
+ <div :render="./init.js">
341
+ <!-- These variables are set by the :render callback -->
342
+ <span>{{ title }}</span>
343
+ <ul :for="item in items">
344
+ <li>{{ item }}</li>
345
+ </ul>
346
+ </div>
347
+ ```
348
+
349
+ ```js
350
+ // init.js
351
+ export default async function (elem, renderer) {
352
+ await renderer.set("title", "My List");
353
+ await renderer.set("items", ["a", "b", "c"]);
354
+ }
355
+ ```
356
+
357
+ The auto-initialization only happens when variables are accessed during an effect (such as template
358
+ rendering). Accessing a variable outside of an effect context will return `undefined` without
359
+ creating an observer.
360
+
296
361
  When setting a variable, there are 3 possible cases:
297
362
 
298
363
  1. The variable has already been defined in the current renderer. Then it gets updated in the
@@ -457,7 +522,5 @@ For a more complete example, see [examples/wrangler](./examples/wrangler).
457
522
 
458
523
  ## Dependencies
459
524
 
460
- The browser bundle contains a single external dependency, [`jexpr`][jexpr]. The unbundled version
525
+ The browser bundle contains no external dependencies. The unbundled version
461
526
  can use `htmlparser2`, which is compatible with web workers, or `jsdom`.
462
-
463
- [jexpr]: https://github.com/justinfagnani/jexpr
package/dist/browser.d.ts CHANGED
@@ -1,8 +1,11 @@
1
1
  import { IRenderer } from "./renderer.js";
2
- import { ParserParams, RenderParams } from "./interfaces.js";
2
+ import type { StoreState } from "./store.js";
3
+ import type { ParserParams, RenderParams } from "./interfaces.js";
4
+ export { IRenderer } from "./renderer.js";
5
+ export type { ParserParams, RenderParams, RendererPlugin } from "./interfaces.js";
3
6
  export { default as basicCssRules } from "./css_gen_basic.js";
4
7
  export { default as utilsCssRules } from "./css_gen_utils.js";
5
- export declare class Renderer extends IRenderer {
8
+ export declare class Renderer<T extends StoreState = StoreState> extends IRenderer<T> {
6
9
  readonly impl = "browser";
7
10
  protected readonly dirpath: string;
8
11
  parseHTML(content: string, params?: ParserParams): Document | DocumentFragment;
@@ -11,4 +14,40 @@ export declare class Renderer extends IRenderer {
11
14
  createElement(tag: string, owner?: Document | null): Element;
12
15
  textContent(node: Node, content: string): void;
13
16
  }
14
- export declare const Mancha: Renderer;
17
+ export declare const Mancha: Renderer<StoreState>;
18
+ /** Options for CSS injection. */
19
+ export type CssName = "basic" | "utils";
20
+ /**
21
+ * Injects CSS rules into the document head.
22
+ * @param names - Array of CSS names to inject ("basic", "utils").
23
+ */
24
+ export declare function injectCss(names: CssName[]): void;
25
+ /**
26
+ * Injects the basic CSS rules into the document head.
27
+ */
28
+ export declare function injectBasicCss(): void;
29
+ /**
30
+ * Injects the utils CSS rules into the document head.
31
+ */
32
+ export declare function injectUtilsCss(): void;
33
+ /** Options for initializing Mancha. */
34
+ export interface InitManchaOptions {
35
+ /** CSS styles to inject before mounting. */
36
+ css?: CssName[];
37
+ /** Target selector(s) to mount the renderer to. */
38
+ target?: string | string[];
39
+ /** Enable debug mode. */
40
+ debug?: boolean;
41
+ /** Cache policy for fetch requests. */
42
+ cache?: RequestCache;
43
+ /** Initial state to set before mounting. */
44
+ state?: Record<string, unknown>;
45
+ }
46
+ /**
47
+ * Initializes Mancha with the provided options.
48
+ * This is a convenience function for bundled environments.
49
+ *
50
+ * @param options - Initialization options.
51
+ * @returns A promise that resolves to the Renderer instance.
52
+ */
53
+ export declare function initMancha<T extends StoreState = StoreState>(options?: InitManchaOptions): Promise<Renderer<T>>;