rebuildjs 0.70.46 → 0.71.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 +10 -2
- package/server/build/index.js +10 -2
- package/server/export/cloudflare/index.d.ts +37 -0
- package/server/export/cloudflare/index.js +108 -0
- package/server/export/index.d.ts +29 -0
- package/server/export/index.js +168 -0
- package/server/index.d.ts +2 -0
- package/server/index.js +2 -0
- package/server/route/index.d.ts +12 -0
- package/server/route/index.js +52 -0
- package/server/static/index.d.ts +11 -0
- package/server/static/index.js +37 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "rebuildjs",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.71.0",
|
|
4
4
|
"description": "Reactive esbuild...simple hackable alternative to vite for Multi Page Apps",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"reactive",
|
|
@@ -51,6 +51,14 @@
|
|
|
51
51
|
"types": "./server/index.d.ts",
|
|
52
52
|
"default": "./server/index.js"
|
|
53
53
|
},
|
|
54
|
+
"./server/export": {
|
|
55
|
+
"types": "./server/export/index.d.ts",
|
|
56
|
+
"default": "./server/export/index.js"
|
|
57
|
+
},
|
|
58
|
+
"./server/export/cloudflare": {
|
|
59
|
+
"types": "./server/export/cloudflare/index.d.ts",
|
|
60
|
+
"default": "./server/export/cloudflare/index.js"
|
|
61
|
+
},
|
|
54
62
|
"./package.json": "./package.json"
|
|
55
63
|
},
|
|
56
64
|
"scripts": {
|
|
@@ -65,7 +73,6 @@
|
|
|
65
73
|
},
|
|
66
74
|
"dependencies": {
|
|
67
75
|
"ctx-core": "*",
|
|
68
|
-
"elysia": "^1.4.27",
|
|
69
76
|
"fdir": "^6.5.0",
|
|
70
77
|
"picomatch": "^4.0.3"
|
|
71
78
|
},
|
|
@@ -75,6 +82,7 @@
|
|
|
75
82
|
"devDependencies": {
|
|
76
83
|
"@typescript-eslint/eslint-plugin": "^8.56.1",
|
|
77
84
|
"@typescript-eslint/parser": "^8.56.1",
|
|
85
|
+
"bun-types": "^1.3.10",
|
|
78
86
|
"c8": "^11.0.0",
|
|
79
87
|
"eslint": "^10.0.2",
|
|
80
88
|
"esmock": "^2.7.3",
|
package/server/build/index.js
CHANGED
|
@@ -276,6 +276,7 @@ export async function rebuildjs_browser__build(config) {
|
|
|
276
276
|
sourcemap: 'external',
|
|
277
277
|
loader: default_loader,
|
|
278
278
|
publicPath: '/',
|
|
279
|
+
write: true,
|
|
279
280
|
...esbuild__config,
|
|
280
281
|
entryPoints,
|
|
281
282
|
format: 'esm',
|
|
@@ -292,7 +293,10 @@ export async function rebuildjs_browser__build(config) {
|
|
|
292
293
|
console.log('browser__build|watch')
|
|
293
294
|
return esbuild_ctx
|
|
294
295
|
} else {
|
|
295
|
-
await build(esbuild_config)
|
|
296
|
+
const result = await build(esbuild_config)
|
|
297
|
+
if (!result.metafile) {
|
|
298
|
+
throw new Error('rebuildjs_browser__build: esbuild.build() produced no metafile')
|
|
299
|
+
}
|
|
296
300
|
return undefined
|
|
297
301
|
}
|
|
298
302
|
}
|
|
@@ -333,6 +337,7 @@ export async function rebuildjs_server__build(config) {
|
|
|
333
337
|
sourcemap: 'external',
|
|
334
338
|
loader: default_loader,
|
|
335
339
|
publicPath: '/',
|
|
340
|
+
write: true,
|
|
336
341
|
...esbuild__config,
|
|
337
342
|
entryPoints,
|
|
338
343
|
format: 'esm',
|
|
@@ -349,7 +354,10 @@ export async function rebuildjs_server__build(config) {
|
|
|
349
354
|
console.log('server__build|watch')
|
|
350
355
|
return esbuild_ctx
|
|
351
356
|
} else {
|
|
352
|
-
await build(esbuild_config)
|
|
357
|
+
const result = await build(esbuild_config)
|
|
358
|
+
if (!result.metafile) {
|
|
359
|
+
throw new Error('rebuildjs_server__build: esbuild.build() produced no metafile')
|
|
360
|
+
}
|
|
353
361
|
return undefined
|
|
354
362
|
}
|
|
355
363
|
}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
/// <reference types="bun-types" />
|
|
2
|
+
import type { static_export_config_T, static_export_result_T } from '../index.js'
|
|
3
|
+
export declare function cloudflare_export_(
|
|
4
|
+
config:cloudflare_export_config_T,
|
|
5
|
+
static_export_:(config:static_export_config_T, app__start:(app?:any)=>Promise<any>)=>Promise<static_export_result_T>,
|
|
6
|
+
app__start:(app?:any)=>Promise<any>
|
|
7
|
+
):Promise<cloudflare_export_result_T>
|
|
8
|
+
export declare function worker_entry__generate_(
|
|
9
|
+
dynamic_routes:route_handler_T[]
|
|
10
|
+
):string
|
|
11
|
+
export declare function wrangler_toml__generate_(
|
|
12
|
+
overrides:Partial<wrangler_config_T>,
|
|
13
|
+
out_dir?:string
|
|
14
|
+
):string
|
|
15
|
+
export type cloudflare_export_config_T =
|
|
16
|
+
& static_export_config_T
|
|
17
|
+
& {
|
|
18
|
+
dynamic_routes?:route_handler_T[]
|
|
19
|
+
worker_out?:string
|
|
20
|
+
wrangler?:Partial<wrangler_config_T>
|
|
21
|
+
}
|
|
22
|
+
export type cloudflare_export_result_T =
|
|
23
|
+
& static_export_result_T
|
|
24
|
+
& {
|
|
25
|
+
worker_entry_path?:string
|
|
26
|
+
wrangler_path:string
|
|
27
|
+
}
|
|
28
|
+
export type route_handler_T = {
|
|
29
|
+
pattern:string
|
|
30
|
+
handler:string
|
|
31
|
+
}
|
|
32
|
+
export type wrangler_config_T = {
|
|
33
|
+
name:string
|
|
34
|
+
compatibility_date:string
|
|
35
|
+
vars?:Record<string, string>
|
|
36
|
+
routes?:{ pattern:string, zone_name?:string }[]
|
|
37
|
+
}
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
/// <reference types="bun-types" />
|
|
2
|
+
/// <reference types="./index.d.ts" />
|
|
3
|
+
import { build } from 'esbuild'
|
|
4
|
+
import { mkdir, writeFile } from 'node:fs/promises'
|
|
5
|
+
import { join, resolve } from 'node:path'
|
|
6
|
+
/**
|
|
7
|
+
* @param {cloudflare_export_config_T} config
|
|
8
|
+
* @param {(config:import('../index.js').static_export_config_T, app__start:(app?:any)=>Promise<any>)=>Promise<import('../index.js').static_export_result_T>} static_export_
|
|
9
|
+
* @param {(app?:any)=>Promise<any>} app__start
|
|
10
|
+
* @returns {Promise<cloudflare_export_result_T>}
|
|
11
|
+
*/
|
|
12
|
+
export async function cloudflare_export_(config, static_export_, app__start) {
|
|
13
|
+
const {
|
|
14
|
+
dynamic_routes = [],
|
|
15
|
+
worker_out = 'dist/worker',
|
|
16
|
+
wrangler = {},
|
|
17
|
+
...static_config
|
|
18
|
+
} = config
|
|
19
|
+
// 1. Run static export
|
|
20
|
+
const static_result = await static_export_(static_config, app__start)
|
|
21
|
+
// 2. Generate and bundle worker if dynamic routes exist
|
|
22
|
+
let worker_entry_path
|
|
23
|
+
if (dynamic_routes.length > 0) {
|
|
24
|
+
const entry_source = worker_entry__generate_(dynamic_routes)
|
|
25
|
+
const generated_path = join(worker_out, '_worker.src.js')
|
|
26
|
+
await mkdir(worker_out, { recursive: true })
|
|
27
|
+
await writeFile(generated_path, entry_source)
|
|
28
|
+
// Bundle with esbuild for Cloudflare Workers runtime
|
|
29
|
+
worker_entry_path = join(worker_out, '_worker.js')
|
|
30
|
+
await build({
|
|
31
|
+
entryPoints: [generated_path],
|
|
32
|
+
bundle: true,
|
|
33
|
+
format: 'esm',
|
|
34
|
+
target: 'es2022',
|
|
35
|
+
platform: 'browser',
|
|
36
|
+
outfile: worker_entry_path,
|
|
37
|
+
minify: true,
|
|
38
|
+
})
|
|
39
|
+
console.info(`[cloudflare_export] worker bundled: ${worker_entry_path}`)
|
|
40
|
+
}
|
|
41
|
+
// 3. Generate wrangler.toml
|
|
42
|
+
const wrangler_config = wrangler_toml__generate_(wrangler, static_config.out_dir)
|
|
43
|
+
const wrangler_path = 'wrangler.toml'
|
|
44
|
+
await writeFile(wrangler_path, wrangler_config)
|
|
45
|
+
console.info(`[cloudflare_export] wrote ${wrangler_path}`)
|
|
46
|
+
return {
|
|
47
|
+
...static_result,
|
|
48
|
+
worker_entry_path,
|
|
49
|
+
wrangler_path,
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
/**
|
|
53
|
+
* @param {route_handler_T[]} dynamic_routes
|
|
54
|
+
* @returns {string}
|
|
55
|
+
*/
|
|
56
|
+
export function worker_entry__generate_(dynamic_routes) {
|
|
57
|
+
const imports = []
|
|
58
|
+
const route_checks = []
|
|
59
|
+
for (let i = 0; i < dynamic_routes.length; i++) {
|
|
60
|
+
const { pattern, handler } = dynamic_routes[i]
|
|
61
|
+
const handler_name = `handler_${i}`
|
|
62
|
+
imports.push(`import ${handler_name} from '${resolve(handler)}'`)
|
|
63
|
+
if (pattern.endsWith('/*')) {
|
|
64
|
+
const prefix = pattern.slice(0, -2)
|
|
65
|
+
route_checks.push(
|
|
66
|
+
`\tif (url.pathname.startsWith('${prefix}')) return ${handler_name}(request, env, ctx)`)
|
|
67
|
+
} else {
|
|
68
|
+
route_checks.push(
|
|
69
|
+
`\tif (url.pathname === '${pattern}') return ${handler_name}(request, env, ctx)`)
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
return `${imports.join('\n')}
|
|
73
|
+
|
|
74
|
+
export default {
|
|
75
|
+
async fetch(request, env, ctx) {
|
|
76
|
+
const url = new URL(request.url)
|
|
77
|
+
${route_checks.join('\n')}
|
|
78
|
+
return env.ASSETS.fetch(request)
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
`
|
|
82
|
+
}
|
|
83
|
+
/**
|
|
84
|
+
* @param {Partial<wrangler_config_T>} overrides
|
|
85
|
+
* @param {string} [out_dir]
|
|
86
|
+
* @returns {string}
|
|
87
|
+
*/
|
|
88
|
+
export function wrangler_toml__generate_(overrides, out_dir = 'dist/browser') {
|
|
89
|
+
const name = overrides.name || 'app'
|
|
90
|
+
const compatibility_date = overrides.compatibility_date || new Date().toISOString().slice(0, 10)
|
|
91
|
+
let toml = `name = "${name}"
|
|
92
|
+
compatibility_date = "${compatibility_date}"
|
|
93
|
+
pages_build_output_dir = "${out_dir}"
|
|
94
|
+
`
|
|
95
|
+
if (overrides.vars) {
|
|
96
|
+
toml += '\n[vars]\n'
|
|
97
|
+
for (const [key, val] of Object.entries(overrides.vars)) {
|
|
98
|
+
toml += `${key} = "${val}"\n`
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
if (overrides.routes) {
|
|
102
|
+
for (const route of overrides.routes) {
|
|
103
|
+
toml += `\n[[routes]]\npattern = "${route.pattern}"\n`
|
|
104
|
+
if (route.zone_name) toml += `zone_name = "${route.zone_name}"\n`
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
return toml
|
|
108
|
+
}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
/// <reference types="bun-types" />
|
|
2
|
+
export declare function static_export_(
|
|
3
|
+
config:static_export_config_T,
|
|
4
|
+
app__start:(app?:any)=>Promise<any>
|
|
5
|
+
):Promise<static_export_result_T>
|
|
6
|
+
export declare function static_export__file_path_(
|
|
7
|
+
route:string,
|
|
8
|
+
out_dir:string,
|
|
9
|
+
content_type?:string
|
|
10
|
+
):string
|
|
11
|
+
export type static_export_config_T = {
|
|
12
|
+
routes?:string[]
|
|
13
|
+
site_url:string
|
|
14
|
+
out_dir?:string
|
|
15
|
+
base_url?:string
|
|
16
|
+
server_import?:string
|
|
17
|
+
app?:any
|
|
18
|
+
sitemap?:boolean
|
|
19
|
+
extra_routes?:string[]
|
|
20
|
+
url_rewrite?:boolean
|
|
21
|
+
incremental?:boolean
|
|
22
|
+
manifest?:boolean
|
|
23
|
+
clean?:boolean
|
|
24
|
+
on_export?:(route:string, file:string)=>void
|
|
25
|
+
}
|
|
26
|
+
export type static_export_result_T = {
|
|
27
|
+
exported:string[]
|
|
28
|
+
errors:string[]
|
|
29
|
+
}
|
|
@@ -0,0 +1,168 @@
|
|
|
1
|
+
/// <reference types="bun-types" />
|
|
2
|
+
/// <reference types="./index.d.ts" />
|
|
3
|
+
import { mkdir, readFile, rm, unlink, writeFile } from 'node:fs/promises'
|
|
4
|
+
import { dirname, extname, join } from 'node:path'
|
|
5
|
+
import { port_ } from '../app/index.js'
|
|
6
|
+
import { app_ctx } from '../ctx/index.js'
|
|
7
|
+
/**
|
|
8
|
+
* @param {static_export_config_T} config
|
|
9
|
+
* @param {(app?:any)=>Promise<any>} app__start
|
|
10
|
+
* @returns {Promise<static_export_result_T>}
|
|
11
|
+
*/
|
|
12
|
+
export async function static_export_(config, app__start) {
|
|
13
|
+
const {
|
|
14
|
+
site_url,
|
|
15
|
+
out_dir = 'dist/browser',
|
|
16
|
+
server_import = './dist/server/index.js',
|
|
17
|
+
sitemap = true,
|
|
18
|
+
url_rewrite = true,
|
|
19
|
+
incremental = false,
|
|
20
|
+
manifest: use_manifest = incremental,
|
|
21
|
+
clean = false,
|
|
22
|
+
on_export,
|
|
23
|
+
} = config
|
|
24
|
+
let { routes = [], extra_routes = [] } = config
|
|
25
|
+
const site_origin = site_url.replace(/\/$/, '')
|
|
26
|
+
const exported = []
|
|
27
|
+
const errors = []
|
|
28
|
+
let app = config.app
|
|
29
|
+
let we_started = false
|
|
30
|
+
let base_url = config.base_url
|
|
31
|
+
try {
|
|
32
|
+
if (base_url) {
|
|
33
|
+
// Connect to already-running external server
|
|
34
|
+
base_url = base_url.replace(/\/$/, '')
|
|
35
|
+
console.info(`[static_export] using external server: ${base_url}`)
|
|
36
|
+
} else if (!app) {
|
|
37
|
+
// Start server internally
|
|
38
|
+
const mod = await import(server_import)
|
|
39
|
+
app = typeof mod.default === 'function' ? await mod.default() : mod.default
|
|
40
|
+
await app__start(app)
|
|
41
|
+
we_started = true
|
|
42
|
+
base_url = `http://localhost:${port_(app_ctx)}`
|
|
43
|
+
console.info(`[static_export] server running on ${base_url}`)
|
|
44
|
+
} else {
|
|
45
|
+
base_url = `http://localhost:${port_(app_ctx)}`
|
|
46
|
+
console.info(`[static_export] using provided app on ${base_url}`)
|
|
47
|
+
}
|
|
48
|
+
console.info(`[static_export] site_url: ${site_origin}`)
|
|
49
|
+
// Clean output directory if requested
|
|
50
|
+
if (clean) {
|
|
51
|
+
await rm(out_dir, { recursive: true, force: true })
|
|
52
|
+
}
|
|
53
|
+
// Discover routes from sitemap
|
|
54
|
+
if (sitemap) {
|
|
55
|
+
try {
|
|
56
|
+
const res = await fetch(`${base_url}/sitemap.xml`)
|
|
57
|
+
if (res.ok) {
|
|
58
|
+
const xml = await res.text()
|
|
59
|
+
for (const m of xml.matchAll(/<loc>https?:\/\/[^<]+<\/loc>/g)) {
|
|
60
|
+
const url = m[0].replace(/<\/?loc>/g, '')
|
|
61
|
+
const path = new URL(url).pathname
|
|
62
|
+
const route = path === '' ? '/' : path
|
|
63
|
+
if (!routes.includes(route)) {
|
|
64
|
+
routes.push(route)
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
console.info(`[static_export] discovered ${routes.length} routes from sitemap`)
|
|
68
|
+
}
|
|
69
|
+
} catch {
|
|
70
|
+
console.warn('[static_export] sitemap fetch failed, using explicit routes only')
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
// Load previous manifest for stale file cleanup
|
|
74
|
+
const manifest_path = join(out_dir, '.export-manifest.json')
|
|
75
|
+
let prev_manifest = []
|
|
76
|
+
if (use_manifest) {
|
|
77
|
+
try {
|
|
78
|
+
prev_manifest = JSON.parse(await readFile(manifest_path, 'utf-8'))
|
|
79
|
+
} catch {
|
|
80
|
+
// No previous manifest
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
// Merge routes and extra_routes
|
|
84
|
+
const all_routes = [...routes, ...extra_routes]
|
|
85
|
+
// Export each route (streaming — write as we go)
|
|
86
|
+
for (const route of all_routes) {
|
|
87
|
+
const url = base_url + route
|
|
88
|
+
try {
|
|
89
|
+
const res = await fetch(url)
|
|
90
|
+
if (!res.ok) {
|
|
91
|
+
console.error(`[static_export] ${route} -> ${res.status} ${res.statusText}`)
|
|
92
|
+
errors.push(route)
|
|
93
|
+
continue
|
|
94
|
+
}
|
|
95
|
+
const content_type = res.headers.get('content-type') ?? ''
|
|
96
|
+
let body = await res.text()
|
|
97
|
+
if (url_rewrite) {
|
|
98
|
+
body = body.replaceAll(base_url, site_origin)
|
|
99
|
+
}
|
|
100
|
+
const file_path = static_export__file_path_(route, out_dir, content_type)
|
|
101
|
+
// Incremental: skip if content unchanged
|
|
102
|
+
if (incremental) {
|
|
103
|
+
try {
|
|
104
|
+
const existing = await readFile(file_path, 'utf-8')
|
|
105
|
+
if (existing === body) {
|
|
106
|
+
exported.push(file_path)
|
|
107
|
+
continue
|
|
108
|
+
}
|
|
109
|
+
} catch {
|
|
110
|
+
// File doesn't exist yet — write it
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
await mkdir(dirname(file_path), { recursive: true })
|
|
114
|
+
await writeFile(file_path, body)
|
|
115
|
+
exported.push(file_path)
|
|
116
|
+
on_export?.(route, file_path)
|
|
117
|
+
console.info(`[static_export] ${route} -> ${file_path} (${body.length} bytes)`)
|
|
118
|
+
} catch (err) {
|
|
119
|
+
console.error(`[static_export] ${route} -> ${err.message}`)
|
|
120
|
+
errors.push(route)
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
// Manifest: write new manifest and delete stale files
|
|
124
|
+
if (use_manifest) {
|
|
125
|
+
await mkdir(dirname(manifest_path), { recursive: true })
|
|
126
|
+
await writeFile(manifest_path, JSON.stringify(exported, null, '\t'))
|
|
127
|
+
const stale = prev_manifest.filter(f=>!exported.includes(f))
|
|
128
|
+
for (const file of stale) {
|
|
129
|
+
try {
|
|
130
|
+
await unlink(file)
|
|
131
|
+
console.info(`[static_export] deleted stale: ${file}`)
|
|
132
|
+
} catch {
|
|
133
|
+
// Already gone
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
if (errors.length > 0) {
|
|
138
|
+
console.warn(`[static_export] completed with ${errors.length} error(s)`)
|
|
139
|
+
} else {
|
|
140
|
+
console.info(`[static_export] exported ${exported.length} files`)
|
|
141
|
+
}
|
|
142
|
+
} finally {
|
|
143
|
+
if (we_started && app?.stop) {
|
|
144
|
+
app.stop()
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
return { exported, errors }
|
|
148
|
+
}
|
|
149
|
+
/**
|
|
150
|
+
* @param {string} route
|
|
151
|
+
* @param {string} out_dir
|
|
152
|
+
* @param {string} [content_type]
|
|
153
|
+
* @returns {string}
|
|
154
|
+
*/
|
|
155
|
+
export function static_export__file_path_(route, out_dir, content_type) {
|
|
156
|
+
const ext = extname(route)
|
|
157
|
+
// If the server responded with HTML content-type, treat as HTML page
|
|
158
|
+
const is_html = content_type ? content_type.includes('text/html') : !ext || ext === '.html'
|
|
159
|
+
if (ext && !is_html) {
|
|
160
|
+
// Raw file (robots.txt, sitemap.xml, rss.xml, etc.)
|
|
161
|
+
return join(out_dir, route)
|
|
162
|
+
}
|
|
163
|
+
// HTML route -> clean URL directory structure
|
|
164
|
+
if (route === '/') {
|
|
165
|
+
return join(out_dir, 'index.html')
|
|
166
|
+
}
|
|
167
|
+
return join(out_dir, route, 'index.html')
|
|
168
|
+
}
|
package/server/index.d.ts
CHANGED
package/server/index.js
CHANGED
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
/// <reference lib="dom" />
|
|
2
|
+
import type { middleware_ctx_T, request_ctx_T } from '../ctx/index.js'
|
|
3
|
+
export declare function html_route_<framework_ctx_T>(
|
|
4
|
+
middleware_ctx:middleware_ctx_T,
|
|
5
|
+
page_:($p:{ ctx:request_ctx_T })=>({ toString():string }|ReadableStream<string>),
|
|
6
|
+
request_ctx__ensure:(middleware_ctx:middleware_ctx_T, framework_ctx:framework_ctx_T)=>request_ctx_T,
|
|
7
|
+
response_init?:ResponseInit
|
|
8
|
+
):(framework_ctx:framework_ctx_T)=>Response
|
|
9
|
+
export declare function html_response__new(
|
|
10
|
+
html_OR_stream:string|ReadableStream,
|
|
11
|
+
response_init?:ResponseInit
|
|
12
|
+
):Response
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
/// <reference types="./index.d.ts" />
|
|
2
|
+
// TODO: use built-in TextEncoderStream when bunjs implements TextEncoderStream
|
|
3
|
+
// See https://github.com/oven-sh/bun/issues/5648
|
|
4
|
+
// See https://github.com/oven-sh/bun/issues/159
|
|
5
|
+
import { TextEncoderStream } from 'ctx-core/stream'
|
|
6
|
+
import { request_ctx__new } from '../ctx/index.js'
|
|
7
|
+
/**
|
|
8
|
+
* @param {middleware_ctx_T}middleware_ctx
|
|
9
|
+
* @param {($p:{ ctx:request_ctx_T })=>(string|ReadableStream<string|Uint8Array>)}page_
|
|
10
|
+
* @param {(middleware_ctx:middleware_ctx_T, framework_ctx:any)=>request_ctx_T}request_ctx__ensure
|
|
11
|
+
* @param {ResponseInit}[response_init]
|
|
12
|
+
* @returns {(framework_ctx:any)=>Response}
|
|
13
|
+
*/
|
|
14
|
+
export function html_route_(
|
|
15
|
+
middleware_ctx,
|
|
16
|
+
page_,
|
|
17
|
+
request_ctx__ensure,
|
|
18
|
+
response_init
|
|
19
|
+
) {
|
|
20
|
+
return framework_ctx=>
|
|
21
|
+
html_response__new(
|
|
22
|
+
page_({
|
|
23
|
+
ctx: request_ctx__ensure(middleware_ctx, framework_ctx)
|
|
24
|
+
}),
|
|
25
|
+
response_init)
|
|
26
|
+
}
|
|
27
|
+
/**
|
|
28
|
+
* @param {string|ReadableStream}html_OR_stream
|
|
29
|
+
* @param {ResponseInit}[response_init]
|
|
30
|
+
* @returns {Response}
|
|
31
|
+
*/
|
|
32
|
+
export function html_response__new(
|
|
33
|
+
html_OR_stream,
|
|
34
|
+
response_init
|
|
35
|
+
) {
|
|
36
|
+
const headers = new Headers(response_init?.headers)
|
|
37
|
+
headers.set('Content-Type', 'text/html;charset=UTF-8')
|
|
38
|
+
return new Response(
|
|
39
|
+
html_OR_stream.pipeTo
|
|
40
|
+
? html_OR_stream.pipeThrough(new TextEncoderStream())
|
|
41
|
+
: new ReadableStream({
|
|
42
|
+
start(controller) {
|
|
43
|
+
controller.enqueue('' + html_OR_stream)
|
|
44
|
+
controller.close()
|
|
45
|
+
}
|
|
46
|
+
}),
|
|
47
|
+
{
|
|
48
|
+
...(response_init ?? {}),
|
|
49
|
+
headers
|
|
50
|
+
}
|
|
51
|
+
)
|
|
52
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
/// <reference types="bun-types" />
|
|
2
|
+
export declare function static_middleware__routes_(
|
|
3
|
+
config?:static_middleware__config_T
|
|
4
|
+
):Promise<static_middleware__route_T[]>
|
|
5
|
+
export type static_middleware__config_T = {
|
|
6
|
+
headers_?:(url_path:string, content_type:string, path:string)=>Record<string, string>
|
|
7
|
+
}
|
|
8
|
+
export type static_middleware__route_T = {
|
|
9
|
+
url_path:string
|
|
10
|
+
handler:()=>Response
|
|
11
|
+
}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
/// <reference types="bun-types" />
|
|
2
|
+
/// <reference types="./index.d.ts" />
|
|
3
|
+
import { file, Glob } from 'bun'
|
|
4
|
+
import { ext_R_mime } from 'ctx-core/http'
|
|
5
|
+
import { extname, join } from 'node:path'
|
|
6
|
+
import { browser_path_ } from '../app/index.js'
|
|
7
|
+
import { app_ctx } from '../ctx/index.js'
|
|
8
|
+
/**
|
|
9
|
+
* @param {static_middleware__config_T}[config]
|
|
10
|
+
* @returns {Promise<{get:(path:string, handler:Function)=>void}>}
|
|
11
|
+
*/
|
|
12
|
+
export async function static_middleware__routes_(config) {
|
|
13
|
+
const routes = []
|
|
14
|
+
const glob = new Glob('**')
|
|
15
|
+
for await (const relative_path of glob.scan(browser_path_(app_ctx))) {
|
|
16
|
+
const url_path = join('/', relative_path)
|
|
17
|
+
const content_type = ext_R_mime[extname(relative_path)] ?? 'text/plain'
|
|
18
|
+
const path = join(browser_path_(app_ctx), relative_path)
|
|
19
|
+
const headers = (config?.headers_ ?? (()=>({})))(
|
|
20
|
+
url_path,
|
|
21
|
+
content_type,
|
|
22
|
+
path)
|
|
23
|
+
routes.push({
|
|
24
|
+
url_path,
|
|
25
|
+
handler: ()=>{
|
|
26
|
+
const file_ref = file(path)
|
|
27
|
+
return new Response(file_ref.size ? file_ref.stream() : '', {
|
|
28
|
+
headers: {
|
|
29
|
+
'Content-Type': content_type,
|
|
30
|
+
...headers,
|
|
31
|
+
}
|
|
32
|
+
})
|
|
33
|
+
}
|
|
34
|
+
})
|
|
35
|
+
}
|
|
36
|
+
return routes
|
|
37
|
+
}
|