@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.
Files changed (126) hide show
  1. package/CHANGELOG.md +149 -1
  2. package/dist/config/app-root-alias.d.ts +7 -0
  3. package/dist/config/app-root-alias.js +58 -0
  4. package/dist/config/constants.d.ts +2 -0
  5. package/dist/config/constants.js +2 -0
  6. package/dist/config/env.js +13 -10
  7. package/dist/config/fs-routes/index.js +3 -3
  8. package/dist/config/fs-routes/tree-shake.d.ts +27 -0
  9. package/dist/config/fs-routes/tree-shake.js +226 -33
  10. package/dist/config/index.d.ts +154 -5
  11. package/dist/config/index.js +23 -10
  12. package/dist/config/lazy.js +7 -2
  13. package/dist/config/manifest.d.ts +3 -0
  14. package/dist/config/manifest.js +26 -4
  15. package/dist/directives/remove-unused-variables.js +15 -3
  16. package/dist/directives/xxhash32.js +5 -13
  17. package/dist/fns/client.js +14 -5
  18. package/dist/fns/error-handler.d.ts +34 -0
  19. package/dist/fns/error-handler.js +10 -0
  20. package/dist/fns/handler.d.ts +1 -1
  21. package/dist/fns/handler.js +58 -12
  22. package/dist/fns/plugins.d.ts +34 -0
  23. package/dist/fns/plugins.js +33 -0
  24. package/dist/fns/serialization.d.ts +15 -0
  25. package/dist/fns/serialization.js +36 -6
  26. package/dist/middleware/index.js +4 -1
  27. package/dist/server/StartServer.jsx +1 -1
  28. package/dist/server/assets/render.jsx +1 -1
  29. package/dist/server/collect-styles.d.ts +3 -1
  30. package/dist/server/collect-styles.js +8 -5
  31. package/dist/server/fetchEvent.d.ts +10 -0
  32. package/dist/server/fetchEvent.js +10 -0
  33. package/dist/server/handler.d.ts +3 -4
  34. package/dist/server/handler.js +22 -56
  35. package/dist/server/index.d.ts +3 -1
  36. package/dist/server/index.jsx +1 -0
  37. package/dist/server/manifest/dev-client-manifest.js +1 -1
  38. package/dist/server/manifest/dev-ssr-manifest.js +1 -1
  39. package/dist/server/spa/StartServer.jsx +1 -1
  40. package/dist/server/spa/handler.d.ts +2 -3
  41. package/dist/server/spa/handler.js +2 -2
  42. package/dist/server/strip-path-base.d.ts +1 -0
  43. package/dist/server/strip-path-base.js +10 -0
  44. package/dist/server/types.d.ts +12 -1
  45. package/dist/server/web-stream.d.ts +9 -0
  46. package/dist/server/web-stream.js +29 -0
  47. package/dist/shared/ErrorBoundary.jsx +3 -3
  48. package/dist/shared/clientOnly.js +3 -1
  49. package/dist/shared/{dev-overlay → dev-toolbar/error-viewer}/CodeView.jsx +3 -3
  50. package/dist/shared/dev-toolbar/error-viewer/create-stack-frame.js +90 -0
  51. package/dist/shared/{dev-overlay/DevOverlayDialog.d.ts → dev-toolbar/error-viewer/index.d.ts} +3 -2
  52. package/dist/shared/{dev-overlay/DevOverlayDialog.jsx → dev-toolbar/error-viewer/index.jsx} +66 -77
  53. package/dist/shared/dev-toolbar/error-viewer/styles.css +224 -0
  54. package/dist/shared/dev-toolbar/functions/BlobViewer.css +4 -0
  55. package/dist/shared/dev-toolbar/functions/BlobViewer.d.ts +6 -0
  56. package/dist/shared/dev-toolbar/functions/BlobViewer.jsx +37 -0
  57. package/dist/shared/dev-toolbar/functions/FormDataViewer.d.ts +5 -0
  58. package/dist/shared/dev-toolbar/functions/FormDataViewer.jsx +23 -0
  59. package/dist/shared/dev-toolbar/functions/HeadersViewer.css +8 -0
  60. package/dist/shared/dev-toolbar/functions/HeadersViewer.d.ts +6 -0
  61. package/dist/shared/dev-toolbar/functions/HeadersViewer.jsx +15 -0
  62. package/dist/shared/dev-toolbar/functions/HexViewer.css +34 -0
  63. package/dist/shared/dev-toolbar/functions/HexViewer.d.ts +11 -0
  64. package/dist/shared/dev-toolbar/functions/HexViewer.jsx +70 -0
  65. package/dist/shared/dev-toolbar/functions/SerovalValue.css +5 -0
  66. package/dist/shared/dev-toolbar/functions/SerovalValue.d.ts +7 -0
  67. package/dist/shared/dev-toolbar/functions/SerovalValue.jsx +10 -0
  68. package/dist/shared/dev-toolbar/functions/SerovalViewer.css +70 -0
  69. package/dist/shared/dev-toolbar/functions/SerovalViewer.d.ts +6 -0
  70. package/dist/shared/dev-toolbar/functions/SerovalViewer.jsx +796 -0
  71. package/dist/shared/dev-toolbar/functions/URLSearchParamsViewer.d.ts +5 -0
  72. package/dist/shared/dev-toolbar/functions/URLSearchParamsViewer.jsx +22 -0
  73. package/dist/shared/dev-toolbar/functions/index.d.ts +13 -0
  74. package/dist/shared/dev-toolbar/functions/index.jsx +216 -0
  75. package/dist/shared/dev-toolbar/functions/styles.css +109 -0
  76. package/dist/shared/dev-toolbar/functions/tracker.d.ts +19 -0
  77. package/dist/shared/dev-toolbar/functions/tracker.js +29 -0
  78. package/dist/shared/{dev-overlay → dev-toolbar}/icons.d.ts +9 -0
  79. package/dist/shared/{dev-overlay → dev-toolbar}/icons.jsx +19 -1
  80. package/dist/shared/dev-toolbar/index.css +88 -0
  81. package/dist/shared/dev-toolbar/index.d.ts +6 -0
  82. package/dist/shared/dev-toolbar/index.jsx +179 -0
  83. package/dist/shared/ui/Badge.css +33 -0
  84. package/dist/shared/ui/Badge.d.ts +7 -0
  85. package/dist/shared/ui/Badge.jsx +7 -0
  86. package/dist/shared/ui/Button.css +32 -0
  87. package/dist/shared/ui/Button.d.ts +4 -0
  88. package/dist/shared/ui/Button.jsx +4 -0
  89. package/dist/shared/ui/Cascade.css +29 -0
  90. package/dist/shared/ui/Cascade.d.ts +4 -0
  91. package/dist/shared/ui/Cascade.jsx +4 -0
  92. package/dist/shared/ui/Dialog.css +25 -0
  93. package/dist/shared/ui/Dialog.d.ts +5 -0
  94. package/dist/shared/ui/Dialog.jsx +5 -0
  95. package/dist/shared/ui/IconButton.css +39 -0
  96. package/dist/shared/ui/IconButton.d.ts +4 -0
  97. package/dist/shared/ui/IconButton.jsx +4 -0
  98. package/dist/shared/ui/Placeholder.css +12 -0
  99. package/dist/shared/ui/Placeholder.d.ts +6 -0
  100. package/dist/shared/ui/Placeholder.jsx +4 -0
  101. package/dist/shared/ui/Section.css +18 -0
  102. package/dist/shared/ui/Section.d.ts +9 -0
  103. package/dist/shared/ui/Section.jsx +10 -0
  104. package/dist/shared/ui/Select.css +37 -0
  105. package/dist/shared/ui/Select.d.ts +4 -0
  106. package/dist/shared/ui/Select.jsx +4 -0
  107. package/dist/shared/ui/Table.css +22 -0
  108. package/dist/shared/ui/Table.d.ts +11 -0
  109. package/dist/shared/ui/Table.jsx +13 -0
  110. package/dist/shared/ui/Tabs.css +51 -0
  111. package/dist/shared/ui/Tabs.d.ts +6 -0
  112. package/dist/shared/ui/Tabs.jsx +6 -0
  113. package/dist/shared/ui/Text.css +73 -0
  114. package/dist/shared/ui/Text.d.ts +12 -0
  115. package/dist/shared/ui/Text.jsx +17 -0
  116. package/package.json +36 -29
  117. package/dist/shared/dev-overlay/createStackFrame.js +0 -56
  118. package/dist/shared/dev-overlay/index.d.ts +0 -5
  119. package/dist/shared/dev-overlay/index.jsx +0 -39
  120. package/dist/shared/dev-overlay/styles.css +0 -292
  121. /package/dist/shared/{dev-overlay → dev-toolbar/error-viewer}/CodeView.d.ts +0 -0
  122. /package/dist/shared/{dev-overlay/createStackFrame.d.ts → dev-toolbar/error-viewer/create-stack-frame.d.ts} +0 -0
  123. /package/dist/shared/{dev-overlay → dev-toolbar/error-viewer}/download.d.ts +0 -0
  124. /package/dist/shared/{dev-overlay → dev-toolbar/error-viewer}/download.js +0 -0
  125. /package/dist/shared/{dev-overlay → dev-toolbar/error-viewer}/get-source-map.d.ts +0 -0
  126. /package/dist/shared/{dev-overlay → dev-toolbar/error-viewer}/get-source-map.js +0 -0
