@solidjs/start 1.0.0-rc.0 → 1.0.0-rc.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/index.js CHANGED
@@ -133,6 +133,7 @@ export function defineConfig(baseConfig = {}) {
133
133
  },
134
134
  cacheDir: "node_modules/.vinxi/server",
135
135
  define: {
136
+ "import.meta.env.START_APP": JSON.stringify(`${start.appRoot}/app${entryExtension}`),
136
137
  "import.meta.env.START_ISLANDS": JSON.stringify(start.experimental.islands),
137
138
  "import.meta.env.SSR": JSON.stringify(true),
138
139
  "import.meta.env.START_SSR": JSON.stringify(start.ssr),
@@ -200,6 +201,7 @@ export function defineConfig(baseConfig = {}) {
200
201
  },
201
202
  cacheDir: "node_modules/.vinxi/client",
202
203
  define: {
204
+ "import.meta.env.START_APP": JSON.stringify(`${start.appRoot}/app${entryExtension}`),
203
205
  "import.meta.env.START_ISLANDS": JSON.stringify(start.experimental.islands),
204
206
  "import.meta.env.SSR": JSON.stringify(false),
205
207
  "import.meta.env.START_SSR": JSON.stringify(start.ssr),
@@ -261,6 +263,7 @@ export function defineConfig(baseConfig = {}) {
261
263
  }
262
264
  },
263
265
  define: {
266
+ "import.meta.env.START_APP": JSON.stringify(`${start.appRoot}/app${entryExtension}`),
264
267
  "import.meta.env.START_ISLANDS": JSON.stringify(start.experimental.islands),
265
268
  "import.meta.env.SSR": JSON.stringify(true),
266
269
  "import.meta.env.START_SSR": JSON.stringify(start.ssr),
@@ -38,7 +38,7 @@ const router = createRouter({
38
38
  routes: fileRoutes.reduce((memo, route) => {
39
39
  if (!containsHTTP(route))
40
40
  return memo;
41
- let path = route.path.replace(/\([^)/]+\)/g, "").replace(/\*([^/]*)/g, (_, m) => `**:${m}`);
41
+ let path = route.path.replace(/\/\([^)/]+\)/g, "").replace(/\([^)/]+\)/g, "").replace(/\*([^/]*)/g, (_, m) => `**:${m}`);
42
42
  if (/:[^/]*\?/g.test(path)) {
43
43
  throw new Error(`Optional parameters are not supported in API routes: ${path}`);
44
44
  }
@@ -10,10 +10,15 @@ import invariant from "vinxi/lib/invariant";
10
10
  import { cloneEvent, getFetchEvent, mergeResponseHeaders } from "../server/fetchEvent";
11
11
  import { createPageEvent } from "../server/pageEvent";
12
12
  function createChunk(data) {
13
- const bytes = data.length;
13
+ const encodeData = new TextEncoder().encode(data);
14
+ const bytes = encodeData.length;
14
15
  const baseHex = bytes.toString(16);
15
16
  const totalHex = "00000000".substring(0, 8 - baseHex.length) + baseHex; // 32-bit
16
- return new TextEncoder().encode(`;0x${totalHex};${data}`);
17
+ const head = new TextEncoder().encode(`;0x${totalHex};`);
18
+ const chunk = new Uint8Array(12 + bytes);
19
+ chunk.set(head);
20
+ chunk.set(encodeData, 12);
21
+ return chunk;
17
22
  }
18
23
  function serializeToStream(id, value) {
19
24
  return new ReadableStream({
@@ -7,7 +7,7 @@ class SerovalChunkReader {
7
7
  done;
8
8
  constructor(stream) {
9
9
  this.reader = stream.getReader();
10
- this.buffer = "";
10
+ this.buffer = new Uint8Array(0);
11
11
  this.done = false;
12
12
  }
13
13
  async readChunk() {
@@ -15,7 +15,10 @@ class SerovalChunkReader {
15
15
  const chunk = await this.reader.read();
16
16
  if (!chunk.done) {
17
17
  // repopulate the buffer
18
- this.buffer += new TextDecoder().decode(chunk.value);
18
+ let newBuffer = new Uint8Array(this.buffer.length + chunk.value.length);
19
+ newBuffer.set(this.buffer);
20
+ newBuffer.set(chunk.value, this.buffer.length);
21
+ this.buffer = newBuffer;
19
22
  }
20
23
  else {
21
24
  this.done = true;
@@ -23,7 +26,7 @@ class SerovalChunkReader {
23
26
  }
24
27
  async next() {
25
28
  // Check if the buffer is empty
26
- if (this.buffer === "") {
29
+ if (this.buffer.length === 0) {
27
30
  // if we are already done...
28
31
  if (this.done) {
29
32
  return {
@@ -39,7 +42,8 @@ class SerovalChunkReader {
39
42
  // The byte header tells us how big the expected data is
40
43
  // so we know how much data we should wait before we
41
44
  // deserialize the data
42
- const bytes = Number.parseInt(this.buffer.substring(1, 11), 16); // ;0x00000000;
45
+ const head = new TextDecoder().decode(this.buffer.subarray(1, 11));
46
+ const bytes = Number.parseInt(head, 16); // ;0x00000000;
43
47
  // Check if the buffer has enough bytes to be parsed
44
48
  while (bytes > this.buffer.length - 12) {
45
49
  // If it's not enough, and the reader is done
@@ -51,9 +55,9 @@ class SerovalChunkReader {
51
55
  await this.readChunk();
52
56
  }
53
57
  // Extract the exact chunk as defined by the byte header
54
- const partial = this.buffer.substring(12, 12 + bytes);
58
+ const partial = new TextDecoder().decode(this.buffer.subarray(12, 12 + bytes));
55
59
  // The rest goes to the buffer
56
- this.buffer = this.buffer.substring(12 + bytes);
60
+ this.buffer = this.buffer.subarray(12 + bytes);
57
61
  // Deserialize the chunk
58
62
  return {
59
63
  done: false,
@@ -163,7 +167,7 @@ export function createServerReference(fn, id, name) {
163
167
  const fn = async (...args) => {
164
168
  const encodeArgs = options.method && options.method.toUpperCase() === "GET";
165
169
  return fetchServerFunction(encodeArgs
166
- ? url + (args.length ? `&args=${JSON.stringify(await Promise.resolve(toJSONAsync(args, { plugins })))}` : "")
170
+ ? url + (args.length ? `&args=${encodeURIComponent(JSON.stringify(await Promise.resolve(toJSONAsync(args, { plugins }))))}` : "")
167
171
  : `${baseURL}/_server`, `${id}#${name}`, options, encodeArgs ? [] : args);
168
172
  };
169
173
  fn.url = url;
@@ -58,7 +58,7 @@ export function StartServer(props) {
58
58
  {docType}
59
59
  <props.document assets={<>
60
60
  <HydrationScript />
61
- {context.assets.map((m) => renderAsset(m))}
61
+ {context.assets.map((m) => renderAsset(m, nonce))}
62
62
  </>} scripts={nonce ? (<>
63
63
  <script nonce={nonce} innerHTML={`window.manifest = ${JSON.stringify(context.manifest)}`}/>
64
64
  <script type="module" nonce={nonce} async src={import.meta.env.MANIFEST["client"].inputs[import.meta.env.MANIFEST["client"].handler].output.path}/>
@@ -1,4 +1,4 @@
1
- import { appendResponseHeader, getRequestIP, getResponseHeader, getResponseHeaders, getResponseStatus, getResponseStatusText, getWebRequest, removeResponseHeader, setHeader, setResponseHeader, setResponseStatus } from "vinxi/http";
1
+ import { appendResponseHeader, getRequestIP, getResponseHeader, getResponseHeaders, getResponseStatus, getResponseStatusText, getWebRequest, removeResponseHeader, setResponseHeader, setResponseStatus } from "vinxi/http";
2
2
  const fetchEventSymbol = Symbol("fetchEvent");
3
3
  export function createFetchEvent(event) {
4
4
  return {
@@ -23,7 +23,7 @@ export function getFetchEvent(h3Event) {
23
23
  }
24
24
  export function mergeResponseHeaders(h3Event, headers) {
25
25
  for (const [key, value] of headers.entries()) {
26
- setHeader(h3Event, key, value);
26
+ appendResponseHeader(h3Event, key, value);
27
27
  }
28
28
  }
29
29
  class HeaderProxy {
@@ -27,45 +27,44 @@ export function createBaseHandler(fn, createPageEvent, options = {}) {
27
27
  }
28
28
  // render
29
29
  const context = await createPageEvent(event);
30
- if (typeof options === "function")
31
- options = await options(context);
32
- const mode = options.mode || "stream";
30
+ const resolvedOptions = typeof options == "function" ? await options(context) : { ...options };
31
+ const mode = resolvedOptions.mode || "stream";
33
32
  // @ts-ignore
34
- if (options.nonce)
35
- context.nonce = options.nonce;
33
+ if (resolvedOptions.nonce)
34
+ context.nonce = resolvedOptions.nonce;
36
35
  if (mode === "sync" || !import.meta.env.START_SSR) {
37
36
  const html = renderToString(() => {
38
37
  sharedConfig.context.event = context;
39
38
  return fn(context);
40
- }, options);
39
+ }, resolvedOptions);
40
+ context.complete = true;
41
41
  if (context.response && context.response.headers.get("Location")) {
42
42
  return sendRedirect(e, context.response.headers.get("Location"));
43
43
  }
44
44
  return html;
45
45
  }
46
- let cloned = { ...options };
47
- if (cloned.onCompleteAll) {
48
- const og = cloned.onCompleteAll;
49
- cloned.onCompleteAll = options => {
46
+ if (resolvedOptions.onCompleteAll) {
47
+ const og = resolvedOptions.onCompleteAll;
48
+ resolvedOptions.onCompleteAll = options => {
50
49
  handleStreamCompleteRedirect(context)(options);
51
50
  og(options);
52
51
  };
53
52
  }
54
53
  else
55
- cloned.onCompleteAll = handleStreamCompleteRedirect(context);
56
- if (cloned.onCompleteShell) {
57
- const og = cloned.onCompleteShell;
58
- cloned.onCompleteShell = options => {
54
+ resolvedOptions.onCompleteAll = handleStreamCompleteRedirect(context);
55
+ if (resolvedOptions.onCompleteShell) {
56
+ const og = resolvedOptions.onCompleteShell;
57
+ resolvedOptions.onCompleteShell = options => {
59
58
  handleShellCompleteRedirect(context, e)();
60
59
  og(options);
61
60
  };
62
61
  }
63
62
  else
64
- cloned.onCompleteShell = handleShellCompleteRedirect(context, e);
63
+ resolvedOptions.onCompleteShell = handleShellCompleteRedirect(context, e);
65
64
  const stream = renderToStream(() => {
66
65
  sharedConfig.context.event = context;
67
66
  return fn(context);
68
- }, cloned);
67
+ }, resolvedOptions);
69
68
  if (context.response && context.response.headers.get("Location")) {
70
69
  return sendRedirect(e, context.response.headers.get("Location"));
71
70
  }
@@ -89,6 +88,7 @@ function handleShellCompleteRedirect(context, e) {
89
88
  }
90
89
  function handleStreamCompleteRedirect(context) {
91
90
  return ({ write }) => {
91
+ context.complete = true;
92
92
  const to = context.response && context.response.headers.get("Location");
93
93
  to && write(`<script>window.location="${to}"</script>`);
94
94
  };
@@ -26,6 +26,7 @@ export async function createPageEvent(ctx) {
26
26
  manifest: await clientManifest.json(),
27
27
  assets: [
28
28
  ...(await clientManifest.inputs[clientManifest.handler].assets()),
29
+ ...(import.meta.env.DEV ? await clientManifest.inputs[import.meta.env.START_APP].assets() : []),
29
30
  ...(import.meta.env.START_ISLANDS
30
31
  ? (await serverManifest.inputs[serverManifest.handler].assets()).filter(s => s.attrs.rel !== "modulepreload")
31
32
  : [])
@@ -37,6 +38,7 @@ export async function createPageEvent(ctx) {
37
38
  // prevUrl: prevPath || "",
38
39
  // mutation: mutation,
39
40
  // $type: FETCH_EVENT,
41
+ complete: false,
40
42
  $islands: new Set()
41
43
  });
42
44
  return pageEvent;
@@ -1,2 +1,2 @@
1
1
  import type { Asset } from "./types";
2
- export declare function renderAsset(asset: Asset): any;
2
+ export declare function renderAsset(asset: Asset, nonce?: string): any;
@@ -7,7 +7,7 @@ const assetMap = {
7
7
  </script>) : null;
8
8
  }
9
9
  };
10
- export function renderAsset(asset) {
10
+ export function renderAsset(asset, nonce) {
11
11
  let { tag, attrs: { key, ...attrs } = { key: undefined }, children } = asset;
12
- return assetMap[tag]({ attrs, key, children });
12
+ return assetMap[tag]({ attrs: { ...attrs, nonce }, key, children });
13
13
  }
@@ -8,6 +8,7 @@ export async function createPageEvent(ctx) {
8
8
  manifest: await clientManifest.json(),
9
9
  assets: [...(await clientManifest.inputs[clientManifest.handler].assets())],
10
10
  routes: [],
11
+ complete: false,
11
12
  // prevUrl: "",
12
13
  // mutation: false,
13
14
  // $type: FETCH_EVENT,
@@ -60,6 +60,7 @@ export interface PageEvent extends RequestEvent {
60
60
  assets: any;
61
61
  routes: any[];
62
62
  $islands: Set<string>;
63
+ complete: boolean;
63
64
  }
64
65
  export interface APIEvent extends FetchEvent {
65
66
  params: {
@@ -9,7 +9,7 @@ export const HttpHeader = isServer
9
9
  else
10
10
  event.response.headers.set(props.name, props.value);
11
11
  onCleanup(() => {
12
- if (event.nativeEvent.handled)
12
+ if (event.nativeEvent.handled || event.complete)
13
13
  return;
14
14
  const value = event.response.headers.get(props.name);
15
15
  if (!value)
@@ -6,7 +6,7 @@ export const HttpStatusCode = isServer
6
6
  const event = getRequestEvent();
7
7
  event.response.status = props.code;
8
8
  event.response.statusText = props.text;
9
- onCleanup(() => !event.nativeEvent.handled && (event.response.status = 200));
9
+ onCleanup(() => !event.nativeEvent.handled && !event.complete && (event.response.status = 200));
10
10
  return null;
11
11
  }
12
12
  : (_props) => null;
@@ -1,2 +1,3 @@
1
+ /// <reference types=".pnpm/source-map-js@1.2.0/node_modules/source-map-js" />
1
2
  import { SourceMapConsumer } from 'source-map-js';
2
3
  export default function getSourceMap(url: string, content: string): Promise<SourceMapConsumer | null>;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@solidjs/start",
3
- "version": "1.0.0-rc.0",
3
+ "version": "1.0.0-rc.1",
4
4
  "type": "module",
5
5
  "author": "Ryan Carniato",
6
6
  "license": "MIT",
@@ -51,8 +51,8 @@
51
51
  }
52
52
  },
53
53
  "devDependencies": {
54
- "solid-js": "^1.8.16",
55
- "vinxi": "^0.3.10",
54
+ "solid-js": "^1.8.17",
55
+ "vinxi": "^0.3.11",
56
56
  "typescript": "^5.4.2"
57
57
  },
58
58
  "dependencies": {
@@ -70,7 +70,7 @@
70
70
  "source-map-js": "^1.0.2",
71
71
  "terracotta": "^1.0.4",
72
72
  "vite-plugin-inspect": "^0.7.33",
73
- "vite-plugin-solid": "~2.9.1"
73
+ "vite-plugin-solid": "^2.10.2"
74
74
  },
75
75
  "scripts": {
76
76
  "typecheck": "tsc --noEmit",