mationhtml 1.0.2
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.
Potentially problematic release.
This version of mationhtml might be problematic. Click here for more details.
- package/LICENSE +21 -0
- package/README.md +289 -0
- package/dist/mationhtml.min.js +1 -0
- package/dist/mationhtml.node.js +1 -0
- package/package.json +38 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 rezzvy
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,289 @@
|
|
|
1
|
+
# MationHTML
|
|
2
|
+
|
|
3
|
+
Transform HTML into absolutely any format imaginable.
|
|
4
|
+
|
|
5
|
+
## Overview
|
|
6
|
+
|
|
7
|
+
MationHTML lets you transform HTML into specific formats based on rules you define.
|
|
8
|
+
|
|
9
|
+
- Works in both the browser and Node.js.
|
|
10
|
+
- Define transformations using simple strings or logic-based functions.
|
|
11
|
+
- Gain full control over how specific elements are processed.
|
|
12
|
+
- Apply global string replacements for post-conversion cleanup.
|
|
13
|
+
|
|
14
|
+
## Installation & Usage
|
|
15
|
+
|
|
16
|
+
### Installation
|
|
17
|
+
|
|
18
|
+
#### Browser
|
|
19
|
+
|
|
20
|
+
Include the library via script tag:
|
|
21
|
+
|
|
22
|
+
```html
|
|
23
|
+
<script src="https://cdn.jsdelivr.net/gh/rezzvy/mationhtml@933d9de/dist/mationhtml.min.js"></script>
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
```javascript
|
|
27
|
+
const converter = new MationHTML();
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
#### Node
|
|
31
|
+
|
|
32
|
+
Install via npm:
|
|
33
|
+
|
|
34
|
+
```bash
|
|
35
|
+
npm install mationhtml
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
```javascript
|
|
39
|
+
const converter = require("mationhtml");
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
### Usage
|
|
43
|
+
|
|
44
|
+
```javascript
|
|
45
|
+
converter.addRule([
|
|
46
|
+
{
|
|
47
|
+
selector: "p",
|
|
48
|
+
render: "[text]{content}[/text]",
|
|
49
|
+
},
|
|
50
|
+
{
|
|
51
|
+
selector: "strong",
|
|
52
|
+
render: "[b]{content}[/b]",
|
|
53
|
+
},
|
|
54
|
+
{
|
|
55
|
+
selector: "a",
|
|
56
|
+
render: "[url={attributes.href}]{content}[/url]",
|
|
57
|
+
},
|
|
58
|
+
]);
|
|
59
|
+
|
|
60
|
+
const output = converter.convert(`<p><strong>Hello</strong> there! <a href="https://google.com">Click me!</a></p>`);
|
|
61
|
+
console.log(output);
|
|
62
|
+
// [text][b]Hello[/b] there! [url=https://google.com]Click me![/url][/text]
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
## Examples
|
|
66
|
+
|
|
67
|
+
### String Replacement
|
|
68
|
+
|
|
69
|
+
```javascript
|
|
70
|
+
const converter = new MationHTML();
|
|
71
|
+
converter.addStringReplacements({ from: "Hello!", to: "Bonjour!" });
|
|
72
|
+
converter.addRule({
|
|
73
|
+
selector: "strong",
|
|
74
|
+
render: "[b]{content}[/b]",
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
const output = converter.convert(`<strong>Hello!</strong>`);
|
|
78
|
+
console.log(output);
|
|
79
|
+
// [b]Bonjour![/b]
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
### Fallback Handler
|
|
83
|
+
|
|
84
|
+
```javascript
|
|
85
|
+
const converter = new MationHTML();
|
|
86
|
+
converter.fallbackHandler = ({ content, node }) => {
|
|
87
|
+
return `[unknown tag=${node.tagName}]${content}[/unknown]`;
|
|
88
|
+
};
|
|
89
|
+
|
|
90
|
+
converter.addRule({
|
|
91
|
+
selector: "body",
|
|
92
|
+
render: "{content}",
|
|
93
|
+
});
|
|
94
|
+
|
|
95
|
+
const output = converter.convert(`<strong>Hello!</strong>`);
|
|
96
|
+
console.log(output);
|
|
97
|
+
// [unknown tag=STRONG]Hello![/unknown]
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
### Function-Based Render
|
|
101
|
+
|
|
102
|
+
```javascript
|
|
103
|
+
const converter = new MationHTML();
|
|
104
|
+
|
|
105
|
+
converter.addRule({
|
|
106
|
+
selector: "img",
|
|
107
|
+
render: ({ attributes }) => {
|
|
108
|
+
return attributes.alt ? `Image: ${attributes.alt}` : `Source: ${attributes.src}`;
|
|
109
|
+
},
|
|
110
|
+
});
|
|
111
|
+
|
|
112
|
+
const output = converter.convert(`<img src="sample.jpg" alt="Example">`);
|
|
113
|
+
console.log(output);
|
|
114
|
+
// Image: Example
|
|
115
|
+
```
|
|
116
|
+
|
|
117
|
+
### Hide Specific Selectors
|
|
118
|
+
|
|
119
|
+
```javascript
|
|
120
|
+
const converter = new MationHTML();
|
|
121
|
+
converter.addIgnoredSelectors(["strong"]);
|
|
122
|
+
converter.addRule({
|
|
123
|
+
selector: "p",
|
|
124
|
+
render: "{content}",
|
|
125
|
+
});
|
|
126
|
+
|
|
127
|
+
const output = converter.convert(`<p>Hello <strong>there!</strong></p>`);
|
|
128
|
+
console.log(output);
|
|
129
|
+
// Hello
|
|
130
|
+
```
|
|
131
|
+
|
|
132
|
+
## Documentation
|
|
133
|
+
|
|
134
|
+
### API Reference
|
|
135
|
+
|
|
136
|
+
#### `convert(htmlString, normalizeWhitespace)`
|
|
137
|
+
|
|
138
|
+
The primary method to transform your HTML string.
|
|
139
|
+
|
|
140
|
+
| Parameter | Type | Default | Description |
|
|
141
|
+
| :-------------------- | :-------- | :--------- | :------------------------------------------------------ |
|
|
142
|
+
| `htmlString` | `string` | _Required_ | The HTML content to transform. |
|
|
143
|
+
| `normalizeWhitespace` | `boolean` | `true` | Collapses multiple spaces/newlines (except in `<pre>`). |
|
|
144
|
+
|
|
145
|
+
#### `addRule(rule)`
|
|
146
|
+
|
|
147
|
+
Adds one or more transformation rules.
|
|
148
|
+
|
|
149
|
+
| Parameter | Type | Description |
|
|
150
|
+
| :-------- | :------------- | --------------------------------------------------------- |
|
|
151
|
+
| `rule` | `Object/Array` | A rule object `{ selector, render }` or an array of them. |
|
|
152
|
+
|
|
153
|
+
#### `fallbackHandler` (Setter)
|
|
154
|
+
|
|
155
|
+
Sets a global handler for elements that don't match any rules.
|
|
156
|
+
|
|
157
|
+
| Parameter | Type | Description |
|
|
158
|
+
| :--------- | :--------- | :-------------------------------------------------------------------- |
|
|
159
|
+
| `callback` | `function` | A function receiving the API object. Must return a string of content. |
|
|
160
|
+
|
|
161
|
+
#### `addIgnoredSelectors(selectors)`
|
|
162
|
+
|
|
163
|
+
Prevents specific elements and their children from being processed.
|
|
164
|
+
|
|
165
|
+
| Parameter | Type | Description |
|
|
166
|
+
| :---------- | :--------- | :--------------------------------------------------- |
|
|
167
|
+
| `selectors` | `string[]` | Array of CSS selectors (e.g., `['script', '.ads']`). |
|
|
168
|
+
|
|
169
|
+
#### `addStringReplacements(replacements)`
|
|
170
|
+
|
|
171
|
+
Post-processing string swaps performed on the final result.
|
|
172
|
+
|
|
173
|
+
| Parameter | Type | Description |
|
|
174
|
+
| :------------- | :------------- | :---------------------------------------- |
|
|
175
|
+
| `replacements` | `Object/Array` | Object(s) with `{ from, to }` properties. |
|
|
176
|
+
|
|
177
|
+
### API
|
|
178
|
+
|
|
179
|
+
When `render` is a function or when using the `fallbackHandler`, you receive an object with these properties:
|
|
180
|
+
|
|
181
|
+
| Property | Type | Description |
|
|
182
|
+
| :----------------- | :---------------- | :--------------------------------------------------- |
|
|
183
|
+
| `node` | `Node` | The current DOM node being processed. |
|
|
184
|
+
| `attributes` | `Object` | A key-value map of the element's attributes. |
|
|
185
|
+
| `depth` | `number` | The current nesting level. |
|
|
186
|
+
| `content` | `string` (getter) | The processed string of all child nodes. |
|
|
187
|
+
| `applyRules(node)` | `function` | Manually trigger rule processing on a specific node. |
|
|
188
|
+
|
|
189
|
+
### Template Placeholders
|
|
190
|
+
|
|
191
|
+
Shortcuts for defining `render` strings:
|
|
192
|
+
|
|
193
|
+
| Placeholder | Description | Example |
|
|
194
|
+
| :--------------- | :----------------------------------------- | :--------------------- |
|
|
195
|
+
| `{content}` | Inserts processed child text/tags. | `[tag]{content}[/tag]` |
|
|
196
|
+
| `{attributes.x}` | Inserts the value of attribute `x`. | `{attributes.href}` |
|
|
197
|
+
| `{null}` | Returns an empty string (removes the tag). | `render: "{null}"` |
|
|
198
|
+
|
|
199
|
+
### Behavior
|
|
200
|
+
|
|
201
|
+
#### Top-Down Processing
|
|
202
|
+
|
|
203
|
+
By default, MationHTML processes from the parent down to the children. If a parent rule doesn't include the `{content}` placeholder, the children are simply ignored.
|
|
204
|
+
|
|
205
|
+
For example, these li elements won't show up because the ul rule returns a static string:
|
|
206
|
+
|
|
207
|
+
```javascript
|
|
208
|
+
converter.addRule([
|
|
209
|
+
{ selector: "ul", render: "List Placeholder" },
|
|
210
|
+
{ selector: "li", render: "[*] {content}" },
|
|
211
|
+
]);
|
|
212
|
+
```
|
|
213
|
+
|
|
214
|
+
If you want to build the parent structure manually without just dumping `{content}` in, you can process child nodes yourself using `applyRules()`
|
|
215
|
+
|
|
216
|
+
```javascript
|
|
217
|
+
const converter = new MationHTML();
|
|
218
|
+
|
|
219
|
+
converter.addRule([
|
|
220
|
+
{
|
|
221
|
+
selector: "ul",
|
|
222
|
+
render: (api) => {
|
|
223
|
+
let items = Array.from(api.node.children);
|
|
224
|
+
let processedChildren = "";
|
|
225
|
+
items.forEach((el) => {
|
|
226
|
+
processedChildren += api.applyRules(el);
|
|
227
|
+
});
|
|
228
|
+
|
|
229
|
+
return `[list]${processedChildren}[/list]`;
|
|
230
|
+
},
|
|
231
|
+
},
|
|
232
|
+
{
|
|
233
|
+
selector: "li",
|
|
234
|
+
render: "[*] {content}",
|
|
235
|
+
},
|
|
236
|
+
]);
|
|
237
|
+
```
|
|
238
|
+
|
|
239
|
+
#### Cascading Rules
|
|
240
|
+
|
|
241
|
+
MationHTML supports cascading. If an element matches multiple rules, they are applied in the order they were added. The output of the first rule becomes the `{content}` for the subsequent rule.
|
|
242
|
+
|
|
243
|
+
```javascript
|
|
244
|
+
const converter = new MationHTML();
|
|
245
|
+
|
|
246
|
+
converter.addRule([
|
|
247
|
+
{
|
|
248
|
+
selector: ".text",
|
|
249
|
+
render: "@{content}",
|
|
250
|
+
},
|
|
251
|
+
{
|
|
252
|
+
selector: "p",
|
|
253
|
+
render: "{content}@",
|
|
254
|
+
},
|
|
255
|
+
]);
|
|
256
|
+
|
|
257
|
+
const output = converter.convert(`<p class="text">Hi!</p>`);
|
|
258
|
+
console.log(output);
|
|
259
|
+
// @Hi!@
|
|
260
|
+
```
|
|
261
|
+
|
|
262
|
+
#### Rules Must Return a Value
|
|
263
|
+
|
|
264
|
+
When defining a rule, the `render` property (whether it's a string or a function) cannot be `undefined` or an empty string (if it isn't a function-based).
|
|
265
|
+
|
|
266
|
+
```javascript
|
|
267
|
+
// This will throw an error!
|
|
268
|
+
converter.addRule({ selector: "p", render: "" });
|
|
269
|
+
```
|
|
270
|
+
|
|
271
|
+
If you intentionally want to output nothing for a specific rule, use the `{null}` placeholder in strings:
|
|
272
|
+
|
|
273
|
+
```javascript
|
|
274
|
+
converter.addRule({ selector: "p", render: "{null}" });
|
|
275
|
+
```
|
|
276
|
+
|
|
277
|
+
If you are using a function-based render, you can simply return an empty string:
|
|
278
|
+
|
|
279
|
+
```javascript
|
|
280
|
+
converter.addRule({ selector: "p", render: () => "" });
|
|
281
|
+
```
|
|
282
|
+
|
|
283
|
+
## Contributing
|
|
284
|
+
|
|
285
|
+
There's always room for improvement. Feel free to contribute!
|
|
286
|
+
|
|
287
|
+
## Licensing
|
|
288
|
+
|
|
289
|
+
The project is licensed under MIT License. Check the license file for more details.
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
var MationHTML=(()=>{var m=(i,r)=>()=>(r||i((r={exports:{}}).exports,r),r.exports);var g=m((A,y)=>{var f=class i{static DOMParser=null;static Node=null;constructor(){this.rules=[]}#r=null;#e=[];#n=[];set fallbackHandler(r){if(typeof r!="function")throw new Error("Fallback handler must be a function.");this.#r=r}addIgnoredSelectors(r){if(!Array.isArray(r))throw new Error("Selectors must be an array of strings.");for(let t of r){if(typeof t!="string")throw new Error("Selector must be a string.");this.#e.includes(t)||this.#e.push(t)}}addStringReplacements(r){let t=Array.isArray(r)?r:[r];for(let n of t){if(!n?.from||!n?.to)throw new Error("Invalid replacement object. Must contain 'from' and 'to'.");this.#n.push({from:n.from,to:n.to})}}addRule(r){let t=Array.isArray(r)?r:[r];for(let n of t)this.#i(n),this.rules.push(n)}convert(r,t=!0){if(typeof r!="string")throw new Error("Input must be a string.");if(!i.DOMParser)throw new Error("DOMParser is not initialized. Check your entry point.");let l=new i.DOMParser().parseFromString(r,"text/html").body,a=this.#t(l,0,t);for(let c of this.#n)a=a.replaceAll(c.from,c.to);return a.trim()}#t(r,t=0,n=!0){return r.nodeType===i.Node.TEXT_NODE?r.parentElement?.tagName==="PRE"?r.textContent:n?r.textContent.replace(/\s+/g," "):r.textContent:r.nodeType===i.Node.ELEMENT_NODE?this.#e.some(s=>r.matches(s))?"":this.#s(r,t,n):""}#o(r,t,n){let s="";for(let l of Array.from(r.childNodes))s+=this.#t(l,t+1,n);return s}#s(r,t,n){let s=this.rules.filter(e=>r.matches(e.selector)),l=Array.from(r.attributes).reduce((e,o)=>(e[o.name]=o.value,e),{}),a=e=>{if(!e)throw new Error("applyRules requires a targetNode.");if(e===r)throw new Error("applyRules cannot be called with the current node.");return this.#t(e,t,n)},c=()=>this.#o(r,t,n),h=e=>({node:r,attributes:l,depth:t,applyRules:a,get content(){return e()}});if(s.length>0){let e=null;for(let o of s){let p=e!==null?e:c();if(typeof o.render=="function"){let w=h(()=>p),u=o.render(w);if(u===void 0)throw new Error(`Rule for "${o.selector}" returned undefined.`);e=u}else typeof o.render=="string"&&(e=o.render.replace(/{attributes\.([\w-]+)}/g,(w,u)=>l[u]||"").replace(/{content}/g,p).replace(/{null}/g,""))}return e}if(this.#r){let e=h(c),o=this.#r(e);if(o!==void 0)return o;throw new Error("Fallback handler must return content.")}return c()}#i(r){if(typeof r!="object"||!r.selector||!r.render)throw new Error("Invalid rule structure. Requires 'selector' and 'render'.");if(typeof r.render!="string"&&typeof r.render!="function")throw new Error(`Rule for "${r.selector}" is invalid. 'render' must be a string or a function.`)}};y.exports=f});var b=m((N,E)=>{var d=g();d.DOMParser=window.DOMParser;d.Node=window.Node;E.exports=d});return b();})();
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
var E=(i,r)=>()=>(r||i((r={exports:{}}).exports,r),r.exports);var y=E((A,m)=>{var f=class i{static DOMParser=null;static Node=null;constructor(){this.rules=[]}#r=null;#e=[];#n=[];set fallbackHandler(r){if(typeof r!="function")throw new Error("Fallback handler must be a function.");this.#r=r}addIgnoredSelectors(r){if(!Array.isArray(r))throw new Error("Selectors must be an array of strings.");for(let t of r){if(typeof t!="string")throw new Error("Selector must be a string.");this.#e.includes(t)||this.#e.push(t)}}addStringReplacements(r){let t=Array.isArray(r)?r:[r];for(let n of t){if(!n?.from||!n?.to)throw new Error("Invalid replacement object. Must contain 'from' and 'to'.");this.#n.push({from:n.from,to:n.to})}}addRule(r){let t=Array.isArray(r)?r:[r];for(let n of t)this.#i(n),this.rules.push(n)}convert(r,t=!0){if(typeof r!="string")throw new Error("Input must be a string.");if(!i.DOMParser)throw new Error("DOMParser is not initialized. Check your entry point.");let c=new i.DOMParser().parseFromString(r,"text/html").body,a=this.#t(c,0,t);for(let l of this.#n)a=a.replaceAll(l.from,l.to);return a.trim()}#t(r,t=0,n=!0){return r.nodeType===i.Node.TEXT_NODE?r.parentElement?.tagName==="PRE"?r.textContent:n?r.textContent.replace(/\s+/g," "):r.textContent:r.nodeType===i.Node.ELEMENT_NODE?this.#e.some(s=>r.matches(s))?"":this.#s(r,t,n):""}#o(r,t,n){let s="";for(let c of Array.from(r.childNodes))s+=this.#t(c,t+1,n);return s}#s(r,t,n){let s=this.rules.filter(e=>r.matches(e.selector)),c=Array.from(r.attributes).reduce((e,o)=>(e[o.name]=o.value,e),{}),a=e=>{if(!e)throw new Error("applyRules requires a targetNode.");if(e===r)throw new Error("applyRules cannot be called with the current node.");return this.#t(e,t,n)},l=()=>this.#o(r,t,n),h=e=>({node:r,attributes:c,depth:t,applyRules:a,get content(){return e()}});if(s.length>0){let e=null;for(let o of s){let p=e!==null?e:l();if(typeof o.render=="function"){let w=h(()=>p),u=o.render(w);if(u===void 0)throw new Error(`Rule for "${o.selector}" returned undefined.`);e=u}else typeof o.render=="string"&&(e=o.render.replace(/{attributes\.([\w-]+)}/g,(w,u)=>c[u]||"").replace(/{content}/g,p).replace(/{null}/g,""))}return e}if(this.#r){let e=h(l),o=this.#r(e);if(o!==void 0)return o;throw new Error("Fallback handler must return content.")}return l()}#i(r){if(typeof r!="object"||!r.selector||!r.render)throw new Error("Invalid rule structure. Requires 'selector' and 'render'.");if(typeof r.render!="string"&&typeof r.render!="function")throw new Error(`Rule for "${r.selector}" is invalid. 'render' must be a string or a function.`)}};m.exports=f});var{JSDOM:b}=require("jsdom"),d=y(),g=new b;d.DOMParser=g.window.DOMParser;d.Node=g.window.Node;module.exports=d;
|
package/package.json
ADDED
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "mationhtml",
|
|
3
|
+
"version": "1.0.2",
|
|
4
|
+
"description": "Transform HTML into absolutely any format imaginable.",
|
|
5
|
+
"main": "dist/mationhtml.node.js",
|
|
6
|
+
"repository": {
|
|
7
|
+
"type": "git",
|
|
8
|
+
"url": "git+https://github.com/rezzvy/mationhtml.git"
|
|
9
|
+
},
|
|
10
|
+
"bugs": {
|
|
11
|
+
"url": "https://github.com/rezzvy/mationhtml/issues"
|
|
12
|
+
},
|
|
13
|
+
"homepage": "https://github.com/rezzvy/mationhtml#readme",
|
|
14
|
+
"files": [
|
|
15
|
+
"dist/"
|
|
16
|
+
],
|
|
17
|
+
"scripts": {
|
|
18
|
+
"build:browser": "esbuild ./src/mationhtml.browser.js --bundle --minify --platform=browser --format=iife --global-name=MationHTML --outfile=dist/mationhtml.min.js",
|
|
19
|
+
"build:node": "esbuild ./src/mationhtml.node.js --bundle --external:jsdom --minify --platform=node --format=cjs --outfile=dist/mationhtml.node.js",
|
|
20
|
+
"build": "npm run build:browser && npm run build:node",
|
|
21
|
+
"test": "node --test"
|
|
22
|
+
},
|
|
23
|
+
"keywords": [
|
|
24
|
+
"html",
|
|
25
|
+
"html-converter",
|
|
26
|
+
"html-to-text",
|
|
27
|
+
"dom"
|
|
28
|
+
],
|
|
29
|
+
"author": "rezzvy",
|
|
30
|
+
"license": "MIT",
|
|
31
|
+
"type": "commonjs",
|
|
32
|
+
"dependencies": {
|
|
33
|
+
"jsdom": "^29.0.1"
|
|
34
|
+
},
|
|
35
|
+
"devDependencies": {
|
|
36
|
+
"esbuild": "^0.27.4"
|
|
37
|
+
}
|
|
38
|
+
}
|