@bakery-framework/plugin-vue 2.2.1 → 2.3.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/package.json +2 -2
- package/src/client.ts +43 -9
- package/src/handler.ts +7 -1
- package/src/shell.ts +21 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@bakery-framework/plugin-vue",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.3.0",
|
|
4
4
|
"description": "Bakery vue plugin.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"bakery",
|
|
@@ -43,7 +43,7 @@
|
|
|
43
43
|
"vue": "^3.5.38"
|
|
44
44
|
},
|
|
45
45
|
"dependencies": {
|
|
46
|
-
"@bakery-framework/core": "^2.
|
|
46
|
+
"@bakery-framework/core": "^2.3.0"
|
|
47
47
|
},
|
|
48
48
|
"engines": {
|
|
49
49
|
"bun": ">=1.4.0"
|
package/src/client.ts
CHANGED
|
@@ -45,6 +45,12 @@ type StampedRoute = {
|
|
|
45
45
|
export type LayoutNavigation = {
|
|
46
46
|
/** Path segments under the base: `[]` on the bare directory. */
|
|
47
47
|
readonly segments: Ref<string[]>
|
|
48
|
+
/**
|
|
49
|
+
* The query string, as `location.search` spells it: `''`, or `?` and the
|
|
50
|
+
* pairs. Updated with every navigation, a query-only one included, which
|
|
51
|
+
* changes no segment.
|
|
52
|
+
*/
|
|
53
|
+
readonly search: Ref<string>
|
|
48
54
|
/** The URL prefix this page owns. */
|
|
49
55
|
readonly base: string
|
|
50
56
|
/** Navigate within the subtree; segments or a path, `/`-prefixed or not. */
|
|
@@ -63,18 +69,39 @@ export type LayoutListener = (
|
|
|
63
69
|
cause: 'click' | 'navigate' | 'history',
|
|
64
70
|
) => boolean | undefined | void
|
|
65
71
|
|
|
72
|
+
/**
|
|
73
|
+
* `path` without its query and hash: the part a route is matched on. A link
|
|
74
|
+
* to `/campus/ma/students?standing=pending` used to keep its query in the
|
|
75
|
+
* last segment and render a page named `students?standing=pending`.
|
|
76
|
+
*/
|
|
77
|
+
function pathOnly(path: string): string {
|
|
78
|
+
const cut = path.search(/[?#]/)
|
|
79
|
+
return cut < 0 ? path : path.slice(0, cut)
|
|
80
|
+
}
|
|
81
|
+
|
|
66
82
|
/** Is `path` the base itself or inside it? Prefix-safe: `/admin` ≠ `/admini`. */
|
|
67
83
|
export function isUnderBase(base: string, path: string): boolean {
|
|
68
|
-
|
|
69
|
-
|
|
84
|
+
const pathname = pathOnly(path)
|
|
85
|
+
if (base === '') return pathname.startsWith('/')
|
|
86
|
+
return pathname === base || pathname.startsWith(`${base}/`)
|
|
70
87
|
}
|
|
71
88
|
|
|
72
89
|
/** The segments of `path` below `base`: `[]` for the base itself. */
|
|
73
90
|
export function segmentsUnder(base: string, path: string): string[] {
|
|
74
|
-
const
|
|
91
|
+
const pathname = pathOnly(path)
|
|
92
|
+
const rest = base === '' ? pathname : pathname.slice(base.length)
|
|
75
93
|
return rest.split('/').filter(Boolean)
|
|
76
94
|
}
|
|
77
95
|
|
|
96
|
+
/** The query of `path`, `''` when it has none, as `location.search` has it. */
|
|
97
|
+
function searchOf(path: string): string {
|
|
98
|
+
const hash = path.indexOf('#')
|
|
99
|
+
const head = hash < 0 ? path : path.slice(0, hash)
|
|
100
|
+
const query = head.indexOf('?')
|
|
101
|
+
// A bare `?` is no query: `location.search` reads `''` for it.
|
|
102
|
+
return query < 0 || query === head.length - 1 ? '' : head.slice(query)
|
|
103
|
+
}
|
|
104
|
+
|
|
78
105
|
function pathFor(base: string, to: string | string[]): string {
|
|
79
106
|
if (Array.isArray(to)) {
|
|
80
107
|
const joined = to.filter(Boolean).join('/')
|
|
@@ -110,11 +137,11 @@ export function defineLayout(): LayoutNavigation {
|
|
|
110
137
|
return next.length > 0 && claimed.has(next[0])
|
|
111
138
|
}
|
|
112
139
|
|
|
113
|
-
const
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
const
|
|
140
|
+
const here = typeof location !== 'undefined'
|
|
141
|
+
const segments = ref<string[]>(
|
|
142
|
+
here ? segmentsUnder(base, location.pathname) : [],
|
|
143
|
+
)
|
|
144
|
+
const search = ref(here ? location.search : '')
|
|
118
145
|
|
|
119
146
|
function fire(next: string[], cause: Parameters<LayoutListener>[2]): boolean {
|
|
120
147
|
const prev = segments.value
|
|
@@ -141,8 +168,11 @@ export function defineLayout(): LayoutNavigation {
|
|
|
141
168
|
return
|
|
142
169
|
}
|
|
143
170
|
if (!fire(next, cause)) return
|
|
171
|
+
// The whole path goes into history, query and hash included: a reload
|
|
172
|
+
// has to land on the same view.
|
|
144
173
|
if (typeof history !== 'undefined') history.pushState(null, '', path)
|
|
145
174
|
segments.value = next
|
|
175
|
+
search.value = searchOf(path)
|
|
146
176
|
}
|
|
147
177
|
|
|
148
178
|
if (typeof document !== 'undefined') {
|
|
@@ -167,12 +197,14 @@ export function defineLayout(): LayoutNavigation {
|
|
|
167
197
|
url.pathname === location.pathname &&
|
|
168
198
|
url.search === location.search
|
|
169
199
|
) {
|
|
200
|
+
// Only the hash differs: the browser's own same-document scroll.
|
|
201
|
+
if (url.hash && url.hash !== location.hash) return
|
|
170
202
|
event.preventDefault()
|
|
171
203
|
return
|
|
172
204
|
}
|
|
173
205
|
|
|
174
206
|
event.preventDefault()
|
|
175
|
-
go(url.pathname + url.search, 'click')
|
|
207
|
+
go(url.pathname + url.search + url.hash, 'click')
|
|
176
208
|
})
|
|
177
209
|
|
|
178
210
|
window.addEventListener('popstate', () => {
|
|
@@ -190,11 +222,13 @@ export function defineLayout(): LayoutNavigation {
|
|
|
190
222
|
}
|
|
191
223
|
fire(next, 'history') // observable, not cancellable. See `on`
|
|
192
224
|
segments.value = next
|
|
225
|
+
search.value = location.search
|
|
193
226
|
})
|
|
194
227
|
}
|
|
195
228
|
|
|
196
229
|
return {
|
|
197
230
|
segments,
|
|
231
|
+
search,
|
|
198
232
|
base,
|
|
199
233
|
navigate: to => go(to, 'navigate'),
|
|
200
234
|
on(listener) {
|
package/src/handler.ts
CHANGED
|
@@ -32,7 +32,7 @@ import {
|
|
|
32
32
|
parseVue,
|
|
33
33
|
vueBuildVariant,
|
|
34
34
|
} from './compile'
|
|
35
|
-
import { VUE_HTML_SHELL } from './shell'
|
|
35
|
+
import { declaresViewport, VIEWPORT_META, VUE_HTML_SHELL } from './shell'
|
|
36
36
|
import type { ParsedCacheEntry, VueMeta } from './types'
|
|
37
37
|
import {
|
|
38
38
|
cacheDir,
|
|
@@ -709,6 +709,12 @@ export class VueHandler extends DynamicHandler {
|
|
|
709
709
|
() => serverDecl + moduleDecl + routeDecl,
|
|
710
710
|
)
|
|
711
711
|
|
|
712
|
+
// An app's own viewport, through `head`, would lose to the shell's: see
|
|
713
|
+
// `declaresViewport`.
|
|
714
|
+
if (declaresViewport(Bakery.config.head)) {
|
|
715
|
+
hydrated = hydrated.replace(VIEWPORT_META, '')
|
|
716
|
+
}
|
|
717
|
+
|
|
712
718
|
// Static markup, injected verbatim. See parseSkeleton for why it is
|
|
713
719
|
// never rendered. mount() replaces the container children, so it
|
|
714
720
|
// disappears the moment the real component is up.
|
package/src/shell.ts
CHANGED
|
@@ -1,9 +1,30 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The page's default viewport. Without one a phone lays the page out 980 px
|
|
3
|
+
* wide and no `max-width` media query applies (measured in Chrome under 390 px
|
|
4
|
+
* emulation: `innerWidth` 980, and 390 with this tag).
|
|
5
|
+
*/
|
|
6
|
+
export const VIEWPORT_META =
|
|
7
|
+
'<meta name="viewport" content="width=device-width, initial-scale=1">'
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Whether `head` markup declares a viewport of its own.
|
|
11
|
+
*
|
|
12
|
+
* The host's `head` is spliced in right after `<head>`, ahead of the shell's
|
|
13
|
+
* own tags, and of two viewport metas Chrome honors the later one (measured:
|
|
14
|
+
* `width=600` after `device-width` reads 600, before it 390). So the default
|
|
15
|
+
* above would override an app's own, and the handler drops it instead.
|
|
16
|
+
*/
|
|
17
|
+
export function declaresViewport(head: string | undefined): boolean {
|
|
18
|
+
return !!head && /<meta\b[^>]*\sname\s*=\s*["']?viewport\b/i.test(head)
|
|
19
|
+
}
|
|
20
|
+
|
|
1
21
|
export const VUE_HTML_SHELL = `
|
|
2
22
|
<!DOCTYPE html>
|
|
3
23
|
<html lang="en">
|
|
4
24
|
|
|
5
25
|
<head>
|
|
6
26
|
<meta charset="UTF-8">
|
|
27
|
+
${VIEWPORT_META}
|
|
7
28
|
<title>Vue App</title>
|
|
8
29
|
/*__SERVER_VARIABLES__*/
|
|
9
30
|
</head>
|