duto 0.260906.0 → 0.261001.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.
Files changed (75) hide show
  1. package/README.md +34 -12
  2. package/package.json +9 -3
  3. package/src/cli.ts +10 -7
  4. package/src/commands/new.ts +172 -0
  5. package/src/commands/templates/pages/.editorconfig +21 -0
  6. package/src/commands/templates/pages/README.md +21 -0
  7. package/src/commands/templates/pages/_gitignore +24 -0
  8. package/src/commands/templates/pages/components/locale-select.tsx +59 -0
  9. package/src/commands/templates/pages/components/select.tsx +95 -0
  10. package/src/commands/templates/pages/i18n/en.ts +17 -0
  11. package/src/commands/templates/pages/i18n/index.ts +19 -0
  12. package/src/commands/templates/pages/i18n/pt-br.ts +17 -0
  13. package/src/commands/templates/pages/package.json +22 -0
  14. package/src/commands/templates/pages/pages/404.tsx +12 -0
  15. package/src/commands/templates/pages/pages/index.tsx +16 -0
  16. package/src/commands/templates/pages/pages/layout.tsx +24 -0
  17. package/src/commands/templates/pages/style.css +24 -0
  18. package/src/commands/templates/pages/tsconfig.json +54 -0
  19. package/src/commands/templates/pages/types.ts +8 -0
  20. package/src/commands/templates/server/base/.editorconfig +21 -0
  21. package/src/commands/templates/server/base/.env.dev.example +2 -0
  22. package/src/commands/templates/server/base/.env.prod.example +2 -0
  23. package/src/commands/templates/server/base/README.md +29 -0
  24. package/src/commands/templates/server/base/_gitignore +100 -0
  25. package/src/commands/templates/server/base/actions/blog/index.get.ts +9 -0
  26. package/src/commands/templates/server/base/actions/blog/post/[id].get.ts +9 -0
  27. package/src/commands/templates/server/base/actions/index.get.ts +5 -0
  28. package/src/commands/templates/server/base/configs/duto.ts +11 -0
  29. package/src/commands/templates/server/base/configs/roles.ts +5 -0
  30. package/src/commands/templates/server/base/package.json +24 -0
  31. package/src/commands/templates/server/base/tsconfig.json +42 -0
  32. package/src/commands/templates/server/workerd/wrangler.toml +47 -0
  33. package/src/commands/upgrade.ts +30 -0
  34. package/src/index.ts +7 -2
  35. package/src/pages/{commands → cli/commands}/build.ts +13 -7
  36. package/src/pages/{commands → cli/commands}/dev.ts +19 -6
  37. package/src/pages/{commands → cli/commands}/preview.ts +6 -4
  38. package/src/pages/cli/index.ts +10 -0
  39. package/src/pages/cli/utils.ts +126 -0
  40. package/src/pages/client/app.tsx +27 -74
  41. package/src/pages/client/router.ts +15 -281
  42. package/src/pages/compiler/aliases.ts +2 -8
  43. package/src/pages/compiler/ast.ts +1 -538
  44. package/src/pages/compiler/constants.ts +95 -13
  45. package/src/pages/compiler/file.ts +1 -1
  46. package/src/pages/compiler/index.ts +20 -128
  47. package/src/pages/compiler/meta.ts +39 -228
  48. package/src/pages/compiler/routes.ts +4 -5
  49. package/src/pages/compiler/types.ts +11 -101
  50. package/src/pages/compiler/vite-plugin.ts +90 -69
  51. package/src/pages/i18n/client.ts +19 -7
  52. package/src/pages/i18n/element.ts +1 -1
  53. package/src/pages/i18n/server.ts +22 -7
  54. package/src/pages/index.ts +1 -2
  55. package/src/pages/preact/server.ts +1 -1
  56. package/src/server/app.ts +5 -10
  57. package/src/server/auth/ability.ts +1 -1
  58. package/src/server/auth/token.ts +5 -5
  59. package/src/server/cli/commands/build.ts +2 -2
  60. package/src/server/cli/commands/deploy.ts +1 -2
  61. package/src/server/cli/commands/dev.ts +6 -4
  62. package/src/server/cli/commands/routes.ts +0 -1
  63. package/src/server/cli/utils.ts +2 -8
  64. package/src/server/config.ts +18 -3
  65. package/src/server/entry.ts +3 -3
  66. package/src/server/open-api/register.ts +3 -3
  67. package/src/server/open-api/spec.ts +11 -4
  68. package/src/server/request.ts +46 -14
  69. package/src/server/routes.ts +8 -31
  70. package/src/server/utils.ts +25 -0
  71. package/src/utils.ts +43 -2
  72. package/src/pages/commands/dev-old.ts +0 -213
  73. package/src/pages/compiler/island.ts +0 -108
  74. package/src/pages/compiler/utils.ts +0 -5
  75. package/src/server/http.ts +0 -23
