duto 0.261001.0 → 0.261011.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 +1 -1
- package/src/commands/templates/pages/components/html.tsx +55 -0
- package/src/commands/templates/pages/components/theme-select.tsx +60 -0
- package/src/commands/templates/pages/i18n/en.ts +6 -0
- package/src/commands/templates/pages/i18n/pt-br.ts +6 -0
- package/src/commands/templates/pages/pages/404.tsx +1 -1
- package/src/commands/templates/pages/pages/blog/[slug].tsx +88 -0
- package/src/commands/templates/pages/pages/blog/index.tsx +74 -0
- package/src/commands/templates/pages/pages/index.tsx +17 -7
- package/src/commands/templates/pages/pages/layout.tsx +5 -3
- package/src/commands/templates/pages/public/favicon.ico +0 -0
- package/src/commands/templates/pages/public/favicon.svg +1 -0
- package/src/commands/templates/pages/style.css +110 -7
- package/src/commands/templates/pages/tsconfig.json +2 -0
- package/src/pages/cli/utils.ts +5 -2
- package/src/pages/client/app.tsx +177 -28
- package/src/pages/client/link.ts +15 -41
- package/src/pages/client/storage.ts +1 -1
- package/src/pages/client/theme.ts +49 -0
- package/src/pages/compiler/constants.ts +2 -0
- package/src/pages/compiler/index.ts +41 -20
- package/src/pages/compiler/meta.ts +7 -31
- package/src/pages/compiler/types.ts +15 -24
- package/src/pages/compiler/vite-plugin.ts +25 -34
- package/src/pages/components/link.tsx +11 -19
- package/src/pages/i18n/client.ts +3 -4
- package/src/pages/index.ts +4 -21
- package/src/server/app.ts +2 -1
- package/src/pages/client/router.ts +0 -63
package/src/pages/client/app.tsx
CHANGED
|
@@ -1,14 +1,30 @@
|
|
|
1
1
|
// @ts-ignore
|
|
2
2
|
import ':duto/hmr'
|
|
3
3
|
|
|
4
|
-
import { render,
|
|
4
|
+
import { render, h, type ComponentChild } from 'preact'
|
|
5
|
+
import { flushSync, useLayoutEffect, useState } from 'preact/compat'
|
|
5
6
|
import { Switch, Route, Router } from 'wouter-preact'
|
|
6
7
|
import { useLocation, useParams, useSearchParams, } from 'wouter-preact'
|
|
8
|
+
import type { AroundNavHandler } from 'wouter-preact'
|
|
9
|
+
import type { Params, StaticProps, View } from '#/compiler/types'
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
import { setThemeAttr, getTheme } from './theme'
|
|
13
|
+
|
|
14
|
+
setThemeAttr(getTheme())
|
|
7
15
|
|
|
8
16
|
// @ts-ignore
|
|
9
17
|
import { fallbackLocale, locales, routeMap } from ':duto/i18n'
|
|
10
18
|
// import i18n from '../i18n/client'
|
|
11
|
-
import i18n, {
|
|
19
|
+
import i18n, {
|
|
20
|
+
detect, isPrefixed,
|
|
21
|
+
getLocale, setLocale,
|
|
22
|
+
cache, cached,
|
|
23
|
+
r, redirect,
|
|
24
|
+
route,
|
|
25
|
+
d, t, tl,
|
|
26
|
+
updatePickers,
|
|
27
|
+
} from '../i18n/client'
|
|
12
28
|
|
|
13
29
|
await i18n(fallbackLocale, locales, {}, {}, routeMap)
|
|
14
30
|
|
|
@@ -24,31 +40,172 @@ for (let [tag, el, opts] of [
|
|
|
24
40
|
}
|
|
25
41
|
|
|
26
42
|
import {
|
|
27
|
-
views,
|
|
43
|
+
views,
|
|
28
44
|
// @ts-ignore
|
|
29
45
|
} from ':duto'
|
|
30
46
|
// @ts-ignore
|
|
31
47
|
import ':root/styles'
|
|
32
48
|
|
|
33
49
|
|
|
34
|
-
//
|
|
35
|
-
//
|
|
36
|
-
//
|
|
37
|
-
//
|
|
38
|
-
//
|
|
39
|
-
//
|
|
40
|
-
//
|
|
50
|
+
// var props = {}
|
|
51
|
+
// const propKey = "props"
|
|
52
|
+
// const script = document.currentScript || ('function' == typeof document.querySelector ? document.querySelector(`script[${propKey}]`) : void 0)
|
|
53
|
+
// if (script) {
|
|
54
|
+
// const opts = script.getAttribute(propKey)
|
|
55
|
+
// if (opts) {
|
|
56
|
+
// try {
|
|
57
|
+
// props = JSON.parse(opts)
|
|
58
|
+
// } catch (e) { }
|
|
59
|
+
// }
|
|
41
60
|
// }
|
|
42
|
-
import { renderRoute } from './router'
|
|
43
61
|
|
|
44
|
-
|
|
62
|
+
// @ts-ignore
|
|
63
|
+
self.$duto = self.$duto || {}
|
|
64
|
+
// @ts-ignore
|
|
65
|
+
Object.assign(self.$duto, {
|
|
66
|
+
// props,
|
|
67
|
+
// location: '/',
|
|
68
|
+
// navigate: {},
|
|
69
|
+
// params: {},
|
|
70
|
+
// search: {},
|
|
71
|
+
// setSearch: {},
|
|
72
|
+
i18n: {
|
|
73
|
+
detect, isPrefixed,
|
|
74
|
+
getLocale, setLocale,
|
|
75
|
+
cache, cached,
|
|
76
|
+
r, redirect,
|
|
77
|
+
route,
|
|
78
|
+
d, t, tl,
|
|
79
|
+
},
|
|
80
|
+
})
|
|
81
|
+
|
|
82
|
+
|
|
83
|
+
function normalizeParams(
|
|
84
|
+
raw: Record<string, string>,
|
|
85
|
+
paramDefs: Params
|
|
86
|
+
): Record<string, string | string[] | undefined> {
|
|
87
|
+
const result: Record<string, string | string[] | undefined> = {}
|
|
88
|
+
for (const def of paramDefs) {
|
|
89
|
+
const val = raw[def.name]
|
|
90
|
+
if (def.catchAll) {
|
|
91
|
+
result[def.name] = val
|
|
92
|
+
? val.split('/').filter(Boolean)
|
|
93
|
+
: def.optional ? undefined : []
|
|
94
|
+
} else {
|
|
95
|
+
result[def.name] = val === undefined && def.optional ? undefined : val
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
return result
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
async function resolveRoute(
|
|
102
|
+
[, Component, defs, getProps, layouts]: View,
|
|
103
|
+
rawParams: Record<string, string>,
|
|
104
|
+
) {
|
|
105
|
+
let props: StaticProps = defs ? normalizeParams(rawParams, defs) : { ...rawParams },
|
|
106
|
+
sProps = null
|
|
107
|
+
|
|
108
|
+
if (typeof Component === 'string') {
|
|
109
|
+
const mod = await import(/* @vite-ignore */ Component)
|
|
110
|
+
Component = mod.default
|
|
111
|
+
getProps = getProps === 1 ? mod.getProps : 0
|
|
112
|
+
sProps = mod.props ?? null
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
// @ts-ignore
|
|
116
|
+
// const sProps = self.$duto?.props
|
|
117
|
+
// if (sProps && typeof sProps === 'object') { // @ts-ignore
|
|
118
|
+
// self.$duto.props = null
|
|
119
|
+
// Object.assign(props, sProps)
|
|
120
|
+
// } else
|
|
121
|
+
|
|
122
|
+
if (typeof getProps === 'function') {
|
|
123
|
+
try {
|
|
124
|
+
props = {
|
|
125
|
+
...(await getProps(props as Record<string, string | string[] | undefined>, sProps)),
|
|
126
|
+
...props
|
|
127
|
+
}
|
|
128
|
+
} catch (e) {
|
|
129
|
+
console.error('[ssg] getProps failed:', e)
|
|
130
|
+
}
|
|
131
|
+
} else if (sProps && typeof sProps === 'object' && !Array.isArray(sProps)) {
|
|
132
|
+
Object.assign(props, sProps)
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
let el = h(Component, props as any)
|
|
136
|
+
for (const layout of [layouts ?? []].flat())
|
|
137
|
+
el = h(layout, null, el)
|
|
138
|
+
|
|
139
|
+
return el
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
const promises = new Map<string, Promise<ComponentChild>>()
|
|
143
|
+
const resolved = new Map<string, ComponentChild>()
|
|
144
|
+
let last: ComponentChild = null
|
|
145
|
+
|
|
146
|
+
const keyOf = (view: View, params: Record<string, string>) => view[0] + '|' + JSON.stringify(params)
|
|
147
|
+
function load(view: View, params: Record<string, string>) {
|
|
148
|
+
const key = keyOf(view, params)
|
|
149
|
+
if (!promises.has(key)) {
|
|
150
|
+
promises.set(key, resolveRoute(view, params).then((el) => {
|
|
151
|
+
resolved.set(key, el)
|
|
152
|
+
return el
|
|
153
|
+
}))
|
|
154
|
+
}
|
|
155
|
+
return promises.get(key)
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
function View({ view, params }: { view: View, params: Record<string, string>}) {
|
|
159
|
+
const { 0: _wild, ...clean } = params
|
|
160
|
+
const key = keyOf(view, clean)
|
|
161
|
+
|
|
162
|
+
const [loaded, setLoaded] = useState(() => resolved.get(key) ?? last)
|
|
163
|
+
const el = resolved.get(key) ?? loaded
|
|
164
|
+
if (resolved.has(key)) last = el
|
|
165
|
+
|
|
166
|
+
useLayoutEffect(() => {
|
|
167
|
+
let cancelled = false
|
|
168
|
+
self.$duto.route = { pattern: view[0], params: clean, path: location.pathname }
|
|
169
|
+
|
|
170
|
+
load(view, clean).then(el => {
|
|
171
|
+
if (cancelled) return
|
|
172
|
+
last = el
|
|
173
|
+
setLoaded(el)
|
|
174
|
+
// self.dispatchEvent(new CustomEvent('d:load'))
|
|
175
|
+
})
|
|
176
|
+
return () => { cancelled = true }
|
|
177
|
+
}, [key])
|
|
178
|
+
|
|
179
|
+
return el
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
|
|
183
|
+
// enable view transitions for navigation
|
|
184
|
+
const aroundNav: AroundNavHandler = (navigate, to, options) => {
|
|
185
|
+
// feature detection for browsers that dont support View Transitions
|
|
186
|
+
if (!document.startViewTransition)
|
|
187
|
+
return navigate(to, options)
|
|
188
|
+
|
|
189
|
+
// only use view transitions if explicitly requested
|
|
190
|
+
options?.transition
|
|
191
|
+
? document.startViewTransition(() => flushSync(() => navigate(to, options)))
|
|
192
|
+
: navigate(to, options)
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
// clean prerendered content
|
|
196
|
+
while (document.body.firstChild)
|
|
197
|
+
document.body.removeChild(document.body.firstChild)
|
|
198
|
+
|
|
199
|
+
render(<Router aroundNav={aroundNav}>
|
|
45
200
|
{h(() => {
|
|
46
201
|
document.body.removeAttribute('hidden')
|
|
47
|
-
|
|
48
|
-
self.$duto.sroutes = SRoutes
|
|
202
|
+
|
|
49
203
|
const [location, navigate] = useLocation()
|
|
50
204
|
self.$duto.location = location
|
|
51
|
-
self.$duto.navigate =
|
|
205
|
+
self.$duto.navigate = (to,options) => {
|
|
206
|
+
to = new URL(to).pathname
|
|
207
|
+
navigate(to, options)
|
|
208
|
+
}
|
|
52
209
|
self.$duto.params = useParams()
|
|
53
210
|
|
|
54
211
|
const [search, setSearch] = useSearchParams()
|
|
@@ -56,21 +213,13 @@ render(<Router>
|
|
|
56
213
|
self.$duto.setSearch = setSearch
|
|
57
214
|
|
|
58
215
|
updatePickers()
|
|
216
|
+
self.dispatchEvent(new CustomEvent('d:load'))
|
|
59
217
|
}, null)}
|
|
60
218
|
<Switch>
|
|
61
|
-
{
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
// React/Preact hook rules issues with async components)
|
|
66
|
-
|
|
67
|
-
delete params[0]
|
|
68
|
-
window.$duto.route = { pattern: path, params, path: location.pathname }
|
|
69
|
-
|
|
70
|
-
renderRoute(document.body, Component, params, defs, getProps, layouts)
|
|
71
|
-
// Return null — renderRoute will call render() directly on the container
|
|
72
|
-
return null
|
|
73
|
-
}} />
|
|
219
|
+
{views.map(v => (
|
|
220
|
+
<Route key={v[0]} path={v[0]}>
|
|
221
|
+
{(params) => <View view={v} params={params} />}
|
|
222
|
+
</Route>
|
|
74
223
|
))}
|
|
75
224
|
</Switch>
|
|
76
225
|
</Router>, document.body)
|
package/src/pages/client/link.ts
CHANGED
|
@@ -1,3 +1,10 @@
|
|
|
1
|
+
self.addEventListener('d:navigate', e => {
|
|
2
|
+
const el = (e as CustomEvent<HTMLAnchorElement>).detail
|
|
3
|
+
|
|
4
|
+
console.warn('d:navigate', el)
|
|
5
|
+
if (el.href) self.$duto.navigate?.(el.href)
|
|
6
|
+
})
|
|
7
|
+
|
|
1
8
|
export default class Link extends HTMLAnchorElement {
|
|
2
9
|
static observedAttributes = [
|
|
3
10
|
'href',
|
|
@@ -12,7 +19,6 @@ export default class Link extends HTMLAnchorElement {
|
|
|
12
19
|
|
|
13
20
|
constructor() {
|
|
14
21
|
super()
|
|
15
|
-
|
|
16
22
|
this.addEventListener('click', this.#onClick)
|
|
17
23
|
}
|
|
18
24
|
|
|
@@ -21,50 +27,18 @@ export default class Link extends HTMLAnchorElement {
|
|
|
21
27
|
}
|
|
22
28
|
|
|
23
29
|
#onClick = (e: MouseEvent) => {
|
|
24
|
-
// Respeita comportamento padrão
|
|
25
30
|
if (
|
|
26
|
-
e.defaultPrevented
|
|
31
|
+
e.defaultPrevented // respects default behavior
|
|
27
32
|
|| this.target === '_blank'
|
|
28
|
-
|
|
29
|
-
|| e.ctrlKey
|
|
30
|
-
|| e.shiftKey
|
|
31
|
-
|| e.altKey
|
|
33
|
+
// allow ctrl/cmd+click to open in new tab normally
|
|
34
|
+
|| e.metaKey || e.ctrlKey || e.shiftKey || e.altKey
|
|
32
35
|
|| e.button !== 0
|
|
33
|
-
)
|
|
34
|
-
return
|
|
35
|
-
}
|
|
36
|
-
// Allow ctrl/cmd+click to open in new tab normally
|
|
37
|
-
if (e.metaKey || e.ctrlKey || e.shiftKey || e.altKey) return
|
|
38
|
-
// e.preventDefault()
|
|
39
|
-
if ((e.target as HTMLAnchorElement).hasAttribute('href'))
|
|
40
|
-
window.dispatchEvent(new CustomEvent('d:navigate', { detail: e.target }))
|
|
36
|
+
) return
|
|
41
37
|
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
}
|
|
38
|
+
e.preventDefault()
|
|
39
|
+
if (this.hasAttribute('href'))
|
|
40
|
+
self.dispatchEvent(new CustomEvent('d:navigate', { detail: this }))
|
|
46
41
|
|
|
47
|
-
|
|
48
|
-
get url() {
|
|
49
|
-
return new URL(this.href)
|
|
42
|
+
// TODO: scroll from #hash to elements
|
|
50
43
|
}
|
|
51
44
|
}
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
window.addEventListener('d:navigate', e => {
|
|
55
|
-
const el = (e as CustomEvent<HTMLAnchorElement>).detail
|
|
56
|
-
|
|
57
|
-
console.warn('d:navigate', el)
|
|
58
|
-
// if (href) _navigate?.(href)
|
|
59
|
-
// _navigate?.('/haha-2')
|
|
60
|
-
})
|
|
61
|
-
|
|
62
|
-
// const links = document.querySelectorAll('a[d]')
|
|
63
|
-
// for (const link of links) { // @ts-ignore
|
|
64
|
-
// link.addEventListener('click', (e: MouseEvent) => {
|
|
65
|
-
// // Allow ctrl/cmd+click to open in new tab normally
|
|
66
|
-
// if (e.metaKey || e.ctrlKey || e.shiftKey || e.altKey) return
|
|
67
|
-
// e.preventDefault()
|
|
68
|
-
// window.dispatchEvent(new CustomEvent('d:navigate', { detail: e.target }))
|
|
69
|
-
// })
|
|
70
|
-
// }
|
|
@@ -6,7 +6,7 @@ interface IStoreChange<TValue> {
|
|
|
6
6
|
old: TValue | null,
|
|
7
7
|
}
|
|
8
8
|
|
|
9
|
-
type IStoreListener<TValue> = (change: IStoreChange<TValue>) => void
|
|
9
|
+
export type IStoreListener<TValue> = (change: IStoreChange<TValue>) => void
|
|
10
10
|
|
|
11
11
|
/**
|
|
12
12
|
* Store reativo que encapsula o localStorage.
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import { local, type IStoreListener } from './storage'
|
|
2
|
+
|
|
3
|
+
export type Theme = 'auto' | 'dark' | 'light'
|
|
4
|
+
|
|
5
|
+
const key = 'theme'
|
|
6
|
+
const themes = ['dark', 'light']
|
|
7
|
+
const match = () => matchMedia('(prefers-color-scheme: dark)')
|
|
8
|
+
export const preferredColorScheme = () => match().matches ? 'dark' : 'light'
|
|
9
|
+
export const setThemeAttr = (theme: Theme) => {
|
|
10
|
+
if (theme == 'auto') theme = preferredColorScheme()
|
|
11
|
+
document.documentElement.classList.toggle('dark', theme == 'dark')
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
const isValid = (val: string) => themes.includes(val)
|
|
15
|
+
export const getTheme = () => {
|
|
16
|
+
const val = local.get<string>(key) || ''
|
|
17
|
+
return isValid(val) ? val : 'auto'
|
|
18
|
+
}
|
|
19
|
+
export const setTheme = (theme: Theme) => {
|
|
20
|
+
setThemeAttr(theme)
|
|
21
|
+
isValid(theme)
|
|
22
|
+
? local.set(key, theme)
|
|
23
|
+
: local.remove(key)
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export const onThemeChange = (e: IStoreListener<any>) => {
|
|
27
|
+
const mm = match()
|
|
28
|
+
mm.addEventListener('change', e)
|
|
29
|
+
return [local.subscribe(key, e), () => mm.removeEventListener('change', e)]
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
// export function updatePickers(theme = storedTheme || 'auto') {
|
|
33
|
+
// document.querySelectorAll('.theme-select').forEach(el => {
|
|
34
|
+
// const select = el.querySelector('select')
|
|
35
|
+
// if (select) select.value = theme
|
|
36
|
+
// const tmpl = document.querySelector(`#theme-icons`)
|
|
37
|
+
// const newIcon = tmpl && tmpl.content.querySelector('.' + theme)
|
|
38
|
+
// if (newIcon) {
|
|
39
|
+
// const oldIcon = el.querySelector('svg.label-icon')
|
|
40
|
+
// if (oldIcon) oldIcon.replaceChildren(...newIcon.cloneNode(true).childNodes)
|
|
41
|
+
// }
|
|
42
|
+
// })
|
|
43
|
+
// }
|
|
44
|
+
|
|
45
|
+
// <template id="theme-icons">
|
|
46
|
+
// <Icon name="lucide:sun" class="light" />
|
|
47
|
+
// <Icon name="lucide:moon-star" class="dark" />
|
|
48
|
+
// <Icon name="lucide:swatch-book" class="auto" />
|
|
49
|
+
// </template>
|
|
@@ -7,6 +7,8 @@ export const DOCTYPE_RE = /<!doctype html/i
|
|
|
7
7
|
|
|
8
8
|
// export const USE_CLIENT_RE = /^[\s\r\n]*["']use client["'](?=[\s\r\n]|$)/m
|
|
9
9
|
export const USE_CLIENT_RE = /^(?:\s|\/\/.*\n|\/\*[\s\S]*?\*\/)*["']use client["'](?=\s|;|$)/
|
|
10
|
+
export const USE_RAW_RE = /^(?:\s|\/\/.*\n|\/\*[\s\S]*?\*\/)*["']use raw["'](?=\s|;|$)/
|
|
11
|
+
export const USE_HTML_RE = /^(?:\s|\/\/.*\n|\/\*[\s\S]*?\*\/)*["']use html["'](?=\s|;|$)/
|
|
10
12
|
|
|
11
13
|
export const LAYOUT_NAMES = ['_layout', 'layout']
|
|
12
14
|
export const NF_NAMES = ['404', 'not-found']
|
|
@@ -7,10 +7,9 @@ import { h } from 'preact'
|
|
|
7
7
|
import { render } from 'preact-render-to-string'
|
|
8
8
|
import minify from '@minify-html/node'
|
|
9
9
|
|
|
10
|
-
import { JSJSON } from 't0n'
|
|
11
10
|
import { DOCTYPE_RE } from './constants'
|
|
12
11
|
import { getAsset, getChunk } from './assets'
|
|
13
|
-
import {
|
|
12
|
+
import { resolveStaticOutputPath } from './meta'
|
|
14
13
|
import { setLocale } from '../i18n/client'
|
|
15
14
|
|
|
16
15
|
import type { ResolvedConfig } from 'vite'
|
|
@@ -26,13 +25,14 @@ export async function renderPage(
|
|
|
26
25
|
) {
|
|
27
26
|
const {
|
|
28
27
|
mod,
|
|
28
|
+
layouts,
|
|
29
29
|
hasGetProps, hasGetStaticProps,
|
|
30
|
-
|
|
30
|
+
useClient,
|
|
31
31
|
} = page
|
|
32
32
|
if (!mod?.default)
|
|
33
33
|
return console.warn(`No default export in ${page.filePath} — skipping`)
|
|
34
34
|
|
|
35
|
-
if (page.isInternal || page.is404 || (page.isDynamic && !hasGetStaticProps))
|
|
35
|
+
if (page.isInternal || (!page.useRaw && !page.useHtml && page.is404) || (page.isDynamic && !hasGetStaticProps))
|
|
36
36
|
return meta.summary.set('/'+ page.fileName, { type: 2, path: '/'+ page.fileName, size: 0, sizeBr: 0, sizeJS: 0 })
|
|
37
37
|
|
|
38
38
|
const { filePath, pattern, params } = page
|
|
@@ -70,42 +70,52 @@ export async function renderPage(
|
|
|
70
70
|
props.push({ routeParams: {}, pageProps: {} })
|
|
71
71
|
}
|
|
72
72
|
|
|
73
|
-
const
|
|
74
|
-
|
|
73
|
+
const sProps = []
|
|
75
74
|
for (const prop of props) {
|
|
76
75
|
const allProps = { ...prop.pageProps, ...prop.routeParams }
|
|
76
|
+
Object.keys(allProps).length && sProps.push(allProps)
|
|
77
77
|
|
|
78
78
|
// @ts-ignore
|
|
79
|
-
allProps?.locale && setLocale(allProps?.locale || meta.i18n.fallbackLocale)
|
|
79
|
+
allProps?.locale && await setLocale(allProps?.locale || meta.i18n.fallbackLocale)
|
|
80
80
|
|
|
81
81
|
const pathname = resolveStaticOutputPath(page, prop.routeParams)
|
|
82
|
+
|
|
83
|
+
// Shims
|
|
82
84
|
// TODO: melhorar isso para impedir erros do wouter e afins
|
|
83
85
|
// @ts-ignore
|
|
84
86
|
global.location = { pathname: '/'+ (pathname === 'index' ? '' : pathname) }
|
|
87
|
+
global.location.href = global.location.pathname
|
|
85
88
|
// @ts-ignore
|
|
86
|
-
global.window = {
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
89
|
+
global.window = {
|
|
90
|
+
location: global.location,
|
|
91
|
+
$duto: {
|
|
92
|
+
navigate: {},
|
|
93
|
+
route: { pattern: page.pattern, params: prop.routeParams, path: global.location.pathname },
|
|
94
|
+
}
|
|
95
|
+
}
|
|
91
96
|
|
|
92
97
|
let html = ''
|
|
93
98
|
|
|
94
|
-
|
|
99
|
+
if (!useClient) {
|
|
95
100
|
try {
|
|
96
101
|
let el = h(mod.default, allProps as any)
|
|
97
102
|
for (const layout of layouts)
|
|
98
103
|
el = h(layout.mod.default, null, el)
|
|
99
104
|
|
|
105
|
+
// @ts-ignore
|
|
106
|
+
const shell = mod?.Html || mod?.html || mod?.HTML
|
|
107
|
+
// @ts-ignore
|
|
108
|
+
if (shell) el = h(shell, allProps, el)
|
|
109
|
+
|
|
100
110
|
html = render(el)
|
|
101
111
|
} catch (e) {
|
|
102
112
|
console.error(` SSR render failed for ${filePath}: ${e}`)
|
|
103
113
|
console.warn(e)
|
|
104
114
|
html = ''
|
|
105
115
|
}
|
|
106
|
-
|
|
116
|
+
}
|
|
107
117
|
|
|
108
|
-
html = buildHtml(html, Object.keys(allProps).length ? allProps : undefined)
|
|
118
|
+
html = buildHtml(page, html, Object.keys(allProps).length ? allProps : undefined)
|
|
109
119
|
|
|
110
120
|
const outFile = join(config.root, config.build.outDir, pathname +'.html')
|
|
111
121
|
const outHTML = meta.minify ? minify.minify(Buffer.from(html), {}) : html
|
|
@@ -144,16 +154,22 @@ export async function renderPage(
|
|
|
144
154
|
sizeJS: 0,
|
|
145
155
|
})
|
|
146
156
|
}
|
|
147
|
-
|
|
148
157
|
}
|
|
158
|
+
|
|
159
|
+
if (sProps.length)
|
|
160
|
+
page.props = sProps.length < 2 ? sProps[0] : sProps
|
|
149
161
|
}
|
|
150
162
|
|
|
151
163
|
function buildHtml(
|
|
164
|
+
page: IRoute,
|
|
152
165
|
html: string,
|
|
153
|
-
props
|
|
166
|
+
props: StaticProps = {},
|
|
154
167
|
): string {
|
|
168
|
+
if (page.useRaw) return html
|
|
155
169
|
if (!(html.includes('<body') || html.includes('<html') || html.includes('<head')))
|
|
156
170
|
html = wrapHtml(html)
|
|
171
|
+
else
|
|
172
|
+
html = html.replace('<body', '<body hidden')
|
|
157
173
|
|
|
158
174
|
if (!DOCTYPE_RE.test(html))
|
|
159
175
|
html = '<!DOCTYPE html>'+ html
|
|
@@ -164,7 +180,8 @@ function buildHtml(
|
|
|
164
180
|
|
|
165
181
|
const prepend = assets.map(asset => {
|
|
166
182
|
const href = getAsset(asset)
|
|
167
|
-
return href ? `<link href="${href}"
|
|
183
|
+
// return href ? `<link rel="stylesheet" href="${href}">` : null
|
|
184
|
+
return href ? `<link rel="preload" href="${href}" as="style" onload="this.rel='stylesheet'">` : null
|
|
168
185
|
}).filter(Boolean).join('')
|
|
169
186
|
|
|
170
187
|
if (html.includes('</head>')) {
|
|
@@ -175,9 +192,13 @@ function buildHtml(
|
|
|
175
192
|
html = prepend + html
|
|
176
193
|
}
|
|
177
194
|
|
|
178
|
-
|
|
195
|
+
if (page.useHtml) props.__raw = true
|
|
196
|
+
|
|
197
|
+
// const append = (Object.keys(props).length ? `<script>(self.$duto=self.$duto||{}).props=${JSJSON(props)}</script>` : '') + assets.map(file => {
|
|
198
|
+
const append = assets.map(file => {
|
|
179
199
|
const src = getChunk(file)
|
|
180
|
-
return src ? `<script src="${src}"
|
|
200
|
+
return src ? `<script type=module src="${src}"></script>` : null
|
|
201
|
+
// return src ? `<script type=module src="${src}"${(Object.keys(props).length ? ` props='${JSON.stringify(props)}'` : '')}></script>` : null
|
|
181
202
|
}).filter(Boolean).join('')
|
|
182
203
|
|
|
183
204
|
if (html.includes('</body>'))
|
|
@@ -1,15 +1,9 @@
|
|
|
1
|
-
// https://github.com/cloudflare/vinext/blob/main/packages/vinext/src/routing/utils.ts
|
|
2
|
-
|
|
3
|
-
// https://github.com/cloudflare/vinext/blob/adfc2236cba5f7cde63f32f6fb35c0d892428376/packages/vinext/src/build/prerender.ts#L203
|
|
4
|
-
// https://github.com/cloudflare/vinext/blob/main/packages/vinext/src/entries/pages-client-entry.ts
|
|
5
|
-
// https://github.com/cloudflare/vinext/blob/main/packages/vinext/src/shims/ALS-ARCHITECTURE.md
|
|
6
|
-
|
|
7
1
|
import { dirname, extname } from 'node:path'
|
|
8
2
|
|
|
9
3
|
import { getEnv, JSJSON, newID } from 't0n'
|
|
10
4
|
|
|
11
5
|
import { getAliases } from './aliases'
|
|
12
|
-
import { DYNAMIC_RE, INTERNAL_RE, LAYOUT_RE, NF_NAMES, FILE_EXTENSIONS } from './constants'
|
|
6
|
+
import { DYNAMIC_RE, INTERNAL_RE, LAYOUT_RE, NF_NAMES, FILE_EXTENSIONS, USE_RAW_RE, USE_HTML_RE } from './constants'
|
|
13
7
|
import { scan } from '@/utils'
|
|
14
8
|
|
|
15
9
|
import { USE_CLIENT_RE } from './constants'
|
|
@@ -23,13 +17,10 @@ import type {
|
|
|
23
17
|
SummaryMetadata,
|
|
24
18
|
} from './types'
|
|
25
19
|
|
|
26
|
-
|
|
27
|
-
// TODO: https://github.com/cloudflare/vinext/blob/adfc2236cba5f7cde63f32f6fb35c0d892428376/packages/vinext/src/routing/file-matcher.ts#L90
|
|
28
20
|
export async function getPages(meta: Metadata) {
|
|
29
21
|
const nf = new Map<string, IRoute>()
|
|
30
22
|
const layouts = new Map<string, IRoute>()
|
|
31
23
|
const pages = new Map<string, IRoute>()
|
|
32
|
-
const spages = new Set<string>()
|
|
33
24
|
const routes: IRoute[] = []
|
|
34
25
|
|
|
35
26
|
for await (const [file, path, mod] of scan(meta.dir, FILE_EXTENSIONS)) {
|
|
@@ -59,7 +50,9 @@ export async function getPages(meta: Metadata) {
|
|
|
59
50
|
layouts: [],
|
|
60
51
|
hasGetStaticProps: typeof mod.getStaticProps === 'function',
|
|
61
52
|
hasGetProps: typeof mod.getProps === 'function',
|
|
62
|
-
|
|
53
|
+
useClient: USE_CLIENT_RE.test(content),
|
|
54
|
+
useRaw: USE_RAW_RE.test(content),
|
|
55
|
+
useHtml: USE_HTML_RE.test(content),
|
|
63
56
|
}
|
|
64
57
|
|
|
65
58
|
if (LAYOUT_RE.test(path)) {
|
|
@@ -77,31 +70,13 @@ export async function getPages(meta: Metadata) {
|
|
|
77
70
|
if (!page.isInternal)
|
|
78
71
|
pages.set(file, page)
|
|
79
72
|
|
|
80
|
-
if (!page.isInternal)
|
|
73
|
+
if (!page.isInternal && !page.useRaw && !page.useHtml)
|
|
81
74
|
routes.push(page)
|
|
82
|
-
|
|
83
|
-
if (!page.isInternal) {
|
|
84
|
-
if (page.isDynamic && page.hasGetStaticProps) {
|
|
85
|
-
try {
|
|
86
|
-
const _props = await page.mod.getStaticProps?.()
|
|
87
|
-
const props = Array.isArray(_props) ? _props : [_props]
|
|
88
|
-
for (const prop of props)
|
|
89
|
-
spages.add('/'+resolveStaticOutputPath(page, prop as any))
|
|
90
|
-
|
|
91
|
-
} catch(e) {
|
|
92
|
-
console.log('errro', e)
|
|
93
|
-
}
|
|
94
|
-
} else if (!page.isDynamic) {
|
|
95
|
-
spages.add(page.pattern)
|
|
96
|
-
}
|
|
97
|
-
}
|
|
98
|
-
|
|
99
75
|
}
|
|
100
76
|
|
|
101
77
|
meta.nf = nf
|
|
102
78
|
meta.layouts = layouts
|
|
103
79
|
meta.pages = pages
|
|
104
|
-
meta.spages = spages
|
|
105
80
|
meta.routes = await sortRoutes(meta, routes)
|
|
106
81
|
}
|
|
107
82
|
|
|
@@ -140,7 +115,6 @@ ${Array.from(meta.layouts.values()).map(l => `\nimport Layout${l.importName} fro
|
|
|
140
115
|
|
|
141
116
|
export const views = [${meta.routes.map(r => `['${r.pattern}',${r.is404 ? 'Page'+ r.importName : `'${r.fullFilePath}'`},${Object.keys(r.params).length ? JSJSON(Object.entries(r.params).map(([_,v]) => v)) : '0'}${r.hasGetProps ? (r.is404 ? `,getProps${r.importName}` : `,1`) : (r.layouts.length ? ',0': '')}${r.layouts.length ? ',[' + Object.entries(r.layouts).map(([_,l]) => 'Layout'+l.importName) +']' : ''}]`).join(',')}]
|
|
142
117
|
|
|
143
|
-
export const SRoutes = new Set(${JSJSON([...meta.spages])})
|
|
144
118
|
`
|
|
145
119
|
|
|
146
120
|
return meta
|
|
@@ -153,6 +127,8 @@ export const SRoutes = new Set(${JSJSON([...meta.spages])})
|
|
|
153
127
|
|
|
154
128
|
|
|
155
129
|
// //////////////////////////////////////////////////////////////////////////////////////////
|
|
130
|
+
// https://github.com/cloudflare/vinext/blob/main/packages/vinext/src/routing/utils.ts
|
|
131
|
+
|
|
156
132
|
type RouteMeta = {
|
|
157
133
|
staticSegments: number,
|
|
158
134
|
dynamicSegments: number,
|