metaowl 0.4.0 → 0.5.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/CHANGELOG.md +52 -0
- package/README.md +13 -15
- package/build/runtime/bin/metaowl-build.js +10 -0
- package/{bin → build/runtime/bin}/metaowl-create.js +96 -177
- package/build/runtime/bin/metaowl-dev.js +10 -0
- package/build/runtime/bin/metaowl-generate.js +231 -0
- package/build/runtime/bin/metaowl-lint.js +58 -0
- package/build/runtime/bin/utils.js +68 -0
- package/build/runtime/index.js +141 -0
- package/build/runtime/modules/app-mounter.js +65 -0
- package/build/runtime/modules/auto-import.js +140 -0
- package/build/runtime/modules/cache.js +49 -0
- package/build/runtime/modules/composables.js +353 -0
- package/build/runtime/modules/error-boundary.js +116 -0
- package/build/runtime/modules/fetch.js +31 -0
- package/build/runtime/modules/file-router.js +205 -0
- package/build/runtime/modules/forms.js +193 -0
- package/build/runtime/modules/i18n.js +167 -0
- package/build/runtime/modules/layouts.js +163 -0
- package/build/runtime/modules/link.js +141 -0
- package/build/runtime/modules/meta.js +117 -0
- package/build/runtime/modules/odoo-rpc.js +264 -0
- package/build/runtime/modules/pwa.js +262 -0
- package/build/runtime/modules/router.js +389 -0
- package/build/runtime/modules/seo.js +186 -0
- package/build/runtime/modules/store.js +196 -0
- package/build/runtime/modules/templates-manager.js +52 -0
- package/build/runtime/modules/test-utils.js +238 -0
- package/build/runtime/vite/plugin.js +183 -0
- package/eslint.js +29 -0
- package/package.json +29 -11
- package/CONTRIBUTING.md +0 -49
- package/bin/metaowl-build.js +0 -12
- package/bin/metaowl-dev.js +0 -12
- package/bin/metaowl-generate.js +0 -339
- package/bin/metaowl-lint.js +0 -71
- package/bin/utils.js +0 -82
- package/index.js +0 -328
- package/modules/app-mounter.js +0 -104
- package/modules/auto-import.js +0 -225
- package/modules/cache.js +0 -59
- package/modules/composables.js +0 -600
- package/modules/error-boundary.js +0 -228
- package/modules/fetch.js +0 -51
- package/modules/file-router.js +0 -478
- package/modules/forms.js +0 -353
- package/modules/i18n.js +0 -333
- package/modules/layouts.js +0 -431
- package/modules/link.js +0 -255
- package/modules/meta.js +0 -119
- package/modules/odoo-rpc.js +0 -511
- package/modules/pwa.js +0 -515
- package/modules/router.js +0 -769
- package/modules/seo.js +0 -501
- package/modules/store.js +0 -409
- package/modules/templates-manager.js +0 -89
- package/modules/test-utils.js +0 -532
- package/test/auto-import.test.js +0 -110
- package/test/cache.test.js +0 -55
- package/test/composables.test.js +0 -103
- package/test/dynamic-routes.test.js +0 -469
- package/test/error-boundary.test.js +0 -126
- package/test/fetch.test.js +0 -100
- package/test/file-router.test.js +0 -55
- package/test/forms.test.js +0 -203
- package/test/i18n.test.js +0 -188
- package/test/layouts.test.js +0 -395
- package/test/link.test.js +0 -189
- package/test/meta.test.js +0 -146
- package/test/odoo-rpc.test.js +0 -547
- package/test/pwa.test.js +0 -154
- package/test/router-guards.test.js +0 -229
- package/test/router.test.js +0 -77
- package/test/seo.test.js +0 -353
- package/test/store.test.js +0 -476
- package/test/templates-manager.test.js +0 -83
- package/test/test-utils.test.js +0 -314
- package/vite/plugin.js +0 -277
- package/vitest.config.js +0 -8
package/index.js
DELETED
|
@@ -1,328 +0,0 @@
|
|
|
1
|
-
import { mountApp } from './modules/app-mounter.js'
|
|
2
|
-
import { buildRoutes } from './modules/file-router.js'
|
|
3
|
-
import { processRoutes, setSpaMode, _setSpaNavigationCallback } from './modules/router.js'
|
|
4
|
-
import { discoverLayouts, buildLayouts, setDefaultLayout } from './modules/layouts.js'
|
|
5
|
-
|
|
6
|
-
export { default as Fetch } from './modules/fetch.js'
|
|
7
|
-
export { default as Cache } from './modules/cache.js'
|
|
8
|
-
export { configureOwl } from './modules/app-mounter.js'
|
|
9
|
-
export * as Meta from './modules/meta.js'
|
|
10
|
-
export { buildRoutes }
|
|
11
|
-
export { Store, createPersistencePlugin, createStore } from './modules/store.js'
|
|
12
|
-
export {
|
|
13
|
-
registerLayout,
|
|
14
|
-
unregisterLayout,
|
|
15
|
-
getLayout,
|
|
16
|
-
hasLayout,
|
|
17
|
-
getLayoutNames,
|
|
18
|
-
setDefaultLayout,
|
|
19
|
-
getDefaultLayout,
|
|
20
|
-
resolveLayout,
|
|
21
|
-
setRouteLayout,
|
|
22
|
-
getRouteLayout,
|
|
23
|
-
createLayoutWrapper,
|
|
24
|
-
mountWithLayout,
|
|
25
|
-
getCurrentLayout,
|
|
26
|
-
subscribeToLayouts,
|
|
27
|
-
clearLayouts,
|
|
28
|
-
layout,
|
|
29
|
-
defineLayout,
|
|
30
|
-
buildLayouts,
|
|
31
|
-
discoverLayouts
|
|
32
|
-
} from './modules/layouts.js'
|
|
33
|
-
export {
|
|
34
|
-
processRoutes,
|
|
35
|
-
beforeEach,
|
|
36
|
-
afterEach,
|
|
37
|
-
getCurrentRoute,
|
|
38
|
-
getPreviousRoute,
|
|
39
|
-
isNavigating,
|
|
40
|
-
cancelNavigation,
|
|
41
|
-
navigate,
|
|
42
|
-
navigateTo,
|
|
43
|
-
push,
|
|
44
|
-
replace,
|
|
45
|
-
back,
|
|
46
|
-
forward,
|
|
47
|
-
go,
|
|
48
|
-
router,
|
|
49
|
-
setSpaMode,
|
|
50
|
-
isSpaMode
|
|
51
|
-
} from './modules/router.js'
|
|
52
|
-
export { Link, registerLinkTemplate } from './modules/link.js'
|
|
53
|
-
export {
|
|
54
|
-
matchRoute,
|
|
55
|
-
isDynamicRoute,
|
|
56
|
-
findRoute,
|
|
57
|
-
generateUrl,
|
|
58
|
-
validateRouteParams,
|
|
59
|
-
createCatchAllRoute,
|
|
60
|
-
createRedirectRoute,
|
|
61
|
-
defineRoute,
|
|
62
|
-
route
|
|
63
|
-
} from './modules/file-router.js'
|
|
64
|
-
export {
|
|
65
|
-
onError,
|
|
66
|
-
setErrorContext,
|
|
67
|
-
getErrorContext,
|
|
68
|
-
clearErrorContext,
|
|
69
|
-
captureError,
|
|
70
|
-
initGlobalErrorHandling,
|
|
71
|
-
errorBoundary
|
|
72
|
-
} from './modules/error-boundary.js'
|
|
73
|
-
export {
|
|
74
|
-
configureI18n,
|
|
75
|
-
t,
|
|
76
|
-
getLocale,
|
|
77
|
-
setLocale,
|
|
78
|
-
i18n,
|
|
79
|
-
loadLocaleMessages,
|
|
80
|
-
formatDate,
|
|
81
|
-
formatNumber,
|
|
82
|
-
formatCurrency,
|
|
83
|
-
formatRelativeTime,
|
|
84
|
-
createNamespacedT
|
|
85
|
-
} from './modules/i18n.js'
|
|
86
|
-
export {
|
|
87
|
-
useForm,
|
|
88
|
-
validators,
|
|
89
|
-
createSchema,
|
|
90
|
-
fieldProps
|
|
91
|
-
} from './modules/forms.js'
|
|
92
|
-
export {
|
|
93
|
-
OdooService,
|
|
94
|
-
configure,
|
|
95
|
-
authenticate,
|
|
96
|
-
logout,
|
|
97
|
-
searchRead,
|
|
98
|
-
call,
|
|
99
|
-
read,
|
|
100
|
-
create,
|
|
101
|
-
write,
|
|
102
|
-
unlink,
|
|
103
|
-
searchCount,
|
|
104
|
-
listDatabases,
|
|
105
|
-
versionInfo,
|
|
106
|
-
isAuthenticated,
|
|
107
|
-
getSession,
|
|
108
|
-
onAuthChange
|
|
109
|
-
} from './modules/odoo-rpc.js'
|
|
110
|
-
export {
|
|
111
|
-
useAuth,
|
|
112
|
-
useLocalStorage,
|
|
113
|
-
useFetch,
|
|
114
|
-
useDebounce,
|
|
115
|
-
useThrottle,
|
|
116
|
-
useWindowSize,
|
|
117
|
-
useOnlineStatus,
|
|
118
|
-
useAsyncState,
|
|
119
|
-
useCache,
|
|
120
|
-
Composables
|
|
121
|
-
} from './modules/composables.js'
|
|
122
|
-
export {
|
|
123
|
-
createMockStore,
|
|
124
|
-
mockRouter,
|
|
125
|
-
mountComponent,
|
|
126
|
-
wait,
|
|
127
|
-
nextTick,
|
|
128
|
-
flushPromises,
|
|
129
|
-
userEvent,
|
|
130
|
-
dom,
|
|
131
|
-
TestUtils
|
|
132
|
-
} from './modules/test-utils.js'
|
|
133
|
-
export {
|
|
134
|
-
generateSitemap,
|
|
135
|
-
generateRobotsTxt,
|
|
136
|
-
jsonLd,
|
|
137
|
-
createCanonicalUrl,
|
|
138
|
-
generateOpenGraph,
|
|
139
|
-
generateTwitterCard,
|
|
140
|
-
validateSitemap,
|
|
141
|
-
getPriorityByDepth,
|
|
142
|
-
generateSitemapIndex,
|
|
143
|
-
SEO
|
|
144
|
-
} from './modules/seo.js'
|
|
145
|
-
export {
|
|
146
|
-
generateManifest,
|
|
147
|
-
registerServiceWorker,
|
|
148
|
-
unregisterServiceWorker,
|
|
149
|
-
isStandalone,
|
|
150
|
-
isOnline,
|
|
151
|
-
subscribeToConnectivity,
|
|
152
|
-
requestPersistentStorage,
|
|
153
|
-
getStorageInfo,
|
|
154
|
-
sync,
|
|
155
|
-
subscribeToPush,
|
|
156
|
-
unsubscribeFromPush,
|
|
157
|
-
showNotification,
|
|
158
|
-
cache,
|
|
159
|
-
checkCapabilities,
|
|
160
|
-
PWA
|
|
161
|
-
} from './modules/pwa.js'
|
|
162
|
-
|
|
163
|
-
/**
|
|
164
|
-
* Global routes reference for SPA navigation.
|
|
165
|
-
* @type {object[]|null}
|
|
166
|
-
*/
|
|
167
|
-
let _appRoutes = null
|
|
168
|
-
|
|
169
|
-
/**
|
|
170
|
-
* Monotonically-increasing navigation counter.
|
|
171
|
-
* Incremented on every navigation attempt; lets us discard stale navigations
|
|
172
|
-
* that complete AFTER a newer one was already triggered.
|
|
173
|
-
* @type {number}
|
|
174
|
-
*/
|
|
175
|
-
let _navSeq = 0
|
|
176
|
-
|
|
177
|
-
/**
|
|
178
|
-
* Promise of the currently-running mountApp call.
|
|
179
|
-
* Used to serialize mounts: a new navigation waits for the in-progress mount
|
|
180
|
-
* to finish, then checks if it is still the latest before mounting itself.
|
|
181
|
-
* This prevents concurrent OWL App instances on the same element.
|
|
182
|
-
* @type {Promise<void>|null}
|
|
183
|
-
*/
|
|
184
|
-
let _mountingPromise = null
|
|
185
|
-
|
|
186
|
-
function _handle404() {
|
|
187
|
-
const el = document.getElementById('metaowl')
|
|
188
|
-
if (el) {
|
|
189
|
-
el.innerHTML = [
|
|
190
|
-
'<div style="font-family:sans-serif;padding:3rem;text-align:center">',
|
|
191
|
-
'<h1 style="font-size:4rem;font-weight:700;margin:0;color:#6b7280">404</h1>',
|
|
192
|
-
'<p style="font-size:1.25rem;color:#9ca3af;margin-top:0.5rem">Page not found</p>',
|
|
193
|
-
'<p style="margin-top:2rem"><a href="/" style="color:#3b82f6;text-decoration:none">← Go home</a></p>',
|
|
194
|
-
'</div>'
|
|
195
|
-
].join('')
|
|
196
|
-
}
|
|
197
|
-
}
|
|
198
|
-
|
|
199
|
-
/**
|
|
200
|
-
* SPA navigation callback.
|
|
201
|
-
* Called when navigateTo() is used.
|
|
202
|
-
*
|
|
203
|
-
* @param {string} path - The target path
|
|
204
|
-
* @returns {Promise<void>}
|
|
205
|
-
*/
|
|
206
|
-
async function _spaNavigate(path) {
|
|
207
|
-
if (!_appRoutes) {
|
|
208
|
-
console.error('[metaowl] Routes not available for SPA navigation')
|
|
209
|
-
return
|
|
210
|
-
}
|
|
211
|
-
|
|
212
|
-
const seq = ++_navSeq
|
|
213
|
-
|
|
214
|
-
let route
|
|
215
|
-
try {
|
|
216
|
-
route = await processRoutes(_appRoutes, path)
|
|
217
|
-
} catch (error) {
|
|
218
|
-
if (seq !== _navSeq) return
|
|
219
|
-
if (error.message && error.message.startsWith('No route found')) {
|
|
220
|
-
console.warn('[metaowl]', error.message)
|
|
221
|
-
_handle404()
|
|
222
|
-
} else {
|
|
223
|
-
throw error
|
|
224
|
-
}
|
|
225
|
-
return
|
|
226
|
-
}
|
|
227
|
-
|
|
228
|
-
// Bail early if a newer navigation overtook us while processRoutes was running
|
|
229
|
-
if (seq !== _navSeq || !route) return
|
|
230
|
-
|
|
231
|
-
// Wait for any in-progress mount to finish before starting our own.
|
|
232
|
-
// This is the key serialization: it ensures only one OWL App mounts at a time.
|
|
233
|
-
if (_mountingPromise) {
|
|
234
|
-
await _mountingPromise.catch(() => {})
|
|
235
|
-
// After waiting, check again — a newer navigation may have started
|
|
236
|
-
if (seq !== _navSeq) return
|
|
237
|
-
}
|
|
238
|
-
|
|
239
|
-
// Claim the mount slot
|
|
240
|
-
_mountingPromise = mountApp(route)
|
|
241
|
-
try {
|
|
242
|
-
await _mountingPromise
|
|
243
|
-
} finally {
|
|
244
|
-
_mountingPromise = null
|
|
245
|
-
}
|
|
246
|
-
}
|
|
247
|
-
|
|
248
|
-
/**
|
|
249
|
-
* Boots the metaowl application.
|
|
250
|
-
*
|
|
251
|
-
* When called without arguments inside a Vite project, the `metaowl:app`
|
|
252
|
-
* plugin transform automatically rewrites `boot()` to
|
|
253
|
-
* `boot(import.meta.glob('./pages/**\/*.js', { eager: true }))` at build time.
|
|
254
|
-
*
|
|
255
|
-
* Can also be called explicitly with:
|
|
256
|
-
* - An import.meta.glob result (file-based routing):
|
|
257
|
-
* boot(import.meta.glob('./pages/**\/*.js', { eager: true }))
|
|
258
|
-
* - A manual route array:
|
|
259
|
-
* boot([{ name: 'index', path: ['/'], component: IndexPage }])
|
|
260
|
-
*
|
|
261
|
-
* @param {Record<string, object>|object[]} [routesOrModules]
|
|
262
|
-
* @param {object} [options] - Boot options
|
|
263
|
-
* @param {boolean} [options.spa=true] - Enable SPA navigation mode
|
|
264
|
-
*/
|
|
265
|
-
export async function boot(routesOrModules = {}, layoutsOrModules = null, options = {}) {
|
|
266
|
-
const { spa = true } = options
|
|
267
|
-
|
|
268
|
-
// Auto-discover layouts
|
|
269
|
-
try {
|
|
270
|
-
if (layoutsOrModules && typeof layoutsOrModules === 'object' && !Array.isArray(layoutsOrModules)) {
|
|
271
|
-
// Use layouts provided by Vite plugin transformation
|
|
272
|
-
buildLayouts(layoutsOrModules)
|
|
273
|
-
setDefaultLayout('default')
|
|
274
|
-
} else if (typeof layoutsOrModules === 'object' && layoutsOrModules?.spa !== undefined) {
|
|
275
|
-
// Options object passed as second argument
|
|
276
|
-
Object.assign(options, layoutsOrModules)
|
|
277
|
-
} else {
|
|
278
|
-
await discoverLayouts()
|
|
279
|
-
}
|
|
280
|
-
} catch (e) {
|
|
281
|
-
console.warn('[metaowl] Could not auto-discover layouts:', e.message)
|
|
282
|
-
}
|
|
283
|
-
|
|
284
|
-
const routes = Array.isArray(routesOrModules)
|
|
285
|
-
? routesOrModules
|
|
286
|
-
: buildRoutes(routesOrModules)
|
|
287
|
-
|
|
288
|
-
// Store routes for SPA navigation
|
|
289
|
-
_appRoutes = routes
|
|
290
|
-
|
|
291
|
-
// Enable SPA mode
|
|
292
|
-
if (spa) {
|
|
293
|
-
setSpaMode(true)
|
|
294
|
-
_setSpaNavigationCallback(_spaNavigate)
|
|
295
|
-
|
|
296
|
-
// Register global navigateTo handler for Link component
|
|
297
|
-
window.__metaowlNavigate = _spaNavigate
|
|
298
|
-
|
|
299
|
-
// Listen to PopState events (Browser Back/Forward)
|
|
300
|
-
window.addEventListener('popstate', (event) => {
|
|
301
|
-
const path = document.location.pathname
|
|
302
|
-
_spaNavigate(path)
|
|
303
|
-
})
|
|
304
|
-
}
|
|
305
|
-
|
|
306
|
-
let route
|
|
307
|
-
try {
|
|
308
|
-
route = await processRoutes(routes)
|
|
309
|
-
} catch (error) {
|
|
310
|
-
if (error.message && error.message.startsWith('No route found')) {
|
|
311
|
-
console.warn('[metaowl]', error.message)
|
|
312
|
-
const el = document.getElementById('metaowl')
|
|
313
|
-
if (el) {
|
|
314
|
-
el.innerHTML = [
|
|
315
|
-
'<div style="font-family:sans-serif;padding:3rem;text-align:center">',
|
|
316
|
-
'<h1 style="font-size:4rem;font-weight:700;margin:0;color:#6b7280">404</h1>',
|
|
317
|
-
'<p style="font-size:1.25rem;color:#9ca3af;margin-top:0.5rem">Page not found</p>',
|
|
318
|
-
'<p style="margin-top:2rem"><a href="/" style="color:#3b82f6;text-decoration:none">← Go home</a></p>',
|
|
319
|
-
'</div>'
|
|
320
|
-
].join('')
|
|
321
|
-
}
|
|
322
|
-
return
|
|
323
|
-
}
|
|
324
|
-
throw error
|
|
325
|
-
}
|
|
326
|
-
|
|
327
|
-
await mountApp(route)
|
|
328
|
-
}
|
package/modules/app-mounter.js
DELETED
|
@@ -1,104 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* @module AppMounter
|
|
3
|
-
*
|
|
4
|
-
* OWL application mounting with template merging.
|
|
5
|
-
* Handles the low-level mounting of components into the DOM with
|
|
6
|
-
* merged XML templates from the build process.
|
|
7
|
-
*/
|
|
8
|
-
import { mount } from '@odoo/owl'
|
|
9
|
-
import { mergeTemplates } from './templates-manager.js'
|
|
10
|
-
import { resolveLayout, getLayout, mountWithLayout } from './layouts.js'
|
|
11
|
-
import { Link } from './link.js'
|
|
12
|
-
|
|
13
|
-
const _defaults = {
|
|
14
|
-
warnIfNoStaticProps: true,
|
|
15
|
-
willStartTimeout: 10000,
|
|
16
|
-
translatableAttributes: ['title', 'placeholder', 'label', 'alt']
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
let _config = { ..._defaults }
|
|
20
|
-
|
|
21
|
-
/**
|
|
22
|
-
* Reference to the currently mounted OWL App instance.
|
|
23
|
-
* Destroyed before each new mount to prevent zombie app accumulation.
|
|
24
|
-
* @type {import('@odoo/owl').App|null}
|
|
25
|
-
*/
|
|
26
|
-
let _currentApp = null
|
|
27
|
-
|
|
28
|
-
/**
|
|
29
|
-
* Override or extend the default OWL mount configuration.
|
|
30
|
-
* Call before boot() in your project's metaowl.js.
|
|
31
|
-
*
|
|
32
|
-
* @param {object} config - Partial OWL config merged over the defaults.
|
|
33
|
-
*/
|
|
34
|
-
export function configureOwl(config) {
|
|
35
|
-
_config = { ..._defaults, ...config }
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
/**
|
|
39
|
-
* Mount the resolved route's OWL component into `#app`.
|
|
40
|
-
*
|
|
41
|
-
* Loads and merges all XML templates (collected at build time by the
|
|
42
|
-
* metaowl Vite plugin via the `COMPONENTS` define), then mounts the component
|
|
43
|
-
* using the active OWL config.
|
|
44
|
-
*
|
|
45
|
-
* @param {object[]} route - Single-element array returned by `processRoutes()`.
|
|
46
|
-
* @returns {Promise<void>}
|
|
47
|
-
*/
|
|
48
|
-
/**
|
|
49
|
-
* Cached merged templates string. Computed once on first navigation;
|
|
50
|
-
* COMPONENTS (the list of XML files) never changes at runtime so the
|
|
51
|
-
* result is the same for every mount.
|
|
52
|
-
* @type {string|null}
|
|
53
|
-
*/
|
|
54
|
-
let _cachedTemplates = null
|
|
55
|
-
|
|
56
|
-
export async function mountApp(route) {
|
|
57
|
-
// Load and cache templates on first call; reuse on every subsequent navigation.
|
|
58
|
-
// Without caching, every navigation re-fetches all XML template files.
|
|
59
|
-
const components = typeof COMPONENTS !== 'undefined' ? COMPONENTS : []
|
|
60
|
-
if (!_cachedTemplates) {
|
|
61
|
-
_cachedTemplates = await mergeTemplates(components)
|
|
62
|
-
}
|
|
63
|
-
const templates = _cachedTemplates
|
|
64
|
-
const mountElement = document.getElementById('metaowl')
|
|
65
|
-
|
|
66
|
-
// Destroy the previous OWL App before mounting a new one.
|
|
67
|
-
// Without this, every navigation leaves a zombie app running in the background
|
|
68
|
-
// (scheduler, reactive effects, event listeners) that accumulates and causes freezes.
|
|
69
|
-
if (_currentApp) {
|
|
70
|
-
try { _currentApp.destroy() } catch (_) {}
|
|
71
|
-
_currentApp = null
|
|
72
|
-
}
|
|
73
|
-
mountElement.innerHTML = ''
|
|
74
|
-
|
|
75
|
-
const pageComponent = route[0].component
|
|
76
|
-
const pagePath = route[0].path
|
|
77
|
-
|
|
78
|
-
// Check for layout
|
|
79
|
-
const layoutName = resolveLayout(pageComponent, pagePath)
|
|
80
|
-
const LayoutClass = getLayout(layoutName)
|
|
81
|
-
|
|
82
|
-
// Base mount configuration with built-in components
|
|
83
|
-
const baseConfig = {
|
|
84
|
-
..._config,
|
|
85
|
-
templates,
|
|
86
|
-
components: {
|
|
87
|
-
Link,
|
|
88
|
-
't-link': Link
|
|
89
|
-
}
|
|
90
|
-
}
|
|
91
|
-
|
|
92
|
-
let instance
|
|
93
|
-
if (LayoutClass) {
|
|
94
|
-
// Mount with layout
|
|
95
|
-
instance = await mountWithLayout(pageComponent, mountElement, { routePath: pagePath, ...baseConfig })
|
|
96
|
-
} else {
|
|
97
|
-
// Mount without layout
|
|
98
|
-
instance = await mount(pageComponent, mountElement, baseConfig)
|
|
99
|
-
}
|
|
100
|
-
|
|
101
|
-
// Store OWL App reference so we can destroy it before the next navigation.
|
|
102
|
-
// instance.__owl__.app is the underlying App object that owns the scheduler.
|
|
103
|
-
_currentApp = instance?.__owl__?.app ?? null
|
|
104
|
-
}
|
package/modules/auto-import.js
DELETED
|
@@ -1,225 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* @module AutoImport
|
|
3
|
-
*
|
|
4
|
-
* Automatic component importing for MetaOwl applications.
|
|
5
|
-
*
|
|
6
|
-
* Features:
|
|
7
|
-
* - Auto-discovers components from src/components/
|
|
8
|
-
* - Generates import statements at build time
|
|
9
|
-
* - Optional - disabled by default
|
|
10
|
-
*
|
|
11
|
-
* Configuration in vite.config.js:
|
|
12
|
-
* metaowlConfig({
|
|
13
|
-
* autoImport: {
|
|
14
|
-
* enabled: true,
|
|
15
|
-
* componentsDir: 'src/components',
|
|
16
|
-
* pattern: '*.js'
|
|
17
|
-
* }
|
|
18
|
-
* })
|
|
19
|
-
*
|
|
20
|
-
* Usage:
|
|
21
|
-
* - Components are automatically available in templates
|
|
22
|
-
* - No manual import needed
|
|
23
|
-
*/
|
|
24
|
-
|
|
25
|
-
import { globSync } from 'glob'
|
|
26
|
-
import { resolve, relative, basename, extname, dirname } from 'node:path'
|
|
27
|
-
|
|
28
|
-
/**
|
|
29
|
-
* Registry of auto-discovered components.
|
|
30
|
-
*/
|
|
31
|
-
let _importMap = null
|
|
32
|
-
|
|
33
|
-
/**
|
|
34
|
-
* Generate component import map from directory.
|
|
35
|
-
*
|
|
36
|
-
* @param {string} componentsDir - e.g. 'src/components'
|
|
37
|
-
* @returns {Map<string, string>} Map of PascalCase names to import paths
|
|
38
|
-
*/
|
|
39
|
-
export function generateComponentMap(componentsDir, pattern = '*.js') {
|
|
40
|
-
const map = new Map()
|
|
41
|
-
const globPattern = pattern.includes('/') ? pattern : `${componentsDir}/${pattern}`
|
|
42
|
-
const files = globSync(globPattern)
|
|
43
|
-
|
|
44
|
-
for (const file of files) {
|
|
45
|
-
if (file.includes('.test.') || file.includes('.spec.')) continue
|
|
46
|
-
|
|
47
|
-
const name = getComponentName(file)
|
|
48
|
-
if (!name) continue
|
|
49
|
-
|
|
50
|
-
const importPath = `/@components/${relative(componentsDir, file).replace(/\\/g, '/')}`
|
|
51
|
-
map.set(name, importPath)
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
return map
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
function getComponentName(filePath) {
|
|
58
|
-
const ext = extname(filePath)
|
|
59
|
-
const base = basename(filePath, ext)
|
|
60
|
-
|
|
61
|
-
if (basename(filePath) === base + ext) {
|
|
62
|
-
return base
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
const dir = relative(process.cwd(), filePath).split('/')
|
|
66
|
-
if (base === 'index' && dir.length > 1) {
|
|
67
|
-
return toPascalCase(dir[dir.length - 2])
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
return toPascalCase(base)
|
|
71
|
-
}
|
|
72
|
-
|
|
73
|
-
function toPascalCase(str) {
|
|
74
|
-
return str
|
|
75
|
-
.replace(/[-_](.)/g, (_, char) => char.toUpperCase())
|
|
76
|
-
.replace(/^[a-z]/, char => char.toUpperCase())
|
|
77
|
-
}
|
|
78
|
-
|
|
79
|
-
/**
|
|
80
|
-
* Scan components directory for component files.
|
|
81
|
-
*
|
|
82
|
-
* @param {string} componentsDir - Directory to scan
|
|
83
|
-
* @param {object} options - Options
|
|
84
|
-
* @param {string} options.pattern - Glob pattern
|
|
85
|
-
* @returns {Promise<string[]>} Array of component names
|
|
86
|
-
*/
|
|
87
|
-
export async function scanComponents(componentsDir, options = {}) {
|
|
88
|
-
const { pattern = '*.js' } = options
|
|
89
|
-
const absoluteDir = resolve(componentsDir)
|
|
90
|
-
const fs = await import('fs/promises')
|
|
91
|
-
const { join } = await import('path')
|
|
92
|
-
|
|
93
|
-
const components = []
|
|
94
|
-
|
|
95
|
-
async function scanDir(dir) {
|
|
96
|
-
try {
|
|
97
|
-
const entries = await fs.readdir(dir, { withFileTypes: true })
|
|
98
|
-
|
|
99
|
-
for (const entry of entries) {
|
|
100
|
-
const fullPath = join(dir, entry.name)
|
|
101
|
-
|
|
102
|
-
if (entry.isDirectory()) {
|
|
103
|
-
// Recursively scan subdirectories
|
|
104
|
-
await scanDir(fullPath)
|
|
105
|
-
} else if (entry.isFile() && entry.name.endsWith('.js')) {
|
|
106
|
-
// Skip test files
|
|
107
|
-
if (entry.name.includes('.test.') || entry.name.includes('.spec.')) continue
|
|
108
|
-
|
|
109
|
-
const name = getComponentName(fullPath)
|
|
110
|
-
if (name && !components.includes(name)) {
|
|
111
|
-
components.push(name)
|
|
112
|
-
}
|
|
113
|
-
}
|
|
114
|
-
}
|
|
115
|
-
} catch {
|
|
116
|
-
// Directory doesn't exist or can't be read
|
|
117
|
-
}
|
|
118
|
-
}
|
|
119
|
-
|
|
120
|
-
await scanDir(absoluteDir)
|
|
121
|
-
return components
|
|
122
|
-
}
|
|
123
|
-
|
|
124
|
-
/**
|
|
125
|
-
* Generate TypeScript declarations file for components.
|
|
126
|
-
*
|
|
127
|
-
* @param {string[]} components - Array of component names
|
|
128
|
-
* @param {string} outputPath - Path to write .d.ts file
|
|
129
|
-
* @returns {Promise<void>}
|
|
130
|
-
*/
|
|
131
|
-
export async function generateComponentDts(components, outputPath) {
|
|
132
|
-
const { writeFileSync, mkdirSync } = await import('fs')
|
|
133
|
-
const { join } = await import('path')
|
|
134
|
-
|
|
135
|
-
// Ensure directory exists
|
|
136
|
-
const dir = dirname(outputPath)
|
|
137
|
-
mkdirSync(dir, { recursive: true })
|
|
138
|
-
|
|
139
|
-
const declarations = components.map(name =>
|
|
140
|
-
` ${name}: typeof import('./components/${name}/${name}.js').default`
|
|
141
|
-
).join('\n')
|
|
142
|
-
|
|
143
|
-
const content = `// Auto-generated by metaowl - do not edit\ndeclare module '@metaowl/components' {\n${declarations}\n}\n`
|
|
144
|
-
|
|
145
|
-
writeFileSync(outputPath, content, 'utf-8')
|
|
146
|
-
}
|
|
147
|
-
|
|
148
|
-
export function generateImports(componentMap) {
|
|
149
|
-
const lines = []
|
|
150
|
-
for (const [name, path] of componentMap) {
|
|
151
|
-
lines.push(`import ${name} from '${path}'`)
|
|
152
|
-
}
|
|
153
|
-
return lines.join('\n')
|
|
154
|
-
}
|
|
155
|
-
|
|
156
|
-
export function generateComponentsObject(componentMap) {
|
|
157
|
-
const entries = Array.from(componentMap.keys())
|
|
158
|
-
.map(name => ` ${name}`)
|
|
159
|
-
.join(',\n')
|
|
160
|
-
return `{\n${entries}\n}`
|
|
161
|
-
}
|
|
162
|
-
|
|
163
|
-
export function createAutoImportPlugin(options = {}) {
|
|
164
|
-
const {
|
|
165
|
-
enabled = false,
|
|
166
|
-
componentsDir = 'src/components',
|
|
167
|
-
pattern = '**/*.js'
|
|
168
|
-
} = options
|
|
169
|
-
|
|
170
|
-
if (!enabled) {
|
|
171
|
-
return null
|
|
172
|
-
}
|
|
173
|
-
|
|
174
|
-
_importMap = generateComponentMap(componentsDir, `${componentsDir}/${pattern}`)
|
|
175
|
-
|
|
176
|
-
return {
|
|
177
|
-
name: 'metaowl:auto-import',
|
|
178
|
-
enforce: 'pre',
|
|
179
|
-
|
|
180
|
-
config(config) {
|
|
181
|
-
config.resolve ||= {}
|
|
182
|
-
config.resolve.alias ||= {}
|
|
183
|
-
config.resolve.alias['/@components'] = resolve(process.cwd(), componentsDir)
|
|
184
|
-
},
|
|
185
|
-
|
|
186
|
-
transform(code, id) {
|
|
187
|
-
if (!id.includes('/pages/') || !/\.[jt]s$/.test(id)) {
|
|
188
|
-
return null
|
|
189
|
-
}
|
|
190
|
-
|
|
191
|
-
if (!code.includes('/* auto-import */') && !code.includes('// auto-import')) {
|
|
192
|
-
return null
|
|
193
|
-
}
|
|
194
|
-
|
|
195
|
-
const imports = generateImports(_importMap)
|
|
196
|
-
const componentsObj = generateComponentsObject(_importMap)
|
|
197
|
-
|
|
198
|
-
let transformed = imports + '\n\n' + code
|
|
199
|
-
|
|
200
|
-
if (transformed.includes('extends Component')) {
|
|
201
|
-
transformed = transformed.replace(
|
|
202
|
-
/(class\s+\w+\s+extends\s+Component\s*\{)/,
|
|
203
|
-
`$1\n static components = ${componentsObj}\n`
|
|
204
|
-
)
|
|
205
|
-
}
|
|
206
|
-
|
|
207
|
-
return { code: transformed, map: null }
|
|
208
|
-
}
|
|
209
|
-
}
|
|
210
|
-
}
|
|
211
|
-
|
|
212
|
-
export function registerAutoImport(name, path) {
|
|
213
|
-
if (!_importMap) {
|
|
214
|
-
_importMap = new Map()
|
|
215
|
-
}
|
|
216
|
-
_importMap.set(name, path)
|
|
217
|
-
}
|
|
218
|
-
|
|
219
|
-
export function getAutoImportMap() {
|
|
220
|
-
return _importMap ? new Map(_importMap) : null
|
|
221
|
-
}
|
|
222
|
-
|
|
223
|
-
export function clearAutoImports() {
|
|
224
|
-
_importMap = null
|
|
225
|
-
}
|
package/modules/cache.js
DELETED
|
@@ -1,59 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* @module Cache
|
|
3
|
-
*
|
|
4
|
-
* Async-style localStorage wrapper.
|
|
5
|
-
*
|
|
6
|
-
* Values are automatically JSON-serialised on write and deserialised on read.
|
|
7
|
-
* All methods return Promises so they are interchangeable with IndexedDB-based
|
|
8
|
-
* alternatives without changing call-sites.
|
|
9
|
-
*/
|
|
10
|
-
export default class Cache {
|
|
11
|
-
/**
|
|
12
|
-
* Retrieve a value by key.
|
|
13
|
-
*
|
|
14
|
-
* @param {string} key
|
|
15
|
-
* @returns {Promise<any>} Parsed value, or `null` if the key does not exist.
|
|
16
|
-
*/
|
|
17
|
-
static async get(key) {
|
|
18
|
-
return JSON.parse(localStorage.getItem(key))
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
/**
|
|
22
|
-
* Store a value under the given key.
|
|
23
|
-
*
|
|
24
|
-
* @param {string} key
|
|
25
|
-
* @param {any} value - Must be JSON-serialisable.
|
|
26
|
-
* @returns {Promise<void>}
|
|
27
|
-
*/
|
|
28
|
-
static async set(key, value) {
|
|
29
|
-
localStorage.setItem(key, JSON.stringify(value))
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
/**
|
|
33
|
-
* Remove a single entry.
|
|
34
|
-
*
|
|
35
|
-
* @param {string} key
|
|
36
|
-
* @returns {Promise<void>}
|
|
37
|
-
*/
|
|
38
|
-
static async remove(key) {
|
|
39
|
-
localStorage.removeItem(key)
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
/**
|
|
43
|
-
* Remove **all** entries from localStorage.
|
|
44
|
-
*
|
|
45
|
-
* @returns {Promise<void>}
|
|
46
|
-
*/
|
|
47
|
-
static async clear() {
|
|
48
|
-
localStorage.clear()
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
/**
|
|
52
|
-
* Return all keys currently stored in localStorage.
|
|
53
|
-
*
|
|
54
|
-
* @returns {Promise<string[]>}
|
|
55
|
-
*/
|
|
56
|
-
static async keys() {
|
|
57
|
-
return Array.from({ length: localStorage.length }, (_, i) => localStorage.key(i))
|
|
58
|
-
}
|
|
59
|
-
}
|