@@ -1,4 +1,5 @@
1
1
  // import type { ComponentType, FunctionComponent, VNode } from 'preact'
2
+ import type { Platform } from '$/cli/types'
2
3
  import type { FunctionComponent, VNode } from 'preact'
3
4
 
4
5
  export type PluginOptions = {
@@ -7,17 +8,6 @@ export type PluginOptions = {
7
8
  pagesDir?: string,
8
9
  }
9
10
 
10
- export type DepMetadata = {
11
- path: string,
12
- ref: string,
13
- }
14
-
15
- export type FileMetadata = {
16
- path: string,
17
- content: string,
18
- deps: Map<string, DepMetadata>,
19
- }
20
-
21
11
  export type I18nMetadata = {
22
12
  fallbackLocale: string,
23
13
  locales: string[],
@@ -41,16 +31,22 @@ export type SummaryMetadata = {
41
31
  export type Metadata = {
42
32
  root: string,
43
33
  dir: string,
34
+ type: 'pages' | 'server',
35
+ platform: Platform,
36
+ dev: {
37
+ host: string,
38
+ port: number,
39
+ },
40
+ hmr: boolean,
41
+ minify: boolean,
42
+
44
43
  aliases: Record<string, string>,
45
- map: Map<string, FileMetadata>,
46
- violations: Map<string, DepMetadata[]>,
47
44
  layouts: Map<string, IRoute>, // all layouts
48
45
  nf: Map<string, IRoute>, // all 404
49
46
  pages: Map<string, IRoute>, // all
50
47
  spages: Set<string>, // ssg pages
51
48
  routes: Routes, // client-side
52
49
  // routes: Map<string, IRoute>, // client-side
53
- assets: string[],
54
50
  i18n: I18nMetadata,
55
51
  string: string,
56
52
  summary: Map<string, SummaryMetadata>,
@@ -59,8 +55,7 @@ export type Metadata = {
59
55
  export type Page<P = {}> = FunctionComponent<VNode<P>>
60
56
  // A page module as imported by Bun/Vite
61
57
  export type PageModule<P = {}> = {
62
- default: Page<P>,
63
- // default: ComponentType<VNode<any>>,
58
+ default: Page<P>, // ComponentType<VNode<any>>,
64
59
  getStaticProps?: GetStaticProps,
65
60
  getProps?: GetProps,
66
61
  }
@@ -76,12 +71,6 @@ export type GetStaticProps =
76
71
  | (() => StaticProps[] | Promise<StaticProps[]>)
77
72
  export type StaticProps = Record<string, unknown>
78
73
 
79
- /** browser-only */
80
- // export type $duto = {
81
- // sroutes: Set<string>,
82
- // navigate: {},
83
- // route: { pattern: string, params: RouteParams, path: string },
84
- // }
85
74
  export type GetProps = (params: RouteParams) => StaticProps | Promise<StaticProps>
86
75
  export type RouteParams = Record<string, string | string[] | undefined>
87
76
 
@@ -89,14 +78,9 @@ export type Routes = IRoute[]
89
78
  export type IRoute = {
90
79
  content: string,
91
80
  mod: PageModule,
92
- /** True if the page exports getStaticProps */
93
81
  hasGetStaticProps: boolean,
94
- /** True if the page exports getProps */
95
82
  hasGetProps: boolean,
96
83
  isClientOnly: boolean,
97
- islands: ImportUsage[], // TODO mover para cá
98
- // islands: IslandMeta[],
99
- // safeFilePath: string,
100
84
  layouts: IRoute[],
101
85
  importName: string,
102
86
 
@@ -133,77 +117,3 @@ export type RouteParam = {
133
117
  catchAll: boolean,
134
118
  value?: string | string[],
135
119
  }
136
-
137
-
138
-
139
- export interface Usage {
140
- type: 'jsx' | 'call' | 'reference'
141
- source: string,
142
- imported: string,
143
- local: string,
144
- start: number,
145
- end: number,
146
- text: string,
147
- node: unknown,
148
- parent: unknown,
149
- }
150
-
151
- export interface ImportUsage {
152
- source: string,
153
- imported: string,
154
- local: string,
155
- importStart: number,
156
- importEnd: number,
157
- importText: string,
158
- usages: Usage[],
159
- }
160
-
161
- export interface IslandMeta {
162
- /** Identificador curto e único (base64url de 7 chars) */
163
- id: string
164
- /** URL pública do asset JS do componente */
165
- url: string
166
- /** Nome do export usado */
167
- export: string
168
- /** Props serializadas como JSON (HTML-encoded) */
169
- props: string
170
- /** HTML do SSR do componente */
171
- ssrHtml: string
172
- }
173
-
174
- // export interface ReplaceIslandsOptions {
175
- // /**
176
- // * Função que resolve o nome do componente para a URL pública do asset JS.
177
- // * Ex: 'Counter' → '/assets/Counter.BQxoyJbC.js'
178
- // */
179
- // resolveUrl: (exportName: string) => string
180
- // /**
181
- // * Função que carrega o módulo do componente para fazer SSR.
182
- // * Recebe o source da importação (ex: '@/components/counter2') e o nome do export.
183
- // */
184
- // loadComponent: (source: string, exportName: string) => Promise<unknown>
185
- // }
186
-
187
-
188
- // export type Island = {
189
- // /** Unique id — written as data-island on the placeholder div */
190
- // id: string,
191
- // /** Component display name — used by the client to look up the component */
192
- // name: string,
193
- // /** Props serialised into the placeholder for client hydration */
194
- // props: Record<string, unknown>,
195
- // }
196
-
197
- // export type RenderResult = {
198
- // html: string,
199
- // islands: Island[],
200
- // /** true if any islands were found (= client JS needed) */
201
- // hasIslands: boolean,
202
- // }
203
-
204
- // export type IslandMeta = {
205
- // id: string,
206
- // name: string,
207
- // props: Record<string, unknown>,
208
- // }
209
-
@@ -1,34 +1,33 @@
1
1
  // REF: https://github.com/tailwindlabs/tailwindcss/blob/main/packages/%40tailwindcss-vite/src/index.ts
2
2
 
3
- import { existsSync, readFileSync, rmSync } from 'node:fs'
3
+ import { existsSync, rmSync } from 'node:fs'
4
4
  import { join } from 'node:path'
5
5
  import { defineConfig } from 'vite'
6
6
 
7
7
  import preact from '@preact/preset-vite'
8
8
  import tailwind from '@tailwindcss/vite'
9
+ import { JSJSON } from 't0n'
9
10
 
10
11
  import { EXT_RE } from './constants'
11
- import { setAsset, setChunk, setEntry } from './assets'
12
+ import { getChunk, setAsset, setChunk, setEntry } from './assets'
12
13
  import { renderPage } from '.'
13
14
  import { removeExports, resolveImport } from './ast'
14
15
  import { getMetadata } from './meta'
15
16
 
16
- import type { IncomingMessage, ServerResponse } from 'node:http'
17
17
  import type {
18
- // Plugin,
19
18
  UserConfig, ConfigEnv, ResolvedConfig,
20
- PreviewServer,
19
+ // PreviewServer,
21
20
  // ViteDevServer,
22
21
  EnvironmentOptions,
23
22
  PluginOption,
24
23
  } from 'vite'
25
24
  import type { Metadata, PluginOptions } from './types'
26
- import { _duto, _root } from './utils'
25
+ import { _pages, _root } from '@/utils'
27
26
 
28
27
  const VIRTUAL_ID = ':duto'
29
28
  const RESOLVED_ID = '\0'+ VIRTUAL_ID
30
29
 
31
- export const config = (opts: UserConfig & PluginOptions) => {
30
+ export const config = (opts: UserConfig & PluginOptions) => { // @ts-ignore
32
31
  if (!opts.root) opts.root = _root
33
32
  if (!opts.pagesDir) opts.pagesDir = 'pages',
34
33
 
@@ -43,6 +42,7 @@ export const config = (opts: UserConfig & PluginOptions) => {
43
42
  return defineConfig(opts)
44
43
  }
45
44
 
45
+ // TODO: remover..
46
46
  export default config
47
47
 
48
48
  let metadata: Metadata
@@ -51,15 +51,12 @@ export function meta() {
51
51
  }
52
52
 
53
53
  export function plugin(options: PluginOptions = {}): PluginOption[] {
54
- // let server: ViteDevServer
55
54
  let config: ResolvedConfig
56
55
  const {
57
- root = _root, // process.env.npm_config_local_prefix || process.env.PWD || process.cwd() || __dirname,
56
+ root = _root,
58
57
  pagesDir = 'pages',
59
58
  } = options
60
59
 
61
- // let metadata: Metadata
62
-
63
60
  const PAGES_RE = new RegExp(`\/${pagesDir}\/.*\.(tsx?|jsx?)$`, 'i')
64
61
  const stripTarget = new Set(['getStaticProps'])
65
62
 
@@ -101,44 +98,14 @@ export function plugin(options: PluginOptions = {}): PluginOption[] {
101
98
 
102
99
  return opts
103
100
  },
104
-
105
- // configurePreviewServer(server: PreviewServer) {
106
- // const distDir = join(server.config.root, 'dist')
107
-
108
- // server.middlewares.use((req: IncomingMessage, res: ServerResponse, next: (err?: any) => void) => {
109
- // if (req.url && !existsSync(join(distDir, req.url.split('?')[0]))) {
110
- // const notFound = join(distDir, '404.html')
111
-
112
- // if (existsSync(notFound)) {
113
- // res.statusCode = 404
114
- // res.setHeader('Content-Type', 'text/html')
115
- // .end(readFileSync(notFound, 'utf-8'))
116
- // return
117
- // }
118
- // }
119
-
120
- // next()
121
- // })
122
- // },
123
-
124
101
  },
125
102
 
126
103
  {
127
104
  name: 'duto:scan',
128
105
  enforce: 'pre',
129
106
 
130
- // configureServer(_server) {
131
- // server = _server
132
- // // servers.push(server)
133
- // },
134
-
135
107
  async config(conf: UserConfig, env: ConfigEnv) {
136
- // if (!metadata)
137
- metadata = await getMetadata(root, pagesDir)
138
-
139
- // console.log(metadata.map)
140
- // console.log(metadata.violations)
141
- // console.log(Array.from(metadata.violations).filter(file => EXT_RE.test(file)))
108
+ metadata = await getMetadata(root as string, pagesDir)
142
109
 
143
110
  if (conf.build) {
144
111
  // conf.build.emptyOutDir = false
@@ -146,44 +113,47 @@ export function plugin(options: PluginOptions = {}): PluginOption[] {
146
113
  conf.build.manifest = false
147
114
  conf.build.modulePreload = { polyfill: false }
148
115
 
149
- conf.build.minify = true
150
- conf.build.cssMinify = true //////
116
+ conf.build.minify = metadata.minify
117
+ conf.build.cssMinify = metadata.minify
151
118
 
152
119
  conf.build.rolldownOptions = conf.build.rolldownOptions || {}
153
120
  conf.build.rolldownOptions.preserveEntrySignatures = 'exports-only' ///
154
121
  conf.build.rolldownOptions.input = [
122
+ ...metadata.routes.map(r => r.is404 ? '' : r.fullFilePath).filter(Boolean),
155
123
  ':duto/client/app.tsx',
156
124
  // ':duto/not-found.tsx',
157
- ...Object.keys(metadata.pages),
158
- ...metadata.assets,
125
+ // ...Object.keys(metadata.pages),
126
+ // ...metadata.assets,
159
127
  ]
128
+
129
+ const filePrefix = metadata.minify ? '' : '[name].'
160
130
  // conf.build.rolldownOptions.input = ':duto/client/app.tsx'
161
131
  conf.build.rolldownOptions.output = {
162
132
  hoistTransitiveImports: false,
163
133
  format: 'esm',
164
- minifyInternalExports: true,
165
-
166
- // manualChunks: {
167
- // vendor: ['preact', '@preact/signals', 'wouter-preact'],
168
- // },
169
- // preserveModules: true,
134
+ minifyInternalExports: metadata.minify,
170
135
  preserveModulesRoot: '/',
171
- assetFileNames: '[hash][extname]',
172
- entryFileNames: '[hash].js',
173
- chunkFileNames: '[hash].js',
136
+ assetFileNames: filePrefix +'[hash][extname]',
137
+ entryFileNames: filePrefix +'[hash].js',
138
+ chunkFileNames: filePrefix +'[hash].js',
174
139
 
175
140
  // chunkFileNames: '[name].[hash].js',
176
141
  // entryFileNames: 'assets/[name].js',
177
142
  // chunkFileNames: 'assets/chunks/[name]-[hash].js',
178
143
  // assetFileNames: 'assets/[name][extname]',
144
+
145
+ // manualChunks: {
146
+ // vendor: ['preact', '@preact/signals', 'wouter-preact'],
147
+ // },
148
+ // preserveModules: true,
179
149
  }
180
150
  }
181
151
 
182
152
  return conf
183
153
  },
184
154
 
185
- async configResolved(conf) {
186
- config = conf
155
+ async configResolved(c) {
156
+ config = c
187
157
  },
188
158
 
189
159
  resolveId(id) {
@@ -213,13 +183,13 @@ export function plugin(options: PluginOptions = {}): PluginOption[] {
213
183
  if (!id.startsWith(VIRTUAL_ID)) return null
214
184
 
215
185
  if (EXT_RE.test(id) && id.includes('/')) {
216
- const path = join(_duto, id.replace(VIRTUAL_ID, ''))
186
+ const path = join(_pages, id.replace(VIRTUAL_ID, ''))
217
187
  setEntry(id, path)
218
188
  return path
219
189
  }
220
190
 
221
- if (id.endsWith('/hmr') && global.__hmr)
222
- return join(_duto, id.replace(VIRTUAL_ID, '') + '.ts')
191
+ if (id.endsWith('/hmr') && metadata.hmr)
192
+ return join(_pages, 'client', id.replace(VIRTUAL_ID, '') + '.ts')
223
193
 
224
194
  return id.replace(VIRTUAL_ID, RESOLVED_ID)
225
195
  },
@@ -238,15 +208,18 @@ export function plugin(options: PluginOptions = {}): PluginOption[] {
238
208
  // for (const id of moduleIds)
239
209
  // console.error(id)
240
210
 
211
+ // TODO: rever isso...
241
212
  for (const key in bundle) {
242
213
  const file = bundle[key]
214
+
243
215
  if (file.type === 'chunk' && file.isEntry) {
244
216
  setChunk(file.facadeModuleId, '/'+ file.fileName)
245
217
  continue
246
218
  }
247
219
 
248
- if (file.type === 'asset' && file?.originalFileName?.endsWith('duto/client/app.tsx'))
249
- file.source += 'd-b,d-p,i-d,i-s,i-i{display:contents}'
220
+ if (file.type === 'asset' && file?.originalFileName?.endsWith('duto/pages/client/app.tsx'))
221
+ file.source += 'i-i{display:contents}'
222
+ // file.source += 'd-b,d-p,i-d,i-s,i-i{display:contents}'
250
223
 
251
224
  if (file.type === 'asset' && file.fileName.endsWith('.css')) {
252
225
  setAsset(resolveImport(file.originalFileName as string, metadata.root, metadata.aliases), '/'+ file.fileName)
@@ -262,6 +235,35 @@ export function plugin(options: PluginOptions = {}): PluginOption[] {
262
235
 
263
236
  },
264
237
 
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
+ // },
265
267
  {
266
268
  name: 'duto:build',
267
269
  enforce: 'pre',
@@ -296,13 +298,36 @@ export function plugin(options: PluginOptions = {}): PluginOption[] {
296
298
  },
297
299
 
298
300
  async closeBundle() {
299
- for (const [_filePath, page] of metadata.pages) {
301
+ for (const [_filePath, page] of metadata.pages)
300
302
  await renderPage(metadata, config, page)
301
303
  // const mod = await server.ssrLoadModule(_filePath)
302
- }
303
304
 
304
305
  rmSync(join(metadata.root, 'tmp'), {recursive: true, force: true})
305
306
  // await server.close()
307
+
308
+ const outPath = join(config.root, config.build.outDir)
309
+ const entry = Bun.file(join(outPath, getChunk(':duto/client/app.tsx')))
310
+ let entryContent = await entry.text()
311
+
312
+ for (const r of metadata.routes.filter(r => !r.is404))
313
+ entryContent = entryContent.replace(r.fullFilePath, getChunk(r.fullFilePath.replace(config.root, '')))
314
+
315
+ await entry.write(entryContent)
316
+
317
+ // i18n
318
+ for (const locale of metadata.i18n.locales) {
319
+ const intlPath = join(metadata.root, 'i18n', locale + '.ts')
320
+ if (existsSync(intlPath)) {
321
+ const intlMod = await import(intlPath)
322
+ await Bun.write(
323
+ join(outPath, locale +'.js'),
324
+ `var a=${JSJSON({
325
+ routes: intlMod.routes ?? intlMod.default?.routes,
326
+ messages: intlMod.messages ?? intlMod.default?.messages,
327
+ })};export { a as default }`
328
+ )
329
+ }
330
+ }
306
331
  },
307
332
 
308
333
  // TODO: precisa ser aqui mesmo pois se a pagina for importada no entry, pode incluir codigo nao usado no chunck/bundle
@@ -311,12 +336,8 @@ export function plugin(options: PluginOptions = {}): PluginOption[] {
311
336
  // console.log(this.getModuleInfo(id))
312
337
 
313
338
  const stripped = removeExports(id, code, stripTarget)
314
- if (!stripped || stripped === code) return null
315
339
 
316
- return {
317
- code: stripped,
318
- map: null, // source maps omitted for brevity, add if needed
319
- }
340
+ return (!stripped || stripped === code) ? null : { code: stripped, map: null }
320
341
  },
321
342
 
322
343
  },
@@ -1,5 +1,5 @@
1
1
  import { h, type JSX } from 'preact'
2
- import { local } from '#/storage'
2
+ import { local } from '#/client/storage'
3
3
  import { navigate } from '..'
4
4
 
5
5
  const accepted = (lang: string | undefined, locales: string[] = [], fallback: string) => lang && locales.includes(lang) ? lang : fallback
@@ -84,12 +84,22 @@ export const getLocale = () => {
84
84
  }
85
85
  }
86
86
 
87
- export const setLocale = (lang: string) => {
87
+ export const setLocale = async (lang: string) => {
88
+ await loadLang(lang)
88
89
  cache(lang)
89
90
  _locale = accepted(lang, _locales, _detected)
90
91
  }
91
92
 
92
- export default function i18n(
93
+ async function loadLang(lang: string) {
94
+ if (!_routes[lang] || !_messages[lang]) {
95
+ const intlMod = await import('/'+ lang +'.js')
96
+ if (!intlMod) return
97
+ _routes[lang] = intlMod.routes ?? intlMod.default?.routes
98
+ _messages[lang] = intlMod.messages ?? intlMod.default?.messages
99
+ }
100
+ }
101
+
102
+ export default async function i18n(
93
103
  fallbackLocale: string,
94
104
  locales: string[],
95
105
  messages: Record<string, Record<string, string>>,
@@ -103,6 +113,8 @@ export default function i18n(
103
113
  _detected = detect(locales, fallbackLocale)
104
114
  _locale = typeof window === 'undefined' ? fallbackLocale : getLocale()
105
115
 
116
+ await loadLang(_locale)
117
+
106
118
  // console.error(routes)
107
119
  // console.error(routeMap)
108
120
  }
@@ -173,10 +185,10 @@ type TProps = {
173
185
  }
174
186
 
175
187
  const lookup = (key: string, lang: string): string => {
176
- if (!key) return '';
177
- if (key.startsWith('_')) return key.replace(/^_+/, '');
178
- const msg = _messages[lang] ?? _messages[_locale];
179
- return (key in msg ? msg[key] : key) as string;
188
+ if (!key) return ''
189
+ if (key.startsWith('_')) return key.replace(/^_+/, '')
190
+ const msg = _messages[lang] ?? _messages[_locale]
191
+ return (msg && key in msg ? msg[key] : key) as string
180
192
  }
181
193
 
182
194
  const interpolate = (template: string, params: Record<string, Primitives> | Primitives[]): string => {
@@ -1,4 +1,4 @@
1
- import { local } from '#/storage'
1
+ import { local } from '#/client/storage'
2
2
  import { t, _prefixed } from './client'
3
3
 
4
4
  export default class EI18n extends HTMLElement {
@@ -1,7 +1,7 @@
1
1
  import { join } from 'node:path'
2
2
  import { IMPORT, JSJSON } from 't0n'
3
3
 
4
- import type { Metadata, I18nMetadata } from '@/types'
4
+ import type { Metadata, I18nMetadata } from '#/compiler/types'
5
5
  import { existsSync } from 'node:fs'
6
6
 
7
7
  export default async function getI18n(meta: Metadata) {
@@ -11,7 +11,7 @@ export default async function getI18n(meta: Metadata) {
11
11
  'i18n.ts',
12
12
  'i18n/index.ts',
13
13
  ]) {
14
- const path = join(meta.root, variant)
14
+ const path = join(meta.root, variant)
15
15
  if (existsSync(path)) {
16
16
  file = path
17
17
  break
@@ -22,13 +22,28 @@ export default async function getI18n(meta: Metadata) {
22
22
  const mod = await IMPORT<I18nMetadata>(file)
23
23
 
24
24
  // @ts-ignore
25
- if (['locales', 'fallbackLocale', 'messages', 'routes', 'options'].some(i => !mod[i]))
25
+ if (['locales', 'fallbackLocale', 'options'].some(i => !mod[i]))
26
26
  return {} as I18nMetadata
27
27
 
28
+ // const routes = Object.fromEntries(mod.locales.map(locale => [locale, ]))
29
+
30
+ const routes: Record<string, Record<string, string>> = {}
31
+ const messages: Record<string, Record<string, string>> = {}
32
+ for (const locale of mod.locales) {
33
+ const intlPath = join(meta.root, 'i18n', locale + '.ts')
34
+ if (existsSync(intlPath)) {
35
+ const intlMod = await IMPORT<{default:{routes: Record<string, string>, messages: Record<string, string>}}>(intlPath)
36
+ routes[locale] = intlMod.default.routes
37
+ messages[locale] = intlMod.default.messages
38
+ }
39
+ }
40
+
28
41
  const i18n: I18nMetadata = {
29
42
  ...mod,
43
+ routes,
44
+ messages,
30
45
  routeMap: Object.fromEntries(
31
- Object.values(mod.routes)
46
+ Object.values(routes)
32
47
  .map(paths => Object.entries(paths).map(([key, path]) => path == key ? 0 : [path, key]).filter(Boolean))
33
48
  .flat() as Array<[string, string]>
34
49
  ),
@@ -36,7 +51,7 @@ export default async function getI18n(meta: Metadata) {
36
51
  string: '',
37
52
  }
38
53
 
39
- Object.entries(mod.routes)
54
+ Object.entries(routes)
40
55
  .forEach(([locale, paths]) => {
41
56
  Object.entries(paths)
42
57
  .forEach(([route, path]) => i18n.pageRoutes.push({ locale, path }))
@@ -44,8 +59,8 @@ export default async function getI18n(meta: Metadata) {
44
59
 
45
60
  i18n.string = `export const fallbackLocale = ${JSJSON(mod.fallbackLocale)}
46
61
  export const locales = ${JSJSON(mod.locales)}
47
- export const messages = ${JSJSON(mod.messages)}
48
- export const routes = ${JSJSON(mod.routes)}
62
+ export const messages = ${JSJSON(messages)}
63
+ export const routes = ${JSJSON(routes)}
49
64
  export const routeMap = ${JSJSON(i18n.routeMap)}
50
65
  export const pageRoutes = ${JSJSON(i18n.pageRoutes)}
51
66
  export const options = ${JSJSON(mod.options)}
@@ -21,8 +21,7 @@ export const navigate = <S = any>(
21
21
  return
22
22
  }
23
23
 
24
- const n = window?.$duto?.navigate || useLocation()[1]
25
- n(to, options)
24
+ window?.$duto?.navigate || useLocation()[1](to, options)
26
25
  }
27
26
 
28
27
  export { Link } from './components/link'
@@ -8,7 +8,7 @@ import { getContext, incrementIslandId } from './context'
8
8
  import { restoreSignalsOnProps, serializeSignals } from './signals'
9
9
  import StaticHtml from './static-html'
10
10
  import type { IslandAttrs } from './types'
11
- import type { IslandMeta } from '@/types'
11
+ import type { IslandMeta } from '#/compiler/types'
12
12
  // import type { AstroPreactAttrs, RendererContext } from './types.js'
13
13
  // import { createFilter } from '@astrojs/internal-helpers/create-filter'
14
14
 
package/src/server/app.ts CHANGED
@@ -1,18 +1,16 @@
1
1
  import { Hono } from 'hono'
2
- import { Envir } from 't0n'
3
2
  import type {
4
3
  Env, Context, Next,
5
4
  HTTPResponseError,
6
5
  ServerOptions, Route
7
6
  } from './types'
8
- import request, { GET_REQUEST } from './request'
7
+ import request from './request'
9
8
  import response from './response'
10
9
  import { isDev } from './utils/environment'
11
- import { getVerb } from './http'
10
+ import { getVerb } from './utils'
12
11
  import { registerMiddleware, resolveHandle, resolveMw } from './handle'
13
12
 
14
- const NFHandler = () => response.notFound()
15
- const EHandler = async (e: Error | HTTPResponseError) => {
13
+ async function EHandler(e: Error | HTTPResponseError) {
16
14
  console.error(e)
17
15
 
18
16
  switch (true) {
@@ -59,7 +57,7 @@ const EHandler = async (e: Error | HTTPResponseError) => {
59
57
  // stack: isDev (? e.stack : undefined
60
58
  }
61
59
 
62
- export const createApp = <E extends Env>(options?: ServerOptions<E>) => {
60
+ export default function createApp<E extends Env>(options?: ServerOptions<E>) {
63
61
  // const root = options?.root ?? '/'
64
62
  const app = options?.app ?? new Hono<E>()
65
63
 
@@ -76,8 +74,7 @@ export const createApp = <E extends Env>(options?: ServerOptions<E>) => {
76
74
 
77
75
  // @ts-ignore
78
76
  app.onError(options?.onError || EHandler)
79
- // @ts-ignore
80
- app.notFound(options?.notFound || NFHandler)
77
+ app.notFound(options?.notFound || response.notFound)
81
78
 
82
79
  if (options?.init) options.init(app)
83
80
 
@@ -92,5 +89,3 @@ export const createApp = <E extends Env>(options?: ServerOptions<E>) => {
92
89
 
93
90
  return app
94
91
  }
95
-
96
- export default createApp
@@ -1,4 +1,4 @@
1
- import { verbAlias } from '$/http'
1
+ import { verbAlias } from '$/utils'
2
2
  import type { IRequest, Routes } from '$/types'
3
3
  import type { Roles, Abilities } from './types'
4
4