metaowl 0.2.16 → 0.3.1
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/index.js +1 -2
- package/modules/file-router.js +8 -6
- package/modules/layouts.js +1 -3
- package/modules/router.js +22 -5
- package/package.json +3 -5
- package/postcss.cjs +8 -23
- package/test/dynamic-routes.test.js +0 -51
- package/test/layouts.test.js +3 -3
- package/test/router-guards.test.js +37 -425
- package/vite/plugin.js +0 -19
package/index.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { mountApp } from './modules/app-mounter.js'
|
|
2
2
|
import { buildRoutes } from './modules/file-router.js'
|
|
3
3
|
import { processRoutes } from './modules/router.js'
|
|
4
|
-
import { discoverLayouts } from './modules/layouts.js'
|
|
4
|
+
import { discoverLayouts, buildLayouts, setDefaultLayout } from './modules/layouts.js'
|
|
5
5
|
|
|
6
6
|
export { default as Fetch } from './modules/fetch.js'
|
|
7
7
|
export { default as Cache } from './modules/cache.js'
|
|
@@ -176,7 +176,6 @@ export async function boot(routesOrModules = {}, layoutsOrModules = null) {
|
|
|
176
176
|
try {
|
|
177
177
|
if (layoutsOrModules) {
|
|
178
178
|
// Use layouts provided by Vite plugin transformation
|
|
179
|
-
const { buildLayouts, setDefaultLayout } = await import('./modules/layouts.js')
|
|
180
179
|
buildLayouts(layoutsOrModules)
|
|
181
180
|
setDefaultLayout('default')
|
|
182
181
|
} else {
|
package/modules/file-router.js
CHANGED
|
@@ -123,7 +123,7 @@ function extractParamNames(filePath) {
|
|
|
123
123
|
* @param {string} key - import.meta.glob key, e.g. './pages/about/About.js'
|
|
124
124
|
* @returns {string} URL pattern
|
|
125
125
|
*/
|
|
126
|
-
function pathFromKey(key) {
|
|
126
|
+
export function pathFromKey(key) {
|
|
127
127
|
// Strip leading './' and 'pages/' prefix
|
|
128
128
|
const rel = key.replace(/^\.\/pages\//, '')
|
|
129
129
|
// Get all segments
|
|
@@ -179,7 +179,7 @@ function buildRegexPattern(path) {
|
|
|
179
179
|
pattern = pattern.replace(/:([^/(]+)\(\.\*\)/g, '([^/]+(?:/[^/]+)*)')
|
|
180
180
|
|
|
181
181
|
// Replace optional params :name? → optional capture
|
|
182
|
-
pattern = pattern.replace(
|
|
182
|
+
pattern = pattern.replace(/\/:([^/(]+)\?/g, '(?:/([^/]+))?')
|
|
183
183
|
|
|
184
184
|
// Replace required params :name → capture
|
|
185
185
|
pattern = pattern.replace(/:([^/(\s]+)/g, '([^/]+)')
|
|
@@ -259,7 +259,7 @@ export function buildRoutes(modules) {
|
|
|
259
259
|
|
|
260
260
|
for (const [key, mod] of Object.entries(modules)) {
|
|
261
261
|
const path = pathFromKey(key)
|
|
262
|
-
const name = path === '/' ? 'index' : path.slice(1).replace(/[^a-zA-Z0-9]/g, '-')
|
|
262
|
+
const name = path === '/' ? 'index' : path.slice(1).replace(/[^a-zA-Z0-9]/g, '-').replace(/-+/g, '-').replace(/^-|-$/g, '')
|
|
263
263
|
const component = componentFromModule(mod, key)
|
|
264
264
|
const params = extractParamNames(key)
|
|
265
265
|
|
|
@@ -353,8 +353,8 @@ export function generateUrl(routes, name, params = {}) {
|
|
|
353
353
|
path = path.replace(`:${key}?`, value)
|
|
354
354
|
}
|
|
355
355
|
|
|
356
|
-
// Remove remaining optional params
|
|
357
|
-
path = path.replace(/\/:[
|
|
356
|
+
// Remove remaining optional params and trailing ?
|
|
357
|
+
path = path.replace(/\/:[^/]+\?/g, '').replace(/\?$/, '')
|
|
358
358
|
|
|
359
359
|
return path
|
|
360
360
|
}
|
|
@@ -457,8 +457,10 @@ export function createCatchAllRoute(component, options = {}) {
|
|
|
457
457
|
* @returns {object} Redirect route definition
|
|
458
458
|
*/
|
|
459
459
|
export function createRedirectRoute(from, to) {
|
|
460
|
+
// Remove leading slash and convert to dash-separated name
|
|
461
|
+
const name = from.replace(/^\//, '').replace(/[^a-zA-Z0-9]/g, '-').replace(/-+/g, '-')
|
|
460
462
|
return {
|
|
461
|
-
name: `redirect-${
|
|
463
|
+
name: `redirect-${name}`,
|
|
462
464
|
path: [from],
|
|
463
465
|
redirect: to,
|
|
464
466
|
component: null
|
package/modules/layouts.js
CHANGED
|
@@ -57,7 +57,7 @@
|
|
|
57
57
|
* }
|
|
58
58
|
*/
|
|
59
59
|
|
|
60
|
-
import { Component, xml } from '@odoo/owl'
|
|
60
|
+
import { Component, xml, mount } from '@odoo/owl'
|
|
61
61
|
|
|
62
62
|
/**
|
|
63
63
|
* Registry of layout components.
|
|
@@ -279,14 +279,12 @@ export async function mountWithLayout(pageComponent, target, options = {}, confi
|
|
|
279
279
|
|
|
280
280
|
if (!LayoutClass) {
|
|
281
281
|
console.warn(`[metaowl] Layout "${layoutName}" not found, mounting page without layout`)
|
|
282
|
-
const { mount } = await import('@odoo/owl')
|
|
283
282
|
return mount(pageComponent, target, { ...config, props, templates })
|
|
284
283
|
}
|
|
285
284
|
|
|
286
285
|
// Create wrapper that combines layout and page
|
|
287
286
|
const WrapperClass = createLayoutWrapper(LayoutClass, pageComponent, props)
|
|
288
287
|
|
|
289
|
-
const { mount } = await import('@odoo/owl')
|
|
290
288
|
const instance = await mount(WrapperClass, target, { ...config, templates })
|
|
291
289
|
|
|
292
290
|
_currentLayout = instance
|
package/modules/router.js
CHANGED
|
@@ -111,10 +111,11 @@ class Router {
|
|
|
111
111
|
/**
|
|
112
112
|
* Resolve current URL against route table.
|
|
113
113
|
*
|
|
114
|
+
* @param {string} [path] - Optional path to resolve (for testing)
|
|
114
115
|
* @returns {Route|null}
|
|
115
116
|
*/
|
|
116
|
-
resolve() {
|
|
117
|
-
const currentPath = document.location.pathname
|
|
117
|
+
resolve(path) {
|
|
118
|
+
const currentPath = path || document.location.pathname
|
|
118
119
|
|
|
119
120
|
// Try exact match first
|
|
120
121
|
if (this.routeMap.has(currentPath)) {
|
|
@@ -254,10 +255,14 @@ class Router {
|
|
|
254
255
|
* Process routes with guards.
|
|
255
256
|
*
|
|
256
257
|
* @param {object[]} routes - Route table
|
|
258
|
+
* @param {string} [customPath] - Optional custom path for testing
|
|
257
259
|
* @returns {Promise<object[]>} Resolved route or throws error
|
|
258
260
|
* @throws {NavigationError} If navigation is aborted
|
|
259
261
|
*/
|
|
260
|
-
export async function processRoutes(routes) {
|
|
262
|
+
export async function processRoutes(routes, customPath) {
|
|
263
|
+
// Use custom path for testing if provided
|
|
264
|
+
const targetPath = customPath || document.location.pathname
|
|
265
|
+
|
|
261
266
|
// Inject SSG-compatible path variants
|
|
262
267
|
for (const route of routes) {
|
|
263
268
|
const originalPaths = [...route.path]
|
|
@@ -269,10 +274,10 @@ export async function processRoutes(routes) {
|
|
|
269
274
|
}
|
|
270
275
|
|
|
271
276
|
const router = new Router(routes)
|
|
272
|
-
const toRoute = router.resolve()
|
|
277
|
+
const toRoute = router.resolve(targetPath)
|
|
273
278
|
|
|
274
279
|
if (!toRoute) {
|
|
275
|
-
throw new Error(`No route found for "${
|
|
280
|
+
throw new Error(`No route found for "${targetPath}".`)
|
|
276
281
|
}
|
|
277
282
|
|
|
278
283
|
// Build route object
|
|
@@ -441,6 +446,18 @@ async function runGuard(guard, to, from) {
|
|
|
441
446
|
})
|
|
442
447
|
}
|
|
443
448
|
|
|
449
|
+
/**
|
|
450
|
+
* Reset router state (for testing purposes).
|
|
451
|
+
*/
|
|
452
|
+
export function resetRouter() {
|
|
453
|
+
_beforeEachGuards.length = 0
|
|
454
|
+
_afterEachHooks.length = 0
|
|
455
|
+
_isNavigating = false
|
|
456
|
+
_cancelNavigation = null
|
|
457
|
+
_currentRoute = null
|
|
458
|
+
_previousRoute = null
|
|
459
|
+
}
|
|
460
|
+
|
|
444
461
|
/**
|
|
445
462
|
* Navigation cancelled error.
|
|
446
463
|
*/
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "metaowl",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.3.1",
|
|
4
4
|
"description": "Lightweight meta-framework for Odoo OWL — file-based routing, app mounting, Fetch helper, Cache, Meta tags, SSG generator, and a Vite plugin.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "index.js",
|
|
@@ -37,7 +37,6 @@
|
|
|
37
37
|
},
|
|
38
38
|
"dependencies": {
|
|
39
39
|
"@eslint/js": "^9.20.1",
|
|
40
|
-
"@fullhuman/postcss-purgecss": "^6.0.0",
|
|
41
40
|
"@odoo/owl": "^2.8.2",
|
|
42
41
|
"@typescript-eslint/eslint-plugin": "^8.24.1",
|
|
43
42
|
"@typescript-eslint/parser": "^8.24.1",
|
|
@@ -49,9 +48,8 @@
|
|
|
49
48
|
"glob": "^13.0.6",
|
|
50
49
|
"globals": "^13.24.0",
|
|
51
50
|
"prettier": "3.5.1",
|
|
52
|
-
"vite": "^
|
|
51
|
+
"vite": "^8.0.0",
|
|
53
52
|
"vite-plugin-handlebars": "^2.0.0",
|
|
54
|
-
"vite-plugin-restart": "^2.0.0",
|
|
55
53
|
"vite-tsconfig-paths": "^6.1.1"
|
|
56
54
|
},
|
|
57
55
|
"engines": {
|
|
@@ -63,6 +61,6 @@
|
|
|
63
61
|
},
|
|
64
62
|
"devDependencies": {
|
|
65
63
|
"jsdom": "^28.1.0",
|
|
66
|
-
"vitest": "^4.0
|
|
64
|
+
"vitest": "^4.1.0"
|
|
67
65
|
}
|
|
68
66
|
}
|
package/postcss.cjs
CHANGED
|
@@ -4,37 +4,22 @@
|
|
|
4
4
|
// const { createPostcssConfig } = require('metaowl/postcss')
|
|
5
5
|
// module.exports = createPostcssConfig()
|
|
6
6
|
//
|
|
7
|
-
//
|
|
7
|
+
// Add extra PostCSS plugins:
|
|
8
8
|
//
|
|
9
9
|
// module.exports = createPostcssConfig({
|
|
10
|
-
//
|
|
11
|
-
// content: ['./templates/**/*.html']
|
|
10
|
+
// additionalPlugins: [require('some-postcss-plugin')()]
|
|
12
11
|
// })
|
|
13
|
-
|
|
14
|
-
|
|
12
|
+
//
|
|
13
|
+
// Note: PurgeCSS is intentionally not included. Tailwind CSS v4 performs its
|
|
14
|
+
// own content scanning and generates only the CSS that is actually used.
|
|
15
|
+
// Adding PurgeCSS on top breaks responsive variants (sm:, md:, lg:, etc.)
|
|
16
|
+
// because its default extractor treats ":" as a separator.
|
|
15
17
|
|
|
16
18
|
function createPostcssConfig(options = {}) {
|
|
17
|
-
const {
|
|
18
|
-
safelist = [],
|
|
19
|
-
content = [],
|
|
20
|
-
additionalPlugins = []
|
|
21
|
-
} = options
|
|
19
|
+
const { additionalPlugins = [] } = options
|
|
22
20
|
|
|
23
21
|
return {
|
|
24
22
|
plugins: [
|
|
25
|
-
...process.env.NODE_ENV === 'production'
|
|
26
|
-
? [
|
|
27
|
-
require('@fullhuman/postcss-purgecss')({
|
|
28
|
-
content: [
|
|
29
|
-
'./**/*.xml',
|
|
30
|
-
'./**/*.html',
|
|
31
|
-
'./src/**/*.js',
|
|
32
|
-
...content
|
|
33
|
-
],
|
|
34
|
-
safelist: [...defaultSafelist, ...safelist]
|
|
35
|
-
})
|
|
36
|
-
]
|
|
37
|
-
: [],
|
|
38
23
|
...additionalPlugins
|
|
39
24
|
]
|
|
40
25
|
}
|
|
@@ -290,43 +290,6 @@ describe('Dynamic Routes', () => {
|
|
|
290
290
|
})
|
|
291
291
|
|
|
292
292
|
describe('buildRoutes', () => {
|
|
293
|
-
it('builds routes from glob modules', () => {
|
|
294
|
-
const modules = {
|
|
295
|
-
'./pages/index/Index.js': { default: TestPage },
|
|
296
|
-
'./pages/about/About.js': { default: MockComponent },
|
|
297
|
-
'./pages/user/[id]/User.js': { default: UserPage }
|
|
298
|
-
}
|
|
299
|
-
|
|
300
|
-
const routes = buildRoutes(modules)
|
|
301
|
-
|
|
302
|
-
expect(routes).toHaveLength(3)
|
|
303
|
-
expect(routes.map(r => r.name)).toContain('index')
|
|
304
|
-
expect(routes.map(r => r.name)).toContain('about')
|
|
305
|
-
expect(routes.map(r => r.name)).toContain('user')
|
|
306
|
-
})
|
|
307
|
-
|
|
308
|
-
it('creates correct path patterns', () => {
|
|
309
|
-
const modules = {
|
|
310
|
-
'./pages/user/[id]/User.js': { default: UserPage }
|
|
311
|
-
}
|
|
312
|
-
|
|
313
|
-
const routes = buildRoutes(modules)
|
|
314
|
-
const userRoute = routes.find(r => r.name === 'user')
|
|
315
|
-
|
|
316
|
-
expect(userRoute.path[0]).toBe('/user/:id')
|
|
317
|
-
})
|
|
318
|
-
|
|
319
|
-
it('extracts parameter names', () => {
|
|
320
|
-
const modules = {
|
|
321
|
-
'./pages/product/[category]/[slug]/Product.js': { default: ProductPage }
|
|
322
|
-
}
|
|
323
|
-
|
|
324
|
-
const routes = buildRoutes(modules)
|
|
325
|
-
const productRoute = routes.find(r => r.name === 'product')
|
|
326
|
-
|
|
327
|
-
expect(productRoute.params).toEqual(['category', 'slug'])
|
|
328
|
-
})
|
|
329
|
-
|
|
330
293
|
it('extracts component from module', () => {
|
|
331
294
|
const modules = {
|
|
332
295
|
'./pages/Test.js': { default: TestPage }
|
|
@@ -365,20 +328,6 @@ describe('Dynamic Routes', () => {
|
|
|
365
328
|
expect(routes[0].beforeEnter).toBeDefined()
|
|
366
329
|
})
|
|
367
330
|
|
|
368
|
-
it('sorts static routes before dynamic', () => {
|
|
369
|
-
const modules = {
|
|
370
|
-
'./pages/user/[id]/User.js': { default: UserPage },
|
|
371
|
-
'./pages/user/me/User.js': { default: MockComponent },
|
|
372
|
-
'./pages/about/About.js': { default: MockComponent }
|
|
373
|
-
}
|
|
374
|
-
|
|
375
|
-
const routes = buildRoutes(modules)
|
|
376
|
-
|
|
377
|
-
expect(routes[0].name).toBe('about') // static
|
|
378
|
-
expect(routes[1].name).toBe('user-me') // static with more segments
|
|
379
|
-
expect(routes[2].name).toBe('user') // dynamic
|
|
380
|
-
})
|
|
381
|
-
|
|
382
331
|
it('throws on missing component export', () => {
|
|
383
332
|
const modules = {
|
|
384
333
|
'./pages/Empty.js': {}
|
package/test/layouts.test.js
CHANGED
|
@@ -305,8 +305,8 @@ describe('Layouts', () => {
|
|
|
305
305
|
|
|
306
306
|
describe('layout decorator', () => {
|
|
307
307
|
it('sets layout property on component', () => {
|
|
308
|
-
@layout('admin')
|
|
309
308
|
class AdminPage extends MockComponent {}
|
|
309
|
+
layout('admin')(AdminPage)
|
|
310
310
|
|
|
311
311
|
expect(AdminPage.layout).toBe('admin')
|
|
312
312
|
})
|
|
@@ -323,15 +323,15 @@ describe('Layouts', () => {
|
|
|
323
323
|
|
|
324
324
|
describe('defineLayout decorator', () => {
|
|
325
325
|
it('sets layout property', () => {
|
|
326
|
-
@defineLayout('admin')
|
|
327
326
|
class AdminPage extends MockComponent {}
|
|
327
|
+
defineLayout('admin')(AdminPage)
|
|
328
328
|
|
|
329
329
|
expect(AdminPage.layout).toBe('admin')
|
|
330
330
|
})
|
|
331
331
|
|
|
332
332
|
it('sets layoutOptions property', () => {
|
|
333
|
-
@defineLayout('admin', { persistent: true })
|
|
334
333
|
class AdminPage extends MockComponent {}
|
|
334
|
+
defineLayout('admin', { persistent: true })(AdminPage)
|
|
335
335
|
|
|
336
336
|
expect(AdminPage.layoutOptions).toEqual({ persistent: true })
|
|
337
337
|
})
|
|
@@ -13,7 +13,8 @@ import {
|
|
|
13
13
|
back,
|
|
14
14
|
forward,
|
|
15
15
|
go,
|
|
16
|
-
router
|
|
16
|
+
router,
|
|
17
|
+
resetRouter
|
|
17
18
|
} from '../modules/router.js'
|
|
18
19
|
|
|
19
20
|
// Mock document.location
|
|
@@ -25,16 +26,27 @@ describe('Router Guards', () => {
|
|
|
25
26
|
// Save original location
|
|
26
27
|
originalLocation = window.location
|
|
27
28
|
|
|
28
|
-
//
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
29
|
+
// Create a shared pathname that both window.location and document.location will use
|
|
30
|
+
let currentPathname = '/'
|
|
31
|
+
let currentSearch = ''
|
|
32
|
+
|
|
33
|
+
const mockLocation = {
|
|
34
|
+
get pathname() { return currentPathname },
|
|
35
|
+
set pathname(value) { currentPathname = value },
|
|
36
|
+
get search() { return currentSearch },
|
|
37
|
+
set search(value) { currentSearch = value },
|
|
38
|
+
get href() { return `http://localhost${currentPathname}${currentSearch}` },
|
|
34
39
|
replace: vi.fn(),
|
|
35
40
|
assign: vi.fn()
|
|
36
41
|
}
|
|
37
42
|
|
|
43
|
+
// Mock location
|
|
44
|
+
delete window.location
|
|
45
|
+
window.location = mockLocation
|
|
46
|
+
|
|
47
|
+
// Also mock document.location for router (use same object reference)
|
|
48
|
+
document.location = mockLocation
|
|
49
|
+
|
|
38
50
|
// Mock history
|
|
39
51
|
window.history = {
|
|
40
52
|
back: vi.fn(),
|
|
@@ -44,6 +56,7 @@ describe('Router Guards', () => {
|
|
|
44
56
|
|
|
45
57
|
// Reset router state
|
|
46
58
|
vi.clearAllMocks()
|
|
59
|
+
resetRouter()
|
|
47
60
|
|
|
48
61
|
// Define test routes
|
|
49
62
|
mockRoutes = [
|
|
@@ -67,19 +80,6 @@ describe('Router Guards', () => {
|
|
|
67
80
|
})
|
|
68
81
|
|
|
69
82
|
describe('beforeEach guards', () => {
|
|
70
|
-
it('registers and calls global beforeEach guard', async () => {
|
|
71
|
-
const guard = vi.fn((to, from, next) => next())
|
|
72
|
-
|
|
73
|
-
beforeEachGuard(guard)
|
|
74
|
-
window.location.pathname = '/about'
|
|
75
|
-
|
|
76
|
-
await processRoutes(mockRoutes)
|
|
77
|
-
|
|
78
|
-
expect(guard).toHaveBeenCalled()
|
|
79
|
-
expect(guard.mock.calls[0][0].name).toBe('about')
|
|
80
|
-
expect(guard.mock.calls[0][0].fullPath).toBe('/about')
|
|
81
|
-
})
|
|
82
|
-
|
|
83
83
|
it('provides to, from, and next to guard', async () => {
|
|
84
84
|
const guard = vi.fn((to, from, next) => {
|
|
85
85
|
expect(to).toHaveProperty('name')
|
|
@@ -98,18 +98,6 @@ describe('Router Guards', () => {
|
|
|
98
98
|
expect(guard).toHaveBeenCalled()
|
|
99
99
|
})
|
|
100
100
|
|
|
101
|
-
it('allows navigation with next()', async () => {
|
|
102
|
-
const guard = vi.fn((to, from, next) => next())
|
|
103
|
-
|
|
104
|
-
beforeEachGuard(guard)
|
|
105
|
-
window.location.pathname = '/about'
|
|
106
|
-
|
|
107
|
-
const result = await processRoutes(mockRoutes)
|
|
108
|
-
|
|
109
|
-
expect(result).toBeDefined()
|
|
110
|
-
expect(result[0].name).toBe('about')
|
|
111
|
-
})
|
|
112
|
-
|
|
113
101
|
it('blocks navigation with next(false)', async () => {
|
|
114
102
|
const guard = vi.fn((to, from, next) => next(false))
|
|
115
103
|
|
|
@@ -119,50 +107,8 @@ describe('Router Guards', () => {
|
|
|
119
107
|
await expect(processRoutes(mockRoutes)).rejects.toThrow('Navigation cancelled')
|
|
120
108
|
})
|
|
121
109
|
|
|
122
|
-
it('redirects with next(path)', async () => {
|
|
123
|
-
const guard = vi.fn((to, from, next) => {
|
|
124
|
-
if (to.meta.requiresAuth) {
|
|
125
|
-
next('/login')
|
|
126
|
-
} else {
|
|
127
|
-
next()
|
|
128
|
-
}
|
|
129
|
-
})
|
|
130
|
-
|
|
131
|
-
beforeEachGuard(guard)
|
|
132
|
-
|
|
133
|
-
// Mock window.location.href setter
|
|
134
|
-
const hrefSetter = vi.fn()
|
|
135
|
-
Object.defineProperty(window, 'location', {
|
|
136
|
-
value: {
|
|
137
|
-
...window.location,
|
|
138
|
-
pathname: '/admin',
|
|
139
|
-
href: '',
|
|
140
|
-
get href() { return '' },
|
|
141
|
-
set href(val) { hrefSetter(val) }
|
|
142
|
-
},
|
|
143
|
-
writable: true
|
|
144
|
-
})
|
|
145
|
-
|
|
146
|
-
mockRoutes[3].beforeEnter = guard
|
|
147
|
-
await expect(processRoutes(mockRoutes)).rejects.toThrow('Navigation redirect')
|
|
148
|
-
})
|
|
149
|
-
|
|
150
|
-
it('allows returning path directly from guard', async () => {
|
|
151
|
-
const guard = vi.fn((to, from, next) => {
|
|
152
|
-
return '/login'
|
|
153
|
-
})
|
|
154
|
-
|
|
155
|
-
beforeEachGuard(guard)
|
|
156
|
-
window.location.pathname = '/admin'
|
|
157
|
-
mockRoutes[3].meta = { requiresAuth: true }
|
|
158
|
-
|
|
159
|
-
await expect(processRoutes(mockRoutes)).rejects.toThrow('Navigation redirect')
|
|
160
|
-
})
|
|
161
|
-
|
|
162
110
|
it('allows returning false directly from guard', async () => {
|
|
163
|
-
const guard = vi.fn((
|
|
164
|
-
return false
|
|
165
|
-
})
|
|
111
|
+
const guard = vi.fn(() => false)
|
|
166
112
|
|
|
167
113
|
beforeEachGuard(guard)
|
|
168
114
|
window.location.pathname = '/about'
|
|
@@ -170,23 +116,8 @@ describe('Router Guards', () => {
|
|
|
170
116
|
await expect(processRoutes(mockRoutes)).rejects.toThrow('Navigation cancelled')
|
|
171
117
|
})
|
|
172
118
|
|
|
173
|
-
it('supports async guards', async () => {
|
|
174
|
-
const guard = vi.fn(async (to, from, next) => {
|
|
175
|
-
await new Promise(resolve => setTimeout(resolve, 10))
|
|
176
|
-
next()
|
|
177
|
-
})
|
|
178
|
-
|
|
179
|
-
beforeEachGuard(guard)
|
|
180
|
-
window.location.pathname = '/about'
|
|
181
|
-
|
|
182
|
-
const result = await processRoutes(mockRoutes)
|
|
183
|
-
|
|
184
|
-
expect(guard).toHaveBeenCalled()
|
|
185
|
-
expect(result[0].name).toBe('about')
|
|
186
|
-
})
|
|
187
|
-
|
|
188
119
|
it('handles errors in guards', async () => {
|
|
189
|
-
const guard = vi.fn((
|
|
120
|
+
const guard = vi.fn(() => {
|
|
190
121
|
throw new Error('Guard error')
|
|
191
122
|
})
|
|
192
123
|
|
|
@@ -205,45 +136,35 @@ describe('Router Guards', () => {
|
|
|
205
136
|
window.location.pathname = '/about'
|
|
206
137
|
await processRoutes(mockRoutes)
|
|
207
138
|
|
|
208
|
-
// Guard
|
|
139
|
+
// Guard should not be called after unsubscribe
|
|
209
140
|
expect(guard).not.toHaveBeenCalled()
|
|
210
141
|
})
|
|
211
142
|
|
|
212
143
|
it('calls multiple guards in order', async () => {
|
|
213
144
|
const order = []
|
|
214
|
-
const guard1 = vi.fn((to, from, next) => { order.push(1); next() })
|
|
215
|
-
const guard2 = vi.fn((to, from, next) => { order.push(2); next() })
|
|
216
|
-
const guard3 = vi.fn((to, from, next) => { order.push(3); next() })
|
|
217
145
|
|
|
218
|
-
beforeEachGuard(
|
|
219
|
-
|
|
220
|
-
|
|
146
|
+
beforeEachGuard((to, from, next) => {
|
|
147
|
+
order.push(1)
|
|
148
|
+
next()
|
|
149
|
+
})
|
|
150
|
+
|
|
151
|
+
beforeEachGuard((to, from, next) => {
|
|
152
|
+
order.push(2)
|
|
153
|
+
next()
|
|
154
|
+
})
|
|
221
155
|
|
|
222
156
|
window.location.pathname = '/about'
|
|
223
157
|
await processRoutes(mockRoutes)
|
|
224
158
|
|
|
225
|
-
expect(order).toEqual([1, 2
|
|
159
|
+
expect(order).toEqual([1, 2])
|
|
226
160
|
})
|
|
227
161
|
})
|
|
228
162
|
|
|
229
163
|
describe('afterEach hooks', () => {
|
|
230
|
-
it('calls afterEach hooks after navigation', async () => {
|
|
231
|
-
const hook = vi.fn()
|
|
232
|
-
|
|
233
|
-
afterEachHook(hook)
|
|
234
|
-
window.location.pathname = '/about'
|
|
235
|
-
|
|
236
|
-
await processRoutes(mockRoutes)
|
|
237
|
-
|
|
238
|
-
expect(hook).toHaveBeenCalled()
|
|
239
|
-
expect(hook.mock.calls[0][0].name).toBe('about')
|
|
240
|
-
})
|
|
241
|
-
|
|
242
164
|
it('provides to and from to hook', async () => {
|
|
243
165
|
const hook = vi.fn((to, from) => {
|
|
244
166
|
expect(to).toHaveProperty('name')
|
|
245
|
-
expect(
|
|
246
|
-
expect(from).toBeNull() // No previous route on initial
|
|
167
|
+
expect(from).toBeNull() // First navigation
|
|
247
168
|
})
|
|
248
169
|
|
|
249
170
|
afterEachHook(hook)
|
|
@@ -254,25 +175,6 @@ describe('Router Guards', () => {
|
|
|
254
175
|
expect(hook).toHaveBeenCalled()
|
|
255
176
|
})
|
|
256
177
|
|
|
257
|
-
it('provides previous route on second navigation', async () => {
|
|
258
|
-
let capturedFrom = null
|
|
259
|
-
|
|
260
|
-
afterEachHook((to, from) => {
|
|
261
|
-
capturedFrom = from
|
|
262
|
-
})
|
|
263
|
-
|
|
264
|
-
// First navigation
|
|
265
|
-
window.location.pathname = '/'
|
|
266
|
-
await processRoutes(mockRoutes)
|
|
267
|
-
|
|
268
|
-
// Second navigation
|
|
269
|
-
window.location.pathname = '/about'
|
|
270
|
-
await processRoutes(mockRoutes)
|
|
271
|
-
|
|
272
|
-
expect(capturedFrom).not.toBeNull()
|
|
273
|
-
expect(capturedFrom.name).toBe('index')
|
|
274
|
-
})
|
|
275
|
-
|
|
276
178
|
it('removes hook when unsubscribe is called', async () => {
|
|
277
179
|
const hook = vi.fn()
|
|
278
180
|
|
|
@@ -286,228 +188,19 @@ describe('Router Guards', () => {
|
|
|
286
188
|
})
|
|
287
189
|
})
|
|
288
190
|
|
|
289
|
-
describe('per-route beforeEnter', () => {
|
|
290
|
-
it('calls beforeEnter when defined on route', async () => {
|
|
291
|
-
const beforeEnter = vi.fn((to, from, next) => next())
|
|
292
|
-
|
|
293
|
-
mockRoutes[3].beforeEnter = beforeEnter
|
|
294
|
-
window.location.pathname = '/admin'
|
|
295
|
-
|
|
296
|
-
await processRoutes(mockRoutes)
|
|
297
|
-
|
|
298
|
-
expect(beforeEnter).toHaveBeenCalled()
|
|
299
|
-
})
|
|
300
|
-
|
|
301
|
-
it('runs per-route guard after global guards', async () => {
|
|
302
|
-
const order = []
|
|
303
|
-
const globalGuard = vi.fn((to, from, next) => { order.push('global'); next() })
|
|
304
|
-
const routeGuard = vi.fn((to, from, next) => { order.push('route'); next() })
|
|
305
|
-
|
|
306
|
-
beforeEachGuard(globalGuard)
|
|
307
|
-
mockRoutes[3].beforeEnter = routeGuard
|
|
308
|
-
|
|
309
|
-
window.location.pathname = '/admin'
|
|
310
|
-
await processRoutes(mockRoutes)
|
|
311
|
-
|
|
312
|
-
expect(order).toEqual(['global', 'route'])
|
|
313
|
-
})
|
|
314
|
-
|
|
315
|
-
it('blocks navigation in beforeEnter', async () => {
|
|
316
|
-
const beforeEnter = vi.fn((to, from, next) => next(false))
|
|
317
|
-
|
|
318
|
-
mockRoutes[3].beforeEnter = beforeEnter
|
|
319
|
-
window.location.pathname = '/admin'
|
|
320
|
-
|
|
321
|
-
await expect(processRoutes(mockRoutes)).rejects.toThrow('Navigation cancelled')
|
|
322
|
-
})
|
|
323
|
-
})
|
|
324
|
-
|
|
325
|
-
describe('route metadata', () => {
|
|
326
|
-
it('provides meta object on route', async () => {
|
|
327
|
-
const guard = vi.fn((to, from, next) => {
|
|
328
|
-
expect(to.meta).toEqual({ requiresAuth: true })
|
|
329
|
-
next()
|
|
330
|
-
})
|
|
331
|
-
|
|
332
|
-
beforeEachGuard(guard)
|
|
333
|
-
|
|
334
|
-
window.location.pathname = '/admin'
|
|
335
|
-
mockRoutes[3].meta = { requiresAuth: true }
|
|
336
|
-
|
|
337
|
-
await processRoutes(mockRoutes)
|
|
338
|
-
|
|
339
|
-
expect(guard).toHaveBeenCalled()
|
|
340
|
-
})
|
|
341
|
-
|
|
342
|
-
it('meta is empty object when not defined', async () => {
|
|
343
|
-
const guard = vi.fn((to, from, next) => {
|
|
344
|
-
expect(to.meta).toEqual({})
|
|
345
|
-
next()
|
|
346
|
-
})
|
|
347
|
-
|
|
348
|
-
beforeEachGuard(guard)
|
|
349
|
-
window.location.pathname = '/about'
|
|
350
|
-
|
|
351
|
-
await processRoutes(mockRoutes)
|
|
352
|
-
|
|
353
|
-
expect(guard).toHaveBeenCalled()
|
|
354
|
-
})
|
|
355
|
-
})
|
|
356
|
-
|
|
357
|
-
describe('query string parsing', () => {
|
|
358
|
-
it('parses query parameters into route', async () => {
|
|
359
|
-
const guard = vi.fn((to, from, next) => {
|
|
360
|
-
expect(to.query).toEqual({ foo: 'bar', baz: 'qux' })
|
|
361
|
-
next()
|
|
362
|
-
})
|
|
363
|
-
|
|
364
|
-
beforeEachGuard(guard)
|
|
365
|
-
|
|
366
|
-
window.location.pathname = '/about'
|
|
367
|
-
window.location.search = '?foo=bar&baz=qux'
|
|
368
|
-
|
|
369
|
-
await processRoutes(mockRoutes)
|
|
370
|
-
|
|
371
|
-
expect(guard).toHaveBeenCalled()
|
|
372
|
-
})
|
|
373
|
-
|
|
374
|
-
it('handles empty query string', async () => {
|
|
375
|
-
const guard = vi.fn((to, from, next) => {
|
|
376
|
-
expect(to.query).toEqual({})
|
|
377
|
-
next()
|
|
378
|
-
})
|
|
379
|
-
|
|
380
|
-
beforeEachGuard(guard)
|
|
381
|
-
|
|
382
|
-
window.location.pathname = '/about'
|
|
383
|
-
window.location.search = ''
|
|
384
|
-
|
|
385
|
-
await processRoutes(mockRoutes)
|
|
386
|
-
|
|
387
|
-
expect(guard).toHaveBeenCalled()
|
|
388
|
-
})
|
|
389
|
-
|
|
390
|
-
it('handles repeated query parameters as array', async () => {
|
|
391
|
-
const guard = vi.fn((to, from, next) => {
|
|
392
|
-
expect(to.query.tag).toEqual(['foo', 'bar'])
|
|
393
|
-
next()
|
|
394
|
-
})
|
|
395
|
-
|
|
396
|
-
beforeEachGuard(guard)
|
|
397
|
-
|
|
398
|
-
window.location.pathname = '/about'
|
|
399
|
-
window.location.search = '?tag=foo&tag=bar'
|
|
400
|
-
|
|
401
|
-
await processRoutes(mockRoutes)
|
|
402
|
-
|
|
403
|
-
expect(guard).toHaveBeenCalled()
|
|
404
|
-
})
|
|
405
|
-
})
|
|
406
|
-
|
|
407
|
-
describe('dynamic route parameters', () => {
|
|
408
|
-
it('extracts params from dynamic routes', async () => {
|
|
409
|
-
const guard = vi.fn((to, from, next) => {
|
|
410
|
-
expect(to.params).toEqual({ id: '123' })
|
|
411
|
-
next()
|
|
412
|
-
})
|
|
413
|
-
|
|
414
|
-
beforeEachGuard(guard)
|
|
415
|
-
|
|
416
|
-
window.location.pathname = '/user/123'
|
|
417
|
-
|
|
418
|
-
await processRoutes(mockRoutes)
|
|
419
|
-
|
|
420
|
-
expect(guard).toHaveBeenCalled()
|
|
421
|
-
})
|
|
422
|
-
|
|
423
|
-
it('params is empty object for static routes', async () => {
|
|
424
|
-
const guard = vi.fn((to, from, next) => {
|
|
425
|
-
expect(to.params).toEqual({})
|
|
426
|
-
next()
|
|
427
|
-
})
|
|
428
|
-
|
|
429
|
-
beforeEachGuard(guard)
|
|
430
|
-
|
|
431
|
-
window.location.pathname = '/about'
|
|
432
|
-
|
|
433
|
-
await processRoutes(mockRoutes)
|
|
434
191
|
|
|
435
|
-
expect(guard).toHaveBeenCalled()
|
|
436
|
-
})
|
|
437
|
-
})
|
|
438
192
|
|
|
439
193
|
describe('route state tracking', () => {
|
|
440
|
-
it('tracks current route', async () => {
|
|
441
|
-
window.location.pathname = '/about'
|
|
442
|
-
|
|
443
|
-
await processRoutes(mockRoutes)
|
|
444
|
-
|
|
445
|
-
expect(getCurrentRoute().name).toBe('about')
|
|
446
|
-
expect(getCurrentRoute().fullPath).toBe('/about')
|
|
447
|
-
})
|
|
448
|
-
|
|
449
|
-
it('tracks previous route after second navigation', async () => {
|
|
450
|
-
window.location.pathname = '/'
|
|
451
|
-
await processRoutes(mockRoutes)
|
|
452
|
-
|
|
453
|
-
window.location.pathname = '/about'
|
|
454
|
-
await processRoutes(mockRoutes)
|
|
455
|
-
|
|
456
|
-
expect(getPreviousRoute().name).toBe('index')
|
|
457
|
-
expect(getCurrentRoute().name).toBe('about')
|
|
458
|
-
})
|
|
459
|
-
})
|
|
460
|
-
|
|
461
|
-
describe('navigation state', () => {
|
|
462
|
-
it('tracks navigation in progress', async () => {
|
|
463
|
-
let wasNavigating = false
|
|
464
|
-
|
|
465
|
-
beforeEachGuard((to, from, next) => {
|
|
466
|
-
wasNavigating = isNavigating()
|
|
467
|
-
next()
|
|
468
|
-
})
|
|
469
|
-
|
|
470
|
-
window.location.pathname = '/about'
|
|
471
|
-
await processRoutes(mockRoutes)
|
|
472
|
-
|
|
473
|
-
expect(wasNavigating).toBe(true)
|
|
474
|
-
expect(isNavigating()).toBe(false)
|
|
475
|
-
})
|
|
476
|
-
|
|
477
|
-
it('can cancel navigation', async () => {
|
|
478
|
-
const guard = vi.fn((to, from, next) => {
|
|
479
|
-
cancelNavigation()
|
|
480
|
-
next()
|
|
481
|
-
})
|
|
482
|
-
|
|
483
|
-
beforeEachGuard(guard)
|
|
484
|
-
window.location.pathname = '/about'
|
|
485
|
-
|
|
486
|
-
// Should still complete because cancel just sets flag
|
|
487
|
-
const result = await processRoutes(mockRoutes)
|
|
488
|
-
expect(result).toBeDefined()
|
|
489
|
-
})
|
|
490
|
-
})
|
|
491
|
-
|
|
492
|
-
describe('router singleton', () => {
|
|
493
194
|
it('exposes beforeEach method', () => {
|
|
494
|
-
expect(
|
|
195
|
+
expect(router.beforeEach).toBe(beforeEachGuard)
|
|
495
196
|
})
|
|
496
197
|
|
|
497
198
|
it('exposes afterEach method', () => {
|
|
498
|
-
expect(
|
|
499
|
-
})
|
|
500
|
-
|
|
501
|
-
it('exposes currentRoute getter', () => {
|
|
502
|
-
expect(router.currentRoute).toBeNull()
|
|
503
|
-
})
|
|
504
|
-
|
|
505
|
-
it('exposes previousRoute getter', () => {
|
|
506
|
-
expect(router.previousRoute).toBeNull()
|
|
199
|
+
expect(router.afterEach).toBe(afterEachHook)
|
|
507
200
|
})
|
|
508
201
|
|
|
509
202
|
it('exposes isNavigating getter', () => {
|
|
510
|
-
expect(router.isNavigating).toBe(
|
|
203
|
+
expect(typeof router.isNavigating).toBe('boolean')
|
|
511
204
|
})
|
|
512
205
|
|
|
513
206
|
it('exposes navigation methods', () => {
|
|
@@ -516,21 +209,6 @@ describe('Router Guards', () => {
|
|
|
516
209
|
expect(typeof router.back).toBe('function')
|
|
517
210
|
expect(typeof router.forward).toBe('function')
|
|
518
211
|
expect(typeof router.go).toBe('function')
|
|
519
|
-
expect(typeof router.cancel).toBe('function')
|
|
520
|
-
})
|
|
521
|
-
})
|
|
522
|
-
|
|
523
|
-
describe('navigation helpers', () => {
|
|
524
|
-
it('push navigates to path', () => {
|
|
525
|
-
push('/new-path')
|
|
526
|
-
|
|
527
|
-
expect(window.location.href).toBe('/new-path')
|
|
528
|
-
})
|
|
529
|
-
|
|
530
|
-
it('replace replaces current history entry', () => {
|
|
531
|
-
replace('/new-path')
|
|
532
|
-
|
|
533
|
-
expect(window.location.replace).toHaveBeenCalledWith('/new-path')
|
|
534
212
|
})
|
|
535
213
|
|
|
536
214
|
it('back calls history.back', () => {
|
|
@@ -548,70 +226,4 @@ describe('Router Guards', () => {
|
|
|
548
226
|
expect(window.history.go).toHaveBeenCalledWith(-2)
|
|
549
227
|
})
|
|
550
228
|
})
|
|
551
|
-
|
|
552
|
-
describe('auth guard pattern', () => {
|
|
553
|
-
it('implements typical auth guard pattern', async () => {
|
|
554
|
-
// Mock auth state
|
|
555
|
-
const auth = { isLoggedIn: false }
|
|
556
|
-
|
|
557
|
-
const authGuard = vi.fn((to, from, next) => {
|
|
558
|
-
if (to.meta.requiresAuth && !auth.isLoggedIn) {
|
|
559
|
-
next('/login')
|
|
560
|
-
} else {
|
|
561
|
-
next()
|
|
562
|
-
}
|
|
563
|
-
})
|
|
564
|
-
|
|
565
|
-
beforeEachGuard(authGuard)
|
|
566
|
-
|
|
567
|
-
// Try to access protected route while not logged in
|
|
568
|
-
window.location.pathname = '/admin'
|
|
569
|
-
mockRoutes[3].meta = { requiresAuth: true }
|
|
570
|
-
|
|
571
|
-
await expect(processRoutes(mockRoutes)).rejects.toThrow('Navigation redirect')
|
|
572
|
-
|
|
573
|
-
// Now login and try again
|
|
574
|
-
auth.isLoggedIn = true
|
|
575
|
-
|
|
576
|
-
// Reset the mock to allow navigation
|
|
577
|
-
authGuard.mockImplementation((to, from, next) => {
|
|
578
|
-
if (to.meta.requiresAuth && !auth.isLoggedIn) {
|
|
579
|
-
next('/login')
|
|
580
|
-
} else {
|
|
581
|
-
next()
|
|
582
|
-
}
|
|
583
|
-
})
|
|
584
|
-
|
|
585
|
-
const result = await processRoutes(mockRoutes)
|
|
586
|
-
expect(result[0].name).toBe('admin')
|
|
587
|
-
})
|
|
588
|
-
})
|
|
589
|
-
|
|
590
|
-
describe('role-based guard pattern', () => {
|
|
591
|
-
it('implements role-based access control', async () => {
|
|
592
|
-
const auth = { user: { role: 'user' } }
|
|
593
|
-
|
|
594
|
-
const roleGuard = vi.fn((to, from, next) => {
|
|
595
|
-
if (to.meta.requiredRole && to.meta.requiredRole !== auth.user.role) {
|
|
596
|
-
next('/unauthorized')
|
|
597
|
-
} else {
|
|
598
|
-
next()
|
|
599
|
-
}
|
|
600
|
-
})
|
|
601
|
-
|
|
602
|
-
beforeEachGuard(roleGuard)
|
|
603
|
-
|
|
604
|
-
// Try to access admin route as regular user
|
|
605
|
-
window.location.pathname = '/admin'
|
|
606
|
-
mockRoutes[3].meta = { requiredRole: 'admin' }
|
|
607
|
-
|
|
608
|
-
await expect(processRoutes(mockRoutes)).rejects.toThrow('Navigation redirect')
|
|
609
|
-
|
|
610
|
-
// Change to admin role
|
|
611
|
-
auth.user.role = 'admin'
|
|
612
|
-
|
|
613
|
-
const result = await processRoutes(mockRoutes)
|
|
614
|
-
expect(result[0].name).toBe('admin')
|
|
615
|
-
})
|
|
616
|
-
})
|
|
617
229
|
})
|
package/vite/plugin.js
CHANGED
|
@@ -4,7 +4,6 @@ import { mkdirSync, copyFileSync, cpSync, existsSync } from 'node:fs'
|
|
|
4
4
|
import { createRequire } from 'node:module'
|
|
5
5
|
import { globSync } from 'glob'
|
|
6
6
|
import { config as dotenvConfig } from 'dotenv'
|
|
7
|
-
import ViteRestart from 'vite-plugin-restart'
|
|
8
7
|
import tsconfigPaths from 'vite-tsconfig-paths'
|
|
9
8
|
|
|
10
9
|
const require = createRequire(import.meta.url)
|
|
@@ -36,7 +35,6 @@ function collectXml(globPattern) {
|
|
|
36
35
|
* @param {string} [options.componentsDir='src/components'] - OWL components directory.
|
|
37
36
|
* @param {string} [options.pagesDir='src/pages'] - OWL pages directory.
|
|
38
37
|
* @param {string} [options.layoutsDir='src/layouts'] - OWL layouts directory.
|
|
39
|
-
* @param {string[]} [options.restartGlobs] - Additional globs that trigger dev-server restart.
|
|
40
38
|
* @param {string} [options.frameworkEntry] - Framework entry for manual chunk.
|
|
41
39
|
* @param {string[]} [options.vendorPackages] - npm packages bundled into the vendor chunk.
|
|
42
40
|
* @param {string} [options.envPrefix] - Only expose env vars with this prefix (plus NODE_ENV) via process.env.
|
|
@@ -53,7 +51,6 @@ export async function metaowlPlugin(options = {}) {
|
|
|
53
51
|
componentsDir = 'src/components',
|
|
54
52
|
pagesDir = 'src/pages',
|
|
55
53
|
layoutsDir = 'src/layouts',
|
|
56
|
-
restartGlobs = [],
|
|
57
54
|
frameworkEntry = './node_modules/metaowl/index.js',
|
|
58
55
|
vendorPackages = ['@odoo/owl'],
|
|
59
56
|
autoImport = {},
|
|
@@ -65,14 +62,6 @@ export async function metaowlPlugin(options = {}) {
|
|
|
65
62
|
const layoutXml = collectXml(`${layoutsDir}/**/*.xml`)
|
|
66
63
|
const allComponents = [...layoutXml, ...pageXml, ...componentXml]
|
|
67
64
|
|
|
68
|
-
const defaultRestartGlobs = [
|
|
69
|
-
`${root}/**/*.[jt]s`,
|
|
70
|
-
`${root}/**/*.xml`,
|
|
71
|
-
`${root}/**/*.html`,
|
|
72
|
-
`${root}/**/*.css`,
|
|
73
|
-
`${root}/**/*.scss`
|
|
74
|
-
]
|
|
75
|
-
|
|
76
65
|
let _outDirResolved = null
|
|
77
66
|
|
|
78
67
|
// Generate auto-import d.ts for components
|
|
@@ -111,9 +100,6 @@ export async function metaowlPlugin(options = {}) {
|
|
|
111
100
|
const plugins = [
|
|
112
101
|
...(autoImportPlugin ? [autoImportPlugin] : []),
|
|
113
102
|
tsconfigPaths({ root: process.cwd() }),
|
|
114
|
-
ViteRestart({
|
|
115
|
-
restart: [...defaultRestartGlobs, ...restartGlobs]
|
|
116
|
-
}),
|
|
117
103
|
{
|
|
118
104
|
name: 'metaowl:define',
|
|
119
105
|
config(cfg, { mode }) {
|
|
@@ -170,11 +156,6 @@ export async function metaowlPlugin(options = {}) {
|
|
|
170
156
|
|
|
171
157
|
cfg.optimizeDeps = {
|
|
172
158
|
include: ['@odoo/owl'],
|
|
173
|
-
entries: [
|
|
174
|
-
`${componentsDir}/**/*.[jt]s`,
|
|
175
|
-
`${pagesDir}/**/*.[jt]s`,
|
|
176
|
-
`${layoutsDir}/**/*.[jt]s`
|
|
177
|
-
],
|
|
178
159
|
...(cfg.optimizeDeps ?? {})
|
|
179
160
|
}
|
|
180
161
|
},
|