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
|
@@ -1,6 +1,5 @@
|
|
|
1
|
-
// import type { ComponentType, FunctionComponent, VNode } from 'preact'
|
|
2
|
-
import type { Platform } from '$/cli/types'
|
|
3
1
|
import type { FunctionComponent, VNode } from 'preact'
|
|
2
|
+
import type { Platform } from '$/cli/types'
|
|
4
3
|
|
|
5
4
|
export type PluginOptions = {
|
|
6
5
|
root?: string,
|
|
@@ -43,8 +42,7 @@ export type Metadata = {
|
|
|
43
42
|
aliases: Record<string, string>,
|
|
44
43
|
layouts: Map<string, IRoute>, // all layouts
|
|
45
44
|
nf: Map<string, IRoute>, // all 404
|
|
46
|
-
pages: Map<string, IRoute>, // all
|
|
47
|
-
spages: Set<string>, // ssg pages
|
|
45
|
+
pages: Map<string, IRoute>, // all pages
|
|
48
46
|
routes: Routes, // client-side
|
|
49
47
|
// routes: Map<string, IRoute>, // client-side
|
|
50
48
|
i18n: I18nMetadata,
|
|
@@ -64,15 +62,17 @@ export type PageModule<P = {}> = {
|
|
|
64
62
|
* getStaticProps — build-only.
|
|
65
63
|
* For static pages (no dynamic segments): returns a single props object.
|
|
66
64
|
* For dynamic pages: returns an array of { [routeParam]: value, ...props }
|
|
67
|
-
*
|
|
68
65
|
*/
|
|
69
66
|
export type GetStaticProps =
|
|
70
67
|
| (() => StaticProps | Promise<StaticProps>)
|
|
71
68
|
| (() => StaticProps[] | Promise<StaticProps[]>)
|
|
72
69
|
export type StaticProps = Record<string, unknown>
|
|
73
70
|
|
|
74
|
-
export type GetProps = (params: RouteParams) => StaticProps | Promise<StaticProps>
|
|
75
71
|
export type RouteParams = Record<string, string | string[] | undefined>
|
|
72
|
+
export type GetProps = (params: RouteParams) => StaticProps | Promise<StaticProps>
|
|
73
|
+
export type Params = Record<string, RouteParams>
|
|
74
|
+
|
|
75
|
+
export type View = [string, Page, Params | 0, GetProps | 0 | 1 | null, Page[]]
|
|
76
76
|
|
|
77
77
|
export type Routes = IRoute[]
|
|
78
78
|
export type IRoute = {
|
|
@@ -80,34 +80,25 @@ export type IRoute = {
|
|
|
80
80
|
mod: PageModule,
|
|
81
81
|
hasGetStaticProps: boolean,
|
|
82
82
|
hasGetProps: boolean,
|
|
83
|
-
|
|
83
|
+
useClient: boolean,
|
|
84
|
+
useRaw: boolean,
|
|
85
|
+
useHtml: boolean,
|
|
84
86
|
layouts: IRoute[],
|
|
85
87
|
importName: string,
|
|
86
88
|
|
|
89
|
+
props?: StaticProps,
|
|
90
|
+
|
|
87
91
|
////////////////////////////
|
|
88
92
|
|
|
89
93
|
fileExt: string,
|
|
90
94
|
fileName: string,
|
|
91
|
-
|
|
92
|
-
filePath: string,
|
|
95
|
+
filePath: string, // Original file path relative to pages dir, e.g. `blog/[slug].tsx`
|
|
93
96
|
fullFilePath: string,
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
/** Params extracted from the path segments */
|
|
97
|
-
params: Record<string, RouteParam>,
|
|
98
|
-
// params: RouteParam[],
|
|
99
|
-
/**
|
|
100
|
-
* Similar to the "params" field, but with more associated metadata. For example, for `/site/[blog]/[...slug].tsx`, the segments are:
|
|
101
|
-
*
|
|
102
|
-
* 1. `{ content: 'site', dynamic: false, spread: false }`
|
|
103
|
-
* 2. `{ content: 'blog', dynamic: true, spread: false }`
|
|
104
|
-
* 3. `{ content: '...slug', dynamic: true, spread: true }`
|
|
105
|
-
*/
|
|
106
|
-
// segments: RoutePart[][]; // alternativa ao "params"
|
|
97
|
+
pattern: string, // Wouter-compatible pattern, e.g. `/blog/:slug`
|
|
98
|
+
params: Params,
|
|
107
99
|
is404: boolean,
|
|
108
|
-
|
|
100
|
+
|
|
109
101
|
isDynamic: boolean,
|
|
110
|
-
/** True if this route are internal */
|
|
111
102
|
isInternal: boolean,
|
|
112
103
|
}
|
|
113
104
|
|
|
@@ -23,6 +23,7 @@ import type {
|
|
|
23
23
|
} from 'vite'
|
|
24
24
|
import type { Metadata, PluginOptions } from './types'
|
|
25
25
|
import { _pages, _root } from '@/utils'
|
|
26
|
+
import { cp } from 'node:fs/promises'
|
|
26
27
|
|
|
27
28
|
const VIRTUAL_ID = ':duto'
|
|
28
29
|
const RESOLVED_ID = '\0'+ VIRTUAL_ID
|
|
@@ -219,10 +220,15 @@ export function plugin(options: PluginOptions = {}): PluginOption[] {
|
|
|
219
220
|
|
|
220
221
|
if (file.type === 'asset' && file?.originalFileName?.endsWith('duto/pages/client/app.tsx'))
|
|
221
222
|
file.source += 'i-i{display:contents}'
|
|
222
|
-
// file.source += 'd-b,d-p,i-d,i-s,i-i{display:contents}'
|
|
223
223
|
|
|
224
224
|
if (file.type === 'asset' && file.fileName.endsWith('.css')) {
|
|
225
225
|
setAsset(resolveImport(file.originalFileName as string, metadata.root, metadata.aliases), '/'+ file.fileName)
|
|
226
|
+
|
|
227
|
+
const css = typeof file.source === 'string'
|
|
228
|
+
? file.source
|
|
229
|
+
: Buffer.from(file.source).toString('utf-8')
|
|
230
|
+
|
|
231
|
+
file.source = css.replace(/\/\*!\s*tailwindcss[^*]*\*\/\s*/g, '')
|
|
226
232
|
continue
|
|
227
233
|
}
|
|
228
234
|
|
|
@@ -235,35 +241,6 @@ export function plugin(options: PluginOptions = {}): PluginOption[] {
|
|
|
235
241
|
|
|
236
242
|
},
|
|
237
243
|
|
|
238
|
-
// {
|
|
239
|
-
// name: 'duto:routes-inject',
|
|
240
|
-
// enforce: 'post',
|
|
241
|
-
// generateBundle(_, bundle) {
|
|
242
|
-
// // monta o mapa a partir dos inputs de metadata.routes
|
|
243
|
-
// const routeMap: Record<string, string> = {}
|
|
244
|
-
// for (const r of metadata.routes) {
|
|
245
|
-
// if (r.is404) continue
|
|
246
|
-
// // encontra o chunk cujo facadeModuleId bate com r.fullFilePath
|
|
247
|
-
// for (const [fileName, chunk] of Object.entries(bundle)) {
|
|
248
|
-
// if (chunk.type === 'chunk' && chunk.facadeModuleId === r.fullFilePath) {
|
|
249
|
-
// routeMap[r.path] = fileName
|
|
250
|
-
// break
|
|
251
|
-
// }
|
|
252
|
-
// }
|
|
253
|
-
// }
|
|
254
|
-
|
|
255
|
-
// // injeta no chunk do app
|
|
256
|
-
// for (const chunk of Object.values(bundle)) {
|
|
257
|
-
// if (chunk.type !== 'chunk') continue
|
|
258
|
-
// if (chunk.facadeModuleId?.includes('app.tsx') || chunk.code.includes('__DUTO_ROUTES__')) {
|
|
259
|
-
// chunk.code = chunk.code.replace(
|
|
260
|
-
// '__DUTO_ROUTES__',
|
|
261
|
-
// JSON.stringify(routeMap)
|
|
262
|
-
// )
|
|
263
|
-
// }
|
|
264
|
-
// }
|
|
265
|
-
// }
|
|
266
|
-
// },
|
|
267
244
|
{
|
|
268
245
|
name: 'duto:build',
|
|
269
246
|
enforce: 'pre',
|
|
@@ -306,13 +283,26 @@ export function plugin(options: PluginOptions = {}): PluginOption[] {
|
|
|
306
283
|
// await server.close()
|
|
307
284
|
|
|
308
285
|
const outPath = join(config.root, config.build.outDir)
|
|
286
|
+
|
|
287
|
+
await cp(join(config.root, 'public'), outPath, { recursive: true, force: true })
|
|
288
|
+
|
|
309
289
|
const entry = Bun.file(join(outPath, getChunk(':duto/client/app.tsx')))
|
|
310
|
-
|
|
290
|
+
if (await entry.exists()) {
|
|
291
|
+
let entryContent = await entry.text()
|
|
292
|
+
|
|
293
|
+
for (const r of metadata.routes.filter(r => !r.is404)) {
|
|
294
|
+
const rPath = getChunk(r.fullFilePath.replace(config.root, ''))
|
|
311
295
|
|
|
312
|
-
|
|
313
|
-
|
|
296
|
+
if (r.props) {
|
|
297
|
+
const rFile = Bun.file(join(outPath, rPath))
|
|
298
|
+
await rFile.write((await rFile.text()).replace('}export{', `}export const props=${JSJSON(r.props)};export{`))
|
|
299
|
+
}
|
|
314
300
|
|
|
315
|
-
|
|
301
|
+
entryContent = entryContent.replace(r.fullFilePath, rPath)
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
await entry.write(entryContent)
|
|
305
|
+
}
|
|
316
306
|
|
|
317
307
|
// i18n
|
|
318
308
|
for (const locale of metadata.i18n.locales) {
|
|
@@ -328,6 +318,7 @@ export function plugin(options: PluginOptions = {}): PluginOption[] {
|
|
|
328
318
|
)
|
|
329
319
|
}
|
|
330
320
|
}
|
|
321
|
+
|
|
331
322
|
},
|
|
332
323
|
|
|
333
324
|
// TODO: precisa ser aqui mesmo pois se a pagina for importada no entry, pode incluir codigo nao usado no chunck/bundle
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { useLocation } from 'wouter-preact'
|
|
2
2
|
import type { LinkProps } from 'wouter-preact'
|
|
3
3
|
|
|
4
4
|
const isExternalUrl = (href?: string) =>
|
|
@@ -12,27 +12,19 @@ export const Link = ({
|
|
|
12
12
|
href = to,
|
|
13
13
|
...rest
|
|
14
14
|
}: LinkProps) => {
|
|
15
|
-
|
|
16
|
-
const isSRoute = window?.$duto?.sroutes?.has(href)
|
|
17
|
-
|
|
18
|
-
if (isExternal) {
|
|
15
|
+
if (isExternalUrl(rest.href = href)) {
|
|
19
16
|
rest.target = '_blank'
|
|
20
17
|
rest.rel = 'noopener noreferrer'
|
|
18
|
+
} else {
|
|
19
|
+
rest.is = 'i-l'
|
|
21
20
|
}
|
|
22
21
|
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
rest.class = baseClass + (pathname === href ? ' active' : '')
|
|
29
|
-
|
|
30
|
-
!href && (delete rest.href)
|
|
31
|
-
return <a is='i-l' {...rest} />
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
if (typeof rest.class === 'string' || !rest?.class)
|
|
35
|
-
rest.className = a => (rest.class || '') + (a ? ' active' : '')
|
|
22
|
+
const [pathname] = useLocation()
|
|
23
|
+
const baseClass = typeof rest.class === 'string' || !rest?.class
|
|
24
|
+
? (rest.class || '') as string
|
|
25
|
+
: ''
|
|
26
|
+
rest.class = baseClass + (pathname === href ? ' active' : '')
|
|
36
27
|
|
|
37
|
-
|
|
28
|
+
!href && (delete rest.href)
|
|
29
|
+
return <a {...rest} />
|
|
38
30
|
}
|
package/src/pages/i18n/client.ts
CHANGED
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
import { h, type JSX } from 'preact'
|
|
2
2
|
import { local } from '#/client/storage'
|
|
3
|
-
import { navigate } from '..'
|
|
4
3
|
|
|
5
4
|
const accepted = (lang: string | undefined, locales: string[] = [], fallback: string) => lang && locales.includes(lang) ? lang : fallback
|
|
6
5
|
|
|
@@ -41,7 +40,7 @@ const getAccepted = (lang: string, locales: string[] = [], defaultLocale = '') =
|
|
|
41
40
|
return defaultLocale
|
|
42
41
|
}
|
|
43
42
|
|
|
44
|
-
const detect = (locales: string[], defaultLocale: string) => {
|
|
43
|
+
export const detect = (locales: string[], defaultLocale: string) => {
|
|
45
44
|
if (!defaultLocale) defaultLocale = 'en'
|
|
46
45
|
if (typeof window === 'undefined') return defaultLocale
|
|
47
46
|
|
|
@@ -125,7 +124,7 @@ export const cached = () => local.get('lang') as string || ''
|
|
|
125
124
|
export const updateWidth = (el: HTMLSelectElement) => {
|
|
126
125
|
if (el) {
|
|
127
126
|
const option = el.querySelector(`option[value="${el.value}"]`) as HTMLOptionElement
|
|
128
|
-
const length = option
|
|
127
|
+
const length = option?.textContent.split(' ')[0]?.length || 0
|
|
129
128
|
el.style.width = (length * 15.7)+ 'px'
|
|
130
129
|
}
|
|
131
130
|
}
|
|
@@ -153,7 +152,7 @@ export const r = (key: string, lang?: string) => {
|
|
|
153
152
|
const path = '/'+ lang + (key ? '/'+ route(key, lang) : '')
|
|
154
153
|
|
|
155
154
|
if (!location.pathname.endsWith(path))
|
|
156
|
-
navigate(path)
|
|
155
|
+
self.$duto?.navigate(path)
|
|
157
156
|
|
|
158
157
|
// return '/' + lang + '/' + (key in _routes[lang] ? _routes[lang][key] : key)
|
|
159
158
|
}
|
package/src/pages/index.ts
CHANGED
|
@@ -1,27 +1,10 @@
|
|
|
1
1
|
export {
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
} from 'wouter-preact'
|
|
6
|
-
|
|
7
|
-
export {
|
|
8
|
-
t, r, d,
|
|
9
|
-
getLocale, setLocale,
|
|
10
|
-
isPrefixed,
|
|
2
|
+
route, r, redirect,
|
|
3
|
+
t, tl, d,
|
|
4
|
+
getLocale, setLocale, isPrefixed,
|
|
11
5
|
} from './i18n/client'
|
|
12
6
|
|
|
13
7
|
export { local } from './client/storage'
|
|
14
8
|
|
|
15
|
-
export
|
|
16
|
-
to: string | URL,
|
|
17
|
-
options?: { replace?: boolean; state?: S; transition?: boolean }
|
|
18
|
-
) => {
|
|
19
|
-
if (window?.$duto?.sroutes?.has(to)) {
|
|
20
|
-
location.pathname = to
|
|
21
|
-
return
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
window?.$duto?.navigate || useLocation()[1](to, options)
|
|
25
|
-
}
|
|
26
|
-
|
|
9
|
+
export * from './client/theme'
|
|
27
10
|
export { Link } from './components/link'
|
package/src/server/app.ts
CHANGED
|
@@ -10,6 +10,7 @@ import { isDev } from './utils/environment'
|
|
|
10
10
|
import { getVerb } from './utils'
|
|
11
11
|
import { registerMiddleware, resolveHandle, resolveMw } from './handle'
|
|
12
12
|
|
|
13
|
+
const NFHandler = () => response.notFound()
|
|
13
14
|
async function EHandler(e: Error | HTTPResponseError) {
|
|
14
15
|
console.error(e)
|
|
15
16
|
|
|
@@ -74,7 +75,7 @@ export default function createApp<E extends Env>(options?: ServerOptions<E>) {
|
|
|
74
75
|
|
|
75
76
|
// @ts-ignore
|
|
76
77
|
app.onError(options?.onError || EHandler)
|
|
77
|
-
app.notFound(options?.notFound ||
|
|
78
|
+
app.notFound(options?.notFound || NFHandler)
|
|
78
79
|
|
|
79
80
|
if (options?.init) options.init(app)
|
|
80
81
|
|
|
@@ -1,63 +0,0 @@
|
|
|
1
|
-
import { render, h } from 'preact'
|
|
2
|
-
import type { Page, GetProps, IRoute } from '#/compiler/types'
|
|
3
|
-
|
|
4
|
-
function normalizeParams(
|
|
5
|
-
raw: Record<string, string>,
|
|
6
|
-
paramDefs: IRoute['params']
|
|
7
|
-
): Record<string, string | string[] | undefined> {
|
|
8
|
-
const result: Record<string, string | string[] | undefined> = {};
|
|
9
|
-
for (const def of paramDefs) {
|
|
10
|
-
const val = raw[def.name];
|
|
11
|
-
if (def.catchAll) {
|
|
12
|
-
result[def.name] = val
|
|
13
|
-
? val.split('/').filter(Boolean)
|
|
14
|
-
: def.optional ? undefined : []
|
|
15
|
-
} else {
|
|
16
|
-
result[def.name] = val === undefined && def.optional ? undefined : val;
|
|
17
|
-
}
|
|
18
|
-
}
|
|
19
|
-
return result
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
export async function renderRoute(
|
|
23
|
-
container: HTMLElement,
|
|
24
|
-
Component: Page,
|
|
25
|
-
rawParams: Record<string, string>,
|
|
26
|
-
paramDefs?: IRoute['params'] | 0,
|
|
27
|
-
getProps?: GetProps | 0 | 1,
|
|
28
|
-
layouts?: Page[]
|
|
29
|
-
) {
|
|
30
|
-
while (document.body.firstChild)
|
|
31
|
-
document.body.removeChild(document.body.firstChild)
|
|
32
|
-
|
|
33
|
-
let props: Record<string, unknown> = paramDefs ? normalizeParams(rawParams, paramDefs) : rawParams
|
|
34
|
-
|
|
35
|
-
if (typeof Component === 'string') {
|
|
36
|
-
const mod = await import(Component)
|
|
37
|
-
Component = mod.default
|
|
38
|
-
getProps = getProps === 1 ? mod.getProps : 0
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
// @ts-ignore
|
|
42
|
-
const sProps = self.$duto?.props
|
|
43
|
-
if (typeof sProps === 'object' && sProps !== null) {
|
|
44
|
-
Object.assign(props, sProps)
|
|
45
|
-
} else if (typeof getProps === 'function') {
|
|
46
|
-
try { // TODO: melhorar tipagem do props
|
|
47
|
-
const fetched = await Promise.resolve(getProps(props as Record<string, string | string[] | undefined>))
|
|
48
|
-
props = { ...fetched, ...props }
|
|
49
|
-
} catch (e) {
|
|
50
|
-
console.error('[ssg] getProps failed:', e)
|
|
51
|
-
}
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
let el = h(Component, props as any)
|
|
56
|
-
|
|
57
|
-
const _layouts = layouts ? (Array.isArray(layouts) ? layouts : [layouts]) : []
|
|
58
|
-
for (const layout of _layouts)
|
|
59
|
-
el = h(layout, null, el)
|
|
60
|
-
|
|
61
|
-
render(el, container)
|
|
62
|
-
// render(h(Component, props as any), container)
|
|
63
|
-
}
|