metaowl 0.4.1 → 0.6.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.
Files changed (83) hide show
  1. package/CHANGELOG.md +50 -0
  2. package/README.md +267 -2
  3. package/build/runtime/bin/metaowl-build.js +10 -0
  4. package/{bin → build/runtime/bin}/metaowl-create.js +96 -177
  5. package/build/runtime/bin/metaowl-dev.js +10 -0
  6. package/build/runtime/bin/metaowl-generate.js +231 -0
  7. package/build/runtime/bin/metaowl-lint.js +58 -0
  8. package/build/runtime/bin/utils.js +68 -0
  9. package/build/runtime/index.js +144 -0
  10. package/build/runtime/modules/app-mounter.js +73 -0
  11. package/build/runtime/modules/auto-import.js +140 -0
  12. package/build/runtime/modules/cache.js +49 -0
  13. package/build/runtime/modules/composables.js +353 -0
  14. package/build/runtime/modules/constants.js +38 -0
  15. package/build/runtime/modules/error-boundary.js +116 -0
  16. package/build/runtime/modules/fetch.js +31 -0
  17. package/build/runtime/modules/file-router.js +207 -0
  18. package/build/runtime/modules/fonts.js +172 -0
  19. package/build/runtime/modules/forms.js +193 -0
  20. package/build/runtime/modules/i18n.js +180 -0
  21. package/build/runtime/modules/image.js +175 -0
  22. package/build/runtime/modules/layouts.js +214 -0
  23. package/build/runtime/modules/link.js +141 -0
  24. package/build/runtime/modules/meta.js +117 -0
  25. package/build/runtime/modules/odoo-rpc.js +265 -0
  26. package/build/runtime/modules/pwa.js +272 -0
  27. package/build/runtime/modules/router.js +384 -0
  28. package/build/runtime/modules/seo.js +186 -0
  29. package/build/runtime/modules/store.js +198 -0
  30. package/build/runtime/modules/templates-manager.js +52 -0
  31. package/build/runtime/modules/test-utils.js +238 -0
  32. package/build/runtime/vite/plugin.js +197 -0
  33. package/eslint.js +29 -0
  34. package/package.json +45 -27
  35. package/CONTRIBUTING.md +0 -49
  36. package/bin/metaowl-build.js +0 -12
  37. package/bin/metaowl-dev.js +0 -12
  38. package/bin/metaowl-generate.js +0 -339
  39. package/bin/metaowl-lint.js +0 -71
  40. package/bin/utils.js +0 -82
  41. package/eslint.config.js +0 -3
  42. package/index.js +0 -328
  43. package/modules/app-mounter.js +0 -104
  44. package/modules/auto-import.js +0 -225
  45. package/modules/cache.js +0 -59
  46. package/modules/composables.js +0 -600
  47. package/modules/error-boundary.js +0 -228
  48. package/modules/fetch.js +0 -51
  49. package/modules/file-router.js +0 -478
  50. package/modules/forms.js +0 -353
  51. package/modules/i18n.js +0 -333
  52. package/modules/layouts.js +0 -431
  53. package/modules/link.js +0 -255
  54. package/modules/meta.js +0 -119
  55. package/modules/odoo-rpc.js +0 -511
  56. package/modules/pwa.js +0 -515
  57. package/modules/router.js +0 -769
  58. package/modules/seo.js +0 -501
  59. package/modules/store.js +0 -409
  60. package/modules/templates-manager.js +0 -89
  61. package/modules/test-utils.js +0 -532
  62. package/test/auto-import.test.js +0 -110
  63. package/test/cache.test.js +0 -55
  64. package/test/composables.test.js +0 -103
  65. package/test/dynamic-routes.test.js +0 -469
  66. package/test/error-boundary.test.js +0 -126
  67. package/test/fetch.test.js +0 -100
  68. package/test/file-router.test.js +0 -55
  69. package/test/forms.test.js +0 -203
  70. package/test/i18n.test.js +0 -188
  71. package/test/layouts.test.js +0 -395
  72. package/test/link.test.js +0 -189
  73. package/test/meta.test.js +0 -146
  74. package/test/odoo-rpc.test.js +0 -547
  75. package/test/pwa.test.js +0 -154
  76. package/test/router-guards.test.js +0 -229
  77. package/test/router.test.js +0 -77
  78. package/test/seo.test.js +0 -353
  79. package/test/store.test.js +0 -476
  80. package/test/templates-manager.test.js +0 -83
  81. package/test/test-utils.test.js +0 -314
  82. package/vite/plugin.js +0 -290
  83. package/vitest.config.js +0 -8
