@solidjs/start 0.5.0 → 0.5.1
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/config/fs-router.js +23 -53
- package/config/index.js +4 -22
- package/config/server-fns-runtime.ts +0 -1
- package/config/server-handler.ts +1 -2
- package/middleware/index.ts +4 -4
- package/package.json +2 -2
- package/server/fetchEvent.ts +1 -2
- package/server/handler.ts +23 -6
- package/server/pageEvent.ts +3 -3
- package/server/types.ts +1 -1
- package/shared/routes.ts +37 -3
- package/config/api-handler.ts +0 -64
package/config/fs-router.js
CHANGED
|
@@ -24,7 +24,6 @@ export class SolidStartClientFileRouter extends BaseFileSystemRouter {
|
|
|
24
24
|
|
|
25
25
|
if (src.endsWith(".md") || src.endsWith(".mdx")) {
|
|
26
26
|
return {
|
|
27
|
-
type: "page",
|
|
28
27
|
$component: {
|
|
29
28
|
src: src,
|
|
30
29
|
pick: ["$css"]
|
|
@@ -40,7 +39,6 @@ export class SolidStartClientFileRouter extends BaseFileSystemRouter {
|
|
|
40
39
|
const hasRouteConfig = exports.find(e => e.n === "route");
|
|
41
40
|
if (hasDefault) {
|
|
42
41
|
return {
|
|
43
|
-
type: "page",
|
|
44
42
|
$component: {
|
|
45
43
|
src: src,
|
|
46
44
|
pick: ["default", "$css"]
|
|
@@ -58,6 +56,20 @@ export class SolidStartClientFileRouter extends BaseFileSystemRouter {
|
|
|
58
56
|
}
|
|
59
57
|
}
|
|
60
58
|
|
|
59
|
+
const HTTP_METHODS = ["GET", "POST", "PUT", "DELETE", "PATCH"];
|
|
60
|
+
function createHTTPHandlers(src, exports) {
|
|
61
|
+
const handlers = {};
|
|
62
|
+
for (const exp of exports) {
|
|
63
|
+
if (HTTP_METHODS.includes(exp.n)) {
|
|
64
|
+
handlers[`$${exp.n}`] = {
|
|
65
|
+
src: src,
|
|
66
|
+
pick: [exp.n]
|
|
67
|
+
};
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
return handlers;
|
|
71
|
+
}
|
|
72
|
+
|
|
61
73
|
export class SolidStartServerFileRouter extends BaseFileSystemRouter {
|
|
62
74
|
toPath(src) {
|
|
63
75
|
const routePath = cleanPath(src, this.config)
|
|
@@ -79,10 +91,8 @@ export class SolidStartServerFileRouter extends BaseFileSystemRouter {
|
|
|
79
91
|
|
|
80
92
|
toRoute(src) {
|
|
81
93
|
let path = this.toPath(src);
|
|
82
|
-
|
|
83
94
|
if (src.endsWith(".md") || src.endsWith(".mdx")) {
|
|
84
95
|
return {
|
|
85
|
-
type: "page",
|
|
86
96
|
$component: {
|
|
87
97
|
src: src,
|
|
88
98
|
pick: ["$css"]
|
|
@@ -95,62 +105,22 @@ export class SolidStartServerFileRouter extends BaseFileSystemRouter {
|
|
|
95
105
|
|
|
96
106
|
const [_, exports] = analyzeModule(src);
|
|
97
107
|
const hasRouteConfig = exports.find(e => e.n === "route");
|
|
98
|
-
|
|
108
|
+
const hasDefault = exports.find(e => e.n === "default");
|
|
109
|
+
const hasAPIRoutes = exports.find(exp => HTTP_METHODS.includes(exp.n));
|
|
110
|
+
if (hasDefault || hasAPIRoutes) {
|
|
99
111
|
return {
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
112
|
+
$component: hasDefault
|
|
113
|
+
? {
|
|
114
|
+
src: src,
|
|
115
|
+
pick: ["default", "$css"]
|
|
116
|
+
}
|
|
117
|
+
: undefined,
|
|
105
118
|
$$route: hasRouteConfig
|
|
106
119
|
? {
|
|
107
120
|
src: src,
|
|
108
121
|
pick: ["route"]
|
|
109
122
|
}
|
|
110
123
|
: undefined,
|
|
111
|
-
path,
|
|
112
|
-
filePath: src
|
|
113
|
-
};
|
|
114
|
-
}
|
|
115
|
-
}
|
|
116
|
-
}
|
|
117
|
-
|
|
118
|
-
const HTTP_METHODS = ["GET", "POST", "PUT", "DELETE", "PATCH"];
|
|
119
|
-
function createHTTPHandlers(src, exports) {
|
|
120
|
-
const handlers = {};
|
|
121
|
-
for (const exp of exports) {
|
|
122
|
-
if (HTTP_METHODS.includes(exp.n)) {
|
|
123
|
-
handlers[`$${exp.n}`] = {
|
|
124
|
-
src: src,
|
|
125
|
-
pick: [exp.n]
|
|
126
|
-
};
|
|
127
|
-
}
|
|
128
|
-
}
|
|
129
|
-
return handlers;
|
|
130
|
-
}
|
|
131
|
-
|
|
132
|
-
export class SolidStartAPIFileRouter extends BaseFileSystemRouter {
|
|
133
|
-
toPath(src) {
|
|
134
|
-
const routePath = cleanPath(src, this.config)
|
|
135
|
-
// remove the initial slash
|
|
136
|
-
.slice(1)
|
|
137
|
-
.replace(/index$/, "")
|
|
138
|
-
.replace(/\[([^\/]+)\]/g, (_, m) => {
|
|
139
|
-
if (m.length > 2 && m.startsWith("...")) {
|
|
140
|
-
return `**${m.length > 3 ? ":" + m.slice(3) : ""}`;
|
|
141
|
-
}
|
|
142
|
-
return `:${m}`;
|
|
143
|
-
});
|
|
144
|
-
|
|
145
|
-
return routePath?.length > 0 ? `/${routePath}` : "/";
|
|
146
|
-
}
|
|
147
|
-
|
|
148
|
-
toRoute(src) {
|
|
149
|
-
let path = this.toPath(src);
|
|
150
|
-
const [_, exports] = analyzeModule(src);
|
|
151
|
-
if (exports.find(exp => HTTP_METHODS.includes(exp.n))) {
|
|
152
|
-
return {
|
|
153
|
-
type: "api",
|
|
154
124
|
...createHTTPHandlers(src, exports),
|
|
155
125
|
path,
|
|
156
126
|
filePath: src
|
package/config/index.js
CHANGED
|
@@ -8,7 +8,10 @@ import { createApp, resolve } from "vinxi";
|
|
|
8
8
|
import { normalize } from "vinxi/lib/path";
|
|
9
9
|
import { config } from "vinxi/plugins/config";
|
|
10
10
|
import solid from "vite-plugin-solid";
|
|
11
|
-
import {
|
|
11
|
+
import {
|
|
12
|
+
SolidStartClientFileRouter,
|
|
13
|
+
SolidStartServerFileRouter
|
|
14
|
+
} from "./fs-router.js";
|
|
12
15
|
import { serverComponents } from "./server-components.js";
|
|
13
16
|
|
|
14
17
|
const DEFAULT_EXTENSIONS = ["js", "jsx", "ts", "tsx"];
|
|
@@ -37,18 +40,6 @@ function solidStartServerFsRouter(config) {
|
|
|
37
40
|
);
|
|
38
41
|
}
|
|
39
42
|
|
|
40
|
-
function solidStartAPIFileRouter(config) {
|
|
41
|
-
return (router, app) =>
|
|
42
|
-
new SolidStartAPIFileRouter(
|
|
43
|
-
{
|
|
44
|
-
dir: resolve.absolute(config.dir, router.root),
|
|
45
|
-
extensions: config.extensions ?? DEFAULT_EXTENSIONS
|
|
46
|
-
},
|
|
47
|
-
router,
|
|
48
|
-
app
|
|
49
|
-
);
|
|
50
|
-
}
|
|
51
|
-
|
|
52
43
|
export function defineConfig(baseConfig = {}) {
|
|
53
44
|
let { plugins = [], start = {}, ...userConfig } = baseConfig;
|
|
54
45
|
const extensions = [...DEFAULT_EXTENSIONS, ...(start.extensions || [])];
|
|
@@ -86,15 +77,6 @@ export function defineConfig(baseConfig = {}) {
|
|
|
86
77
|
dir: "./public",
|
|
87
78
|
base: "/"
|
|
88
79
|
},
|
|
89
|
-
{
|
|
90
|
-
name: "api",
|
|
91
|
-
type: "http",
|
|
92
|
-
target: "server",
|
|
93
|
-
base: "/",
|
|
94
|
-
handler: normalize(fileURLToPath(new URL("./api-handler.ts", import.meta.url))),
|
|
95
|
-
routes: solidStartAPIFileRouter({ dir: `${start.appRoot}/routes` }),
|
|
96
|
-
middleware: start.middleware
|
|
97
|
-
},
|
|
98
80
|
{
|
|
99
81
|
name: "ssr",
|
|
100
82
|
type: "http",
|
package/config/server-handler.ts
CHANGED
|
@@ -13,10 +13,9 @@ import {
|
|
|
13
13
|
URLSearchParamsPlugin
|
|
14
14
|
} from "seroval-plugins/web";
|
|
15
15
|
import { sharedConfig } from "solid-js";
|
|
16
|
-
/* @ts-ignore */
|
|
17
16
|
import { provideRequestEvent } from "solid-js/web/storage";
|
|
17
|
+
import { eventHandler, setHeader } from "vinxi/http";
|
|
18
18
|
import invariant from "vinxi/lib/invariant";
|
|
19
|
-
import { eventHandler, setHeader } from "vinxi/server";
|
|
20
19
|
import { getFetchEvent, mergeResponseHeaders } from "../server/fetchEvent";
|
|
21
20
|
|
|
22
21
|
function createChunk(data) {
|
package/middleware/index.ts
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import {
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
} from "vinxi/
|
|
2
|
+
H3Event,
|
|
3
|
+
defineMiddleware,
|
|
4
|
+
sendWebResponse
|
|
5
|
+
} from "vinxi/http";
|
|
6
6
|
import { getFetchEvent } from "../server/fetchEvent";
|
|
7
7
|
import type { FetchEvent } from "../server/types";
|
|
8
8
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@solidjs/start",
|
|
3
|
-
"version": "0.5.
|
|
3
|
+
"version": "0.5.1",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"author": "Ryan Carniato",
|
|
6
6
|
"publishConfig": {
|
|
@@ -42,7 +42,7 @@
|
|
|
42
42
|
}
|
|
43
43
|
},
|
|
44
44
|
"devDependencies": {
|
|
45
|
-
"solid-js": "^1.8.
|
|
45
|
+
"solid-js": "^1.8.13",
|
|
46
46
|
"vinxi": "^0.2.1"
|
|
47
47
|
},
|
|
48
48
|
"dependencies": {
|
package/server/fetchEvent.ts
CHANGED
|
@@ -12,7 +12,7 @@ import {
|
|
|
12
12
|
setResponseHeader,
|
|
13
13
|
setResponseStatus,
|
|
14
14
|
toWebRequest
|
|
15
|
-
} from "vinxi/
|
|
15
|
+
} from "vinxi/http";
|
|
16
16
|
import type { FetchEvent, ResponseStub } from "./types";
|
|
17
17
|
|
|
18
18
|
const fetchEventSymbol = Symbol("fetchEvent");
|
|
@@ -40,7 +40,6 @@ export function getFetchEvent(h3Event: H3Event): FetchEvent {
|
|
|
40
40
|
if (!h3Event[fetchEventSymbol]) {
|
|
41
41
|
const fetchEvent = createFetchEvent(h3Event);
|
|
42
42
|
h3Event[fetchEventSymbol] = fetchEvent;
|
|
43
|
-
// @ts-ignore
|
|
44
43
|
}
|
|
45
44
|
|
|
46
45
|
return h3Event[fetchEventSymbol];
|
package/server/handler.ts
CHANGED
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
import { sharedConfig } from "solid-js";
|
|
2
2
|
import { renderToStream, renderToString } from "solid-js/web";
|
|
3
|
-
/* @ts-ignore */
|
|
4
3
|
import { provideRequestEvent } from "solid-js/web/storage";
|
|
5
4
|
import {
|
|
6
5
|
EventHandlerObject,
|
|
@@ -10,10 +9,11 @@ import {
|
|
|
10
9
|
sendRedirect,
|
|
11
10
|
setHeader,
|
|
12
11
|
setResponseStatus
|
|
13
|
-
} from "vinxi/
|
|
12
|
+
} from "vinxi/http";
|
|
13
|
+
import { matchAPIRoute } from "../shared/routes";
|
|
14
14
|
import { getFetchEvent } from "./fetchEvent";
|
|
15
15
|
import { createPageEvent } from "./pageEvent";
|
|
16
|
-
import { FetchEvent, PageEvent } from "./types";
|
|
16
|
+
import type { APIEvent, FetchEvent, PageEvent } from "./types";
|
|
17
17
|
|
|
18
18
|
export function createBaseHandler(
|
|
19
19
|
fn: (context: PageEvent) => unknown,
|
|
@@ -36,6 +36,23 @@ export function createBaseHandler(
|
|
|
36
36
|
const mode = import.meta.env.START_SSR;
|
|
37
37
|
|
|
38
38
|
return provideRequestEvent(event, async () => {
|
|
39
|
+
// api
|
|
40
|
+
const match = matchAPIRoute(new URL(event.request.url).pathname, event.request.method);
|
|
41
|
+
if (match) {
|
|
42
|
+
const mod = await match.handler.import();
|
|
43
|
+
const fn = mod[event.request.method];
|
|
44
|
+
(event as APIEvent).params = match.params;
|
|
45
|
+
// @ts-ignore
|
|
46
|
+
sharedConfig.context = { event };
|
|
47
|
+
const res = await fn(event);
|
|
48
|
+
if (res !== undefined) return res;
|
|
49
|
+
if (event.request.method !== "GET") {
|
|
50
|
+
throw new Error(
|
|
51
|
+
`API handler for ${event.request.method} "${event.request.url}" did not return a response.`
|
|
52
|
+
);
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
|
|
39
56
|
// render
|
|
40
57
|
const context = await createPageEvent(event);
|
|
41
58
|
if (mode === "sync") {
|
|
@@ -65,7 +82,7 @@ export function createBaseHandler(
|
|
|
65
82
|
} else cloned.onCompleteShell = handleShellCompleteRedirect(context, e);
|
|
66
83
|
const stream = renderToStream(() => {
|
|
67
84
|
(sharedConfig.context as any).event = context;
|
|
68
|
-
return fn(context)
|
|
85
|
+
return fn(context);
|
|
69
86
|
}, cloned);
|
|
70
87
|
if (context.response && context.response.headers.get("Location")) {
|
|
71
88
|
return sendRedirect(event, context.response.headers.get("Location"));
|
|
@@ -86,7 +103,7 @@ function handleShellCompleteRedirect(context: PageEvent, e: H3Event<EventHandler
|
|
|
86
103
|
setResponseStatus(e, 302);
|
|
87
104
|
setHeader(e, "Location", context.response.headers.get("Location"));
|
|
88
105
|
}
|
|
89
|
-
}
|
|
106
|
+
};
|
|
90
107
|
}
|
|
91
108
|
|
|
92
109
|
function handleStreamCompleteRedirect(context: PageEvent) {
|
|
@@ -101,4 +118,4 @@ export function createHandler(
|
|
|
101
118
|
options: Parameters<typeof createBaseHandler>[1] = {}
|
|
102
119
|
) {
|
|
103
120
|
return createBaseHandler(fn, { ...options, createPageEvent });
|
|
104
|
-
}
|
|
121
|
+
}
|
package/server/pageEvent.ts
CHANGED
package/server/types.ts
CHANGED
package/shared/routes.ts
CHANGED
|
@@ -1,23 +1,25 @@
|
|
|
1
|
+
import { createRouter } from "radix3";
|
|
1
2
|
import fileRoutes from "vinxi/routes";
|
|
2
3
|
|
|
3
4
|
interface Route {
|
|
4
5
|
path: string;
|
|
5
6
|
id: string;
|
|
6
|
-
type: "page";
|
|
7
7
|
children?: Route[];
|
|
8
|
+
$component?: any;
|
|
8
9
|
}
|
|
9
10
|
|
|
10
11
|
declare module "vinxi/routes" {
|
|
11
12
|
export interface Register {
|
|
12
13
|
route: {
|
|
13
14
|
path: string;
|
|
14
|
-
type: "api" | "page";
|
|
15
15
|
children?: Route[];
|
|
16
16
|
};
|
|
17
17
|
}
|
|
18
18
|
}
|
|
19
19
|
|
|
20
|
-
export const pageRoutes = defineRoutes(
|
|
20
|
+
export const pageRoutes = defineRoutes(
|
|
21
|
+
(fileRoutes as unknown as Route[]).filter(o => o.$component)
|
|
22
|
+
);
|
|
21
23
|
|
|
22
24
|
function defineRoutes(fileRoutes: Route[]) {
|
|
23
25
|
function processRoute(routes: Route[], route: Route, id: string, full: string) {
|
|
@@ -46,3 +48,35 @@ function defineRoutes(fileRoutes: Route[]) {
|
|
|
46
48
|
}, []);
|
|
47
49
|
}
|
|
48
50
|
|
|
51
|
+
export function matchAPIRoute(path: string, method: string) {
|
|
52
|
+
const match = router.lookup(path);
|
|
53
|
+
if (match) {
|
|
54
|
+
const handler = match.route[`$${method}`];
|
|
55
|
+
if (handler === undefined) return;
|
|
56
|
+
return {
|
|
57
|
+
handler,
|
|
58
|
+
params: match.params
|
|
59
|
+
};
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
function containsHTTP(route: Route) {
|
|
64
|
+
return route["$GET"] || route["$POST"] || route["$PUT"] || route["$PATCH"] || route["$DELETE"];
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
const router = createRouter({
|
|
68
|
+
routes: (fileRoutes as unknown as Route[]).reduce((memo, route) => {
|
|
69
|
+
if (!containsHTTP(route)) return memo;
|
|
70
|
+
let path = route.path.replace(/\(.*\)\/?/g, "").replace(/\*([^/]*)/g, (_, m) => `**:${m}`);
|
|
71
|
+
if (/:[^/]*\?/g.test(path)) {
|
|
72
|
+
throw new Error(`Optional parameters are not supported in API routes: ${path}`);
|
|
73
|
+
}
|
|
74
|
+
if (memo[path]) {
|
|
75
|
+
throw new Error(
|
|
76
|
+
`Duplicate API routes for "${path}" found at "${memo[path].route.path}" and "${route.path}"`
|
|
77
|
+
);
|
|
78
|
+
}
|
|
79
|
+
memo[path] = { route };
|
|
80
|
+
return memo;
|
|
81
|
+
}, {} as Record<string, { route: Route }>)
|
|
82
|
+
});
|
package/config/api-handler.ts
DELETED
|
@@ -1,64 +0,0 @@
|
|
|
1
|
-
/// <reference types="vinxi/types/server" />
|
|
2
|
-
import { createRouter } from "radix3";
|
|
3
|
-
import { sharedConfig } from "solid-js";
|
|
4
|
-
import fileRoutes from "vinxi/routes";
|
|
5
|
-
|
|
6
|
-
// @ts-ignore
|
|
7
|
-
import { provideRequestEvent } from "solid-js/web/storage";
|
|
8
|
-
import { eventHandler } from "vinxi/server";
|
|
9
|
-
import { getFetchEvent } from "../server/fetchEvent";
|
|
10
|
-
import { APIEvent } from "../server/types";
|
|
11
|
-
|
|
12
|
-
export default eventHandler(h3Event => {
|
|
13
|
-
const event = getFetchEvent(h3Event);
|
|
14
|
-
const match = matchAPIRoute(new URL(event.request.url).pathname, event.request.method);
|
|
15
|
-
if (match) {
|
|
16
|
-
return provideRequestEvent(event, async () => {
|
|
17
|
-
const mod = await match.handler.import();
|
|
18
|
-
const fn = mod[event.request.method];
|
|
19
|
-
(event as APIEvent).params = match.params;
|
|
20
|
-
// @ts-ignore
|
|
21
|
-
sharedConfig.context = { event };
|
|
22
|
-
const res = await fn(event);
|
|
23
|
-
if (res === undefined && event.request.method !== "GET") {
|
|
24
|
-
throw new Error(
|
|
25
|
-
`API handler for ${event.request.method} "${event.request.url}" did not return a response.`
|
|
26
|
-
);
|
|
27
|
-
}
|
|
28
|
-
return res;
|
|
29
|
-
});
|
|
30
|
-
}
|
|
31
|
-
});
|
|
32
|
-
|
|
33
|
-
interface Route {
|
|
34
|
-
path: string;
|
|
35
|
-
id: string;
|
|
36
|
-
type: "api" | "page";
|
|
37
|
-
children?: Route[];
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
const router = createRouter({
|
|
41
|
-
routes: (fileRoutes as unknown as Route[]).reduce((memo, route) => {
|
|
42
|
-
if (route.type !== "api") return memo;
|
|
43
|
-
const path = route.path.replace(/\(.*\)\/?/g, "");
|
|
44
|
-
if (memo[path]) {
|
|
45
|
-
throw new Error(
|
|
46
|
-
`Duplicate API routes for "${path}" found at "${memo[path].route.path}" and "${route.path}"`
|
|
47
|
-
);
|
|
48
|
-
}
|
|
49
|
-
memo[path] = { route };
|
|
50
|
-
return memo;
|
|
51
|
-
}, {} as Record<string, { route: Route }>)
|
|
52
|
-
});
|
|
53
|
-
|
|
54
|
-
function matchAPIRoute(path: string, method: string) {
|
|
55
|
-
const match = router.lookup(path);
|
|
56
|
-
if (match) {
|
|
57
|
-
const handler = match.route[`$${method}`];
|
|
58
|
-
if (handler === "skip" || handler === undefined) return;
|
|
59
|
-
return {
|
|
60
|
-
handler,
|
|
61
|
-
params: match.params
|
|
62
|
-
};
|
|
63
|
-
}
|
|
64
|
-
}
|