rhonojs 0.1.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/browser/index.d.ts +1 -0
- package/browser/index.js +1 -0
- package/index.d.ts +1 -0
- package/index.js +1 -0
- package/package.json +66 -0
- package/server/app/index.d.ts +15 -0
- package/server/app/index.js +175 -0
- package/server/build/index.d.ts +23 -0
- package/server/build/index.js +202 -0
- package/server/compression/index.d.ts +5 -0
- package/server/compression/index.js +11 -0
- package/server/export/cloudflare/index.d.ts +34 -0
- package/server/export/cloudflare/index.js +103 -0
- package/server/export/index.d.ts +28 -0
- package/server/export/index.js +154 -0
- package/server/hono/index.d.ts +9 -0
- package/server/hono/index.js +26 -0
- package/server/index.d.ts +7 -0
- package/server/index.js +7 -0
- package/server/route/index.d.ts +16 -0
- package/server/route/index.js +63 -0
- package/server/static/index.d.ts +7 -0
- package/server/static/index.js +34 -0
- package/types/index.d.ts +1 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from 'rebuildjs/browser'
|
package/browser/index.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from 'rebuildjs/browser'
|
package/index.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {}
|
package/index.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {}
|
package/package.json
ADDED
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "rhonojs",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Reactive Web app server focusing on MPAs with a simple server route & browser build api...uses Hono, ESBuild, rmemo, & ctx-core",
|
|
5
|
+
"keywords": [
|
|
6
|
+
"reactive",
|
|
7
|
+
"web app",
|
|
8
|
+
"web server",
|
|
9
|
+
"hono",
|
|
10
|
+
"cloudflare workers",
|
|
11
|
+
"esbuild",
|
|
12
|
+
"rmemo",
|
|
13
|
+
"ctx-core"
|
|
14
|
+
],
|
|
15
|
+
"homepage": "https://github.com/btakita/rhonojs#readme",
|
|
16
|
+
"license": "Apache-2.0",
|
|
17
|
+
"author": {
|
|
18
|
+
"name": "Brian Takita",
|
|
19
|
+
"url": "https://briantakita.me",
|
|
20
|
+
"email": "info+rhonojs@briantakita.me"
|
|
21
|
+
},
|
|
22
|
+
"type": "module",
|
|
23
|
+
"files": [
|
|
24
|
+
"*.d.ts",
|
|
25
|
+
"*.js",
|
|
26
|
+
"*.json",
|
|
27
|
+
"browser",
|
|
28
|
+
"server",
|
|
29
|
+
"types"
|
|
30
|
+
],
|
|
31
|
+
"exports": {
|
|
32
|
+
".": {
|
|
33
|
+
"types": "./index.d.ts",
|
|
34
|
+
"default": "./index.js"
|
|
35
|
+
},
|
|
36
|
+
"./browser": {
|
|
37
|
+
"types": "./browser/index.d.ts",
|
|
38
|
+
"default": "./browser/index.js"
|
|
39
|
+
},
|
|
40
|
+
"./types": {
|
|
41
|
+
"types": "./types/index.d.ts"
|
|
42
|
+
},
|
|
43
|
+
"./server": {
|
|
44
|
+
"types": "./server/index.d.ts",
|
|
45
|
+
"default": "./server/index.js"
|
|
46
|
+
},
|
|
47
|
+
"./server/export": {
|
|
48
|
+
"types": "./server/export/index.d.ts",
|
|
49
|
+
"default": "./server/export/index.js"
|
|
50
|
+
},
|
|
51
|
+
"./server/export/cloudflare": {
|
|
52
|
+
"types": "./server/export/cloudflare/index.d.ts",
|
|
53
|
+
"default": "./server/export/cloudflare/index.js"
|
|
54
|
+
},
|
|
55
|
+
"./package.json": "./package.json"
|
|
56
|
+
},
|
|
57
|
+
"dependencies": {
|
|
58
|
+
"ctx-core": "*",
|
|
59
|
+
"hono": "^4.0.0",
|
|
60
|
+
"rebuildjs": "*"
|
|
61
|
+
},
|
|
62
|
+
"peerDependencies": {
|
|
63
|
+
"esbuild": "^0.27.3"
|
|
64
|
+
},
|
|
65
|
+
"sideEffects": false
|
|
66
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import type { ctx__be_T, ctx__get_T, ctx__set_T, sig_T } from 'ctx-core/rmemo'
|
|
2
|
+
import type { Hono } from 'hono'
|
|
3
|
+
export declare const app$_:ctx__be_T<sig_T<Hono|undefined>, 'app'>
|
|
4
|
+
export declare const app_:ctx__get_T<Hono|undefined, 'app'>
|
|
5
|
+
export declare const app__set:ctx__set_T<Hono|undefined, 'app'>
|
|
6
|
+
export declare const server_entry__relative_path$_:ctx__be_T<sig_T<string|undefined>, 'app'>
|
|
7
|
+
export declare const server_entry__relative_path_:ctx__get_T<string|undefined, 'app'>
|
|
8
|
+
export declare const server_entry__output__relative_path$_:ctx__be_T<sig_T<string|undefined>, 'app'>
|
|
9
|
+
export declare const server_entry__output__relative_path_:ctx__get_T<string|undefined, 'app'>
|
|
10
|
+
export declare const server_entry__output__path$_:ctx__be_T<sig_T<string|undefined>, 'app'>
|
|
11
|
+
export declare const server_entry__output__path_:ctx__get_T<string|undefined, 'app'>
|
|
12
|
+
export declare const server_entry__output__link__path$_:ctx__be_T<sig_T<string|undefined>, 'app'>
|
|
13
|
+
export declare const server_entry__output__link__path_:ctx__get_T<string|undefined, 'app'>
|
|
14
|
+
export declare function app__attach(app?:Hono):Promise<Hono>
|
|
15
|
+
export declare function app__start(app?:Hono):Promise<Hono>
|
|
@@ -0,0 +1,175 @@
|
|
|
1
|
+
import { file_exists_, file_exists__waitfor } from 'ctx-core/fs'
|
|
2
|
+
import {
|
|
3
|
+
calling,
|
|
4
|
+
memo_,
|
|
5
|
+
ns_id_be_memo_pair_,
|
|
6
|
+
ns_id_be_sig_triple_,
|
|
7
|
+
nullish__none_,
|
|
8
|
+
off,
|
|
9
|
+
promise__cancel,
|
|
10
|
+
promise__cancel__throw,
|
|
11
|
+
ref__bind,
|
|
12
|
+
rmemo__wait,
|
|
13
|
+
run,
|
|
14
|
+
tup
|
|
15
|
+
} from 'ctx-core/rmemo'
|
|
16
|
+
import { Hono } from 'hono'
|
|
17
|
+
import { dirname, join } from 'node:path'
|
|
18
|
+
import {
|
|
19
|
+
app__relative_path_,
|
|
20
|
+
app_ctx,
|
|
21
|
+
browser__metafile$_,
|
|
22
|
+
build_id_,
|
|
23
|
+
cwd_,
|
|
24
|
+
metafile__wait,
|
|
25
|
+
port_,
|
|
26
|
+
server__metafile_,
|
|
27
|
+
server__output_,
|
|
28
|
+
server__output__relative_path_,
|
|
29
|
+
server__output__relative_path_M_middleware_ctx_
|
|
30
|
+
} from 'rebuildjs/server'
|
|
31
|
+
export const [
|
|
32
|
+
app$_,
|
|
33
|
+
app_,
|
|
34
|
+
app__set
|
|
35
|
+
] = ns_id_be_sig_triple_(
|
|
36
|
+
'app',
|
|
37
|
+
'app',
|
|
38
|
+
()=>undefined)
|
|
39
|
+
export const [
|
|
40
|
+
server_entry__relative_path$_,
|
|
41
|
+
server_entry__relative_path_,
|
|
42
|
+
] = ns_id_be_memo_pair_(
|
|
43
|
+
'app',
|
|
44
|
+
'server_entry__relative_path',
|
|
45
|
+
ctx=>
|
|
46
|
+
join(app__relative_path_(ctx), 'index.ts'))
|
|
47
|
+
export const [
|
|
48
|
+
server_entry__output__relative_path$_,
|
|
49
|
+
server_entry__output__relative_path_,
|
|
50
|
+
] = ns_id_be_memo_pair_(
|
|
51
|
+
'app',
|
|
52
|
+
'server_entry__output__relative_path',
|
|
53
|
+
ctx=>
|
|
54
|
+
nullish__none_(tup(server__metafile_(ctx), server_entry__relative_path_(ctx)),
|
|
55
|
+
(server__metafile, server_entry__relative_path)=>{
|
|
56
|
+
const { outputs } = server__metafile
|
|
57
|
+
for (const output_path in outputs) {
|
|
58
|
+
const output = outputs[output_path]
|
|
59
|
+
if (output.entryPoint === server_entry__relative_path) return output_path
|
|
60
|
+
}
|
|
61
|
+
}))
|
|
62
|
+
export const [
|
|
63
|
+
server_entry__output__path$_,
|
|
64
|
+
server_entry__output__path_,
|
|
65
|
+
] = ns_id_be_memo_pair_(
|
|
66
|
+
'app',
|
|
67
|
+
'server_entry__output__path',
|
|
68
|
+
ctx=>
|
|
69
|
+
nullish__none_(tup(cwd_(ctx), server_entry__output__relative_path_(ctx)),
|
|
70
|
+
(cwd, server_entry__output__relative_path)=>
|
|
71
|
+
join(cwd, server_entry__output__relative_path)))
|
|
72
|
+
export const [
|
|
73
|
+
server_entry__output__link__path$_,
|
|
74
|
+
server_entry__output__link__path_,
|
|
75
|
+
] = ns_id_be_memo_pair_(
|
|
76
|
+
'app',
|
|
77
|
+
'server_entry__output__link__path',
|
|
78
|
+
ctx=>
|
|
79
|
+
nullish__none_([server_entry__output__path_(ctx)],
|
|
80
|
+
server_entry__output__path=>
|
|
81
|
+
join(dirname(server_entry__output__path), 'index.js')))
|
|
82
|
+
/**
|
|
83
|
+
* @param {Hono}[app]
|
|
84
|
+
* @returns {Promise<Hono>}
|
|
85
|
+
*/
|
|
86
|
+
export async function app__attach(app) {
|
|
87
|
+
await metafile__wait(app_ctx)
|
|
88
|
+
const neq_undefined = val=>val !== undefined
|
|
89
|
+
await rmemo__wait(browser__metafile$_(app_ctx), neq_undefined)
|
|
90
|
+
const app$ = app$__new()
|
|
91
|
+
const val = await rmemo__wait(app$, app=>app, 10_000)
|
|
92
|
+
if (val instanceof Error) {
|
|
93
|
+
throw val
|
|
94
|
+
}
|
|
95
|
+
return val
|
|
96
|
+
function app$__new() {
|
|
97
|
+
return calling(memo_(app$=>{
|
|
98
|
+
app ??= new Hono()
|
|
99
|
+
app._rhonojs = 1
|
|
100
|
+
const build_id = build_id_(app_ctx)
|
|
101
|
+
const server__output__relative_path_M_middleware_ctx = server__output__relative_path_M_middleware_ctx_(app_ctx)
|
|
102
|
+
const middleware_a1 = []
|
|
103
|
+
run(async ()=>{
|
|
104
|
+
for (
|
|
105
|
+
const middleware_ctx of server__output__relative_path_M_middleware_ctx.values()
|
|
106
|
+
) {
|
|
107
|
+
const output = server__output_(middleware_ctx)
|
|
108
|
+
if (!output) {
|
|
109
|
+
return
|
|
110
|
+
}
|
|
111
|
+
if (output.entryPoint !== server_entry__relative_path_(app_ctx)) {
|
|
112
|
+
await file_exists__waitfor(async ()=>{
|
|
113
|
+
const path = join(cwd_(app_ctx), server__output__relative_path_(middleware_ctx))
|
|
114
|
+
if (!await cmd(file_exists_(path))) {
|
|
115
|
+
return false
|
|
116
|
+
}
|
|
117
|
+
const server__middleware_ =
|
|
118
|
+
await cmd(import(path).then(mod=>mod.default))
|
|
119
|
+
if (server__middleware_) {
|
|
120
|
+
middleware_a1.push(server__middleware_(middleware_ctx))
|
|
121
|
+
} else {
|
|
122
|
+
console.warn('module ' + path + ' does not export a default function')
|
|
123
|
+
return false
|
|
124
|
+
}
|
|
125
|
+
return true
|
|
126
|
+
})
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
for (const middleware of middleware_a1) {
|
|
130
|
+
app.use(middleware)
|
|
131
|
+
}
|
|
132
|
+
app$.set(app)
|
|
133
|
+
}).catch(err=>{
|
|
134
|
+
app$.set(err)
|
|
135
|
+
})
|
|
136
|
+
return app$.val
|
|
137
|
+
async function cmd(promise) {
|
|
138
|
+
if (cancel_()) promise__cancel__throw(promise)
|
|
139
|
+
if (!promise) return promise
|
|
140
|
+
ref__bind(promise, calling(memo_(rhonojs_cancel$=>{
|
|
141
|
+
if (cancel_()) {
|
|
142
|
+
promise__cancel(promise)
|
|
143
|
+
off(rhonojs_cancel$)
|
|
144
|
+
}
|
|
145
|
+
})))
|
|
146
|
+
const ret = await promise
|
|
147
|
+
if (cancel_()) promise__cancel__throw(promise)
|
|
148
|
+
return ret
|
|
149
|
+
}
|
|
150
|
+
function cancel_() {
|
|
151
|
+
return (
|
|
152
|
+
build_id_(app_ctx) !== build_id
|
|
153
|
+
|| server__output__relative_path_M_middleware_ctx_(app_ctx) !== server__output__relative_path_M_middleware_ctx
|
|
154
|
+
)
|
|
155
|
+
}
|
|
156
|
+
}))
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
/**
|
|
160
|
+
* @param {Hono}[app]
|
|
161
|
+
* @returns {Promise<Hono>}
|
|
162
|
+
*/
|
|
163
|
+
export async function app__start(app) {
|
|
164
|
+
if (!app?._rhonojs) {
|
|
165
|
+
app = await app__attach(app)
|
|
166
|
+
}
|
|
167
|
+
app__set(app_ctx, app)
|
|
168
|
+
const port = port_(app_ctx)
|
|
169
|
+
Bun.serve({
|
|
170
|
+
port,
|
|
171
|
+
fetch: app.fetch,
|
|
172
|
+
})
|
|
173
|
+
console.info(`server started on port ${port}`)
|
|
174
|
+
return app
|
|
175
|
+
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import type { ctx__be_T, ctx__get_T, ctx__set_T, sig_T } from 'ctx-core/rmemo'
|
|
2
|
+
import type { BuildContext, Plugin } from 'esbuild'
|
|
3
|
+
import type { rebuildjs_build_config_T } from 'rebuildjs/server'
|
|
4
|
+
export declare const rhonojs__build_id$_:ctx__be_T<sig_T<string>, 'app'>
|
|
5
|
+
export declare const rhonojs__build_id_:ctx__get_T<string, 'app'>
|
|
6
|
+
export declare const rhonojs__build_id__set:ctx__set_T<string, 'app'>
|
|
7
|
+
export declare const rhonojs__ready$_:ctx__be_T<sig_T<boolean>, 'app'>
|
|
8
|
+
export declare const rhonojs__ready_:ctx__get_T<boolean, 'app'>
|
|
9
|
+
export declare function rhonojs__ready__wait(timeout?:number):Promise<void>
|
|
10
|
+
export declare function rhonojs_browser__build(
|
|
11
|
+
config?:rhonojs__build_config_T
|
|
12
|
+
):Promise<BuildContext>
|
|
13
|
+
export declare function rhonojs_server__build(
|
|
14
|
+
config?:rhonojs__build_config_T
|
|
15
|
+
):Promise<BuildContext>
|
|
16
|
+
export declare function rhonojs_plugin_(config?:rhonojs_plugin_config_T):Plugin
|
|
17
|
+
export type rhonojs__build_config_T =
|
|
18
|
+
& rebuildjs_build_config_T
|
|
19
|
+
& { rhonojs?:rhonojs_plugin_config_T }
|
|
20
|
+
export type rhonojs_plugin_config_T = {
|
|
21
|
+
server_entry?:string
|
|
22
|
+
app__start?:boolean
|
|
23
|
+
}
|
|
@@ -0,0 +1,202 @@
|
|
|
1
|
+
/// <reference types="esbuild" />
|
|
2
|
+
/// <reference types="./index.d.ts" />
|
|
3
|
+
import { file_exists_, file_exists__waitfor } from 'ctx-core/fs'
|
|
4
|
+
import {
|
|
5
|
+
calling,
|
|
6
|
+
Cancel,
|
|
7
|
+
ns_id_be,
|
|
8
|
+
ns_id_be_memo_pair_,
|
|
9
|
+
ns_id_be_sig_triple_,
|
|
10
|
+
nullish__none_,
|
|
11
|
+
promise__cancel,
|
|
12
|
+
promise__cancel__throw,
|
|
13
|
+
ref__bind,
|
|
14
|
+
run
|
|
15
|
+
} from 'ctx-core/rmemo'
|
|
16
|
+
import { Hono } from 'hono'
|
|
17
|
+
import { link, rm } from 'node:fs/promises'
|
|
18
|
+
import { join } from 'node:path'
|
|
19
|
+
import {
|
|
20
|
+
app_ctx,
|
|
21
|
+
app_path_,
|
|
22
|
+
build_id_,
|
|
23
|
+
memo_,
|
|
24
|
+
metafile__build_id_,
|
|
25
|
+
off,
|
|
26
|
+
port_,
|
|
27
|
+
rebuildjs__esbuild__done_,
|
|
28
|
+
rebuildjs__ready_,
|
|
29
|
+
rebuildjs_browser__build,
|
|
30
|
+
rebuildjs_server__build,
|
|
31
|
+
rmemo__wait
|
|
32
|
+
} from 'rebuildjs/server'
|
|
33
|
+
import { app_, app__start, server_entry__output__link__path_, server_entry__output__path_ } from '../app/index.js'
|
|
34
|
+
export const [
|
|
35
|
+
rhonojs__build_id$_,
|
|
36
|
+
rhonojs__build_id_,
|
|
37
|
+
rhonojs__build_id__set,
|
|
38
|
+
] = ns_id_be_sig_triple_(
|
|
39
|
+
'app',
|
|
40
|
+
'rhonojs_plugin__build_id',
|
|
41
|
+
()=>undefined)
|
|
42
|
+
export const [
|
|
43
|
+
rhonojs__ready$_,
|
|
44
|
+
rhonojs__ready_,
|
|
45
|
+
] = ns_id_be_memo_pair_(
|
|
46
|
+
'app',
|
|
47
|
+
'rhonojs__ready',
|
|
48
|
+
ctx=>
|
|
49
|
+
!!(
|
|
50
|
+
build_id_(ctx)
|
|
51
|
+
&& rebuildjs__ready_(ctx)
|
|
52
|
+
&& build_id_(ctx) === metafile__build_id_(ctx)
|
|
53
|
+
&& build_id_(ctx) === rhonojs__build_id_(ctx)))
|
|
54
|
+
/**
|
|
55
|
+
* @param {number}[timeout]
|
|
56
|
+
* @returns {Promise<void>}
|
|
57
|
+
*/
|
|
58
|
+
export function rhonojs__ready__wait(timeout) {
|
|
59
|
+
return rmemo__wait(
|
|
60
|
+
rhonojs__ready$_(app_ctx),
|
|
61
|
+
ready=>ready,
|
|
62
|
+
timeout ?? 5000)
|
|
63
|
+
}
|
|
64
|
+
/**
|
|
65
|
+
* @param {rhonojs__build_config_T}[config]
|
|
66
|
+
*/
|
|
67
|
+
export function rhonojs_browser__build(config) {
|
|
68
|
+
const {
|
|
69
|
+
rhonojs,
|
|
70
|
+
...rebuildjs__config
|
|
71
|
+
} = config ?? {}
|
|
72
|
+
return rebuildjs_browser__build(rebuildjs__config)
|
|
73
|
+
}
|
|
74
|
+
/**
|
|
75
|
+
* @param {rhonojs__build_config_T}[config]
|
|
76
|
+
* @returns {Promise<void>}
|
|
77
|
+
*/
|
|
78
|
+
export function rhonojs_server__build(config) {
|
|
79
|
+
const {
|
|
80
|
+
rhonojs,
|
|
81
|
+
...rebuildjs__config
|
|
82
|
+
} = config ?? {}
|
|
83
|
+
const plugins = [rhonojs_plugin_(rhonojs), ...(config?.plugins ?? [])]
|
|
84
|
+
const entryPoints = config?.entryPoints ?? []
|
|
85
|
+
const server_entry = rhonojs?.server_entry ?? join(app_path_(app_ctx), 'index.ts')
|
|
86
|
+
entryPoints.push({ in: server_entry, out: 'index' })
|
|
87
|
+
return rebuildjs_server__build({
|
|
88
|
+
...rebuildjs__config,
|
|
89
|
+
entryPoints,
|
|
90
|
+
plugins,
|
|
91
|
+
})
|
|
92
|
+
}
|
|
93
|
+
/**
|
|
94
|
+
* @param {rhonojs_plugin_config_T}[config]
|
|
95
|
+
* @returns {Plugin}
|
|
96
|
+
*/
|
|
97
|
+
export function rhonojs_plugin_(config) {
|
|
98
|
+
return { name: 'rhonojs_plugin', setup: setup_() }
|
|
99
|
+
function setup_() {
|
|
100
|
+
const setup = build=>{
|
|
101
|
+
build.onEnd(async result=>{
|
|
102
|
+
if (result.errors.length) {
|
|
103
|
+
throw new Error(`Build errors: ${result.errors.length} errors`)
|
|
104
|
+
}
|
|
105
|
+
})
|
|
106
|
+
}
|
|
107
|
+
ref__bind(setup, rhonojs__link$_())
|
|
108
|
+
return setup
|
|
109
|
+
function rhonojs__link$_() {
|
|
110
|
+
return ns_id_be(
|
|
111
|
+
app_ctx,
|
|
112
|
+
'app',
|
|
113
|
+
'rhonojs__link$',
|
|
114
|
+
ctx=>
|
|
115
|
+
calling(memo_(()=>{
|
|
116
|
+
if (!rebuildjs__esbuild__done_(ctx)) return
|
|
117
|
+
nullish__none_([
|
|
118
|
+
build_id_(ctx),
|
|
119
|
+
server_entry__output__path_(ctx),
|
|
120
|
+
server_entry__output__link__path_(ctx),
|
|
121
|
+
], (
|
|
122
|
+
build_id,
|
|
123
|
+
server_entry__output__path,
|
|
124
|
+
server_entry__output__link__path,
|
|
125
|
+
)=>{
|
|
126
|
+
run(async ()=>{
|
|
127
|
+
try {
|
|
128
|
+
await Promise.all([
|
|
129
|
+
file_exists__waitfor(async ()=>{
|
|
130
|
+
await cmd(
|
|
131
|
+
rm(server_entry__output__link__path, { force: true }))
|
|
132
|
+
await cmd(
|
|
133
|
+
link(server_entry__output__path, server_entry__output__link__path))
|
|
134
|
+
return true
|
|
135
|
+
}),
|
|
136
|
+
file_exists__waitfor(async ()=>{
|
|
137
|
+
await cmd(
|
|
138
|
+
rm(
|
|
139
|
+
server_entry__output__link__path + '.map',
|
|
140
|
+
{ force: true }))
|
|
141
|
+
await cmd(
|
|
142
|
+
link(
|
|
143
|
+
server_entry__output__path + '.map',
|
|
144
|
+
server_entry__output__link__path + '.map'))
|
|
145
|
+
return true
|
|
146
|
+
})
|
|
147
|
+
])
|
|
148
|
+
rhonojs__build_id__set(ctx, build_id)
|
|
149
|
+
if (config?.app__start ?? true) {
|
|
150
|
+
const _app = app_(ctx)
|
|
151
|
+
if (_app) {
|
|
152
|
+
// Previous Bun.serve server will be replaced
|
|
153
|
+
}
|
|
154
|
+
try {
|
|
155
|
+
await cmd(rhonojs__ready__wait(30_000))
|
|
156
|
+
await cmd(file_exists__waitfor(server_entry__output__link__path))
|
|
157
|
+
await cmd(file_exists__waitfor(async ()=>{
|
|
158
|
+
if (!await file_exists_(server_entry__output__link__path)) {
|
|
159
|
+
return false
|
|
160
|
+
}
|
|
161
|
+
let app = new Hono()
|
|
162
|
+
app.route('/',
|
|
163
|
+
await cmd(import(server_entry__output__link__path))
|
|
164
|
+
.then(mod=>mod.default()))
|
|
165
|
+
await cmd(app__start(app))
|
|
166
|
+
return true
|
|
167
|
+
}))
|
|
168
|
+
} catch (err) {
|
|
169
|
+
if (err instanceof Cancel) return
|
|
170
|
+
throw err
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
} catch (err) {
|
|
174
|
+
if (err instanceof Cancel) return
|
|
175
|
+
throw err
|
|
176
|
+
}
|
|
177
|
+
})
|
|
178
|
+
async function cmd(promise) {
|
|
179
|
+
if (cancel_()) promise__cancel__throw(promise)
|
|
180
|
+
if (!promise) return promise
|
|
181
|
+
ref__bind(promise, calling(memo_(rhonojs_cancel$=>{
|
|
182
|
+
if (cancel_()) {
|
|
183
|
+
promise__cancel(promise)
|
|
184
|
+
off(rhonojs_cancel$)
|
|
185
|
+
}
|
|
186
|
+
})))
|
|
187
|
+
const ret = await promise
|
|
188
|
+
if (cancel_()) promise__cancel__throw(promise)
|
|
189
|
+
return ret
|
|
190
|
+
}
|
|
191
|
+
function cancel_() {
|
|
192
|
+
return (
|
|
193
|
+
build_id_(ctx) !== build_id
|
|
194
|
+
|| server_entry__output__path_(ctx) !== server_entry__output__path
|
|
195
|
+
|| server_entry__output__link__path_(ctx) !== server_entry__output__link__path
|
|
196
|
+
)
|
|
197
|
+
}
|
|
198
|
+
})
|
|
199
|
+
})))
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
/// <reference types="./index.d.ts" />
|
|
2
|
+
import { compress } from 'hono/compress'
|
|
3
|
+
/**
|
|
4
|
+
* @param {compression_middleware_config_T}[config]
|
|
5
|
+
* @returns {import('hono').MiddlewareHandler}
|
|
6
|
+
*/
|
|
7
|
+
export function compression_middleware_(config) {
|
|
8
|
+
return compress({
|
|
9
|
+
encoding: config?.type ?? 'gzip',
|
|
10
|
+
})
|
|
11
|
+
}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import type { static_export_config_T, static_export_result_T } from '../index.js'
|
|
2
|
+
export declare function cloudflare_export_(
|
|
3
|
+
config:cloudflare_export_config_T
|
|
4
|
+
):Promise<cloudflare_export_result_T>
|
|
5
|
+
export declare function worker_entry__generate_(
|
|
6
|
+
dynamic_routes:route_handler_T[]
|
|
7
|
+
):string
|
|
8
|
+
export declare function wrangler_toml__generate_(
|
|
9
|
+
overrides:Partial<wrangler_config_T>,
|
|
10
|
+
out_dir?:string
|
|
11
|
+
):string
|
|
12
|
+
export type cloudflare_export_config_T =
|
|
13
|
+
& static_export_config_T
|
|
14
|
+
& {
|
|
15
|
+
dynamic_routes?:route_handler_T[]
|
|
16
|
+
worker_out?:string
|
|
17
|
+
wrangler?:Partial<wrangler_config_T>
|
|
18
|
+
}
|
|
19
|
+
export type cloudflare_export_result_T =
|
|
20
|
+
& static_export_result_T
|
|
21
|
+
& {
|
|
22
|
+
worker_entry_path?:string
|
|
23
|
+
wrangler_path:string
|
|
24
|
+
}
|
|
25
|
+
export type route_handler_T = {
|
|
26
|
+
pattern:string
|
|
27
|
+
handler:string
|
|
28
|
+
}
|
|
29
|
+
export type wrangler_config_T = {
|
|
30
|
+
name:string
|
|
31
|
+
compatibility_date:string
|
|
32
|
+
vars?:Record<string, string>
|
|
33
|
+
routes?:{ pattern:string, zone_name?:string }[]
|
|
34
|
+
}
|
|
@@ -0,0 +1,103 @@
|
|
|
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 { dirname, join, resolve } from 'node:path'
|
|
6
|
+
import { static_export_ } from '../index.js'
|
|
7
|
+
/**
|
|
8
|
+
* @param {cloudflare_export_config_T} config
|
|
9
|
+
* @returns {Promise<cloudflare_export_result_T>}
|
|
10
|
+
*/
|
|
11
|
+
export async function cloudflare_export_(config) {
|
|
12
|
+
const {
|
|
13
|
+
dynamic_routes = [],
|
|
14
|
+
worker_out = 'dist/worker',
|
|
15
|
+
wrangler = {},
|
|
16
|
+
...static_config
|
|
17
|
+
} = config
|
|
18
|
+
const static_result = await static_export_(static_config)
|
|
19
|
+
let worker_entry_path
|
|
20
|
+
if (dynamic_routes.length > 0) {
|
|
21
|
+
const entry_source = worker_entry__generate_(dynamic_routes)
|
|
22
|
+
const generated_path = join(worker_out, '_worker.src.js')
|
|
23
|
+
await mkdir(worker_out, { recursive: true })
|
|
24
|
+
await writeFile(generated_path, entry_source)
|
|
25
|
+
worker_entry_path = join(worker_out, '_worker.js')
|
|
26
|
+
await build({
|
|
27
|
+
entryPoints: [generated_path],
|
|
28
|
+
bundle: true,
|
|
29
|
+
format: 'esm',
|
|
30
|
+
target: 'es2022',
|
|
31
|
+
platform: 'browser',
|
|
32
|
+
outfile: worker_entry_path,
|
|
33
|
+
minify: true,
|
|
34
|
+
})
|
|
35
|
+
console.info(`[cloudflare_export] worker bundled: ${worker_entry_path}`)
|
|
36
|
+
}
|
|
37
|
+
const wrangler_config = wrangler_toml__generate_(wrangler, static_config.out_dir)
|
|
38
|
+
const wrangler_path = 'wrangler.toml'
|
|
39
|
+
await writeFile(wrangler_path, wrangler_config)
|
|
40
|
+
console.info(`[cloudflare_export] wrote ${wrangler_path}`)
|
|
41
|
+
return {
|
|
42
|
+
...static_result,
|
|
43
|
+
worker_entry_path,
|
|
44
|
+
wrangler_path,
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
/**
|
|
48
|
+
* @param {route_handler_T[]} dynamic_routes
|
|
49
|
+
* @returns {string}
|
|
50
|
+
*/
|
|
51
|
+
export function worker_entry__generate_(dynamic_routes) {
|
|
52
|
+
const imports = []
|
|
53
|
+
const route_checks = []
|
|
54
|
+
for (let i = 0; i < dynamic_routes.length; i++) {
|
|
55
|
+
const { pattern, handler } = dynamic_routes[i]
|
|
56
|
+
const handler_name = `handler_${i}`
|
|
57
|
+
imports.push(`import ${handler_name} from '${resolve(handler)}'`)
|
|
58
|
+
if (pattern.endsWith('/*')) {
|
|
59
|
+
const prefix = pattern.slice(0, -2)
|
|
60
|
+
route_checks.push(
|
|
61
|
+
`\tif (url.pathname.startsWith('${prefix}')) return ${handler_name}(request, env, ctx)`)
|
|
62
|
+
} else {
|
|
63
|
+
route_checks.push(
|
|
64
|
+
`\tif (url.pathname === '${pattern}') return ${handler_name}(request, env, ctx)`)
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
return `${imports.join('\n')}
|
|
68
|
+
|
|
69
|
+
export default {
|
|
70
|
+
async fetch(request, env, ctx) {
|
|
71
|
+
const url = new URL(request.url)
|
|
72
|
+
${route_checks.join('\n')}
|
|
73
|
+
return env.ASSETS.fetch(request)
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
`
|
|
77
|
+
}
|
|
78
|
+
/**
|
|
79
|
+
* @param {Partial<wrangler_config_T>} overrides
|
|
80
|
+
* @param {string} [out_dir]
|
|
81
|
+
* @returns {string}
|
|
82
|
+
*/
|
|
83
|
+
export function wrangler_toml__generate_(overrides, out_dir = 'dist/browser') {
|
|
84
|
+
const name = overrides.name || 'app'
|
|
85
|
+
const compatibility_date = overrides.compatibility_date || new Date().toISOString().slice(0, 10)
|
|
86
|
+
let toml = `name = "${name}"
|
|
87
|
+
compatibility_date = "${compatibility_date}"
|
|
88
|
+
pages_build_output_dir = "${out_dir}"
|
|
89
|
+
`
|
|
90
|
+
if (overrides.vars) {
|
|
91
|
+
toml += '\n[vars]\n'
|
|
92
|
+
for (const [key, val] of Object.entries(overrides.vars)) {
|
|
93
|
+
toml += `${key} = "${val}"\n`
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
if (overrides.routes) {
|
|
97
|
+
for (const route of overrides.routes) {
|
|
98
|
+
toml += `\n[[routes]]\npattern = "${route.pattern}"\n`
|
|
99
|
+
if (route.zone_name) toml += `zone_name = "${route.zone_name}"\n`
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
return toml
|
|
103
|
+
}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import type { Hono } from 'hono'
|
|
2
|
+
export declare function static_export_(
|
|
3
|
+
config:static_export_config_T
|
|
4
|
+
):Promise<static_export_result_T>
|
|
5
|
+
export declare function static_export__file_path_(
|
|
6
|
+
route:string,
|
|
7
|
+
out_dir:string,
|
|
8
|
+
content_type?:string
|
|
9
|
+
):string
|
|
10
|
+
export type static_export_config_T = {
|
|
11
|
+
routes?:string[]
|
|
12
|
+
site_url:string
|
|
13
|
+
out_dir?:string
|
|
14
|
+
base_url?:string
|
|
15
|
+
server_import?:string
|
|
16
|
+
app?:Hono
|
|
17
|
+
sitemap?:boolean
|
|
18
|
+
extra_routes?:string[]
|
|
19
|
+
url_rewrite?:boolean
|
|
20
|
+
incremental?:boolean
|
|
21
|
+
manifest?:boolean
|
|
22
|
+
clean?:boolean
|
|
23
|
+
on_export?:(route:string, file:string)=>void
|
|
24
|
+
}
|
|
25
|
+
export type static_export_result_T = {
|
|
26
|
+
exported:string[]
|
|
27
|
+
errors:string[]
|
|
28
|
+
}
|
|
@@ -0,0 +1,154 @@
|
|
|
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 { app_ctx, port_ } from 'rebuildjs/server'
|
|
6
|
+
import { app__start } from '../app/index.js'
|
|
7
|
+
/**
|
|
8
|
+
* @param {static_export_config_T} config
|
|
9
|
+
* @returns {Promise<static_export_result_T>}
|
|
10
|
+
*/
|
|
11
|
+
export async function static_export_(config) {
|
|
12
|
+
const {
|
|
13
|
+
site_url,
|
|
14
|
+
out_dir = 'dist/browser',
|
|
15
|
+
server_import = './dist/server/index.js',
|
|
16
|
+
sitemap = true,
|
|
17
|
+
url_rewrite = true,
|
|
18
|
+
incremental = false,
|
|
19
|
+
manifest: use_manifest = incremental,
|
|
20
|
+
clean = false,
|
|
21
|
+
on_export,
|
|
22
|
+
} = config
|
|
23
|
+
let { routes = [], extra_routes = [] } = config
|
|
24
|
+
const site_origin = site_url.replace(/\/$/, '')
|
|
25
|
+
const exported = []
|
|
26
|
+
const errors = []
|
|
27
|
+
let app = config.app
|
|
28
|
+
let we_started = false
|
|
29
|
+
let base_url = config.base_url
|
|
30
|
+
let server
|
|
31
|
+
try {
|
|
32
|
+
if (base_url) {
|
|
33
|
+
base_url = base_url.replace(/\/$/, '')
|
|
34
|
+
console.info(`[static_export] using external server: ${base_url}`)
|
|
35
|
+
} else if (!app) {
|
|
36
|
+
const mod = await import(server_import)
|
|
37
|
+
app = typeof mod.default === 'function' ? await mod.default() : mod.default
|
|
38
|
+
await app__start(app)
|
|
39
|
+
we_started = true
|
|
40
|
+
base_url = `http://localhost:${port_(app_ctx)}`
|
|
41
|
+
console.info(`[static_export] server running on ${base_url}`)
|
|
42
|
+
} else {
|
|
43
|
+
base_url = `http://localhost:${port_(app_ctx)}`
|
|
44
|
+
console.info(`[static_export] using provided app on ${base_url}`)
|
|
45
|
+
}
|
|
46
|
+
console.info(`[static_export] site_url: ${site_origin}`)
|
|
47
|
+
if (clean) {
|
|
48
|
+
await rm(out_dir, { recursive: true, force: true })
|
|
49
|
+
}
|
|
50
|
+
if (sitemap) {
|
|
51
|
+
try {
|
|
52
|
+
const res = await fetch(`${base_url}/sitemap.xml`)
|
|
53
|
+
if (res.ok) {
|
|
54
|
+
const xml = await res.text()
|
|
55
|
+
for (const m of xml.matchAll(/<loc>https?:\/\/[^<]+<\/loc>/g)) {
|
|
56
|
+
const url = m[0].replace(/<\/?loc>/g, '')
|
|
57
|
+
const path = new URL(url).pathname
|
|
58
|
+
const route = path === '' ? '/' : path
|
|
59
|
+
if (!routes.includes(route)) {
|
|
60
|
+
routes.push(route)
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
console.info(`[static_export] discovered ${routes.length} routes from sitemap`)
|
|
64
|
+
}
|
|
65
|
+
} catch {
|
|
66
|
+
console.warn('[static_export] sitemap fetch failed, using explicit routes only')
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
const manifest_path = join(out_dir, '.export-manifest.json')
|
|
70
|
+
let prev_manifest = []
|
|
71
|
+
if (use_manifest) {
|
|
72
|
+
try {
|
|
73
|
+
prev_manifest = JSON.parse(await readFile(manifest_path, 'utf-8'))
|
|
74
|
+
} catch {
|
|
75
|
+
// No previous manifest
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
const all_routes = [...routes, ...extra_routes]
|
|
79
|
+
for (const route of all_routes) {
|
|
80
|
+
const url = base_url + route
|
|
81
|
+
try {
|
|
82
|
+
const res = await fetch(url)
|
|
83
|
+
if (!res.ok) {
|
|
84
|
+
console.error(`[static_export] ${route} -> ${res.status} ${res.statusText}`)
|
|
85
|
+
errors.push(route)
|
|
86
|
+
continue
|
|
87
|
+
}
|
|
88
|
+
const content_type = res.headers.get('content-type') ?? ''
|
|
89
|
+
let body = await res.text()
|
|
90
|
+
if (url_rewrite) {
|
|
91
|
+
body = body.replaceAll(base_url, site_origin)
|
|
92
|
+
}
|
|
93
|
+
const file_path = static_export__file_path_(route, out_dir, content_type)
|
|
94
|
+
if (incremental) {
|
|
95
|
+
try {
|
|
96
|
+
const existing = await readFile(file_path, 'utf-8')
|
|
97
|
+
if (existing === body) {
|
|
98
|
+
exported.push(file_path)
|
|
99
|
+
continue
|
|
100
|
+
}
|
|
101
|
+
} catch {
|
|
102
|
+
// File doesn't exist yet
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
await mkdir(dirname(file_path), { recursive: true })
|
|
106
|
+
await writeFile(file_path, body)
|
|
107
|
+
exported.push(file_path)
|
|
108
|
+
on_export?.(route, file_path)
|
|
109
|
+
console.info(`[static_export] ${route} -> ${file_path} (${body.length} bytes)`)
|
|
110
|
+
} catch (err) {
|
|
111
|
+
console.error(`[static_export] ${route} -> ${err.message}`)
|
|
112
|
+
errors.push(route)
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
if (use_manifest) {
|
|
116
|
+
await mkdir(dirname(manifest_path), { recursive: true })
|
|
117
|
+
await writeFile(manifest_path, JSON.stringify(exported, null, '\t'))
|
|
118
|
+
const stale = prev_manifest.filter(f=>!exported.includes(f))
|
|
119
|
+
for (const file of stale) {
|
|
120
|
+
try {
|
|
121
|
+
await unlink(file)
|
|
122
|
+
console.info(`[static_export] deleted stale: ${file}`)
|
|
123
|
+
} catch {
|
|
124
|
+
// Already gone
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
if (errors.length > 0) {
|
|
129
|
+
console.warn(`[static_export] completed with ${errors.length} error(s)`)
|
|
130
|
+
} else {
|
|
131
|
+
console.info(`[static_export] exported ${exported.length} files`)
|
|
132
|
+
}
|
|
133
|
+
} finally {
|
|
134
|
+
// Bun.serve cleanup is handled by process exit
|
|
135
|
+
}
|
|
136
|
+
return { exported, errors }
|
|
137
|
+
}
|
|
138
|
+
/**
|
|
139
|
+
* @param {string} route
|
|
140
|
+
* @param {string} out_dir
|
|
141
|
+
* @param {string} [content_type]
|
|
142
|
+
* @returns {string}
|
|
143
|
+
*/
|
|
144
|
+
export function static_export__file_path_(route, out_dir, content_type) {
|
|
145
|
+
const ext = extname(route)
|
|
146
|
+
const is_html = content_type ? content_type.includes('text/html') : !ext || ext === '.html'
|
|
147
|
+
if (ext && !is_html) {
|
|
148
|
+
return join(out_dir, route)
|
|
149
|
+
}
|
|
150
|
+
if (route === '/') {
|
|
151
|
+
return join(out_dir, 'index.html')
|
|
152
|
+
}
|
|
153
|
+
return join(out_dir, route, 'index.html')
|
|
154
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import type { ctx__be_T, ctx__get_T, ctx__set_T, sig_T } from 'ctx-core/rmemo'
|
|
2
|
+
import type { Context } from 'hono'
|
|
3
|
+
export declare const hono_context$_:ctx__be_T<sig_T<Context|undefined>, 'request'>
|
|
4
|
+
export declare const hono_context_:ctx__get_T<Context|undefined, 'request'>
|
|
5
|
+
export declare const hono_context__set:ctx__set_T<Context|undefined, 'request'>
|
|
6
|
+
export declare const request$_:ctx__be_T<sig_T<Request|undefined>, 'request'>
|
|
7
|
+
export declare const request_:ctx__get_T<Request|undefined, 'request'>
|
|
8
|
+
export declare const request_url$_:ctx__be_T<sig_T<URL|undefined>, 'request'>
|
|
9
|
+
export declare const request_url_:ctx__get_T<URL|undefined, 'request'>
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { ns_id_be_memo_pair_, ns_id_be_sig_triple_, nullish__none_ } from 'ctx-core/rmemo'
|
|
2
|
+
export const [
|
|
3
|
+
hono_context$_,
|
|
4
|
+
hono_context_,
|
|
5
|
+
hono_context__set,
|
|
6
|
+
] = ns_id_be_sig_triple_(
|
|
7
|
+
'request',
|
|
8
|
+
'hono_context',
|
|
9
|
+
()=>undefined)
|
|
10
|
+
export const [
|
|
11
|
+
request$_,
|
|
12
|
+
request_,
|
|
13
|
+
] = ns_id_be_memo_pair_(
|
|
14
|
+
'request',
|
|
15
|
+
'request',
|
|
16
|
+
ctx=>
|
|
17
|
+
hono_context_(ctx)?.req?.raw)
|
|
18
|
+
export const [
|
|
19
|
+
request_url$_,
|
|
20
|
+
request_url_,
|
|
21
|
+
] = ns_id_be_memo_pair_(
|
|
22
|
+
'request',
|
|
23
|
+
'request_url',
|
|
24
|
+
ctx=>
|
|
25
|
+
nullish__none_([request_(ctx)],
|
|
26
|
+
request=>new URL(request.url)))
|
package/server/index.js
ADDED
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
/// <reference lib="dom" />
|
|
2
|
+
import type { Context } from 'hono'
|
|
3
|
+
import type { middleware_ctx_T, request_ctx_T } from 'rebuildjs/server'
|
|
4
|
+
export declare function html_route_(
|
|
5
|
+
middleware_ctx:middleware_ctx_T,
|
|
6
|
+
page_:($p:{ ctx:request_ctx_T })=>({ toString():string }|ReadableStream<string>),
|
|
7
|
+
response_init?:ResponseInit
|
|
8
|
+
):(c:Context)=>Response
|
|
9
|
+
export declare function request_ctx__ensure(
|
|
10
|
+
middleware_ctx:middleware_ctx_T,
|
|
11
|
+
c:Context,
|
|
12
|
+
):request_ctx_T
|
|
13
|
+
export declare function html_response__new(
|
|
14
|
+
html_OR_stream:string|ReadableStream,
|
|
15
|
+
response_init?:ResponseInit
|
|
16
|
+
):Response
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
/// <reference types="./index.d.ts" />
|
|
2
|
+
import { request_ctx__new } from 'rebuildjs/server'
|
|
3
|
+
import { hono_context__set } from '../hono/index.js'
|
|
4
|
+
/**
|
|
5
|
+
* @param {middleware_ctx_T}middleware_ctx
|
|
6
|
+
* @param {($p:{ ctx:request_ctx_T })=>(string|ReadableStream<string|Uint8Array>)}page_
|
|
7
|
+
* @param {ResponseInit}[response_init]
|
|
8
|
+
* @returns {(c:Context)=>Response}
|
|
9
|
+
*/
|
|
10
|
+
export function html_route_(
|
|
11
|
+
middleware_ctx,
|
|
12
|
+
page_,
|
|
13
|
+
response_init
|
|
14
|
+
) {
|
|
15
|
+
return c=>{
|
|
16
|
+
const request_ctx = request_ctx__ensure(middleware_ctx, c)
|
|
17
|
+
return html_response__new(
|
|
18
|
+
page_({ ctx: request_ctx }),
|
|
19
|
+
response_init)
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
/**
|
|
23
|
+
* @param {string|ReadableStream}html_OR_stream
|
|
24
|
+
* @param {ResponseInit}[response_init]
|
|
25
|
+
* @returns {Response}
|
|
26
|
+
*/
|
|
27
|
+
export function html_response__new(
|
|
28
|
+
html_OR_stream,
|
|
29
|
+
response_init
|
|
30
|
+
) {
|
|
31
|
+
const headers = new Headers(response_init?.headers)
|
|
32
|
+
headers.set('Content-Type', 'text/html;charset=UTF-8')
|
|
33
|
+
return new Response(
|
|
34
|
+
html_OR_stream.pipeTo
|
|
35
|
+
? html_OR_stream.pipeThrough(new TextEncoderStream())
|
|
36
|
+
: new ReadableStream({
|
|
37
|
+
start(controller) {
|
|
38
|
+
controller.enqueue('' + html_OR_stream)
|
|
39
|
+
controller.close()
|
|
40
|
+
}
|
|
41
|
+
}),
|
|
42
|
+
{
|
|
43
|
+
...(response_init ?? {}),
|
|
44
|
+
headers
|
|
45
|
+
}
|
|
46
|
+
)
|
|
47
|
+
}
|
|
48
|
+
/**
|
|
49
|
+
* @param {middleware_ctx_T}middleware_ctx
|
|
50
|
+
* @param {Context}c
|
|
51
|
+
*/
|
|
52
|
+
export function request_ctx__ensure(
|
|
53
|
+
middleware_ctx,
|
|
54
|
+
c,
|
|
55
|
+
) {
|
|
56
|
+
let request_ctx = c.get('request_ctx')
|
|
57
|
+
if (!request_ctx) {
|
|
58
|
+
request_ctx = request_ctx__new(middleware_ctx)
|
|
59
|
+
c.set('request_ctx', request_ctx)
|
|
60
|
+
}
|
|
61
|
+
hono_context__set(request_ctx, c)
|
|
62
|
+
return request_ctx
|
|
63
|
+
}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import type { Hono } from 'hono'
|
|
2
|
+
export declare function static_middleware_(
|
|
3
|
+
config?:static_middleware__config_T
|
|
4
|
+
):Promise<Hono>
|
|
5
|
+
export type static_middleware__config_T = {
|
|
6
|
+
headers_?:(url_path:string, content_type:string, path:string)=>Record<string, string>
|
|
7
|
+
}
|
|
@@ -0,0 +1,34 @@
|
|
|
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 { Hono } from 'hono'
|
|
6
|
+
import { extname, join } from 'node:path'
|
|
7
|
+
import { app_ctx, browser_path_ } from 'rebuildjs/server'
|
|
8
|
+
/**
|
|
9
|
+
* @param {static_middleware__config_T}[config]
|
|
10
|
+
* @returns {Promise<Hono>}
|
|
11
|
+
*/
|
|
12
|
+
export async function static_middleware_(config) {
|
|
13
|
+
const app = new Hono()
|
|
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
|
+
app.get(url_path, c=>{
|
|
24
|
+
const file_ref = file(path)
|
|
25
|
+
return new Response(file_ref.size ? file_ref.stream() : '', {
|
|
26
|
+
headers: {
|
|
27
|
+
'Content-Type': content_type,
|
|
28
|
+
...headers,
|
|
29
|
+
}
|
|
30
|
+
})
|
|
31
|
+
})
|
|
32
|
+
}
|
|
33
|
+
return app
|
|
34
|
+
}
|
package/types/index.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from 'rebuildjs/types'
|