@@ -1,6 +1,6 @@
1
1
  import { crossSerializeStream, deserialize, Feature, fromCrossJSON, getCrossReferenceHeader, toCrossJSONStream, } from "seroval";
2
2
  import { AbortSignalPlugin, CustomEventPlugin, DOMExceptionPlugin, EventPlugin, FormDataPlugin, HeadersPlugin, ReadableStreamPlugin, RequestPlugin, ResponsePlugin, URLPlugin, URLSearchParamsPlugin, } from "seroval-plugins/web";
3
- // TODO(Alexis): if we can, allow providing an option to extend these.
3
+ import userPlugins from "solid-start:seroval-plugins";
4
4
  const DEFAULT_PLUGINS = [
5
5
  AbortSignalPlugin,
6
6
  CustomEventPlugin,
@@ -14,8 +14,37 @@ const DEFAULT_PLUGINS = [
14
14
  URLSearchParamsPlugin,
15
15
  URLPlugin,
16
16
  ];
17
+ /**
18
+ * Plugins from `serialization.plugins` are appended rather than prepended:
19
+ * seroval picks the first plugin whose `test()` passes, so the built-ins win
20
+ * and a loose user `test()` can't take over `Request`/`FormData`/`URL`.
21
+ *
22
+ * The same list has to be used on both ends of a server function, which is why
23
+ * this comes from a virtual module bundled into the client and the server
24
+ * rather than a runtime option.
25
+ */
26
+ const PLUGINS = [...DEFAULT_PLUGINS, ...userPlugins];
17
27
  const MAX_SERIALIZATION_DEPTH_LIMIT = 64;
18
28
  const DISABLED_FEATURES = Feature.RegExp;
29
+ /**
30
+ * An error thrown by a server function is serialized and rethrown on the
31
+ * client, and seroval includes `Error.prototype.stack` by default. In
32
+ * production that leaks server file paths, internal function names and the
33
+ * shape of the deployment to anyone who can trigger a throw, so strip it.
34
+ * Development keeps the stack: that's where it's actually useful, and the
35
+ * paths it exposes are the developer's own.
36
+ *
37
+ * Only applied when writing; parsing leaves `DISABLED_FEATURES` alone so an
38
+ * incoming payload that does carry a stack still deserializes.
39
+ */
40
+ const SERIALIZE_DISABLED_FEATURES = import.meta.env.PROD
41
+ ? DISABLED_FEATURES | Feature.ErrorPrototypeStack
42
+ : DISABLED_FEATURES;
43
+ /**
44
+ * `crossSerializeStream` historically ran with every feature enabled, so only
45
+ * add the stack removal here rather than the full serialize set.
46
+ */
47
+ const JS_SERIALIZE_DISABLED_FEATURES = import.meta.env.PROD ? Feature.ErrorPrototypeStack : 0;
19
48
  /**
20
49
  * Alexis:
21
50
  *
@@ -47,7 +76,8 @@ export function serializeToJSStream(id, value) {
47
76
  start(controller) {
48
77
  crossSerializeStream(value, {
49
78
  scopeId: id,
50
- plugins: DEFAULT_PLUGINS,
79
+ disabledFeatures: JS_SERIALIZE_DISABLED_FEATURES,
80
+ plugins: PLUGINS,
51
81
  onSerialize(data, initial) {
52
82
  controller.enqueue(createChunk(initial ? `(${getCrossReferenceHeader(id)},${data})` : data));
53
83
  },
@@ -65,9 +95,9 @@ export function serializeToJSONStream(value) {
65
95
  return new ReadableStream({
66
96
  start(controller) {
67
97
  toCrossJSONStream(value, {
68
- disabledFeatures: DISABLED_FEATURES,
98
+ disabledFeatures: SERIALIZE_DISABLED_FEATURES,
69
99
  depthLimit: MAX_SERIALIZATION_DEPTH_LIMIT,
70
- plugins: DEFAULT_PLUGINS,
100
+ plugins: PLUGINS,
71
101
  onParse(node) {
72
102
  controller.enqueue(createChunk(JSON.stringify(node)));
73
103
  },
@@ -81,7 +111,7 @@ export function serializeToJSONStream(value) {
81
111
  },
82
112
  });
83
113
  }
84
- class SerovalChunkReader {
114
+ export class SerovalChunkReader {
85
115
  reader;
86
116
  buffer;
87
117
  done;
@@ -180,7 +210,7 @@ export async function deserializeJSONStream(response) {
180
210
  refs,
181
211
  disabledFeatures: DISABLED_FEATURES,
182
212
  depthLimit: MAX_SERIALIZATION_DEPTH_LIMIT,
183
- plugins: DEFAULT_PLUGINS,
213
+ plugins: PLUGINS,
184
214
  });
185
215
  return value;
186
216
  }
@@ -54,7 +54,10 @@ export function createMiddleware(args) {
54
54
  mw.push(wrapResponseMiddleware(args.onBeforeResponse));
55
55
  }
56
56
  else if (Array.isArray(args.onBeforeResponse)) {
57
- mw.push(...args.onBeforeResponse.map(wrapResponseMiddleware));
57
+ // h3 middleware compose onion-style: logic after `await next()` unwinds
58
+ // innermost-first (reverse registration order). Register the wrappers
59
+ // reversed so onBeforeResponse functions run in their declared order.
60
+ mw.push(...args.onBeforeResponse.map(wrapResponseMiddleware).reverse());
58
61
  }
59
62
  return mw;
60
63
  }
@@ -19,7 +19,7 @@ export function StartServer(props) {
19
19
  <TopErrorBoundary>
20
20
  <props.document assets={<HydrationScript />} scripts={<>
21
21
  <PatchVirtualDevStyles nonce={nonce}/>
22
- <script type="module" nonce={nonce} async src={getSsrManifest("client").path(import.meta.env.START_CLIENT_ENTRY)}/>
22
+ <script type="module" nonce={nonce} async src={getSsrManifest("client").path(import.meta.env.START_CLIENT_ENTRY_URL)}/>
23
23
  </>}>
24
24
  {!import.meta.env.START_ISLANDS ? (<Hydration>
25
25
  <ErrorBoundary>
@@ -9,6 +9,6 @@ const assetMap = {
9
9
  noscript: (props) => (<noscript {...props.attrs}>{props.children}</noscript>),
10
10
  };
11
11
  export function renderAsset(asset, nonce) {
12
- let { tag, attrs: { key, ...attrs } = { key: undefined }, children, } = asset;
12
+ let { tag, attrs: { key, ...attrs } = { key: undefined }, children } = asset;
13
13
  return assetMap[tag]({ attrs: { ...attrs, nonce }, key, children });
14
14
  }
@@ -1,3 +1,5 @@
1
1
  import type { DevEnvironment } from "vite";
2
+ type StyleFilter = (id: string) => boolean;
2
3
  export declare const isCssModulesFile: (file: string) => boolean;
3
- export declare function findStylesInModuleGraph(vite: DevEnvironment, id: string): Promise<Record<string, any>>;
4
+ export declare function findStylesInModuleGraph(vite: DevEnvironment, id: string, filter: StyleFilter): Promise<Record<string, any>>;
5
+ export {};
@@ -8,13 +8,16 @@ async function getViteModuleNode(vite, file, importer) {
8
8
  }
9
9
  catch (err) { }
10
10
  }
11
- async function findModuleDependencies(vite, file, deps, crawledFiles = new Set(), importer) {
11
+ async function findModuleDependencies(vite, file, deps, filter, crawledFiles = new Set(), importer) {
12
12
  crawledFiles.add(file);
13
13
  const module = await getViteModuleNode(vite, file, importer);
14
14
  if (!module?.id || deps.has(module))
15
15
  return;
16
16
  deps.add(module);
17
- if (module.url.endsWith(".css") || module.url.includes("node_modules"))
17
+ if (module.url.endsWith(".css"))
18
+ return;
19
+ // Apply user-config file filters only to real files (virtual modules should always be included)
20
+ if (module.file && !module.id.startsWith("\0") && !filter(module.file))
18
21
  return;
19
22
  if (!module.transformResult) {
20
23
  await vite.transformRequest(module.id).catch(() => { });
@@ -28,7 +31,7 @@ async function findModuleDependencies(vite, file, deps, crawledFiles = new Set()
28
31
  if (crawledFiles.has(dep)) {
29
32
  continue;
30
33
  }
31
- await findModuleDependencies(vite, dep, deps, crawledFiles, module.id);
34
+ await findModuleDependencies(vite, dep, deps, filter, crawledFiles, module.id);
32
35
  }
33
36
  }
34
37
  // Vite doesn't expose these so we just copy the list for now
@@ -38,11 +41,11 @@ const cssFileRegExp = /\.(css|less|sass|scss|styl|stylus|pcss|postcss|sss)$/;
38
41
  const cssModulesRegExp = new RegExp(`\\.module${cssFileRegExp.source}`);
39
42
  const isCssFile = (file) => cssFileRegExp.test(file);
40
43
  export const isCssModulesFile = (file) => cssModulesRegExp.test(file);
41
- export async function findStylesInModuleGraph(vite, id) {
44
+ export async function findStylesInModuleGraph(vite, id, filter) {
42
45
  const absolute = path.resolve(process.cwd(), id);
43
46
  const dependencies = new Set();
44
47
  try {
45
- await findModuleDependencies(vite, absolute, dependencies);
48
+ await findModuleDependencies(vite, absolute, dependencies, filter);
46
49
  }
47
50
  catch (e) {
48
51
  console.error(e);
@@ -3,5 +3,15 @@ import type { FetchEvent } from "./types.ts";
3
3
  export declare function createFetchEvent(event: H3Event): FetchEvent;
4
4
  export declare function getFetchEvent(h3Event: H3Event): FetchEvent;
5
5
  export declare function mergeResponseHeaders(h3Event: H3Event, headers: Headers): void;
6
+ /**
7
+ * Wrap an h3 event handler so Solid's request context is available while it runs.
8
+ *
9
+ * @experimental
10
+ */
6
11
  export declare const decorateHandler: <T extends EventHandler>(fn: T) => T;
12
+ /**
13
+ * Wrap h3 middleware so Solid's request context is available while it runs.
14
+ *
15
+ * @experimental
16
+ */
7
17
  export declare const decorateMiddleware: <T extends Middleware>(fn: T) => T;
@@ -22,5 +22,15 @@ export function mergeResponseHeaders(h3Event, headers) {
22
22
  h3Event.res.headers.append(key, value);
23
23
  }
24
24
  }