package/CHANGELOG.md CHANGED
@@ -5,6 +5,56 @@ All notable changes to this project will be documented in this file.
5
5
  The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
6
6
  and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7
7
 
8
+ ## [0.6.0] - 2026-04-24
9
+
10
+ ### Added
11
+
12
+ - **Nested layouts support** — layouts can now have parent-child relationships using `setParentLayout()`, `getParentLayout()`, and `getLayoutChain()`. The `createNestedLayoutWrapper()` function creates wrapper components for multi-level nesting. The `defineNestedLayout()` decorator simplifies declaring nested layouts on components.
13
+ - **Image optimization module** — new `modules/image.ts` with `generateSrcSet()`, `calculateAspectRatio()`, `generateSizesAttribute()`, `createResponsiveImage()`, `prefetchImage()`, `prefetchImages()`, `isImageLoaded()`, `getImageDimensions()`, `observeImageVisibility()`, `swapImageSource()`, and `generateDominantColorPlaceholder()`.
14
+ - **Fonts optimization module** — new `modules/fonts.ts` with `defineFontFace()`, `loadFont()`, `loadFontFamily()`, `isFontLoaded()`, `preloadFont()`, `removeFontPreload()`, `createFontFaceRule()`, `injectFontFaceRules()`, `measureTextWidth()`, `estimateFontMetrics()`, `adjustFontForFout()`, and `getFontLoadStatus()`.
15
+
16
+ ### Changed
17
+
18
+ - **TypeScript migration completed** — the framework source, Vite integration, CLI entrypoints,
19
+ and test suite now use TypeScript as the primary source of truth while preserving the existing
20
+ public API and runtime behavior.
21
+ - **Runtime build output separated from source** — publishable JavaScript is now emitted to
22
+ `build/runtime`, and package `main`, `exports`, and `bin` entries resolve from that generated
23
+ runtime output.
24
+
25
+ ### Removed
26
+
27
+ - **Redundant source JavaScript files** — legacy hand-maintained `.js` source files in `modules/`,
28
+ `bin/`, `vite/`, and the root entrypoint were removed in favor of the TypeScript sources and the
29
+ generated runtime build.
30
+
31
+ ### Added
32
+
33
+ - **Release build workflow** — added dedicated runtime build and release helper scripts for
34
+ clean runtime generation, release checks, and package dry-run validation.
35
+
36
+ ## [0.5.0] - 2026-04-24
37
+
38
+ ### Changed
39
+
40
+ - **TypeScript migration completed** — the framework source, Vite integration, CLI entrypoints,
41
+ and test suite now use TypeScript as the primary source of truth while preserving the existing
42
+ public API and runtime behavior.
43
+ - **Runtime build output separated from source** — publishable JavaScript is now emitted to
44
+ `build/runtime`, and package `main`, `exports`, and `bin` entries resolve from that generated
45
+ runtime output.
46
+
47
+ ### Removed
48
+
49
+ - **Redundant source JavaScript files** — legacy hand-maintained `.js` source files in `modules/`,
50
+ `bin/`, `vite/`, and the root entrypoint were removed in favor of the TypeScript sources and the
51
+ generated runtime build.
52
+
53
+ ### Added
54
+
55
+ - **Release build workflow** — added dedicated runtime build and release helper scripts for
56
+ clean runtime generation, release checks, and package dry-run validation.
57
+
8
58
  ## [0.4.1] - 2026-03-25
9
59
 
10
60
  ### Added
