mancha 0.6.3 → 0.6.5

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 CHANGED
@@ -1,80 +1,182 @@
1
1
  # mancha
2
2
 
3
- `mancha` is an HTML rendering library. It can work as a command-line tool, imported as a Javascript
4
- function, or as a `Gulp` plugin.
3
+ `mancha` is a simple HTML templating and rendering library for simple people. It works on the
4
+ browser or the server. It can be used as a command-line tool, imported as a Javascript module, or as
5
+ a plugin for [`Gulp`](https://gulpjs.com).
5
6
 
6
- ## Examples
7
-
8
- Here are some of the things you can use `mancha` for.
9
-
10
- ### Replace simple variables using `{{ value }}` format
11
-
12
- index.html:
13
-
14
- ```html
15
- <span>Hello {{ name }}</span>
16
- ```
17
-
18
- Command:
19
-
20
- ```bash
21
- npx mancha --input="./index.html" --vars='{"name": "World"}'
22
- ```
23
-
24
- Result:
25
-
26
- ```html
27
- <div>Hello World</div>
28
- ```
29
-
30
- ### Include files from a relative path using the `<include>` tag
31
-
32
- hello-name.html:
33
-
34
- ```html
35
- <span>Hello {{ name }}</span>
36
- ```
37
-
38
- index.html:
7
+ Here's a small sample of the things that you can do with `mancha`:
39
8
 
40
9
  ```html
41
- <div>
42
- <include src="./hello-name.html" data-name="World"></include>
43
- </div>
10
+ <!-- Use the bundled file from `unkpg`. -->
11
+ <script src="//unpkg.com/mancha" target="main" init></script>
12
+
13
+ <!-- Scoped variables using the `:data` attribute. -->
14
+ <main :data="{count: 0, name: 'Stranger'}">
15
+ <!-- Custom HTML tag element registration. -->
16
+ <template is="counter">
17
+ <div>
18
+ <slot></slot>
19
+ <button @click="count++">Counter: {{ count }}</button>
20
+ </div>
21
+ </template>
22
+
23
+ <!-- Custom HTML tag element usage. -->
24
+ <counter>Click me:</counter>
25
+
26
+ <!-- Reactive data binding. -->
27
+ <p>Enter your name: <input type="text" :bind="name" /></p>
28
+ <p>Hello, {{ name }}!</p>
29
+
30
+ <!-- Include HTML partials. -->
31
+ <include src="html/partial/footer.tpl.html"></include>
32
+ </main>
44
33
  ```
45
34
 
46
- Command:
47
-
48
- ```bash
49
- npx mancha --input="./index.html"
50
- ```
51
-
52
- Result:
53
-
54
- ```html
55
- <div>
56
- <span>Hello World</span>
57
- </div>
58
- ```
35
+ ## Why another front-end Javascript library?
36
+
37
+ There are plenty of other front-end Javascript libraries, many of them of great quality, including:
38
+
39
+ - [Google's Svelte](https://svelte.dev)
40
+ - [Meta's React](https://react.dev)
41
+ - [Vue.js](https://vuejs.org) and [petite-vue](https://github.com/vuejs/petite-vue)
42
+ - [Alpine.js](https://alpinejs.dev)
43
+
44
+ None of them have all the key features that make `mancha` unique:
45
+
46
+ | Feature | mancha | Svelte | React.js | Vue.js | petite-vue | Alpine.js |
47
+ | --------------------- | ------ | ------ | -------- | ------ | ---------- | --------- |
48
+ | Simple to learn | ✔️ | ❌ | ❌ | ❌ | ✔️ | ✔️ |
49
+ | < 10kb compressed | ✔️ | ❌ | ❌ | ❌ | ✔️ | ❌ |
50
+ | Custom web components | ✔️ | ✔️ | ✔️ | ✔️ | ❌ | ❌ |
51
+ | Client-side rendering | ✔️ | ❌ | ❌ | ✔️ | ✔️ | ✔️ |
52
+ | Server-side rendering | ✔️ | ✔️ | ✔️ | ✔️ | ❌ | ❌ |
53
+
54
+ `mancha` is great for:
55
+
56
+ - **prototyping**, just plop a script tag in your HTML and off you go
57
+ - **testing**, individual components can be rendered and tested outside the browser
58
+ - **progressive enhancement**, from simple templating and basic reactivity to a full-blown app
59
+
60
+ A core benefit of using `mancha` is that it allows you to compartmentalize the complexity of
61
+ front-end development. Whether you decide to break up your app into reusable partial sections via
62
+ `<include>` or create custom web components, you can write HTML as if your mother was watching.
63
+
64
+ `mancha` implements its own reactivity engine, so the bundled browser module contains no external
65
+ dependencies.
66
+
67
+ ## Preprocessing
68
+
69
+ As part of the rendering lifecycle, `mancha` first preprocesses the HTML. The two main stages of
70
+ preprocessing consist of:
71
+
72
+ - Resolution of `<include>` tags
73
+
74
+ ```html
75
+ <!-- ./button.tpl.html -->
76
+ <button>Click Me</button>
77
+
78
+ <!-- ./index.html -->
79
+ <div>
80
+ <include src="button.tpl.html"></include>
81
+ </div>
82
+
83
+ <!-- Result after rendering `index.html`. -->
84
+ <div>
85
+ <button>Click Me</button>
86
+ </div>
87
+ ```
88
+
89
+ - Registration and resolution of all custom web components
90
+
91
+ ```html
92
+ <!-- Use <template is="my-component-name"> to register a component. -->
93
+ <template is="my-red-button">
94
+ <button style="background-color: red;">
95
+ <slot></slot>
96
+ </button>
97
+ </template>
98
+
99
+ <!-- Any node traversed after registration can use the component. -->
100
+ <my-red-button @click="console.log('clicked')">
101
+ <!-- The contents within will replace the `<slot></slot>` tag. -->
102
+ Click Me
103
+ </my-red-button>
104
+ ```
105
+
106
+ ## Rendering
107
+
108
+ Once the HTML has been preprocessed, it is rendered by traversing every node in the DOM and applying
109
+ a series of plugins. Each plugin is only applied if specific conditions are met such as the HTML
110
+ element tag or attributes match a specific criteria. Here's the list of attributes handled:
111
+
112
+ - `:data` provides scoped variables to all subnodes
113
+ ```html
114
+ <div :data="{name: 'Stranger'}"></div>
115
+ ```
116
+ - `:for` clones the node and repeats it
117
+ ```html
118
+ <div :for="item in ['a', 'b', 'c']">{{ item }}</div>
119
+ ```
120
+ - `$text` sets the `textContent` value of a node
121
+ ```html
122
+ <div :data="{foo: 'bar'}" $text="foo"></div>
123
+ ```
124
+ - `$html` sets the `innerHTML` value of a node
125
+ ```html
126
+ <div $html="<span>Hello World</span>"></div>
127
+ ```
128
+ - `:show` toggles `$elem.style.display` to `none`
129
+ ```html
130
+ <div :data="{foo: false}" :show="foo"></div>
131
+ ```
132
+ - `@watch` executes an expression anytime its dependencies change
133
+ ```html
134
+ <div :data="{foo: 'bar'}" @watch="console.log('foo changed:', foo)"></div>
135
+ ```
136
+ - `:bind` binds (two-way) a variable to the `value` or `checked` property of the element.
137
+ ```html
138
+ <div :data="{name: 'Stranger'}">
139
+ <input type="text" :bind="name" />
140
+ </div>
141
+ ```
142
+ - `${prop}` binds (one-way) the node property `prop` with the given expression
143
+ ```html
144
+ <div :data="{foo: false}">
145
+ <input type="submit" $disabled="foo" />
146
+ </div>
147
+ ```
148
+ - `:{attr}` binds (one-way) the node attribute `attr` with the given expression
149
+ ```html
150
+ <div :data="{foo: 'bar'}">
151
+ <input type="text" :placeholder="foo" />
152
+ </div>
153
+ ```
154
+ - `@{event}` adds an event listener for `event` to the node
155
+ ```html
156
+ <button @click="console.log('clicked')"></button>
157
+ ```
158
+ - `{{ value }}` replaces `value` in text nodes
159
+ ```html
160
+ <button :data="{label: 'Click Me'}">{{ label }}</button>
161
+ ```
59
162
 
60
163
  ## Usage
61
164
 
62
165
  ### Client Side Rendering (CSR)
63
166
 
64
- To use `mancha` on the client (browser), use the `mancha.js` bundled file available via `unpkg`.
167
+ To use `mancha` on the client (browser), use the `mancha` bundled file available via `unpkg`.
65
168
 
66
169
  ```html
67
- <body>
170
+ <body :data="{ name: 'John' }">
68
171
  <span>Hello, {{ name }}!</span>
69
172
  </body>
70
173
 
71
- <script src="//unpkg.com/mancha" data-name="John" target="body" init></script>
174
+ <script src="//unpkg.com/mancha" target="body" defer init></script>
72
175
  ```
73
176
 
74
177
  Script tag attributes:
75
178
 
76
179
  - `init`: whether to automatically render upon script load
77
- - `data-name`: dataset atribute, where `data-{{key}}` will be replaced with the attribute's value
78
180
  - `target`: comma-separated document elements to render e.g. "body" or "head,body" (defaults to "body")
79
181
 
80
182
  For a more complete example, see [examples/browser](./examples/browser).
@@ -109,13 +211,20 @@ the HTML code on demand for each incoming request:
109
211
 
110
212
  ```js
111
213
  import express from "express";
112
- import { renderLocalPath } from "mancha";
214
+ import { Renderer } from "mancha";
113
215
  import vars from "./vars.json";
114
216
 
115
217
  const app = express();
116
218
 
117
219
  app.get("/", async (req, res) => {
118
- const html = await renderLocalPath("src/index.html", vars);
220
+ const name = req.query.name || "Stranger";
221
+ // Instantiate a new renderer.
222
+ const renderer = new Renderer({ name, ...vars });
223
+ // Preprocess input HTML from a local file path.
224
+ const fragment = await renderer.preprocessLocal("src/index.html");
225
+ // Render and serialize output HTML.
226
+ const html = renderer.serializeHTML(await renderer.renderNode(fragment));
227
+ // Send it to the client.
119
228
  res.set("Content-Type", "text/html");
120
229
  res.send(html);
121
230
  });
@@ -123,50 +232,56 @@ app.get("/", async (req, res) => {
123
232
  app.listen(process.env.PORT || 8080);
124
233
  ```
125
234
 
126
- For a more complete example, see [examples/rendered](./examples/rendered).
235
+ For a more complete example, see [examples/express](./examples/express).
127
236
 
128
237
  ### Web Worker Runtime Server Side Rendering (SSR)
129
238
 
130
239
  For servers hosted as worker runtimes, such as `Cloudflare Workers`, you will need to import a
131
- stripped down version of `mancha` that does not have the ability to read local files. Any HTML files
132
- will need to be separately hosted by a static server, although you can also generate strings
133
- containing HTML on demand.
240
+ stripped down version of `mancha` that does not have the ability to read local files or evaluate
241
+ expressions. The contents of any `{{ value }}` must be an existing variable in the renderer
242
+ instance, since string evaluation is not permitted in some runtimes.
134
243
 
135
244
  ```js
136
- import { renderRemotePath } from "mancha/dist/core"
137
-
138
- const VARS = {...};
139
- const HTML_ROOT = "https://example.com/html";
245
+ import { Renderer } from "mancha/dist/worker";
246
+ import htmlIndex from "./index.html";
247
+ import vars from "./vars.json";
140
248
 
141
- self.addEventListener('fetch', async event => {
142
- const content = await renderRemotePath(`${HTML_ROOT}/index.html`, VARS);
143
- event.respondWith(new Response(content, { headers: {"Content-Type": "text/html"} }))
249
+ self.addEventListener("fetch", async (event) => {
250
+ // Instantiate a new renderer.
251
+ const renderer = new Renderer({ ...vars });
252
+ // Preprocess input HTML from a string.
253
+ const fragment = await renderer.preprocessString(htmlIndex);
254
+ // Render and serialize output HTML.
255
+ const html = renderer.serializeHTML(await renderer.renderNode(fragment));
256
+ // Send it to the client.
257
+ event.respondWith(new Response(content, { headers: { "Content-Type": "text/html" } }));
144
258
  });
145
259
  ```
146
260
 
261
+ To meet the size requirements of popular worker runtimes, the worker version of `mancha` uses
262
+ `htmlparser2` instead of `jsdom` for the underlying HTML and DOM manipulation. This keeps the
263
+ footprint of `mancha` under 100kb.
264
+
147
265
  For a more complete example, see [examples/wrangler](./examples/wrangler).
148
266
 
149
- ## Compile Time `gulpfile` Scripts
267
+ ## Compile Time `Gulp` Plugin
150
268
 
151
- To use `mancha` in your `gulpfile`, you can do the following:
269
+ To use `mancha` as a `Gulp` plugin in `gulpfile.js`, you can do the following:
152
270
 
153
271
  ```js
272
+ import GulpClient from "gulp";
154
273
  import { mancha } from "mancha/dist/gulp";
155
- gulp.src(...).pipe(mancha({"myvar": "myval"})).pipe(...)
156
- ```
157
-
158
- The first argument consists of a dictionary of `<key, value>` pairs of literal string replacements.
159
- `key` will become `{{ key }}` before replacing it with `value` in the processed files. For example,
160
- if we passed `{"name": "World"}` as the argument:
161
-
162
- Source:
163
-
164
- ```html
165
- <div>Hello {{ name }}</div>
166
- ```
167
-
168
- Result:
274
+ import vars from "./vars.json";
169
275
 
170
- ```html
171
- <div>Hello World</div>
276
+ GulpClient.task("build", function () {
277
+ return (
278
+ GulpClient
279
+ // Inlcude all HTML files, but exclude all partials (ending in .tpl.html).
280
+ .src(["src/**/*.html", "!src/**/*.tpl/html"])
281
+ // Render the HTML content using `mancha`.
282
+ .pipe(mancha(vars))
283
+ // Pipe the output to the destination folder.
284
+ .pipe(GulpClient.dest("public"))
285
+ );
286
+ });
172
287
  ```
package/dist/browser.js CHANGED
@@ -1,8 +1,8 @@
1
1
  import { dirname, IRenderer } from "./core.js";
2
2
  class Renderer extends IRenderer {
3
3
  dirpath = dirname(self.location.href);
4
- parseHTML(content, params = { root: false }) {
5
- if (params.root) {
4
+ parseHTML(content, params = { rootDocument: false }) {
5
+ if (params.rootDocument) {
6
6
  return new DOMParser().parseFromString(content, "text/html");
7
7
  }
8
8
  else {
@@ -15,7 +15,7 @@ class Renderer extends IRenderer {
15
15
  return new XMLSerializer().serializeToString(root).replace(/\s?xmlns="[^"]+"/gm, "");
16
16
  }
17
17
  preprocessLocal(fpath, params) {
18
- // In the browser, "local" paths (i.e., relative) can still be fetched.
18
+ // In the browser, "local" paths (i.e., relative paths) can still be fetched.
19
19
  return this.preprocessRemote(fpath, params);
20
20
  }
21
21
  }
package/dist/cli.js CHANGED
File without changes
package/dist/core.d.ts CHANGED
@@ -1,10 +1,30 @@
1
1
  import { ReactiveProxyStore } from "./reactive.js";
2
2
  import { ParserParams, RenderParams } from "./interfaces.js";
3
3
  export type EvalListener = (result: any, dependencies: string[]) => any;
4
- export declare function traverse(root: Node | DocumentFragment | Document, skip?: Set<Node>): Generator<ChildNode>;
4
+ /**
5
+ * Returns the directory name from a given file path.
6
+ * @param fpath - The file path.
7
+ * @returns The directory name.
8
+ */
5
9
  export declare function dirname(fpath: string): string;
10
+ /**
11
+ * Checks if a given file path is a relative path.
12
+ *
13
+ * @param fpath - The file path to check.
14
+ * @returns A boolean indicating whether the file path is relative or not.
15
+ */
6
16
  export declare function isRelativePath(fpath: string): boolean;
17
+ /**
18
+ * Creates an evaluation function based on the provided code and arguments.
19
+ * @param code The code to be evaluated.
20
+ * @param args The arguments to be passed to the evaluation function. Default is an empty array.
21
+ * @returns The evaluation function.
22
+ */
7
23
  export declare function makeEvalFunction(code: string, args?: string[]): Function;
24
+ /**
25
+ * Represents an abstract class for rendering and manipulating HTML content.
26
+ * Extends the `ReactiveProxyStore` class.
27
+ */
8
28
  export declare abstract class IRenderer extends ReactiveProxyStore {
9
29
  protected debugging: boolean;
10
30
  protected readonly dirpath: string;
@@ -15,22 +35,115 @@ export declare abstract class IRenderer extends ReactiveProxyStore {
15
35
  readonly _customElements: Map<string, Node>;
16
36
  abstract parseHTML(content: string, params?: ParserParams): DocumentFragment;
17
37
  abstract serializeHTML(root: DocumentFragment | Node): string;
38
+ /**
39
+ * Sets the debugging flag for the current instance.
40
+ *
41
+ * @param flag - The flag indicating whether debugging is enabled or disabled.
42
+ * @returns The current instance of the class.
43
+ */
18
44
  debug(flag: boolean): this;
45
+ /**
46
+ * Fetches the remote file at the specified path and returns its content as a string.
47
+ * @param fpath - The path of the remote file to fetch.
48
+ * @param params - Optional parameters for the fetch operation.
49
+ * @returns A promise that resolves to the content of the remote file as a string.
50
+ */
19
51
  fetchRemote(fpath: string, params?: RenderParams): Promise<string>;
52
+ /**
53
+ * Fetches a local path and returns its content as a string.
54
+ *
55
+ * @param fpath - The file path of the resource.
56
+ * @param params - Optional render parameters.
57
+ * @returns A promise that resolves to the fetched resource as a string.
58
+ */
20
59
  fetchLocal(fpath: string, params?: RenderParams): Promise<string>;
60
+ /**
61
+ * Preprocesses a string content with optional rendering and parsing parameters.
62
+ *
63
+ * @param content - The string content to preprocess.
64
+ * @param params - Optional rendering and parsing parameters.
65
+ * @returns A promise that resolves to a DocumentFragment representing the preprocessed content.
66
+ */
21
67
  preprocessString(content: string, params?: RenderParams & ParserParams): Promise<DocumentFragment>;
22
- preprocessLocal(fpath: string, params?: RenderParams & ParserParams): Promise<DocumentFragment>;
68
+ /**
69
+ * Preprocesses a remote file by fetching its content and applying preprocessing steps.
70
+ * @param fpath - The path to the remote file.
71
+ * @param params - Optional parameters for rendering and parsing.
72
+ * @returns A Promise that resolves to a DocumentFragment representing the preprocessed content.
73
+ */
23
74
  preprocessRemote(fpath: string, params?: RenderParams & ParserParams): Promise<DocumentFragment>;
75
+ /**
76
+ * Preprocesses a local file by fetching its content and applying preprocessing steps.
77
+ * @param fpath - The path to the local file.
78
+ * @param params - Optional parameters for rendering and parsing.
79
+ * @returns A promise that resolves to the preprocessed document fragment.
80
+ */
81
+ preprocessLocal(fpath: string, params?: RenderParams & ParserParams): Promise<DocumentFragment>;
82
+ /**
83
+ * Creates a deep copy of the current renderer instance.
84
+ * @returns A new instance of the renderer with the same state as the original.
85
+ */
24
86
  clone(): IRenderer;
87
+ /**
88
+ * Logs the provided arguments if debugging is enabled.
89
+ * @param args - The arguments to be logged.
90
+ */
25
91
  log(...args: any[]): void;
92
+ /**
93
+ * Retrieves or creates a cached expression function based on the provided expression.
94
+ * @param expr - The expression to retrieve or create a cached function for.
95
+ * @returns The cached expression function.
96
+ */
26
97
  private cachedExpressionFunction;
98
+ /**
99
+ * Evaluates an expression and returns the result along with its dependencies.
100
+ * If the expression is already stored, it returns the stored value directly.
101
+ * Otherwise, it performs the expression evaluation using the cached expression function.
102
+ * @param expr - The expression to evaluate.
103
+ * @param args - Optional arguments to be passed to the expression function.
104
+ * @returns A promise that resolves to the result and the dependencies of the expression.
105
+ */
27
106
  eval(expr: string, args?: {
28
107
  [key: string]: any;
29
108
  }): Promise<[any, string[]]>;
109
+ /**
110
+ * This function is intended for internal use only.
111
+ *
112
+ * Executes the given expression and invokes the provided callback whenever the any of the
113
+ * dependencies change.
114
+ *
115
+ * @param expr - The expression to watch for changes.
116
+ * @param args - The arguments to be passed to the expression during evaluation.
117
+ * @param callback - The callback function to be invoked when the dependencies change.
118
+ * @returns A promise that resolves when the initial evaluation is complete.
119
+ */
30
120
  watchExpr(expr: string, args: {
31
121
  [key: string]: any;
32
122
  }, callback: EvalListener): Promise<void>;
33
- preprocessNode(root: Document | DocumentFragment | Node, params?: RenderParams): Promise<void>;
34
- renderNode(root: Document | DocumentFragment | Node, params?: RenderParams): Promise<Document | DocumentFragment | Node>;
123
+ /**
124
+ * Preprocesses a node by applying all the registered preprocessing plugins.
125
+ *
126
+ * @template T - The type of the input node.
127
+ * @param {T} root - The root node to preprocess.
128
+ * @param {RenderParams} [params] - Optional parameters for preprocessing.
129
+ * @returns {Promise<T>} - A promise that resolves to the preprocessed node.
130
+ */
131
+ preprocessNode<T extends Document | DocumentFragment | Node>(root: T, params?: RenderParams): Promise<T>;
132
+ /**
133
+ * Renders the node by applies all the registered rendering plugins.
134
+ *
135
+ * @template T - The type of the root node (Document, DocumentFragment, or Node).
136
+ * @param {T} root - The root node to render.
137
+ * @param {RenderParams} [params] - Optional parameters for rendering.
138
+ * @returns {Promise<T>} - A promise that resolves to the fully rendered root node.
139
+ */
140
+ renderNode<T extends Document | DocumentFragment | Node>(root: T, params?: RenderParams): Promise<T>;
141
+ /**
142
+ * Mounts the Mancha application to a root element in the DOM.
143
+ *
144
+ * @param root - The root element to mount the application to.
145
+ * @param params - Optional parameters for rendering the application.
146
+ * @returns A promise that resolves when the mounting process is complete.
147
+ */
35
148
  mount(root: Document | DocumentFragment | Node, params?: RenderParams): Promise<void>;
36
149
  }
package/dist/core.js CHANGED
@@ -1,24 +1,12 @@
1
1
  import { ReactiveProxyStore } from "./reactive.js";
2
2
  import { Iterator } from "./iterator.js";
3
3
  import { RendererPlugins } from "./plugins.js";
4
- export function* traverse(root, skip = new Set()) {
5
- const explored = new Set();
6
- const frontier = Array.from(root.childNodes).filter((node) => !skip.has(node));
7
- // Also yield the root node.
8
- yield root;
9
- while (frontier.length) {
10
- const node = frontier.pop();
11
- if (!explored.has(node)) {
12
- explored.add(node);
13
- yield node;
14
- }
15
- if (node.childNodes) {
16
- Array.from(node.childNodes)
17
- .filter((node) => !skip.has(node))
18
- .forEach((node) => frontier.push(node));
19
- }
20
- }
21
- }
4
+ import { traverse } from "./dome.js";
5
+ /**
6
+ * Returns the directory name from a given file path.
7
+ * @param fpath - The file path.
8
+ * @returns The directory name.
9
+ */
22
10
  export function dirname(fpath) {
23
11
  if (!fpath.includes("/")) {
24
12
  return "";
@@ -27,15 +15,31 @@ export function dirname(fpath) {
27
15
  return fpath.split("/").slice(0, -1).join("/");
28
16
  }
29
17
  }
18
+ /**
19
+ * Checks if a given file path is a relative path.
20
+ *
21
+ * @param fpath - The file path to check.
22
+ * @returns A boolean indicating whether the file path is relative or not.
23
+ */
30
24
  export function isRelativePath(fpath) {
31
25
  return (!fpath.includes("://") &&
32
26
  !fpath.startsWith("/") &&
33
27
  !fpath.startsWith("#") &&
34
28
  !fpath.startsWith("data:"));
35
29
  }
30
+ /**
31
+ * Creates an evaluation function based on the provided code and arguments.
32
+ * @param code The code to be evaluated.
33
+ * @param args The arguments to be passed to the evaluation function. Default is an empty array.
34
+ * @returns The evaluation function.
35
+ */
36
36
  export function makeEvalFunction(code, args = []) {
37
37
  return new Function(...args, `with (this) { return (async () => (${code}))(); }`);
38
38
  }
39
+ /**
40
+ * Represents an abstract class for rendering and manipulating HTML content.
41
+ * Extends the `ReactiveProxyStore` class.
42
+ */
39
43
  export class IRenderer extends ReactiveProxyStore {
40
44
  debugging = false;
41
45
  dirpath = "";
@@ -44,30 +48,54 @@ export class IRenderer extends ReactiveProxyStore {
44
48
  evalCallbacks = new Map();
45
49
  _skipNodes = new Set();
46
50
  _customElements = new Map();
51
+ /**
52
+ * Sets the debugging flag for the current instance.
53
+ *
54
+ * @param flag - The flag indicating whether debugging is enabled or disabled.
55
+ * @returns The current instance of the class.
56
+ */
47
57
  debug(flag) {
48
58
  this.debugging = flag;
49
59
  return this;
50
60
  }
61
+ /**
62
+ * Fetches the remote file at the specified path and returns its content as a string.
63
+ * @param fpath - The path of the remote file to fetch.
64
+ * @param params - Optional parameters for the fetch operation.
65
+ * @returns A promise that resolves to the content of the remote file as a string.
66
+ */
51
67
  async fetchRemote(fpath, params) {
52
68
  return fetch(fpath, { cache: params?.cache ?? "default" }).then((res) => res.text());
53
69
  }
70
+ /**
71
+ * Fetches a local path and returns its content as a string.
72
+ *
73
+ * @param fpath - The file path of the resource.
74
+ * @param params - Optional render parameters.
75
+ * @returns A promise that resolves to the fetched resource as a string.
76
+ */
54
77
  async fetchLocal(fpath, params) {
55
78
  return this.fetchRemote(fpath, params);
56
79
  }
80
+ /**
81
+ * Preprocesses a string content with optional rendering and parsing parameters.
82
+ *
83
+ * @param content - The string content to preprocess.
84
+ * @param params - Optional rendering and parsing parameters.
85
+ * @returns A promise that resolves to a DocumentFragment representing the preprocessed content.
86
+ */
57
87
  async preprocessString(content, params) {
58
88
  this.log("Preprocessing string content with params:\n", params);
59
89
  const fragment = this.parseHTML(content, params);
60
90
  await this.preprocessNode(fragment, params);
61
91
  return fragment;
62
92
  }
63
- async preprocessLocal(fpath, params) {
64
- const content = await this.fetchLocal(fpath, params);
65
- return this.preprocessString(content, {
66
- ...params,
67
- dirpath: dirname(fpath),
68
- root: params?.root ?? !fpath.endsWith(".tpl.html"),
69
- });
70
- }
93
+ /**
94
+ * Preprocesses a remote file by fetching its content and applying preprocessing steps.
95
+ * @param fpath - The path to the remote file.
96
+ * @param params - Optional parameters for rendering and parsing.
97
+ * @returns A Promise that resolves to a DocumentFragment representing the preprocessed content.
98
+ */
71
99
  async preprocessRemote(fpath, params) {
72
100
  const fetchOptions = {};
73
101
  if (params?.cache)
@@ -76,23 +104,60 @@ export class IRenderer extends ReactiveProxyStore {
76
104
  return this.preprocessString(content, {
77
105
  ...params,
78
106
  dirpath: dirname(fpath),
79
- root: params?.root ?? !fpath.endsWith(".tpl.html"),
107
+ rootDocument: params?.rootDocument ?? !fpath.endsWith(".tpl.html"),
80
108
  });
81
109
  }
110
+ /**
111
+ * Preprocesses a local file by fetching its content and applying preprocessing steps.
112
+ * @param fpath - The path to the local file.
113
+ * @param params - Optional parameters for rendering and parsing.
114
+ * @returns A promise that resolves to the preprocessed document fragment.
115
+ */
116
+ async preprocessLocal(fpath, params) {
117
+ const content = await this.fetchLocal(fpath, params);
118
+ return this.preprocessString(content, {
119
+ ...params,
120
+ dirpath: dirname(fpath),
121
+ rootDocument: params?.rootDocument ?? !fpath.endsWith(".tpl.html"),
122
+ });
123
+ }
124
+ /**
125
+ * Creates a deep copy of the current renderer instance.
126
+ * @returns A new instance of the renderer with the same state as the original.
127
+ */
82
128
  clone() {
83
129
  const instance = new this.constructor(Object.fromEntries(this.store.entries()));
130
+ // Custom elements are shared across all instances.
131
+ instance._customElements = this._customElements;
84
132
  return instance.debug(this.debugging);
85
133
  }
134
+ /**
135
+ * Logs the provided arguments if debugging is enabled.
136
+ * @param args - The arguments to be logged.
137
+ */
86
138
  log(...args) {
87
139
  if (this.debugging)
88
140
  console.debug(...args);
89
141
  }
142
+ /**
143
+ * Retrieves or creates a cached expression function based on the provided expression.
144
+ * @param expr - The expression to retrieve or create a cached function for.
145
+ * @returns The cached expression function.
146
+ */
90
147
  cachedExpressionFunction(expr) {
91
148
  if (!this.expressionCache.has(expr)) {
92
149
  this.expressionCache.set(expr, makeEvalFunction(expr, this.evalkeys));
93
150
  }
94
151
  return this.expressionCache.get(expr);
95
152
  }
153
+ /**
154
+ * Evaluates an expression and returns the result along with its dependencies.
155
+ * If the expression is already stored, it returns the stored value directly.
156
+ * Otherwise, it performs the expression evaluation using the cached expression function.
157
+ * @param expr - The expression to evaluate.
158
+ * @param args - Optional arguments to be passed to the expression function.
159
+ * @returns A promise that resolves to the result and the dependencies of the expression.
160
+ */
96
161
  async eval(expr, args = {}) {
97
162
  if (this.store.has(expr)) {
98
163
  // Shortcut: if the expression is just an item from the value store, use that directly.
@@ -113,6 +178,17 @@ export class IRenderer extends ReactiveProxyStore {
113
178
  return [result, dependencies];
114
179
  }
115
180
  }
181
+ /**
182
+ * This function is intended for internal use only.
183
+ *
184
+ * Executes the given expression and invokes the provided callback whenever the any of the
185
+ * dependencies change.
186
+ *
187
+ * @param expr - The expression to watch for changes.
188
+ * @param args - The arguments to be passed to the expression during evaluation.
189
+ * @param callback - The callback function to be invoked when the dependencies change.
190
+ * @returns A promise that resolves when the initial evaluation is complete.
191
+ */
116
192
  watchExpr(expr, args, callback) {
117
193
  // Early exit: this eval has already been registered, we just need to add our callback.
118
194
  if (this.evalCallbacks.has(expr)) {
@@ -138,8 +214,16 @@ export class IRenderer extends ReactiveProxyStore {
138
214
  };
139
215
  return inner();
140
216
  }
217
+ /**
218
+ * Preprocesses a node by applying all the registered preprocessing plugins.
219
+ *
220
+ * @template T - The type of the input node.
221
+ * @param {T} root - The root node to preprocess.
222
+ * @param {RenderParams} [params] - Optional parameters for preprocessing.
223
+ * @returns {Promise<T>} - A promise that resolves to the preprocessed node.
224
+ */
141
225
  async preprocessNode(root, params) {
142
- params = Object.assign({ dirpath: this.dirpath, maxdepth: 10 }, params);
226
+ params = { dirpath: this.dirpath, maxdepth: 10, ...params };
143
227
  const promises = new Iterator(traverse(root, this._skipNodes)).map(async (node) => {
144
228
  this.log("Preprocessing node:\n", node);
145
229
  // Resolve all the includes in the node.
@@ -153,7 +237,17 @@ export class IRenderer extends ReactiveProxyStore {
153
237
  });
154
238
  // Wait for all the rendering operations to complete.
155
239
  await Promise.all(promises.generator());
240
+ // Return the input node, which should now be fully preprocessed.
241
+ return root;
156
242
  }
243
+ /**
244
+ * Renders the node by applies all the registered rendering plugins.
245
+ *
246
+ * @template T - The type of the root node (Document, DocumentFragment, or Node).
247
+ * @param {T} root - The root node to render.
248
+ * @param {RenderParams} [params] - Optional parameters for rendering.
249
+ * @returns {Promise<T>} - A promise that resolves to the fully rendered root node.
250
+ */
157
251
  async renderNode(root, params) {
158
252
  // Iterate over all the nodes and apply appropriate handlers.
159
253
  // Do these steps one at a time to avoid any potential race conditions.
@@ -185,10 +279,20 @@ export class IRenderer extends ReactiveProxyStore {
185
279
  // Return the input node, which should now be fully rendered.
186
280
  return root;
187
281
  }
282
+ /**
283
+ * Mounts the Mancha application to a root element in the DOM.
284
+ *
285
+ * @param root - The root element to mount the application to.
286
+ * @param params - Optional parameters for rendering the application.
287
+ * @returns A promise that resolves when the mounting process is complete.
288
+ */
188
289
  async mount(root, params) {
290
+ params = { ...params, rootNode: root };
189
291
  // Preprocess all the elements recursively first.
190
292
  await this.preprocessNode(root, params);
191
293
  // Now that the DOM is complete, render all the nodes.
192
294
  await this.renderNode(root, params);
295
+ // Attach ourselves to the HTML node.
296
+ root.renderer = this;
193
297
  }
194
298
  }
package/dist/dome.d.ts CHANGED
@@ -3,6 +3,15 @@ type __Node = Node | _Node;
3
3
  type __ParentNode = ParentNode | _ParentNode;
4
4
  type __Element = Element | _Element;
5
5
  type __ChildNode = ChildNode | _ChildNode;
6
+ /**
7
+ * Traverses the DOM tree starting from the given root node and yields each child node.
8
+ * Nodes in the `skip` set will be skipped during traversal.
9
+ *
10
+ * @param root - The root node to start the traversal from.
11
+ * @param skip - A set of nodes to skip during traversal.
12
+ * @returns A generator that yields each child node in the DOM tree.
13
+ */
14
+ export declare function traverse(root: Node | DocumentFragment | Document, skip?: Set<Node>): Generator<ChildNode>;
6
15
  /**
7
16
  * Converts from an attribute name to camelCase, e.g. `foo-bar` becomes `fooBar`.
8
17
  * @param name attribute name
@@ -13,6 +22,7 @@ export declare function getAttribute(elem: __Element, name: string): string | nu
13
22
  export declare function setAttribute(elem: __Element, name: string, value: string): void;
14
23
  export declare function removeAttribute(elem: __Element, name: string): void;
15
24
  export declare function cloneAttribute(elemFrom: __Element, elemDest: __Element, name: string): void;
25
+ export declare function firstElementChild(elem: __Element): __Element | null;
16
26
  export declare function replaceWith(original: __ChildNode, ...replacement: __Node[]): void;
17
27
  export declare function appendChild(parent: __Node, node: __Node): __Node;
18
28
  export declare function removeChild(parent: __Node, node: __Node): __Node;
package/dist/dome.js CHANGED
@@ -1,5 +1,31 @@
1
1
  import { Element as _Element, Node as _Node, Text as _Text, } from "domhandler";
2
2
  import { DomUtils } from "htmlparser2";
3
+ /**
4
+ * Traverses the DOM tree starting from the given root node and yields each child node.
5
+ * Nodes in the `skip` set will be skipped during traversal.
6
+ *
7
+ * @param root - The root node to start the traversal from.
8
+ * @param skip - A set of nodes to skip during traversal.
9
+ * @returns A generator that yields each child node in the DOM tree.
10
+ */
11
+ export function* traverse(root, skip = new Set()) {
12
+ const explored = new Set();
13
+ const frontier = Array.from(root.childNodes).filter((node) => !skip.has(node));
14
+ // Also yield the root node.
15
+ yield root;
16
+ while (frontier.length) {
17
+ const node = frontier.shift();
18
+ if (!explored.has(node)) {
19
+ explored.add(node);
20
+ yield node;
21
+ }
22
+ if (node.childNodes) {
23
+ Array.from(node.childNodes)
24
+ .filter((node) => !skip.has(node))
25
+ .forEach((node) => frontier.push(node));
26
+ }
27
+ }
28
+ }
3
29
  function hasFunction(obj, func) {
4
30
  return typeof obj?.[func] === "function";
5
31
  }
@@ -34,8 +60,16 @@ export function cloneAttribute(elemFrom, elemDest, name) {
34
60
  elemDest.attribs[name] = elemFrom.attribs[name];
35
61
  }
36
62
  else {
37
- const attr = elemFrom.getAttributeNode(name);
38
- elemDest.setAttributeNode(attr?.cloneNode(true));
63
+ const attr = elemFrom?.getAttributeNode?.(name);
64
+ elemDest?.setAttributeNode?.(attr?.cloneNode(true));
65
+ }
66
+ }
67
+ export function firstElementChild(elem) {
68
+ if (elem instanceof _Element) {
69
+ return elem.children.find((child) => child instanceof _Element);
70
+ }
71
+ else {
72
+ return elem.firstElementChild;
39
73
  }
40
74
  }
41
75
  export function replaceWith(original, ...replacement) {
@@ -26,7 +26,7 @@ function mancha(context = {}) {
26
26
  if (file.isBuffer()) {
27
27
  const chunk = file.contents.toString(encoding);
28
28
  renderer
29
- .preprocessString(chunk, { dirpath, root: !file.path.endsWith(".tpl.html") })
29
+ .preprocessString(chunk, { dirpath, rootDocument: !file.path.endsWith(".tpl.html") })
30
30
  .then(async (fragment) => {
31
31
  await renderer.renderNode(fragment);
32
32
  const content = renderer.serializeHTML(fragment);
@@ -48,7 +48,7 @@ function mancha(context = {}) {
48
48
  })
49
49
  .on("end", () => {
50
50
  renderer
51
- .preprocessString(docstr, { dirpath, root: !file.path.endsWith(".tpl.html") })
51
+ .preprocessString(docstr, { dirpath, rootDocument: !file.path.endsWith(".tpl.html") })
52
52
  .then(async (document) => {
53
53
  await renderer.renderNode(document);
54
54
  const content = renderer.serializeHTML(document);
package/dist/index.d.ts CHANGED
@@ -1,6 +1,5 @@
1
1
  import { ParserParams, RenderParams } from "./interfaces.js";
2
2
  import { IRenderer } from "./core.js";
3
- /** The Node Mancha renderer is just like the worker renderer, but it also uses the filesystem. */
4
3
  export declare class Renderer extends IRenderer {
5
4
  parseHTML(content: string, params?: ParserParams): DocumentFragment;
6
5
  serializeHTML(root: Node | DocumentFragment | Document): string;
package/dist/index.js CHANGED
@@ -1,11 +1,10 @@
1
1
  import * as fs from "fs/promises";
2
2
  import { JSDOM } from "jsdom";
3
3
  import { IRenderer } from "./core.js";
4
- /** The Node Mancha renderer is just like the worker renderer, but it also uses the filesystem. */
5
4
  export class Renderer extends IRenderer {
6
- parseHTML(content, params = { root: false }) {
5
+ parseHTML(content, params = { rootDocument: false }) {
7
6
  const dom = new JSDOM();
8
- if (params.root) {
7
+ if (params.rootDocument) {
9
8
  const DOMParser = dom.window.DOMParser;
10
9
  return new DOMParser().parseFromString(content, "text/html");
11
10
  }
@@ -24,5 +23,5 @@ export class Renderer extends IRenderer {
24
23
  return fs.readFile(fpath, { encoding: params?.encoding || "utf8" });
25
24
  }
26
25
  }
27
- // Export the renderer instance directly.
26
+ // Export a global renderer instance directly.
28
27
  export const Mancha = new Renderer();
@@ -2,7 +2,7 @@
2
2
  import { IRenderer } from "./core.js";
3
3
  export interface ParserParams {
4
4
  /** Whether the file parsed is a root document, or a document fragment. */
5
- root?: boolean;
5
+ rootDocument?: boolean;
6
6
  /** Encoding to use when processing local files. */
7
7
  encoding?: BufferEncoding;
8
8
  }
@@ -14,5 +14,7 @@ export interface RenderParams {
14
14
  maxdepth?: number;
15
15
  /** Cache policy used when resolving remote paths. */
16
16
  cache?: RequestCache | null;
17
+ /** Whether the current node is the root used in Mancha.moun(...). */
18
+ rootNode?: Node;
17
19
  }
18
20
  export type RendererPlugin = (this: IRenderer, node: ChildNode, params?: RenderParams) => Promise<void>;
@@ -3,6 +3,7 @@ export declare class Iterator<T> {
3
3
  constructor(iter: Iterable<T>);
4
4
  filter(fn: (val: T) => boolean): Iterator<T>;
5
5
  map<S>(fn: (val: T) => S): Iterator<S>;
6
+ find(fn: (val: T) => boolean): T | undefined;
6
7
  array(): T[];
7
8
  generator(): Iterable<T>;
8
9
  static filterGenerator<T>(fn: (val: T) => boolean, iter: Iterable<T>): Iterable<T>;
package/dist/iterator.js CHANGED
@@ -9,6 +9,13 @@ export class Iterator {
9
9
  map(fn) {
10
10
  return new Iterator(Iterator.mapGenerator(fn, this.iterable));
11
11
  }
12
+ find(fn) {
13
+ for (const val of this.iterable) {
14
+ if (fn(val))
15
+ return val;
16
+ }
17
+ return undefined;
18
+ }
12
19
  array() {
13
20
  return Array.from(this.iterable);
14
21
  }
package/dist/mancha.js CHANGED
@@ -1 +1 @@
1
- (()=>{"use strict";class t{timeouts=new Map;debounce(t,e){return new Promise(((s,r)=>{const n=this.timeouts.get(e);n&&clearTimeout(n),this.timeouts.set(e,setTimeout((()=>{try{s(e()),this.timeouts.delete(e)}catch(t){r(t)}}),t))}))}}function e(t,r,n=!0){if(null==t||function(t){return t instanceof s||t.__is_proxy__}(t))return t;if(n)for(const s in t)t.hasOwnProperty(s)&&"object"==typeof t[s]&&null!=t[s]&&(t[s]=e(t[s],r));return new Proxy(t,{deleteProperty:(t,e)=>e in t&&(delete t[e],r(),!0),set:(t,s,i,a)=>{n&&"object"==typeof i&&(i=e(i,r));const o=Reflect.set(t,s,i,a);return r(),o},get:(t,e,s)=>"__is_proxy__"===e||Reflect.get(t,e,s)})}class s extends t{value=null;listeners=[];constructor(t=null,...e){super(),this.value=this.wrapObjValue(t),e.forEach((t=>this.watch(t)))}static from(t,...e){return t instanceof s?(e.forEach(t.watch),t):new s(t,...e)}wrapObjValue(t){return null===t||"object"!=typeof t?t:e(t,(()=>this.trigger()))}get(){return this.value}async set(t){if(this.value!==t){const e=this.value;this.value=this.wrapObjValue(t),await this.trigger(e)}}watch(t){this.listeners.push(t)}unwatch(t){this.listeners=this.listeners.filter((e=>e!==t))}trigger(t=null){const e=this.listeners.slice();return this.debounce(10,(()=>Promise.all(e.map((e=>e(this.value,t)))).then((()=>{}))))}}class r extends t{store=new Map;debouncedListeners=new Map;lock=Promise.resolve();constructor(t){super();for(const[e,r]of Object.entries(t||{}))this.store.set(e,s.from(this.wrapFnValue(r)))}wrapFnValue(t){return t&&"function"==typeof t?(...e)=>t.call(n(this),...e):t}get $(){return n(this)}entries(){return this.store.entries()}get(t){return this.store.get(t)?.get()}async set(t,e){this.store.has(t)?await this.store.get(t).set(this.wrapFnValue(e)):this.store.set(t,s.from(this.wrapFnValue(e)))}del(t){return this.store.delete(t)}has(t){return this.store.has(t)}async update(t){await Promise.all(Object.entries(t).map((([t,e])=>this.set(t,e))))}watch(t,e){t=Array.isArray(t)?t:[t];const s=()=>e(...t.map((t=>this.store.get(t).get()))),r=()=>this.debounce(10,s);t.forEach((t=>this.store.get(t).watch(r))),this.debouncedListeners.set(e,r)}unwatch(t,e){(t=Array.isArray(t)?t:[t]).forEach((t=>this.store.get(t).unwatch(this.debouncedListeners.get(e)))),this.debouncedListeners.delete(e)}async trigger(t){t=Array.isArray(t)?t:[t],await Promise.all(t.map((t=>this.store.get(t).trigger())))}async trace(t){const e=new Set,s=n(this,((t,s)=>{"get"===t&&e.add(s)}));return[await t.call(s),Array.from(e)]}async computed(t,e){const[s,r]=await this.trace(e);this.watch(r,(async()=>this.set(t,await e.call(n(this))))),this.set(t,s)}}function n(t,e=(()=>{})){const s=Array.from(t.entries()).map((([t])=>t)),r=Object.fromEntries(s.map((t=>[t,void 0])));return new Proxy(Object.assign({},t,r),{get:(s,r,n)=>"string"==typeof r&&t.has(r)?(e("get",r),t.get(r)):"get"===r?s=>(e("get",s),t.get(s)):Reflect.get(t,r,n),set:(s,r,n,i)=>("string"!=typeof r||r in t?Reflect.set(t,r,n,i):(e("set",r,n),t.set(r,n)),!0)})}class i{iterable;constructor(t){this.iterable=t}filter(t){return new i(i.filterGenerator(t,this.iterable))}map(t){return new i(i.mapGenerator(t,this.iterable))}array(){return Array.from(this.iterable)}*generator(){for(const t of this.iterable)yield t}static*filterGenerator(t,e){for(const s of e)t(s)&&(yield s)}static*mapGenerator(t,e){for(const s of e)yield t(s)}static equals(t,e){const s=t[Symbol.iterator](),r=e[Symbol.iterator]();let n=s.next(),i=r.next();for(;!n.done&&!i.done;){if(n.value!==i.value)return!1;n=s.next(),i=r.next()}return n.done===i.done}}var a,o;(o=a||(a={})).Root="root",o.Text="text",o.Directive="directive",o.Comment="comment",o.Script="script",o.Style="style",o.Tag="tag",o.CDATA="cdata",o.Doctype="doctype",a.Root,a.Text,a.Directive,a.Comment,a.Script,a.Style,a.Tag,a.CDATA,a.Doctype;class c{constructor(){this.parent=null,this.prev=null,this.next=null,this.startIndex=null,this.endIndex=null}get parentNode(){return this.parent}set parentNode(t){this.parent=t}get previousSibling(){return this.prev}set previousSibling(t){this.prev=t}get nextSibling(){return this.next}set nextSibling(t){this.next=t}cloneNode(t=!1){return w(this,t)}}class l extends c{constructor(t){super(),this.data=t}get nodeValue(){return this.data}set nodeValue(t){this.data=t}}class h extends l{constructor(){super(...arguments),this.type=a.Text}get nodeType(){return 3}}class u extends l{constructor(){super(...arguments),this.type=a.Comment}get nodeType(){return 8}}class d extends l{constructor(t,e){super(e),this.name=t,this.type=a.Directive}get nodeType(){return 1}}class p extends c{constructor(t){super(),this.children=t}get firstChild(){var t;return null!==(t=this.children[0])&&void 0!==t?t:null}get lastChild(){return this.children.length>0?this.children[this.children.length-1]:null}get childNodes(){return this.children}set childNodes(t){this.children=t}}class f extends p{constructor(){super(...arguments),this.type=a.CDATA}get nodeType(){return 4}}class m extends p{constructor(){super(...arguments),this.type=a.Root}get nodeType(){return 9}}class g extends p{constructor(t,e,s=[],r=("script"===t?a.Script:"style"===t?a.Style:a.Tag)){super(s),this.name=t,this.attribs=e,this.type=r}get nodeType(){return 1}get tagName(){return this.name}set tagName(t){this.name=t}get attributes(){return Object.keys(this.attribs).map((t=>{var e,s;return{name:t,value:this.attribs[t],namespace:null===(e=this["x-attribsNamespace"])||void 0===e?void 0:e[t],prefix:null===(s=this["x-attribsPrefix"])||void 0===s?void 0:s[t]}}))}}function w(t,e=!1){let s;if(function(t){return t.type===a.Text}(t))s=new h(t.data);else if(function(t){return t.type===a.Comment}(t))s=new u(t.data);else if(function(t){return(e=t).type===a.Tag||e.type===a.Script||e.type===a.Style;var e}(t)){const r=e?y(t.children):[],n=new g(t.name,{...t.attribs},r);r.forEach((t=>t.parent=n)),null!=t.namespace&&(n.namespace=t.namespace),t["x-attribsNamespace"]&&(n["x-attribsNamespace"]={...t["x-attribsNamespace"]}),t["x-attribsPrefix"]&&(n["x-attribsPrefix"]={...t["x-attribsPrefix"]}),s=n}else if(function(t){return t.type===a.CDATA}(t)){const r=e?y(t.children):[],n=new f(r);r.forEach((t=>t.parent=n)),s=n}else if(function(t){return t.type===a.Root}(t)){const r=e?y(t.children):[],n=new m(r);r.forEach((t=>t.parent=n)),t["x-mode"]&&(n["x-mode"]=t["x-mode"]),s=n}else{if(!function(t){return t.type===a.Directive}(t))throw new Error(`Not implemented yet: ${t.type}`);{const e=new d(t.name,t.data);null!=t["x-name"]&&(e["x-name"]=t["x-name"],e["x-publicId"]=t["x-publicId"],e["x-systemId"]=t["x-systemId"]),s=e}}return s.startIndex=t.startIndex,s.endIndex=t.endIndex,null!=t.sourceCodeLocation&&(s.sourceCodeLocation=t.sourceCodeLocation),s}function y(t){const e=t.map((t=>w(t,!0)));for(let t=1;t<e.length;t++)e[t].prev=e[t-1],e[t-1].next=e[t];return e}function b(t,e){return"function"==typeof t?.[e]}function v(t,e){return t instanceof g?t.attribs?.[e]:t.getAttribute?.(e)}function x(t,e,s){t instanceof g?t.attribs[e]=s:t.setAttribute?.(e,s)}function N(t,e){t instanceof g?delete t.attribs[e]:t.removeAttribute?.(e)}function A(t,e,s){if(t instanceof g&&e instanceof g)e.attribs[s]=t.attribs[s];else{const r=t.getAttributeNode(s);e.setAttributeNode(r?.cloneNode(!0))}}function $(t,...e){if(b(t,"replaceWith"))return t.replaceWith(...e);{const s=t,r=s.parentNode,n=Array.from(r.childNodes).indexOf(s);e.forEach((t=>t.parentNode=r)),r.childNodes=[].concat(Array.from(r.childNodes).slice(0,n)).concat(e).concat(Array.from(r.childNodes).slice(n+1))}}function E(t,e){return b(e,"appendChild")?t.appendChild(e):(t.childNodes.push(e),e.parentNode=t,e)}function C(t,e){if(b(e,"removeChild"))return t.removeChild(e);{const s=e;return t.childNodes=t.children.filter((t=>t!==s)),s}}function k(t,e,s){return s?b(t,"insertBefore")?t.insertBefore(e,s):($(s,e,s),e):E(t,e)}window.htmlparser2;const _=new Set([":bind",":bind-events",":data",":for",":show","@watch","$html"]);var S;function*T(t,e=new Set){const s=new Set,r=Array.from(t.childNodes).filter((t=>!e.has(t)));for(yield t;r.length;){const t=r.pop();s.has(t)||(s.add(t),yield t),t.childNodes&&Array.from(t.childNodes).filter((t=>!e.has(t))).forEach((t=>r.push(t)))}}function L(t){return t.includes("/")?t.split("/").slice(0,-1).join("/"):""}function P(t){return!(t.includes("://")||t.startsWith("/")||t.startsWith("#")||t.startsWith("data:"))}!function(t){t.resolveIncludes=async function(t,e){const s=t;if("include"!==s.tagName?.toLocaleLowerCase())return;this.log("<include> tag found in:\n",t),this.log("<include> params:",e);const r=v(s,"src");if(!r)throw new Error(`"src" attribute missing from ${t}.`);const n=e=>{const r=e.firstChild;for(const t of Array.from(s.attributes))r&&"src"!==t.name&&A(s,r,t.name);$(t,...e.childNodes)},i={...e,root:!1,maxdepth:e?.maxdepth-1};if(0===i.maxdepth)throw new Error("Maximum recursion depth reached.");if(r.includes("://")||r.startsWith("//"))this.log("Including remote file from absolute path:",r),await this.preprocessRemote(r,i).then(n);else if(e?.dirpath?.includes("://")||e?.dirpath?.startsWith("//")){const t=e.dirpath&&"."!==e.dirpath?`${e.dirpath}/${r}`:r;this.log("Including remote file from relative path:",t),await this.preprocessRemote(t,i).then(n)}else if("/"===r.charAt(0))this.log("Including local file from absolute path:",r),await this.preprocessLocal(r,i).then(n);else{const t=e?.dirpath&&"."!==e?.dirpath?`${e?.dirpath}/${r}`:r;this.log("Including local file from relative path:",t),await this.preprocessLocal(t,i).then(n)}},t.rebaseRelativePaths=async function(t,e){const s=t,r=s.tagName?.toLowerCase();if(!e?.dirpath)return;const n=v(s,"src"),i=v(s,"href"),a=v(s,"data"),o=n||i||a;o&&(o&&P(o)&&this.log("Rebasing relative path as:",e.dirpath,"/",o),"img"===r&&n&&P(n)?x(s,"src",`${e.dirpath}/${n}`):"a"===r&&i&&P(i)||"link"===r&&i&&P(i)?x(s,"href",`${e.dirpath}/${i}`):"script"===r&&n&&P(n)||"source"===r&&n&&P(n)||"audio"===r&&n&&P(n)||"video"===r&&n&&P(n)||"track"===r&&n&&P(n)||"iframe"===r&&n&&P(n)?x(s,"src",`${e.dirpath}/${n}`):"object"===r&&a&&P(a)?x(s,"data",`${e.dirpath}/${a}`):"input"===r&&n&&P(n)?x(s,"src",`${e.dirpath}/${n}`):("area"===r&&i&&P(i)||"base"===r&&i&&P(i))&&x(s,"href",`${e.dirpath}/${i}`))},t.registerCustomElements=async function(t,e){const s=t;if("template"===s.tagName?.toLowerCase()&&v(s,"is")){this.log("Registering custom element:\n",s);const t=v(s,"is");this._customElements.has(t)||this._customElements.set(t,s),C(s.parentNode,s)}},t.resolveCustomElements=async function(t,e){const s=t,r=s.tagName?.toLowerCase();if(this._customElements.has(r)){this.log("Processing custom element:\n",s);const e=this._customElements.get(r),n=(e.content||e).cloneNode(!0),i=n.firstChild;for(const t of Array.from(s.attributes))i&&A(s,i,t.name);$(t,...n.childNodes)}},t.resolveTextNodeExpressions=async function(t,e){if(3!==t.nodeType)return;const s=function(t){return t instanceof c?t.data:t.nodeValue}(t)||"";this.log("Processing node content value:\n",s);const r=new RegExp(/{{ ([^}]+) }}/gm),n=Array.from(s.matchAll(r)).map((t=>t[1])),i=async()=>{let e=s;for(const s of n){const[r]=await this.eval(s,{$elem:t});e=e.replace(`{{ ${s} }}`,String(r))}!function(t,e){t instanceof c?t.data=e:t.nodeValue=e}(t,e)};await Promise.all(n.map((e=>this.watchExpr(e,{$elem:t},i))))},t.resolveDataAttribute=async function(t,e){if(this._skipNodes.has(t))return;const s=t,r=v(s,":data");if(r){this.log(":data attribute found in:\n",t),N(s,":data");const n=this.clone();t.renderer=n;const[i]=await n.eval(r,{$elem:t});await n.update(i);for(const e of T(t,this._skipNodes))this._skipNodes.add(e);await n.mount(t,e)}},t.resolveWatchAttribute=async function(t,e){if(this._skipNodes.has(t))return;const s=t,r=v(s,"@watch");r&&(this.log("@watch attribute found in:\n",t),N(s,"@watch"),await this.watchExpr(r,{$elem:t},(()=>{})))},t.resolveTextAttributes=async function(t,e){if(this._skipNodes.has(t))return;const s=t,r=v(s,"$text");r&&(this.log("$text attribute found in:\n",t),N(s,"$text"),await this.watchExpr(r,{$elem:t},(e=>function(t,e){t instanceof g?t.children=[new h(e)]:t.textContent=e}(t,e))))},t.resolveHtmlAttribute=async function(t,e){if(this._skipNodes.has(t))return;const s=t,r=v(s,"$html");if(r){this.log("$html attribute found in:\n",t),N(s,"$html");const n=this.clone();await this.watchExpr(r,{$elem:t},(async t=>{const r=await n.preprocessString(t,e);await n.renderNode(r,e),function(t,...e){b(t,"replaceChildren")?t.replaceChildren(...e):(t.childNodes=e,e.forEach((e=>e.parentNode=t)))}(s,r)}))}},t.resolvePropAttributes=async function(t,e){if(this._skipNodes.has(t))return;const s=t;for(const e of Array.from(s.attributes||[]))if(e.name.startsWith("$")&&!_.has(e.name)){this.log(e.name,"attribute found in:\n",t),N(s,e.name);const r=e.name.slice(1).replace(/-./g,(t=>t[1].toUpperCase()));await this.watchExpr(e.value,{$elem:t},(e=>t[r]=e))}},t.resolveAttrAttributes=async function(t,e){if(this._skipNodes.has(t))return;const s=t;for(const e of Array.from(s.attributes||[]))if(e.name.startsWith(":")&&!_.has(e.name)){this.log(e.name,"attribute found in:\n",t),N(s,e.name);const r=e.name.slice(1);await this.watchExpr(e.value,{$elem:t},(t=>x(s,r,t)))}},t.resolveEventAttributes=async function(t,e){if(this._skipNodes.has(t))return;const s=t;for(const e of Array.from(s.attributes||[]))e.name.startsWith("@")&&!_.has(e.name)&&(this.log(e.name,"attribute found in:\n",t),N(s,e.name),t.addEventListener?.(e.name.substring(1),(s=>{this.eval(e.value,{$elem:t,$event:s})})))},t.resolveForAttribute=async function(t,e){if(this._skipNodes.has(t))return;const s=t,r=v(s,":for")?.trim();if(r){this.log(":for attribute found in:\n",t),N(s,":for");for(const e of T(t,this._skipNodes))this._skipNodes.add(e);const n=t.parentNode,i=function(t,e){return e?e.createElement(t):new g(t,{})}("template",t.ownerDocument);k(n,i,t),C(n,t),E(i,t),this.log(":for template:\n",i);const a=r.split(" in ",2);if(2!==a.length)throw new Error(`Invalid :for format: \`${r}\`. Expected "{key} in {expression}".`);const o=[],[c,l]=a;await this.watchExpr(l,{$elem:t},(s=>(this.log(":for list items:",s),this.lock=this.lock.then((()=>new Promise((async r=>{if(o.splice(0,o.length).forEach((t=>{C(n,t),this._skipNodes.delete(t)})),!Array.isArray(s))return console.error(`Expression did not yield a list: \`${l}\` => \`${s}\``),r();for(const r of s){const s=this.clone();await s.set(c,r);const n=t.cloneNode(!0);o.push(n),this._skipNodes.add(n),await s.mount(n,e),this.log("Rendered list child:\n",n,n.outerHTML)}const a=i.nextSibling;for(const t of o)k(n,t,a);r()})))).catch((t=>{throw console.error(t),new Error(t)})).then(),this.lock)))}},t.resolveBindAttribute=async function(t,e){if(this._skipNodes.has(t))return;const s=t,r=v(s,":bind");if(r){this.log(":bind attribute found in:\n",t);const e=["change","input"],n=v(s,":bind-events")?.split(",")||e;N(s,":bind"),N(s,":bind-events");const i="checkbox"===v(s,"type")?"checked":"value",a=`$elem.${i} = ${r}`;await this.watchExpr(a,{$elem:t},(t=>s[i]=t));const o=`${r} = $elem.${i}`;for(const e of n)t.addEventListener(e,(()=>this.eval(o,{$elem:t})))}},t.resolveShowAttribute=async function(t,e){if(this._skipNodes.has(t))return;const s=t,r=v(s,":show");if(r){this.log(":show attribute found in:\n",t),N(s,":show");const e="none"===s.style?.display?"":s.style?.display??v(s,"style")?.split(";")?.find((t=>"display"===t.split(":")[0]))?.split(":")?.at(1)?.trim();await this.watchExpr(r,{$elem:t},(t=>{s.style?s.style.display=t?e:"none":x(s,"style",`display: ${t?e:"none"};`)}))}}}(S||(S={}));class R extends r{debugging=!1;dirpath="";evalkeys=["$elem","$event"];expressionCache=new Map;evalCallbacks=new Map;_skipNodes=new Set;_customElements=new Map;debug(t){return this.debugging=t,this}async fetchRemote(t,e){return fetch(t,{cache:e?.cache??"default"}).then((t=>t.text()))}async fetchLocal(t,e){return this.fetchRemote(t,e)}async preprocessString(t,e){this.log("Preprocessing string content with params:\n",e);const s=this.parseHTML(t,e);return await this.preprocessNode(s,e),s}async preprocessLocal(t,e){const s=await this.fetchLocal(t,e);return this.preprocessString(s,{...e,dirpath:L(t),root:e?.root??!t.endsWith(".tpl.html")})}async preprocessRemote(t,e){const s={};e?.cache&&(s.cache=e.cache);const r=await fetch(t,s).then((t=>t.text()));return this.preprocessString(r,{...e,dirpath:L(t),root:e?.root??!t.endsWith(".tpl.html")})}clone(){return new this.constructor(Object.fromEntries(this.store.entries())).debug(this.debugging)}log(...t){this.debugging&&console.debug(...t)}cachedExpressionFunction(t){return this.expressionCache.has(t)||this.expressionCache.set(t,function(t,e=[]){return new Function(...e,`with (this) { return (async () => (${t}))(); }`)}(t,this.evalkeys)),this.expressionCache.get(t)}async eval(t,e={}){if(this.store.has(t))return[this.get(t),[t]];{const s=this.cachedExpressionFunction(t),r=this.evalkeys.map((t=>e[t]));if(Object.keys(e).some((t=>!this.evalkeys.includes(t))))throw new Error(`Invalid argument key, must be one of: ${this.evalkeys.join(", ")}`);const[n,i]=await this.trace((async function(){return s.call(this,...r)}));return this.log(`eval \`${t}\` => `,n,`[ ${i.join(", ")} ]`),[n,i]}}watchExpr(t,e,s){if(this.evalCallbacks.has(t))return this.evalCallbacks.get(t)?.push(s),this.eval(t,e).then((([t,e])=>s(t,e)));this.evalCallbacks.set(t,[s]);const r=[],n=async()=>{const[s,i]=await this.eval(t,e),a=this.evalCallbacks.get(t)||[];await Promise.all(a.map((t=>t(s,i)))),r.length>0&&this.unwatch(r,n),r.splice(0,r.length,...i),this.watch(i,n)};return n()}async preprocessNode(t,e){e=Object.assign({dirpath:this.dirpath,maxdepth:10},e);const s=new i(T(t,this._skipNodes)).map((async t=>{this.log("Preprocessing node:\n",t),await S.resolveIncludes.call(this,t,e),await S.rebaseRelativePaths.call(this,t,e),await S.registerCustomElements.call(this,t,e),await S.resolveCustomElements.call(this,t,e)}));await Promise.all(s.generator())}async renderNode(t,e){for(const s of T(t,this._skipNodes))this.log("Rendering node:\n",s),await S.resolveDataAttribute.call(this,s,e),await S.resolveForAttribute.call(this,s,e),await S.resolveTextAttributes.call(this,s,e),await S.resolveHtmlAttribute.call(this,s,e),await S.resolveShowAttribute.call(this,s,e),await S.resolveWatchAttribute.call(this,s,e),await S.resolveBindAttribute.call(this,s,e),await S.resolvePropAttributes.call(this,s,e),await S.resolveAttrAttributes.call(this,s,e),await S.resolveEventAttributes.call(this,s,e),await S.resolveTextNodeExpressions.call(this,s,e);return t}async mount(t,e){await this.preprocessNode(t,e),await this.renderNode(t,e)}}const j=new class extends R{dirpath=L(self.location.href);parseHTML(t,e={root:!1}){if(e.root)return(new DOMParser).parseFromString(t,"text/html");{const e=document.createRange();return e.selectNodeContents(document.body),e.createContextualFragment(t)}}serializeHTML(t){return(new XMLSerializer).serializeToString(t).replace(/\s?xmlns="[^"]+"/gm,"")}preprocessLocal(t,e){return this.preprocessRemote(t,e)}};self.Mancha=j;const I=self.document?.currentScript;if(self.document?.currentScript?.hasAttribute("init")){const t=I?.hasAttribute("debug"),e=I?.getAttribute("cache"),s=I?.getAttribute("target")?.split(",")||["body"];window.addEventListener("load",(()=>{s.map((async s=>{const r=self.document.querySelector(s);await j.debug(t).mount(r,{cache:e})}))}))}})();
1
+ (()=>{"use strict";class t{timeouts=new Map;debounce(t,e){return new Promise(((s,n)=>{const r=this.timeouts.get(e);r&&clearTimeout(r),this.timeouts.set(e,setTimeout((()=>{try{s(e()),this.timeouts.delete(e)}catch(t){n(t)}}),t))}))}}function e(t,n,r=!0){if(null==t||function(t){return t instanceof s||t.__is_proxy__}(t)||t instanceof Promise)return t;if(r)for(const s in t)t.hasOwnProperty(s)&&"object"==typeof t[s]&&null!=t[s]&&(t[s]=e(t[s],n));return new Proxy(t,{deleteProperty:(t,e)=>e in t&&(delete t[e],n(),!0),set:(t,s,i,a)=>{r&&"object"==typeof i&&(i=e(i,n));const o=Reflect.set(t,s,i,a);return n(),o},get:(t,e,s)=>"__is_proxy__"===e||Reflect.get(t,e,s)})}class s extends t{value=null;listeners=[];constructor(t=null,...e){super(),this.value=this.wrapObjValue(t),e.forEach((t=>this.watch(t)))}static from(t,...e){return t instanceof s?(e.forEach(t.watch),t):new s(t,...e)}wrapObjValue(t){return null===t||"object"!=typeof t?t:e(t,(()=>this.trigger()))}get(){return this.value}async set(t){if(this.value!==t){const e=this.value;this.value=this.wrapObjValue(t),await this.trigger(e)}}watch(t){this.listeners.push(t)}unwatch(t){this.listeners=this.listeners.filter((e=>e!==t))}trigger(t=null){const e=this.listeners.slice();return this.debounce(10,(()=>Promise.all(e.map((e=>e(this.value,t)))).then((()=>{}))))}}class n extends t{store=new Map;debouncedListeners=new Map;lock=Promise.resolve();constructor(t){super();for(const[e,n]of Object.entries(t||{}))this.store.set(e,s.from(this.wrapFnValue(n)))}wrapFnValue(t){return t&&"function"==typeof t?(...e)=>t.call(r(this),...e):t}get $(){return r(this)}entries(){return this.store.entries()}get(t){return this.store.get(t)?.get()}async set(t,e){this.store.has(t)?await this.store.get(t).set(this.wrapFnValue(e)):this.store.set(t,s.from(this.wrapFnValue(e)))}del(t){return this.store.delete(t)}has(t){return this.store.has(t)}async update(t){await Promise.all(Object.entries(t).map((([t,e])=>this.set(t,e))))}watch(t,e){t=Array.isArray(t)?t:[t];const s=()=>e(...t.map((t=>this.store.get(t).get()))),n=()=>this.debounce(10,s);t.forEach((t=>this.store.get(t).watch(n))),this.debouncedListeners.set(e,n)}unwatch(t,e){(t=Array.isArray(t)?t:[t]).forEach((t=>this.store.get(t).unwatch(this.debouncedListeners.get(e)))),this.debouncedListeners.delete(e)}async trigger(t){t=Array.isArray(t)?t:[t],await Promise.all(t.map((t=>this.store.get(t).trigger())))}async trace(t){const e=new Set,s=r(this,((t,s)=>{"get"===t&&e.add(s)}));return[await t.call(s),Array.from(e)]}async computed(t,e){const[s,n]=await this.trace(e);this.watch(n,(async()=>this.set(t,await e.call(r(this))))),this.set(t,s)}}function r(t,e=(()=>{})){const s=Array.from(t.entries()).map((([t])=>t)),n=Object.fromEntries(s.map((t=>[t,void 0])));return new Proxy(Object.assign({},t,n),{get:(s,n,r)=>"string"==typeof n&&t.has(n)?(e("get",n),t.get(n)):"get"===n?s=>(e("get",s),t.get(s)):Reflect.get(t,n,r),set:(s,n,r,i)=>("string"!=typeof n||n in t?Reflect.set(t,n,r,i):(e("set",n,r),t.set(n,r)),!0)})}class i{iterable;constructor(t){this.iterable=t}filter(t){return new i(i.filterGenerator(t,this.iterable))}map(t){return new i(i.mapGenerator(t,this.iterable))}find(t){for(const e of this.iterable)if(t(e))return e}array(){return Array.from(this.iterable)}*generator(){for(const t of this.iterable)yield t}static*filterGenerator(t,e){for(const s of e)t(s)&&(yield s)}static*mapGenerator(t,e){for(const s of e)yield t(s)}static equals(t,e){const s=t[Symbol.iterator](),n=e[Symbol.iterator]();let r=s.next(),i=n.next();for(;!r.done&&!i.done;){if(r.value!==i.value)return!1;r=s.next(),i=n.next()}return r.done===i.done}}var a,o;(o=a||(a={})).Root="root",o.Text="text",o.Directive="directive",o.Comment="comment",o.Script="script",o.Style="style",o.Tag="tag",o.CDATA="cdata",o.Doctype="doctype",a.Root,a.Text,a.Directive,a.Comment,a.Script,a.Style,a.Tag,a.CDATA,a.Doctype;class c{constructor(){this.parent=null,this.prev=null,this.next=null,this.startIndex=null,this.endIndex=null}get parentNode(){return this.parent}set parentNode(t){this.parent=t}get previousSibling(){return this.prev}set previousSibling(t){this.prev=t}get nextSibling(){return this.next}set nextSibling(t){this.next=t}cloneNode(t=!1){return w(this,t)}}class l extends c{constructor(t){super(),this.data=t}get nodeValue(){return this.data}set nodeValue(t){this.data=t}}class h extends l{constructor(){super(...arguments),this.type=a.Text}get nodeType(){return 3}}class u extends l{constructor(){super(...arguments),this.type=a.Comment}get nodeType(){return 8}}class d extends l{constructor(t,e){super(e),this.name=t,this.type=a.Directive}get nodeType(){return 1}}class p extends c{constructor(t){super(),this.children=t}get firstChild(){var t;return null!==(t=this.children[0])&&void 0!==t?t:null}get lastChild(){return this.children.length>0?this.children[this.children.length-1]:null}get childNodes(){return this.children}set childNodes(t){this.children=t}}class f extends p{constructor(){super(...arguments),this.type=a.CDATA}get nodeType(){return 4}}class m extends p{constructor(){super(...arguments),this.type=a.Root}get nodeType(){return 9}}class g extends p{constructor(t,e,s=[],n=("script"===t?a.Script:"style"===t?a.Style:a.Tag)){super(s),this.name=t,this.attribs=e,this.type=n}get nodeType(){return 1}get tagName(){return this.name}set tagName(t){this.name=t}get attributes(){return Object.keys(this.attribs).map((t=>{var e,s;return{name:t,value:this.attribs[t],namespace:null===(e=this["x-attribsNamespace"])||void 0===e?void 0:e[t],prefix:null===(s=this["x-attribsPrefix"])||void 0===s?void 0:s[t]}}))}}function w(t,e=!1){let s;if(function(t){return t.type===a.Text}(t))s=new h(t.data);else if(function(t){return t.type===a.Comment}(t))s=new u(t.data);else if(function(t){return(e=t).type===a.Tag||e.type===a.Script||e.type===a.Style;var e}(t)){const n=e?y(t.children):[],r=new g(t.name,{...t.attribs},n);n.forEach((t=>t.parent=r)),null!=t.namespace&&(r.namespace=t.namespace),t["x-attribsNamespace"]&&(r["x-attribsNamespace"]={...t["x-attribsNamespace"]}),t["x-attribsPrefix"]&&(r["x-attribsPrefix"]={...t["x-attribsPrefix"]}),s=r}else if(function(t){return t.type===a.CDATA}(t)){const n=e?y(t.children):[],r=new f(n);n.forEach((t=>t.parent=r)),s=r}else if(function(t){return t.type===a.Root}(t)){const n=e?y(t.children):[],r=new m(n);n.forEach((t=>t.parent=r)),t["x-mode"]&&(r["x-mode"]=t["x-mode"]),s=r}else{if(!function(t){return t.type===a.Directive}(t))throw new Error(`Not implemented yet: ${t.type}`);{const e=new d(t.name,t.data);null!=t["x-name"]&&(e["x-name"]=t["x-name"],e["x-publicId"]=t["x-publicId"],e["x-systemId"]=t["x-systemId"]),s=e}}return s.startIndex=t.startIndex,s.endIndex=t.endIndex,null!=t.sourceCodeLocation&&(s.sourceCodeLocation=t.sourceCodeLocation),s}function y(t){const e=t.map((t=>w(t,!0)));for(let t=1;t<e.length;t++)e[t].prev=e[t-1],e[t-1].next=e[t];return e}function*b(t,e=new Set){const s=new Set,n=Array.from(t.childNodes).filter((t=>!e.has(t)));for(yield t;n.length;){const t=n.shift();s.has(t)||(s.add(t),yield t),t.childNodes&&Array.from(t.childNodes).filter((t=>!e.has(t))).forEach((t=>n.push(t)))}}function v(t,e){return"function"==typeof t?.[e]}function x(t,e){return t instanceof g?t.attribs?.[e]:t.getAttribute?.(e)}function N(t,e,s){t instanceof g?t.attribs[e]=s:t.setAttribute?.(e,s)}function A(t,e){t instanceof g?delete t.attribs[e]:t.removeAttribute?.(e)}function $(t,e,s){if(t instanceof g&&e instanceof g)e.attribs[s]=t.attribs[s];else{const n=t?.getAttributeNode?.(s);e?.setAttributeNode?.(n?.cloneNode(!0))}}function E(t,...e){if(v(t,"replaceWith"))return t.replaceWith(...e);{const s=t,n=s.parentNode,r=Array.from(n.childNodes).indexOf(s);e.forEach((t=>t.parentNode=n)),n.childNodes=[].concat(Array.from(n.childNodes).slice(0,r)).concat(e).concat(Array.from(n.childNodes).slice(r+1))}}function C(t,e){return v(e,"appendChild")?t.appendChild(e):(t.childNodes.push(e),e.parentNode=t,e)}function k(t,e){if(v(e,"removeChild"))return t.removeChild(e);{const s=e;return t.childNodes=t.children.filter((t=>t!==s)),s}}function _(t,e,s){return s?v(t,"insertBefore")?t.insertBefore(e,s):(E(s,e,s),e):C(t,e)}window.htmlparser2;const S=new Set([":bind",":bind-events",":data",":for",":show","@watch","$html"]);var T;function L(t){return t.includes("/")?t.split("/").slice(0,-1).join("/"):""}function P(t){return!(t.includes("://")||t.startsWith("/")||t.startsWith("#")||t.startsWith("data:"))}!function(t){t.resolveIncludes=async function(t,e){const s=t;if("include"!==s.tagName?.toLocaleLowerCase())return;this.log("<include> tag found in:\n",t),this.log("<include> params:",e);const n=x(s,"src");if(!n)throw new Error(`"src" attribute missing from ${t}.`);const r=e=>{const n=e.firstChild;for(const t of Array.from(s.attributes))n&&"src"!==t.name&&$(s,n,t.name);E(t,...e.childNodes)},i={...e,rootDocument:!1,maxdepth:e?.maxdepth-1};if(0===i.maxdepth)throw new Error("Maximum recursion depth reached.");if(n.includes("://")||n.startsWith("//"))this.log("Including remote file from absolute path:",n),await this.preprocessRemote(n,i).then(r);else if(e?.dirpath?.includes("://")||e?.dirpath?.startsWith("//")){const t=n.startsWith("/")?n:`${e.dirpath}/${n}`;this.log("Including remote file from relative path:",t),await this.preprocessRemote(t,i).then(r)}else if("/"===n.charAt(0))this.log("Including local file from absolute path:",n),await this.preprocessLocal(n,i).then(r);else{const t=e?.dirpath&&"."!==e?.dirpath?`${e?.dirpath}/${n}`:n;this.log("Including local file from relative path:",t),await this.preprocessLocal(t,i).then(r)}},t.rebaseRelativePaths=async function(t,e){const s=t,n=s.tagName?.toLowerCase();if(!e?.dirpath)return;const r=x(s,"src"),i=x(s,"href"),a=x(s,"data"),o=r||i||a;o&&(o&&P(o)&&this.log("Rebasing relative path as:",e.dirpath,"/",o),"img"===n&&r&&P(r)?N(s,"src",`${e.dirpath}/${r}`):"a"===n&&i&&P(i)||"link"===n&&i&&P(i)?N(s,"href",`${e.dirpath}/${i}`):"script"===n&&r&&P(r)||"source"===n&&r&&P(r)||"audio"===n&&r&&P(r)||"video"===n&&r&&P(r)||"track"===n&&r&&P(r)||"iframe"===n&&r&&P(r)?N(s,"src",`${e.dirpath}/${r}`):"object"===n&&a&&P(a)?N(s,"data",`${e.dirpath}/${a}`):"input"===n&&r&&P(r)?N(s,"src",`${e.dirpath}/${r}`):("area"===n&&i&&P(i)||"base"===n&&i&&P(i))&&N(s,"href",`${e.dirpath}/${i}`))},t.registerCustomElements=async function(t,e){const s=t;if("template"===s.tagName?.toLowerCase()&&x(s,"is")){const t=x(s,"is")?.toLowerCase();this._customElements.has(t)||(this.log(`Registering custom element: ${t}\n`,s),this._customElements.set(t,s.cloneNode(!0)),k(s.parentNode,s))}},t.resolveCustomElements=async function(t,e){const s=t,n=s.tagName?.toLowerCase();if(this._customElements.has(n)){this.log(`Processing custom element: ${n}\n`,s);const e=this._customElements.get(n),r=(e.content||e).cloneNode(!0),a=function(t){return t instanceof g?t.children.find((t=>t instanceof g)):t.firstElementChild}(r);for(const t of Array.from(s.attributes))a&&$(s,a,t.name);const o=new i(b(r)).find((t=>"slot"===t.tagName?.toLowerCase()));o&&E(o,...s.childNodes),E(t,...r.childNodes)}},t.resolveTextNodeExpressions=async function(t,e){if(3!==t.nodeType)return;const s=function(t){return t instanceof c?t.data:t.nodeValue}(t)||"";this.log("Processing node content value:\n",s);const n=new RegExp(/{{ ([^}]+) }}/gm),r=Array.from(s.matchAll(n)).map((t=>t[1])),i=async()=>{let e=s;for(const s of r){const[n]=await this.eval(s,{$elem:t});e=e.replace(`{{ ${s} }}`,String(n))}!function(t,e){t instanceof c?t.data=e:t.nodeValue=e}(t,e)};await Promise.all(r.map((e=>this.watchExpr(e,{$elem:t},i))))},t.resolveDataAttribute=async function(t,e){if(this._skipNodes.has(t))return;const s=t,n=x(s,":data");if(n)if(this.log(":data attribute found in:\n",t),A(s,":data"),e?.rootNode===t){const[e]=await this.eval(n,{$elem:t});await this.update(e)}else{const s=this.clone();t.renderer=s;const[r]=await s.eval(n,{$elem:t});await s.update(r);for(const e of b(t,this._skipNodes))this._skipNodes.add(e);await s.mount(t,e)}},t.resolveWatchAttribute=async function(t,e){if(this._skipNodes.has(t))return;const s=t,n=x(s,"@watch");n&&(this.log("@watch attribute found in:\n",t),A(s,"@watch"),await this.watchExpr(n,{$elem:t},(()=>{})))},t.resolveTextAttributes=async function(t,e){if(this._skipNodes.has(t))return;const s=t,n=x(s,"$text");n&&(this.log("$text attribute found in:\n",t),A(s,"$text"),await this.watchExpr(n,{$elem:t},(e=>function(t,e){t instanceof g?t.children=[new h(e)]:t.textContent=e}(t,e))))},t.resolveHtmlAttribute=async function(t,e){if(this._skipNodes.has(t))return;const s=t,n=x(s,"$html");if(n){this.log("$html attribute found in:\n",t),A(s,"$html");const r=this.clone();await this.watchExpr(n,{$elem:t},(async t=>{const n=await r.preprocessString(t,e);await r.renderNode(n,e),function(t,...e){v(t,"replaceChildren")?t.replaceChildren(...e):(t.childNodes=e,e.forEach((e=>e.parentNode=t)))}(s,n)}))}},t.resolvePropAttributes=async function(t,e){if(this._skipNodes.has(t))return;const s=t;for(const e of Array.from(s.attributes||[]))if(e.name.startsWith("$")&&!S.has(e.name)){this.log(e.name,"attribute found in:\n",t),A(s,e.name);const n=e.name.slice(1).replace(/-./g,(t=>t[1].toUpperCase()));await this.watchExpr(e.value,{$elem:t},(e=>t[n]=e))}},t.resolveAttrAttributes=async function(t,e){if(this._skipNodes.has(t))return;const s=t;for(const e of Array.from(s.attributes||[]))if(e.name.startsWith(":")&&!S.has(e.name)){this.log(e.name,"attribute found in:\n",t),A(s,e.name);const n=e.name.slice(1);await this.watchExpr(e.value,{$elem:t},(t=>N(s,n,t)))}},t.resolveEventAttributes=async function(t,e){if(this._skipNodes.has(t))return;const s=t;for(const e of Array.from(s.attributes||[]))e.name.startsWith("@")&&!S.has(e.name)&&(this.log(e.name,"attribute found in:\n",t),A(s,e.name),t.addEventListener?.(e.name.substring(1),(s=>{this.eval(e.value,{$elem:t,$event:s})})))},t.resolveForAttribute=async function(t,e){if(this._skipNodes.has(t))return;const s=t,n=x(s,":for")?.trim();if(n){this.log(":for attribute found in:\n",t),A(s,":for");for(const e of b(t,this._skipNodes))this._skipNodes.add(e);const r=t.parentNode,i=function(t,e){return e?e.createElement(t):new g(t,{})}("template",t.ownerDocument);_(r,i,t),k(r,t),C(i,t),this.log(":for template:\n",i);const a=n.split(" in ",2);if(2!==a.length)throw new Error(`Invalid :for format: \`${n}\`. Expected "{key} in {expression}".`);const o=[],[c,l]=a;await this.watchExpr(l,{$elem:t},(s=>(this.log(":for list items:",s),this.lock=this.lock.then((()=>new Promise((async n=>{if(o.splice(0,o.length).forEach((t=>{k(r,t),this._skipNodes.delete(t)})),!Array.isArray(s))return console.error(`Expression did not yield a list: \`${l}\` => \`${s}\``),n();for(const n of s){const s=this.clone();await s.set(c,n);const r=t.cloneNode(!0);o.push(r),this._skipNodes.add(r),await s.mount(r,e),this.log("Rendered list child:\n",r,r.outerHTML)}const a=i.nextSibling;for(const t of o)_(r,t,a);n()})))).catch((t=>{throw console.error(t),new Error(t)})).then(),this.lock)))}},t.resolveBindAttribute=async function(t,e){if(this._skipNodes.has(t))return;const s=t,n=x(s,":bind");if(n){this.log(":bind attribute found in:\n",t);const e=["change","input"],r=x(s,":bind-events")?.split(",")||e;A(s,":bind"),A(s,":bind-events");const i="checkbox"===x(s,"type")?"checked":"value",a=`$elem.${i} = ${n}`;await this.watchExpr(a,{$elem:t},(t=>s[i]=t));const o=`${n} = $elem.${i}`;for(const e of r)t.addEventListener(e,(()=>this.eval(o,{$elem:t})))}},t.resolveShowAttribute=async function(t,e){if(this._skipNodes.has(t))return;const s=t,n=x(s,":show");if(n){this.log(":show attribute found in:\n",t),A(s,":show");const e="none"===s.style?.display?"":s.style?.display??x(s,"style")?.split(";")?.find((t=>"display"===t.split(":")[0]))?.split(":")?.at(1)?.trim();await this.watchExpr(n,{$elem:t},(t=>{s.style?s.style.display=t?e:"none":N(s,"style",`display: ${t?e:"none"};`)}))}}}(T||(T={}));class R extends n{debugging=!1;dirpath="";evalkeys=["$elem","$event"];expressionCache=new Map;evalCallbacks=new Map;_skipNodes=new Set;_customElements=new Map;debug(t){return this.debugging=t,this}async fetchRemote(t,e){return fetch(t,{cache:e?.cache??"default"}).then((t=>t.text()))}async fetchLocal(t,e){return this.fetchRemote(t,e)}async preprocessString(t,e){this.log("Preprocessing string content with params:\n",e);const s=this.parseHTML(t,e);return await this.preprocessNode(s,e),s}async preprocessRemote(t,e){const s={};e?.cache&&(s.cache=e.cache);const n=await fetch(t,s).then((t=>t.text()));return this.preprocessString(n,{...e,dirpath:L(t),rootDocument:e?.rootDocument??!t.endsWith(".tpl.html")})}async preprocessLocal(t,e){const s=await this.fetchLocal(t,e);return this.preprocessString(s,{...e,dirpath:L(t),rootDocument:e?.rootDocument??!t.endsWith(".tpl.html")})}clone(){const t=new this.constructor(Object.fromEntries(this.store.entries()));return t._customElements=this._customElements,t.debug(this.debugging)}log(...t){this.debugging&&console.debug(...t)}cachedExpressionFunction(t){return this.expressionCache.has(t)||this.expressionCache.set(t,function(t,e=[]){return new Function(...e,`with (this) { return (async () => (${t}))(); }`)}(t,this.evalkeys)),this.expressionCache.get(t)}async eval(t,e={}){if(this.store.has(t))return[this.get(t),[t]];{const s=this.cachedExpressionFunction(t),n=this.evalkeys.map((t=>e[t]));if(Object.keys(e).some((t=>!this.evalkeys.includes(t))))throw new Error(`Invalid argument key, must be one of: ${this.evalkeys.join(", ")}`);const[r,i]=await this.trace((async function(){return s.call(this,...n)}));return this.log(`eval \`${t}\` => `,r,`[ ${i.join(", ")} ]`),[r,i]}}watchExpr(t,e,s){if(this.evalCallbacks.has(t))return this.evalCallbacks.get(t)?.push(s),this.eval(t,e).then((([t,e])=>s(t,e)));this.evalCallbacks.set(t,[s]);const n=[],r=async()=>{const[s,i]=await this.eval(t,e),a=this.evalCallbacks.get(t)||[];await Promise.all(a.map((t=>t(s,i)))),n.length>0&&this.unwatch(n,r),n.splice(0,n.length,...i),this.watch(i,r)};return r()}async preprocessNode(t,e){e={dirpath:this.dirpath,maxdepth:10,...e};const s=new i(b(t,this._skipNodes)).map((async t=>{this.log("Preprocessing node:\n",t),await T.resolveIncludes.call(this,t,e),await T.rebaseRelativePaths.call(this,t,e),await T.registerCustomElements.call(this,t,e),await T.resolveCustomElements.call(this,t,e)}));return await Promise.all(s.generator()),t}async renderNode(t,e){for(const s of b(t,this._skipNodes))this.log("Rendering node:\n",s),await T.resolveDataAttribute.call(this,s,e),await T.resolveForAttribute.call(this,s,e),await T.resolveTextAttributes.call(this,s,e),await T.resolveHtmlAttribute.call(this,s,e),await T.resolveShowAttribute.call(this,s,e),await T.resolveWatchAttribute.call(this,s,e),await T.resolveBindAttribute.call(this,s,e),await T.resolvePropAttributes.call(this,s,e),await T.resolveAttrAttributes.call(this,s,e),await T.resolveEventAttributes.call(this,s,e),await T.resolveTextNodeExpressions.call(this,s,e);return t}async mount(t,e){e={...e,rootNode:t},await this.preprocessNode(t,e),await this.renderNode(t,e),t.renderer=this}}const D=new class extends R{dirpath=L(self.location.href);parseHTML(t,e={rootDocument:!1}){if(e.rootDocument)return(new DOMParser).parseFromString(t,"text/html");{const e=document.createRange();return e.selectNodeContents(document.body),e.createContextualFragment(t)}}serializeHTML(t){return(new XMLSerializer).serializeToString(t).replace(/\s?xmlns="[^"]+"/gm,"")}preprocessLocal(t,e){return this.preprocessRemote(t,e)}};self.Mancha=D;const I=self.document?.currentScript;if(self.document?.currentScript?.hasAttribute("init")){const t=I?.hasAttribute("debug"),e=I?.getAttribute("cache"),s=I?.getAttribute("target")?.split(",")||["body"];window.addEventListener("load",(()=>{s.map((async s=>{const n=self.document.querySelector(s);await D.debug(t).mount(n,{cache:e})}))}))}})();
package/dist/plugins.js CHANGED
@@ -1,5 +1,6 @@
1
- import { appendChild, attributeNameToCamelCase, cloneAttribute, createElement, getAttribute, getNodeValue, insertBefore, removeAttribute, removeChild, replaceChildren, replaceWith, setAttribute, setNodeValue, setTextContent, } from "./dome.js";
2
- import { isRelativePath, traverse } from "./core.js";
1
+ import { appendChild, attributeNameToCamelCase, cloneAttribute, createElement, firstElementChild, getAttribute, getNodeValue, insertBefore, removeAttribute, removeChild, replaceChildren, replaceWith, setAttribute, setNodeValue, setTextContent, traverse, } from "./dome.js";
2
+ import { isRelativePath } from "./core.js";
3
+ import { Iterator } from "./iterator.js";
3
4
  const KW_ATTRIBUTES = new Set([
4
5
  ":bind",
5
6
  ":bind-events",
@@ -36,7 +37,11 @@ export var RendererPlugins;
36
37
  replaceWith(node, ...fragment.childNodes);
37
38
  };
38
39
  // Compute the subparameters being passed down to the included file.
39
- const subparameters = { ...params, root: false, maxdepth: params?.maxdepth - 1 };
40
+ const subparameters = {
41
+ ...params,
42
+ rootDocument: false,
43
+ maxdepth: params?.maxdepth - 1,
44
+ };
40
45
  if (subparameters.maxdepth === 0)
41
46
  throw new Error("Maximum recursion depth reached.");
42
47
  // Case 1: Absolute remote path.
@@ -46,7 +51,7 @@ export var RendererPlugins;
46
51
  // Case 2: Relative remote path.
47
52
  }
48
53
  else if (params?.dirpath?.includes("://") || params?.dirpath?.startsWith("//")) {
49
- const relpath = params.dirpath && params.dirpath !== "." ? `${params.dirpath}/${src}` : src;
54
+ const relpath = src.startsWith("/") ? src : `${params.dirpath}/${src}`;
50
55
  this.log("Including remote file from relative path:", relpath);
51
56
  await this.preprocessRemote(relpath, subparameters).then(handler);
52
57
  // Case 3: Local absolute path.
@@ -122,27 +127,33 @@ export var RendererPlugins;
122
127
  RendererPlugins.registerCustomElements = async function (node, params) {
123
128
  const elem = node;
124
129
  if (elem.tagName?.toLowerCase() === "template" && getAttribute(elem, "is")) {
125
- this.log("Registering custom element:\n", elem);
126
- const tagName = getAttribute(elem, "is");
127
- if (!this._customElements.has(tagName))
128
- this._customElements.set(tagName, elem);
129
- // Remove the node from the DOM.
130
- removeChild(elem.parentNode, elem);
130
+ const tagName = getAttribute(elem, "is")?.toLowerCase();
131
+ if (!this._customElements.has(tagName)) {
132
+ this.log(`Registering custom element: ${tagName}\n`, elem);
133
+ this._customElements.set(tagName, elem.cloneNode(true));
134
+ // Remove the node from the DOM.
135
+ removeChild(elem.parentNode, elem);
136
+ }
131
137
  }
132
138
  };
133
139
  RendererPlugins.resolveCustomElements = async function (node, params) {
134
140
  const elem = node;
135
141
  const tagName = elem.tagName?.toLowerCase();
136
142
  if (this._customElements.has(tagName)) {
137
- this.log("Processing custom element:\n", elem);
143
+ this.log(`Processing custom element: ${tagName}\n`, elem);
138
144
  const template = this._customElements.get(tagName);
139
145
  const clone = (template.content || template).cloneNode(true);
140
146
  // Add whatever attributes the custom element tag had to the first child.
141
- const child = clone.firstChild;
147
+ const child = firstElementChild(clone);
142
148
  for (const attr of Array.from(elem.attributes)) {
143
149
  if (child)
144
150
  cloneAttribute(elem, child, attr.name);
145
151
  }
152
+ // If there's a <slot> element, replace it with the contents of the custom element.
153
+ const iter = new Iterator(traverse(clone));
154
+ const slot = iter.find((x) => x.tagName?.toLowerCase() === "slot");
155
+ if (slot)
156
+ replaceWith(slot, ...elem.childNodes);
146
157
  // Replace the custom element tag with the contents of the template.
147
158
  replaceWith(node, ...clone.childNodes);
148
159
  }
@@ -177,17 +188,23 @@ export var RendererPlugins;
177
188
  this.log(":data attribute found in:\n", node);
178
189
  // Remove the attribute from the node.
179
190
  removeAttribute(elem, ":data");
180
- // Create a subrenderer and process the tag.
181
- const subrenderer = this.clone();
182
- node.renderer = subrenderer;
183
- const [result] = await subrenderer.eval(dataAttr, { $elem: node });
184
- await subrenderer.update(result);
185
- // Skip all the children of the current node.
186
- for (const child of traverse(node, this._skipNodes)) {
187
- this._skipNodes.add(child);
191
+ // Create a subrenderer and process the tag, unless it's the root node.
192
+ if (params?.rootNode === node) {
193
+ const [result] = await this.eval(dataAttr, { $elem: node });
194
+ await this.update(result);
195
+ }
196
+ else {
197
+ const subrenderer = this.clone();
198
+ node.renderer = subrenderer;
199
+ const [result] = await subrenderer.eval(dataAttr, { $elem: node });
200
+ await subrenderer.update(result);
201
+ // Skip all the children of the current node.
202
+ for (const child of traverse(node, this._skipNodes)) {
203
+ this._skipNodes.add(child);
204
+ }
205
+ // Mount the current node with the subrenderer.
206
+ await subrenderer.mount(node, params);
188
207
  }
189
- // Mount the current node with the subrenderer.
190
- await subrenderer.mount(node, params);
191
208
  }
192
209
  };
193
210
  RendererPlugins.resolveWatchAttribute = async function (node, params) {
package/dist/reactive.js CHANGED
@@ -23,8 +23,8 @@ function isProxified(object) {
23
23
  /** Default debouncer time in millis. */
24
24
  export const REACTIVE_DEBOUNCE_MILLIS = 10;
25
25
  export function proxifyObject(object, callback, deep = true) {
26
- // If this object is already a proxy, return it as-is.
27
- if (object == null || isProxified(object))
26
+ // If this object is already a proxy or a Promise, return it as-is.
27
+ if (object == null || isProxified(object) || object instanceof Promise)
28
28
  return object;
29
29
  // First, proxify any existing properties if deep = true.
30
30
  if (deep) {
package/dist/worker.js CHANGED
@@ -2,7 +2,7 @@ import * as htmlparser2 from "htmlparser2";
2
2
  import { render as renderDOM } from "dom-serializer";
3
3
  import { IRenderer } from "./core.js";
4
4
  export class Renderer extends IRenderer {
5
- parseHTML(content, params = { root: false }) {
5
+ parseHTML(content, params = { rootDocument: false }) {
6
6
  return htmlparser2.parseDocument(content);
7
7
  }
8
8
  serializeHTML(root) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "mancha",
3
- "version": "0.6.3",
3
+ "version": "0.6.5",
4
4
  "description": "Javscript HTML rendering engine",
5
5
  "main": "dist/index.js",
6
6
  "type": "module",
@@ -21,18 +21,13 @@
21
21
  "url": "git+https://github.com/fresho-dev/mancha.git"
22
22
  },
23
23
  "keywords": [
24
- "gulp",
25
- "render",
26
- "html",
27
- "include",
28
- "preprocessor"
24
+ "templating",
25
+ "rendering",
26
+ "reactive",
27
+ "html"
29
28
  ],
30
- "author": "omtinez@gmail.com",
29
+ "author": "oscar@wahltinez.org",
31
30
  "license": "MIT",
32
- "bugs": {
33
- "url": "https://gitlab.com/omtinez/mancha/issues"
34
- },
35
- "homepage": "https://gitlab.com/omtinez/mancha#README",
36
31
  "dependencies": {
37
32
  "dom-serializer": "^2.0.0",
38
33
  "htmlparser2": "^9.1.0",
package/yarn-error.log CHANGED
@@ -1,5 +1,5 @@
1
1
  Arguments:
2
- /opt/homebrew/Cellar/node/21.5.0/bin/node /usr/local/bin/yarn add --dev @types/dom-handler
2
+ /opt/homebrew/Cellar/node/21.5.0/bin/node /usr/local/bin/yarn add --dev @types/ulive
3
3
 
4
4
  PATH:
5
5
  /Users/owahltinez/Downloads/google-cloud-sdk/bin:/opt/homebrew/bin:/opt/homebrew/sbin:/usr/local/git/git-google/bin:/usr/local/git/current/bin:/usr/local/bin:/usr/bin:/bin:/usr/local/sbin:/usr/sbin:/sbin:/var/run/com.apple.security.cryptexd/codex.system/bootstrap/usr/local/bin:/var/run/com.apple.security.cryptexd/codex.system/bootstrap/usr/bin:/var/run/com.apple.security.cryptexd/codex.system/bootstrap/usr/appleinternal/bin:/opt/X11/bin:/Library/Apple/usr/bin:/Users/owahltinez/.local/bin
@@ -14,7 +14,7 @@ Platform:
14
14
  darwin arm64
15
15
 
16
16
  Trace:
17
- Error: https://registry.yarnpkg.com/@types%2fdom-handler: Not found
17
+ Error: https://registry.yarnpkg.com/@types%2fulive: Not found
18
18
  at params.callback [as _callback] (/usr/local/lib/node_modules/yarn/lib/cli.js:66145:18)
19
19
  at self.callback (/usr/local/lib/node_modules/yarn/lib/cli.js:140890:22)
20
20
  at Request.emit (node:events:519:28)
@@ -29,7 +29,7 @@ Trace:
29
29
  npm manifest:
30
30
  {
31
31
  "name": "mancha",
32
- "version": "0.5.5",
32
+ "version": "0.6.4",
33
33
  "description": "Javscript HTML rendering engine",
34
34
  "main": "dist/index.js",
35
35
  "type": "module",
@@ -50,18 +50,13 @@ npm manifest:
50
50
  "url": "git+https://github.com/fresho-dev/mancha.git"
51
51
  },
52
52
  "keywords": [
53
- "gulp",
54
- "render",
55
- "html",
56
- "include",
57
- "preprocessor"
53
+ "templating",
54
+ "rendering",
55
+ "reactive",
56
+ "html"
58
57
  ],
59
- "author": "omtinez@gmail.com",
58
+ "author": "oscar@wahltinez.org",
60
59
  "license": "MIT",
61
- "bugs": {
62
- "url": "https://gitlab.com/omtinez/mancha/issues"
63
- },
64
- "homepage": "https://gitlab.com/omtinez/mancha#README",
65
60
  "dependencies": {
66
61
  "dom-serializer": "^2.0.0",
67
62
  "htmlparser2": "^9.1.0",