25
+ /**
26
+ * Wrap an h3 event handler so Solid's request context is available while it runs.
27
+ *
28
+ * @experimental
29
+ */
25
30
  export const decorateHandler = (fn) => (event => provideRequestEvent(getFetchEvent(event), () => fn(event)));
31
+ /**
32
+ * Wrap h3 middleware so Solid's request context is available while it runs.
33
+ *
34
+ * @experimental
35
+ */
26
36
  export const decorateMiddleware = (fn) => ((event, next) => provideRequestEvent(getFetchEvent(event), () => fn(event, next)));
@@ -1,6 +1,5 @@
1
- import { H3 } from "h3/generic";
2
1
  import type { JSX } from "solid-js";
3
- import type { FetchEvent, HandlerOptions, PageEvent } from "./types.ts";
4
- export declare function createBaseHandler(createPageEvent: (e: FetchEvent) => Promise<PageEvent>, fn: (context: PageEvent) => JSX.Element, options?: HandlerOptions | ((context: PageEvent) => HandlerOptions | Promise<HandlerOptions>)): H3;
5
- export declare function createHandler(fn: (context: PageEvent) => JSX.Element, options?: HandlerOptions | ((context: PageEvent) => HandlerOptions | Promise<HandlerOptions>)): H3;
2
+ import type { FetchEvent, HandlerOptions, PageEvent, StartHandler } from "./types.ts";
3
+ export declare function createBaseHandler(createPageEvent: (e: FetchEvent) => Promise<PageEvent>, fn: (context: PageEvent) => JSX.Element, options?: HandlerOptions | ((context: PageEvent) => HandlerOptions | Promise<HandlerOptions>), routerLoad?: (event: FetchEvent) => Promise<void>): StartHandler;
4
+ export declare function createHandler(fn: (context: PageEvent) => JSX.Element, options?: HandlerOptions | ((context: PageEvent) => HandlerOptions | Promise<HandlerOptions>), routerLoad?: (event: FetchEvent) => Promise<void>): StartHandler;
6
5
  export declare function createPageEvent(ctx: FetchEvent): Promise<PageEvent>;