package/README.md CHANGED
@@ -17,7 +17,7 @@ metaowl is a complete solution for building OWL applications with everything you
17
17
 
18
18
  **Developer Experience:** Composables for common patterns (auth, localStorage, fetching), form handling with validation, error boundaries, and internationalization.
19
19
 
20
- **SEO & PWA:** Sitemap/robots.txt generation, structured data support, service worker integration, web app manifest, and push notifications.
20
+ **SEO & PWA:** Sitemap/robots.txt generation, structured data support, image optimization with srcset generation, font optimization with preloading, service worker integration, web app manifest, and push notifications.
21
21
 
22
22
  **Testing & Quality:** Mock stores, router mocking, component testing utilities, plus bundled ESLint and PostCSS configs.
23
23
 
@@ -44,6 +44,8 @@ All powered by a batteries-included Vite plugin that handles the build pipeline,
44
44
  - [Auto-Import](#auto-import)
45
45
  - [Odoo JSON-RPC Service](#odoo-json-rpc-service)
46
46
  - [Composables / Hooks](#composables--hooks)
47
+ - [Image Optimization](#image-optimization)
48
+ - [Font Optimization](#font-optimization)
47
49
  - [CLI Reference](#cli-reference)
48
50
  - [API Reference](#api-reference)
49
51
  - [boot](#bootroutes)
@@ -61,6 +63,8 @@ All powered by a batteries-included Vite plugin that handles the build pipeline,
61
63
  - [Forms](#forms-api)
62
64
  - [OdooService](#odooservice-api)
63
65
  - [Composables](#composables-api)
66
+ - [Image API](#image-api)
67
+ - [Fonts API](#fonts-api)
64
68
  - [Vite Plugin](#vite-plugin)
65
69
  - [metaowlPlugin](#metaowlpluginoptions)
66
70
  - [metaowlConfig](#metaowlconfigoptions)
@@ -93,6 +97,8 @@ All powered by a batteries-included Vite plugin that handles the build pipeline,
93
97
  - **Composables** — reusable hooks for auth, localStorage, fetching, and more
94
98
  - **Testing Utilities** — mock store, router mocking, component mount helpers
95
99
  - **SEO Utils** — sitemap, robots.txt, JSON-LD, Open Graph, Twitter Cards
100
+ - **Image Optimization** — responsive srcset generation, lazy loading, placeholder support, dominant color extraction
101
+ - **Font Optimization** — FontFace creation, font preloading, @font-face CSS generation, FOUT handling
96
102
  - **PWA Support** — service worker, manifest generation, push notifications
97
103
  - **SSG generator** — statically pre-renders HTML pages with correct meta tags at build time
98
104
  - **Vite plugin** — handles `COMPONENTS` injection, XML template copying, CSS auto-import, chunk splitting, and env filtering
@@ -117,6 +123,18 @@ npm install metaowl
117
123
 
118
124
  `@odoo/owl` is bundled with metaowl and resolved automatically — no separate installation required.
119
125
 
126
+ ### Release Workflow
127
+
128
+ For the package itself, the TypeScript sources remain the source of truth and the publishable runtime files are generated into `build/runtime`.
129
+
130
+ ```bash
131
+ npm run release:check # typecheck + tests
132
+ npm run build:runtime # clean build/runtime and emit JS
133
+ npm run release:pack # full check + build + npm pack --dry-run
134
+ ```
135
+
136
+ `npm pack` and `npm publish` also trigger `prepack`, which rebuilds `build/runtime` automatically.
137
+
120
138
  ---
121
139
 
122
140
  ## Create a New Project
@@ -377,6 +395,37 @@ If no layout is specified, the `default` layout is used automatically.
377
395
  </templates>
378
396
  ```
379
397
 
398
+ ### Nested Layouts
399
+
400
+ Layouts can be nested in a parent-child hierarchy. Use `setParentLayout()` to define relationships:
401
+
402
+ ```js
403
+ import { setParentLayout, getLayoutChain } from 'metaowl'
404
+
405
+ // Define layout hierarchy
406
+ setParentLayout('inner', 'middle')
407
+ setParentLayout('middle', 'outer')
408
+
409
+ // Get the full chain for a layout
410
+ const chain = getLayoutChain('inner')
411
+ // ['inner', 'middle', 'outer']
412
+ ```
413
+
414
+ Or use the `defineNestedLayout()` decorator:
415
+
416
+ ```js
417
+ import { defineNestedLayout } from 'metaowl'
418
+
419
+ export class AdminDashboard extends Component {
420
+ static template = 'AdminDashboard'
421
+ static layout = 'admin-dashboard'
422
+ }
423
+
424
+ defineNestedLayout('admin-dashboard', 'admin-base')(AdminDashboard)
425
+ ```
426
+
427
+ When a page uses `admin-dashboard` layout, it's rendered with: `outer > middle > admin-dashboard > page`.
428
+
380
429
  ---
381
430
 
382
431
  ## Navigation Guards
@@ -889,6 +938,98 @@ class MyComponent extends Component {
889
938
 
890
939
  ---
891
940
 
941
+ ## Image Optimization
942
+
943
+ Responsive image handling with srcset generation, lazy loading, and placeholder support:
944
+
945
+ ```js
946
+ import { Image } from 'metaowl'
947
+
948
+ // Generate srcset for responsive images
949
+ const srcset = Image.generateSrcSet('https://example.com/image.jpg', [320, 640, 960, 1280])
950
+ // "https://example.com/image.jpg?width=320&quality=80 320w, ..."
951
+
952
+ // Create a fully configured responsive image object
953
+ const responsive = Image.createResponsiveImage({
954
+ src: 'https://example.com/hero.jpg',
955
+ alt: 'Hero image',
956
+ widths: [640, 1024, 1920],
957
+ lazy: true,
958
+ placeholder: true
959
+ })
960
+ // Returns: { src, srcset, loading: 'lazy', decoding: 'async', placeholder, ... }
961
+ ```
962
+
963
+ **Image utilities:**
964
+
965
+ | Function | Description |
966
+ |---|---|
967
+ | `generateSrcSet(src, widths, options)` | Generates responsive srcset string |
968
+ | `calculateAspectRatio(width, height)` | Computes aspect ratio as `W/H` |
969
+ | `generateSizesAttribute(src, breakpoints)` | Generates sizes attribute with breakpoints |
970
+ | `createResponsiveImage(options)` | Creates responsive image object |
971
+ | `prefetchImage(src)` / `prefetchImages(sources)` | Prefetch images for faster loading |
972
+ | `isImageLoaded(img)` | Check if image has finished loading |
973
+ | `getImageDimensions(src)` | Get natural width/height of image |
974
+ | `observeImageVisibility(img, callback)` | IntersectionObserver for lazy loading |
975
+ | `swapImageSource(img, newSrc, newSrcset)` | Swap src/srcset atomically |
976
+ | `generateDominantColorPlaceholder(src)` | Extract dominant color as placeholder |
977
+
978
+ ---
979
+
980
+ ## Font Optimization
981
+
982
+ Font loading and management with preloading and FOUT (Flash of Unstyled Text) handling:
983
+
984
+ ```js
985
+ import { Fonts } from 'metaowl'
986
+
987
+ // Define and load a font
988
+ const fontFace = await Fonts.loadFont({
989
+ family: 'Inter',
990
+ src: 'https://fonts.example.com/inter.woff2',
991
+ weight: 'normal',
992
+ display: 'swap'
993
+ })
994
+
995
+ // Preload font for critical text
996
+ Fonts.preloadFont('Inter', 'https://fonts.example.com/inter.woff2')
997
+
998
+ // Generate @font-face CSS rule
999
+ const cssRule = Fonts.createFontFaceRule({
1000
+ family: 'Inter',
1001
+ src: 'https://fonts.example.com/inter.woff2',
1002
+ weight: 'bold',
1003
+ display: 'optional'
1004
+ })
1005
+ // @font-face { font-family: 'Inter'; src: url("..."); font-weight: bold; font-display: optional; }
1006
+
1007
+ // Inject font faces into document
1008
+ Fonts.injectFontFaceRules({
1009
+ family: 'Inter',
1010
+ src: ['woff2', 'woff'].map(f => `https://fonts.example.com/${f}`),
1011
+ weight: '400 700'
1012
+ })
1013
+ ```
1014
+
1015
+ **Font utilities:**
1016
+
1017
+ | Function | Description |
1018
+ |---|---|
1019
+ | `defineFontFace(options)` | Creates a FontFace object |
1020
+ | `loadFont(options)` | Loads font and tracks it |
1021
+ | `loadFontFamily(family, variants)` | Load multiple variants |
1022
+ | `isFontLoaded(family, weight?)` | Check if font is loaded |
1023
+ | `preloadFont(family, src, options)` | Add preload link for font |
1024
+ | `removeFontPreload(family, weight?)` | Remove preload link |
1025
+ | `createFontFaceRule(options)` | Generate @font-face CSS |
1026
+ | `injectFontFaceRules(options)` | Inject @font-face rules |
1027
+ | `measureTextWidth(text, font, size)` | Measure rendered text width |
1028
+ | `adjustFontForFout(el, fallback, timeout)` | Handle FOUT gracefully |
1029
+ | `getFontLoadStatus()` | Get loaded fonts status |
1030
+
1031
+ ---
1032
+
892
1033
  ## CLI Reference
893
1034
 
894
1035
  metaowl ships four CLI commands that use its own bundled Vite, Prettier, and ESLint binaries — no need to install them separately in your project.
@@ -1107,11 +1248,24 @@ Functions for layout management.
1107
1248
 
1108
1249
  | Function | Description |
1109
1250
  |---|---|
1110
- | `registerLayout(name, Component)` | Register a layout |
1251
+ | `registerLayout(name, Component, options?)` | Register a layout |
1252
+ | `unregisterLayout(name)` | Remove a layout |
1111
1253
  | `getLayout(name)` | Get layout component by name |
1254
+ | `hasLayout(name)` | Check if layout exists |
1255
+ | `getLayoutNames()` | Get all registered layout names |
1112
1256
  | `setDefaultLayout(name)` | Set default layout |
1257
+ | `getDefaultLayout()` | Get default layout name |
1113
1258
  | `resolveLayout(Component, path?)` | Resolve layout for component |
1259
+ | `setRouteLayout(routePath, layoutName)` | Set layout for a route |
1260
+ | `getRouteLayout(routePath)` | Get layout for a route |
1261
+ | `setParentLayout(layoutName, parentName)` | Set parent-child relationship |
1262
+ | `getParentLayout(layoutName)` | Get parent layout name |
1263
+ | `getLayoutChain(layoutName)` | Get full layout chain |
1264
+ | `createNestedLayoutWrapper(layouts, page, props)` | Create nested layout wrapper |
1114
1265
  | `subscribeToLayouts(callback)` | Listen to layout events |
1266
+ | `clearLayouts()` | Clear all layouts |
1267
+ | `defineLayout(name, options?)` | Decorator for layout |
1268
+ | `defineNestedLayout(name, parent, options?)` | Decorator for nested layout |
1115
1269
 
1116
1270
  **Component Layout Property:**
1117
1271
 
@@ -1446,6 +1600,117 @@ const { value, set, get, remove, clear } = useCache('user-prefs', {})
1446
1600
 
1447
1601
  ---
1448
1602
 
1603
+ ### `Image API`
1604
+
1605
+ Image optimization utilities.
1606
+
1607
+ ```ts
1608
+ import { Image } from 'metaowl'
1609
+
1610
+ // Generate srcset for responsive images
1611
+ Image.generateSrcSet('https://example.com/image.jpg', [320, 640, 960])
1612
+
1613
+ // Calculate aspect ratio
1614
+ Image.calculateAspectRatio(1920, 1080) // "16/9"
1615
+
1616
+ // Generate sizes attribute
1617
+ Image.generateSizesAttribute('https://example.com/image.jpg', {
1618
+ '(min-width: 1024px)': 1000,
1619
+ '(min-width: 768px)': 800
1620
+ })
1621
+
1622
+ // Create responsive image object
1623
+ const responsive = Image.createResponsiveImage({
1624
+ src: 'https://example.com/hero.jpg',
1625
+ alt: 'Hero',
1626
+ widths: [320, 640, 1024],
1627
+ lazy: true,
1628
+ placeholder: true
1629
+ })
1630
+
1631
+ // Prefetch images
1632
+ await Image.prefetchImages(['/img1.jpg', '/img2.jpg'])
1633
+
1634
+ // Check if loaded
1635
+ Image.isImageLoaded(imgElement)
1636
+
1637
+ // Get dimensions
1638
+ const dims = await Image.getImageDimensions('https://example.com/image.jpg')
1639
+
1640
+ // Observe visibility
1641
+ const observer = Image.observeImageVisibility(imgElement, (visible) => {
1642
+ if (visible) loadHighResImage()
1643
+ })
1644
+
1645
+ // Swap source
1646
+ Image.swapImageSource(imgElement, '/new-image.jpg', '/new-image-2x.jpg 2x')
1647
+
1648
+ // Generate dominant color placeholder
1649
+ const placeholder = await Image.generateDominantColorPlaceholder(src)
1650
+ ```
1651
+
1652
+ ---
1653
+
1654
+ ### `Fonts API`
1655
+
1656
+ Font optimization utilities.
1657
+
1658
+ ```ts
1659
+ import { Fonts } from 'metaowl'
1660
+
1661
+ // Define a font face
1662
+ const fontFace = Fonts.defineFontFace({
1663
+ family: 'Inter',
1664
+ src: 'https://fonts.example.com/inter.woff2',
1665
+ weight: '400',
1666
+ style: 'normal',
1667
+ display: 'swap'
1668
+ })
1669
+
1670
+ // Load and track a font
1671
+ await Fonts.loadFont({
1672
+ family: 'Inter',
1673
+ src: 'https://fonts.example.com/inter.woff2'
1674
+ })
1675
+
1676
+ // Check if loaded
1677
+ Fonts.isFontLoaded('Inter') // true
1678
+
1679
+ // Preload a font
1680
+ Fonts.preloadFont('Inter', 'https://fonts.example.com/inter.woff2')
1681
+
1682
+ // Remove preload link
1683
+ Fonts.removeFontPreload('Inter')
1684
+
1685
+ // Generate @font-face CSS rule
1686
+ const rule = Fonts.createFontFaceRule({
1687
+ family: 'Inter',
1688
+ src: 'https://fonts.example.com/inter.woff2',
1689
+ weight: 'bold'
1690
+ })
1691
+
1692
+ // Inject @font-face rules into document
1693
+ Fonts.injectFontFaceRules({
1694
+ family: 'Inter',
1695
+ src: ['woff2', 'woff'].map(f => `https://fonts.example.com/${f}`),
1696
+ weight: '400 700'
1697
+ })
1698
+
1699
+ // Measure text width
1700
+ Fonts.measureTextWidth('Hello', 'Inter', 16)
1701
+
1702
+ // Adjust for FOUT
1703
+ await Fonts.adjustFontForFout(element, 'sans-serif', 3000)
1704
+
1705
+ // Get loaded font status
1706
+ Fonts.getFontLoadStatus() // { 'Inter': true }
1707
+
1708
+ // Clear all loaded fonts
1709
+ Fonts.clearLoadedFonts()
1710
+ ```
1711
+
1712
+ ---
1713
+
1449
1714
  ## Vite Plugin
1450
1715
 
1451
1716
  ### `metaowlPlugin(options)`
@@ -0,0 +1,10 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * metaowl build — lint then production build.
4
+ */
5
+ import { banner, resolveBin, resolveOwnRuntimeBin, run, success } from './utils.js';
6
+ banner('build');
7
+ run('Linting', `node "${resolveOwnRuntimeBin('metaowl-lint')}"`);
8
+ run('Building', `"${resolveBin('vite')}" build`);
9
+ success('Build complete');
10
+ console.log();