@solidjs/start 2.0.0-beta.9 → 2.0.0-rc.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/CHANGELOG.md +149 -1
- package/dist/config/app-root-alias.d.ts +7 -0
- package/dist/config/app-root-alias.js +58 -0
- package/dist/config/constants.d.ts +2 -0
- package/dist/config/constants.js +2 -0
- package/dist/config/env.js +13 -10
- package/dist/config/fs-routes/index.js +3 -3
- package/dist/config/fs-routes/tree-shake.d.ts +27 -0
- package/dist/config/fs-routes/tree-shake.js +226 -33
- package/dist/config/index.d.ts +154 -5
- package/dist/config/index.js +23 -10
- package/dist/config/lazy.js +7 -2
- package/dist/config/manifest.d.ts +3 -0
- package/dist/config/manifest.js +26 -4
- package/dist/directives/remove-unused-variables.js +15 -3
- package/dist/directives/xxhash32.js +5 -13
- package/dist/fns/client.js +14 -5
- package/dist/fns/error-handler.d.ts +34 -0
- package/dist/fns/error-handler.js +10 -0
- package/dist/fns/handler.d.ts +1 -1
- package/dist/fns/handler.js +58 -12
- package/dist/fns/plugins.d.ts +34 -0
- package/dist/fns/plugins.js +33 -0
- package/dist/fns/serialization.d.ts +15 -0
- package/dist/fns/serialization.js +36 -6
- package/dist/middleware/index.js +4 -1
- package/dist/server/StartServer.jsx +1 -1
- package/dist/server/assets/render.jsx +1 -1
- package/dist/server/collect-styles.d.ts +3 -1
- package/dist/server/collect-styles.js +8 -5
- package/dist/server/fetchEvent.d.ts +10 -0
- package/dist/server/fetchEvent.js +10 -0
- package/dist/server/handler.d.ts +3 -4
- package/dist/server/handler.js +22 -56
- package/dist/server/index.d.ts +3 -1
- package/dist/server/index.jsx +1 -0
- package/dist/server/manifest/dev-client-manifest.js +1 -1
- package/dist/server/manifest/dev-ssr-manifest.js +1 -1
- package/dist/server/spa/StartServer.jsx +1 -1
- package/dist/server/spa/handler.d.ts +2 -3
- package/dist/server/spa/handler.js +2 -2
- package/dist/server/strip-path-base.d.ts +1 -0
- package/dist/server/strip-path-base.js +10 -0
- package/dist/server/types.d.ts +12 -1
- package/dist/server/web-stream.d.ts +9 -0
- package/dist/server/web-stream.js +29 -0
- package/dist/shared/ErrorBoundary.jsx +3 -3
- package/dist/shared/clientOnly.js +3 -1
- package/dist/shared/{dev-overlay → dev-toolbar/error-viewer}/CodeView.jsx +3 -3
- package/dist/shared/dev-toolbar/error-viewer/create-stack-frame.js +90 -0
- package/dist/shared/{dev-overlay/DevOverlayDialog.d.ts → dev-toolbar/error-viewer/index.d.ts} +3 -2
- package/dist/shared/{dev-overlay/DevOverlayDialog.jsx → dev-toolbar/error-viewer/index.jsx} +66 -77
- package/dist/shared/dev-toolbar/error-viewer/styles.css +224 -0
- package/dist/shared/dev-toolbar/functions/BlobViewer.css +4 -0
- package/dist/shared/dev-toolbar/functions/BlobViewer.d.ts +6 -0
- package/dist/shared/dev-toolbar/functions/BlobViewer.jsx +37 -0
- package/dist/shared/dev-toolbar/functions/FormDataViewer.d.ts +5 -0
- package/dist/shared/dev-toolbar/functions/FormDataViewer.jsx +23 -0
- package/dist/shared/dev-toolbar/functions/HeadersViewer.css +8 -0
- package/dist/shared/dev-toolbar/functions/HeadersViewer.d.ts +6 -0
- package/dist/shared/dev-toolbar/functions/HeadersViewer.jsx +15 -0
- package/dist/shared/dev-toolbar/functions/HexViewer.css +34 -0
- package/dist/shared/dev-toolbar/functions/HexViewer.d.ts +11 -0
- package/dist/shared/dev-toolbar/functions/HexViewer.jsx +70 -0
- package/dist/shared/dev-toolbar/functions/SerovalValue.css +5 -0
- package/dist/shared/dev-toolbar/functions/SerovalValue.d.ts +7 -0
- package/dist/shared/dev-toolbar/functions/SerovalValue.jsx +10 -0
- package/dist/shared/dev-toolbar/functions/SerovalViewer.css +70 -0
- package/dist/shared/dev-toolbar/functions/SerovalViewer.d.ts +6 -0
- package/dist/shared/dev-toolbar/functions/SerovalViewer.jsx +796 -0
- package/dist/shared/dev-toolbar/functions/URLSearchParamsViewer.d.ts +5 -0
- package/dist/shared/dev-toolbar/functions/URLSearchParamsViewer.jsx +22 -0
- package/dist/shared/dev-toolbar/functions/index.d.ts +13 -0
- package/dist/shared/dev-toolbar/functions/index.jsx +216 -0
- package/dist/shared/dev-toolbar/functions/styles.css +109 -0
- package/dist/shared/dev-toolbar/functions/tracker.d.ts +19 -0
- package/dist/shared/dev-toolbar/functions/tracker.js +29 -0
- package/dist/shared/{dev-overlay → dev-toolbar}/icons.d.ts +9 -0
- package/dist/shared/{dev-overlay → dev-toolbar}/icons.jsx +19 -1
- package/dist/shared/dev-toolbar/index.css +88 -0
- package/dist/shared/dev-toolbar/index.d.ts +6 -0
- package/dist/shared/dev-toolbar/index.jsx +179 -0
- package/dist/shared/ui/Badge.css +33 -0
- package/dist/shared/ui/Badge.d.ts +7 -0
- package/dist/shared/ui/Badge.jsx +7 -0
- package/dist/shared/ui/Button.css +32 -0
- package/dist/shared/ui/Button.d.ts +4 -0
- package/dist/shared/ui/Button.jsx +4 -0
- package/dist/shared/ui/Cascade.css +29 -0
- package/dist/shared/ui/Cascade.d.ts +4 -0
- package/dist/shared/ui/Cascade.jsx +4 -0
- package/dist/shared/ui/Dialog.css +25 -0
- package/dist/shared/ui/Dialog.d.ts +5 -0
- package/dist/shared/ui/Dialog.jsx +5 -0
- package/dist/shared/ui/IconButton.css +39 -0
- package/dist/shared/ui/IconButton.d.ts +4 -0
- package/dist/shared/ui/IconButton.jsx +4 -0
- package/dist/shared/ui/Placeholder.css +12 -0
- package/dist/shared/ui/Placeholder.d.ts +6 -0
- package/dist/shared/ui/Placeholder.jsx +4 -0
- package/dist/shared/ui/Section.css +18 -0
- package/dist/shared/ui/Section.d.ts +9 -0
- package/dist/shared/ui/Section.jsx +10 -0
- package/dist/shared/ui/Select.css +37 -0
- package/dist/shared/ui/Select.d.ts +4 -0
- package/dist/shared/ui/Select.jsx +4 -0
- package/dist/shared/ui/Table.css +22 -0
- package/dist/shared/ui/Table.d.ts +11 -0
- package/dist/shared/ui/Table.jsx +13 -0
- package/dist/shared/ui/Tabs.css +51 -0
- package/dist/shared/ui/Tabs.d.ts +6 -0
- package/dist/shared/ui/Tabs.jsx +6 -0
- package/dist/shared/ui/Text.css +73 -0
- package/dist/shared/ui/Text.d.ts +12 -0
- package/dist/shared/ui/Text.jsx +17 -0
- package/package.json +36 -29
- package/dist/shared/dev-overlay/createStackFrame.js +0 -56
- package/dist/shared/dev-overlay/index.d.ts +0 -5
- package/dist/shared/dev-overlay/index.jsx +0 -39
- package/dist/shared/dev-overlay/styles.css +0 -292
- /package/dist/shared/{dev-overlay → dev-toolbar/error-viewer}/CodeView.d.ts +0 -0
- /package/dist/shared/{dev-overlay/createStackFrame.d.ts → dev-toolbar/error-viewer/create-stack-frame.d.ts} +0 -0
- /package/dist/shared/{dev-overlay → dev-toolbar/error-viewer}/download.d.ts +0 -0
- /package/dist/shared/{dev-overlay → dev-toolbar/error-viewer}/download.js +0 -0
- /package/dist/shared/{dev-overlay → dev-toolbar/error-viewer}/get-source-map.d.ts +0 -0
- /package/dist/shared/{dev-overlay → dev-toolbar/error-viewer}/get-source-map.js +0 -0
package/dist/config/index.js
CHANGED
|
@@ -1,14 +1,16 @@
|
|
|
1
1
|
import { defu } from "defu";
|
|
2
2
|
import { globSync } from "node:fs";
|
|
3
|
-
import { extname, isAbsolute, join } from "node:path";
|
|
3
|
+
import { basename, extname, isAbsolute, join } from "node:path";
|
|
4
4
|
import solid from "vite-plugin-solid";
|
|
5
5
|
import { serverFunctionsPlugin } from "../directives/index.js";
|
|
6
|
+
import { appRootAlias } from "./app-root-alias.js";
|
|
6
7
|
import { boundaryModules } from "./boundary-modules.js";
|
|
7
8
|
import { DEFAULT_EXTENSIONS, VIRTUAL_MODULES, VITE_ENVIRONMENTS } from "./constants.js";
|
|
8
9
|
import { devServer } from "./dev-server.js";
|
|
9
10
|
import { envPlugin } from "./env.js";
|
|
10
11
|
import { SolidStartClientFileRouter, SolidStartServerFileRouter } from "./fs-router.js";
|
|
11
12
|
import { fsRoutes } from "./fs-routes/index.js";
|
|
13
|
+
import { sanitizeChunkFileName, toPickId } from "./fs-routes/tree-shake.js";
|
|
12
14
|
import lazy from "./lazy.js";
|
|
13
15
|
import { manifest } from "./manifest.js";
|
|
14
16
|
import { parseIdQuery } from "./utils.js";
|
|
@@ -61,24 +63,33 @@ export function solidStart(options) {
|
|
|
61
63
|
},
|
|
62
64
|
};
|
|
63
65
|
},
|
|
64
|
-
async config(
|
|
66
|
+
async config(config, env) {
|
|
65
67
|
const clientInput = [handlers.client];
|
|
68
|
+
const clientEntryUrl = env.command === "serve" && config.experimental?.bundledDev
|
|
69
|
+
? `assets/${basename(handlers.client, entryExtension)}.js`
|
|
70
|
+
: handlers.client;
|
|
66
71
|
if (env.command === "build") {
|
|
67
72
|
const clientRouter = globalThis.ROUTERS.client;
|
|
68
73
|
for (const route of await clientRouter.getRoutes()) {
|
|
69
74
|
for (const [key, value] of Object.entries(route)) {
|
|
70
75
|
if (value && key.startsWith("$") && !key.startsWith("$$")) {
|
|
71
|
-
|
|
72
|
-
return `${route.src}?${route.pick.map((p) => `pick=${p}`).join("&")}`;
|
|
73
|
-
}
|
|
74
|
-
clientInput.push(toRouteId(value));
|
|
76
|
+
clientInput.push(toPickId(value.src, value.pick));
|
|
75
77
|
}
|
|
76
78
|
}
|
|
77
79
|
}
|
|
78
80
|
}
|
|
79
81
|
return {
|
|
80
82
|
appType: "custom",
|
|
81
|
-
build: {
|
|
83
|
+
build: {
|
|
84
|
+
assetsDir: "_build/assets",
|
|
85
|
+
rolldownOptions: {
|
|
86
|
+
output: {
|
|
87
|
+
// Keeps route chunks named after their file rather than after
|
|
88
|
+
// the `?pick=...` id that addresses them. See toPickId.
|
|
89
|
+
sanitizeFileName: sanitizeChunkFileName,
|
|
90
|
+
},
|
|
91
|
+
},
|
|
92
|
+
},
|
|
82
93
|
optimizeDeps: {
|
|
83
94
|
// Suppress TS errors from Vite 7 types when configuring Vite 8's Rolldown
|
|
84
95
|
...{
|
|
@@ -96,7 +107,7 @@ export function solidStart(options) {
|
|
|
96
107
|
write: true,
|
|
97
108
|
manifest: true,
|
|
98
109
|
outDir: "dist/client",
|
|
99
|
-
|
|
110
|
+
rolldownOptions: {
|
|
100
111
|
input: clientInput,
|
|
101
112
|
treeshake: true,
|
|
102
113
|
preserveEntrySignatures: "exports-only",
|
|
@@ -110,7 +121,7 @@ export function solidStart(options) {
|
|
|
110
121
|
write: true,
|
|
111
122
|
manifest: true,
|
|
112
123
|
copyPublicDir: false,
|
|
113
|
-
|
|
124
|
+
rolldownOptions: {
|
|
114
125
|
input: handlers.server,
|
|
115
126
|
},
|
|
116
127
|
outDir: "dist/server",
|
|
@@ -123,7 +134,6 @@ export function solidStart(options) {
|
|
|
123
134
|
resolve: {
|
|
124
135
|
alias: {
|
|
125
136
|
"@solidjs/start/server/entry": handlers.server,
|
|
126
|
-
"~": join(process.cwd(), start.appRoot),
|
|
127
137
|
...(!start.ssr
|
|
128
138
|
? {
|
|
129
139
|
"@solidjs/start/server": "@solidjs/start/server/spa",
|
|
@@ -149,7 +159,9 @@ export function solidStart(options) {
|
|
|
149
159
|
// esbuild receives a valid JS string literal for the define value
|
|
150
160
|
"import.meta.env.START_APP_ENTRY": JSON.stringify(appEntryPath),
|
|
151
161
|
"import.meta.env.START_CLIENT_ENTRY": JSON.stringify(handlers.client),
|
|
162
|
+
"import.meta.env.START_CLIENT_ENTRY_URL": JSON.stringify(clientEntryUrl),
|
|
152
163
|
"import.meta.env.START_DEV_OVERLAY": JSON.stringify(start.devOverlay),
|
|
164
|
+
"import.meta.env.SERVER_BASE_URL": JSON.stringify(config.server?.baseURL ?? ""),
|
|
153
165
|
"import.meta.env.SEROVAL_MODE": JSON.stringify(start.serialization?.mode || "json"),
|
|
154
166
|
},
|
|
155
167
|
builder: {
|
|
@@ -170,6 +182,7 @@ export function solidStart(options) {
|
|
|
170
182
|
};
|
|
171
183
|
},
|
|
172
184
|
},
|
|
185
|
+
appRootAlias(root, start.appRoot),
|
|
173
186
|
manifest(start),
|
|
174
187
|
fsRoutes({
|
|
175
188
|
routers: {
|
package/dist/config/lazy.js
CHANGED
|
@@ -58,12 +58,17 @@ const lazy = () => {
|
|
|
58
58
|
if (this.environment.name !== VITE_ENVIRONMENTS.client)
|
|
59
59
|
return;
|
|
60
60
|
for (const chunk of Object.values(bundle)) {
|
|
61
|
-
if (chunk.type !== "chunk" ||
|
|
61
|
+
if (chunk.type !== "chunk" || chunk.facadeModuleId)
|
|
62
|
+
continue;
|
|
63
|
+
if (!chunk.viteMetadata?.importedCss.size)
|
|
64
|
+
continue;
|
|
65
|
+
const moduleIds = chunk.moduleIds.filter(id => !id.endsWith("css"));
|
|
66
|
+
if (moduleIds.length <= 1)
|
|
62
67
|
continue;
|
|
63
68
|
// Has to follow Vites implementation:
|
|
64
69
|
// https://github.com/vitejs/vite/blob/4be37a8389c67873880f826b01fe40137e1c29a7/packages/vite/src/node/plugins/manifest.ts#L179
|
|
65
70
|
const chunkName = `_${basename(chunk.fileName)}`;
|
|
66
|
-
for (const id of
|
|
71
|
+
for (const id of moduleIds) {
|
|
67
72
|
sharedChunkNames[id] = chunkName;
|
|
68
73
|
}
|
|
69
74
|
}
|
|
@@ -1,3 +1,6 @@
|
|
|
1
1
|
import { type PluginOption } from "vite";
|
|
2
2
|
import { type SolidStartOptions } from "./index.ts";
|
|
3
|
+
type StyleFilterOptions = NonNullable<SolidStartOptions["css"]>["filter"];
|
|
4
|
+
export declare function createStyleFilter(options?: StyleFilterOptions): (id: unknown) => boolean;
|
|
3
5
|
export declare function manifest(start: SolidStartOptions): PluginOption;
|
|
6
|
+
export {};
|
package/dist/config/manifest.js
CHANGED
|
@@ -1,12 +1,17 @@
|
|
|
1
1
|
import fs from "node:fs";
|
|
2
2
|
import path from "node:path";
|
|
3
|
-
import { version as viteVersion } from "vite";
|
|
3
|
+
import { createFilter, version as viteVersion } from "vite";
|
|
4
4
|
import { fileURLToPath } from "node:url";
|
|
5
5
|
import { findStylesInModuleGraph } from "../server/collect-styles.js";
|
|
6
6
|
import { VIRTUAL_MODULES } from "./constants.js";
|
|
7
7
|
import { wrapId } from "./vite-utils.js";
|
|
8
|
+
const DEFAULT_STYLE_EXCLUDE = /node_modules/;
|
|
9
|
+
export function createStyleFilter(options) {
|
|
10
|
+
return createFilter(options?.include || [], options?.exclude || DEFAULT_STYLE_EXCLUDE);
|
|
11
|
+
}
|
|
8
12
|
export function manifest(start) {
|
|
9
13
|
let devServer = undefined;
|
|
14
|
+
const styleFilter = createStyleFilter(start.css?.filter);
|
|
10
15
|
return {
|
|
11
16
|
name: "solid-start:manifest-plugin",
|
|
12
17
|
enforce: "pre",
|
|
@@ -28,6 +33,18 @@ export function manifest(start) {
|
|
|
28
33
|
return await this.resolve(start.middleware);
|
|
29
34
|
return `\0${VIRTUAL_MODULES.middleware}`;
|
|
30
35
|
}
|
|
36
|
+
if (id === VIRTUAL_MODULES.serovalPlugins) {
|
|
37
|
+
const plugins = start.serialization?.plugins;
|
|
38
|
+
if (plugins)
|
|
39
|
+
return await this.resolve(plugins);
|
|
40
|
+
return `\0${VIRTUAL_MODULES.serovalPlugins}`;
|
|
41
|
+
}
|
|
42
|
+
if (id === VIRTUAL_MODULES.serverFnErrorHandler) {
|
|
43
|
+
const onError = start.serverFunctions?.onError;
|
|
44
|
+
if (onError)
|
|
45
|
+
return await this.resolve(onError);
|
|
46
|
+
return `\0${VIRTUAL_MODULES.serverFnErrorHandler}`;
|
|
47
|
+
}
|
|
31
48
|
},
|
|
32
49
|
async load(id) {
|
|
33
50
|
if (id === `\0${VIRTUAL_MODULES.clientViteManifest}`) {
|
|
@@ -40,7 +57,7 @@ export function manifest(start) {
|
|
|
40
57
|
if (!entry)
|
|
41
58
|
throw new Error("No client entry found");
|
|
42
59
|
let rawManifest;
|
|
43
|
-
const viteMajor = parseInt(viteVersion.split(
|
|
60
|
+
const viteMajor = parseInt(viteVersion.split(".")[0], 10);
|
|
44
61
|
const manifestKey = Object.keys(globalThis.START_CLIENT_BUNDLE).find(k => k.endsWith("manifest.json"));
|
|
45
62
|
if (manifestKey && viteMajor < 8) {
|
|
46
63
|
const manifestAsset = globalThis.START_CLIENT_BUNDLE[manifestKey];
|
|
@@ -53,7 +70,8 @@ export function manifest(start) {
|
|
|
53
70
|
if (devServer?.environments?.client?.config?.build?.outDir) {
|
|
54
71
|
outDir = devServer.environments.client.config.build.outDir;
|
|
55
72
|
}
|
|
56
|
-
else if (this.environment?.config?.build?.outDir &&
|
|
73
|
+
else if (this.environment?.config?.build?.outDir &&
|
|
74
|
+
this.environment?.config?.consumer === "client") {
|
|
57
75
|
outDir = this.environment.config.build.outDir;
|
|
58
76
|
}
|
|
59
77
|
else if (globalThis.START_CLIENT_OUT_DIR) {
|
|
@@ -75,6 +93,10 @@ export function manifest(start) {
|
|
|
75
93
|
}
|
|
76
94
|
else if (id === `\0${VIRTUAL_MODULES.middleware}`)
|
|
77
95
|
return "export default {};";
|
|
96
|
+
else if (id === `\0${VIRTUAL_MODULES.serovalPlugins}`)
|
|
97
|
+
return "export default [];";
|
|
98
|
+
else if (id === `\0${VIRTUAL_MODULES.serverFnErrorHandler}`)
|
|
99
|
+
return "export default undefined;";
|
|
78
100
|
else if (id.startsWith("/@manifest")) {
|
|
79
101
|
if (this.environment.mode !== "dev")
|
|
80
102
|
throw new Error("@manifest queries are only allowed in dev");
|
|
@@ -91,7 +113,7 @@ export function manifest(start) {
|
|
|
91
113
|
// Client env does not have css dependencies in mod.transformResult
|
|
92
114
|
// Aalways use ssr env instead, to prevent hydration mismatches
|
|
93
115
|
const env = devServer.environments["ssr"];
|
|
94
|
-
const styles = await findStylesInModuleGraph(env, id);
|
|
116
|
+
const styles = await findStylesInModuleGraph(env, id, styleFilter);
|
|
95
117
|
const cssAssets = Object.entries(styles).map(([key, value]) => `{
|
|
96
118
|
tag: "style",
|
|
97
119
|
attrs: {
|
|
@@ -8,10 +8,22 @@ function isInvalidForRemoval(path) {
|
|
|
8
8
|
// This one is for destructured variables
|
|
9
9
|
let target = path;
|
|
10
10
|
if (isPathValid(path, t.isVariableDeclarator)) {
|
|
11
|
-
target = path.get(
|
|
11
|
+
target = path.get("id");
|
|
12
12
|
}
|
|
13
13
|
return isPathValid(target, t.isObjectPattern) || isPathValid(target, t.isArrayPattern);
|
|
14
14
|
}
|
|
15
|
+
function countValidImport(node) {
|
|
16
|
+
if (node.importKind === "type") {
|
|
17
|
+
return 0;
|
|
18
|
+
}
|
|
19
|
+
let count = 0;
|
|
20
|
+
for (const specifier of node.specifiers) {
|
|
21
|
+
if (specifier.type !== "ImportSpecifier" || specifier.importKind === "value") {
|
|
22
|
+
count += 1;
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
return count;
|
|
26
|
+
}
|
|
15
27
|
export function removeUnusedVariables(program) {
|
|
16
28
|
// TODO(Alexis):
|
|
17
29
|
// This implementation is simple but slow
|
|
@@ -34,7 +46,7 @@ export function removeUnusedVariables(program) {
|
|
|
34
46
|
if (binding.references === 0 && !binding.path.removed) {
|
|
35
47
|
const parent = binding.path.parentPath;
|
|
36
48
|
if (isPathValid(parent, t.isImportDeclaration)) {
|
|
37
|
-
if (parent.node
|
|
49
|
+
if (countValidImport(parent.node) <= 1) {
|
|
38
50
|
parent.remove();
|
|
39
51
|
}
|
|
40
52
|
else {
|
|
@@ -42,7 +54,7 @@ export function removeUnusedVariables(program) {
|
|
|
42
54
|
}
|
|
43
55
|
dirty = true;
|
|
44
56
|
}
|
|
45
|
-
else if (!
|
|
57
|
+
else if (!isInvalidForRemoval(binding.path)) {
|
|
46
58
|
binding.path.remove();
|
|
47
59
|
dirty = true;
|
|
48
60
|
}
|
|
@@ -34,7 +34,7 @@ function toUtf8(text) {
|
|
|
34
34
|
* @param seed - optional seed (32-bit unsigned);
|
|
35
35
|
*/
|
|
36
36
|
export default function xxHash32(buffer, seed = 0) {
|
|
37
|
-
buffer = typeof buffer ===
|
|
37
|
+
buffer = typeof buffer === "string" ? toUtf8(buffer) : buffer;
|
|
38
38
|
const b = buffer;
|
|
39
39
|
/*
|
|
40
40
|
Step 1. Initialize internal accumulators
|
|
@@ -144,9 +144,7 @@ export default function xxHash32(buffer, seed = 0) {
|
|
|
144
144
|
const laneP = laneN0 * PRIME32_3 + ((laneN1 * PRIME32_3) << 16);
|
|
145
145
|
acc = (acc + laneP) & 0xffffffff;
|
|
146
146
|
acc = (acc << 17) | (acc >>> 15);
|
|
147
|
-
acc =
|
|
148
|
-
((acc & 0xffff) * PRIME32_4 + (((acc >>> 16) * PRIME32_4) << 16)) &
|
|
149
|
-
0xffffffff;
|
|
147
|
+
acc = ((acc & 0xffff) * PRIME32_4 + (((acc >>> 16) * PRIME32_4) << 16)) & 0xffffffff;
|
|
150
148
|
}
|
|
151
149
|
/*
|
|
152
150
|
```
|
|
@@ -162,9 +160,7 @@ export default function xxHash32(buffer, seed = 0) {
|
|
|
162
160
|
const lane = b[offset];
|
|
163
161
|
acc += lane * PRIME32_5;
|
|
164
162
|
acc = (acc << 11) | (acc >>> 21);
|
|
165
|
-
acc =
|
|
166
|
-
((acc & 0xffff) * PRIME32_1 + (((acc >>> 16) * PRIME32_1) << 16)) &
|
|
167
|
-
0xffffffff;
|
|
163
|
+
acc = ((acc & 0xffff) * PRIME32_1 + (((acc >>> 16) * PRIME32_1) << 16)) & 0xffffffff;
|
|
168
164
|
}
|
|
169
165
|
/*
|
|
170
166
|
Step 6. Final mix (avalanche)
|
|
@@ -180,13 +176,9 @@ export default function xxHash32(buffer, seed = 0) {
|
|
|
180
176
|
```
|
|
181
177
|
*/
|
|
182
178
|
acc ^= acc >>> 15;
|
|
183
|
-
acc =
|
|
184
|
-
(((acc & 0xffff) * PRIME32_2) & 0xffffffff) +
|
|
185
|
-
(((acc >>> 16) * PRIME32_2) << 16);
|
|
179
|
+
acc = (((acc & 0xffff) * PRIME32_2) & 0xffffffff) + (((acc >>> 16) * PRIME32_2) << 16);
|
|
186
180
|
acc ^= acc >>> 13;
|
|
187
|
-
acc =
|
|
188
|
-
(((acc & 0xffff) * PRIME32_3) & 0xffffffff) +
|
|
189
|
-
(((acc >>> 16) * PRIME32_3) << 16);
|
|
181
|
+
acc = (((acc & 0xffff) * PRIME32_3) & 0xffffffff) + (((acc >>> 16) * PRIME32_3) << 16);
|
|
190
182
|
acc ^= acc >>> 16;
|
|
191
183
|
// turn any negatives back into a positive number;
|
|
192
184
|
return acc < 0 ? acc + 4294967296 : acc;
|
package/dist/fns/client.js
CHANGED
|
@@ -1,10 +1,11 @@
|
|
|
1
|
+
import { pushRequest, pushResponse } from "../shared/dev-toolbar/functions/tracker.js";
|
|
1
2
|
import {
|
|
2
3
|
// serializeToJSONStream,
|
|
3
4
|
serializeToJSONString, } from "./serialization.js";
|
|
4
|
-
import { BODY_FORMAT_KEY, BodyFormat, extractBody, getHeadersAndBody
|
|
5
|
+
import { BODY_FORMAT_KEY, BodyFormat, extractBody, getHeadersAndBody } from "./shared.js";
|
|
5
6
|
let INSTANCE = 0;
|
|
6
|
-
function createRequest(base, id, instance, options) {
|
|
7
|
-
|
|
7
|
+
async function createRequest(base, id, instance, options) {
|
|
8
|
+
const request = new Request(base, {
|
|
8
9
|
method: "POST",
|
|
9
10
|
...options,
|
|
10
11
|
headers: {
|
|
@@ -13,6 +14,14 @@ function createRequest(base, id, instance, options) {
|
|
|
13
14
|
"X-Server-Instance": instance,
|
|
14
15
|
},
|
|
15
16
|
});
|
|
17
|
+
if (import.meta.env.DEV) {
|
|
18
|
+
pushRequest(id, instance, request.clone());
|
|
19
|
+
}
|
|
20
|
+
const response = await fetch(request);
|
|
21
|
+
if (import.meta.env.DEV) {
|
|
22
|
+
pushResponse(id, instance, response.clone());
|
|
23
|
+
}
|
|
24
|
+
return response;
|
|
16
25
|
}
|
|
17
26
|
async function initializeResponse(base, id, instance, options, args) {
|
|
18
27
|
// No args, skip serialization
|
|
@@ -63,8 +72,8 @@ async function fetchServerFunction(base, id, options, args) {
|
|
|
63
72
|
return response;
|
|
64
73
|
}
|
|
65
74
|
const result = await extractBody(instance, true, response.clone());
|
|
66
|
-
if (response.headers.has("X-Error")) {
|
|
67
|
-
throw result;
|
|
75
|
+
if (response.headers.has("X-Error") || response.status >= 500) {
|
|
76
|
+
throw result ?? new Error(`Server function call failed with status ${response.status}`);
|
|
68
77
|
}
|
|
69
78
|
return result;
|
|
70
79
|
}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Handles whatever a server function threw, before it reaches the client. Use
|
|
3
|
+
* it to type the default export of the module named by the
|
|
4
|
+
* `serverFunctions.onError` option in `vite.config.ts`.
|
|
5
|
+
*
|
|
6
|
+
* The handler may be asynchronous. SolidStart awaits its return value before
|
|
7
|
+
* sending the error to the client. If the handler throws or rejects, SolidStart
|
|
8
|
+
* sends the original thrown value instead.
|
|
9
|
+
*
|
|
10
|
+
* @param thrown The value the server function threw. This is a `Response` when
|
|
11
|
+
* the server function threw control flow such as a `redirect()`.
|
|
12
|
+
* @returns `undefined` (or `null`) to send `thrown` unchanged, a `Response` to
|
|
13
|
+
* preserve control flow, any other replacement value, or a promise resolving
|
|
14
|
+
* to any of those values.
|
|
15
|
+
*
|
|
16
|
+
* @example
|
|
17
|
+
* ```ts
|
|
18
|
+
* // src/server-fn-error.ts
|
|
19
|
+
* import type { ServerFunctionErrorHandler } from "@solidjs/start/server";
|
|
20
|
+
* import { captureException } from "./your-monitoring-client";
|
|
21
|
+
*
|
|
22
|
+
* const onServerFunctionError: ServerFunctionErrorHandler = thrown => {
|
|
23
|
+
* // redirect() throws a Response, so returning it preserves that control flow.
|
|
24
|
+
* if (thrown instanceof Response) return thrown;
|
|
25
|
+
*
|
|
26
|
+
* captureException(thrown); // or console.error, or any reporter
|
|
27
|
+
* return new Error("Something went wrong");
|
|
28
|
+
* };
|
|
29
|
+
*
|
|
30
|
+
* export default onServerFunctionError;
|
|
31
|
+
* ```
|
|
32
|
+
*/
|
|
33
|
+
export type ServerFunctionErrorHandler = (thrown: unknown) => unknown;
|
|
34
|
+
export declare function applyServerFunctionErrorHandler(thrown: unknown): Promise<unknown>;
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import onServerFunctionError from "solid-start:server-fn-error-handler";
|
|
2
|
+
export async function applyServerFunctionErrorHandler(thrown) {
|
|
3
|
+
try {
|
|
4
|
+
const replacement = await onServerFunctionError?.(thrown);
|
|
5
|
+
return replacement ?? thrown;
|
|
6
|
+
}
|
|
7
|
+
catch {
|
|
8
|
+
return thrown;
|
|
9
|
+
}
|
|
10
|
+
}
|
package/dist/fns/handler.d.ts
CHANGED
|
@@ -2,4 +2,4 @@ import { type H3Event } from "h3";
|
|
|
2
2
|
import "solid-start:server-fn-manifest";
|
|
3
3
|
import type { FetchEvent } from "../server/types.ts";
|
|
4
4
|
export declare function handleServerFunction(h3Event: H3Event): Promise<unknown>;
|
|
5
|
-
export declare function createSingleFlightHeaders(sourceEvent: FetchEvent): Headers;
|
|
5
|
+
export declare function createSingleFlightHeaders(sourceEvent: FetchEvent, result?: unknown): Headers;
|
package/dist/fns/handler.js
CHANGED
|
@@ -17,6 +17,7 @@ import { deserializeFromJSONString, serializeToJSONStream, serializeToJSStream,
|
|
|
17
17
|
import { BODY_FORMAT_KEY, BodyFormat, extractBody, getHeadersAndBody } from "./shared.js";
|
|
18
18
|
import "solid-start:server-fn-manifest";
|
|
19
19
|
import { getServerFunction, hasServerFunction } from "./registration.js";
|
|
20
|
+
import { applyServerFunctionErrorHandler } from "./error-handler.js";
|
|
20
21
|
import { getExpectedRedirectStatus } from "../server/util.js";
|
|
21
22
|
export async function handleServerFunction(h3Event) {
|
|
22
23
|
const event = getFetchEvent(h3Event);
|
|
@@ -109,7 +110,7 @@ export async function handleServerFunction(h3Event) {
|
|
|
109
110
|
}
|
|
110
111
|
// handle no JS success case
|
|
111
112
|
if (!instance)
|
|
112
|
-
return handleNoJS(result, request, parsed);
|
|
113
|
+
return await handleNoJS(result, request, parsed);
|
|
113
114
|
const body = getHeadersAndBody(result);
|
|
114
115
|
if (body) {
|
|
115
116
|
return new Response(body.body, {
|
|
@@ -121,10 +122,10 @@ export async function handleServerFunction(h3Event) {
|
|
|
121
122
|
h3Event.res.headers.set("content-type", "text/javascript");
|
|
122
123
|
return serializeToJSStream(instance, result);
|
|
123
124
|
}
|
|
124
|
-
h3Event.res.headers.set("content-type", "text/plain");
|
|
125
125
|
return serializeToJSONStream(result);
|
|
126
126
|
}
|
|
127
127
|
catch (x) {
|
|
128
|
+
x = await applyServerFunctionErrorHandler(x);
|
|
128
129
|
if (x instanceof Response) {
|
|
129
130
|
if (singleFlight && instance) {
|
|
130
131
|
x = await handleSingleFlight(event, x);
|
|
@@ -144,10 +145,10 @@ export async function handleServerFunction(h3Event) {
|
|
|
144
145
|
}
|
|
145
146
|
else if (instance) {
|
|
146
147
|
const error = x instanceof Error ? x.message : typeof x === "string" ? x : "true";
|
|
147
|
-
h3Event.res.headers.set("X-Error", error
|
|
148
|
+
h3Event.res.headers.set("X-Error", toHeaderValue(error));
|
|
148
149
|
}
|
|
149
150
|
else {
|
|
150
|
-
x = handleNoJS(x, request, parsed, true);
|
|
151
|
+
x = await handleNoJS(x, request, parsed, true);
|
|
151
152
|
}
|
|
152
153
|
if (instance) {
|
|
153
154
|
const body = getHeadersAndBody(x);
|
|
@@ -158,7 +159,7 @@ export async function handleServerFunction(h3Event) {
|
|
|
158
159
|
headers.set("X-Error", errorHeader);
|
|
159
160
|
}
|
|
160
161
|
return new Response(body.body, {
|
|
161
|
-
headers
|
|
162
|
+
headers,
|
|
162
163
|
});
|
|
163
164
|
}
|
|
164
165
|
h3Event.res.headers.set(BODY_FORMAT_KEY, BodyFormat.Seroval);
|
|
@@ -166,13 +167,36 @@ export async function handleServerFunction(h3Event) {
|
|
|
166
167
|
h3Event.res.headers.set("content-type", "text/javascript");
|
|
167
168
|
return serializeToJSStream(instance, x);
|
|
168
169
|
}
|
|
169
|
-
h3Event.res.headers.set("content-type", "text/plain");
|
|
170
170
|
return serializeToJSONStream(x);
|
|
171
171
|
}
|
|
172
172
|
return x;
|
|
173
173
|
}
|
|
174
174
|
}
|
|
175
|
-
|
|
175
|
+
// header values must be ByteString-safe (no chars > 0xFF), otherwise Headers.set throws
|
|
176
|
+
function toHeaderValue(value) {
|
|
177
|
+
const stripped = value.replace(/[\r\n]+/g, "");
|
|
178
|
+
try {
|
|
179
|
+
return /[^\x00-\xFF]/.test(stripped) ? encodeURIComponent(stripped) : stripped;
|
|
180
|
+
}
|
|
181
|
+
catch {
|
|
182
|
+
// encodeURIComponent throws on lone surrogates
|
|
183
|
+
return "true";
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
function getRefererLocation(request, url) {
|
|
187
|
+
const referer = request.headers.get("referer");
|
|
188
|
+
try {
|
|
189
|
+
if (referer)
|
|
190
|
+
return new URL(referer).toString();
|
|
191
|
+
}
|
|
192
|
+
catch {
|
|
193
|
+
// fall through to the app root below
|
|
194
|
+
}
|
|
195
|
+
// no usable referer (e.g. a no-referrer policy): the app root still beats
|
|
196
|
+
// leaving the browser sitting on the server function endpoint
|
|
197
|
+
return new URL(import.meta.env.BASE_URL, url.origin).toString();
|
|
198
|
+
}
|
|
199
|
+
async function handleNoJS(result, request, parsed, thrown) {
|
|
176
200
|
const url = new URL(request.url);
|
|
177
201
|
const isError = result instanceof Error;
|
|
178
202
|
let statusCode = 302;
|
|
@@ -183,10 +207,19 @@ function handleNoJS(result, request, parsed, thrown) {
|
|
|
183
207
|
headers.set(`Location`, new URL(result.headers.get("Location"), url.origin + import.meta.env.BASE_URL).toString());
|
|
184
208
|
statusCode = getExpectedRedirectStatus(result);
|
|
185
209
|
}
|
|
210
|
+
else {
|
|
211
|
+
// responses that carry a value rather than a destination (json(), reload())
|
|
212
|
+
// still have to send the browser back to the page the form came from
|
|
213
|
+
headers.set("Location", getRefererLocation(request, url));
|
|
214
|
+
}
|
|
215
|
+
// the body is dropped from the redirect, so don't advertise its type
|
|
216
|
+
headers.delete("Content-Type");
|
|
217
|
+
// mirror the JS path: the flash cookie carries the value, not the Response
|
|
218
|
+
result = result.customBody ? await result.customBody() : null;
|
|
186
219
|
}
|
|
187
220
|
else
|
|
188
221
|
headers = new Headers({
|
|
189
|
-
Location:
|
|
222
|
+
Location: getRefererLocation(request, url),
|
|
190
223
|
});
|
|
191
224
|
if (result) {
|
|
192
225
|
headers.append("Set-Cookie", `flash=${encodeURIComponent(JSON.stringify({
|
|
@@ -194,7 +227,9 @@ function handleNoJS(result, request, parsed, thrown) {
|
|
|
194
227
|
result: isError ? result.message : result,
|
|
195
228
|
thrown: thrown,
|
|
196
229
|
error: isError,
|
|
197
|
-
input:
|
|
230
|
+
input: parsed.length
|
|
231
|
+
? [...parsed.slice(0, -1), [...parsed[parsed.length - 1].entries()]]
|
|
232
|
+
: [],
|
|
198
233
|
}))}; Secure; HttpOnly;`);
|
|
199
234
|
}
|
|
200
235
|
return new Response(null, {
|
|
@@ -203,13 +238,19 @@ function handleNoJS(result, request, parsed, thrown) {
|
|
|
203
238
|
});
|
|
204
239
|
}
|
|
205
240
|
let App;
|
|
206
|
-
export function createSingleFlightHeaders(sourceEvent) {
|
|
241
|
+
export function createSingleFlightHeaders(sourceEvent, result) {
|
|
207
242
|
// cookie handling logic is pretty simplistic so this might be imperfect
|
|
208
243
|
// unclear if h3 internals are available on all platforms but we need a way to
|
|
209
244
|
// update request headers on the underlying H3 event.
|
|
210
245
|
const headers = new Headers(sourceEvent.request.headers);
|
|
211
246
|
const cookies = parseCookies(sourceEvent.nativeEvent);
|
|
212
247
|
const SetCookies = sourceEvent.response.headers.getSetCookie();
|
|
248
|
+
// cookies attached to the returned/thrown response (eg. `redirect(to, { headers })`)
|
|
249
|
+
// haven't been merged onto the event response yet, but a browser round trip would
|
|
250
|
+
// have sent them back with the next request, so apply them here too. They come after
|
|
251
|
+
// the ones on the event response, so they win on conflict.
|
|
252
|
+
if (result instanceof Response)
|
|
253
|
+
SetCookies.push(...result.headers.getSetCookie());
|
|
213
254
|
headers.delete("cookie");
|
|
214
255
|
// let useH3Internals = false;
|
|
215
256
|
// if (sourceEvent.nativeEvent.node?.req) {
|
|
@@ -219,7 +260,12 @@ export function createSingleFlightHeaders(sourceEvent) {
|
|
|
219
260
|
SetCookies.forEach(cookie => {
|
|
220
261
|
if (!cookie)
|
|
221
262
|
return;
|
|
222
|
-
|
|
263
|
+
// `parseSetCookie` returns undefined for forbidden cookie names and for
|
|
264
|
+
// cookies with an empty name and value.
|
|
265
|
+
const parsed = parseSetCookie(cookie);
|
|
266
|
+
if (!parsed)
|
|
267
|
+
return;
|
|
268
|
+
const { maxAge, expires, name, value } = parsed;
|
|
223
269
|
if (maxAge != null && maxAge <= 0) {
|
|
224
270
|
delete cookies[name];
|
|
225
271
|
return;
|
|
@@ -248,7 +294,7 @@ async function handleSingleFlight(sourceEvent, result) {
|
|
|
248
294
|
}
|
|
249
295
|
const event = { ...sourceEvent };
|
|
250
296
|
event.request = new Request(url, {
|
|
251
|
-
headers: createSingleFlightHeaders(sourceEvent),
|
|
297
|
+
headers: createSingleFlightHeaders(sourceEvent, result),
|
|
252
298
|
});
|
|
253
299
|
return await provideRequestEvent(event, async () => {
|
|
254
300
|
await createPageEvent(event);
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Re-exports Seroval's plugin authoring API for use with the
|
|
3
|
+
* `serialization.plugins` option in `vite.config.ts`.
|
|
4
|
+
*
|
|
5
|
+
* Importing from here rather than depending on `seroval` directly keeps plugin
|
|
6
|
+
* authors on the exact Seroval version SolidStart serializes with. A mismatched
|
|
7
|
+
* version would not fail the build: it would produce plugin nodes the other end
|
|
8
|
+
* of the wire can't interpret.
|
|
9
|
+
*
|
|
10
|
+
* @see https://github.com/solidjs/solid-start/issues/1474
|
|
11
|
+
*
|
|
12
|
+
* @example
|
|
13
|
+
* ```ts
|
|
14
|
+
* // src/seroval-plugins.ts
|
|
15
|
+
* import { createPlugin } from "@solidjs/start/serialization";
|
|
16
|
+
* import { ObjectId } from "mongodb";
|
|
17
|
+
*
|
|
18
|
+
* const ObjectIdPlugin = createPlugin<ObjectId, { hex: any }>({
|
|
19
|
+
* tag: "app/ObjectId",
|
|
20
|
+
* test: value => value instanceof ObjectId,
|
|
21
|
+
* parse: {
|
|
22
|
+
* sync: (value, ctx) => ({ hex: ctx.parse(value.toHexString()) }),
|
|
23
|
+
* async: async (value, ctx) => ({ hex: await ctx.parse(value.toHexString()) }),
|
|
24
|
+
* stream: (value, ctx) => ({ hex: ctx.parse(value.toHexString()) }),
|
|
25
|
+
* },
|
|
26
|
+
* serialize: (node, ctx) => `globalThis.ObjectId(${ctx.serialize(node.hex)})`,
|
|
27
|
+
* deserialize: (node, ctx) => new ObjectId(ctx.deserialize(node.hex) as string),
|
|
28
|
+
* });
|
|
29
|
+
*
|
|
30
|
+
* export default [ObjectIdPlugin];
|
|
31
|
+
* ```
|
|
32
|
+
*/
|
|
33
|
+
export { createPlugin, OpaqueReference } from "seroval";
|
|
34
|
+
export type { AsyncParsePluginContext, DeserializePluginContext, Plugin, PluginData, PluginInfo, SerializePluginContext, SerovalNode, StreamParsePluginContext, SyncParsePluginContext, } from "seroval";
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Re-exports Seroval's plugin authoring API for use with the
|
|
3
|
+
* `serialization.plugins` option in `vite.config.ts`.
|
|
4
|
+
*
|
|
5
|
+
* Importing from here rather than depending on `seroval` directly keeps plugin
|
|
6
|
+
* authors on the exact Seroval version SolidStart serializes with. A mismatched
|
|
7
|
+
* version would not fail the build: it would produce plugin nodes the other end
|
|
8
|
+
* of the wire can't interpret.
|
|
9
|
+
*
|
|
10
|
+
* @see https://github.com/solidjs/solid-start/issues/1474
|
|
11
|
+
*
|
|
12
|
+
* @example
|
|
13
|
+
* ```ts
|
|
14
|
+
* // src/seroval-plugins.ts
|
|
15
|
+
* import { createPlugin } from "@solidjs/start/serialization";
|
|
16
|
+
* import { ObjectId } from "mongodb";
|
|
17
|
+
*
|
|
18
|
+
* const ObjectIdPlugin = createPlugin<ObjectId, { hex: any }>({
|
|
19
|
+
* tag: "app/ObjectId",
|
|
20
|
+
* test: value => value instanceof ObjectId,
|
|
21
|
+
* parse: {
|
|
22
|
+
* sync: (value, ctx) => ({ hex: ctx.parse(value.toHexString()) }),
|
|
23
|
+
* async: async (value, ctx) => ({ hex: await ctx.parse(value.toHexString()) }),
|
|
24
|
+
* stream: (value, ctx) => ({ hex: ctx.parse(value.toHexString()) }),
|
|
25
|
+
* },
|
|
26
|
+
* serialize: (node, ctx) => `globalThis.ObjectId(${ctx.serialize(node.hex)})`,
|
|
27
|
+
* deserialize: (node, ctx) => new ObjectId(ctx.deserialize(node.hex) as string),
|
|
28
|
+
* });
|
|
29
|
+
*
|
|
30
|
+
* export default [ObjectIdPlugin];
|
|
31
|
+
* ```
|
|
32
|
+
*/
|
|
33
|
+
export { createPlugin, OpaqueReference } from "seroval";
|
|
@@ -1,5 +1,20 @@
|
|
|
1
1
|
export declare function serializeToJSStream(id: string, value: any): ReadableStream<any>;
|
|
2
2
|
export declare function serializeToJSONStream(value: any): ReadableStream<any>;
|
|
3
|
+
export declare class SerovalChunkReader {
|
|
4
|
+
reader: ReadableStreamDefaultReader<Uint8Array>;
|
|
5
|
+
buffer: Uint8Array;
|
|
6
|
+
done: boolean;
|
|
7
|
+
constructor(stream: ReadableStream<Uint8Array>);
|
|
8
|
+
readChunk(): Promise<void>;
|
|
9
|
+
next(): Promise<{
|
|
10
|
+
done: true;
|
|
11
|
+
value: undefined;
|
|
12
|
+
} | {
|
|
13
|
+
done: false;
|
|
14
|
+
value: string;
|
|
15
|
+
}>;
|
|
16
|
+
drain(interpret: (chunk: string) => void): Promise<void>;
|
|
17
|
+
}
|
|
3
18
|
export declare function serializeToJSONString(value: any): Promise<string>;
|
|
4
19
|
export declare function deserializeFromJSONString(json: string): Promise<unknown>;
|
|
5
20
|
export declare function deserializeJSONStream(response: Response | Request): Promise<unknown>;
|