mancha 0.15.0 → 0.16.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +9 -2
- package/dist/browser.js +1 -1
- package/dist/cli.js +102 -13
- package/dist/cli.js.map +1 -1
- package/dist/mancha.js +1 -1
- package/dist/plugins.js +10 -0
- package/dist/plugins.js.map +1 -1
- package/dist/renderer.js +2 -0
- package/dist/renderer.js.map +1 -1
- package/dist/safe_browser.js +1 -1
- package/dist/test_types/api.d.ts +11 -0
- package/dist/test_types/api.js +3 -0
- package/dist/test_types/api.js.map +1 -0
- package/dist/test_types/product.d.ts +11 -0
- package/dist/test_types/product.js +3 -0
- package/dist/test_types/product.js.map +1 -0
- package/dist/test_types/user.d.ts +10 -0
- package/dist/test_types/user.js +3 -0
- package/dist/test_types/user.js.map +1 -0
- package/dist/type_checker.d.ts +5 -0
- package/dist/type_checker.js +281 -0
- package/dist/type_checker.js.map +1 -0
- package/docs/quickstart.md +281 -2
- package/package.json +10 -8
package/docs/quickstart.md
CHANGED
|
@@ -41,8 +41,8 @@ For example, a basic form might look like this
|
|
|
41
41
|
|
|
42
42
|
In the code above, the `:on:submit` tag simply prints 'submitted' to the console. Note that `Mancha`
|
|
43
43
|
automatically prevents the form submission from refreshing the page by calling
|
|
44
|
-
`event.
|
|
45
|
-
|
|
44
|
+
`event.preventDefault()`. You can also trigger this behavior with other events by adding a `.prevent`
|
|
45
|
+
modifier to the event attribute, for example `:on:click.prevent`. To provide more complex handlers, you can define callbacks as a function:
|
|
46
46
|
|
|
47
47
|
```html
|
|
48
48
|
<body :data="{ name: null, message: null }">
|
|
@@ -279,3 +279,282 @@ describe("My Component", () => {
|
|
|
279
279
|
});
|
|
280
280
|
```
|
|
281
281
|
|
|
282
|
+
## Type Checking (Experimental)
|
|
283
|
+
|
|
284
|
+
**⚠️ This feature is experimental and may change in future versions.**
|
|
285
|
+
|
|
286
|
+
`mancha` includes an experimental type checker that can validate your template expressions using TypeScript. This helps catch type errors during development before they become runtime errors.
|
|
287
|
+
|
|
288
|
+
### Basic Type Checking
|
|
289
|
+
|
|
290
|
+
Use the `:types` attribute to declare types for variables in your templates:
|
|
291
|
+
|
|
292
|
+
```html
|
|
293
|
+
<div :types='{"name": "string", "age": "number"}'>
|
|
294
|
+
<span>{{ name.toUpperCase() }}</span>
|
|
295
|
+
<span>{{ age.toFixed(0) }}</span>
|
|
296
|
+
</div>
|
|
297
|
+
```
|
|
298
|
+
|
|
299
|
+
The type checker will validate that:
|
|
300
|
+
- `name.toUpperCase()` is valid (string has toUpperCase method)
|
|
301
|
+
- `age.toFixed(0)` is valid (number has toFixed method)
|
|
302
|
+
- Using `name.toFixed()` would be an error (string doesn't have toFixed)
|
|
303
|
+
|
|
304
|
+
### Running the Type Checker
|
|
305
|
+
|
|
306
|
+
```bash
|
|
307
|
+
# Check a single file
|
|
308
|
+
npx mancha check src/index.html
|
|
309
|
+
|
|
310
|
+
# Check with strict mode
|
|
311
|
+
npx mancha check src/index.html --strict
|
|
312
|
+
```
|
|
313
|
+
|
|
314
|
+
### Stripping Types in Production
|
|
315
|
+
|
|
316
|
+
The `:types` attributes are only used for static analysis and have no runtime behavior. However, you may want to remove them from your production HTML to reduce file size and avoid exposing type information:
|
|
317
|
+
|
|
318
|
+
```bash
|
|
319
|
+
# Render with types stripped
|
|
320
|
+
npx mancha render src/index.html --output public/index.html --strip-types
|
|
321
|
+
|
|
322
|
+
# Render without stripping (default)
|
|
323
|
+
npx mancha render src/index.html --output public/index.html
|
|
324
|
+
```
|
|
325
|
+
|
|
326
|
+
The `--strip-types` flag removes all `:types` and `data-types` attributes from the rendered output.
|
|
327
|
+
|
|
328
|
+
### Type Checking with For-Loops
|
|
329
|
+
|
|
330
|
+
The type checker understands `:for` loops and infers the item type from the array:
|
|
331
|
+
|
|
332
|
+
```html
|
|
333
|
+
<div :types='{"users": "{ name: string, age: number }[]"}'>
|
|
334
|
+
<ul :for="user in users">
|
|
335
|
+
<!-- 'user' is automatically typed as { name: string, age: number } -->
|
|
336
|
+
<li>{{ user.name.toUpperCase() }}</li>
|
|
337
|
+
<li>{{ user.age.toFixed(0) }}</li>
|
|
338
|
+
</ul>
|
|
339
|
+
</div>
|
|
340
|
+
```
|
|
341
|
+
|
|
342
|
+
### Nested Scopes
|
|
343
|
+
|
|
344
|
+
Child scopes inherit types from parent scopes:
|
|
345
|
+
|
|
346
|
+
```html
|
|
347
|
+
<div :types='{"name": "string", "age": "number"}'>
|
|
348
|
+
<span>{{ name.toUpperCase() }}</span>
|
|
349
|
+
|
|
350
|
+
<div :types='{"city": "string"}'>
|
|
351
|
+
<!-- This scope has access to: name, age, and city -->
|
|
352
|
+
<span>{{ name.toLowerCase() }}</span>
|
|
353
|
+
<span>{{ city.toUpperCase() }}</span>
|
|
354
|
+
<span>{{ age.toFixed(0) }}</span>
|
|
355
|
+
</div>
|
|
356
|
+
</div>
|
|
357
|
+
```
|
|
358
|
+
|
|
359
|
+
Child scopes can override parent types:
|
|
360
|
+
|
|
361
|
+
```html
|
|
362
|
+
<div :types='{"value": "string"}'>
|
|
363
|
+
<span>{{ value.toUpperCase() }}</span>
|
|
364
|
+
|
|
365
|
+
<div :types='{"value": "number"}'>
|
|
366
|
+
<!-- 'value' is now number, not string -->
|
|
367
|
+
<span>{{ value.toFixed(2) }}</span>
|
|
368
|
+
</div>
|
|
369
|
+
|
|
370
|
+
<!-- Back to string scope -->
|
|
371
|
+
<span>{{ value.toLowerCase() }}</span>
|
|
372
|
+
</div>
|
|
373
|
+
```
|
|
374
|
+
|
|
375
|
+
### Importing Types (Experimental)
|
|
376
|
+
|
|
377
|
+
**⚠️ This feature is highly experimental and the syntax may change.**
|
|
378
|
+
|
|
379
|
+
You can import TypeScript types from external files using the `@import:` syntax:
|
|
380
|
+
|
|
381
|
+
```typescript
|
|
382
|
+
// types/user.ts
|
|
383
|
+
export interface User {
|
|
384
|
+
id: number;
|
|
385
|
+
name: string;
|
|
386
|
+
email: string;
|
|
387
|
+
isAdmin: boolean;
|
|
388
|
+
}
|
|
389
|
+
|
|
390
|
+
export interface Product {
|
|
391
|
+
id: number;
|
|
392
|
+
name: string;
|
|
393
|
+
price: number;
|
|
394
|
+
}
|
|
395
|
+
```
|
|
396
|
+
|
|
397
|
+
```html
|
|
398
|
+
<!-- Import a single type -->
|
|
399
|
+
<div :types='{"user": "@import:./types/user.ts:User"}'>
|
|
400
|
+
<span>{{ user.name.toUpperCase() }}</span>
|
|
401
|
+
<span>{{ user.email.toLowerCase() }}</span>
|
|
402
|
+
<span :show="user.isAdmin">Admin Badge</span>
|
|
403
|
+
</div>
|
|
404
|
+
```
|
|
405
|
+
|
|
406
|
+
#### Import Syntax
|
|
407
|
+
|
|
408
|
+
The format is: `@import:MODULE_PATH:TYPE_NAME`
|
|
409
|
+
|
|
410
|
+
- **MODULE_PATH**:
|
|
411
|
+
- Starts with `.` → relative path (e.g., `./types/user.ts`)
|
|
412
|
+
- No `.` → library import (e.g., `moment`)
|
|
413
|
+
- **TYPE_NAME**: The exported type/interface name
|
|
414
|
+
|
|
415
|
+
#### Arrays of Imported Types
|
|
416
|
+
|
|
417
|
+
```html
|
|
418
|
+
<div :types='{"users": "@import:./types/user.ts:User[]"}'>
|
|
419
|
+
<ul :for="user in users">
|
|
420
|
+
<li>{{ user.name }} - {{ user.email }}</li>
|
|
421
|
+
</ul>
|
|
422
|
+
</div>
|
|
423
|
+
```
|
|
424
|
+
|
|
425
|
+
#### Multiple Imports
|
|
426
|
+
|
|
427
|
+
```html
|
|
428
|
+
<div :types='{
|
|
429
|
+
"user": "@import:./types/user.ts:User",
|
|
430
|
+
"product": "@import:./types/user.ts:Product",
|
|
431
|
+
"count": "number"
|
|
432
|
+
}'>
|
|
433
|
+
<span>{{ user.name }}</span>
|
|
434
|
+
<span>{{ product.name }} - ${{ product.price.toFixed(2) }}</span>
|
|
435
|
+
<span>Total: {{ count }}</span>
|
|
436
|
+
</div>
|
|
437
|
+
```
|
|
438
|
+
|
|
439
|
+
#### Imports in Complex Types
|
|
440
|
+
|
|
441
|
+
Use imports anywhere you'd use a type:
|
|
442
|
+
|
|
443
|
+
```html
|
|
444
|
+
<!-- In object types -->
|
|
445
|
+
<div :types='{"response": "{ data: @import:./types/user.ts:User[], total: number }"}'>
|
|
446
|
+
<span>Total users: {{ response.total }}</span>
|
|
447
|
+
<ul :for="user in response.data">
|
|
448
|
+
<li>{{ user.name }}</li>
|
|
449
|
+
</ul>
|
|
450
|
+
</div>
|
|
451
|
+
|
|
452
|
+
<!-- With generics -->
|
|
453
|
+
<div :types='{"response": "@import:./api.ts:ApiResponse<@import:./types/user.ts:User>"}'>
|
|
454
|
+
<span>{{ response.data.name }}</span>
|
|
455
|
+
<span>Status: {{ response.status }}</span>
|
|
456
|
+
</div>
|
|
457
|
+
|
|
458
|
+
<!-- With unions -->
|
|
459
|
+
<div :types='{"user": "@import:./types/user.ts:User | null"}'>
|
|
460
|
+
<span :show="user !== null">{{ user.name }}</span>
|
|
461
|
+
<span :show="user === null">Not logged in</span>
|
|
462
|
+
</div>
|
|
463
|
+
```
|
|
464
|
+
|
|
465
|
+
#### Nested Scopes with Imports
|
|
466
|
+
|
|
467
|
+
Imports are inherited by nested scopes:
|
|
468
|
+
|
|
469
|
+
```html
|
|
470
|
+
<div :types='{"user": "@import:./types/user.ts:User"}'>
|
|
471
|
+
<span>{{ user.name }}</span>
|
|
472
|
+
|
|
473
|
+
<div :types='{"product": "@import:./types/user.ts:Product"}'>
|
|
474
|
+
<!-- Has access to both User and Product types -->
|
|
475
|
+
<span>{{ user.name }} bought {{ product.name }}</span>
|
|
476
|
+
</div>
|
|
477
|
+
</div>
|
|
478
|
+
```
|
|
479
|
+
|
|
480
|
+
#### Complex Example
|
|
481
|
+
|
|
482
|
+
```html
|
|
483
|
+
<div :types='{"orders": "@import:./types/orders.ts:Order[]"}'>
|
|
484
|
+
<div :for="order in orders">
|
|
485
|
+
<h2>Order #{{ order.id }}</h2>
|
|
486
|
+
<p>Customer: {{ order.customer.name }}</p>
|
|
487
|
+
|
|
488
|
+
<div :types='{"selectedProduct": "@import:./types/products.ts:Product"}'>
|
|
489
|
+
<ul :for="item in order.items">
|
|
490
|
+
<li>
|
|
491
|
+
{{ item.product.name }} x {{ item.quantity }}
|
|
492
|
+
= ${{ (item.product.price * item.quantity).toFixed(2) }}
|
|
493
|
+
</li>
|
|
494
|
+
</ul>
|
|
495
|
+
|
|
496
|
+
<div :show="selectedProduct">
|
|
497
|
+
<h3>Selected: {{ selectedProduct.name }}</h3>
|
|
498
|
+
<p>${{ selectedProduct.price.toFixed(2) }}</p>
|
|
499
|
+
</div>
|
|
500
|
+
</div>
|
|
501
|
+
|
|
502
|
+
<p>Total: ${{ order.total.toFixed(2) }}</p>
|
|
503
|
+
</div>
|
|
504
|
+
</div>
|
|
505
|
+
```
|
|
506
|
+
|
|
507
|
+
### Best Practices
|
|
508
|
+
|
|
509
|
+
1. **Start Simple**: Add types gradually, starting with the most critical paths
|
|
510
|
+
2. **Use Strict Mode**: Enable strict mode in your TypeScript config for better type safety
|
|
511
|
+
3. **Import Shared Types**: Keep commonly used types in separate files and import them
|
|
512
|
+
4. **Document Complex Types**: Add comments for complex object structures
|
|
513
|
+
5. **Test Your Types**: Run the type checker in your CI/CD pipeline
|
|
514
|
+
|
|
515
|
+
### Limitations
|
|
516
|
+
|
|
517
|
+
- The type checker analyzes templates statically, not at runtime
|
|
518
|
+
- Some dynamic JavaScript patterns may not be fully supported
|
|
519
|
+
- Import paths must be resolvable by TypeScript
|
|
520
|
+
- The import syntax is experimental and may change
|
|
521
|
+
|
|
522
|
+
### Examples
|
|
523
|
+
|
|
524
|
+
#### Form Validation
|
|
525
|
+
|
|
526
|
+
```html
|
|
527
|
+
<div :types='{
|
|
528
|
+
"formData": "{ name: string, email: string, age: number }",
|
|
529
|
+
"errors": "{ name?: string, email?: string, age?: string }"
|
|
530
|
+
}'>
|
|
531
|
+
<form>
|
|
532
|
+
<input type="text" :bind="formData.name" />
|
|
533
|
+
<span :show="errors.name" class="error">{{ errors.name }}</span>
|
|
534
|
+
|
|
535
|
+
<input type="email" :bind="formData.email" />
|
|
536
|
+
<span :show="errors.email" class="error">{{ errors.email }}</span>
|
|
537
|
+
|
|
538
|
+
<input type="number" :bind="formData.age" />
|
|
539
|
+
<span :show="errors.age" class="error">{{ errors.age }}</span>
|
|
540
|
+
</form>
|
|
541
|
+
</div>
|
|
542
|
+
```
|
|
543
|
+
|
|
544
|
+
#### API Response Handling
|
|
545
|
+
|
|
546
|
+
```html
|
|
547
|
+
<div :types='{
|
|
548
|
+
"response": "@import:./api.ts:ApiResponse<@import:./types/user.ts:User>",
|
|
549
|
+
"loading": "boolean",
|
|
550
|
+
"error": "string | null"
|
|
551
|
+
}'>
|
|
552
|
+
<div :show="loading">Loading...</div>
|
|
553
|
+
<div :show="error">Error: {{ error }}</div>
|
|
554
|
+
<div :show="!loading && !error && response">
|
|
555
|
+
<h1>{{ response.data.name }}</h1>
|
|
556
|
+
<p>{{ response.data.email }}</p>
|
|
557
|
+
</div>
|
|
558
|
+
</div>
|
|
559
|
+
```
|
|
560
|
+
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "mancha",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.16.0",
|
|
4
4
|
"description": "Javscript HTML rendering engine",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"type": "module",
|
|
@@ -10,9 +10,10 @@
|
|
|
10
10
|
"clean": "rm -rf dist/",
|
|
11
11
|
"build": "npx gulp build",
|
|
12
12
|
"prepare": "npm run build",
|
|
13
|
-
"
|
|
14
|
-
"
|
|
15
|
-
"test": "
|
|
13
|
+
"pretest": "npm run build",
|
|
14
|
+
"test:node": "mocha dist/*.test.js",
|
|
15
|
+
"test:browser": "web-test-runner dist/*browser.test.js --node-resolve --compatibility all --playwright --browsers chromium --no-sandbox",
|
|
16
|
+
"test": "npm run test:node && npm run test:browser",
|
|
16
17
|
"check_size": "brotli -c dist/mancha.js | wc -c",
|
|
17
18
|
"cli": "node dist/cli.js"
|
|
18
19
|
},
|
|
@@ -35,20 +36,21 @@
|
|
|
35
36
|
"dom-serializer": "^2.0.0",
|
|
36
37
|
"htmlparser2": "^9.1.0",
|
|
37
38
|
"jexpr": "^1.0.0-pre.9",
|
|
38
|
-
"jsdom": "^
|
|
39
|
+
"jsdom": "^27.0.1",
|
|
39
40
|
"safevalues": "^0.6.0"
|
|
40
41
|
},
|
|
41
42
|
"devDependencies": {
|
|
42
|
-
"@types/chai": "^5.
|
|
43
|
+
"@types/chai": "^5.2.3",
|
|
43
44
|
"@types/chai-as-promised": "^8.0.1",
|
|
44
45
|
"@types/jsdom": "^21.1.6",
|
|
45
46
|
"@types/mocha": "^10.0.10",
|
|
46
47
|
"@types/node": "^20.12.11",
|
|
47
48
|
"@types/path-browserify": "^1.0.1",
|
|
49
|
+
"@types/trusted-types": "^2.0.7",
|
|
48
50
|
"@types/yargs": "^17.0.29",
|
|
49
51
|
"@web/test-runner": "^0.19.0",
|
|
50
52
|
"@web/test-runner-playwright": "^0.11.1",
|
|
51
|
-
"chai": "^5.
|
|
53
|
+
"chai": "^5.3.3",
|
|
52
54
|
"chai-as-promised": "^8.0.1",
|
|
53
55
|
"css-loader": "^7.1.2",
|
|
54
56
|
"csso": "^5.0.5",
|
|
@@ -60,7 +62,7 @@
|
|
|
60
62
|
"terser-webpack-plugin": "^5.3.10",
|
|
61
63
|
"ts-node": "^10.9.2",
|
|
62
64
|
"tsec": "^0.2.8",
|
|
63
|
-
"typescript": "^5.
|
|
65
|
+
"typescript": "^5.9.3",
|
|
64
66
|
"webpack": "^5.97.1",
|
|
65
67
|
"webpack-cli": "^5.1.4",
|
|
66
68
|
"yargs": "^17.7.2"
|