honox 0.1.8 → 0.1.10
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/README.md +26 -18
- package/dist/server/server.d.ts +13 -7
- package/dist/server/server.js +34 -14
- package/dist/server/with-defaults.d.ts +11 -2
- package/dist/server/with-defaults.js +1 -0
- package/dist/utils/file.js +8 -5
- package/dist/vite/inject-importing-islands.js +2 -2
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
# HonoX
|
|
2
2
|
|
|
3
|
-
**HonoX** is a simple and fast -
|
|
3
|
+
**HonoX** is a simple and fast meta-framework for creating full-stack websites or Web APIs - (formerly _[Sonik](https://github.com/sonikjs/sonik)_). It stands on the shoulders of giants; built on [Hono](https://hono.dev/), [Vite](https://vitejs.dev/), and UI libraries.
|
|
4
4
|
|
|
5
|
-
**Note**: _HonoX is currently in
|
|
5
|
+
**Note**: _HonoX is currently in the "alpha stage". Breaking changes are introduced without following semantic versioning._
|
|
6
6
|
|
|
7
7
|
## Features
|
|
8
8
|
|
|
@@ -68,9 +68,9 @@ export default defineConfig({
|
|
|
68
68
|
|
|
69
69
|
### Server Entry File
|
|
70
70
|
|
|
71
|
-
A server entry file is required. The file
|
|
71
|
+
A server entry file is required. The file should be placed at `app/server.ts`. This file is first called by the Vite during the development or build phase.
|
|
72
72
|
|
|
73
|
-
In the entry file, simply initialize your app using the `createApp()` function. `app` will be an instance of Hono, so you can use Hono's middleware and the `showRoutes()`in`hono/dev`.
|
|
73
|
+
In the entry file, simply initialize your app using the `createApp()` function. `app` will be an instance of Hono, so you can use Hono's middleware and the `showRoutes()` in `hono/dev`.
|
|
74
74
|
|
|
75
75
|
```ts
|
|
76
76
|
// app/server.ts
|
|
@@ -528,22 +528,32 @@ export const POST = createRoute(zValidator('form', schema), async (c) => {
|
|
|
528
528
|
|
|
529
529
|
Alternatively, you can use a `_middleware.(ts|tsx)` file in a directory to have that middleware applied to the current route, as well as all child routes. Middleware is ran in the order that it is listed within the array.
|
|
530
530
|
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
```tsx
|
|
534
|
-
// /app/_middleware.ts
|
|
531
|
+
```ts
|
|
532
|
+
// /app/routes/_middleware.ts
|
|
535
533
|
import { createRoute } from 'honox/factory'
|
|
536
|
-
import {
|
|
537
|
-
import {
|
|
534
|
+
import { logger } from 'hono/logger'
|
|
535
|
+
import { secureHeaders } from 'hono/secure-headers'
|
|
538
536
|
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
})
|
|
537
|
+
export default createRoute(logger(), secureHeaders(), ...<more-middleware>)
|
|
538
|
+
```
|
|
542
539
|
|
|
543
|
-
|
|
540
|
+
### Trailing Slash
|
|
541
|
+
|
|
542
|
+
By default, trailing slashes are removed if the root file is an index file such as `index.tsx` or `index.mdx`.
|
|
543
|
+
However, if you set the `trailingSlash` option to `true` as the following, the trailing slash is not removed.
|
|
544
|
+
|
|
545
|
+
```ts
|
|
546
|
+
import { createApp } from 'honox/server'
|
|
547
|
+
|
|
548
|
+
const app = createApp({
|
|
549
|
+
trailingSlash: true,
|
|
550
|
+
})
|
|
544
551
|
```
|
|
545
552
|
|
|
546
|
-
|
|
553
|
+
Like the followings:
|
|
554
|
+
|
|
555
|
+
- `trailingSlash` is `false` (default): `app/routes/path/index.mdx` => `/path`
|
|
556
|
+
- `trailingSlash` is `true`: `app/routes/path/index.mdx` => `/path/`
|
|
547
557
|
|
|
548
558
|
### Using Tailwind CSS
|
|
549
559
|
|
|
@@ -694,6 +704,7 @@ If you want to use Cloudflare's Bindings in your development environment, create
|
|
|
694
704
|
```toml
|
|
695
705
|
name = "my-project-name"
|
|
696
706
|
compatibility_date = "2023-12-01"
|
|
707
|
+
pages_build_output_dir = "./dist"
|
|
697
708
|
|
|
698
709
|
# [vars]
|
|
699
710
|
# MY_VARIABLE = "production_value"
|
|
@@ -721,9 +732,6 @@ export default defineConfig({
|
|
|
721
732
|
})
|
|
722
733
|
```
|
|
723
734
|
|
|
724
|
-
> [!NOTE]
|
|
725
|
-
> The `wrangler.toml` is not used in the Cloudflare Pages production environment. Please configure Bindings from the dashboard.
|
|
726
|
-
|
|
727
735
|
## Deployment
|
|
728
736
|
|
|
729
737
|
Since a HonoX instance is essentially a Hono instance, it can be deployed on any platform that Hono supports.
|
package/dist/server/server.d.ts
CHANGED
|
@@ -4,12 +4,13 @@ import { Env, Hono, MiddlewareHandler, NotFoundHandler, ErrorHandler } from 'hon
|
|
|
4
4
|
import { IMPORTING_ISLANDS_ID } from '../constants.js';
|
|
5
5
|
|
|
6
6
|
declare const METHODS: readonly ["GET", "POST", "PUT", "DELETE", "OPTIONS", "PATCH"];
|
|
7
|
-
type
|
|
8
|
-
default: Hono;
|
|
9
|
-
};
|
|
10
|
-
type InnerMeta = {
|
|
7
|
+
type HasIslandFile = {
|
|
11
8
|
[key in typeof IMPORTING_ISLANDS_ID]?: boolean;
|
|
12
9
|
};
|
|
10
|
+
type InnerMeta = {} & HasIslandFile;
|
|
11
|
+
type AppFile = {
|
|
12
|
+
default: Hono;
|
|
13
|
+
} & InnerMeta;
|
|
13
14
|
type RouteFile = {
|
|
14
15
|
default?: Function;
|
|
15
16
|
} & {
|
|
@@ -17,13 +18,13 @@ type RouteFile = {
|
|
|
17
18
|
} & InnerMeta;
|
|
18
19
|
type RendererFile = {
|
|
19
20
|
default: MiddlewareHandler;
|
|
20
|
-
};
|
|
21
|
+
} & InnerMeta;
|
|
21
22
|
type NotFoundFile = {
|
|
22
23
|
default: NotFoundHandler;
|
|
23
|
-
};
|
|
24
|
+
} & InnerMeta;
|
|
24
25
|
type ErrorFile = {
|
|
25
26
|
default: ErrorHandler;
|
|
26
|
-
};
|
|
27
|
+
} & InnerMeta;
|
|
27
28
|
type MiddlewareFile = {
|
|
28
29
|
default: MiddlewareHandler[];
|
|
29
30
|
};
|
|
@@ -37,6 +38,11 @@ type BaseServerOptions<E extends Env = Env> = {
|
|
|
37
38
|
root: string;
|
|
38
39
|
app?: Hono<E>;
|
|
39
40
|
init?: InitFunction<E>;
|
|
41
|
+
/**
|
|
42
|
+
* Appends a trailing slash to URL if the route file is an index file, e.g., `index.tsx` or `index.mdx`.
|
|
43
|
+
* @default false
|
|
44
|
+
*/
|
|
45
|
+
trailingSlash?: boolean;
|
|
40
46
|
};
|
|
41
47
|
type ServerOptions<E extends Env = Env> = Partial<BaseServerOptions<E>>;
|
|
42
48
|
declare const createApp: <E extends Env>(options: BaseServerOptions<E>) => Hono<E, hono_types.BlankSchema, "/">;
|
package/dist/server/server.js
CHANGED
|
@@ -14,6 +14,7 @@ const createApp = (options) => {
|
|
|
14
14
|
const root = options.root;
|
|
15
15
|
const rootRegExp = new RegExp(`^${root}`);
|
|
16
16
|
const app = options.app ?? new Hono();
|
|
17
|
+
const trailingSlash = options.trailingSlash ?? false;
|
|
17
18
|
if (options.init) {
|
|
18
19
|
options.init(app);
|
|
19
20
|
}
|
|
@@ -24,7 +25,6 @@ const createApp = (options) => {
|
|
|
24
25
|
const RENDERER_FILE = options.RENDERER;
|
|
25
26
|
const rendererList = listByDirectory(RENDERER_FILE);
|
|
26
27
|
const MIDDLEWARE_FILE = options.MIDDLEWARE;
|
|
27
|
-
const middlewareList = listByDirectory(MIDDLEWARE_FILE);
|
|
28
28
|
const ROUTES_FILE = options.ROUTES;
|
|
29
29
|
const routesMap = sortDirectoriesByDepth(groupByDirectory(ROUTES_FILE));
|
|
30
30
|
const getPaths = (currentDirectory, fileList) => {
|
|
@@ -47,28 +47,34 @@ const createApp = (options) => {
|
|
|
47
47
|
for (const map of routesMap) {
|
|
48
48
|
for (const [dir, content] of Object.entries(map)) {
|
|
49
49
|
const subApp = new Hono();
|
|
50
|
+
let hasIslandComponent = false;
|
|
50
51
|
const rendererPaths = getPaths(dir, rendererList);
|
|
51
52
|
rendererPaths.map((path) => {
|
|
52
53
|
const renderer = RENDERER_FILE[path];
|
|
54
|
+
const importingIslands = renderer[IMPORTING_ISLANDS_ID];
|
|
55
|
+
if (importingIslands) {
|
|
56
|
+
hasIslandComponent = true;
|
|
57
|
+
}
|
|
53
58
|
const rendererDefault = renderer.default;
|
|
54
59
|
if (rendererDefault) {
|
|
55
60
|
subApp.all("*", rendererDefault);
|
|
56
61
|
}
|
|
57
62
|
});
|
|
58
|
-
const
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
63
|
+
const middlewareFile = Object.keys(MIDDLEWARE_FILE).find(
|
|
64
|
+
(x) => new RegExp(dir + "/_middleware.tsx?").test(x)
|
|
65
|
+
);
|
|
66
|
+
if (middlewareFile) {
|
|
67
|
+
const middleware = MIDDLEWARE_FILE[middlewareFile];
|
|
68
|
+
if (middleware.default) {
|
|
69
|
+
subApp.use(...middleware.default);
|
|
64
70
|
}
|
|
65
|
-
}
|
|
71
|
+
}
|
|
66
72
|
let rootPath = dir.replace(rootRegExp, "");
|
|
67
73
|
rootPath = filePathToPath(rootPath);
|
|
68
74
|
for (const [filename, route] of Object.entries(content)) {
|
|
69
75
|
const importingIslands = route[IMPORTING_ISLANDS_ID];
|
|
70
76
|
const setInnerMeta = createMiddleware(async function innerMeta(c, next) {
|
|
71
|
-
c.set(IMPORTING_ISLANDS_ID, importingIslands);
|
|
77
|
+
c.set(IMPORTING_ISLANDS_ID, importingIslands ? true : hasIslandComponent);
|
|
72
78
|
await next();
|
|
73
79
|
});
|
|
74
80
|
const routeDefault = route.default;
|
|
@@ -97,6 +103,9 @@ const createApp = (options) => {
|
|
|
97
103
|
}
|
|
98
104
|
applyNotFound(subApp, dir, notFoundMap);
|
|
99
105
|
applyError(subApp, dir, errorMap);
|
|
106
|
+
if (trailingSlash) {
|
|
107
|
+
rootPath = /\/$/.test(rootPath) ? rootPath : rootPath + "/";
|
|
108
|
+
}
|
|
100
109
|
app.route(rootPath, subApp);
|
|
101
110
|
}
|
|
102
111
|
}
|
|
@@ -108,6 +117,13 @@ function applyNotFound(app, dir, map) {
|
|
|
108
117
|
const notFound = content[NOTFOUND_FILENAME];
|
|
109
118
|
if (notFound) {
|
|
110
119
|
const notFoundHandler = notFound.default;
|
|
120
|
+
const importingIslands = notFound[IMPORTING_ISLANDS_ID];
|
|
121
|
+
if (importingIslands) {
|
|
122
|
+
app.use("*", (c, next) => {
|
|
123
|
+
c.set(IMPORTING_ISLANDS_ID, true);
|
|
124
|
+
return next();
|
|
125
|
+
});
|
|
126
|
+
}
|
|
111
127
|
app.get("*", (c) => {
|
|
112
128
|
c.status(404);
|
|
113
129
|
return notFoundHandler(c);
|
|
@@ -119,12 +135,16 @@ function applyNotFound(app, dir, map) {
|
|
|
119
135
|
function applyError(app, dir, map) {
|
|
120
136
|
for (const [mapDir, content] of Object.entries(map)) {
|
|
121
137
|
if (dir === mapDir) {
|
|
122
|
-
const
|
|
123
|
-
if (
|
|
124
|
-
const errorHandler =
|
|
125
|
-
app.onError((
|
|
138
|
+
const errorFile = content[ERROR_FILENAME];
|
|
139
|
+
if (errorFile) {
|
|
140
|
+
const errorHandler = errorFile.default;
|
|
141
|
+
app.onError(async (error, c) => {
|
|
142
|
+
const importingIslands = errorFile[IMPORTING_ISLANDS_ID];
|
|
143
|
+
if (importingIslands) {
|
|
144
|
+
c.set(IMPORTING_ISLANDS_ID, importingIslands);
|
|
145
|
+
}
|
|
126
146
|
c.status(500);
|
|
127
|
-
return errorHandler(
|
|
147
|
+
return errorHandler(error, c);
|
|
128
148
|
});
|
|
129
149
|
}
|
|
130
150
|
}
|
|
@@ -14,17 +14,25 @@ declare const createApp: <E extends Env>(options?: Partial<{
|
|
|
14
14
|
PATCH?: hono_types.H[] | undefined;
|
|
15
15
|
} & {
|
|
16
16
|
__importing_islands?: boolean | undefined;
|
|
17
|
-
}) | {
|
|
17
|
+
}) | ({
|
|
18
18
|
default: hono.Hono<Env, hono_types.BlankSchema, "/">;
|
|
19
|
-
}
|
|
19
|
+
} & {
|
|
20
|
+
__importing_islands?: boolean | undefined;
|
|
21
|
+
})>;
|
|
20
22
|
RENDERER: Record<string, {
|
|
21
23
|
default: hono.MiddlewareHandler;
|
|
24
|
+
} & {
|
|
25
|
+
__importing_islands?: boolean | undefined;
|
|
22
26
|
}>;
|
|
23
27
|
NOT_FOUND: Record<string, {
|
|
24
28
|
default: hono.NotFoundHandler;
|
|
29
|
+
} & {
|
|
30
|
+
__importing_islands?: boolean | undefined;
|
|
25
31
|
}>;
|
|
26
32
|
ERROR: Record<string, {
|
|
27
33
|
default: hono.ErrorHandler;
|
|
34
|
+
} & {
|
|
35
|
+
__importing_islands?: boolean | undefined;
|
|
28
36
|
}>;
|
|
29
37
|
MIDDLEWARE: Record<string, {
|
|
30
38
|
default: hono.MiddlewareHandler[];
|
|
@@ -32,6 +40,7 @@ declare const createApp: <E extends Env>(options?: Partial<{
|
|
|
32
40
|
root: string;
|
|
33
41
|
app?: hono.Hono<E, hono_types.BlankSchema, "/"> | undefined;
|
|
34
42
|
init?: ((app: hono.Hono<E, hono_types.BlankSchema, "/">) => void) | undefined;
|
|
43
|
+
trailingSlash?: boolean | undefined;
|
|
35
44
|
}> | undefined) => hono.Hono<E, hono_types.BlankSchema, "/">;
|
|
36
45
|
|
|
37
46
|
export { createApp };
|
package/dist/utils/file.js
CHANGED
|
@@ -33,12 +33,15 @@ const sortDirectoriesByDepth = (directories) => {
|
|
|
33
33
|
const sortedKeys = Object.keys(directories).sort((a, b) => {
|
|
34
34
|
const depthA = a.split("/").length;
|
|
35
35
|
const depthB = b.split("/").length;
|
|
36
|
-
return
|
|
36
|
+
return depthA - depthB;
|
|
37
37
|
});
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
38
|
+
if (sortedKeys.find((x) => /.*\/routes$/.test(x))) {
|
|
39
|
+
sortedKeys.push(sortedKeys[0]);
|
|
40
|
+
sortedKeys.shift();
|
|
41
|
+
}
|
|
42
|
+
return sortedKeys.map((key) => ({
|
|
43
|
+
[key]: directories[key]
|
|
44
|
+
}));
|
|
42
45
|
};
|
|
43
46
|
const listByDirectory = (files) => {
|
|
44
47
|
const organizedFiles = {};
|
|
@@ -8,7 +8,7 @@ import { IMPORTING_ISLANDS_ID } from "../constants.js";
|
|
|
8
8
|
const generate = _generate.default ?? _generate;
|
|
9
9
|
async function injectImportingIslands() {
|
|
10
10
|
const isIslandRegex = new RegExp(/\/islands\//);
|
|
11
|
-
const
|
|
11
|
+
const fileRegex = new RegExp(/(routes|_renderer|_error|_404)\/.*\.[tj]sx$/);
|
|
12
12
|
const cache = {};
|
|
13
13
|
const walkDependencyTree = async (baseFile, dependencyFile) => {
|
|
14
14
|
const depPath = dependencyFile ? path.join(path.dirname(baseFile), dependencyFile) + ".tsx" : baseFile;
|
|
@@ -32,7 +32,7 @@ async function injectImportingIslands() {
|
|
|
32
32
|
return {
|
|
33
33
|
name: "inject-importing-islands",
|
|
34
34
|
async transform(sourceCode, id) {
|
|
35
|
-
if (!
|
|
35
|
+
if (!fileRegex.test(id)) {
|
|
36
36
|
return;
|
|
37
37
|
}
|
|
38
38
|
const hasIslandsImport = (await walkDependencyTree(id)).flat().some((x) => isIslandRegex.test(normalizePath(x)));
|