@@ -1,5 +1,5 @@
1
1
  import middleware from "solid-start:middleware";
2
- import { defineHandler, getCookie, H3, redirect, setCookie } from "h3/generic";
2
+ import { defineHandler, getCookie, H3, iterable, redirect, setCookie, } from "h3/generic";
3
3
  import { sharedConfig } from "solid-js";
4
4
  import { getRequestEvent, renderToStream, renderToString } from "solid-js/web";
5
5
  import { createRoutes } from "../router.jsx";
@@ -8,8 +8,10 @@ import { getSsrManifest } from "./manifest/ssr-manifest.js";
8
8
  import { matchAPIRoute } from "./routes.js";
9
9
  import { handleServerFunction } from "../fns/handler.js";
10
10
  import { getExpectedRedirectStatus } from "./util.js";
11
+ import { toWebReadableStream } from "./web-stream.js";
12
+ import { stripPathBase } from "./strip-path-base.js";
11
13
  const SERVER_FN_BASE = "/_server";
12
- export function createBaseHandler(createPageEvent, fn, options = {}) {
14
+ export function createBaseHandler(createPageEvent, fn, options = {}, routerLoad) {
13
15
  const handler = defineHandler({
14
16
  middleware: middleware.length ? middleware.map(decorateMiddleware) : undefined,
15
17
  handler: decorateHandler(async (e) => {
@@ -17,12 +19,7 @@ export function createBaseHandler(createPageEvent, fn, options = {}) {
17
19
  const url = new URL(event.request.url);
18
20
  const pathname = stripBaseUrl(url.pathname);
19
21
  if (pathname.startsWith(SERVER_FN_BASE)) {
20
- const serverFnResponse = await handleServerFunction(e);
21
- if (serverFnResponse instanceof Response)
22
- return produceResponseWithEventHeaders(serverFnResponse);
23
- return new Response(serverFnResponse, {
24
- headers: e.res.headers,
25
- });
22
+ return await handleServerFunction(e);
26
23
  }
27
24
  const match = matchAPIRoute(pathname, event.request.method);
28
25
  if (match) {
@@ -34,8 +31,6 @@ export function createBaseHandler(createPageEvent, fn, options = {}) {
34
31
  sharedConfig.context = { event };
35
32
  const res = await fn(event);
36
33
  if (res !== undefined) {
37
- if (res instanceof Response)
38
- return produceResponseWithEventHeaders(res);
39
34
  return res;
40
35
  }
41
36
  if (event.request.method !== "GET") {
@@ -45,6 +40,8 @@ export function createBaseHandler(createPageEvent, fn, options = {}) {
45
40
  return;
46
41
  }
47
42
  }
43
+ if (routerLoad)
44
+ await routerLoad(event);
48
45
  const context = await createPageEvent(event);
49
46
  const resolvedOptions = typeof options === "function" ? await options(context) : { ...options };
50
47
  const mode = resolvedOptions.mode || "stream";
@@ -92,23 +89,17 @@ export function createBaseHandler(createPageEvent, fn, options = {}) {
92
89
  }
93
90
  if (mode === "async")
94
91
  return await stream;
95
- delete stream.then;
96
- // using TransformStream in dev can cause solid-start-dev-server to crash
97
- // when stream is cancelled
98
- if (globalThis.USING_SOLID_START_DEV_SERVER)
99
- return stream;
100
- // returning stream directly breaks cloudflare workers
101
- const { writable, readable } = new TransformStream();
102
- stream.pipeTo(writable);
103
- return readable;
92
+ // h3 expects a standard web ReadableStream across runtimes. The adapter
93
+ // also tolerates cancellation while Solid finishes outstanding work.
94
+ return iterable(toWebReadableStream(stream));
104
95
  }),
105
96
  });
106
97
  const app = new H3();
107
98
  app.use(handler);
108
99
  return app;
109
100
  }
110
- export function createHandler(fn, options = {}) {
111
- return createBaseHandler(createPageEvent, fn, options);
101
+ export function createHandler(fn, options = {}, routerLoad) {
102
+ return createBaseHandler(createPageEvent, fn, options, routerLoad);
112
103
  }
113
104
  export async function createPageEvent(ctx) {
114
105
  ctx.response.headers.set("Content-Type", "text/html");
@@ -180,43 +171,18 @@ function handleStreamCompleteRedirect(context) {
180
171
  return ({ write }) => {
181
172
  context.complete = true;
182
173
  const to = context.response && context.response.headers.get("Location");
183
- to && write(`<script>window.location=${JSON.stringify(to).replace(/</g, "\\u003c")}</script>`);
174
+ if (!to)
175
+ return;
176
+ // The shell has already flushed, so the redirect has to happen client side.
177
+ // Carry the nonce so a strict `script-src` CSP doesn't block it.
178
+ const nonce = context.nonce ? ` nonce="${escapeAttribute(context.nonce)}"` : "";
179
+ write(`<script${nonce}>window.location=${JSON.stringify(to).replace(/</g, "\\u003c")}</script>`);
184
180
  };
185
181
  }
186
- function produceResponseWithEventHeaders(res) {
187
- const event = getRequestEvent();
188
- let ret = res;
189
- // Response.redirect returns an immutable value, so we clone on any redirect just in case
190
- if (300 <= res.status && res.status < 400) {
191
- const cookies = res.headers.getSetCookie?.() ?? [];
192
- const headers = new Headers();
193
- res.headers.forEach((value, key) => {
194
- if (key.toLowerCase() !== "set-cookie") {
195
- headers.set(key, value);
196
- }
197
- });
198
- for (const cookie of cookies) {
199
- headers.append("Set-Cookie", cookie);
200
- }
201
- ret = new Response(res.body, {
202
- status: res.status,
203
- statusText: res.statusText,
204
- headers,
205
- });
206
- }
207
- const eventCookies = event.response.headers.getSetCookie?.() ?? [];
208
- for (const cookie of eventCookies) {
209
- ret.headers.append("Set-Cookie", cookie);
210
- }
211
- for (const [name, value] of event.response.headers) {
212
- if (name.toLowerCase() !== "set-cookie") {
213
- ret.headers.set(name, value);
214
- }
215
- }
216
- return ret;
182
+ function escapeAttribute(value) {
183
+ return value.replace(/&/g, "&amp;").replace(/"/g, "&quot;").replace(/</g, "&lt;");
217
184
  }
218
185
  function stripBaseUrl(path) {
219
- if (import.meta.env.BASE_URL === "/" || import.meta.env.BASE_URL === "")
220
- return path;
221
- return path.slice(import.meta.env.BASE_URL.length);
186
+ const base = import.meta.env.SERVER_BASE_URL || import.meta.env.BASE_URL || "/";
187
+ return stripPathBase(path, base);
222
188
  }
@@ -1,8 +1,10 @@
1
1
  export { default as lazy } from "../shared/lazy.ts";
2
2
  export { getServerFunctionMeta } from "../shared/serverFunction.ts";
3
3
  export { StartServer } from "./StartServer.tsx";
4
+ export { decorateHandler, decorateMiddleware } from "./fetchEvent.ts";
4
5
  export { createHandler } from "./handler.ts";
5
- export type { APIEvent, APIHandler, Asset, ContextMatches, DocumentComponentProps, FetchEvent, HandlerOptions, PageEvent, ResponseStub, ServerFunctionMeta, } from "./types.ts";
6
+ export type { ServerFunctionErrorHandler } from "../fns/error-handler.ts";
7
+ export type { APIEvent, APIHandler, Asset, ContextMatches, DocumentComponentProps, FetchEvent, HandlerOptions, PageEvent, ResponseStub, ServerFunctionMeta, StartHandler, } from "./types.ts";
6
8
  /**
7
9
  * Checks if user has set a redirect status in the response.
8
10
  * If not, falls back to the 302 (temporary redirect)
@@ -1,6 +1,7 @@
1
1
  export { default as lazy } from "../shared/lazy.js";
2
2
  export { getServerFunctionMeta } from "../shared/serverFunction.js";
3
3
  export { StartServer } from "./StartServer.jsx";
4
+ export { decorateHandler, decorateMiddleware } from "./fetchEvent.js";
4
5
  export { createHandler } from "./handler.js";
5
6
  /**
6
7
  * Checks if user has set a redirect status in the response.
@@ -13,7 +13,7 @@ export function getClientDevManifest() {
13
13
  return import(__rewriteRelativeImportExtension(/* @vite-ignore */ join("/", id), true));
14
14
  },
15
15
  async getAssets(id) {
16
- const assetsPath = `/@manifest/client/${Date.now()}/assets?id=${id}`;
16
+ const assetsPath = `/@manifest/client/${Date.now()}/assets?id=${encodeURIComponent(id)}`;
17
17
  const assets = (await import(__rewriteRelativeImportExtension(/* @vite-ignore */ assetsPath, true))).default;
18
18
  return await Promise.all(assets.map(async (v) => ({
19
19
  ...v,
@@ -11,7 +11,7 @@ export function getSsrDevManifest(environment) {
11
11
  return {
12
12
  path: (id) => normalize(join(import.meta.env.BASE_URL, id)),
13
13
  async getAssets(id) {
14
- const assetsPath = `/@manifest/${environment}/${Date.now()}/assets?id=${id}`;
14
+ const assetsPath = `/@manifest/${environment}/${Date.now()}/assets?id=${encodeURIComponent(id)}`;
15
15
  const assets = (await import(__rewriteRelativeImportExtension(/* @vite-ignore */ assetsPath, true))).default;
16
16
  return await Promise.all(assets.map(async (v) => ({
17
17
  ...v,
@@ -18,7 +18,7 @@ export function StartServer(props) {
18
18
  <TopErrorBoundary>
19
19
  <props.document scripts={<>
20
20
  <PatchVirtualDevStyles nonce={nonce}/>
21
- <script type="module" src={getSsrManifest("client").path(import.meta.env.START_CLIENT_ENTRY)}/>
21
+ <script type="module" nonce={nonce} src={getSsrManifest("client").path(import.meta.env.START_CLIENT_ENTRY_URL)}/>
22
22
  </>}/>
23
23
  </TopErrorBoundary>
24
24
  </NoHydration>);
@@ -1,8 +1,7 @@
1
- import type { H3 } from "h3/generic";
2
1
  import type { JSX } from "solid-js";
3
- import type { HandlerOptions, PageEvent } from "../types.ts";
2
+ import type { FetchEvent, HandlerOptions, PageEvent, StartHandler } from "../types.ts";
4
3
  /**
5
4
  *
6
5
  * Read more: https://docs.solidjs.com/solid-start/reference/server/create-handler
7
6
  */
8
- export declare function createHandler(fn: (context: PageEvent) => JSX.Element, options?: HandlerOptions | ((context: PageEvent) => HandlerOptions)): H3;
7
+ export declare function createHandler(fn: (context: PageEvent) => JSX.Element, options?: HandlerOptions | ((context: PageEvent) => HandlerOptions), routerLoad?: (event: FetchEvent) => Promise<void>): StartHandler;
@@ -4,8 +4,8 @@ import { getSsrManifest } from "../manifest/ssr-manifest.js";
4
4
  *
5
5
  * Read more: https://docs.solidjs.com/solid-start/reference/server/create-handler
6
6
  */
7
- export function createHandler(fn, options) {
8
- return createBaseHandler(createPageEvent, fn, options);
7
+ export function createHandler(fn, options, routerLoad) {
8
+ return createBaseHandler(createPageEvent, fn, options, routerLoad);
9
9
  }
10
10
  async function createPageEvent(ctx) {
11
11
  const manifest = getSsrManifest("ssr");
@@ -0,0 +1 @@
1
+ export declare function stripPathBase(path: string, base: string): string;
@@ -0,0 +1,10 @@
1
+ export function stripPathBase(path, base) {
2
+ if (!base || base === "/")
3
+ return path;
4
+ const normalizedBase = base.endsWith("/") ? base.slice(0, -1) : base;
5
+ if (path === normalizedBase)
6
+ return "/";
7
+ if (path.startsWith(`${normalizedBase}/`))
8
+ return path.slice(normalizedBase.length);
9
+ return path;
10
+ }
@@ -1,6 +1,17 @@
1
- import type { H3Event } from "h3";
1
+ import type { H3, H3Event } from "h3";
2
2
  import type { JSX } from "solid-js";
3
3
  import type { RequestEvent } from "solid-js/web";
4
+ /**
5
+ * The h3 app instance returned by `createHandler`.
6
+ *
7
+ * Structurally identical to h3's `H3`, but declared here so that `export default
8
+ * createHandler(...)` in `entry-server.tsx` can be named through `@solidjs/start`.
9
+ * Referring to `H3` directly makes TypeScript emit a reference to h3's internal
10
+ * `H3$1` class, which is not portable when h3 is nested inside
11
+ * `node_modules/@solidjs/start/node_modules` (TS2742 / TS2883).
12
+ */
13
+ export interface StartHandler extends H3 {
14
+ }
4
15
  export type DocumentComponentProps = {
5
16
  assets?: JSX.Element;
6
17
  scripts: JSX.Element;
@@ -0,0 +1,9 @@
1
+ type PipeableStream = {
2
+ pipe(writable: {
3
+ write(payload: string): void;
4
+ end(): void;
5
+ }): void;
6
+ };
7
+ /** Convert Solid's streaming SSR result into a cancellation-safe web stream. */
8
+ export declare function toWebReadableStream(stream: PipeableStream): ReadableStream<Uint8Array<ArrayBufferLike>>;
9
+ export {};
@@ -0,0 +1,29 @@
1
+ /** Convert Solid's streaming SSR result into a cancellation-safe web stream. */
2
+ export function toWebReadableStream(stream) {
3
+ const encoder = new TextEncoder();
4
+ let active = true;
5
+ return new ReadableStream({
6
+ start(controller) {
7
+ stream.pipe({
8
+ write(payload) {
9
+ if (!active)
10
+ return;
11
+ // Encoding string to Uint8Array makes sure that
12
+ // the stream can be consumed as Response body
13
+ controller.enqueue(encoder.encode(payload));
14
+ },
15
+ end() {
16
+ if (!active)
17
+ return;
18
+ active = false;
19
+ controller.close();
20
+ },
21
+ });
22
+ },
23
+ cancel() {
24
+ // Solid may still resolve Suspense resources after the response is
25
+ // cancelled. Ignore those writes and let Solid finish its cleanup.
26
+ active = false;
27
+ },
28
+ });
29
+ }
@@ -1,10 +1,10 @@
1
1
  // @refresh skip
2
- import { ErrorBoundary as DefaultErrorBoundary, catchError } from "solid-js";
2
+ import { catchError, ErrorBoundary as DefaultErrorBoundary } from "solid-js";
3
3
  import { isServer } from "solid-js/web";
4
+ import { DevToolbar } from "./dev-toolbar/index.jsx";
4
5
  import { HttpStatusCode } from "./HttpStatusCode.js";
5
- import { DevOverlay } from "./dev-overlay/index.jsx";
6
6
  export const ErrorBoundary = import.meta.env.DEV && import.meta.env.START_DEV_OVERLAY
7
- ? (props) => <DevOverlay>{props.children}</DevOverlay>
7
+ ? (props) => <DevToolbar>{props.children}</DevToolbar>
8
8
  : (props) => {
9
9
  const message = isServer
10
10
  ? "500 | Internal Server Error"
@@ -19,7 +19,9 @@ export default function clientOnly(fn, options = {}) {
19
19
  return Comp(rest);
20
20
  const [mounted, setMounted] = createSignal(!sharedConfig.context);
21
21
  onMount(() => setMounted(true));
22
- return createMemo(() => ((Comp = comp()), (m = mounted()), untrack(() => (Comp && m ? Comp(rest) : props.fallback))));
22
+ return createMemo(() => ((Comp = comp()),
23
+ (m = mounted()),
24
+ untrack(() => (Comp && m ? Comp(rest) : props.fallback))));
23
25
  };
24
26
  }
25
27
  function load(fn, setComp) {
@@ -51,12 +51,12 @@ export function CodeView(props) {
51
51
  for (let i = 0, len = lines.length; i < len; i++) {
52
52
  const el = lines[i];
53
53
  if (props.line - minLine() - 1 === i) {
54
- el.dataset.startDevOverlayErrorLine = '';
54
+ el.dataset.startErrorViewerErrorLine = "";
55
55
  }
56
56
  }
57
57
  }
58
58
  });
59
- return (<div ref={ref} data-start-dev-overlay-code-view style={{
60
- "--dev-overlay-code-view-start": minLine() + 1,
59
+ return (<div ref={ref} data-start-error-viewer-code-view style={{
60
+ "--error-viewer-code-view-start": minLine() + 1,
61
61
  }}/>);
62
62
  }
@@ -0,0 +1,90 @@
1
+ import { createMemo, createResource } from "solid-js";
2
+ import getSourceMap from "./get-source-map.js";
3
+ const HTTP_URL_REGEX = /^https?:\/\//;
4
+ const LEADING_SLASH_REGEX = /^\/+/;
5
+ // Frames from an SSR error carry filesystem paths (or file:// URLs) rather
6
+ // than the http URL the module is served from. Their positions are already
7
+ // mapped back to the original source by the Vite module runner, unlike
8
+ // client frames whose positions refer to the compiled module.
9
+ function isServerSource(path) {
10
+ return !HTTP_URL_REGEX.test(path);
11
+ }
12
+ function getActualFileSource(path) {
13
+ if (path.startsWith("file://")) {
14
+ return "/@fs/" + path.substring("file://".length).replace(LEADING_SLASH_REGEX, "");
15
+ }
16
+ if (isServerSource(path)) {
17
+ return "/@fs/" + path.replace(LEADING_SLASH_REGEX, "");
18
+ }
19
+ return path;
20
+ }
21
+ export function createStackFrame(stackframe, isCompiled) {
22
+ const [data] = createResource(() => ({
23
+ fileName: stackframe.fileName,
24
+ line: stackframe.lineNumber,
25
+ column: stackframe.columnNumber,
26
+ functionName: stackframe.functionName,
27
+ }), async (source) => {
28
+ if (!source.fileName) {
29
+ return null;
30
+ }
31
+ const url = getActualFileSource(source.fileName);
32
+ const response = await fetch(url);
33
+ if (!response.ok) {
34
+ return null;
35
+ }
36
+ const content = await response.text();
37
+ const sourceMap = await getSourceMap(url, content);
38
+ return {
39
+ source,
40
+ content,
41
+ sourceMap,
42
+ isServer: isServerSource(source.fileName),
43
+ };
44
+ });
45
+ const info = createMemo(() => {
46
+ const current = data();
47
+ if (!current) {
48
+ return undefined;
49
+ }
50
+ const { source, content, sourceMap, isServer } = current;
51
+ if (!isCompiled() && source.line && source.column && sourceMap) {
52
+ if (isServer) {
53
+ // The position is already original; only the original content needs
54
+ // to be pulled out of the source map.
55
+ const originalContent = sourceMap.sources.length
56
+ ? sourceMap.sourceContentFor(sourceMap.sources[0], true)
57
+ : null;
58
+ if (originalContent) {
59
+ return {
60
+ source: source.fileName,
61
+ line: source.line,
62
+ column: source.column,
63
+ name: source.functionName,
64
+ content: originalContent,
65
+ };
66
+ }
67
+ }
68
+ else {
69
+ const result = sourceMap.originalPositionFor({
70
+ line: source.line,
71
+ column: source.column,
72
+ });
73
+ if (result.source) {
74
+ return {
75
+ ...result,
76
+ content: sourceMap.sourceContentFor(result.source, true),
77
+ };
78
+ }
79
+ }
80
+ }
81
+ return {
82
+ source: source.fileName,
83
+ line: source.line,
84
+ column: source.column,
85
+ name: source.functionName,
86
+ content,
87
+ };
88
+ });
89
+ return info;
90
+ }
@@ -1,9 +1,10 @@
1
1
  import type { JSX } from "solid-js";
2
2
  import "./styles.css";
3
3
  export declare function classNames(...classes: (string | boolean | undefined)[]): string;
4
- interface DevOverlayDialogProps {
4
+ interface ErrorViewerProps {
5
+ show?: boolean;
5
6
  errors: any[];
6
7
  resetError: () => void;
7
8
  }
8
- export default function DevOverlayDialog(props: DevOverlayDialogProps): JSX.Element;
9
+ export default function ErrorViewer(props: ErrorViewerProps): JSX.Element;
9
10
  export {};