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.
- package/CHANGELOG.md +50 -0
- package/README.md +267 -2
- 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 +144 -0
- package/build/runtime/modules/app-mounter.js +73 -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/constants.js +38 -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 +207 -0
- package/build/runtime/modules/fonts.js +172 -0
- package/build/runtime/modules/forms.js +193 -0
- package/build/runtime/modules/i18n.js +180 -0
- package/build/runtime/modules/image.js +175 -0
- package/build/runtime/modules/layouts.js +214 -0
- package/build/runtime/modules/link.js +141 -0
- package/build/runtime/modules/meta.js +117 -0
- package/build/runtime/modules/odoo-rpc.js +265 -0
- package/build/runtime/modules/pwa.js +272 -0
- package/build/runtime/modules/router.js +384 -0
- package/build/runtime/modules/seo.js +186 -0
- package/build/runtime/modules/store.js +198 -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 +197 -0
- package/eslint.js +29 -0
- package/package.json +45 -27
- 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/eslint.config.js +0 -3
- 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 -290
- package/vitest.config.js +0 -8
package/bin/utils.js
DELETED
|
@@ -1,82 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Shared CLI utilities for metaowl bin scripts.
|
|
3
|
-
* Uses ANSI escape codes only when stdout is a TTY (no color when piped).
|
|
4
|
-
*/
|
|
5
|
-
import { existsSync, readFileSync } from 'node:fs'
|
|
6
|
-
import { resolve, dirname } from 'node:path'
|
|
7
|
-
import { fileURLToPath } from 'node:url'
|
|
8
|
-
import { execSync } from 'node:child_process'
|
|
9
|
-
|
|
10
|
-
export const metaowlRoot = resolve(dirname(fileURLToPath(import.meta.url)), '..')
|
|
11
|
-
export const bin = resolve(metaowlRoot, 'node_modules/.bin')
|
|
12
|
-
export const cwd = process.cwd()
|
|
13
|
-
const cwdBin = resolve(cwd, 'node_modules/.bin')
|
|
14
|
-
|
|
15
|
-
const { version } = JSON.parse(readFileSync(resolve(metaowlRoot, 'package.json'), 'utf-8'))
|
|
16
|
-
export { version }
|
|
17
|
-
|
|
18
|
-
/**
|
|
19
|
-
* Resolve an executable path with fallback for hoisted installs.
|
|
20
|
-
* Priority:
|
|
21
|
-
* 1) metaowl-local node_modules/.bin
|
|
22
|
-
* 2) project node_modules/.bin
|
|
23
|
-
* 3) command name (PATH lookup by shell)
|
|
24
|
-
*
|
|
25
|
-
* @param {string} name
|
|
26
|
-
* @returns {string}
|
|
27
|
-
*/
|
|
28
|
-
export function resolveBin(name) {
|
|
29
|
-
const local = resolve(bin, name)
|
|
30
|
-
if (existsSync(local)) return local
|
|
31
|
-
|
|
32
|
-
const project = resolve(cwdBin, name)
|
|
33
|
-
if (existsSync(project)) return project
|
|
34
|
-
|
|
35
|
-
return name
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
const TTY = Boolean(process.stdout.isTTY)
|
|
39
|
-
const a = (str, code) => TTY ? `\x1b[${code}m${str}\x1b[0m` : str
|
|
40
|
-
|
|
41
|
-
/** Print a styled header for the current command. */
|
|
42
|
-
export function banner(command) {
|
|
43
|
-
console.log()
|
|
44
|
-
console.log(` ${a('metaowl', '1;36')} ${a(command, '1')} ${a(`v${version}`, '2')}`)
|
|
45
|
-
console.log()
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
/** Print a step indicator: " › message" */
|
|
49
|
-
export function step(msg) {
|
|
50
|
-
console.log(` ${a('›', '36')} ${msg}`)
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
/** Print a success line: " ✓ message" */
|
|
54
|
-
export function success(msg) {
|
|
55
|
-
console.log(` ${a('✓', '32')} ${a(msg, '2')}`)
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
/** Print an error line: " ✗ message" */
|
|
59
|
-
export function failure(msg) {
|
|
60
|
-
console.error(` ${a('✗', '31')} ${msg}`)
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
/**
|
|
64
|
-
* Run a shell command, printing a step label before and a blank line after.
|
|
65
|
-
* Exits the process with code 1 on failure.
|
|
66
|
-
*
|
|
67
|
-
* @param {string} label - Human-readable step description.
|
|
68
|
-
* @param {string} cmd - Shell command to execute.
|
|
69
|
-
* @param {object} [opts] - Additional options forwarded to execSync.
|
|
70
|
-
*/
|
|
71
|
-
export function run(label, cmd, opts = {}) {
|
|
72
|
-
step(label)
|
|
73
|
-
console.log()
|
|
74
|
-
try {
|
|
75
|
-
execSync(cmd, { stdio: 'inherit', cwd, ...opts })
|
|
76
|
-
} catch {
|
|
77
|
-
console.log()
|
|
78
|
-
failure(`${label} failed`)
|
|
79
|
-
process.exit(1)
|
|
80
|
-
}
|
|
81
|
-
console.log()
|
|
82
|
-
}
|
package/eslint.config.js
DELETED
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
|
-
}
|