rhonojs 0.1.0 → 0.1.2
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 +13 -1
- package/server/app/index.js +5 -1
- package/server/app/index.test.ts +88 -0
- package/server/build/index.test.ts +84 -0
- package/server/compression/index.test.ts +19 -0
- package/server/export/cloudflare/index.js +10 -96
- package/server/export/cloudflare/index.test.ts +55 -0
- package/server/export/index.js +7 -146
- package/server/export/index.test.ts +41 -0
- package/server/hono/index.test.ts +80 -0
- package/server/index.test.ts +64 -0
- package/server/route/index.js +3 -33
- package/server/route/index.test.ts +98 -0
- package/server/static/index.js +4 -22
- package/server/static/index.test.ts +11 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "rhonojs",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.2",
|
|
4
4
|
"description": "Reactive Web app server focusing on MPAs with a simple server route & browser build api...uses Hono, ESBuild, rmemo, & ctx-core",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"reactive",
|
|
@@ -54,6 +54,13 @@
|
|
|
54
54
|
},
|
|
55
55
|
"./package.json": "./package.json"
|
|
56
56
|
},
|
|
57
|
+
"scripts": {
|
|
58
|
+
"build": ":",
|
|
59
|
+
"clean": ":",
|
|
60
|
+
"exec": "$@",
|
|
61
|
+
"test": "bun run test:unit",
|
|
62
|
+
"test:unit": "bun -c node_modules/uvu/bin.js . '\\.test\\.(ts|js)$'"
|
|
63
|
+
},
|
|
57
64
|
"dependencies": {
|
|
58
65
|
"ctx-core": "*",
|
|
59
66
|
"hono": "^4.0.0",
|
|
@@ -62,5 +69,10 @@
|
|
|
62
69
|
"peerDependencies": {
|
|
63
70
|
"esbuild": "^0.27.3"
|
|
64
71
|
},
|
|
72
|
+
"devDependencies": {
|
|
73
|
+
"bun-types": "^1.3.10",
|
|
74
|
+
"typescript": "next",
|
|
75
|
+
"uvu": "^0.5.6"
|
|
76
|
+
},
|
|
65
77
|
"sideEffects": false
|
|
66
78
|
}
|
package/server/app/index.js
CHANGED
|
@@ -127,7 +127,11 @@ export async function app__attach(app) {
|
|
|
127
127
|
}
|
|
128
128
|
}
|
|
129
129
|
for (const middleware of middleware_a1) {
|
|
130
|
-
|
|
130
|
+
if (middleware instanceof Hono) {
|
|
131
|
+
app.route('/', middleware)
|
|
132
|
+
} else {
|
|
133
|
+
app.use(middleware)
|
|
134
|
+
}
|
|
131
135
|
}
|
|
132
136
|
app$.set(app)
|
|
133
137
|
}).catch(err=>{
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
import { Hono } from 'hono'
|
|
2
|
+
import { app_ctx, app_path__set, ctx_, cwd__set, server__metafile__set } from 'rebuildjs/server'
|
|
3
|
+
import { test } from 'uvu'
|
|
4
|
+
import { equal, throws } from 'uvu/assert'
|
|
5
|
+
import { server__metafile0 } from '../../_fixtures/metafile.js'
|
|
6
|
+
import {
|
|
7
|
+
app$_,
|
|
8
|
+
app_,
|
|
9
|
+
app__set,
|
|
10
|
+
server_entry__output__link__path$_,
|
|
11
|
+
server_entry__output__link__path_,
|
|
12
|
+
server_entry__output__path$_,
|
|
13
|
+
server_entry__output__path_,
|
|
14
|
+
server_entry__output__relative_path$_,
|
|
15
|
+
server_entry__output__relative_path_,
|
|
16
|
+
server_entry__relative_path$_,
|
|
17
|
+
server_entry__relative_path_
|
|
18
|
+
} from './index.js'
|
|
19
|
+
test.after.each(()=>{
|
|
20
|
+
app_ctx.s.app.clear()
|
|
21
|
+
})
|
|
22
|
+
test('app', ()=>{
|
|
23
|
+
equal(app$_(app_ctx)(), undefined)
|
|
24
|
+
equal(app_(app_ctx), undefined)
|
|
25
|
+
const app = new Hono()
|
|
26
|
+
app__set(app_ctx, app)
|
|
27
|
+
equal(app$_(app_ctx)(), app)
|
|
28
|
+
equal(app_(app_ctx), app)
|
|
29
|
+
// @ts-expect-error TS2345
|
|
30
|
+
throws(()=>app$_(ctx_())())
|
|
31
|
+
// @ts-expect-error TS2345
|
|
32
|
+
throws(()=>app_(ctx_()))
|
|
33
|
+
// @ts-expect-error TS2345
|
|
34
|
+
throws(()=>app__set(ctx_(), app))
|
|
35
|
+
})
|
|
36
|
+
test('server_entry__relative_path', ()=>{
|
|
37
|
+
equal(server_entry__relative_path$_(app_ctx)(), 'src/app/index.ts')
|
|
38
|
+
equal(server_entry__relative_path_(app_ctx), 'src/app/index.ts')
|
|
39
|
+
cwd__set(app_ctx, '/cwd')
|
|
40
|
+
app_path__set(app_ctx, '/cwd/src/app2')
|
|
41
|
+
equal(server_entry__relative_path$_(app_ctx)(), 'src/app2/index.ts')
|
|
42
|
+
equal(server_entry__relative_path_(app_ctx), 'src/app2/index.ts')
|
|
43
|
+
// @ts-expect-error TS2345
|
|
44
|
+
throws(()=>server_entry__relative_path$_(ctx_()))
|
|
45
|
+
// @ts-expect-error TS2345
|
|
46
|
+
throws(()=>server_entry__relative_path_(ctx_()))
|
|
47
|
+
})
|
|
48
|
+
test('server_entry__output__relative_path', ()=>{
|
|
49
|
+
equal(server_entry__output__relative_path$_(app_ctx)(), undefined)
|
|
50
|
+
equal(server_entry__output__relative_path_(app_ctx), undefined)
|
|
51
|
+
equal(server_entry__relative_path_(app_ctx), 'src/app/index.ts')
|
|
52
|
+
server__metafile__set(app_ctx, server__metafile0)
|
|
53
|
+
equal(server__metafile0.outputs['dist/dev-server/index-OUBLL3JD.js'].entryPoint, 'src/app/index.ts')
|
|
54
|
+
equal(server_entry__output__relative_path$_(app_ctx)(), 'dist/dev-server/index-OUBLL3JD.js')
|
|
55
|
+
equal(server_entry__output__relative_path_(app_ctx), 'dist/dev-server/index-OUBLL3JD.js')
|
|
56
|
+
// @ts-expect-error TS2345
|
|
57
|
+
throws(()=>server_entry__output__relative_path$_(ctx_()))
|
|
58
|
+
// @ts-expect-error TS2345
|
|
59
|
+
throws(()=>server_entry__output__relative_path_(ctx_()))
|
|
60
|
+
})
|
|
61
|
+
test('server_entry__output__path', ()=>{
|
|
62
|
+
equal(server_entry__output__path$_(app_ctx)(), undefined)
|
|
63
|
+
equal(server_entry__output__path_(app_ctx), undefined)
|
|
64
|
+
cwd__set(app_ctx, '/cwd')
|
|
65
|
+
server__metafile__set(app_ctx, server__metafile0)
|
|
66
|
+
equal(server_entry__relative_path_(app_ctx), 'src/app/index.ts')
|
|
67
|
+
equal(server__metafile0.outputs['dist/dev-server/index-OUBLL3JD.js'].entryPoint, 'src/app/index.ts')
|
|
68
|
+
equal(server_entry__output__path$_(app_ctx)(), '/cwd/dist/dev-server/index-OUBLL3JD.js')
|
|
69
|
+
equal(server_entry__output__path_(app_ctx), '/cwd/dist/dev-server/index-OUBLL3JD.js')
|
|
70
|
+
// @ts-expect-error TS2345
|
|
71
|
+
throws(()=>server_entry__output__path$_(ctx_()))
|
|
72
|
+
// @ts-expect-error TS2345
|
|
73
|
+
throws(()=>server_entry__output__path_(ctx_()))
|
|
74
|
+
})
|
|
75
|
+
test('server_entry__output__link__path', ()=>{
|
|
76
|
+
equal(server_entry__output__link__path$_(app_ctx)(), undefined)
|
|
77
|
+
equal(server_entry__output__link__path_(app_ctx), undefined)
|
|
78
|
+
cwd__set(app_ctx, '/cwd')
|
|
79
|
+
server__metafile__set(app_ctx, server__metafile0)
|
|
80
|
+
equal(server_entry__relative_path_(app_ctx), 'src/app/index.ts')
|
|
81
|
+
equal(server_entry__output__link__path$_(app_ctx)(), '/cwd/dist/dev-server/index.js')
|
|
82
|
+
equal(server_entry__output__link__path_(app_ctx), '/cwd/dist/dev-server/index.js')
|
|
83
|
+
// @ts-expect-error TS2345
|
|
84
|
+
throws(()=>server_entry__output__link__path$_(ctx_()))
|
|
85
|
+
// @ts-expect-error TS2345
|
|
86
|
+
throws(()=>server_entry__output__link__path_(ctx_()))
|
|
87
|
+
})
|
|
88
|
+
test.run()
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
import { sleep } from 'ctx-core/function'
|
|
2
|
+
import { ctx_, ns_be_sig_triple_, rmemo__wait } from 'ctx-core/rmemo'
|
|
3
|
+
import {
|
|
4
|
+
app_ctx,
|
|
5
|
+
build_id_,
|
|
6
|
+
build_id__refresh,
|
|
7
|
+
build_id__set,
|
|
8
|
+
browser__metafile__set,
|
|
9
|
+
metafile__build_id_,
|
|
10
|
+
rebuildjs__build_id__set,
|
|
11
|
+
rebuildjs__esbuild__build_id__set,
|
|
12
|
+
rebuildjs__ready__add,
|
|
13
|
+
server__metafile__set,
|
|
14
|
+
} from 'rebuildjs/server'
|
|
15
|
+
import { test } from 'uvu'
|
|
16
|
+
import { equal, throws } from 'uvu/assert'
|
|
17
|
+
import { browser__metafile0, server__metafile0 } from '../../_fixtures/metafile.js'
|
|
18
|
+
import {
|
|
19
|
+
rhonojs__build_id$_,
|
|
20
|
+
rhonojs__build_id_,
|
|
21
|
+
rhonojs__build_id__set,
|
|
22
|
+
rhonojs__ready__wait,
|
|
23
|
+
} from './index.js'
|
|
24
|
+
test.after.each(()=>{
|
|
25
|
+
app_ctx.s.app.clear()
|
|
26
|
+
})
|
|
27
|
+
test('rhonojs__build_id', ()=>{
|
|
28
|
+
equal(rhonojs__build_id$_(app_ctx)(), undefined)
|
|
29
|
+
equal(rhonojs__build_id_(app_ctx), undefined)
|
|
30
|
+
build_id__refresh()
|
|
31
|
+
equal(typeof build_id_(app_ctx), 'string')
|
|
32
|
+
rhonojs__build_id__set(app_ctx, build_id_(app_ctx)!)
|
|
33
|
+
equal(rhonojs__build_id$_(app_ctx)(), build_id_(app_ctx)!)
|
|
34
|
+
equal(rhonojs__build_id_(app_ctx), build_id_(app_ctx)!)
|
|
35
|
+
// @ts-expect-error TS2345
|
|
36
|
+
throws(()=>rhonojs__build_id$_(ctx_()))
|
|
37
|
+
// @ts-expect-error TS2345
|
|
38
|
+
throws(()=>rhonojs__build_id_(ctx_()))
|
|
39
|
+
})
|
|
40
|
+
test('rhonojs__ready__wait', async ()=>{
|
|
41
|
+
let done = false
|
|
42
|
+
const [
|
|
43
|
+
plugin__ready$_,
|
|
44
|
+
,
|
|
45
|
+
plugin__ready__set
|
|
46
|
+
] = ns_be_sig_triple_(
|
|
47
|
+
'app',
|
|
48
|
+
()=>false)
|
|
49
|
+
rebuildjs__ready__add(plugin__ready$_)
|
|
50
|
+
rhonojs__ready__wait().then(()=>done = true)
|
|
51
|
+
equal(done, false)
|
|
52
|
+
const build_id = server__metafile0.build_id!
|
|
53
|
+
build_id__set(app_ctx, build_id)
|
|
54
|
+
await sleep(0)
|
|
55
|
+
equal(done, false)
|
|
56
|
+
server__metafile__set(app_ctx, server__metafile0)
|
|
57
|
+
await sleep(0)
|
|
58
|
+
equal(done, false)
|
|
59
|
+
browser__metafile__set(app_ctx, browser__metafile0)
|
|
60
|
+
await sleep(0)
|
|
61
|
+
equal(done, false)
|
|
62
|
+
rebuildjs__esbuild__build_id__set(app_ctx, build_id)
|
|
63
|
+
await sleep(0)
|
|
64
|
+
equal(done, false)
|
|
65
|
+
rhonojs__build_id__set(app_ctx, build_id)
|
|
66
|
+
await sleep(0)
|
|
67
|
+
equal(done, false)
|
|
68
|
+
plugin__ready__set(app_ctx, true)
|
|
69
|
+
await sleep(0)
|
|
70
|
+
equal(done, false)
|
|
71
|
+
rebuildjs__build_id__set(app_ctx, build_id)
|
|
72
|
+
await sleep(0)
|
|
73
|
+
equal(done, true)
|
|
74
|
+
})
|
|
75
|
+
test('rhonojs__ready__wait|timeout', async ()=>{
|
|
76
|
+
let err:Error|undefined = undefined
|
|
77
|
+
try {
|
|
78
|
+
await rhonojs__ready__wait(0)
|
|
79
|
+
} catch (_err) {
|
|
80
|
+
err = _err as Error
|
|
81
|
+
}
|
|
82
|
+
equal(err!.message, 'Timeout 0ms')
|
|
83
|
+
})
|
|
84
|
+
test.run()
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { test } from 'uvu'
|
|
2
|
+
import { equal } from 'uvu/assert'
|
|
3
|
+
import { compression_middleware_ } from './index.js'
|
|
4
|
+
test('compression_middleware_|loads', ()=>{
|
|
5
|
+
equal(typeof compression_middleware_, 'function')
|
|
6
|
+
})
|
|
7
|
+
test('compression_middleware_|returns middleware', ()=>{
|
|
8
|
+
const middleware = compression_middleware_()
|
|
9
|
+
equal(typeof middleware, 'function')
|
|
10
|
+
})
|
|
11
|
+
test('compression_middleware_|accepts gzip type', ()=>{
|
|
12
|
+
const middleware = compression_middleware_({ type: 'gzip' })
|
|
13
|
+
equal(typeof middleware, 'function')
|
|
14
|
+
})
|
|
15
|
+
test('compression_middleware_|accepts deflate type', ()=>{
|
|
16
|
+
const middleware = compression_middleware_({ type: 'deflate' })
|
|
17
|
+
equal(typeof middleware, 'function')
|
|
18
|
+
})
|
|
19
|
+
test.run()
|
|
@@ -1,103 +1,17 @@
|
|
|
1
1
|
/// <reference types="bun-types" />
|
|
2
2
|
/// <reference types="./index.d.ts" />
|
|
3
|
-
import {
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
3
|
+
import {
|
|
4
|
+
cloudflare_export_ as _cloudflare_export_,
|
|
5
|
+
worker_entry__generate_,
|
|
6
|
+
wrangler_toml__generate_,
|
|
7
|
+
} from 'rebuildjs/server/export/cloudflare'
|
|
8
|
+
import { static_export_ as _static_export_ } from 'rebuildjs/server/export'
|
|
9
|
+
import { app__start } from '../../app/index.js'
|
|
10
|
+
export { worker_entry__generate_, wrangler_toml__generate_ }
|
|
7
11
|
/**
|
|
8
12
|
* @param {cloudflare_export_config_T} config
|
|
9
13
|
* @returns {Promise<cloudflare_export_result_T>}
|
|
10
14
|
*/
|
|
11
|
-
export
|
|
12
|
-
|
|
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
|
|
15
|
+
export function cloudflare_export_(config) {
|
|
16
|
+
return _cloudflare_export_(config, _static_export_, app__start)
|
|
103
17
|
}
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import { test } from 'uvu'
|
|
2
|
+
import { equal, ok } from 'uvu/assert'
|
|
3
|
+
import { worker_entry__generate_, wrangler_toml__generate_ } from './index.js'
|
|
4
|
+
test('worker_entry__generate_|single route', ()=>{
|
|
5
|
+
const source = worker_entry__generate_([
|
|
6
|
+
{ pattern: '/api/*', handler: './handlers/api.js' }
|
|
7
|
+
])
|
|
8
|
+
ok(source.includes("import handler_0 from '"))
|
|
9
|
+
ok(source.includes("url.pathname.startsWith('/api')"))
|
|
10
|
+
ok(source.includes('env.ASSETS.fetch(request)'))
|
|
11
|
+
})
|
|
12
|
+
test('worker_entry__generate_|exact route', ()=>{
|
|
13
|
+
const source = worker_entry__generate_([
|
|
14
|
+
{ pattern: '/health', handler: './handlers/health.js' }
|
|
15
|
+
])
|
|
16
|
+
ok(source.includes("url.pathname === '/health'"))
|
|
17
|
+
})
|
|
18
|
+
test('worker_entry__generate_|multiple routes', ()=>{
|
|
19
|
+
const source = worker_entry__generate_([
|
|
20
|
+
{ pattern: '/api/*', handler: './handlers/api.js' },
|
|
21
|
+
{ pattern: '/health', handler: './handlers/health.js' }
|
|
22
|
+
])
|
|
23
|
+
ok(source.includes('handler_0'))
|
|
24
|
+
ok(source.includes('handler_1'))
|
|
25
|
+
})
|
|
26
|
+
test('wrangler_toml__generate_|defaults', ()=>{
|
|
27
|
+
const toml = wrangler_toml__generate_({})
|
|
28
|
+
ok(toml.includes('name = "app"'))
|
|
29
|
+
ok(toml.includes('compatibility_date'))
|
|
30
|
+
ok(toml.includes('pages_build_output_dir = "dist/browser"'))
|
|
31
|
+
})
|
|
32
|
+
test('wrangler_toml__generate_|custom name', ()=>{
|
|
33
|
+
const toml = wrangler_toml__generate_({ name: 'my-app' })
|
|
34
|
+
ok(toml.includes('name = "my-app"'))
|
|
35
|
+
})
|
|
36
|
+
test('wrangler_toml__generate_|custom out_dir', ()=>{
|
|
37
|
+
const toml = wrangler_toml__generate_({}, 'build/output')
|
|
38
|
+
ok(toml.includes('pages_build_output_dir = "build/output"'))
|
|
39
|
+
})
|
|
40
|
+
test('wrangler_toml__generate_|vars', ()=>{
|
|
41
|
+
const toml = wrangler_toml__generate_({
|
|
42
|
+
vars: { API_KEY: 'test123' }
|
|
43
|
+
})
|
|
44
|
+
ok(toml.includes('[vars]'))
|
|
45
|
+
ok(toml.includes('API_KEY = "test123"'))
|
|
46
|
+
})
|
|
47
|
+
test('wrangler_toml__generate_|routes', ()=>{
|
|
48
|
+
const toml = wrangler_toml__generate_({
|
|
49
|
+
routes: [{ pattern: 'example.com/*', zone_name: 'example.com' }]
|
|
50
|
+
})
|
|
51
|
+
ok(toml.includes('[[routes]]'))
|
|
52
|
+
ok(toml.includes('pattern = "example.com/*"'))
|
|
53
|
+
ok(toml.includes('zone_name = "example.com"'))
|
|
54
|
+
})
|
|
55
|
+
test.run()
|
package/server/export/index.js
CHANGED
|
@@ -1,154 +1,15 @@
|
|
|
1
1
|
/// <reference types="bun-types" />
|
|
2
2
|
/// <reference types="./index.d.ts" />
|
|
3
|
-
import {
|
|
4
|
-
|
|
5
|
-
|
|
3
|
+
import {
|
|
4
|
+
static_export_ as _static_export_,
|
|
5
|
+
static_export__file_path_,
|
|
6
|
+
} from 'rebuildjs/server/export'
|
|
6
7
|
import { app__start } from '../app/index.js'
|
|
8
|
+
export { static_export__file_path_ }
|
|
7
9
|
/**
|
|
8
10
|
* @param {static_export_config_T} config
|
|
9
11
|
* @returns {Promise<static_export_result_T>}
|
|
10
12
|
*/
|
|
11
|
-
export
|
|
12
|
-
|
|
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')
|
|
13
|
+
export function static_export_(config) {
|
|
14
|
+
return _static_export_(config, app__start)
|
|
154
15
|
}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import { test } from 'uvu'
|
|
2
|
+
import { equal } from 'uvu/assert'
|
|
3
|
+
import { static_export__file_path_ } from './index.js'
|
|
4
|
+
test('static_export__file_path_|/ returns index.html', ()=>{
|
|
5
|
+
equal(static_export__file_path_('/', 'dist'), 'dist/index.html')
|
|
6
|
+
})
|
|
7
|
+
test('static_export__file_path_|/about returns about/index.html', ()=>{
|
|
8
|
+
equal(static_export__file_path_('/about', 'dist'), 'dist/about/index.html')
|
|
9
|
+
})
|
|
10
|
+
test('static_export__file_path_|/nested/path returns nested/path/index.html', ()=>{
|
|
11
|
+
equal(static_export__file_path_('/nested/path', 'dist'), 'dist/nested/path/index.html')
|
|
12
|
+
})
|
|
13
|
+
test('static_export__file_path_|/robots.txt with text/plain content-type returns bare file', ()=>{
|
|
14
|
+
equal(static_export__file_path_('/robots.txt', 'dist', 'text/plain'), 'dist/robots.txt')
|
|
15
|
+
})
|
|
16
|
+
test('static_export__file_path_|/sitemap.xml with application/xml content-type returns bare file', ()=>{
|
|
17
|
+
equal(static_export__file_path_('/sitemap.xml', 'dist', 'application/xml'), 'dist/sitemap.xml')
|
|
18
|
+
})
|
|
19
|
+
test('static_export__file_path_|/protocol.love with text/html content-type returns index.html', ()=>{
|
|
20
|
+
equal(
|
|
21
|
+
static_export__file_path_('/protocol.love', 'dist', 'text/html;charset=UTF-8'),
|
|
22
|
+
'dist/protocol.love/index.html')
|
|
23
|
+
})
|
|
24
|
+
test('static_export__file_path_|/protocol.love without content-type falls back to bare file', ()=>{
|
|
25
|
+
equal(static_export__file_path_('/protocol.love', 'dist'), 'dist/protocol.love')
|
|
26
|
+
})
|
|
27
|
+
test('static_export__file_path_|/rss.xml with application/xml returns bare file', ()=>{
|
|
28
|
+
equal(static_export__file_path_('/rss.xml', 'dist', 'application/xml'), 'dist/rss.xml')
|
|
29
|
+
})
|
|
30
|
+
test('static_export__file_path_|/feed.json with application/json returns bare file', ()=>{
|
|
31
|
+
equal(static_export__file_path_('/feed.json', 'dist', 'application/json'), 'dist/feed.json')
|
|
32
|
+
})
|
|
33
|
+
test('static_export__file_path_|/page.html with text/html returns page.html/index.html', ()=>{
|
|
34
|
+
equal(
|
|
35
|
+
static_export__file_path_('/page.html', 'dist', 'text/html'),
|
|
36
|
+
'dist/page.html/index.html')
|
|
37
|
+
})
|
|
38
|
+
test('static_export__file_path_|/page with no extension and no content-type returns index.html', ()=>{
|
|
39
|
+
equal(static_export__file_path_('/page', 'dist'), 'dist/page/index.html')
|
|
40
|
+
})
|
|
41
|
+
test.run()
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
import { Hono } from 'hono'
|
|
2
|
+
import { app_ctx, ctx_, middleware_ctx__new, request_ctx__new } from 'rebuildjs/server'
|
|
3
|
+
import { test } from 'uvu'
|
|
4
|
+
import { equal, throws } from 'uvu/assert'
|
|
5
|
+
import {
|
|
6
|
+
hono_context$_,
|
|
7
|
+
hono_context_,
|
|
8
|
+
hono_context__set,
|
|
9
|
+
request$_,
|
|
10
|
+
request_,
|
|
11
|
+
request_url$_,
|
|
12
|
+
request_url_,
|
|
13
|
+
} from './index.js'
|
|
14
|
+
test.after.each(()=>{
|
|
15
|
+
app_ctx.s.app.clear()
|
|
16
|
+
})
|
|
17
|
+
test('hono_context', async ()=>{
|
|
18
|
+
const request_ctx = request_ctx__new(middleware_ctx__new())
|
|
19
|
+
equal(hono_context$_(request_ctx)(), undefined)
|
|
20
|
+
equal(hono_context_(request_ctx), undefined)
|
|
21
|
+
// Use a real Hono context by running through a handler
|
|
22
|
+
const app = new Hono()
|
|
23
|
+
let captured_c:any
|
|
24
|
+
app.get('/', c=>{
|
|
25
|
+
captured_c = c
|
|
26
|
+
return c.text('ok')
|
|
27
|
+
})
|
|
28
|
+
await app.request('http://localhost:3000/')
|
|
29
|
+
hono_context__set(request_ctx, captured_c)
|
|
30
|
+
equal(hono_context$_(request_ctx)(), captured_c)
|
|
31
|
+
equal(hono_context_(request_ctx), captured_c)
|
|
32
|
+
// @ts-expect-error TS2345
|
|
33
|
+
throws(()=>hono_context$_(ctx_())())
|
|
34
|
+
// @ts-expect-error TS2345
|
|
35
|
+
throws(()=>hono_context_(ctx_()))
|
|
36
|
+
// @ts-expect-error TS2345
|
|
37
|
+
throws(()=>hono_context__set(ctx_(), captured_c))
|
|
38
|
+
})
|
|
39
|
+
test('request', async ()=>{
|
|
40
|
+
const request_ctx = request_ctx__new(middleware_ctx__new())
|
|
41
|
+
equal(request$_(request_ctx)(), undefined)
|
|
42
|
+
equal(request_(request_ctx), undefined)
|
|
43
|
+
const app = new Hono()
|
|
44
|
+
let captured_c:any
|
|
45
|
+
app.get('/', c=>{
|
|
46
|
+
captured_c = c
|
|
47
|
+
return c.text('ok')
|
|
48
|
+
})
|
|
49
|
+
await app.request('http://localhost:3000/')
|
|
50
|
+
hono_context__set(request_ctx, captured_c)
|
|
51
|
+
const req = request_(request_ctx)
|
|
52
|
+
equal(req instanceof Request, true)
|
|
53
|
+
equal(request$_(request_ctx)(), req)
|
|
54
|
+
// @ts-expect-error TS2345
|
|
55
|
+
throws(()=>request$_(ctx_())())
|
|
56
|
+
// @ts-expect-error TS2345
|
|
57
|
+
throws(()=>request_(ctx_()))
|
|
58
|
+
})
|
|
59
|
+
test('request_url', async ()=>{
|
|
60
|
+
const request_ctx = request_ctx__new(middleware_ctx__new())
|
|
61
|
+
equal(request_url$_(request_ctx)(), undefined)
|
|
62
|
+
equal(request_url_(request_ctx), undefined)
|
|
63
|
+
const app = new Hono()
|
|
64
|
+
let captured_c:any
|
|
65
|
+
app.get('/foo/bar', c=>{
|
|
66
|
+
captured_c = c
|
|
67
|
+
return c.text('ok')
|
|
68
|
+
})
|
|
69
|
+
await app.request('http://localhost:3000/foo/bar')
|
|
70
|
+
hono_context__set(request_ctx, captured_c)
|
|
71
|
+
const url = request_url_(request_ctx)
|
|
72
|
+
equal(url instanceof URL, true)
|
|
73
|
+
equal(url!.pathname, '/foo/bar')
|
|
74
|
+
equal(request_url$_(request_ctx)(), url)
|
|
75
|
+
// @ts-expect-error TS2345
|
|
76
|
+
throws(()=>request_url$_(ctx_())())
|
|
77
|
+
// @ts-expect-error TS2345
|
|
78
|
+
throws(()=>request_url_(ctx_()))
|
|
79
|
+
})
|
|
80
|
+
test.run()
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
import { test } from 'uvu'
|
|
2
|
+
import { equal } from 'uvu/assert'
|
|
3
|
+
import * as server from './index.js'
|
|
4
|
+
// Adapter interface conformance: verify rhonojs/server exports the expected API surface
|
|
5
|
+
// These are the functions/signals that both adapters must provide
|
|
6
|
+
const required_exports = [
|
|
7
|
+
// From rebuildjs/server (re-exported)
|
|
8
|
+
'app_ctx',
|
|
9
|
+
'middleware_ctx__new',
|
|
10
|
+
'request_ctx__new',
|
|
11
|
+
'html_response__new',
|
|
12
|
+
// html_route_ is intentionally excluded from barrel — ambiguous between rebuildjs and adapter versions.
|
|
13
|
+
// Import from 'rhonojs/server/route' or 'rebuildjs/server/route' instead.
|
|
14
|
+
'static_middleware__routes_',
|
|
15
|
+
'rebuildjs_browser__build',
|
|
16
|
+
'rebuildjs_server__build',
|
|
17
|
+
'build_id_',
|
|
18
|
+
'cwd_',
|
|
19
|
+
'port_',
|
|
20
|
+
// From rhonojs adapter modules
|
|
21
|
+
'app$_',
|
|
22
|
+
'app_',
|
|
23
|
+
'app__set',
|
|
24
|
+
'app__attach',
|
|
25
|
+
'app__start',
|
|
26
|
+
'server_entry__relative_path$_',
|
|
27
|
+
'server_entry__relative_path_',
|
|
28
|
+
'server_entry__output__relative_path$_',
|
|
29
|
+
'server_entry__output__relative_path_',
|
|
30
|
+
'server_entry__output__path$_',
|
|
31
|
+
'server_entry__output__path_',
|
|
32
|
+
'server_entry__output__link__path$_',
|
|
33
|
+
'server_entry__output__link__path_',
|
|
34
|
+
// Build
|
|
35
|
+
'rhonojs__build_id$_',
|
|
36
|
+
'rhonojs__build_id_',
|
|
37
|
+
'rhonojs__build_id__set',
|
|
38
|
+
'rhonojs__ready$_',
|
|
39
|
+
'rhonojs__ready_',
|
|
40
|
+
'rhonojs__ready__wait',
|
|
41
|
+
'rhonojs_browser__build',
|
|
42
|
+
'rhonojs_server__build',
|
|
43
|
+
'rhonojs_plugin_',
|
|
44
|
+
// Compression
|
|
45
|
+
'compression_middleware_',
|
|
46
|
+
// Hono context
|
|
47
|
+
'hono_context$_',
|
|
48
|
+
'hono_context_',
|
|
49
|
+
'hono_context__set',
|
|
50
|
+
'request$_',
|
|
51
|
+
'request_',
|
|
52
|
+
'request_url$_',
|
|
53
|
+
'request_url_',
|
|
54
|
+
// Route
|
|
55
|
+
'request_ctx__ensure',
|
|
56
|
+
// Static
|
|
57
|
+
'static_middleware_',
|
|
58
|
+
]
|
|
59
|
+
for (const name of required_exports) {
|
|
60
|
+
test(`exports|${name}`, ()=>{
|
|
61
|
+
equal(name in server, true, `rhonojs/server should export '${name}'`)
|
|
62
|
+
})
|
|
63
|
+
}
|
|
64
|
+
test.run()
|
package/server/route/index.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
/// <reference types="./index.d.ts" />
|
|
2
|
-
import { request_ctx__new } from 'rebuildjs/server'
|
|
2
|
+
import { html_route_ as _html_route_, request_ctx__new } from 'rebuildjs/server'
|
|
3
3
|
import { hono_context__set } from '../hono/index.js'
|
|
4
|
+
export { html_response__new } from 'rebuildjs/server'
|
|
4
5
|
/**
|
|
5
6
|
* @param {middleware_ctx_T}middleware_ctx
|
|
6
7
|
* @param {($p:{ ctx:request_ctx_T })=>(string|ReadableStream<string|Uint8Array>)}page_
|
|
@@ -12,38 +13,7 @@ export function html_route_(
|
|
|
12
13
|
page_,
|
|
13
14
|
response_init
|
|
14
15
|
) {
|
|
15
|
-
return
|
|
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
|
-
)
|
|
16
|
+
return _html_route_(middleware_ctx, page_, request_ctx__ensure, response_init)
|
|
47
17
|
}
|
|
48
18
|
/**
|
|
49
19
|
* @param {middleware_ctx_T}middleware_ctx
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
import { Hono } from 'hono'
|
|
2
|
+
import { app_ctx, middleware_ctx__new } from 'rebuildjs/server'
|
|
3
|
+
import { test } from 'uvu'
|
|
4
|
+
import { equal } from 'uvu/assert'
|
|
5
|
+
import { hono_context_ } from '../hono/index.js'
|
|
6
|
+
import { html_route_, request_ctx__ensure } from './index.js'
|
|
7
|
+
test.after.each(()=>{
|
|
8
|
+
app_ctx.s.app.clear()
|
|
9
|
+
})
|
|
10
|
+
test('html_route_|string', async ()=>{
|
|
11
|
+
const middleware_ctx = middleware_ctx__new()
|
|
12
|
+
const html = `<!DOCTYPE html><html><head></head><body><div>Test</div></body></html>`
|
|
13
|
+
const html_route = html_route_(middleware_ctx, ()=>
|
|
14
|
+
html)
|
|
15
|
+
const app = new Hono()
|
|
16
|
+
app.get('/', html_route)
|
|
17
|
+
const response = await app.request('/')
|
|
18
|
+
equal(html, await response.text())
|
|
19
|
+
})
|
|
20
|
+
test('html_route_|toString', async ()=>{
|
|
21
|
+
const middleware_ctx = middleware_ctx__new()
|
|
22
|
+
const html = `<!DOCTYPE html><html><head></head><body><div>Test</div></body></html>`
|
|
23
|
+
const html_route = html_route_(middleware_ctx, ()=>(
|
|
24
|
+
{ toString: ()=>html }))
|
|
25
|
+
const app = new Hono()
|
|
26
|
+
app.get('/', html_route)
|
|
27
|
+
const response = await app.request('/')
|
|
28
|
+
equal(await response.text(), html)
|
|
29
|
+
})
|
|
30
|
+
test('html_route_|ReadableStream|string', async ()=>{
|
|
31
|
+
const middleware_ctx = middleware_ctx__new()
|
|
32
|
+
const html = `<!DOCTYPE html><html><head></head><body><div>Test</div></body></html>`
|
|
33
|
+
const html_route = html_route_(middleware_ctx, ()=>
|
|
34
|
+
new ReadableStream({
|
|
35
|
+
start(controller) {
|
|
36
|
+
let first = true
|
|
37
|
+
for (const chunk of html.split('>')) {
|
|
38
|
+
if (!first) controller.enqueue('>')
|
|
39
|
+
first = false
|
|
40
|
+
if (chunk != null) controller.enqueue(chunk)
|
|
41
|
+
}
|
|
42
|
+
controller.close()
|
|
43
|
+
}
|
|
44
|
+
}))
|
|
45
|
+
const app = new Hono()
|
|
46
|
+
app.get('/', html_route)
|
|
47
|
+
const response = await app.request('/')
|
|
48
|
+
equal(await response.text(), html)
|
|
49
|
+
})
|
|
50
|
+
test('html_route_|response_init|all', async ()=>{
|
|
51
|
+
const middleware_ctx = middleware_ctx__new()
|
|
52
|
+
const html = `<!DOCTYPE html><html><head></head><body><div>Test</div></body></html>`
|
|
53
|
+
const html_route = html_route_(middleware_ctx, ()=>html, {
|
|
54
|
+
status: 403,
|
|
55
|
+
statusText: 'Forbidden',
|
|
56
|
+
headers: {
|
|
57
|
+
'Content-Type': 'application/json',
|
|
58
|
+
'FOO': 'BAR',
|
|
59
|
+
}
|
|
60
|
+
})
|
|
61
|
+
const app = new Hono()
|
|
62
|
+
app.get('/', html_route)
|
|
63
|
+
const response = await app.request('/')
|
|
64
|
+
equal(response.status, 403)
|
|
65
|
+
equal(response.statusText, 'Forbidden')
|
|
66
|
+
equal(response.headers.get('Content-Type'), 'text/html;charset=UTF-8')
|
|
67
|
+
equal(response.headers.get('FOO'), 'BAR')
|
|
68
|
+
})
|
|
69
|
+
test('html_route_|response_init|additional headers', async ()=>{
|
|
70
|
+
const middleware_ctx = middleware_ctx__new()
|
|
71
|
+
const html = `<!DOCTYPE html><html><head></head><body><div>Test</div></body></html>`
|
|
72
|
+
const html_route = html_route_(middleware_ctx, ()=>html, {
|
|
73
|
+
headers: {
|
|
74
|
+
'FOO': 'BAR'
|
|
75
|
+
}
|
|
76
|
+
})
|
|
77
|
+
const app = new Hono()
|
|
78
|
+
app.get('/', html_route)
|
|
79
|
+
const response = await app.request('/')
|
|
80
|
+
equal(response.status, 200)
|
|
81
|
+
equal(response.headers.get('Content-Type'), 'text/html;charset=UTF-8')
|
|
82
|
+
equal(response.headers.get('FOO'), 'BAR')
|
|
83
|
+
})
|
|
84
|
+
test('request_ctx__ensure', async ()=>{
|
|
85
|
+
const middleware_ctx = middleware_ctx__new()
|
|
86
|
+
let captured_request_ctx:any
|
|
87
|
+
const app = new Hono()
|
|
88
|
+
app.get('/', c=>{
|
|
89
|
+
const request_ctx = request_ctx__ensure(middleware_ctx, c)
|
|
90
|
+
captured_request_ctx = request_ctx
|
|
91
|
+
equal(c.get('request_ctx'), request_ctx)
|
|
92
|
+
equal(hono_context_(request_ctx), c)
|
|
93
|
+
return c.text('ok')
|
|
94
|
+
})
|
|
95
|
+
await app.request('/')
|
|
96
|
+
equal(captured_request_ctx != null, true)
|
|
97
|
+
})
|
|
98
|
+
test.run()
|
package/server/static/index.js
CHANGED
|
@@ -1,34 +1,16 @@
|
|
|
1
1
|
/// <reference types="bun-types" />
|
|
2
2
|
/// <reference types="./index.d.ts" />
|
|
3
|
-
import { file, Glob } from 'bun'
|
|
4
|
-
import { ext_R_mime } from 'ctx-core/http'
|
|
5
3
|
import { Hono } from 'hono'
|
|
6
|
-
import {
|
|
7
|
-
import { app_ctx, browser_path_ } from 'rebuildjs/server'
|
|
4
|
+
import { static_middleware__routes_ } from 'rebuildjs/server'
|
|
8
5
|
/**
|
|
9
6
|
* @param {static_middleware__config_T}[config]
|
|
10
7
|
* @returns {Promise<Hono>}
|
|
11
8
|
*/
|
|
12
9
|
export async function static_middleware_(config) {
|
|
13
10
|
const app = new Hono()
|
|
14
|
-
const
|
|
15
|
-
for
|
|
16
|
-
|
|
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
|
-
})
|
|
11
|
+
const routes = await static_middleware__routes_(config)
|
|
12
|
+
for (const { url_path, handler } of routes) {
|
|
13
|
+
app.get(url_path, c=>handler())
|
|
32
14
|
}
|
|
33
15
|
return app
|
|
34
16
|
}
|