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.
@@ -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
- isClientOnly: boolean,
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
- /** Original file path relative to pages dir, e.g. `blog/[slug].tsx` */
92
- filePath: string,
95
+ filePath: string, // Original file path relative to pages dir, e.g. `blog/[slug].tsx`
93
96
  fullFilePath: string,
94
- /** Wouter-compatible pattern, e.g. `/blog/:slug` */
95
- pattern: string,
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
- /** True if this route has any dynamic segments */
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
- let entryContent = await entry.text()
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
- for (const r of metadata.routes.filter(r => !r.is404))
313
- entryContent = entryContent.replace(r.fullFilePath, getChunk(r.fullFilePath.replace(config.root, '')))
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
- await entry.write(entryContent)
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 { Link as WouterLink, useLocation } from 'wouter-preact'
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
- const isExternal = isExternalUrl(rest.href = href)
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
- if (!href || isExternal || isSRoute) {
24
- const [pathname] = useLocation()
25
- const baseClass = typeof rest.class === 'string' || !rest?.class
26
- ? (rest.class || '') as string
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
- return <WouterLink is='i-l' {...rest} />
28
+ !href && (delete rest.href)
29
+ return <a {...rest} />
38
30
  }
@@ -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.textContent.split(' ')[0]?.length || 0
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
  }
@@ -1,27 +1,10 @@
1
1
  export {
2
- Redirect,
3
- useLocation, useParams, useSearch, useSearchParams,
4
- matchRoute,
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 const navigate = <S = any>(
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 || response.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
- }