duto 0.261011.0 → 0.261012.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 +22 -26
- package/src/pages/cli/commands/build.ts +1 -2
- package/src/pages/cli/utils.ts +3 -4
- package/src/pages/client/app.tsx +1 -1
- package/src/pages/compiler/ast.ts +13 -0
- package/src/pages/compiler/bun-plugin.ts +245 -0
- package/src/pages/compiler/index.ts +3 -3
- package/src/pages/compiler/types.ts +0 -1
- package/src/pages/compiler/vite-plugin.ts +0 -336
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "duto",
|
|
3
3
|
"description": "All-in-one serverless framework for web artisans",
|
|
4
|
-
"version": "0.
|
|
4
|
+
"version": "0.261012.0",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./src/index.ts",
|
|
7
7
|
"types": "./src/index.d.ts",
|
|
@@ -25,46 +25,42 @@
|
|
|
25
25
|
"./length": "./src/server/utils/length.ts"
|
|
26
26
|
},
|
|
27
27
|
"dependencies": {
|
|
28
|
-
"@inquirer/confirm": "^5.1.
|
|
29
|
-
"@inquirer/input": "^4.1
|
|
30
|
-
"@inquirer/select": "^4.
|
|
28
|
+
"@inquirer/confirm": "^5.1.21",
|
|
29
|
+
"@inquirer/input": "^4.3.1",
|
|
30
|
+
"@inquirer/select": "^4.4.2",
|
|
31
31
|
"nanospinner": "^1.2.2",
|
|
32
|
-
"@hono/node-server": "^1.19.
|
|
33
|
-
"@hono/standard-validator": "^0.2.
|
|
32
|
+
"@hono/node-server": "^1.19.17",
|
|
33
|
+
"@hono/standard-validator": "^0.2.3",
|
|
34
34
|
"@hono/zod-validator": "^0.7.6",
|
|
35
|
-
"@scalar/hono-api-reference": "^0.12.
|
|
35
|
+
"@scalar/hono-api-reference": "^0.12.11",
|
|
36
36
|
"@minify-html/node": "^0.18.1",
|
|
37
|
-
"@preact/signals": "^2.
|
|
38
|
-
"
|
|
39
|
-
"chokidar": "^3.
|
|
37
|
+
"@preact/signals": "^2.11.3",
|
|
38
|
+
"bun-plugin-tailwind": "^0.1.2",
|
|
39
|
+
"chokidar": "^3.6.0",
|
|
40
40
|
"citty": "^0.2.2",
|
|
41
41
|
"consola": "^3.4.2",
|
|
42
42
|
"cripta": "0.x",
|
|
43
|
-
"dotenv": "^16.
|
|
44
|
-
"esbuild": "^0.25.
|
|
43
|
+
"dotenv": "^16.6.1",
|
|
44
|
+
"esbuild": "^0.25.12",
|
|
45
45
|
"forj": "0.x",
|
|
46
|
-
"hono": "^4.13.
|
|
47
|
-
"hono-openapi": "^1.3.
|
|
46
|
+
"hono": "^4.13.13",
|
|
47
|
+
"hono-openapi": "^1.3.5",
|
|
48
48
|
"localflare-api": "^0.5.0",
|
|
49
49
|
"localflare-core": "^0.5.0",
|
|
50
50
|
"miniflare": "^4.20260730.0",
|
|
51
|
-
"pathe": "^2.0",
|
|
52
|
-
"preact": "^
|
|
53
|
-
"preact-render-to-string": "^6.
|
|
51
|
+
"pathe": "^2.0.3",
|
|
52
|
+
"preact": "^11.0.1",
|
|
53
|
+
"preact-render-to-string": "^6.8.0",
|
|
54
54
|
"quansync": "^0.2.11",
|
|
55
55
|
"t0n": "0.x",
|
|
56
|
-
"
|
|
57
|
-
"
|
|
58
|
-
"
|
|
59
|
-
"zod": "^4.3.6",
|
|
56
|
+
"wouter-preact": "^3.13.0",
|
|
57
|
+
"wrangler": "^4.149.0",
|
|
58
|
+
"zod": "^4.6.6",
|
|
60
59
|
"zod-openapi": "^5.4.6"
|
|
61
60
|
},
|
|
62
61
|
"devDependencies": {
|
|
63
|
-
"@cloudflare/workers-types": "^4.
|
|
64
|
-
"
|
|
65
|
-
"@preact/preset-vite": "^2.10.5",
|
|
66
|
-
"@tailwindcss/vite": "^4.3.1",
|
|
67
|
-
"bun-types": "^1.4.0",
|
|
62
|
+
"@cloudflare/workers-types": "^4.20260702.1",
|
|
63
|
+
"bun-types": "^1.4.3",
|
|
68
64
|
"typescript": "^5.9.3"
|
|
69
65
|
},
|
|
70
66
|
"resolutions": {
|
|
@@ -1,9 +1,8 @@
|
|
|
1
|
-
import { build } from 'vite'
|
|
2
1
|
import { setEnv } from 't0n'
|
|
3
2
|
import { command } from 't0n/cli'
|
|
4
3
|
import { dim, underline } from 't0n/color'
|
|
5
4
|
import { wait, error, rn, event } from 't0n/log'
|
|
6
|
-
import { meta } from '#/compiler/
|
|
5
|
+
import { meta, build } from '#/compiler/bun-plugin'
|
|
7
6
|
import type { SummaryMetadata } from '#/compiler/types'
|
|
8
7
|
import { getConfig } from '../utils'
|
|
9
8
|
import { filesize } from '@/utils'
|
package/src/pages/cli/utils.ts
CHANGED
|
@@ -1,17 +1,16 @@
|
|
|
1
1
|
import { IMPORT, Datte } from 't0n'
|
|
2
2
|
import { join } from 'pathe'
|
|
3
3
|
import { gray, green, yellow, red, cyan } from 't0n/color'
|
|
4
|
-
import type { UserConfig } from 'vite'
|
|
5
4
|
|
|
6
5
|
import { resolveExtension } from '#/compiler/file'
|
|
7
|
-
import config from '#/compiler/
|
|
6
|
+
import config, { type DutoOptions } from '#/compiler/bun-plugin'
|
|
8
7
|
import { _root } from '@/utils'
|
|
9
8
|
import { match, match404 } from '#/compiler/routes'
|
|
10
9
|
import type { Metadata, PluginOptions } from '#/compiler/types'
|
|
11
10
|
import { EXTENSIONS_LIST, NF_NAMES } from '#/compiler/constants'
|
|
12
11
|
|
|
13
|
-
export async function getConfig(name = 'config'): Promise<
|
|
14
|
-
let opts = {} as
|
|
12
|
+
export async function getConfig(name = 'config'): Promise<DutoOptions> {
|
|
13
|
+
let opts = {} as DutoOptions
|
|
15
14
|
const file = resolveExtension(name)
|
|
16
15
|
if (file) opts = await IMPORT(join(_root, file), 'default')
|
|
17
16
|
return config(opts)
|
package/src/pages/client/app.tsx
CHANGED
|
@@ -106,7 +106,7 @@ async function resolveRoute(
|
|
|
106
106
|
sProps = null
|
|
107
107
|
|
|
108
108
|
if (typeof Component === 'string') {
|
|
109
|
-
const mod = await import(
|
|
109
|
+
const mod = await import((Component[0] == '/' ? '' : '/')+ Component)
|
|
110
110
|
Component = mod.default
|
|
111
111
|
getProps = getProps === 1 ? mod.getProps : 0
|
|
112
112
|
sProps = mod.props ?? null
|
|
@@ -1,6 +1,19 @@
|
|
|
1
1
|
import { resolve } from 'node:path'
|
|
2
2
|
import { resolveAlias } from './aliases'
|
|
3
3
|
|
|
4
|
+
const IMPORT_RE = /(?:\/\/[^\n]*|\/\*[\s\S]*?\*\/)|import\s*(?:type\s*)?(?:[^'"]*?from\s*)?['"]([^'"]+)['"]/g
|
|
5
|
+
export function getImports(str: string) {
|
|
6
|
+
const found: string[] = []
|
|
7
|
+
let match: RegExpExecArray | null
|
|
8
|
+
while ((match = IMPORT_RE.exec(str))) {
|
|
9
|
+
if (match[0].includes('import type') || !match[1]) continue
|
|
10
|
+
|
|
11
|
+
found.push(match[1])
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
return found
|
|
15
|
+
}
|
|
16
|
+
|
|
4
17
|
const resolveImportCache = new Map<string, string>()
|
|
5
18
|
export const resolveImport = (lib: string, importer: string, aliases: Record<string, string>) => {
|
|
6
19
|
if (resolveImportCache.has(lib))
|
|
@@ -0,0 +1,245 @@
|
|
|
1
|
+
import { existsSync, rmSync } from 'node:fs'
|
|
2
|
+
import { cp, mkdir } from 'node:fs/promises'
|
|
3
|
+
import { dirname, extname, join, relative } from 'node:path'
|
|
4
|
+
|
|
5
|
+
import tailwind from 'bun-plugin-tailwind'
|
|
6
|
+
import { JSJSON } from 't0n'
|
|
7
|
+
|
|
8
|
+
import { EXT_RE } from './constants'
|
|
9
|
+
import { getChunk, setAsset, setChunk, setEntry } from './assets'
|
|
10
|
+
import { renderPage } from '.'
|
|
11
|
+
import { getImports, removeExports } from './ast'
|
|
12
|
+
import { getMetadata } from './meta'
|
|
13
|
+
import { _pages, _root } from '@/utils'
|
|
14
|
+
|
|
15
|
+
import type { BunPlugin, BuildArtifact } from 'bun'
|
|
16
|
+
import type { Metadata } from './types'
|
|
17
|
+
|
|
18
|
+
const VIRTUAL_ID = ':duto'
|
|
19
|
+
const NAMESPACE = 'duto'
|
|
20
|
+
const APP_ID = ':duto/client/app.tsx'
|
|
21
|
+
|
|
22
|
+
export type DutoOptions = {
|
|
23
|
+
root: string
|
|
24
|
+
pagesDir: string
|
|
25
|
+
outDir: string
|
|
26
|
+
plugins: BunPlugin[]
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export const config = (opts: DutoOptions): DutoOptions => {
|
|
30
|
+
opts.root ||= _root
|
|
31
|
+
opts.pagesDir ||= 'pages'
|
|
32
|
+
opts.outDir ||= 'dist'
|
|
33
|
+
opts.plugins = [...(opts.plugins || [])]
|
|
34
|
+
return opts
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
let metadata: Metadata
|
|
38
|
+
export function meta() {
|
|
39
|
+
return metadata
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
const CSS_CANDIDATES = [
|
|
43
|
+
'style.css',
|
|
44
|
+
'styles.css',
|
|
45
|
+
'styles/index.css',
|
|
46
|
+
'style/index.css',
|
|
47
|
+
'tailwind.css',
|
|
48
|
+
'tailwindcss.css',
|
|
49
|
+
'styles/tailwind.css',
|
|
50
|
+
'styles/tailwindcss.css',
|
|
51
|
+
]
|
|
52
|
+
|
|
53
|
+
const REACT_ALIAS: Record<string, string> = {
|
|
54
|
+
'react': 'preact/compat',
|
|
55
|
+
'react-dom': 'preact/compat',
|
|
56
|
+
'react-dom/client': 'preact/compat/client',
|
|
57
|
+
'react-dom/test-utils': 'preact/test-utils',
|
|
58
|
+
'react/jsx-runtime': 'preact/jsx-runtime',
|
|
59
|
+
'react/jsx-dev-runtime': 'preact/jsx-dev-runtime',
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
const stripExt = (p: string) => p.slice(0, p.length - extname(p).length)
|
|
63
|
+
|
|
64
|
+
export async function build(opts: DutoOptions) {
|
|
65
|
+
const conf = config(opts)
|
|
66
|
+
const outPath = join(conf.root, conf.outDir)
|
|
67
|
+
|
|
68
|
+
const PAGES_RE = new RegExp(`/${conf.pagesDir}/.*\\.(tsx?|jsx?)$`, 'i')
|
|
69
|
+
const stripTarget = new Set(['getStaticProps'])
|
|
70
|
+
|
|
71
|
+
metadata = await getMetadata(conf.root, conf.pagesDir)
|
|
72
|
+
|
|
73
|
+
const appEntry = join(_pages, 'client', 'app.tsx')
|
|
74
|
+
setEntry(APP_ID, relative(conf.root, appEntry))
|
|
75
|
+
|
|
76
|
+
const routes = metadata.routes.filter(r => !r.is404)
|
|
77
|
+
const entrypoints = [...routes.map(r => r.fullFilePath), appEntry]
|
|
78
|
+
|
|
79
|
+
const sources = new Map(entrypoints.map(e => [relative(conf.root, stripExt(e)), e]))
|
|
80
|
+
|
|
81
|
+
const prefix = metadata.minify ? '' : '[name].'
|
|
82
|
+
|
|
83
|
+
rmSync(outPath, { recursive: true, force: true })
|
|
84
|
+
await mkdir(outPath, { recursive: true })
|
|
85
|
+
|
|
86
|
+
const result = await Bun.build({
|
|
87
|
+
entrypoints,
|
|
88
|
+
root: conf.root,
|
|
89
|
+
target: 'browser',
|
|
90
|
+
format: 'esm',
|
|
91
|
+
splitting: true,
|
|
92
|
+
minify: metadata.minify,
|
|
93
|
+
sourcemap: 'none',
|
|
94
|
+
naming: {
|
|
95
|
+
entry: '[dir]/[name]-[hash6].[ext]', // used to map output to source
|
|
96
|
+
chunk: prefix + '[hash6].[ext]',
|
|
97
|
+
asset: prefix + '[hash6].[ext]',
|
|
98
|
+
},
|
|
99
|
+
define: {
|
|
100
|
+
'process.env.NODE_ENV': '"production"',
|
|
101
|
+
},
|
|
102
|
+
plugins: [
|
|
103
|
+
...(conf.plugins || []),
|
|
104
|
+
tailwind,
|
|
105
|
+
{
|
|
106
|
+
name: 'duto',
|
|
107
|
+
setup(build) {
|
|
108
|
+
build.onResolve({ filter: /^react(-dom)?(\/|$)/ }, args => {
|
|
109
|
+
const target = REACT_ALIAS[args.path]
|
|
110
|
+
if (!target) return
|
|
111
|
+
return { path: Bun.resolveSync(target, conf.root) }
|
|
112
|
+
})
|
|
113
|
+
|
|
114
|
+
build.onResolve({ filter: /^:root/ }, args => {
|
|
115
|
+
for (const f of CSS_CANDIDATES) {
|
|
116
|
+
const path = join(conf.root, f)
|
|
117
|
+
if (existsSync(path)) return { path }
|
|
118
|
+
}
|
|
119
|
+
})
|
|
120
|
+
|
|
121
|
+
build.onResolve({ filter: /^:duto/ }, args => {
|
|
122
|
+
const id = args.path
|
|
123
|
+
|
|
124
|
+
if (EXT_RE.test(id) && id.includes('/')) {
|
|
125
|
+
const path = join(_pages, id.replace(VIRTUAL_ID, ''))
|
|
126
|
+
return { path }
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
if (id.endsWith('/hmr') && metadata.hmr)
|
|
130
|
+
return { path: join(_pages, 'client', id.replace(VIRTUAL_ID, '') + '.ts') }
|
|
131
|
+
|
|
132
|
+
return { path: id, namespace: NAMESPACE }
|
|
133
|
+
})
|
|
134
|
+
|
|
135
|
+
build.onLoad({ filter: /.*/, namespace: NAMESPACE }, args => ({
|
|
136
|
+
contents: args.path.endsWith('/i18n') ? metadata.i18n.string : metadata.string,
|
|
137
|
+
loader: 'ts',
|
|
138
|
+
}))
|
|
139
|
+
|
|
140
|
+
build.onLoad({ filter: PAGES_RE }, async args => {
|
|
141
|
+
if (args.path.includes('node_modules')) return
|
|
142
|
+
|
|
143
|
+
const code = await Bun.file(args.path).text()
|
|
144
|
+
const stripped = removeExports(args.path, code, stripTarget)
|
|
145
|
+
if (!stripped || stripped === code) return
|
|
146
|
+
|
|
147
|
+
const ext = extname(args.path).slice(1) as 'ts' | 'tsx' | 'js' | 'jsx'
|
|
148
|
+
return { contents: stripped, loader: ext }
|
|
149
|
+
})
|
|
150
|
+
},
|
|
151
|
+
},
|
|
152
|
+
],
|
|
153
|
+
})
|
|
154
|
+
|
|
155
|
+
if (!result.success)
|
|
156
|
+
throw new AggregateError(result.logs, 'duto: build failed')
|
|
157
|
+
|
|
158
|
+
const keyOf = (a: BuildArtifact, fileName: string) => {
|
|
159
|
+
let noExt = stripExt(fileName).replaceAll('_.._', '..')
|
|
160
|
+
if (a.hash) noExt = noExt.replace(new RegExp(`-${a.hash}$`), '')
|
|
161
|
+
return noExt
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
const dirs = new Set<string>()
|
|
165
|
+
for (const out of result.outputs) {
|
|
166
|
+
const fileName = out.path.replace(/^\.\//, '').replaceAll('_.._', '..')
|
|
167
|
+
const dest = metadata.minify ? fileName.split('/').at(-1)!.split('-').at(-1)! : fileName.replace('../../packages/duto/src/pages', '')
|
|
168
|
+
const filePath = join(outPath, dest)
|
|
169
|
+
const key = keyOf(out, fileName)
|
|
170
|
+
const owner = sources.get(key)
|
|
171
|
+
|
|
172
|
+
const fileDir = dirname(filePath)
|
|
173
|
+
if (fileDir !== outPath && !dirs.has(fileDir)) {
|
|
174
|
+
dirs.add(fileDir)
|
|
175
|
+
await mkdir(fileDir, { recursive: true })
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
let content = await out.text()
|
|
179
|
+
if (fileName.endsWith('.css')) {
|
|
180
|
+
if (owner === appEntry) content += 'i-i{display:contents}'
|
|
181
|
+
content = content.replace(/\/\*!\s*tailwindcss[^*]*\*\/\s*/g, '')
|
|
182
|
+
|
|
183
|
+
setAsset(relative(conf.root, owner ?? fileName), '/'+ dest)
|
|
184
|
+
} else if (out.kind === 'entry-point' && owner) {
|
|
185
|
+
setChunk(relative(conf.root, owner),'/'+ dest)
|
|
186
|
+
// o lookup original usa o caminho relativo ao root
|
|
187
|
+
// if (owner !== appEntry) setChunk(owner.replace(conf.root, ''), name)
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
const imports = getImports(content)
|
|
191
|
+
for (const path of imports) {
|
|
192
|
+
content = content.replaceAll(path, '/'+ path.split('/').at(-1))
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
await Bun.write(filePath, content)
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
for (const [_filePath, page] of metadata.pages)
|
|
199
|
+
await renderPage(metadata, conf, page)
|
|
200
|
+
|
|
201
|
+
rmSync(join(conf.root, 'tmp'), { recursive: true, force: true })
|
|
202
|
+
|
|
203
|
+
if (existsSync(join(conf.root, 'public')))
|
|
204
|
+
await cp(join(conf.root, 'public'), outPath, { recursive: true, force: true })
|
|
205
|
+
|
|
206
|
+
const entryPath = join(outPath, getChunk(APP_ID))
|
|
207
|
+
const entry = Bun.file(entryPath)
|
|
208
|
+
|
|
209
|
+
if (await entry.exists()) {
|
|
210
|
+
let entryContent = await entry.text()
|
|
211
|
+
|
|
212
|
+
for (const r of routes) {
|
|
213
|
+
const rPath = getChunk(r.fullFilePath.replace(conf.root, ''))
|
|
214
|
+
|
|
215
|
+
if (r.props) {
|
|
216
|
+
const rFilePath = join(outPath, rPath)
|
|
217
|
+
const rText = await Bun.file(rFilePath).text()
|
|
218
|
+
await Bun.write(rFilePath, `export const props=${JSJSON(r.props)};${rText}`)
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
entryContent = entryContent.replace(r.fullFilePath, rPath)
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
await Bun.write(entryPath, entryContent)
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
// i18n
|
|
228
|
+
for (const locale of metadata.i18n.locales) {
|
|
229
|
+
const intlPath = join(conf.root, 'i18n', locale + '.ts')
|
|
230
|
+
if (!existsSync(intlPath)) continue
|
|
231
|
+
|
|
232
|
+
const intlMod = await import(intlPath)
|
|
233
|
+
await Bun.write(
|
|
234
|
+
join(outPath, locale + '.js'),
|
|
235
|
+
`var a=${JSJSON({
|
|
236
|
+
routes: intlMod.routes ?? intlMod.default?.routes,
|
|
237
|
+
messages: intlMod.messages ?? intlMod.default?.messages,
|
|
238
|
+
})};export{a as default}`,
|
|
239
|
+
)
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
return result
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
export default config
|
|
@@ -12,15 +12,15 @@ import { getAsset, getChunk } from './assets'
|
|
|
12
12
|
import { resolveStaticOutputPath } from './meta'
|
|
13
13
|
import { setLocale } from '../i18n/client'
|
|
14
14
|
|
|
15
|
-
import type { ResolvedConfig } from 'vite'
|
|
16
15
|
import type { IRoute, Metadata, RouteParams, StaticProps, SummaryMetadata } from './types'
|
|
16
|
+
import type { DutoOptions } from './bun-plugin'
|
|
17
17
|
|
|
18
18
|
|
|
19
19
|
const brCompress = promisify(brotliCompress)
|
|
20
20
|
|
|
21
21
|
export async function renderPage(
|
|
22
22
|
meta: Metadata,
|
|
23
|
-
config:
|
|
23
|
+
config: DutoOptions,
|
|
24
24
|
page: IRoute
|
|
25
25
|
) {
|
|
26
26
|
const {
|
|
@@ -117,7 +117,7 @@ export async function renderPage(
|
|
|
117
117
|
|
|
118
118
|
html = buildHtml(page, html, Object.keys(allProps).length ? allProps : undefined)
|
|
119
119
|
|
|
120
|
-
const outFile = join(config.root, config.
|
|
120
|
+
const outFile = join(config.root, config.outDir, pathname +'.html')
|
|
121
121
|
const outHTML = meta.minify ? minify.minify(Buffer.from(html), {}) : html
|
|
122
122
|
await mkdir(dirname(outFile), {recursive: true})
|
|
123
123
|
await Bun.write(outFile, outHTML)
|
|
@@ -1,336 +0,0 @@
|
|
|
1
|
-
// REF: https://github.com/tailwindlabs/tailwindcss/blob/main/packages/%40tailwindcss-vite/src/index.ts
|
|
2
|
-
|
|
3
|
-
import { existsSync, rmSync } from 'node:fs'
|
|
4
|
-
import { join } from 'node:path'
|
|
5
|
-
import { defineConfig } from 'vite'
|
|
6
|
-
|
|
7
|
-
import preact from '@preact/preset-vite'
|
|
8
|
-
import tailwind from '@tailwindcss/vite'
|
|
9
|
-
import { JSJSON } from 't0n'
|
|
10
|
-
|
|
11
|
-
import { EXT_RE } from './constants'
|
|
12
|
-
import { getChunk, setAsset, setChunk, setEntry } from './assets'
|
|
13
|
-
import { renderPage } from '.'
|
|
14
|
-
import { removeExports, resolveImport } from './ast'
|
|
15
|
-
import { getMetadata } from './meta'
|
|
16
|
-
|
|
17
|
-
import type {
|
|
18
|
-
UserConfig, ConfigEnv, ResolvedConfig,
|
|
19
|
-
// PreviewServer,
|
|
20
|
-
// ViteDevServer,
|
|
21
|
-
EnvironmentOptions,
|
|
22
|
-
PluginOption,
|
|
23
|
-
} from 'vite'
|
|
24
|
-
import type { Metadata, PluginOptions } from './types'
|
|
25
|
-
import { _pages, _root } from '@/utils'
|
|
26
|
-
import { cp } from 'node:fs/promises'
|
|
27
|
-
|
|
28
|
-
const VIRTUAL_ID = ':duto'
|
|
29
|
-
const RESOLVED_ID = '\0'+ VIRTUAL_ID
|
|
30
|
-
|
|
31
|
-
export const config = (opts: UserConfig & PluginOptions) => { // @ts-ignore
|
|
32
|
-
if (!opts.root) opts.root = _root
|
|
33
|
-
if (!opts.pagesDir) opts.pagesDir = 'pages',
|
|
34
|
-
|
|
35
|
-
opts.logLevel = 'silent'
|
|
36
|
-
opts.plugins = [
|
|
37
|
-
...(opts.plugins || []),
|
|
38
|
-
preact(),
|
|
39
|
-
tailwind(),
|
|
40
|
-
plugin(opts),
|
|
41
|
-
]
|
|
42
|
-
|
|
43
|
-
return defineConfig(opts)
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
// TODO: remover..
|
|
47
|
-
export default config
|
|
48
|
-
|
|
49
|
-
let metadata: Metadata
|
|
50
|
-
export function meta() {
|
|
51
|
-
return metadata
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
export function plugin(options: PluginOptions = {}): PluginOption[] {
|
|
55
|
-
let config: ResolvedConfig
|
|
56
|
-
const {
|
|
57
|
-
root = _root,
|
|
58
|
-
pagesDir = 'pages',
|
|
59
|
-
} = options
|
|
60
|
-
|
|
61
|
-
const PAGES_RE = new RegExp(`\/${pagesDir}\/.*\.(tsx?|jsx?)$`, 'i')
|
|
62
|
-
const stripTarget = new Set(['getStaticProps'])
|
|
63
|
-
|
|
64
|
-
return [
|
|
65
|
-
{
|
|
66
|
-
name: 'duto:preact-environment',
|
|
67
|
-
|
|
68
|
-
configEnvironment(name, options) {
|
|
69
|
-
const opts: EnvironmentOptions = {optimizeDeps: {}, resolve: {}}
|
|
70
|
-
|
|
71
|
-
if (name === 'client') {
|
|
72
|
-
opts.optimizeDeps!.include = [
|
|
73
|
-
'preact',
|
|
74
|
-
'preact/jsx-runtime',
|
|
75
|
-
]
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
opts.resolve = {dedupe: ['preact/compat', 'preact']}
|
|
79
|
-
|
|
80
|
-
if (name === 'client')
|
|
81
|
-
opts.optimizeDeps!.include!.push(
|
|
82
|
-
'preact/compat',
|
|
83
|
-
// 'preact/test-utils',
|
|
84
|
-
'preact/compat/jsx-runtime',
|
|
85
|
-
)
|
|
86
|
-
|
|
87
|
-
if (
|
|
88
|
-
!options.resolve?.noExternal &&
|
|
89
|
-
(name === 'ssr' || name === 'prerender')
|
|
90
|
-
) {
|
|
91
|
-
// noExternal React entrypoints to be bundled, resolved, and aliased by Vite
|
|
92
|
-
opts.resolve!.noExternal = [
|
|
93
|
-
'react',
|
|
94
|
-
'react-dom',
|
|
95
|
-
'react-dom/test-utils',
|
|
96
|
-
'react/jsx-runtime',
|
|
97
|
-
]
|
|
98
|
-
}
|
|
99
|
-
|
|
100
|
-
return opts
|
|
101
|
-
},
|
|
102
|
-
},
|
|
103
|
-
|
|
104
|
-
{
|
|
105
|
-
name: 'duto:scan',
|
|
106
|
-
enforce: 'pre',
|
|
107
|
-
|
|
108
|
-
async config(conf: UserConfig, env: ConfigEnv) {
|
|
109
|
-
metadata = await getMetadata(root as string, pagesDir)
|
|
110
|
-
|
|
111
|
-
if (conf.build) {
|
|
112
|
-
// conf.build.emptyOutDir = false
|
|
113
|
-
conf.build.copyPublicDir = false
|
|
114
|
-
conf.build.manifest = false
|
|
115
|
-
conf.build.modulePreload = { polyfill: false }
|
|
116
|
-
|
|
117
|
-
conf.build.minify = metadata.minify
|
|
118
|
-
conf.build.cssMinify = metadata.minify
|
|
119
|
-
|
|
120
|
-
conf.build.rolldownOptions = conf.build.rolldownOptions || {}
|
|
121
|
-
conf.build.rolldownOptions.preserveEntrySignatures = 'exports-only' ///
|
|
122
|
-
conf.build.rolldownOptions.input = [
|
|
123
|
-
...metadata.routes.map(r => r.is404 ? '' : r.fullFilePath).filter(Boolean),
|
|
124
|
-
':duto/client/app.tsx',
|
|
125
|
-
// ':duto/not-found.tsx',
|
|
126
|
-
// ...Object.keys(metadata.pages),
|
|
127
|
-
// ...metadata.assets,
|
|
128
|
-
]
|
|
129
|
-
|
|
130
|
-
const filePrefix = metadata.minify ? '' : '[name].'
|
|
131
|
-
// conf.build.rolldownOptions.input = ':duto/client/app.tsx'
|
|
132
|
-
conf.build.rolldownOptions.output = {
|
|
133
|
-
hoistTransitiveImports: false,
|
|
134
|
-
format: 'esm',
|
|
135
|
-
minifyInternalExports: metadata.minify,
|
|
136
|
-
preserveModulesRoot: '/',
|
|
137
|
-
assetFileNames: filePrefix +'[hash][extname]',
|
|
138
|
-
entryFileNames: filePrefix +'[hash].js',
|
|
139
|
-
chunkFileNames: filePrefix +'[hash].js',
|
|
140
|
-
|
|
141
|
-
// chunkFileNames: '[name].[hash].js',
|
|
142
|
-
// entryFileNames: 'assets/[name].js',
|
|
143
|
-
// chunkFileNames: 'assets/chunks/[name]-[hash].js',
|
|
144
|
-
// assetFileNames: 'assets/[name][extname]',
|
|
145
|
-
|
|
146
|
-
// manualChunks: {
|
|
147
|
-
// vendor: ['preact', '@preact/signals', 'wouter-preact'],
|
|
148
|
-
// },
|
|
149
|
-
// preserveModules: true,
|
|
150
|
-
}
|
|
151
|
-
}
|
|
152
|
-
|
|
153
|
-
return conf
|
|
154
|
-
},
|
|
155
|
-
|
|
156
|
-
async configResolved(c) {
|
|
157
|
-
config = c
|
|
158
|
-
},
|
|
159
|
-
|
|
160
|
-
resolveId(id) {
|
|
161
|
-
if (id === VIRTUAL_ID) return RESOLVED_ID
|
|
162
|
-
|
|
163
|
-
if (id.startsWith(':root')) {
|
|
164
|
-
for (const f of [
|
|
165
|
-
'style.css',
|
|
166
|
-
'styles.css',
|
|
167
|
-
'styles/index.css',
|
|
168
|
-
'style/index.css',
|
|
169
|
-
'tailwind.css',
|
|
170
|
-
'tailwindcss.css',
|
|
171
|
-
'styles/tailwind.css',
|
|
172
|
-
'styles/tailwindcss.css',
|
|
173
|
-
]) {
|
|
174
|
-
const path = join(config.root, f)
|
|
175
|
-
if (existsSync(path)) {
|
|
176
|
-
setEntry(id, path)
|
|
177
|
-
return path
|
|
178
|
-
}
|
|
179
|
-
}
|
|
180
|
-
|
|
181
|
-
return null
|
|
182
|
-
}
|
|
183
|
-
|
|
184
|
-
if (!id.startsWith(VIRTUAL_ID)) return null
|
|
185
|
-
|
|
186
|
-
if (EXT_RE.test(id) && id.includes('/')) {
|
|
187
|
-
const path = join(_pages, id.replace(VIRTUAL_ID, ''))
|
|
188
|
-
setEntry(id, path)
|
|
189
|
-
return path
|
|
190
|
-
}
|
|
191
|
-
|
|
192
|
-
if (id.endsWith('/hmr') && metadata.hmr)
|
|
193
|
-
return join(_pages, 'client', id.replace(VIRTUAL_ID, '') + '.ts')
|
|
194
|
-
|
|
195
|
-
return id.replace(VIRTUAL_ID, RESOLVED_ID)
|
|
196
|
-
},
|
|
197
|
-
|
|
198
|
-
async load(id) {
|
|
199
|
-
if (!id.startsWith(RESOLVED_ID)) return
|
|
200
|
-
|
|
201
|
-
if (id.endsWith('/i18n'))
|
|
202
|
-
return metadata.i18n.string
|
|
203
|
-
|
|
204
|
-
return metadata.string
|
|
205
|
-
},
|
|
206
|
-
|
|
207
|
-
generateBundle(_, bundle) {
|
|
208
|
-
// const moduleIds = this.getModuleIds()
|
|
209
|
-
// for (const id of moduleIds)
|
|
210
|
-
// console.error(id)
|
|
211
|
-
|
|
212
|
-
// TODO: rever isso...
|
|
213
|
-
for (const key in bundle) {
|
|
214
|
-
const file = bundle[key]
|
|
215
|
-
|
|
216
|
-
if (file.type === 'chunk' && file.isEntry) {
|
|
217
|
-
setChunk(file.facadeModuleId, '/'+ file.fileName)
|
|
218
|
-
continue
|
|
219
|
-
}
|
|
220
|
-
|
|
221
|
-
if (file.type === 'asset' && file?.originalFileName?.endsWith('duto/pages/client/app.tsx'))
|
|
222
|
-
file.source += 'i-i{display:contents}'
|
|
223
|
-
|
|
224
|
-
if (file.type === 'asset' && file.fileName.endsWith('.css')) {
|
|
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, '')
|
|
232
|
-
continue
|
|
233
|
-
}
|
|
234
|
-
|
|
235
|
-
// console.error(file.fileName)
|
|
236
|
-
// console.error(file.type)
|
|
237
|
-
// console.error(file.viteMetadata)
|
|
238
|
-
// if (file.type !== 'chunk' || !file.isEntry) continue
|
|
239
|
-
}
|
|
240
|
-
},
|
|
241
|
-
|
|
242
|
-
},
|
|
243
|
-
|
|
244
|
-
{
|
|
245
|
-
name: 'duto:build',
|
|
246
|
-
enforce: 'pre',
|
|
247
|
-
apply: 'build',
|
|
248
|
-
|
|
249
|
-
async buildStart() {
|
|
250
|
-
// if (!server) {
|
|
251
|
-
// const { createServer } = await import('vite')
|
|
252
|
-
// server = await createServer({ server: {middlewareMode: true} })
|
|
253
|
-
// }
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
// teste de import de assets
|
|
257
|
-
// for (const file of metadata.assets) {
|
|
258
|
-
// const id = await this.resolve(file)
|
|
259
|
-
// if (!id) continue
|
|
260
|
-
|
|
261
|
-
// console.error(id)
|
|
262
|
-
// console.error(relative(metadata.root, file).replace(/\.(ts|tsx)$/, '.js'),)
|
|
263
|
-
|
|
264
|
-
// this.emitFile({
|
|
265
|
-
// type: 'chunk',
|
|
266
|
-
// id: id.id,
|
|
267
|
-
// implicitlyLoadedAfterOneOf: [
|
|
268
|
-
// ':duto/app.tsx',
|
|
269
|
-
// ]
|
|
270
|
-
// // fileName: 'assets/[name]-[hash][extname]',
|
|
271
|
-
// // fileName: relative(metadata.root, file).replace(/\.(ts|tsx)$/, '.js'),
|
|
272
|
-
// })
|
|
273
|
-
// }
|
|
274
|
-
|
|
275
|
-
},
|
|
276
|
-
|
|
277
|
-
async closeBundle() {
|
|
278
|
-
for (const [_filePath, page] of metadata.pages)
|
|
279
|
-
await renderPage(metadata, config, page)
|
|
280
|
-
// const mod = await server.ssrLoadModule(_filePath)
|
|
281
|
-
|
|
282
|
-
rmSync(join(metadata.root, 'tmp'), {recursive: true, force: true})
|
|
283
|
-
// await server.close()
|
|
284
|
-
|
|
285
|
-
const outPath = join(config.root, config.build.outDir)
|
|
286
|
-
|
|
287
|
-
await cp(join(config.root, 'public'), outPath, { recursive: true, force: true })
|
|
288
|
-
|
|
289
|
-
const entry = Bun.file(join(outPath, getChunk(':duto/client/app.tsx')))
|
|
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, ''))
|
|
295
|
-
|
|
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
|
-
}
|
|
300
|
-
|
|
301
|
-
entryContent = entryContent.replace(r.fullFilePath, rPath)
|
|
302
|
-
}
|
|
303
|
-
|
|
304
|
-
await entry.write(entryContent)
|
|
305
|
-
}
|
|
306
|
-
|
|
307
|
-
// i18n
|
|
308
|
-
for (const locale of metadata.i18n.locales) {
|
|
309
|
-
const intlPath = join(metadata.root, 'i18n', locale + '.ts')
|
|
310
|
-
if (existsSync(intlPath)) {
|
|
311
|
-
const intlMod = await import(intlPath)
|
|
312
|
-
await Bun.write(
|
|
313
|
-
join(outPath, locale +'.js'),
|
|
314
|
-
`var a=${JSJSON({
|
|
315
|
-
routes: intlMod.routes ?? intlMod.default?.routes,
|
|
316
|
-
messages: intlMod.messages ?? intlMod.default?.messages,
|
|
317
|
-
})};export { a as default }`
|
|
318
|
-
)
|
|
319
|
-
}
|
|
320
|
-
}
|
|
321
|
-
|
|
322
|
-
},
|
|
323
|
-
|
|
324
|
-
// TODO: precisa ser aqui mesmo pois se a pagina for importada no entry, pode incluir codigo nao usado no chunck/bundle
|
|
325
|
-
async transform(code, id) {
|
|
326
|
-
if (!PAGES_RE.test(id) || id.includes('node_modules')) return null
|
|
327
|
-
// console.log(this.getModuleInfo(id))
|
|
328
|
-
|
|
329
|
-
const stripped = removeExports(id, code, stripTarget)
|
|
330
|
-
|
|
331
|
-
return (!stripped || stripped === code) ? null : { code: stripped, map: null }
|
|
332
|
-
},
|
|
333
|
-
|
|
334
|
-
},
|
|
335
|
-
]
|
|
336
|
-
}
|