diff --git a/.editorconfig b/.editorconfig index 54c8a9183..de279a9ee 100644 --- a/.editorconfig +++ b/.editorconfig @@ -5,4 +5,6 @@ indent_style = space indent_size = 2 end_of_line = LF charset = utf-8 -insert_final_newline = true \ No newline at end of file +insert_final_newline = true +max_line_length = 100 +quote_type = double diff --git a/.oxfmtrc.json b/.oxfmtrc.json new file mode 100644 index 000000000..45c63abd9 --- /dev/null +++ b/.oxfmtrc.json @@ -0,0 +1,11 @@ +{ + "$schema": "https://raw.githubusercontent.com/oxc-project/oxc/main/npm/oxfmt/configuration_schema.json", + "tabWidth": 2, + "useTabs": false, + "endOfLine": "lf", + "trailingComma": "all", + "semi": true, + "singleQuote": false, + "arrowParens": "avoid", + "printWidth": 100 +} diff --git a/.prettierrc b/.prettierrc deleted file mode 100644 index 6aeb7ea15..000000000 --- a/.prettierrc +++ /dev/null @@ -1,8 +0,0 @@ -{ - "trailingComma": "none", - "tabWidth": 2, - "semi": true, - "singleQuote": false, - "arrowParens": "avoid", - "printWidth": 100 -} diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 318496622..a882e9714 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -74,6 +74,10 @@ Once the PR is there, **create an issue** and link the PR (mention the PR as you > [!IMPORTANT] > Mark the **allow edit by the maintainers** so we can more easily investigate the failing test and propose a fix. Otherwise we may need to close your PR and cherry-pick your commit. +### Formatting your code + +We have a set of rules defined in the `.editorconfig` and `.oxfmtrc.json` files. Please format your code before opening a PR, so that we keep the codebase consistent. Regardless of what editor you use, running `pnpm format` will format your code according to our rules. + --- If you have read all the way here, you're already a champ! 🏆 diff --git a/apps/fixtures/bare/vite.config.ts b/apps/fixtures/bare/vite.config.ts index 2d2e72458..27a23708d 100644 --- a/apps/fixtures/bare/vite.config.ts +++ b/apps/fixtures/bare/vite.config.ts @@ -3,5 +3,5 @@ import { solidStart } from "../../../packages/start/src/config"; import { nitroV2Plugin } from "../../../packages/start-nitro-v2-vite-plugin/src"; export default defineConfig({ - plugins: [solidStart(), nitroV2Plugin()], + plugins: [solidStart(), nitroV2Plugin()], }); diff --git a/apps/fixtures/basic/vite.config.ts b/apps/fixtures/basic/vite.config.ts index 2d2e72458..27a23708d 100644 --- a/apps/fixtures/basic/vite.config.ts +++ b/apps/fixtures/basic/vite.config.ts @@ -3,5 +3,5 @@ import { solidStart } from "../../../packages/start/src/config"; import { nitroV2Plugin } from "../../../packages/start-nitro-v2-vite-plugin/src"; export default defineConfig({ - plugins: [solidStart(), nitroV2Plugin()], + plugins: [solidStart(), nitroV2Plugin()], }); diff --git a/apps/fixtures/css/src/components/layout.tsx b/apps/fixtures/css/src/components/layout.tsx index 91d077054..99763f047 100644 --- a/apps/fixtures/css/src/components/layout.tsx +++ b/apps/fixtures/css/src/components/layout.tsx @@ -26,8 +26,8 @@ const Layout = (props: FlowProps<{ title: string }>) => { diff --git a/apps/fixtures/css/src/components/lazy.tsx b/apps/fixtures/css/src/components/lazy.tsx index 462362ec2..1a64df9d1 100644 --- a/apps/fixtures/css/src/components/lazy.tsx +++ b/apps/fixtures/css/src/components/lazy.tsx @@ -1,7 +1,7 @@ import "../styles/lazy.css"; const Lazy = () => { - return <> -} + return <>; +}; export default Lazy; diff --git a/apps/fixtures/css/src/components/lazyGlob.tsx b/apps/fixtures/css/src/components/lazyGlob.tsx index 75dca500b..0256e799e 100644 --- a/apps/fixtures/css/src/components/lazyGlob.tsx +++ b/apps/fixtures/css/src/components/lazyGlob.tsx @@ -1,7 +1,7 @@ import "../styles/lazyGlob.css"; const Lazy = () => { - return <> -} + return <>; +}; export default Lazy; diff --git a/apps/fixtures/css/src/components/lazyLink.tsx b/apps/fixtures/css/src/components/lazyLink.tsx index f3b9084e7..5d352d41b 100644 --- a/apps/fixtures/css/src/components/lazyLink.tsx +++ b/apps/fixtures/css/src/components/lazyLink.tsx @@ -1,5 +1,5 @@ import url from "../styles/lazyLink.css?url"; -const Lazy = () => +const Lazy = () => ; export default Lazy; diff --git a/apps/fixtures/css/src/components/lazyLinkTmp.tsx b/apps/fixtures/css/src/components/lazyLinkTmp.tsx index 3a04dd96e..6061f995b 100644 --- a/apps/fixtures/css/src/components/lazyLinkTmp.tsx +++ b/apps/fixtures/css/src/components/lazyLinkTmp.tsx @@ -1,5 +1,5 @@ import url from "../styles/lazyLinkTmp.css?url"; -const Lazy = () => +const Lazy = () => ; export default Lazy; diff --git a/apps/fixtures/css/src/components/test.tsx b/apps/fixtures/css/src/components/test.tsx index 8f3e8ea1a..43b7de1b9 100644 --- a/apps/fixtures/css/src/components/test.tsx +++ b/apps/fixtures/css/src/components/test.tsx @@ -23,7 +23,7 @@ const Test = (props: { "grid grid-cols-subgrid col-span-full items-center rounded text-white font-medium py-1 px-2 border-4 transition-colors duration-[1.5s]", props.invert ? "bg-success" : "bg-error", props.noSupport ? "border-warn" : "border-transparent", - props.class + props.class, )} >
{props.component}
diff --git a/apps/fixtures/css/src/routes/[...404].tsx b/apps/fixtures/css/src/routes/[...404].tsx index 53b221ce7..4ea71ec7f 100644 --- a/apps/fixtures/css/src/routes/[...404].tsx +++ b/apps/fixtures/css/src/routes/[...404].tsx @@ -16,4 +16,4 @@ export default function NotFound() {

); -} \ No newline at end of file +} diff --git a/apps/fixtures/css/vite.config.ts b/apps/fixtures/css/vite.config.ts index 598584d4b..636b0a385 100644 --- a/apps/fixtures/css/vite.config.ts +++ b/apps/fixtures/css/vite.config.ts @@ -4,5 +4,5 @@ import { nitroV2Plugin } from "../../../packages/start-nitro-v2-vite-plugin/src" import { solidStart } from "../../../packages/start/src/config"; export default defineConfig({ - plugins: [solidStart(), nitroV2Plugin(), tailwindcss()] + plugins: [solidStart(), nitroV2Plugin(), tailwindcss()], }); diff --git a/apps/fixtures/experiments/src/components/BreaksOnServer.tsx b/apps/fixtures/experiments/src/components/BreaksOnServer.tsx index 8e6ae7eec..f6b161610 100644 --- a/apps/fixtures/experiments/src/components/BreaksOnServer.tsx +++ b/apps/fixtures/experiments/src/components/BreaksOnServer.tsx @@ -2,5 +2,5 @@ const location = window.document.location; export default function BreaksOnServer() { - return
Breaks on server {location.href}
-} \ No newline at end of file + return
Breaks on server {location.href}
; +} diff --git a/apps/fixtures/experiments/src/entry-server.tsx b/apps/fixtures/experiments/src/entry-server.tsx index 9ac19e964..3cc2ec0c5 100644 --- a/apps/fixtures/experiments/src/entry-server.tsx +++ b/apps/fixtures/experiments/src/entry-server.tsx @@ -8,23 +8,21 @@ declare module "@solidjs/start/server" { } } -export default createHandler( - () => ( - ( - - - - - - {assets} - - -
{children}
- {scripts} - - - )} - /> - ) -); +export default createHandler(() => ( + ( + + + + + + {assets} + + +
{children}
+ {scripts} + + + )} + /> +)); diff --git a/apps/fixtures/experiments/src/middleware.ts b/apps/fixtures/experiments/src/middleware.ts index 83072a35c..4b54dfb5e 100644 --- a/apps/fixtures/experiments/src/middleware.ts +++ b/apps/fixtures/experiments/src/middleware.ts @@ -2,19 +2,19 @@ import { getRequestURL } from "@solidjs/start/http"; import { createMiddleware } from "@solidjs/start/middleware"; export default createMiddleware({ - onRequest: [ - (event) => { - event.locals.foo = "bar"; - console.log("REQUEST", event.request.url); - console.log( - "SEARCH PARAM KEYS FROM ASYNC CONTEXT", - Array.from(getRequestURL().searchParams.keys()), - ); - }, - ], - onBeforeResponse: [ - (event, { body }) => { - console.log("BEFORE RESPONSE", body); - }, - ], + onRequest: [ + event => { + event.locals.foo = "bar"; + console.log("REQUEST", event.request.url); + console.log( + "SEARCH PARAM KEYS FROM ASYNC CONTEXT", + Array.from(getRequestURL().searchParams.keys()), + ); + }, + ], + onBeforeResponse: [ + (event, { body }) => { + console.log("BEFORE RESPONSE", body); + }, + ], }); diff --git a/apps/fixtures/experiments/src/routes/(group).tsx b/apps/fixtures/experiments/src/routes/(group).tsx index cd362dfeb..f8b17d8fd 100644 --- a/apps/fixtures/experiments/src/routes/(group).tsx +++ b/apps/fixtures/experiments/src/routes/(group).tsx @@ -1,8 +1,10 @@ import { RouteSectionProps } from "@solidjs/router"; -export default function(props: RouteSectionProps) { - return <> -

Group

- {props.children} - -} \ No newline at end of file +export default function (props: RouteSectionProps) { + return ( + <> +

Group

+ {props.children} + + ); +} diff --git a/apps/fixtures/experiments/src/routes/(group)/other.tsx b/apps/fixtures/experiments/src/routes/(group)/other.tsx index ec973e9b2..64c3cec02 100644 --- a/apps/fixtures/experiments/src/routes/(group)/other.tsx +++ b/apps/fixtures/experiments/src/routes/(group)/other.tsx @@ -1,3 +1,3 @@ -export default function() { - return
OTHER
-} \ No newline at end of file +export default function () { + return
OTHER
; +} diff --git a/apps/fixtures/experiments/src/routes/(group2).tsx b/apps/fixtures/experiments/src/routes/(group2).tsx index 9da3d63e2..b6f213fb2 100644 --- a/apps/fixtures/experiments/src/routes/(group2).tsx +++ b/apps/fixtures/experiments/src/routes/(group2).tsx @@ -1,8 +1,10 @@ import { RouteSectionProps } from "@solidjs/router"; -export default function(props: RouteSectionProps) { - return <> -

Group 2

- {props.children} - -} \ No newline at end of file +export default function (props: RouteSectionProps) { + return ( + <> +

Group 2

+ {props.children} + + ); +} diff --git a/apps/fixtures/experiments/src/routes/(group2)/something.tsx b/apps/fixtures/experiments/src/routes/(group2)/something.tsx index e8e024771..0cab8149c 100644 --- a/apps/fixtures/experiments/src/routes/(group2)/something.tsx +++ b/apps/fixtures/experiments/src/routes/(group2)/something.tsx @@ -1,3 +1,3 @@ -export default function() { - return
SOMETHING
-} \ No newline at end of file +export default function () { + return
SOMETHING
; +} diff --git a/apps/fixtures/experiments/src/routes/[...404].tsx b/apps/fixtures/experiments/src/routes/[...404].tsx index f979d513f..c2329dac9 100644 --- a/apps/fixtures/experiments/src/routes/[...404].tsx +++ b/apps/fixtures/experiments/src/routes/[...404].tsx @@ -2,10 +2,9 @@ import { Title } from "@solidjs/meta"; import { HttpStatusCode } from "@solidjs/start"; import type { APIEvent } from "@solidjs/start/server"; - export const GET = (event: APIEvent) => { if (event.request.headers.get("accept") !== "application/json") return; - return { notFound: "API"} + return { notFound: "API" }; }; export default function NotFound() { diff --git a/apps/fixtures/experiments/src/routes/[[option]]/thing.tsx b/apps/fixtures/experiments/src/routes/[[option]]/thing.tsx index 325fd74d0..4135b83e8 100644 --- a/apps/fixtures/experiments/src/routes/[[option]]/thing.tsx +++ b/apps/fixtures/experiments/src/routes/[[option]]/thing.tsx @@ -1,5 +1,5 @@ import type { RouteSectionProps } from "@solidjs/router"; -export default function(props: RouteSectionProps) { - return
THING: {props.params.option || "NO"}
-} \ No newline at end of file +export default function (props: RouteSectionProps) { + return
THING: {props.params.option || "NO"}
; +} diff --git a/apps/fixtures/experiments/src/routes/api/hello/[name].ts b/apps/fixtures/experiments/src/routes/api/hello/[name].ts index 55873210b..98a0d9713 100644 --- a/apps/fixtures/experiments/src/routes/api/hello/[name].ts +++ b/apps/fixtures/experiments/src/routes/api/hello/[name].ts @@ -2,4 +2,4 @@ import type { APIHandler } from "@solidjs/start/server"; export const GET: APIHandler = async ({ params }) => { return `Hello ${params.name}!`; -}; \ No newline at end of file +}; diff --git a/apps/fixtures/experiments/src/routes/index.tsx b/apps/fixtures/experiments/src/routes/index.tsx index 1201bd2c0..189744fb1 100644 --- a/apps/fixtures/experiments/src/routes/index.tsx +++ b/apps/fixtures/experiments/src/routes/index.tsx @@ -13,7 +13,7 @@ const hello = GET(async (name: string) => { console.log("ID", id, e.locals.foo); return json( { hello: new Promise(r => setTimeout(() => r(name), 1000)) }, - { headers: { "cache-control": "max-age=60" } } + { headers: { "cache-control": "max-age=60" } }, ); }); @@ -22,9 +22,9 @@ export default function Home() { console.log(v); console.log(await v.hello); }); - const port = isServer ? new URL(getRequestEvent()!.request.url).port: location.port; + const port = isServer ? new URL(getRequestEvent()!.request.url).port : location.port; fetch(`http://localhost:${port}${import.meta.env.BASE_URL}/unknown`, { - headers: { Accept: "application/json" } + headers: { Accept: "application/json" }, }).then(async res => console.log(await res.json())); return (
diff --git a/apps/fixtures/experiments/src/routes/test(named)/[name]/home.tsx b/apps/fixtures/experiments/src/routes/test(named)/[name]/home.tsx index 09379129c..ed60ca55d 100644 --- a/apps/fixtures/experiments/src/routes/test(named)/[name]/home.tsx +++ b/apps/fixtures/experiments/src/routes/test(named)/[name]/home.tsx @@ -1,3 +1,3 @@ -export default function() { - return
DIFFERENT CONTENT
-} \ No newline at end of file +export default function () { + return
DIFFERENT CONTENT
; +} diff --git a/apps/fixtures/experiments/src/routes/test/(hi).tsx b/apps/fixtures/experiments/src/routes/test/(hi).tsx index e48dba856..5e6a5aa68 100644 --- a/apps/fixtures/experiments/src/routes/test/(hi).tsx +++ b/apps/fixtures/experiments/src/routes/test/(hi).tsx @@ -1,3 +1,3 @@ -export default function() { - return
CONTENT
-} \ No newline at end of file +export default function () { + return
CONTENT
; +} diff --git a/apps/fixtures/experiments/src/routes/test/[name].tsx b/apps/fixtures/experiments/src/routes/test/[name].tsx index 5a6a3e216..9ea84914d 100644 --- a/apps/fixtures/experiments/src/routes/test/[name].tsx +++ b/apps/fixtures/experiments/src/routes/test/[name].tsx @@ -1,5 +1,5 @@ import { RouteSectionProps } from "@solidjs/router"; -export default function(props: RouteSectionProps) { - return
{props.params.name}
-} \ No newline at end of file +export default function (props: RouteSectionProps) { + return
{props.params.name}
; +} diff --git "a/apps/fixtures/experiments/src/routes/\346\274\242\345\255\227.tsx" "b/apps/fixtures/experiments/src/routes/\346\274\242\345\255\227.tsx" index 589296b5f..c8920b4a5 100644 --- "a/apps/fixtures/experiments/src/routes/\346\274\242\345\255\227.tsx" +++ "b/apps/fixtures/experiments/src/routes/\346\274\242\345\255\227.tsx" @@ -1,3 +1,3 @@ -export default function() { - return
漢字
-} \ No newline at end of file +export default function () { + return
漢字
; +} diff --git a/apps/fixtures/experiments/vite.config.ts b/apps/fixtures/experiments/vite.config.ts index 96122c421..57596416d 100644 --- a/apps/fixtures/experiments/vite.config.ts +++ b/apps/fixtures/experiments/vite.config.ts @@ -1,7 +1,7 @@ import { defineConfig } from "vite"; import { solidStart } from "../../../packages/start/src/config"; -import { nitroV2Plugin } from '../../../packages/start-nitro-v2-vite-plugin/src' +import { nitroV2Plugin } from "../../../packages/start-nitro-v2-vite-plugin/src"; export default defineConfig({ - plugins: [solidStart({ middleware: "./src/middleware.ts" }), nitroV2Plugin()], + plugins: [solidStart({ middleware: "./src/middleware.ts" }), nitroV2Plugin()], }); diff --git a/apps/fixtures/hackernews/public/sw.js b/apps/fixtures/hackernews/public/sw.js index b2ecb5056..fcf9d79af 100644 --- a/apps/fixtures/hackernews/public/sw.js +++ b/apps/fixtures/hackernews/public/sw.js @@ -1,14 +1,14 @@ -self.addEventListener("fetch", (e) => { +self.addEventListener("fetch", e => { (e.request.url.includes("localhost") || e.request.url.includes("workers")) && e.respondWith( caches .open("solid-hn") - .then((t) => + .then(t => t .match(e.request) - .then((n) => n || fetch(e.request).then((n) => (t.put(e.request, n.clone()), n))) - ) + .then(n => n || fetch(e.request).then(n => (t.put(e.request, n.clone()), n))), + ), ); }); -self.addEventListener("activate", (e) => e.waitUntil(caches.delete("solid-hn"))); +self.addEventListener("activate", e => e.waitUntil(caches.delete("solid-hn"))); diff --git a/apps/fixtures/hackernews/src/components/comment.tsx b/apps/fixtures/hackernews/src/components/comment.tsx index 5fac5d45f..8a85eb9df 100644 --- a/apps/fixtures/hackernews/src/components/comment.tsx +++ b/apps/fixtures/hackernews/src/components/comment.tsx @@ -7,8 +7,8 @@ const Comment: Component<{ comment: CommentDefinition }> = props => { return (
  • - {props.comment.user}{" "} - {props.comment.time_ago} ago + {props.comment.user} {props.comment.time_ago}{" "} + ago
    diff --git a/apps/fixtures/hackernews/src/lib/api.ts b/apps/fixtures/hackernews/src/lib/api.ts index 95c983170..6b69a444b 100644 --- a/apps/fixtures/hackernews/src/lib/api.ts +++ b/apps/fixtures/hackernews/src/lib/api.ts @@ -31,13 +31,16 @@ const mapStories = { new: "newest", show: "show", ask: "ask", - job: "jobs" + job: "jobs", } as const; -export const getStories = query(async (type: StoryTypes, page: number): Promise => { - "use server"; - return fetchAPI(`${mapStories[type]}?page=${page}`); -}, "stories"); +export const getStories = query( + async (type: StoryTypes, page: number): Promise => { + "use server"; + return fetchAPI(`${mapStories[type]}?page=${page}`); + }, + "stories", +); export const getStory = query(async (id: string): Promise => { "use server"; diff --git a/apps/fixtures/hackernews/src/routes/[...stories].tsx b/apps/fixtures/hackernews/src/routes/[...stories].tsx index 6b0aae992..b219dfb0f 100644 --- a/apps/fixtures/hackernews/src/routes/[...stories].tsx +++ b/apps/fixtures/hackernews/src/routes/[...stories].tsx @@ -7,7 +7,7 @@ import { StoryTypes } from "~/types"; export const route = { preload({ location, params }) { void getStories((params.stories as StoryTypes) || "top", +location.query.page || 1); - } + }, } satisfies RouteDefinition; export default function Stories(props: RouteSectionProps) { diff --git a/apps/fixtures/hackernews/src/routes/stories/[id].tsx b/apps/fixtures/hackernews/src/routes/stories/[id].tsx index 9aa19502f..59350e70d 100644 --- a/apps/fixtures/hackernews/src/routes/stories/[id].tsx +++ b/apps/fixtures/hackernews/src/routes/stories/[id].tsx @@ -6,7 +6,7 @@ import { getStory } from "~/lib/api"; export const route = { preload({ params }) { void getStory(params.id); - } + }, } satisfies RouteDefinition; export default function Story(props: RouteSectionProps) { diff --git a/apps/fixtures/hackernews/src/routes/users/[id].tsx b/apps/fixtures/hackernews/src/routes/users/[id].tsx index f6f758404..7a14752de 100644 --- a/apps/fixtures/hackernews/src/routes/users/[id].tsx +++ b/apps/fixtures/hackernews/src/routes/users/[id].tsx @@ -5,7 +5,7 @@ import { getUser } from "~/lib/api"; export const route = { preload({ params }) { void getUser(params.id); - } + }, } satisfies RouteDefinition; export default function User(props: RouteSectionProps) { @@ -34,4 +34,4 @@ export default function User(props: RouteSectionProps) {
    ); -}; +} diff --git a/apps/fixtures/hackernews/vite.config.ts b/apps/fixtures/hackernews/vite.config.ts index 5c978d7d2..96aa23a3c 100644 --- a/apps/fixtures/hackernews/vite.config.ts +++ b/apps/fixtures/hackernews/vite.config.ts @@ -4,5 +4,5 @@ import { solidStart } from "../../../packages/start/src/config"; import { nitroV2Plugin } from "../../../packages/start-nitro-v2-vite-plugin/src"; export default defineConfig({ - plugins: [solidStart()], + plugins: [solidStart()], }); diff --git a/apps/fixtures/nitro-3/vite.config.ts b/apps/fixtures/nitro-3/vite.config.ts index 72eeabf4e..8e7a17f88 100644 --- a/apps/fixtures/nitro-3/vite.config.ts +++ b/apps/fixtures/nitro-3/vite.config.ts @@ -3,5 +3,5 @@ import { nitro } from "nitro/vite"; import { defineConfig } from "vite"; export default defineConfig({ - plugins: [solidStart(), nitro({ preset: "node-server" })] + plugins: [solidStart(), nitro({ preset: "node-server" })], }); diff --git a/apps/fixtures/notes/src/components/NoteList.tsx b/apps/fixtures/notes/src/components/NoteList.tsx index 3e981e85e..0a78ba3a8 100644 --- a/apps/fixtures/notes/src/components/NoteList.tsx +++ b/apps/fixtures/notes/src/components/NoteList.tsx @@ -7,25 +7,25 @@ export default function NoteList(props: { searchText: string }) { const notes = createAsyncStore(() => getNotes(props.searchText)); return ( - - {props.searchText - ? `Couldn't find any notes titled "${props.searchText}".` - : "No notes created yet!"} - - } - > -
      - - {note => ( -
    • - -
    • - )} -
      -
    -
    + + {props.searchText + ? `Couldn't find any notes titled "${props.searchText}".` + : "No notes created yet!"} + + } + > +
      + + {note => ( +
    • + +
    • + )} +
      +
    +
    ); } diff --git a/apps/fixtures/notes/src/components/SearchField.tsx b/apps/fixtures/notes/src/components/SearchField.tsx index 62a4685f6..f64c5f2da 100644 --- a/apps/fixtures/notes/src/components/SearchField.tsx +++ b/apps/fixtures/notes/src/components/SearchField.tsx @@ -22,7 +22,7 @@ export default function SearchField() { value={search.searchText || ""} onInput={e => { setParams({ - searchText: e.target.value + searchText: e.target.value, }); }} /> diff --git a/apps/fixtures/notes/src/components/SidebarNote.tsx b/apps/fixtures/notes/src/components/SidebarNote.tsx index 04155f276..ec2a53cfa 100644 --- a/apps/fixtures/notes/src/components/SidebarNote.tsx +++ b/apps/fixtures/notes/src/components/SidebarNote.tsx @@ -34,7 +34,7 @@ export default function SidebarNote(props: { note: Note }) { itemRef.classList.remove("flash"); }} style={{ - color: "black" + color: "black", }} class={["sidebar-note-list-item", isExpanded() ? "note-expanded" : ""].join(" ")} > @@ -49,9 +49,9 @@ export default function SidebarNote(props: { note: Note }) { "background-color": isPending() ? "var(--gray-80)" : isActive() - ? "var(--tertiary-blue)" - : "", - border: isActive() ? "1px solid var(--primary-border)" : "1px solid transparent" + ? "var(--tertiary-blue)" + : "", + border: isActive() ? "1px solid var(--primary-border)" : "1px solid transparent", }} > Open note for preview @@ -72,7 +72,7 @@ export default function SidebarNote(props: { note: Note }) {
    ); -} \ No newline at end of file +} diff --git a/apps/fixtures/notes/src/routes/notes/[id]/(preview).tsx b/apps/fixtures/notes/src/routes/notes/[id]/(preview).tsx index c33cdb564..e5a87ad25 100644 --- a/apps/fixtures/notes/src/routes/notes/[id]/(preview).tsx +++ b/apps/fixtures/notes/src/routes/notes/[id]/(preview).tsx @@ -6,7 +6,7 @@ import { getNotePreview } from "~/lib/api"; export const route = { preload({ params }) { getNotePreview(+params.id); - } + }, } satisfies RouteDefinition; export default function NotePage({ params }: RouteSectionProps) { diff --git a/apps/fixtures/notes/src/routes/notes/[id]/edit.tsx b/apps/fixtures/notes/src/routes/notes/[id]/edit.tsx index 81ec84004..af56e8d71 100644 --- a/apps/fixtures/notes/src/routes/notes/[id]/edit.tsx +++ b/apps/fixtures/notes/src/routes/notes/[id]/edit.tsx @@ -6,7 +6,7 @@ import { getNote } from "~/lib/api"; export const route = { preload({ params }) { getNote(+params.id); - } + }, } satisfies RouteDefinition; export default function EditNote({ params }: RouteSectionProps) { diff --git a/apps/fixtures/notes/vite.config.ts b/apps/fixtures/notes/vite.config.ts index fad292267..27a23708d 100644 --- a/apps/fixtures/notes/vite.config.ts +++ b/apps/fixtures/notes/vite.config.ts @@ -3,5 +3,5 @@ import { solidStart } from "../../../packages/start/src/config"; import { nitroV2Plugin } from "../../../packages/start-nitro-v2-vite-plugin/src"; export default defineConfig({ - plugins: [solidStart(), nitroV2Plugin()] + plugins: [solidStart(), nitroV2Plugin()], }); diff --git a/apps/fixtures/todomvc/src/global.d.ts b/apps/fixtures/todomvc/src/global.d.ts index cee6301fa..0682c3c14 100644 --- a/apps/fixtures/todomvc/src/global.d.ts +++ b/apps/fixtures/todomvc/src/global.d.ts @@ -4,4 +4,4 @@ declare module App { * Declare your getRequestEvent().locals here */ } -} \ No newline at end of file +} diff --git a/apps/fixtures/todomvc/src/lib/api.ts b/apps/fixtures/todomvc/src/lib/api.ts index 8b3be3fa9..454748d59 100644 --- a/apps/fixtures/todomvc/src/lib/api.ts +++ b/apps/fixtures/todomvc/src/lib/api.ts @@ -12,7 +12,7 @@ export const addTodo = action(async (formData: FormData) => { const title = formData.get("title") as string; let [{ value: todos }, { value: index }] = await storage.getItems([ "todos:data", - "todos:counter" + "todos:counter", ]); // default value for first write todos = todos || []; @@ -21,9 +21,9 @@ export const addTodo = action(async (formData: FormData) => { await Promise.all([ storage.setItem("todos:data", [ ...(todos as Todo[]), - { id: index as number, title, completed: false } + { id: index as number, title, completed: false }, ]), - storage.setItem("todos:counter", (index as number) + 1) + storage.setItem("todos:counter", (index as number) + 1), ]); }); @@ -32,7 +32,7 @@ export const removeTodo = action(async (id: number) => { const todos = (await storage.getItem("todos:data")) as Todo[]; await storage.setItem( "todos:data", - todos.filter(todo => todo.id !== id) + todos.filter(todo => todo.id !== id), ); }); @@ -46,7 +46,7 @@ export const toggleTodo = action(async (id: number) => { todo.completed = !todo.completed; } return todo; - }) + }), ); }); @@ -61,7 +61,7 @@ export const editTodo = action(async (id: number, formData: FormData) => { todo.title = title; } return todo; - }) + }), ); }); @@ -70,7 +70,7 @@ export const toggleAll = action(async (completed: boolean) => { const todos = (await storage.getItem("todos:data")) as Todo[]; await storage.setItem( "todos:data", - todos.map(todo => ({ ...todo, completed })) + todos.map(todo => ({ ...todo, completed })), ); }); @@ -79,6 +79,6 @@ export const clearCompleted = action(async () => { const todos = (await storage.getItem("todos:data")) as Todo[]; await storage.setItem( "todos:data", - todos.filter(todo => !todo.completed) + todos.filter(todo => !todo.completed), ); }); diff --git a/apps/fixtures/todomvc/src/lib/db.ts b/apps/fixtures/todomvc/src/lib/db.ts index 2140a3d20..69567bbcc 100644 --- a/apps/fixtures/todomvc/src/lib/db.ts +++ b/apps/fixtures/todomvc/src/lib/db.ts @@ -5,6 +5,6 @@ import fsLiteDriver from "unstorage/drivers/fs-lite"; // swap with the key value of your choice in your deployed environment export const storage = createStorage({ driver: fsLiteDriver({ - base: "./.data" - }) -}); \ No newline at end of file + base: "./.data", + }), +}); diff --git a/apps/fixtures/todomvc/src/routes/index.tsx b/apps/fixtures/todomvc/src/routes/index.tsx index c37462896..4b8c21bc8 100644 --- a/apps/fixtures/todomvc/src/routes/index.tsx +++ b/apps/fixtures/todomvc/src/routes/index.tsx @@ -3,7 +3,7 @@ import { createAsyncStore, useSubmission, useSubmissions, - type RouteSectionProps + type RouteSectionProps, } from "@solidjs/router"; import { For, Show, createMemo, createSignal } from "solid-js"; import { CompleteIcon, IncompleteIcon } from "~/components/icons"; @@ -14,7 +14,7 @@ import { getTodos, removeTodo, toggleAll, - toggleTodo + toggleTodo, } from "~/lib/api"; import { Todo } from "~/types"; @@ -30,7 +30,7 @@ const setFocus = (el: HTMLElement) => setTimeout(() => el.focus()); export const route = { preload() { getTodos(); - } + }, } satisfies RouteDefinition; export default function TodoApp(props: RouteSectionProps) { @@ -50,7 +50,7 @@ export default function TodoApp(props: RouteSectionProps) { todos().length + addingTodo.length - todos().filter(todo => todo.completed).length - - removingTodo.length + removingTodo.length, ); const filterList = (todos: Todo[]) => { if (location.query.show === "active") return todos.filter(todo => !todo.completed); @@ -112,7 +112,7 @@ export default function TodoApp(props: RouteSectionProps) { classList={{ editing: editingTodoId() === todo.id, completed: completed(), - pending: pending() + pending: pending(), }} >
    diff --git a/apps/fixtures/todomvc/src/types.ts b/apps/fixtures/todomvc/src/types.ts index d80664d84..f9e06b381 100644 --- a/apps/fixtures/todomvc/src/types.ts +++ b/apps/fixtures/todomvc/src/types.ts @@ -2,4 +2,4 @@ export interface Todo { id: number; title: string; completed: boolean; -} \ No newline at end of file +} diff --git a/apps/fixtures/todomvc/vite.config.ts b/apps/fixtures/todomvc/vite.config.ts index 2edadfafd..1ce8d96d1 100644 --- a/apps/fixtures/todomvc/vite.config.ts +++ b/apps/fixtures/todomvc/vite.config.ts @@ -4,5 +4,5 @@ import { solidStart } from "../../../packages/start/src/config"; import { nitroV2Plugin } from "../../../packages/start-nitro-v2-vite-plugin/src"; export default defineConfig({ - plugins: [solidStart(), nitroV2Plugin()], + plugins: [solidStart(), nitroV2Plugin()], }); diff --git a/apps/landing-page/postcss.config.js b/apps/landing-page/postcss.config.js index c841f15a5..f78038278 100644 --- a/apps/landing-page/postcss.config.js +++ b/apps/landing-page/postcss.config.js @@ -1,7 +1,7 @@ export default { - plugins: { - "tailwindcss/nesting": {}, - tailwindcss: {}, - autoprefixer: {}, - }, + plugins: { + "tailwindcss/nesting": {}, + tailwindcss: {}, + autoprefixer: {}, + }, }; diff --git a/apps/landing-page/src/components/bento.tsx b/apps/landing-page/src/components/bento.tsx index 2a770be0f..eae7dd6ee 100644 --- a/apps/landing-page/src/components/bento.tsx +++ b/apps/landing-page/src/components/bento.tsx @@ -14,43 +14,43 @@ function getAccent(accent: BentoItemProps["accent"]) { case "pink": return { box: "dark:hover:border-pink-300 dark:hover:shadow-pink-200 hover:border-pink-500 hover:shadow-pink-600", - title: "group-hover:text-pink-500 dark:group-hover:text-pink-300" + title: "group-hover:text-pink-500 dark:group-hover:text-pink-300", }; case "yellow": return { box: "hover:border-yellow-500 hover:shadow-yellow-600 dark:hover:border-yellow-300 dark:hover:shadow-yellow-200", - title: "group-hover:text-yellow-500 dark:group-hover:text-yellow-300" + title: "group-hover:text-yellow-500 dark:group-hover:text-yellow-300", }; case "neutral": return { box: "hover:border-neutral-500 hover:shadow-neutral-600 dark:hover:border-neutral-300 dark:hover:shadow-neutral-200", - title: "group-hover:text-neutral-500 dark:group-hover:text-neutral-300" + title: "group-hover:text-neutral-500 dark:group-hover:text-neutral-300", }; case "emerald": return { box: "hover:border-emerald-500 hover:shadow-emerald-600 dark:hover:border-emerald-300 dark:hover:shadow-emerald-200", - title: "group-hover:text-emerald-500 dark:group-hover:text-emerald-300" + title: "group-hover:text-emerald-500 dark:group-hover:text-emerald-300", }; case "purple": return { box: "hover:border-purple-500 hover:shadow-purple-600 dark:hover:border-purple-300 dark:hover:shadow-purple-200", - title: "group-hover:text-purple-500 dark:group-hover:text-purple-300" + title: "group-hover:text-purple-500 dark:group-hover:text-purple-300", }; case "lime": return { box: "hover:border-lime-500 hover:shadow-lime-600 dark:hover:border-lime-300 dark:hover:shadow-lime-200", - title: "group-hover:text-lime-500 dark:group-hover:text-lime-300" + title: "group-hover:text-lime-500 dark:group-hover:text-lime-300", }; case "teal": return { box: "hover:border-teal-500 hover:shadow-teal-600 dark:hover:border-teal-300 dark:hover:shadow-teal-200", - title: "group-hover:text-teal-500 dark:group-hover:text-teal-300" + title: "group-hover:text-teal-500 dark:group-hover:text-teal-300", }; case "cyan": default: return { box: "hover:border-cyan-500 hover:shadow-cyan-600 dark:hover:border-cyan-300 dark:hover:shadow-cyan-200", - title: "group-hover:text-cyan-500 dark:group-hover:text-cyan-300" + title: "group-hover:text-cyan-500 dark:group-hover:text-cyan-300", }; } } diff --git a/apps/landing-page/src/components/download-logos-menu.tsx b/apps/landing-page/src/components/download-logos-menu.tsx index a4be87a75..3f9c66c4d 100644 --- a/apps/landing-page/src/components/download-logos-menu.tsx +++ b/apps/landing-page/src/components/download-logos-menu.tsx @@ -7,7 +7,7 @@ import { ContextMenuSub, ContextMenuSubContent, ContextMenuSubTrigger, - ContextMenuTrigger + ContextMenuTrigger, } from "~/components/ui/context-menu"; import { SolidStartLogo } from "./icons/solidstart-logo"; export function DownloadLogosMenu() { diff --git a/apps/landing-page/src/components/icons/lego-icon.tsx b/apps/landing-page/src/components/icons/lego-icon.tsx index 3fd5031a2..510e4f82b 100644 --- a/apps/landing-page/src/components/icons/lego-icon.tsx +++ b/apps/landing-page/src/components/icons/lego-icon.tsx @@ -15,10 +15,7 @@ export function LegoSVG() { id="bottom" class="translate-y-20 group-hover:translate-y-0 transition-transform duration-200 ease-in-out" > - + - + - + - + - + @@ -35,18 +28,8 @@ export function SolidIcon(props: Props) { opacity=".3" fill="url(#a)" /> - - + + @@ -68,10 +51,7 @@ export function SolidIcon(props: Props) { - + - + ); } diff --git a/apps/landing-page/src/components/nested-grid.tsx b/apps/landing-page/src/components/nested-grid.tsx index 3827dad64..c945db01e 100644 --- a/apps/landing-page/src/components/nested-grid.tsx +++ b/apps/landing-page/src/components/nested-grid.tsx @@ -14,43 +14,43 @@ function getAccent(accent: BentoItemProps["accent"]) { case "pink": return { box: "dark:hover:border-pink-300 dark:hover:shadow-pink-200 hover:border-pink-500 hover:shadow-pink-600", - title: "group-hover:text-pink-500 dark:group-hover:text-pink-300" + title: "group-hover:text-pink-500 dark:group-hover:text-pink-300", }; case "yellow": return { box: "hover:border-yellow-500 hover:shadow-yellow-600 dark:hover:border-yellow-300 dark:hover:shadow-yellow-200", - title: "group-hover:text-yellow-500 dark:group-hover:text-yellow-300" + title: "group-hover:text-yellow-500 dark:group-hover:text-yellow-300", }; case "neutral": return { box: "hover:border-neutral-500 hover:shadow-neutral-600 dark:hover:border-neutral-300 dark:hover:shadow-neutral-200", - title: "group-hover:text-neutral-500 dark:group-hover:text-neutral-300" + title: "group-hover:text-neutral-500 dark:group-hover:text-neutral-300", }; case "emerald": return { box: "hover:border-emerald-500 hover:shadow-emerald-600 dark:hover:border-emerald-300 dark:hover:shadow-emerald-200", - title: "group-hover:text-emerald-500 dark:group-hover:text-emerald-300" + title: "group-hover:text-emerald-500 dark:group-hover:text-emerald-300", }; case "purple": return { box: "hover:border-purple-500 hover:shadow-purple-600 dark:hover:border-purple-300 dark:hover:shadow-purple-200", - title: "group-hover:text-purple-500 dark:group-hover:text-purple-300" + title: "group-hover:text-purple-500 dark:group-hover:text-purple-300", }; case "lime": return { box: "hover:border-lime-500 hover:shadow-lime-600 dark:hover:border-lime-300 dark:hover:shadow-lime-200", - title: "group-hover:text-lime-500 dark:group-hover:text-lime-300" + title: "group-hover:text-lime-500 dark:group-hover:text-lime-300", }; case "teal": return { box: "hover:border-teal-500 hover:shadow-teal-600 dark:hover:border-teal-300 dark:hover:shadow-teal-200", - title: "group-hover:text-teal-500 dark:group-hover:text-teal-300" + title: "group-hover:text-teal-500 dark:group-hover:text-teal-300", }; case "cyan": default: return { box: "hover:border-cyan-500 hover:shadow-cyan-600 dark:hover:border-cyan-300 dark:hover:shadow-cyan-200", - title: "group-hover:text-cyan-500 dark:group-hover:text-cyan-300" + title: "group-hover:text-cyan-500 dark:group-hover:text-cyan-300", }; } } diff --git a/apps/landing-page/src/components/sections/deploy-anywhere.tsx b/apps/landing-page/src/components/sections/deploy-anywhere.tsx index 59185a1ab..3a1b8f8ff 100644 --- a/apps/landing-page/src/components/sections/deploy-anywhere.tsx +++ b/apps/landing-page/src/components/sections/deploy-anywhere.tsx @@ -12,12 +12,12 @@ const PLATFORMS = [ { name: "Cloudflare", url: "https://www.cloudflare.com/", - icon: + icon: , }, { name: "Netlify", url: "https://www.netlify.com/", - icon: + icon: , }, { @@ -25,28 +25,28 @@ const PLATFORMS = [ url: "https://vercel.com/", icon: ( - ) + ), }, { name: "Bun", url: "https://bun.sh/", - icon: + icon: , }, { name: "Deno", url: "https://deno.land/", - icon: + icon: , }, { name: "AWS", url: "https://aws.amazon.com/", - icon: + icon: , }, { name: "Azure", url: "https://azure.microsoft.com/", - icon: - } + icon: , + }, ]; export function DeployAnywhere() { diff --git a/apps/landing-page/src/components/sections/hero.tsx b/apps/landing-page/src/components/sections/hero.tsx index dd25b96fa..2853ba7d5 100644 --- a/apps/landing-page/src/components/sections/hero.tsx +++ b/apps/landing-page/src/components/sections/hero.tsx @@ -6,7 +6,7 @@ import { buttonVariants } from "../ui/button"; import { AnimatedShinyText } from "../ui/mystic/shine"; const buttonOutlineStyles = buttonVariants({ - variant: "outline" + variant: "outline", }); export function Hero() { diff --git a/apps/landing-page/src/components/theme-toggle.tsx b/apps/landing-page/src/components/theme-toggle.tsx index 4d7cf0bd4..fe2234b8e 100644 --- a/apps/landing-page/src/components/theme-toggle.tsx +++ b/apps/landing-page/src/components/theme-toggle.tsx @@ -17,9 +17,9 @@ export function ThemeToggle() { return ( - - - Toggle theme + + + Toggle theme setColorMode("light")}> diff --git a/apps/landing-page/src/components/ui/accordion.tsx b/apps/landing-page/src/components/ui/accordion.tsx index e7fb88c3c..eade77217 100644 --- a/apps/landing-page/src/components/ui/accordion.tsx +++ b/apps/landing-page/src/components/ui/accordion.tsx @@ -1,37 +1,35 @@ import { splitProps } from "solid-js"; -import { AccordionContentProps, AccordionItemProps, Accordion as AccordionPrimitive, AccordionTriggerProps } from "@kobalte/core/accordion"; +import { + AccordionContentProps, + AccordionItemProps, + Accordion as AccordionPrimitive, + AccordionTriggerProps, +} from "@kobalte/core/accordion"; import { cn } from "~/lib/utils"; import { OverrideComponentProps } from "@kobalte/utils"; const Accordion = AccordionPrimitive; -const AccordionItem = ( - props: OverrideComponentProps<"div", AccordionItemProps> -) => { +const AccordionItem = (props: OverrideComponentProps<"div", AccordionItemProps>) => { const [, rest] = splitProps(props, ["class"]); return ( ); }; -const AccordionTrigger = ( - props: OverrideComponentProps<"button", AccordionTriggerProps> -) => { +const AccordionTrigger = (props: OverrideComponentProps<"button", AccordionTriggerProps>) => { const [, rest] = splitProps(props, ["class", "children"]); return ( svg]:rotate-180", - props.class + props.class, )} {...rest} > @@ -53,15 +51,13 @@ const AccordionTrigger = ( ); }; -const AccordionContent = ( - props: OverrideComponentProps<"div", AccordionContentProps> -) => { +const AccordionContent = (props: OverrideComponentProps<"div", AccordionContentProps>) => { const [, rest] = splitProps(props, ["class", "children"]); return ( diff --git a/apps/landing-page/src/components/ui/button.tsx b/apps/landing-page/src/components/ui/button.tsx index 08fd4bbaa..3bececa90 100644 --- a/apps/landing-page/src/components/ui/button.tsx +++ b/apps/landing-page/src/components/ui/button.tsx @@ -12,12 +12,9 @@ const buttonVariants = cva( variants: { variant: { default: "bg-primary text-primary-foreground hover:bg-primary/90", - destructive: - "bg-destructive text-destructive-foreground hover:bg-destructive/90", - outline: - "border border-input hover:bg-accent hover:text-accent-foreground", - secondary: - "bg-secondary text-secondary-foreground hover:bg-secondary/80", + destructive: "bg-destructive text-destructive-foreground hover:bg-destructive/90", + outline: "border border-input hover:bg-accent hover:text-accent-foreground", + secondary: "bg-secondary text-secondary-foreground hover:bg-secondary/80", ghost: "hover:bg-accent hover:text-accent-foreground", link: "text-primary underline-offset-4 hover:underline", }, @@ -32,21 +29,18 @@ const buttonVariants = cva( variant: "default", size: "default", }, - } + }, ); export interface ButtonProps extends ComponentProps<"button">, VariantProps {} -const Button: Component = (props) => { +const Button: Component = props => { const [, rest] = splitProps(props, ["variant", "size", "class"]); return (
  • - referencing multiple export named functions in the same file + + referencing multiple export named functions in the same file +
  • {props.children} diff --git a/apps/tests/src/e2e/api-call.test.ts b/apps/tests/src/e2e/api-call.test.ts index 29a354b7e..32eb475cc 100644 --- a/apps/tests/src/e2e/api-call.test.ts +++ b/apps/tests/src/e2e/api-call.test.ts @@ -13,7 +13,9 @@ test.describe("api calls", () => { expect(okResp.headers.get("x-return-header")).toBe("value"); expect(okResp.headers.get("x-shared-header")).toBe("event"); - const redirectResp = await fetch("http://localhost:3000/api/header-merging?status=redirect", { redirect: "manual" }); + const redirectResp = await fetch("http://localhost:3000/api/header-merging?status=redirect", { + redirect: "manual", + }); expect(redirectResp.headers.get("Set-Cookie")).toBeTruthy(); expect(redirectResp.headers.get("x-event-header")).toBe("value"); expect(redirectResp.headers.get("x-return-header")).toBe("value"); @@ -21,9 +23,8 @@ test.describe("api calls", () => { }); test("should preserve multiple Set-Cookie headers on redirect (RFC 6265)", async () => { - const response = await fetch("http://localhost:3000/api/multi-set-cookie-redirect", { - redirect: "manual" + redirect: "manual", }); expect(response.status).toBe(302); diff --git a/apps/tests/src/e2e/http-header.test.ts b/apps/tests/src/e2e/http-header.test.ts index 572a77721..c26f5a19f 100644 --- a/apps/tests/src/e2e/http-header.test.ts +++ b/apps/tests/src/e2e/http-header.test.ts @@ -1,10 +1,10 @@ import { expect, test } from "@playwright/test"; test.describe("http header", () => { - // couldn't get this to see the headers but verified in chrome devtools - test.skip("should set http header", async ({ page }) => { - const response = await page.goto("/http-header"); + // couldn't get this to see the headers but verified in chrome devtools + test.skip("should set http header", async ({ page }) => { + const response = await page.goto("/http-header"); - expect(response?.headers()["test-header"]).toBe("test-value"); - }); + expect(response?.headers()["test-header"]).toBe("test-value"); + }); }); diff --git a/apps/tests/src/e2e/route-groups.test.ts b/apps/tests/src/e2e/route-groups.test.ts index 638706c65..25406163f 100644 --- a/apps/tests/src/e2e/route-groups.test.ts +++ b/apps/tests/src/e2e/route-groups.test.ts @@ -2,28 +2,28 @@ import { test, expect } from "@playwright/test"; test.describe("route-groups", () => { test("should resolve `/routes/nested/(ignored)route0.tsx` to `nested/route0`", async ({ - page + page, }) => { await page.goto("http://localhost:3000/nested/route0"); await expect(page.locator("body")).toContainText("nested route 0"); }); test("should resolve `/routes/nested/(level1)/(ignored)route1.tsx` to `nested/route1`", async ({ - page + page, }) => { await page.goto("http://localhost:3000/nested/route1"); await expect(page.locator("body")).toContainText("nested route 1"); }); test("should resolve `/routes/nested/(level1)/(level2)/(ignored)route2.tsx` to `nested/route2`", async ({ - page + page, }) => { await page.goto("http://localhost:3000/nested/route2"); await expect(page.locator("body")).toContainText("nested route 2"); }); test("should resolve `/routes/nested/(level1)/(level2)/route3.tsx` to `nested/route3`", async ({ - page + page, }) => { await page.goto("http://localhost:3000/nested/route3"); await expect(page.locator("body")).toContainText("nested route 3"); diff --git a/apps/tests/src/e2e/server-function.test.ts b/apps/tests/src/e2e/server-function.test.ts index 12dbf112e..7a8131be9 100644 --- a/apps/tests/src/e2e/server-function.test.ts +++ b/apps/tests/src/e2e/server-function.test.ts @@ -12,7 +12,7 @@ test.describe("server-function", () => { }); test("should have an id of type string in the server function meta - nested", async ({ - page + page, }) => { await page.goto("http://localhost:3000/server-function-meta-nested"); await expect(page.locator("#server-fn-test")).toContainText('{"serverFnWithMeta":"string"}'); @@ -21,14 +21,14 @@ test.describe("server-function", () => { test("should externalize node builtin in server function - nested", async ({ page }) => { await page.goto("http://localhost:3000/node-builtin-nested"); await expect(page.locator("#server-fn-test")).toContainText( - '{"serverFnWithNodeBuiltin":"can/externalize"}' + '{"serverFnWithNodeBuiltin":"can/externalize"}', ); }); test("should externalize npm module in server function - nested", async ({ page }) => { await page.goto("http://localhost:3000/npm-module-nested"); await expect(page.locator("#server-fn-test")).toContainText( - '{"serverFnWithNpmModule":[2,4,6]}' + '{"serverFnWithNpmModule":[2,4,6]}', ); }); @@ -38,7 +38,7 @@ test.describe("server-function", () => { }); test("should have an id of type string in the server function meta - toplevel", async ({ - page + page, }) => { await page.goto("http://localhost:3000/server-function-meta"); await expect(page.locator("#server-fn-test")).toContainText('{"serverFnWithMeta":"string"}'); @@ -47,14 +47,14 @@ test.describe("server-function", () => { test("should externalize node builtin in server function - toplevel", async ({ page }) => { await page.goto("http://localhost:3000/node-builtin-toplevel"); await expect(page.locator("#server-fn-test")).toContainText( - '{"serverFnWithNodeBuiltin":"can/externalize"}' + '{"serverFnWithNodeBuiltin":"can/externalize"}', ); }); test("should externalize npm module in server function - toplevel", async ({ page }) => { await page.goto("http://localhost:3000/npm-module-toplevel"); await expect(page.locator("#server-fn-test")).toContainText( - '{"serverFnWithNpmModule":[2,4,6]}' + '{"serverFnWithNpmModule":[2,4,6]}', ); }); diff --git a/apps/tests/src/routes/(basic).tsx b/apps/tests/src/routes/(basic).tsx index 13c3aeeb1..54d1741b5 100644 --- a/apps/tests/src/routes/(basic).tsx +++ b/apps/tests/src/routes/(basic).tsx @@ -2,11 +2,13 @@ import { createSignal } from "solid-js"; export default function App() { const [counter, setCounter] = createSignal(0); - + return (
    {counter()} - +
    ); } diff --git a/apps/tests/src/routes/[...404].tsx b/apps/tests/src/routes/[...404].tsx index 7d71e47bc..f1d7221c8 100644 --- a/apps/tests/src/routes/[...404].tsx +++ b/apps/tests/src/routes/[...404].tsx @@ -13,9 +13,7 @@ export default function NotFound() { Not Found

    Page Not Found

    -

    - {"Your page cannot be found... >_<"} -

    +

    {"Your page cannot be found... >_<"}

    ); } diff --git a/apps/tests/src/routes/api/header-merging.ts b/apps/tests/src/routes/api/header-merging.ts index 0b85a77ea..23fe07359 100644 --- a/apps/tests/src/routes/api/header-merging.ts +++ b/apps/tests/src/routes/api/header-merging.ts @@ -4,26 +4,26 @@ export async function GET() { const url = getRequestURL(); const s = await useSession({ password: "0".repeat(32) }); - await s.update(d => ({count: (d.count || 0) + 1})) + await s.update(d => ({ count: (d.count || 0) + 1 })); setHeader("x-event-header", "value"); setHeader("x-shared-header", "event"); - if(url.searchParams.get("status") === "redirect") { + if (url.searchParams.get("status") === "redirect") { return new Response(null, { status: 301, headers: { location: "http://::/abc", "x-return-header": "value", - "x-shared-header": "return" - } - }) + "x-shared-header": "return", + }, + }); } else { return new Response(null, { headers: { "x-return-header": "value", - "x-shared-header": "return" - } - }) + "x-shared-header": "return", + }, + }); } } diff --git a/apps/tests/src/routes/api/multi-set-cookie-redirect.ts b/apps/tests/src/routes/api/multi-set-cookie-redirect.ts index 1877c1677..c65f7c3b4 100644 --- a/apps/tests/src/routes/api/multi-set-cookie-redirect.ts +++ b/apps/tests/src/routes/api/multi-set-cookie-redirect.ts @@ -12,6 +12,6 @@ export async function GET() { return new Response(null, { status: 302, - headers + headers, }); } diff --git a/apps/tests/src/routes/client-only/_component.tsx b/apps/tests/src/routes/client-only/_component.tsx index d8737ad84..687d2b85d 100644 --- a/apps/tests/src/routes/client-only/_component.tsx +++ b/apps/tests/src/routes/client-only/_component.tsx @@ -1,14 +1,14 @@ import { createSignal } from "solid-js"; import { isServer } from "solid-js/web"; - export default function ClientOnlyComponent() { - const [output, setOutput] = createSignal<{ clientWithIsServer?: boolean; }>({}); + const [output, setOutput] = createSignal<{ clientWithIsServer?: boolean }>({}); + + setOutput(prev => ({ ...prev, clientWithIsServer: isServer })); - setOutput(prev => ({ ...prev, clientWithIsServer: isServer })); - - return ( -
    - {JSON.stringify(output())} -
    ) -} \ No newline at end of file + return ( +
    + {JSON.stringify(output())} +
    + ); +} diff --git a/apps/tests/src/routes/client-only/index.tsx b/apps/tests/src/routes/client-only/index.tsx index e456d13ee..8e44c8d30 100644 --- a/apps/tests/src/routes/client-only/index.tsx +++ b/apps/tests/src/routes/client-only/index.tsx @@ -1,12 +1,11 @@ import { clientOnly } from "@solidjs/start"; -const Component = clientOnly(() => import('./_component')) +const Component = clientOnly(() => import("./_component")); export default function App() { - return ( <> - + ); } diff --git a/apps/tests/src/routes/generator-server-function.tsx b/apps/tests/src/routes/generator-server-function.tsx index 03ba7b308..8757f8452 100644 --- a/apps/tests/src/routes/generator-server-function.tsx +++ b/apps/tests/src/routes/generator-server-function.tsx @@ -1,19 +1,19 @@ import { createSignal, onMount } from "solid-js"; -import { sayHello } from '~/functions/use-generator-server-function'; +import { sayHello } from "~/functions/use-generator-server-function"; export default function GeneratorServerFunction() { - const [output, setOutput] = createSignal(''); + const [output, setOutput] = createSignal(""); - onMount(async () => { - const greetings = await sayHello(); - for await (const greeting of greetings) { - setOutput(greeting); - } - }); + onMount(async () => { + const greetings = await sayHello(); + for await (const greeting of greetings) { + setOutput(greeting); + } + }); - return ( -
    -
    {output()}
    -
    - ); -} \ No newline at end of file + return ( +
    +
    {output()}
    +
    + ); +} diff --git a/apps/tests/src/routes/http-header.tsx b/apps/tests/src/routes/http-header.tsx index a12d99293..8259da4b0 100644 --- a/apps/tests/src/routes/http-header.tsx +++ b/apps/tests/src/routes/http-header.tsx @@ -1,10 +1,10 @@ import { HttpHeader } from "@solidjs/start"; export default function HttpHeaderRoute() { - return ( -
    -

    Http Header

    - -
    - ); + return ( +
    +

    Http Header

    + +
    + ); } diff --git a/apps/tests/src/routes/is-server-const.tsx b/apps/tests/src/routes/is-server-const.tsx index 6c8821e17..7304392f4 100644 --- a/apps/tests/src/routes/is-server-const.tsx +++ b/apps/tests/src/routes/is-server-const.tsx @@ -2,18 +2,16 @@ import { createEffect, createSignal } from "solid-js"; import { serverFnWithIsServer } from "~/functions/use-is-server-const"; export default function App() { - const [output, setOutput] = createSignal<{ serverFnWithIsServer?: boolean }>({}); + const [output, setOutput] = createSignal<{ serverFnWithIsServer?: boolean }>({}); + createEffect(async () => { + const result = await serverFnWithIsServer(); + setOutput(prev => ({ ...prev, serverFnWithIsServer: result })); + }); - createEffect(async () => { - const result = await serverFnWithIsServer(); - setOutput(prev => ({ ...prev, serverFnWithIsServer: result })); - }); - - - return ( -
    - {JSON.stringify(output())} -
    - ); + return ( +
    + {JSON.stringify(output())} +
    + ); } diff --git a/apps/tests/src/routes/is-server-nested.tsx b/apps/tests/src/routes/is-server-nested.tsx index 3e6086453..f670c6350 100644 --- a/apps/tests/src/routes/is-server-nested.tsx +++ b/apps/tests/src/routes/is-server-nested.tsx @@ -8,15 +8,13 @@ function serverFnWithIsServer() { } export default function App() { - const [output, setOutput] = createSignal<{ serverFnWithIsServer?: boolean }>({}); - + const [output, setOutput] = createSignal<{ serverFnWithIsServer?: boolean }>({}); createEffect(async () => { const result = await serverFnWithIsServer(); setOutput(prev => ({ ...prev, serverFnWithIsServer: result })); }); - return (
    {JSON.stringify(output())} diff --git a/apps/tests/src/routes/is-server-toplevel.tsx b/apps/tests/src/routes/is-server-toplevel.tsx index b829cef9d..e145db8f3 100644 --- a/apps/tests/src/routes/is-server-toplevel.tsx +++ b/apps/tests/src/routes/is-server-toplevel.tsx @@ -2,15 +2,13 @@ import { createEffect, createSignal } from "solid-js"; import { serverFnWithIsServer } from "~/functions/use-is-server"; export default function App() { - const [output, setOutput] = createSignal<{ serverFnWithIsServer?: boolean }>({}); - + const [output, setOutput] = createSignal<{ serverFnWithIsServer?: boolean }>({}); createEffect(async () => { const result = await serverFnWithIsServer(); setOutput(prev => ({ ...prev, serverFnWithIsServer: result })); }); - return (
    {JSON.stringify(output())} diff --git a/apps/tests/src/routes/is-server-with-anon-default-export.tsx b/apps/tests/src/routes/is-server-with-anon-default-export.tsx index 47922b2c8..a46876dfd 100644 --- a/apps/tests/src/routes/is-server-with-anon-default-export.tsx +++ b/apps/tests/src/routes/is-server-with-anon-default-export.tsx @@ -2,18 +2,16 @@ import { createEffect, createSignal } from "solid-js"; import { serverFnWithIsServer } from "~/functions/use-is-server-with-anon-default-export"; export default function App() { - const [output, setOutput] = createSignal<{ serverFnWithIsServer?: boolean }>({}); + const [output, setOutput] = createSignal<{ serverFnWithIsServer?: boolean }>({}); + createEffect(async () => { + const result = await serverFnWithIsServer(); + setOutput(prev => ({ ...prev, serverFnWithIsServer: result })); + }); - createEffect(async () => { - const result = await serverFnWithIsServer(); - setOutput(prev => ({ ...prev, serverFnWithIsServer: result })); - }); - - - return ( -
    - {JSON.stringify(output())} -
    - ); + return ( +
    + {JSON.stringify(output())} +
    + ); } diff --git a/apps/tests/src/routes/nested/(ignored)route0.tsx b/apps/tests/src/routes/nested/(ignored)route0.tsx index be352577d..c7092deef 100644 --- a/apps/tests/src/routes/nested/(ignored)route0.tsx +++ b/apps/tests/src/routes/nested/(ignored)route0.tsx @@ -1,4 +1,3 @@ - export default function nested() { return

    nested route 0

    ; } diff --git a/apps/tests/src/routes/nested/(level1)/(ignored)route1.tsx b/apps/tests/src/routes/nested/(level1)/(ignored)route1.tsx index 471db7aaa..ec6169cd4 100644 --- a/apps/tests/src/routes/nested/(level1)/(ignored)route1.tsx +++ b/apps/tests/src/routes/nested/(level1)/(ignored)route1.tsx @@ -1,4 +1,3 @@ - export default function nested() { return

    nested route 1

    ; } diff --git a/apps/tests/src/routes/nested/(level1)/(level2)/(ignored)route2.tsx b/apps/tests/src/routes/nested/(level1)/(level2)/(ignored)route2.tsx index fcc953235..b59f52e94 100644 --- a/apps/tests/src/routes/nested/(level1)/(level2)/(ignored)route2.tsx +++ b/apps/tests/src/routes/nested/(level1)/(level2)/(ignored)route2.tsx @@ -1,4 +1,3 @@ - export default function nested() { return

    nested route 2

    ; } diff --git a/apps/tests/src/routes/nested/(level1)/(level2)/route3.tsx b/apps/tests/src/routes/nested/(level1)/(level2)/route3.tsx index 3d0eea984..61e0b761f 100644 --- a/apps/tests/src/routes/nested/(level1)/(level2)/route3.tsx +++ b/apps/tests/src/routes/nested/(level1)/(level2)/route3.tsx @@ -1,4 +1,3 @@ - export default function nested() { return

    nested route 3

    ; } diff --git a/apps/tests/src/routes/node-builtin-nested.tsx b/apps/tests/src/routes/node-builtin-nested.tsx index e1f27c2df..988de7139 100644 --- a/apps/tests/src/routes/node-builtin-nested.tsx +++ b/apps/tests/src/routes/node-builtin-nested.tsx @@ -1,17 +1,15 @@ -import { join } from 'node:path'; +import { join } from "node:path"; import { createEffect, createSignal } from "solid-js"; function serverFnWithNodeBuiltin() { "use server"; - return join('can','externalize'); + return join("can", "externalize"); } export default function App() { const [output, setOutput] = createSignal<{ serverFnWithNodeBuiltin?: string }>({}); - - createEffect(async () => { const result = await serverFnWithNodeBuiltin(); setOutput(prev => ({ ...prev, serverFnWithNodeBuiltin: result })); diff --git a/apps/tests/src/routes/node-builtin-toplevel.tsx b/apps/tests/src/routes/node-builtin-toplevel.tsx index b9ca3db82..3f5fb170c 100644 --- a/apps/tests/src/routes/node-builtin-toplevel.tsx +++ b/apps/tests/src/routes/node-builtin-toplevel.tsx @@ -1,4 +1,3 @@ - import { createEffect, createSignal } from "solid-js"; import { serverFnWithNodeBuiltin } from "~/functions/use-node-builtin"; diff --git a/apps/tests/src/routes/referencing-multiple-export-named-functions-in-the-same-file.tsx b/apps/tests/src/routes/referencing-multiple-export-named-functions-in-the-same-file.tsx index 6d477bd43..f4f81315e 100644 --- a/apps/tests/src/routes/referencing-multiple-export-named-functions-in-the-same-file.tsx +++ b/apps/tests/src/routes/referencing-multiple-export-named-functions-in-the-same-file.tsx @@ -1,4 +1,4 @@ -import { TextRenderTestComponent as ExternalCuteFaceDisplay} from "../functions/text-render-test-component"; +import { TextRenderTestComponent as ExternalCuteFaceDisplay } from "../functions/text-render-test-component"; export function TextRenderTestComponent() { return <>(´。• ᵕ •。`) ♡; @@ -15,9 +15,11 @@ export const testObjectExport = { }; export default function () { - return <> - - - - ; + return ( + <> + + + + + ); } diff --git a/apps/tests/src/routes/text-plain-response.tsx b/apps/tests/src/routes/text-plain-response.tsx index 5fc11d61d..ab8fbfd8a 100644 --- a/apps/tests/src/routes/text-plain-response.tsx +++ b/apps/tests/src/routes/text-plain-response.tsx @@ -12,4 +12,4 @@ export default function App() {
    ); -}; +} diff --git a/apps/tests/src/routes/treeshaking/treeshake.server.test.ts b/apps/tests/src/routes/treeshaking/treeshake.server.test.ts index 4926f0c80..c1fe56750 100644 --- a/apps/tests/src/routes/treeshaking/treeshake.server.test.ts +++ b/apps/tests/src/routes/treeshaking/treeshake.server.test.ts @@ -8,7 +8,7 @@ describe("Make sure treeshaking works", () => { const buildDir = path.resolve(process.cwd(), ".output/public/_build/assets"); const files = await readdir(buildDir); const targetFile = files.find( - file => file.startsWith("(no-side-effects)-") && file.endsWith(".js") + file => file.startsWith("(no-side-effects)-") && file.endsWith(".js"), ); if (!targetFile) { throw new Error("Treeshaking test: No target file not found"); diff --git a/apps/tests/vite.config.ts b/apps/tests/vite.config.ts index fc5997e4f..4149be667 100644 --- a/apps/tests/vite.config.ts +++ b/apps/tests/vite.config.ts @@ -3,8 +3,8 @@ import { solidStart } from "../../packages/start/src/config"; import { nitroV2Plugin } from "../../packages/start-nitro-v2-vite-plugin/src"; export default defineConfig({ - server: { - port: 3000, - }, - plugins: [solidStart(), nitroV2Plugin()], + server: { + port: 3000, + }, + plugins: [solidStart(), nitroV2Plugin()], }); diff --git a/apps/tests/vitest.config.ts b/apps/tests/vitest.config.ts index c433b3c70..9255aca52 100644 --- a/apps/tests/vitest.config.ts +++ b/apps/tests/vitest.config.ts @@ -15,8 +15,8 @@ export default defineConfig({ test: { include: ["**/*.server.test.ts"], // Matches the tree-shaking test name: { label: "Node Logic", color: "green" }, - environment: "node" - } + environment: "node", + }, }, { // 2. BROWSER Project (For Solid components and DOM interaction) @@ -30,10 +30,10 @@ export default defineConfig({ provider: playwright(), enabled: true, headless: true, - instances: [{ browser: "chromium" }] - } - } - } - ] - } + instances: [{ browser: "chromium" }], + }, + }, + }, + ], + }, }); diff --git a/package.json b/package.json index a4973f35f..3953f708d 100644 --- a/package.json +++ b/package.json @@ -25,11 +25,13 @@ "packages:clean": "pnpx rimraf ./packages/*/node_modules/ ./packages/*/dist/", "clean:test": "pnpx rimraf .tmp", "release": "pnpm build && changeset publish", + "format": "pnpm oxfmt", "rewrite-exports": "pnpm --filter='./packages/*' -c exec \"echo \\$(cat package.json | jq '.exports = .publishConfig.exports') > package.json\"" }, "devDependencies": { "@changesets/cli": "^2.29.8", "citty": "^0.1.5", + "oxfmt": "^0.14.0", "tinyglobby": "^0.2.2", "tippy.js": "^6.3.7", "typescript": "^5.7.0" diff --git a/packages/start-nitro-v2-vite-plugin/src/index.ts b/packages/start-nitro-v2-vite-plugin/src/index.ts index 131182a69..03d1934bb 100644 --- a/packages/start-nitro-v2-vite-plugin/src/index.ts +++ b/packages/start-nitro-v2-vite-plugin/src/index.ts @@ -5,7 +5,7 @@ import { type Nitro, type NitroConfig, prepare, - prerender + prerender, } from "nitropack"; import { promises as fsp } from "node:fs"; import path, { dirname, resolve } from "node:path"; @@ -33,7 +33,7 @@ export function nitroV2Plugin(nitroConfig?: UserNitroConfig): PluginOption { if (file.isEntry) { if (entryFile !== undefined) { this.error( - `Multiple entry points found for service "${this.environment.name}". Only one entry point is allowed.` + `Multiple entry points found for service "${this.environment.name}". Only one entry point is allowed.`, ); } entryFile = file.fileName; @@ -45,7 +45,7 @@ export function nitroV2Plugin(nitroConfig?: UserNitroConfig): PluginOption { } ssrEntryFile = entryFile!; ssrBundle = bundle; - } + }, }, config() { return { @@ -54,12 +54,12 @@ export function nitroV2Plugin(nitroConfig?: UserNitroConfig): PluginOption { consumer: "server", build: { commonjsOptions: { - include: [] + include: [], }, ssr: true, - sourcemap: true - } - } + sourcemap: true, + }, + }, }, builder: { sharedPlugins: true, @@ -80,7 +80,7 @@ export function nitroV2Plugin(nitroConfig?: UserNitroConfig): PluginOption { preset: "node-server", typescript: { generateTsConfig: false, - generateRuntimeConfigTypes: false + generateRuntimeConfigTypes: false, }, ...nitroConfig, dev: false, @@ -88,33 +88,33 @@ export function nitroV2Plugin(nitroConfig?: UserNitroConfig): PluginOption { { dir: client.config.build.outDir, maxAge: 31536000, // 1 year - baseURL: "/" - } + baseURL: "/", + }, ], renderer: virtualEntry, rollupConfig: { ...nitroConfig?.rollupConfig, - plugins: [virtualBundlePlugin(ssrBundle) as any] + plugins: [virtualBundlePlugin(ssrBundle) as any], }, experimental: { ...nitroConfig?.experimental, - asyncContext: true + asyncContext: true, }, virtual: { ...nitroConfig?.virtual, [virtualEntry]: `import { fromWebHandler } from 'h3' import handler from '${ssrEntryFile}' - export default fromWebHandler(handler.fetch)` - } + export default fromWebHandler(handler.fetch)`, + }, }; const nitro = await createNitro(resolvedNitroConfig); await buildNitroEnvironment(nitro, () => build(nitro)); - } - } + }, + }, }; - } + }, }, nitroConfig?.preset === "netlify" && { name: "solid-start-nitro-netlify-fix", @@ -123,11 +123,11 @@ export function nitroV2Plugin(nitroConfig?: UserNitroConfig): PluginOption { return { environments: { client: { build: { outDir: ".solid-start/client" } }, - ssr: { build: { outDir: ".solid-start/server" } } - } + ssr: { build: { outDir: ".solid-start/server" } }, + }, }; - } - } + }, + }, ]; } @@ -161,7 +161,7 @@ function virtualBundlePlugin(ssrBundle: Rollup.OutputBundle): PluginOption { if (content.type === "chunk") { const virtualModule: VirtualModule = { code: content.code, - map: null + map: null, }; const maybeMap = ssrBundle[`${fileName}.map`]; if (maybeMap && maybeMap.type === "asset") { @@ -193,6 +193,6 @@ function virtualBundlePlugin(ssrBundle: Rollup.OutputBundle): PluginOption { return null; } return m; - } + }, }; } diff --git a/packages/start/scripts/build.js b/packages/start/scripts/build.js index 82064ab7e..79e50409b 100644 --- a/packages/start/scripts/build.js +++ b/packages/start/scripts/build.js @@ -9,7 +9,7 @@ await Promise.all( fs.cp( path.join(import.meta.dirname, "../src", file), path.join(import.meta.dirname, "../dist", file), - { recursive: true } - ) - ) + { recursive: true }, + ), + ), ); diff --git a/packages/start/scripts/validate-imports.js b/packages/start/scripts/validate-imports.js index ef10b8cf4..f10845aa2 100755 --- a/packages/start/scripts/validate-imports.js +++ b/packages/start/scripts/validate-imports.js @@ -115,7 +115,7 @@ function extractImportExportStatements(content, filePath) { // Export from: export { ... } from '...' /export\s+(?:\{[^}]*\}|\*)\s+from\s+['"`]([^'"`]+)['"`]/g, // Export default from: export { default } from '...' - /export\s+\{\s*default\s*\}\s+from\s+['"`]([^'"`]+)['"`]/g + /export\s+\{\s*default\s*\}\s+from\s+['"`]([^'"`]+)['"`]/g, // Note: Dynamic imports are excluded as they're handled by bundlers ]; @@ -147,7 +147,7 @@ function extractImportExportStatements(content, filePath) { path: importPath, line: lineNumber + 1, fullLine: line.trim(), - filePath + filePath, }); } } @@ -175,7 +175,7 @@ function validateFile(filePath) { line: statement.line, importPath: statement.path, fullLine: statement.fullLine, - type: "invalid-extension" + type: "invalid-extension", }); } else if (!hasValidExtension(statement.path)) { errors.push({ @@ -183,7 +183,7 @@ function validateFile(filePath) { line: statement.line, importPath: statement.path, fullLine: statement.fullLine, - type: "missing-extension" + type: "missing-extension", }); } } @@ -243,7 +243,7 @@ function validateImports() { if (missingExtensionErrors.length > 0) { console.log( - `❌ Found ${missingExtensionErrors.length} relative import(s) without extensions:\n` + `❌ Found ${missingExtensionErrors.length} relative import(s) without extensions:\n`, ); const missingByFile = new Map(); @@ -262,7 +262,7 @@ function validateImports() { console.log(` Line ${error.line}: ${error.importPath}`); console.log(` ${error.fullLine}`); console.log( - ` ${"".padStart(error.fullLine.indexOf(error.importPath), " ")}${"".padStart(error.importPath.length, "^")}` + ` ${"".padStart(error.fullLine.indexOf(error.importPath), " ")}${"".padStart(error.importPath.length, "^")}`, ); }); console.log(""); @@ -271,7 +271,7 @@ function validateImports() { if (invalidExtensionErrors.length > 0) { console.log( - `❌ Found ${invalidExtensionErrors.length} relative import(s) with invalid extensions:\n` + `❌ Found ${invalidExtensionErrors.length} relative import(s) with invalid extensions:\n`, ); const invalidByFile = new Map(); @@ -290,7 +290,7 @@ function validateImports() { console.log(` Line ${error.line}: ${error.importPath}`); console.log(` ${error.fullLine}`); console.log( - ` ${"".padStart(error.fullLine.indexOf(error.importPath), " ")}${"".padStart(error.importPath.length, "^")}` + ` ${"".padStart(error.fullLine.indexOf(error.importPath), " ")}${"".padStart(error.importPath.length, "^")}`, ); }); console.log(""); diff --git a/packages/start/src/client/mount.ts b/packages/start/src/client/mount.ts index ac22d2196..744e759a9 100644 --- a/packages/start/src/client/mount.ts +++ b/packages/start/src/client/mount.ts @@ -5,7 +5,7 @@ import { getHydrationKey, getOwner, hydrate, - type MountableElement + type MountableElement, } from "solid-js/web"; /** @@ -48,14 +48,14 @@ export function mount(fn: () => JSX.Element, el: MountableElement) { (a as any).__$owner = getOwner(); }); return; - } + }, }); map.set(el, props); hydrate(() => createComponent(Component, props[0]), el, { renderId: hk.slice(0, hk.length - 1) + `${1 + Number(el.dataset.offset)}-`, - owner: lookupOwner(el) + owner: lookupOwner(el), }); delete el.dataset.hk; @@ -84,8 +84,8 @@ export function mount(fn: () => JSX.Element, el: MountableElement) { /* @vite-ignore */ import.meta.env.MANIFEST["client"]!.chunks[ asset.split("#")[0] as string ]!.output.path - ) - ) + ), + ), ) .then(() => { islands.forEach((el: HTMLElement) => { diff --git a/packages/start/src/config/constants.ts b/packages/start/src/config/constants.ts index 2dbf16159..cb2190aa0 100644 --- a/packages/start/src/config/constants.ts +++ b/packages/start/src/config/constants.ts @@ -10,10 +10,10 @@ export const VIRTUAL_MODULES = { serverFnManifest: "solidstart:server-fn-manifest", clientEntry: "solid-start:client-entry", serverEntry: "solid-start:server-entry", - app: "solid-start:app" + app: "solid-start:app", } as const; export const VITE_ENVIRONMENTS = { client: "client", - server: "ssr" -} + server: "ssr", +}; diff --git a/packages/start/src/config/dev-server.ts b/packages/start/src/config/dev-server.ts index 2913d0dff..6d783312e 100644 --- a/packages/start/src/config/dev-server.ts +++ b/packages/start/src/config/dev-server.ts @@ -1,84 +1,80 @@ import { NodeRequest, sendNodeResponse } from "srvx/node"; import { - type Connect, - isRunnableDevEnvironment, - type PluginOption, - type ViteDevServer, + type Connect, + isRunnableDevEnvironment, + type PluginOption, + type ViteDevServer, } from "vite"; import { VITE_ENVIRONMENTS } from "./constants.ts"; export function devServer(): Array { - return [ - { - name: "solid-start-dev-server", - configureServer(viteDevServer) { - (globalThis as any).VITE_DEV_SERVER = viteDevServer; - return async () => { - if (viteDevServer.config.server.middlewareMode) return + return [ + { + name: "solid-start-dev-server", + configureServer(viteDevServer) { + (globalThis as any).VITE_DEV_SERVER = viteDevServer; + return async () => { + if (viteDevServer.config.server.middlewareMode) return; - const serverEnv = - viteDevServer.environments[VITE_ENVIRONMENTS.server]; + const serverEnv = viteDevServer.environments[VITE_ENVIRONMENTS.server]; - if (!serverEnv) throw new Error("Server environment not found"); - if ( - // do not check via `isFetchableDevEnvironment` since nitro does implement the `FetchableDevEnvironment` interface but not via inheritance (which this helper checks) - "dispatchFetch" in serverEnv - ) - return; - // another plugin is controlling the dev server - if (!isRunnableDevEnvironment(serverEnv)) { + if (!serverEnv) throw new Error("Server environment not found"); + if ( + // do not check via `isFetchableDevEnvironment` since nitro does implement the `FetchableDevEnvironment` interface but not via inheritance (which this helper checks) + "dispatchFetch" in serverEnv + ) return; - } + // another plugin is controlling the dev server + if (!isRunnableDevEnvironment(serverEnv)) { + return; + } globalThis.USING_SOLID_START_DEV_SERVER = true; - removeHtmlMiddlewares(viteDevServer); + removeHtmlMiddlewares(viteDevServer); - viteDevServer.middlewares.use(async (req, res) => { - if (req.originalUrl) { - req.url = req.originalUrl; - } - const webReq = new NodeRequest({ req, res }); + viteDevServer.middlewares.use(async (req, res) => { + if (req.originalUrl) { + req.url = req.originalUrl; + } + const webReq = new NodeRequest({ req, res }); - try { - const serverEntry: { - default: { fetch: (req: Request) => Promise }; - } = await serverEnv.runner.import("./src/entry-server.tsx"); + try { + const serverEntry: { + default: { fetch: (req: Request) => Promise }; + } = await serverEnv.runner.import("./src/entry-server.tsx"); - const webRes = await serverEntry.default.fetch(webReq); + const webRes = await serverEntry.default.fetch(webReq); - return sendNodeResponse(res, webRes); - } catch (e: unknown) { - console.error(e); - viteDevServer.ssrFixStacktrace(e as Error); + return sendNodeResponse(res, webRes); + } catch (e: unknown) { + console.error(e); + viteDevServer.ssrFixStacktrace(e as Error); - if ( - webReq.headers.get("content-type")?.includes("application/json") - ) { - return sendNodeResponse( - res, - Response.json( - { - status: 500, - error: "Internal Server Error", - message: - "An unexpected error occurred. Please try again later.", - timestamp: new Date().toISOString(), - }, - { - status: 500, - headers: { - "Content-Type": "application/json", - }, - }, - ), - ); - } + if (webReq.headers.get("content-type")?.includes("application/json")) { + return sendNodeResponse( + res, + Response.json( + { + status: 500, + error: "Internal Server Error", + message: "An unexpected error occurred. Please try again later.", + timestamp: new Date().toISOString(), + }, + { + status: 500, + headers: { + "Content-Type": "application/json", + }, + }, + ), + ); + } - return sendNodeResponse( - res, - new Response( - ` + return sendNodeResponse( + res, + new Response( + ` @@ -87,26 +83,26 @@ export function devServer(): Array { `, - { - status: 500, - headers: { "Content-Type": "text/html" }, - }, - ), - ); - } - }); - }; - }, - }, - ]; + { + status: 500, + headers: { "Content-Type": "text/html" }, + }, + ), + ); + } + }); + }; + }, + }, + ]; } /** @@ -115,21 +111,21 @@ export function devServer(): Array { * @param server */ function removeHtmlMiddlewares(server: ViteDevServer) { - const html_middlewares = [ - "viteIndexHtmlMiddleware", - "vite404Middleware", - "viteSpaFallbackMiddleware", - ]; - for (let i = server.middlewares.stack.length - 1; i > 0; i--) { - if ( - html_middlewares.includes( - // @ts-expect-error - server.middlewares.stack[i].handle.name, - ) - ) { - server.middlewares.stack.splice(i, 1); - } - } + const html_middlewares = [ + "viteIndexHtmlMiddleware", + "vite404Middleware", + "viteSpaFallbackMiddleware", + ]; + for (let i = server.middlewares.stack.length - 1; i > 0; i--) { + if ( + html_middlewares.includes( + // @ts-expect-error + server.middlewares.stack[i].handle.name, + ) + ) { + server.middlewares.stack.splice(i, 1); + } + } } /** @@ -139,11 +135,11 @@ function removeHtmlMiddlewares(server: ViteDevServer) { * @returns */ function prepareError(req: Connect.IncomingMessage, error: unknown) { - const e = error as Error; - return { - message: `An error occured while server rendering ${req.url}:\n\n\t${ - typeof e === "string" ? e : e.message - } `, - stack: typeof e === "string" ? "" : e.stack, - }; + const e = error as Error; + return { + message: `An error occured while server rendering ${req.url}:\n\n\t${ + typeof e === "string" ? e : e.message + } `, + stack: typeof e === "string" ? "" : e.stack, + }; } diff --git a/packages/start/src/config/fs-router.ts b/packages/start/src/config/fs-router.ts index 668d61a95..fdc90bd0c 100644 --- a/packages/start/src/config/fs-router.ts +++ b/packages/start/src/config/fs-router.ts @@ -1,177 +1,162 @@ import type { ExportSpecifier } from "es-module-lexer"; import { - analyzeModule, - BaseFileSystemRouter, - cleanPath, - type FileSystemRouterConfig, + analyzeModule, + BaseFileSystemRouter, + cleanPath, + type FileSystemRouterConfig, } from "./fs-routes/router.ts"; export class SolidStartClientFileRouter extends BaseFileSystemRouter { - toPath(src: string) { - const routePath = cleanPath(src, this.config) - // remove the initial slash - .slice(1) - .replace(/index$/, "") - .replace(/\[([^/]+)\]/g, (_, m) => { - if (m.length > 3 && m.startsWith("...")) { - return `*${m.slice(3)}`; - } - if (m.length > 2 && m.startsWith("[") && m.endsWith("]")) { - return `:${m.slice(1, -1)}?`; - } - return `:${m}`; - }); + toPath(src: string) { + const routePath = cleanPath(src, this.config) + // remove the initial slash + .slice(1) + .replace(/index$/, "") + .replace(/\[([^/]+)\]/g, (_, m) => { + if (m.length > 3 && m.startsWith("...")) { + return `*${m.slice(3)}`; + } + if (m.length > 2 && m.startsWith("[") && m.endsWith("]")) { + return `:${m.slice(1, -1)}?`; + } + return `:${m}`; + }); - return routePath?.length > 0 ? `/${routePath}` : "/"; - } + return routePath?.length > 0 ? `/${routePath}` : "/"; + } - toRoute(src: string) { - const path = this.toPath(src); + toRoute(src: string) { + const path = this.toPath(src); - if (src.endsWith(".md") || src.endsWith(".mdx")) { - return { - page: true, - $component: { - src: src, - pick: ["$css"], - }, - $$route: undefined, - path, - // filePath: src - }; - } + if (src.endsWith(".md") || src.endsWith(".mdx")) { + return { + page: true, + $component: { + src: src, + pick: ["$css"], + }, + $$route: undefined, + path, + // filePath: src + }; + } - const [_, exports] = analyzeModule(src); - const hasDefault = !!exports.find((e) => e.n === "default"); - const hasRouteConfig = !!exports.find((e) => e.n === "route"); - if (hasDefault) { - return { - page: true, - $component: { - src: src, - pick: [ - ...exports - .filter((e) => e.n === e.ln && e.n !== "route") - .map((e) => e.n), - "default", - "$css", - ], - }, - $$route: hasRouteConfig - ? { - src: src, - pick: ["route"], - } - : undefined, - path, - // filePath: src - }; - } - } + const [_, exports] = analyzeModule(src); + const hasDefault = !!exports.find(e => e.n === "default"); + const hasRouteConfig = !!exports.find(e => e.n === "route"); + if (hasDefault) { + return { + page: true, + $component: { + src: src, + pick: [ + ...exports.filter(e => e.n === e.ln && e.n !== "route").map(e => e.n), + "default", + "$css", + ], + }, + $$route: hasRouteConfig + ? { + src: src, + pick: ["route"], + } + : undefined, + path, + // filePath: src + }; + } + } } -const HTTP_METHODS = [ - "HEAD", - "GET", - "POST", - "PUT", - "DELETE", - "PATCH", - "OPTIONS", -]; +const HTTP_METHODS = ["HEAD", "GET", "POST", "PUT", "DELETE", "PATCH", "OPTIONS"]; function createHTTPHandlers(src: string, exports: readonly ExportSpecifier[]) { - const handlers: Record = {}; - for (const exp of exports) { - if (HTTP_METHODS.includes(exp.n)) { - handlers[`$${exp.n}`] = { - src: src, - pick: [exp.n], - }; - if (exp.n === "GET" && !exports.find((exp) => exp.n === "HEAD")) { - handlers.$HEAD = { - src: src, - pick: [exp.n], - }; - } - } - } + const handlers: Record = {}; + for (const exp of exports) { + if (HTTP_METHODS.includes(exp.n)) { + handlers[`$${exp.n}`] = { + src: src, + pick: [exp.n], + }; + if (exp.n === "GET" && !exports.find(exp => exp.n === "HEAD")) { + handlers.$HEAD = { + src: src, + pick: [exp.n], + }; + } + } + } - return handlers; + return handlers; } export class SolidStartServerFileRouter extends BaseFileSystemRouter { - declare config: FileSystemRouterConfig & { dataOnly?: boolean }; + declare config: FileSystemRouterConfig & { dataOnly?: boolean }; - constructor(config: FileSystemRouterConfig & { dataOnly?: boolean }) { - super(config); - } + constructor(config: FileSystemRouterConfig & { dataOnly?: boolean }) { + super(config); + } - toPath(src: string) { - const routePath = cleanPath(src, this.config) - // remove the initial slash - .slice(1) - .replace(/index$/, "") - .replace(/\[([^/]+)\]/g, (_, m) => { - if (m.length > 3 && m.startsWith("...")) { - return `*${m.slice(3)}`; - } - if (m.length > 2 && m.startsWith("[") && m.endsWith("]")) { - return `:${m.slice(1, -1)}?`; - } - return `:${m}`; - }); + toPath(src: string) { + const routePath = cleanPath(src, this.config) + // remove the initial slash + .slice(1) + .replace(/index$/, "") + .replace(/\[([^/]+)\]/g, (_, m) => { + if (m.length > 3 && m.startsWith("...")) { + return `*${m.slice(3)}`; + } + if (m.length > 2 && m.startsWith("[") && m.endsWith("]")) { + return `:${m.slice(1, -1)}?`; + } + return `:${m}`; + }); - return routePath?.length > 0 ? `/${routePath}` : "/"; - } + return routePath?.length > 0 ? `/${routePath}` : "/"; + } - toRoute(src: string) { - const path = this.toPath(src); - if (src.endsWith(".md") || src.endsWith(".mdx")) { - return { - page: true, - $component: { - src: src, - pick: ["$css"], - }, - $$route: undefined, - path, - }; - } + toRoute(src: string) { + const path = this.toPath(src); + if (src.endsWith(".md") || src.endsWith(".mdx")) { + return { + page: true, + $component: { + src: src, + pick: ["$css"], + }, + $$route: undefined, + path, + }; + } - const [_, exports] = analyzeModule(src); - const hasRouteConfig = exports.find((e) => e.n === "route"); - const hasDefault = !!exports.find((e) => e.n === "default"); - const hasAPIRoutes = !!exports.find((exp) => HTTP_METHODS.includes(exp.n)); - if (hasDefault || hasAPIRoutes) { - return { - page: hasDefault, - $component: - !this.config.dataOnly && hasDefault - ? { - src: src, - pick: [ - ...exports - .filter( - (e) => - e.n === e.ln && - e.n !== "route" && - !HTTP_METHODS.includes(e.n), - ) - .map((e) => e.n), - "default", - "$css", - ], - } - : undefined, - $$route: hasRouteConfig - ? { - src: src, - pick: ["route"], - } - : undefined, - ...createHTTPHandlers(src, exports), - path, - }; - } - } + const [_, exports] = analyzeModule(src); + const hasRouteConfig = exports.find(e => e.n === "route"); + const hasDefault = !!exports.find(e => e.n === "default"); + const hasAPIRoutes = !!exports.find(exp => HTTP_METHODS.includes(exp.n)); + if (hasDefault || hasAPIRoutes) { + return { + page: hasDefault, + $component: + !this.config.dataOnly && hasDefault + ? { + src: src, + pick: [ + ...exports + .filter(e => e.n === e.ln && e.n !== "route" && !HTTP_METHODS.includes(e.n)) + .map(e => e.n), + "default", + "$css", + ], + } + : undefined, + $$route: hasRouteConfig + ? { + src: src, + pick: ["route"], + } + : undefined, + ...createHTTPHandlers(src, exports), + path, + }; + } + } } diff --git a/packages/start/src/config/fs-routes/fs-watcher.ts b/packages/start/src/config/fs-routes/fs-watcher.ts index 5e7543293..0437ea947 100644 --- a/packages/start/src/config/fs-routes/fs-watcher.ts +++ b/packages/start/src/config/fs-routes/fs-watcher.ts @@ -1,81 +1,77 @@ import type { - EnvironmentModuleNode, - FSWatcher, - ModuleGraph, - ModuleNode, - PluginOption, - ViteDevServer, + EnvironmentModuleNode, + FSWatcher, + ModuleGraph, + ModuleNode, + PluginOption, + ViteDevServer, } from "vite"; import { moduleId } from "./index.ts"; import type { BaseFileSystemRouter } from "./router.ts"; interface CompiledRouter { - removeRoute(path: string): void; - addRoute(path: string): void; - updateRoute(path: string): void; - addEventListener(event: "reload", handler: () => void): void; - removeEventListener(event: "reload", handler: () => void): void; + removeRoute(path: string): void; + addRoute(path: string): void; + updateRoute(path: string): void; + addEventListener(event: "reload", handler: () => void): void; + removeEventListener(event: "reload", handler: () => void): void; } function setupWatcher(watcher: FSWatcher, routes: CompiledRouter): void { - watcher.on("unlink", (path) => routes.removeRoute(path)); - watcher.on("add", (path) => routes.addRoute(path)); - watcher.on("change", (path) => routes.updateRoute(path)); + watcher.on("unlink", path => routes.removeRoute(path)); + watcher.on("add", path => routes.addRoute(path)); + watcher.on("change", path => routes.updateRoute(path)); } function createRoutesReloader( - server: ViteDevServer, - routes: CompiledRouter, - environment: "client" | "ssr", + server: ViteDevServer, + routes: CompiledRouter, + environment: "client" | "ssr", ): () => void { - routes.addEventListener("reload", handleRoutesReload); - return () => routes.removeEventListener("reload", handleRoutesReload); + routes.addEventListener("reload", handleRoutesReload); + return () => routes.removeEventListener("reload", handleRoutesReload); - function handleRoutesReload(): void { - if (environment === "ssr") { - // Handle server environment HMR reload - const serverEnv = server.environments.server; - if (serverEnv && serverEnv.moduleGraph) { - const mod: EnvironmentModuleNode | undefined = - serverEnv.moduleGraph.getModuleById(moduleId); - if (mod) { - const seen = new Set(); - serverEnv.moduleGraph.invalidateModule(mod, seen); - } - } - } else { - // Handle client environment HMR reload - const { moduleGraph }: { moduleGraph: ModuleGraph } = server; - const mod: ModuleNode | undefined = moduleGraph.getModuleById(moduleId); - if (mod) { - const seen = new Set(); - moduleGraph.invalidateModule(mod, seen); - server.reloadModule(mod); - } - } + function handleRoutesReload(): void { + if (environment === "ssr") { + // Handle server environment HMR reload + const serverEnv = server.environments.server; + if (serverEnv && serverEnv.moduleGraph) { + const mod: EnvironmentModuleNode | undefined = + serverEnv.moduleGraph.getModuleById(moduleId); + if (mod) { + const seen = new Set(); + serverEnv.moduleGraph.invalidateModule(mod, seen); + } + } + } else { + // Handle client environment HMR reload + const { moduleGraph }: { moduleGraph: ModuleGraph } = server; + const mod: ModuleNode | undefined = moduleGraph.getModuleById(moduleId); + if (mod) { + const seen = new Set(); + moduleGraph.invalidateModule(mod, seen); + server.reloadModule(mod); + } + } - if (!server.hot) { - server.ws.send({ type: "full-reload" }); - } - } + if (!server.hot) { + server.ws.send({ type: "full-reload" }); + } + } } export const fileSystemWatcher = ( - routers: Record<"client" | "ssr", BaseFileSystemRouter>, + routers: Record<"client" | "ssr", BaseFileSystemRouter>, ): PluginOption => { - const plugin: PluginOption = { - name: "fs-watcher", - async configureServer(server: ViteDevServer) { - Object.keys(routers).forEach((environment) => { - const router = (globalThis as any).ROUTERS[environment]; - setupWatcher(server.watcher, router); - createRoutesReloader( - server, - router, - environment as keyof typeof routers, - ); - }); - }, - }; - return plugin; + const plugin: PluginOption = { + name: "fs-watcher", + async configureServer(server: ViteDevServer) { + Object.keys(routers).forEach(environment => { + const router = (globalThis as any).ROUTERS[environment]; + setupWatcher(server.watcher, router); + createRoutesReloader(server, router, environment as keyof typeof routers); + }); + }, + }; + return plugin; }; diff --git a/packages/start/src/config/fs-routes/index.ts b/packages/start/src/config/fs-routes/index.ts index 81bad92fe..272f7abaa 100644 --- a/packages/start/src/config/fs-routes/index.ts +++ b/packages/start/src/config/fs-routes/index.ts @@ -8,131 +8,129 @@ import { treeShake } from "./tree-shake.ts"; export const moduleId = "solid-start:routes"; export interface FsRoutesArgs { - routers: Record<"client" | "ssr", BaseFileSystemRouter>; + routers: Record<"client" | "ssr", BaseFileSystemRouter>; } export function fsRoutes({ routers }: FsRoutesArgs): Array { - (globalThis as any).ROUTERS = routers; - - return [ - { - name: "solid-start-fs-routes", - enforce: "pre", - resolveId(id) { - if (id === moduleId) return id; - }, - async load(id) { - const root = this.environment.config.root; - const isBuild = this.environment.mode === "build"; - - if (id !== moduleId) return; - const js = jsCode(); - - const router = (globalThis as any).ROUTERS[this.environment.name]; - - const routes = await router.getRoutes(); - - let routesCode = JSON.stringify(routes ?? [], (k, v) => { - if (v === undefined) return undefined; - - if (k.startsWith("$$")) { - const buildId = `${v.src}?${v.pick.map((p: any) => `pick=${p}`).join("&")}`; - - /** - * @type {{ [key: string]: string }} - */ - const refs: Record = {}; - for (var pick of v.pick) { - refs[pick] = js.addNamedImport(pick, buildId); - } - return { - require: `_$() => ({ ${Object.entries(refs) - .map(([pick, namedImport]) => `'${pick}': ${namedImport}`) - .join(", ")} })$_`, - // src: isBuild ? relative(root, buildId) : buildId - }; - } else if (k.startsWith("$")) { - const buildId = `${v.src}?${v.pick.map((p: any) => `pick=${p}`).join("&")}`; - return { - src: relative(root, buildId), - build: isBuild - ? `_$() => import(/* @vite-ignore */ '${buildId}')$_` - : undefined, - import: `_$() => import(/* @vite-ignore */ '${buildId}')$_` - }; - } - return v; - }); - - routesCode = routesCode.replaceAll('"_$(', "(").replaceAll(')$_"', ")"); - - const code = ` + (globalThis as any).ROUTERS = routers; + + return [ + { + name: "solid-start-fs-routes", + enforce: "pre", + resolveId(id) { + if (id === moduleId) return id; + }, + async load(id) { + const root = this.environment.config.root; + const isBuild = this.environment.mode === "build"; + + if (id !== moduleId) return; + const js = jsCode(); + + const router = (globalThis as any).ROUTERS[this.environment.name]; + + const routes = await router.getRoutes(); + + let routesCode = JSON.stringify(routes ?? [], (k, v) => { + if (v === undefined) return undefined; + + if (k.startsWith("$$")) { + const buildId = `${v.src}?${v.pick.map((p: any) => `pick=${p}`).join("&")}`; + + /** + * @type {{ [key: string]: string }} + */ + const refs: Record = {}; + for (var pick of v.pick) { + refs[pick] = js.addNamedImport(pick, buildId); + } + return { + require: `_$() => ({ ${Object.entries(refs) + .map(([pick, namedImport]) => `'${pick}': ${namedImport}`) + .join(", ")} })$_`, + // src: isBuild ? relative(root, buildId) : buildId + }; + } else if (k.startsWith("$")) { + const buildId = `${v.src}?${v.pick.map((p: any) => `pick=${p}`).join("&")}`; + return { + src: relative(root, buildId), + build: isBuild ? `_$() => import(/* @vite-ignore */ '${buildId}')$_` : undefined, + import: `_$() => import(/* @vite-ignore */ '${buildId}')$_`, + }; + } + return v; + }); + + routesCode = routesCode.replaceAll('"_$(', "(").replaceAll(')$_"', ")"); + + const code = ` ${js.getImportStatements()} export default ${routesCode}`; - return code; - }, - }, - treeShake(), - fileSystemWatcher(routers), - ]; + return code; + }, + }, + treeShake(), + fileSystemWatcher(routers), + ]; } function jsCode() { - const imports = new Map(); - let vars = 0; - - function addImport(p: any) { - let id = imports.get(p); - if (!id) { - id = {}; - imports.set(p, id); - } - - const d = "routeData" + vars++; - id["default"] = d; - return d; - } - - function addNamedImport(name: string | number, p: any) { - let id = imports.get(p); - if (!id) { - id = {}; - imports.set(p, id); - } - - const d = "routeData" + vars++; - id[name] = d; - return d; - } - - const getNamedExport = (p: any) => { - const id = imports.get(p); - - delete id["default"]; - - return Object.keys(id).length > 0 - ? `{ ${Object.keys(id) - .map((k) => `${k} as ${id[k]}`) - .join(", ")} }` - : ""; - }; - - const getImportStatements = () => { - return `${[...imports.keys()] - .map( - (i) => - `import ${ - imports.get(i).default - ? `${imports.get(i).default}${Object.keys(imports.get(i)).length > 1 ? ", " : ""}` - : "" - } ${getNamedExport(i)} from '${i}';`, - ) - .join("\n")}`; - }; - - return { - addImport, - addNamedImport, - getImportStatements, - }; + const imports = new Map(); + let vars = 0; + + function addImport(p: any) { + let id = imports.get(p); + if (!id) { + id = {}; + imports.set(p, id); + } + + const d = "routeData" + vars++; + id["default"] = d; + return d; + } + + function addNamedImport(name: string | number, p: any) { + let id = imports.get(p); + if (!id) { + id = {}; + imports.set(p, id); + } + + const d = "routeData" + vars++; + id[name] = d; + return d; + } + + const getNamedExport = (p: any) => { + const id = imports.get(p); + + delete id["default"]; + + return Object.keys(id).length > 0 + ? `{ ${Object.keys(id) + .map(k => `${k} as ${id[k]}`) + .join(", ")} }` + : ""; + }; + + const getImportStatements = () => { + return `${[...imports.keys()] + .map( + i => + `import ${ + imports.get(i).default + ? `${imports.get(i).default}${Object.keys(imports.get(i)).length > 1 ? ", " : ""}` + : "" + } ${getNamedExport(i)} from '${i}';`, + ) + .join("\n")}`; + }; + + return { + addImport, + addNamedImport, + getImportStatements, + }; } diff --git a/packages/start/src/config/fs-routes/router.ts b/packages/start/src/config/fs-routes/router.ts index f694acc0f..bfc66c96b 100644 --- a/packages/start/src/config/fs-routes/router.ts +++ b/packages/start/src/config/fs-routes/router.ts @@ -27,9 +27,9 @@ export function analyzeModule(src: string) { esbuild.transformSync(fs.readFileSync(src, "utf-8"), { jsx: "transform", format: "esm", - loader: "tsx" + loader: "tsx", }).code, - src + src, ); } @@ -86,9 +86,9 @@ export class BaseFileSystemRouter extends EventTarget { return { $component: { src: src, - pick: ["default", "$css"] + pick: ["default", "$css"], }, - path + path, // filePath: src }; } @@ -123,9 +123,9 @@ export class BaseFileSystemRouter extends EventTarget { new Event("reload", { // @ts-ignore detail: { - route - } - }) + route, + }, + }), ); } diff --git a/packages/start/src/config/fs-routes/tree-shake.ts b/packages/start/src/config/fs-routes/tree-shake.ts index b27ff1c86..5e69f6eb0 100644 --- a/packages/start/src/config/fs-routes/tree-shake.ts +++ b/packages/start/src/config/fs-routes/tree-shake.ts @@ -9,384 +9,363 @@ import { basename } from "pathe"; import type { Plugin, ResolvedConfig, ViteDevServer } from "vite"; type State = Omit & { - opts: { pick: string[] }; - refs: Set; - done: boolean; + opts: { pick: string[] }; + refs: Set; + done: boolean; }; function treeShakeTransform({ types: t }: typeof Babel): PluginObj { - function getIdentifier(path: any) { - const parentPath = path.parentPath; - if (parentPath.type === "VariableDeclarator") { - const pp = parentPath; - const name = pp.get("id"); - return name.node.type === "Identifier" ? name : null; - } - if (parentPath.type === "AssignmentExpression") { - const pp = parentPath; - const name = pp.get("left"); - return name.node.type === "Identifier" ? name : null; - } - if (path.node.type === "ArrowFunctionExpression") { - return null; - } - return path.node.id && path.node.id.type === "Identifier" - ? path.get("id") - : null; - } + function getIdentifier(path: any) { + const parentPath = path.parentPath; + if (parentPath.type === "VariableDeclarator") { + const pp = parentPath; + const name = pp.get("id"); + return name.node.type === "Identifier" ? name : null; + } + if (parentPath.type === "AssignmentExpression") { + const pp = parentPath; + const name = pp.get("left"); + return name.node.type === "Identifier" ? name : null; + } + if (path.node.type === "ArrowFunctionExpression") { + return null; + } + return path.node.id && path.node.id.type === "Identifier" ? path.get("id") : null; + } - function isIdentifierReferenced(ident: any) { - const b: Binding | undefined = ident.scope.getBinding(ident.node.name); - if (b?.referenced) { - if (b.path.type === "FunctionDeclaration") { - return !b.constantViolations - .concat(b.referencePaths) - .every((ref) => ref.findParent((p) => p === b.path)); - } - return true; - } - return false; - } - function markFunction(path: any, state: any) { - const ident = getIdentifier(path); - if (ident && ident.node && isIdentifierReferenced(ident)) { - state.refs.add(ident); - } - } - function markImport(path: any, state: any) { - const local = path.get("local"); - if (isIdentifierReferenced(local)) { - state.refs.add(local); - } - } + function isIdentifierReferenced(ident: any) { + const b: Binding | undefined = ident.scope.getBinding(ident.node.name); + if (b?.referenced) { + if (b.path.type === "FunctionDeclaration") { + return !b.constantViolations + .concat(b.referencePaths) + .every(ref => ref.findParent(p => p === b.path)); + } + return true; + } + return false; + } + function markFunction(path: any, state: any) { + const ident = getIdentifier(path); + if (ident && ident.node && isIdentifierReferenced(ident)) { + state.refs.add(ident); + } + } + function markImport(path: any, state: any) { + const local = path.get("local"); + if (isIdentifierReferenced(local)) { + state.refs.add(local); + } + } - return { - visitor: { - Program: { - enter(path, state) { - state.refs = new Set(); - state.done = false; - path.traverse( - { - VariableDeclarator(variablePath, variableState: any) { - if (variablePath.node.id.type === "Identifier") { - const local = variablePath.get("id"); - if (isIdentifierReferenced(local)) { - variableState.refs.add(local); - } - } else if (variablePath.node.id.type === "ObjectPattern") { - const pattern = variablePath.get("id"); - const properties = pattern.get( - "properties", - ) as Array; - properties.forEach((p) => { - const local = p.get( - p.node.type === "ObjectProperty" - ? "value" - : p.node.type === "RestElement" - ? "argument" - : (() => { - throw new Error("invariant"); - })(), - ); - if (isIdentifierReferenced(local)) { - variableState.refs.add(local); - } - }); - } else if (variablePath.node.id.type === "ArrayPattern") { - const pattern = variablePath.get("id"); - const elements = pattern.get("elements") as Array; - elements.forEach((e) => { - let local: NodePath; - if (e.node && e.node.type === "Identifier") { - local = e; - } else if (e.node && e.node.type === "RestElement") { - local = e.get("argument"); - } else { - return; - } - if (isIdentifierReferenced(local)) { - variableState.refs.add(local); - } - }); - } - }, - ExportDefaultDeclaration(exportNamedPath) { - // if opts.keep is true, we don't remove the routeData export - if (state.opts.pick && !state.opts.pick.includes("default")) { - exportNamedPath.remove(); - } - }, - ExportNamedDeclaration(exportNamedPath) { - // if opts.keep is false, we don't remove the routeData export - if (!state.opts.pick) { - return; - } - const specifiers = exportNamedPath.get("specifiers"); - if (specifiers.length) { - specifiers.forEach((s) => { - if ( - t.isIdentifier(s.node.exported) - ? s.node.exported.name - : state.opts.pick.includes(s.node.exported.value) - ) { - s.remove(); - } - }); - if (exportNamedPath.node.specifiers.length < 1) { - exportNamedPath.remove(); - } - return; - } - const decl = exportNamedPath.get("declaration"); - if (decl == null || decl.node == null) { - return; - } - switch (decl.node.type) { - case "FunctionDeclaration": { - const name = decl.node.id?.name; - if ( - name && - state.opts.pick && - !state.opts.pick.includes(name) - ) { - exportNamedPath.remove(); - } - break; - } - case "VariableDeclaration": { - const inner = decl.get("declarations") as Array< - NodePath - >; - inner.forEach((d) => { - if (d.node.id.type !== "Identifier") { - return; - } - const name = d.node.id.name; - if (state.opts.pick && !state.opts.pick.includes(name)) { - d.remove(); - } - }); - break; - } - default: { - break; - } - } - }, - FunctionDeclaration: markFunction, - FunctionExpression: markFunction, - ArrowFunctionExpression: markFunction, - ImportSpecifier: markImport, - ImportDefaultSpecifier: markImport, - ImportNamespaceSpecifier: markImport, - ImportDeclaration: (path, state) => { - if ( - path.node.source.value.endsWith(".css") && - state.opts.pick && - !state.opts.pick.includes("$css") - ) { - path.remove(); - } - }, - }, - state, - ); + return { + visitor: { + Program: { + enter(path, state) { + state.refs = new Set(); + state.done = false; + path.traverse( + { + VariableDeclarator(variablePath, variableState: any) { + if (variablePath.node.id.type === "Identifier") { + const local = variablePath.get("id"); + if (isIdentifierReferenced(local)) { + variableState.refs.add(local); + } + } else if (variablePath.node.id.type === "ObjectPattern") { + const pattern = variablePath.get("id"); + const properties = pattern.get("properties") as Array; + properties.forEach(p => { + const local = p.get( + p.node.type === "ObjectProperty" + ? "value" + : p.node.type === "RestElement" + ? "argument" + : (() => { + throw new Error("invariant"); + })(), + ); + if (isIdentifierReferenced(local)) { + variableState.refs.add(local); + } + }); + } else if (variablePath.node.id.type === "ArrayPattern") { + const pattern = variablePath.get("id"); + const elements = pattern.get("elements") as Array; + elements.forEach(e => { + let local: NodePath; + if (e.node && e.node.type === "Identifier") { + local = e; + } else if (e.node && e.node.type === "RestElement") { + local = e.get("argument"); + } else { + return; + } + if (isIdentifierReferenced(local)) { + variableState.refs.add(local); + } + }); + } + }, + ExportDefaultDeclaration(exportNamedPath) { + // if opts.keep is true, we don't remove the routeData export + if (state.opts.pick && !state.opts.pick.includes("default")) { + exportNamedPath.remove(); + } + }, + ExportNamedDeclaration(exportNamedPath) { + // if opts.keep is false, we don't remove the routeData export + if (!state.opts.pick) { + return; + } + const specifiers = exportNamedPath.get("specifiers"); + if (specifiers.length) { + specifiers.forEach(s => { + if ( + t.isIdentifier(s.node.exported) + ? s.node.exported.name + : state.opts.pick.includes(s.node.exported.value) + ) { + s.remove(); + } + }); + if (exportNamedPath.node.specifiers.length < 1) { + exportNamedPath.remove(); + } + return; + } + const decl = exportNamedPath.get("declaration"); + if (decl == null || decl.node == null) { + return; + } + switch (decl.node.type) { + case "FunctionDeclaration": { + const name = decl.node.id?.name; + if (name && state.opts.pick && !state.opts.pick.includes(name)) { + exportNamedPath.remove(); + } + break; + } + case "VariableDeclaration": { + const inner = decl.get("declarations") as Array>; + inner.forEach(d => { + if (d.node.id.type !== "Identifier") { + return; + } + const name = d.node.id.name; + if (state.opts.pick && !state.opts.pick.includes(name)) { + d.remove(); + } + }); + break; + } + default: { + break; + } + } + }, + FunctionDeclaration: markFunction, + FunctionExpression: markFunction, + ArrowFunctionExpression: markFunction, + ImportSpecifier: markImport, + ImportDefaultSpecifier: markImport, + ImportNamespaceSpecifier: markImport, + ImportDeclaration: (path, state) => { + if ( + path.node.source.value.endsWith(".css") && + state.opts.pick && + !state.opts.pick.includes("$css") + ) { + path.remove(); + } + }, + }, + state, + ); - const refs = state.refs; - let count = 0; - const sweepFunction = (sweepPath: any) => { - const ident = getIdentifier(sweepPath); - if ( - ident && - ident.node && - refs.has(ident) && - !isIdentifierReferenced(ident) - ) { - ++count; - if ( - t.isAssignmentExpression(sweepPath.parentPath) || - t.isVariableDeclarator(sweepPath.parentPath) - ) { - sweepPath.parentPath.remove(); - } else { - sweepPath.remove(); - } - } - }; - function sweepImport(sweepPath: any) { - const local = sweepPath.get("local"); - if (refs.has(local) && !isIdentifierReferenced(local)) { - ++count; - sweepPath.remove(); - if (sweepPath.parent.specifiers.length === 0) { - sweepPath.parentPath.remove(); - } - } - } - do { - path.scope.crawl(); - count = 0; - path.traverse({ - VariableDeclarator(variablePath) { - if (variablePath.node.id.type === "Identifier") { - const local = variablePath.get("id"); - if (refs.has(local) && !isIdentifierReferenced(local)) { - ++count; - variablePath.remove(); - } - } else if (variablePath.node.id.type === "ObjectPattern") { - const pattern = variablePath.get("id"); - const beforeCount = count; - const properties = pattern.get("properties"); - properties.forEach((p) => { - const local = p.get( - p.node.type === "ObjectProperty" - ? "value" - : p.node.type === "RestElement" - ? "argument" - : (() => { - throw new Error("invariant"); - })(), - ); - if (refs.has(local) && !isIdentifierReferenced(local)) { - ++count; - p.remove(); - } - }); - if ( - beforeCount !== count && - pattern.get("properties").length < 1 - ) { - variablePath.remove(); - } - } else if (variablePath.node.id.type === "ArrayPattern") { - const pattern = variablePath.get("id"); - const beforeCount = count; - const elements = pattern.get("elements"); - elements.forEach((e) => { - let local: NodePath | undefined; - if (e.node && e.node.type === "Identifier") { - local = e; - } else if (e.node && e.node.type === "RestElement") { - local = e.get("argument"); - } else { - return; - } - if (refs.has(local) && !isIdentifierReferenced(local)) { - ++count; - e.remove(); - } - }); - if ( - beforeCount !== count && - pattern.get("elements").length < 1 - ) { - variablePath.remove(); - } - } - }, - FunctionDeclaration: sweepFunction, - FunctionExpression: sweepFunction, - ArrowFunctionExpression: sweepFunction, - ImportSpecifier: sweepImport, - ImportDefaultSpecifier: sweepImport, - ImportNamespaceSpecifier: sweepImport, - }); - } while (count); - }, - }, - }, - }; + const refs = state.refs; + let count = 0; + const sweepFunction = (sweepPath: any) => { + const ident = getIdentifier(sweepPath); + if (ident && ident.node && refs.has(ident) && !isIdentifierReferenced(ident)) { + ++count; + if ( + t.isAssignmentExpression(sweepPath.parentPath) || + t.isVariableDeclarator(sweepPath.parentPath) + ) { + sweepPath.parentPath.remove(); + } else { + sweepPath.remove(); + } + } + }; + function sweepImport(sweepPath: any) { + const local = sweepPath.get("local"); + if (refs.has(local) && !isIdentifierReferenced(local)) { + ++count; + sweepPath.remove(); + if (sweepPath.parent.specifiers.length === 0) { + sweepPath.parentPath.remove(); + } + } + } + do { + path.scope.crawl(); + count = 0; + path.traverse({ + VariableDeclarator(variablePath) { + if (variablePath.node.id.type === "Identifier") { + const local = variablePath.get("id"); + if (refs.has(local) && !isIdentifierReferenced(local)) { + ++count; + variablePath.remove(); + } + } else if (variablePath.node.id.type === "ObjectPattern") { + const pattern = variablePath.get("id"); + const beforeCount = count; + const properties = pattern.get("properties"); + properties.forEach(p => { + const local = p.get( + p.node.type === "ObjectProperty" + ? "value" + : p.node.type === "RestElement" + ? "argument" + : (() => { + throw new Error("invariant"); + })(), + ); + if (refs.has(local) && !isIdentifierReferenced(local)) { + ++count; + p.remove(); + } + }); + if (beforeCount !== count && pattern.get("properties").length < 1) { + variablePath.remove(); + } + } else if (variablePath.node.id.type === "ArrayPattern") { + const pattern = variablePath.get("id"); + const beforeCount = count; + const elements = pattern.get("elements"); + elements.forEach(e => { + let local: NodePath | undefined; + if (e.node && e.node.type === "Identifier") { + local = e; + } else if (e.node && e.node.type === "RestElement") { + local = e.get("argument"); + } else { + return; + } + if (refs.has(local) && !isIdentifierReferenced(local)) { + ++count; + e.remove(); + } + }); + if (beforeCount !== count && pattern.get("elements").length < 1) { + variablePath.remove(); + } + } + }, + FunctionDeclaration: sweepFunction, + FunctionExpression: sweepFunction, + ArrowFunctionExpression: sweepFunction, + ImportSpecifier: sweepImport, + ImportDefaultSpecifier: sweepImport, + ImportNamespaceSpecifier: sweepImport, + }); + } while (count); + }, + }, + }, + }; } export function treeShake(): Plugin { - let config: ResolvedConfig; - const cache: Record = {}; - let server: ViteDevServer; + let config: ResolvedConfig; + const cache: Record = {}; + let server: ViteDevServer; - async function transform(id: string, code: string) { - const [path, queryString] = id.split("?"); - const query = new URLSearchParams(queryString); - if (query.has("pick")) { - const babel = await import("@babel/core"); - const transformed = await babel.transformAsync(code, { - plugins: [[treeShakeTransform, { pick: query.getAll("pick") }]], - parserOpts: { - plugins: ["jsx", "typescript"], - }, - filename: basename(id), - ast: false, - sourceMaps: true, - configFile: false, - babelrc: false, - sourceFileName: id, - }); + async function transform(id: string, code: string) { + const [path, queryString] = id.split("?"); + const query = new URLSearchParams(queryString); + if (query.has("pick")) { + const babel = await import("@babel/core"); + const transformed = await babel.transformAsync(code, { + plugins: [[treeShakeTransform, { pick: query.getAll("pick") }]], + parserOpts: { + plugins: ["jsx", "typescript"], + }, + filename: basename(id), + ast: false, + sourceMaps: true, + configFile: false, + babelrc: false, + sourceFileName: id, + }); - return transformed; - } - } - return { - name: "tree-shake", - enforce: "pre", - configResolved(resolvedConfig) { - config = resolvedConfig; - }, - configureServer(s) { - server = s; - }, - async handleHotUpdate(ctx) { - if (cache[ctx.file]) { - const mods = []; - const newCode = await ctx.read(); - for (const [id, code] of Object.entries(cache[ctx.file])) { - const transformed = await transform(id, newCode); - if (!transformed) continue; + return transformed; + } + } + return { + name: "tree-shake", + enforce: "pre", + configResolved(resolvedConfig) { + config = resolvedConfig; + }, + configureServer(s) { + server = s; + }, + async handleHotUpdate(ctx) { + if (cache[ctx.file]) { + const mods = []; + const newCode = await ctx.read(); + for (const [id, code] of Object.entries(cache[ctx.file])) { + const transformed = await transform(id, newCode); + if (!transformed) continue; - const { code: transformedCode } = transformed; + const { code: transformedCode } = transformed; - if (transformedCode !== code) { - const mod = server.moduleGraph.getModuleById(id); - if (mod) mods.push(mod); - } + if (transformedCode !== code) { + const mod = server.moduleGraph.getModuleById(id); + if (mod) mods.push(mod); + } - cache[ctx.file] ??= {}; - cache[ctx.file][id] = transformedCode; - // server.moduleGraph.setModuleSource(id, code); - } + cache[ctx.file] ??= {}; + cache[ctx.file][id] = transformedCode; + // server.moduleGraph.setModuleSource(id, code); + } - return mods; - } - // const mods = []; - // [...server.moduleGraph.urlToModuleMap.entries()].forEach(([url, m]) => { - // if (m.file === ctx.file && m.id.includes("pick=")) { - // if (!m.id.includes("pick=loader")) { - // mods.push(m); - // } - // } - // }); - // return mods; - // // this.router.updateRoute(ctx.path); - // } - }, - async transform(code, id) { - const [path, queryString] = id.split("?"); - if (!path) return; - const query = new URLSearchParams(queryString); - const ext = path.split(".").pop(); - if (!ext) return; - if (query.has("pick") && ["js", "jsx", "ts", "tsx"].includes(ext)) { - const transformed = await transform(id, code); - if (!transformed?.code) return; + return mods; + } + // const mods = []; + // [...server.moduleGraph.urlToModuleMap.entries()].forEach(([url, m]) => { + // if (m.file === ctx.file && m.id.includes("pick=")) { + // if (!m.id.includes("pick=loader")) { + // mods.push(m); + // } + // } + // }); + // return mods; + // // this.router.updateRoute(ctx.path); + // } + }, + async transform(code, id) { + const [path, queryString] = id.split("?"); + if (!path) return; + const query = new URLSearchParams(queryString); + const ext = path.split(".").pop(); + if (!ext) return; + if (query.has("pick") && ["js", "jsx", "ts", "tsx"].includes(ext)) { + const transformed = await transform(id, code); + if (!transformed?.code) return; - cache[path] ??= {}; - cache[path][id] = transformed.code; + cache[path] ??= {}; + cache[path][id] = transformed.code; - return { - code: transformed.code, - map: transformed.map, - }; - } - }, - }; + return { + code: transformed.code, + map: transformed.map, + }; + } + }, + }; } diff --git a/packages/start/src/config/index.ts b/packages/start/src/config/index.ts index 599b1c8ae..39a1d0afd 100644 --- a/packages/start/src/config/index.ts +++ b/packages/start/src/config/index.ts @@ -33,10 +33,10 @@ export function solidStart(options?: SolidStartOptions): Array { ssr: true, devOverlay: true, experimental: { - islands: false + islands: false, }, solid: {}, - extensions: [] + extensions: [], }); const extensions = [...DEFAULT_EXTENSIONS, ...(start.extensions || [])]; const routeDir = join(start.appRoot, start.routeDir); @@ -48,7 +48,7 @@ export function solidStart(options?: SolidStartOptions): Array { const entryExtension = extname(appEntryPath); const handlers = { client: `${start.appRoot}/entry-client${entryExtension}`, - server: `${start.appRoot}/entry-server${entryExtension}` + server: `${start.appRoot}/entry-server${entryExtension}`, }; return [ { @@ -57,12 +57,12 @@ export function solidStart(options?: SolidStartOptions): Array { configEnvironment(name) { return { define: { - "import.meta.env.SSR": JSON.stringify(name === VITE_ENVIRONMENTS.server) + "import.meta.env.SSR": JSON.stringify(name === VITE_ENVIRONMENTS.server), }, resolve: { // remove when https://github.com/solidjs/vite-plugin-solid/pull/228 is released - externalConditions: ["solid", "node"] - } + externalConditions: ["solid", "node"], + }, }; }, async config(_, env) { @@ -93,9 +93,9 @@ export function solidStart(options?: SolidStartOptions): Array { rollupOptions: { input: clientInput, treeshake: true, - preserveEntrySignatures: "exports-only" - } - } + preserveEntrySignatures: "exports-only", + }, + }, }, [VITE_ENVIRONMENTS.server]: { consumer: "server", @@ -105,14 +105,14 @@ export function solidStart(options?: SolidStartOptions): Array { manifest: true, copyPublicDir: false, rollupOptions: { - input: "~/entry-server.tsx" + input: "~/entry-server.tsx", }, outDir: "dist/server", commonjsOptions: { - include: [/node_modules/] - } - } - } + include: [/node_modules/], + }, + }, + }, }, resolve: { alias: { @@ -121,17 +121,17 @@ export function solidStart(options?: SolidStartOptions): Array { ...(!start.ssr ? { "@solidjs/start/server": "@solidjs/start/server/spa", - "@solidjs/start/client": "@solidjs/start/client/spa" + "@solidjs/start/client": "@solidjs/start/client/spa", } - : {}) - } + : {}), + }, }, define: { "import.meta.env.MANIFEST": `globalThis.MANIFEST`, "import.meta.env.START_SSR": JSON.stringify(start.ssr), "import.meta.env.START_APP_ENTRY": `"${appEntryPath}"`, "import.meta.env.START_CLIENT_ENTRY": `"${handlers.client}"`, - "import.meta.env.START_DEV_OVERLAY": JSON.stringify(start.devOverlay) + "import.meta.env.START_DEV_OVERLAY": JSON.stringify(start.devOverlay), }, builder: { sharedPlugins: true, @@ -144,24 +144,24 @@ export function solidStart(options?: SolidStartOptions): Array { if (!client.isBuilt) await builder.build(client); if (!server.isBuilt) await builder.build(server); - } - } + }, + }, }; - } + }, }, manifest(start), fsRoutes({ routers: { client: new SolidStartClientFileRouter({ dir: absolute(routeDir, root), - extensions + extensions, }), ssr: new SolidStartServerFileRouter({ dir: absolute(routeDir, root), extensions, - dataOnly: !start.ssr - }) - } + dataOnly: !start.ssr, + }), + }, }), lazy(), // Must be placed after fsRoutes, as treeShake will remove the @@ -177,28 +177,28 @@ export function solidStart(options?: SolidStartOptions): Array { envName: VITE_ENVIRONMENTS.client, getRuntimeCode: () => `import { createServerReference } from "${normalize( - fileURLToPath(new URL("../server/server-runtime", import.meta.url)) + fileURLToPath(new URL("../server/server-runtime", import.meta.url)), )}"`, - replacer: opts => `createServerReference('${opts.functionId}')` + replacer: opts => `createServerReference('${opts.functionId}')`, }, { envConsumer: "server", envName: VITE_ENVIRONMENTS.server, getRuntimeCode: () => `import { createServerReference } from '${normalize( - fileURLToPath(new URL("../server/server-fns-runtime", import.meta.url)) + fileURLToPath(new URL("../server/server-fns-runtime", import.meta.url)), )}'`, - replacer: opts => `createServerReference(${opts.fn}, '${opts.functionId}')` - } + replacer: opts => `createServerReference(${opts.fn}, '${opts.functionId}')`, + }, ], provider: { envName: VITE_ENVIRONMENTS.server, getRuntimeCode: () => `import { createServerReference } from '${normalize( - fileURLToPath(new URL("../server/server-fns-runtime", import.meta.url)) + fileURLToPath(new URL("../server/server-fns-runtime", import.meta.url)), )}'`, - replacer: opts => `createServerReference(${opts.fn}, '${opts.functionId}')` - } + replacer: opts => `createServerReference(${opts.fn}, '${opts.functionId}')`, + }, }), { name: "solid-start:virtual-modules", @@ -217,20 +217,20 @@ export function solidStart(options?: SolidStartOptions): Array { if (query.size > 0) id += `?${query.toString()}`; return id; } - } + }, }, { name: "solid-start:capture-client-bundle", enforce: "post", generateBundle(_options, bundle) { globalThis.START_CLIENT_BUNDLE = bundle; - } + }, }, devServer(), solid({ ...start.solid, ssr: true, - extensions: extensions.map(ext => `.${ext}`) - }) + extensions: extensions.map(ext => `.${ext}`), + }), ]; } diff --git a/packages/start/src/config/lazy.ts b/packages/start/src/config/lazy.ts index 65602f3bb..f94129383 100644 --- a/packages/start/src/config/lazy.ts +++ b/packages/start/src/config/lazy.ts @@ -13,12 +13,12 @@ const idTransform = (id: string): PluginItem => { path.node.body.unshift( t.exportNamedDeclaration( t.variableDeclaration("const", [ - t.variableDeclarator(t.identifier("id$$"), t.stringLiteral(id)) - ]) - ) + t.variableDeclarator(t.identifier("id$$"), t.stringLiteral(id)), + ]), + ), ); - } - } + }, + }, }; }; @@ -34,13 +34,13 @@ const importTransform = (): PluginItem => { path.insertAfter( t.importDeclaration( [t.importSpecifier(t.identifier("lazy"), t.identifier("lazy"))], - t.stringLiteral("@solidjs/start/server") - ) + t.stringLiteral("@solidjs/start/server"), + ), ); - } + }, }); - } - } + }, + }, }; }; @@ -79,21 +79,21 @@ const lazy = (): PluginOption => { const transformed = await babel.transformAsync(src, { plugins, parserOpts: { - plugins: ["jsx", "typescript"] + plugins: ["jsx", "typescript"], }, filename: basename(id), ast: false, sourceMaps: true, configFile: false, babelrc: false, - sourceFileName: id + sourceFileName: id, }); if (!transformed?.code) return; const { code, map } = transformed; return { code, map }; - } + }, }; }; diff --git a/packages/start/src/config/manifest.ts b/packages/start/src/config/manifest.ts index 5a0205c20..e1276d600 100644 --- a/packages/start/src/config/manifest.ts +++ b/packages/start/src/config/manifest.ts @@ -17,7 +17,7 @@ export function manifest(start: SolidStartOptions): PluginOption { return `\0${VIRTUAL_MODULES.clientViteManifest}`; if (id === VIRTUAL_MODULES.getClientManifest) return this.resolve( - new URL("../server/manifest/client-manifest", import.meta.url).pathname + new URL("../server/manifest/client-manifest", import.meta.url).pathname, ); if (id === VIRTUAL_MODULES.getManifest) { return this.environment.config.consumer === "client" @@ -36,11 +36,11 @@ export function manifest(start: SolidStartOptions): PluginOption { clientViteManifest = {}; } else { const entry = Object.values(globalThis.START_CLIENT_BUNDLE).find( - v => "isEntry" in v && v.isEntry + v => "isEntry" in v && v.isEntry, ); if (!entry) throw new Error("No client entry found"); clientViteManifest = JSON.parse( - (globalThis.START_CLIENT_BUNDLE[".vite/manifest.json"] as any).source + (globalThis.START_CLIENT_BUNDLE[".vite/manifest.json"] as any).source, ); } return `export const clientViteManifest = ${JSON.stringify(clientViteManifest)};`; @@ -73,12 +73,12 @@ export function manifest(start: SolidStartOptions): PluginOption { "data-vite-ref": "0", }, children: () => import("${value}?inline").then(mod => mod.default), - }` + }`, ); return `export default [${cssAssets.join(",")}]`; } } - } + }, }; } diff --git a/packages/start/src/config/vite-utils.ts b/packages/start/src/config/vite-utils.ts index de44bd9ea..f5a7d7627 100644 --- a/packages/start/src/config/vite-utils.ts +++ b/packages/start/src/config/vite-utils.ts @@ -64,10 +64,7 @@ export function unwrapId(id: string): string { : id; } -export function normalizeViteImportAnalysisUrl( - environment: DevEnvironment, - id: string, -): string { +export function normalizeViteImportAnalysisUrl(environment: DevEnvironment, id: string): string { let url = normalizeResolvedIdToUrl(environment, id, { id }); // https://github.com/vitejs/vite/blob/c18ce868c4d70873406e9f7d1b2d0a03264d2168/packages/vite/src/node/plugins/importAnalysis.ts#L416 @@ -101,8 +98,7 @@ export function slash(p: string): string { return p.replace(windowsSlashRE, "/"); } -const isWindows = - typeof process !== "undefined" && process.platform === "win32"; +const isWindows = typeof process !== "undefined" && process.platform === "win32"; export function injectQuery(url: string, queryToInject: string): string { const { file, postfix } = splitFileAndPostfix(url); diff --git a/packages/start/src/env.d.ts b/packages/start/src/env.d.ts index 3ba015c32..a2c99e4fb 100644 --- a/packages/start/src/env.d.ts +++ b/packages/start/src/env.d.ts @@ -5,31 +5,29 @@ /* eslint-disable @typescript-eslint/consistent-type-imports */ declare namespace App { - export interface RequestEventLocals { - [key: string | symbol]: any; - } + export interface RequestEventLocals { + [key: string | symbol]: any; + } } declare module "solidstart:server-fn-manifest" { - type ServerFn = (...args: Array) => Promise; - export function getServerFnById(id: string): Promise; + type ServerFn = (...args: Array) => Promise; + export function getServerFnById(id: string): Promise; } -interface ImportMetaEnv - extends Record<`VITE_${string}`, any>, - SolidStartMetaEnv { - BASE_URL: string; - MODE: string; - DEV: boolean; - PROD: boolean; - SSR: boolean; +interface ImportMetaEnv extends Record<`VITE_${string}`, any>, SolidStartMetaEnv { + BASE_URL: string; + MODE: string; + DEV: boolean; + PROD: boolean; + SSR: boolean; } interface SolidStartMetaEnv { - START_SSR: boolean; - START_APP_ENTRY: string; - START_CLIENT_ENTRY: string; - // START_ISLANDS: boolean; - // START_DEV_OVERLAY: boolean; - // SERVER_BASE_URL: string; + START_SSR: boolean; + START_APP_ENTRY: string; + START_CLIENT_ENTRY: string; + // START_ISLANDS: boolean; + // START_DEV_OVERLAY: boolean; + // SERVER_BASE_URL: string; } diff --git a/packages/start/src/http/index.ts b/packages/start/src/http/index.ts index e748dfb5d..0b4339929 100644 --- a/packages/start/src/http/index.ts +++ b/packages/start/src/http/index.ts @@ -3,121 +3,105 @@ import * as h3 from "h3"; import { getRequestEvent } from "solid-js/web"; function _setContext(event: H3Event, key: string, value: any) { - event.context[key] = value; + event.context[key] = value; } function _getContext(event: H3Event, key: string) { - return event.context[key]; + return event.context[key]; } function getEvent() { - return getRequestEvent()!.nativeEvent; + return getRequestEvent()!.nativeEvent; } export function getWebRequest(): Request { - return getEvent().req; + return getEvent().req; } export const HTTPEventSymbol = Symbol("$HTTPEvent"); -export function isEvent( - obj: any, -): obj is h3.H3Event | { [HTTPEventSymbol]: h3.H3Event } { - return ( - typeof obj === "object" && - (obj instanceof h3.H3Event || - obj?.[HTTPEventSymbol] instanceof h3.H3Event || - obj?.__is_event__ === true) - ); - // Implement logic to check if obj is an H3Event +export function isEvent(obj: any): obj is h3.H3Event | { [HTTPEventSymbol]: h3.H3Event } { + return ( + typeof obj === "object" && + (obj instanceof h3.H3Event || + obj?.[HTTPEventSymbol] instanceof h3.H3Event || + obj?.__is_event__ === true) + ); + // Implement logic to check if obj is an H3Event } type Tail = T extends [any, ...infer U] ? U : never; type PrependOverload< - TOriginal extends (...args: Array) => any, - TOverload extends (...args: Array) => any, + TOriginal extends (...args: Array) => any, + TOverload extends (...args: Array) => any, > = TOverload & TOriginal; // add an overload to the function without the event argument type WrapFunction) => any> = PrependOverload< - TFn, - ( - ...args: Parameters extends [ - h3.HTTPEvent | h3.H3Event, - ...infer TArgs, - ] - ? TArgs - : Parameters - ) => ReturnType + TFn, + ( + ...args: Parameters extends [h3.HTTPEvent | h3.H3Event, ...infer TArgs] + ? TArgs + : Parameters + ) => ReturnType >; function createWrapperFunction) => any>( - h3Function: TFn, + h3Function: TFn, ): WrapFunction { - return ((...args: Array) => { - const event = args[0]; - if (!isEvent(event)) { - args.unshift(getEvent()); - } else { - args[0] = - event instanceof h3.H3Event || (event as any).__is_event__ - ? event - : event[HTTPEventSymbol]; - } - - return (h3Function as any)(...args); - }) as any; + return ((...args: Array) => { + const event = args[0]; + if (!isEvent(event)) { + args.unshift(getEvent()); + } else { + args[0] = + event instanceof h3.H3Event || (event as any).__is_event__ ? event : event[HTTPEventSymbol]; + } + + return (h3Function as any)(...args); + }) as any; } // Creating wrappers for each utility and exporting them with their original names // readRawBody => getWebRequest().text()/.arrayBuffer() type WrappedReadBody = >( - ...args: Tail>> + ...args: Tail>> ) => ReturnType>; export const readBody = createWrapperFunction(h3.readBody) as PrependOverload< - typeof h3.readBody, - WrappedReadBody + typeof h3.readBody, + WrappedReadBody >; -type WrappedGetQuery = < - T, - TEventInput = Exclude, undefined>, ->( - ...args: Tail>> +type WrappedGetQuery = , undefined>>( + ...args: Tail>> ) => ReturnType>; export const getQuery = createWrapperFunction(h3.getQuery) as PrependOverload< - typeof h3.getQuery, - WrappedGetQuery + typeof h3.getQuery, + WrappedGetQuery >; export const isMethod = createWrapperFunction(h3.isMethod); export const isPreflightRequest = createWrapperFunction(h3.isPreflightRequest); type WrappedGetValidatedQuery = < - T extends HTTPEvent, - TEventInput = InferEventInput<"query", H3Event, T>, + T extends HTTPEvent, + TEventInput = InferEventInput<"query", H3Event, T>, >( - ...args: Tail< - Parameters> - > + ...args: Tail>> ) => ReturnType>; -export const getValidatedQuery = createWrapperFunction( - h3.getValidatedQuery, -) as PrependOverload; +export const getValidatedQuery = createWrapperFunction(h3.getValidatedQuery) as PrependOverload< + typeof h3.getValidatedQuery, + WrappedGetValidatedQuery +>; export const getRouterParams = createWrapperFunction(h3.getRouterParams); export const getRouterParam = createWrapperFunction(h3.getRouterParam); type WrappedGetValidatedRouterParams = < - T extends HTTPEvent, - TEventInput = InferEventInput<"routerParams", H3Event, T>, + T extends HTTPEvent, + TEventInput = InferEventInput<"routerParams", H3Event, T>, >( - ...args: Tail< - Parameters> - > + ...args: Tail>> ) => ReturnType>; export const getValidatedRouterParams = createWrapperFunction( - h3.getValidatedRouterParams, -) as PrependOverload< - typeof h3.getValidatedRouterParams, - WrappedGetValidatedRouterParams ->; + h3.getValidatedRouterParams, +) as PrependOverload; export const assertMethod = createWrapperFunction(h3.assertMethod); export const getRequestHeaders = createWrapperFunction(h3.getRequestHeaders); export const getRequestHeader = createWrapperFunction(h3.getRequestHeader); @@ -126,53 +110,46 @@ export const getRequestHost = createWrapperFunction(h3.getRequestHost); export const getRequestProtocol = createWrapperFunction(h3.getRequestProtocol); export const getRequestIP = createWrapperFunction(h3.getRequestIP); export const setResponseStatus = (code?: number, text?: string) => { - const e = getEvent(); + const e = getEvent(); - if (e.res.status !== undefined) e.res.status = code; - if (e.res.statusText !== undefined) e.res.statusText = text; + if (e.res.status !== undefined) e.res.status = code; + if (e.res.statusText !== undefined) e.res.statusText = text; }; export const getResponseStatus = () => getEvent().res.status; export const getResponseStatusText = () => getEvent().res.statusText; -export const getResponseHeaders = () => - Object.fromEntries(getEvent().res.headers.entries()); -export const getResponseHeader = (name: string) => - getEvent().res.headers.get(name); +export const getResponseHeaders = () => Object.fromEntries(getEvent().res.headers.entries()); +export const getResponseHeader = (name: string) => getEvent().res.headers.get(name); export const setResponseHeaders = (values: Record) => { - const headers = getEvent().res.headers; - for (const [name, value] of Object.entries(values)) { - headers.set(name, value); - } + const headers = getEvent().res.headers; + for (const [name, value] of Object.entries(values)) { + headers.set(name, value); + } }; export const setResponseHeader = (name: string, value: string | string[]) => { - const headers = getEvent().res.headers; + const headers = getEvent().res.headers; - (Array.isArray(value) ? value : [value]).forEach((value) => { - headers.set(name, value); - }); + (Array.isArray(value) ? value : [value]).forEach(value => { + headers.set(name, value); + }); }; export const appendResponseHeaders = (values: Record) => { - const headers = getEvent().res.headers; - for (const [name, value] of Object.entries(values)) { - headers.append(name, value); - } + const headers = getEvent().res.headers; + for (const [name, value] of Object.entries(values)) { + headers.append(name, value); + } }; -export const appendResponseHeader = ( - name: string, - value: string | string[], -) => { - const headers = getEvent().res.headers; - - (Array.isArray(value) ? value : [value]).forEach((value) => { - headers.append(name, value); - }); +export const appendResponseHeader = (name: string, value: string | string[]) => { + const headers = getEvent().res.headers; + + (Array.isArray(value) ? value : [value]).forEach(value => { + headers.append(name, value); + }); }; export const defaultContentType = (type: string) => - getEvent().res.headers.set("content-type", type); + getEvent().res.headers.set("content-type", type); export const proxyRequest = createWrapperFunction(h3.proxyRequest); export const fetchWithEvent = createWrapperFunction(h3.fetchWithEvent); -export const getProxyRequestHeaders = createWrapperFunction( - h3.getProxyRequestHeaders, -); +export const getProxyRequestHeaders = createWrapperFunction(h3.getProxyRequestHeaders); export const parseCookies = createWrapperFunction(h3.parseCookies); export const getCookie = createWrapperFunction(h3.getCookie); @@ -181,73 +158,59 @@ export const deleteCookie = createWrapperFunction(h3.deleteCookie); // not exported :( type SessionDataT = Record; type WrappedUseSession = ( - ...args: Tail>> + ...args: Tail>> ) => ReturnType>; -export const useSession: WrappedUseSession = createWrapperFunction( - h3.useSession, -); +export const useSession: WrappedUseSession = createWrapperFunction(h3.useSession); type WrappedGetSession = ( - ...args: Tail>> + ...args: Tail>> ) => ReturnType>; -export const getSession: WrappedGetSession = createWrapperFunction( - h3.getSession, -); +export const getSession: WrappedGetSession = createWrapperFunction(h3.getSession); type WrappedUpdateSession = ( - ...args: Tail>> + ...args: Tail>> ) => ReturnType>; -export const updateSession: WrappedUpdateSession = createWrapperFunction( - h3.updateSession, -); +export const updateSession: WrappedUpdateSession = createWrapperFunction(h3.updateSession); type WrappedSealSession = ( - ...args: Tail>> + ...args: Tail>> ) => ReturnType>; -export const sealSession: WrappedSealSession = createWrapperFunction( - h3.sealSession, -); +export const sealSession: WrappedSealSession = createWrapperFunction(h3.sealSession); export const unsealSession = createWrapperFunction(h3.unsealSession); export const clearSession = createWrapperFunction(h3.clearSession); export const handleCacheHeaders = createWrapperFunction(h3.handleCacheHeaders); export const handleCors = createWrapperFunction(h3.handleCors); export const appendCorsHeaders = createWrapperFunction(h3.appendCorsHeaders); -export const appendCorsPreflightHeaders = createWrapperFunction( - h3.appendCorsPreflightHeaders, -); +export const appendCorsPreflightHeaders = createWrapperFunction(h3.appendCorsPreflightHeaders); export const appendHeader = appendResponseHeader; export const appendHeaders = appendResponseHeaders; export const setHeader = setResponseHeader; export const setHeaders = setResponseHeaders; export const getHeader = getRequestHeader; export const getHeaders = getRequestHeaders; -export const getRequestFingerprint = createWrapperFunction( - h3.getRequestFingerprint, -); +export const getRequestFingerprint = createWrapperFunction(h3.getRequestFingerprint); export const getRequestWebStream = () => getEvent().req.body; export const readFormData = () => getEvent().req.formData(); type WrappedReadValidatedBody = < - T extends HTTPEvent, - TEventInput = InferEventInput<"body", H3Event, T>, + T extends HTTPEvent, + TEventInput = InferEventInput<"body", H3Event, T>, >( - ...args: Tail< - Parameters> - > + ...args: Tail>> ) => ReturnType>; -export const readValidatedBody = createWrapperFunction( - h3.readValidatedBody, -) as PrependOverload; +export const readValidatedBody = createWrapperFunction(h3.readValidatedBody) as PrependOverload< + typeof h3.readValidatedBody, + WrappedReadValidatedBody +>; export const getContext = createWrapperFunction(_getContext); export const setContext = createWrapperFunction(_setContext); -export const removeResponseHeader = (name: string) => - getEvent().res.headers.delete(name); +export const removeResponseHeader = (name: string) => getEvent().res.headers.delete(name); export const clearResponseHeaders = (headerNames?: string[]) => { - const headers = getEvent().res.headers; - - if (headerNames && headerNames.length > 0) { - for (const name of headerNames) { - headers.delete(name); - } - } else { - for (const name of headers.keys()) { - headers.delete(name); - } - } + const headers = getEvent().res.headers; + + if (headerNames && headerNames.length > 0) { + for (const name of headerNames) { + headers.delete(name); + } + } else { + for (const name of headers.keys()) { + headers.delete(name); + } + } }; diff --git a/packages/start/src/index.tsx b/packages/start/src/index.tsx index be39a77a3..8d877e714 100644 --- a/packages/start/src/index.tsx +++ b/packages/start/src/index.tsx @@ -1,17 +1,18 @@ // @refresh skip export type { - APIEvent, - APIHandler, - Asset, - ContextMatches, - DocumentComponentProps, - FetchEvent, - HandlerOptions, - PageEvent, - ResponseStub, - ServerFunctionMeta + APIEvent, + APIHandler, + Asset, + ContextMatches, + DocumentComponentProps, + FetchEvent, + HandlerOptions, + PageEvent, + ResponseStub, + ServerFunctionMeta, } from "./server/types.ts"; + export { default as clientOnly } from "./shared/clientOnly.ts"; export { GET } from "./shared/GET.ts"; export { HttpHeader } from "./shared/HttpHeader.tsx"; diff --git a/packages/start/src/internal.d.ts b/packages/start/src/internal.d.ts index 7aee6253e..dd4ef0e40 100644 --- a/packages/start/src/internal.d.ts +++ b/packages/start/src/internal.d.ts @@ -10,5 +10,5 @@ declare module "h3" { import type { Rollup } from "vite"; declare global { var START_CLIENT_BUNDLE: Rollup.OutputBundle; - var USING_SOLID_START_DEV_SERVER: boolean | undefined + var USING_SOLID_START_DEV_SERVER: boolean | undefined; } diff --git a/packages/start/src/middleware/index.ts b/packages/start/src/middleware/index.ts index 692379bb0..26d28e717 100644 --- a/packages/start/src/middleware/index.ts +++ b/packages/start/src/middleware/index.ts @@ -10,73 +10,63 @@ export type MiddlewareFn = (event: FetchEvent) => Promise | unknown; /** This composes an array of Exchanges into a single ExchangeIO function */ export type RequestMiddleware = ( - event: FetchEvent, -) => - | Response - | Promise - | void - | Promise - | Promise; + event: FetchEvent, +) => Response | Promise | void | Promise | Promise; // copy-pasted from h3/dist/index.d.ts type EventHandlerResponse = T | Promise; type ResponseMiddlewareResponseParam = { body?: Awaited }; export type ResponseMiddleware = ( - event: FetchEvent, - response: ResponseMiddlewareResponseParam, + event: FetchEvent, + response: ResponseMiddlewareResponseParam, ) => Response | Promise | void | Promise; function wrapRequestMiddleware(onRequest: RequestMiddleware) { - return async (h3Event: H3Event) => { - const fetchEvent = getFetchEvent(h3Event); - const response = await onRequest(fetchEvent); - if (response) return response; - }; + return async (h3Event: H3Event) => { + const fetchEvent = getFetchEvent(h3Event); + const response = await onRequest(fetchEvent); + if (response) return response; + }; } -function wrapResponseMiddleware( - onBeforeResponse: ResponseMiddleware, -): Middleware { - return async (h3Event, next) => { - const resp = await next(); +function wrapResponseMiddleware(onBeforeResponse: ResponseMiddleware): Middleware { + return async (h3Event, next) => { + const resp = await next(); - const fetchEvent = getFetchEvent(h3Event); - const mwResponse = await onBeforeResponse(fetchEvent, { - body: (resp as any)?.body, - }); - if (mwResponse) return mwResponse; - }; + const fetchEvent = getFetchEvent(h3Event); + const mwResponse = await onBeforeResponse(fetchEvent, { + body: (resp as any)?.body, + }); + if (mwResponse) return mwResponse; + }; } export function createMiddleware( - args: - | { - /** @deprecated Use H3 `Middleware` */ - onRequest?: RequestMiddleware | RequestMiddleware[] | undefined; - /** @deprecated Use H3 `Middleware` */ - onBeforeResponse?: - | ResponseMiddleware - | ResponseMiddleware[] - | undefined; - } - | Middleware[], + args: + | { + /** @deprecated Use H3 `Middleware` */ + onRequest?: RequestMiddleware | RequestMiddleware[] | undefined; + /** @deprecated Use H3 `Middleware` */ + onBeforeResponse?: ResponseMiddleware | ResponseMiddleware[] | undefined; + } + | Middleware[], ): Middleware[] { - if (Array.isArray(args)) return args; + if (Array.isArray(args)) return args; - const mw: Middleware[] = []; + const mw: Middleware[] = []; - if (typeof args.onRequest === "function") { - mw.push(wrapRequestMiddleware(args.onRequest)); - } else if (Array.isArray(args.onRequest)) { - mw.push(...args.onRequest.map(wrapRequestMiddleware)); - } + if (typeof args.onRequest === "function") { + mw.push(wrapRequestMiddleware(args.onRequest)); + } else if (Array.isArray(args.onRequest)) { + mw.push(...args.onRequest.map(wrapRequestMiddleware)); + } - if (typeof args.onBeforeResponse === "function") { - mw.push(wrapResponseMiddleware(args.onBeforeResponse)); - } else if (Array.isArray(args.onBeforeResponse)) { - mw.push(...args.onBeforeResponse.map(wrapResponseMiddleware)); - } + if (typeof args.onBeforeResponse === "function") { + mw.push(wrapResponseMiddleware(args.onBeforeResponse)); + } else if (Array.isArray(args.onBeforeResponse)) { + mw.push(...args.onBeforeResponse.map(wrapResponseMiddleware)); + } - return mw; + return mw; } diff --git a/packages/start/src/router.tsx b/packages/start/src/router.tsx index 7b2116e25..980942739 100644 --- a/packages/start/src/router.tsx +++ b/packages/start/src/router.tsx @@ -8,17 +8,18 @@ const components: Record = {}; export function createRoutes() { function createRoute(route: any) { - const component = route.$component && (components[route.$component.src] ??= lazy(route.$component.import)); + const component = + route.$component && (components[route.$component.src] ??= lazy(route.$component.import)); return { ...route, ...(route.$$route ? route.$$route.require().route : undefined), info: { ...(route.$$route ? route.$$route.require().route.info : {}), - filesystem: true + filesystem: true, }, component, - children: route.children ? route.children.map(createRoute) : undefined + children: route.children ? route.children.map(createRoute) : undefined, }; } const routes = routeConfigs.map(createRoute); diff --git a/packages/start/src/server/StartServer.tsx b/packages/start/src/server/StartServer.tsx index 1059412bf..97419175c 100644 --- a/packages/start/src/server/StartServer.tsx +++ b/packages/start/src/server/StartServer.tsx @@ -1,12 +1,6 @@ // @refresh skip import type { Component } from "solid-js"; -import { - Hydration, - HydrationScript, - NoHydration, - getRequestEvent, - ssr -} from "solid-js/web"; +import { Hydration, HydrationScript, NoHydration, getRequestEvent, ssr } from "solid-js/web"; import App from "solid-start:app"; import { ErrorBoundary, TopErrorBoundary } from "../shared/ErrorBoundary.tsx"; diff --git a/packages/start/src/server/assets/index.ts b/packages/start/src/server/assets/index.ts index 5cff04dd0..947b2acaf 100644 --- a/packages/start/src/server/assets/index.ts +++ b/packages/start/src/server/assets/index.ts @@ -24,7 +24,7 @@ const getEntity = (registry: Registry, asset: Asset) => { const entity = (registry[key] ??= { key, - consumers: 0 + consumers: 0, }); return entity; diff --git a/packages/start/src/server/assets/render.tsx b/packages/start/src/server/assets/render.tsx index 4b458e645..63aa2a07f 100644 --- a/packages/start/src/server/assets/render.tsx +++ b/packages/start/src/server/assets/render.tsx @@ -18,11 +18,15 @@ const assetMap = { }, noscript: (props: { attrs: JSX.HTMLAttributes; children: JSX.Element }) => ( - ) + ), }; export function renderAsset(asset: Asset, nonce?: string) { - let { tag, attrs: { key, ...attrs } = { key: undefined }, children } = asset as any; + let { + tag, + attrs: { key, ...attrs } = { key: undefined }, + children, + } = asset as any; return (assetMap as any)[tag]({ attrs: { ...attrs, nonce }, key, children }); } diff --git a/packages/start/src/server/collect-styles.ts b/packages/start/src/server/collect-styles.ts index 85079ae0e..7e4b40453 100644 --- a/packages/start/src/server/collect-styles.ts +++ b/packages/start/src/server/collect-styles.ts @@ -2,93 +2,86 @@ import path from "node:path"; import { resolve } from "pathe"; import type { DevEnvironment, EnvironmentModuleNode } from "vite"; -async function getViteModuleNode( - vite: DevEnvironment, - file: string, -) { - let nodePath = file; - let node = vite.moduleGraph.getModuleById(file); +async function getViteModuleNode(vite: DevEnvironment, file: string) { + let nodePath = file; + let node = vite.moduleGraph.getModuleById(file); - if (!node) { - const resolvedId = await vite.pluginContainer.resolveId(file, undefined); - if (!resolvedId) return; + if (!node) { + const resolvedId = await vite.pluginContainer.resolveId(file, undefined); + if (!resolvedId) return; - nodePath = resolvedId.id; - node = vite.moduleGraph.getModuleById(file); - } + nodePath = resolvedId.id; + node = vite.moduleGraph.getModuleById(file); + } - if (!node) { - nodePath = resolve(nodePath); - node = await vite.moduleGraph.getModuleByUrl(file); - } + if (!node) { + nodePath = resolve(nodePath); + node = await vite.moduleGraph.getModuleByUrl(file); + } - if (!node) { - await vite.moduleGraph.ensureEntryFromUrl(nodePath, false); - node = vite.moduleGraph.getModuleById(nodePath); - } + if (!node) { + await vite.moduleGraph.ensureEntryFromUrl(nodePath, false); + node = vite.moduleGraph.getModuleById(nodePath); + } - return node; + return node; } async function findModuleDependencies( - vite: DevEnvironment, - file: string, - deps: Set, - crawledFiles = new Set() + vite: DevEnvironment, + file: string, + deps: Set, + crawledFiles = new Set(), ) { - crawledFiles.add(file); - const module = await getViteModuleNode(vite, file); - if (!module?.id || deps.has(module)) return; - - deps.add(module); - - if (module.url.endsWith(".css") || module.url.includes("node_modules")) return; - - if (!module.transformResult) { - await vite.transformRequest(module.id).catch(() => {}); - } - if (!module.transformResult?.deps) return; - - // Relying on module.transformResult.deps instead of module.importedModules because: - // transformResult properly separates imports into deps and dynamicDeps, importedModules doesn't - // Style crawling has to skip dynamic imports as such modules load their styles themselves - for (const dep of module.transformResult.deps) { - if (crawledFiles.has(dep)) { - continue; - } - await findModuleDependencies(vite, dep, deps, crawledFiles); - } + crawledFiles.add(file); + const module = await getViteModuleNode(vite, file); + if (!module?.id || deps.has(module)) return; + + deps.add(module); + + if (module.url.endsWith(".css") || module.url.includes("node_modules")) return; + + if (!module.transformResult) { + await vite.transformRequest(module.id).catch(() => {}); + } + if (!module.transformResult?.deps) return; + + // Relying on module.transformResult.deps instead of module.importedModules because: + // transformResult properly separates imports into deps and dynamicDeps, importedModules doesn't + // Style crawling has to skip dynamic imports as such modules load their styles themselves + for (const dep of module.transformResult.deps) { + if (crawledFiles.has(dep)) { + continue; + } + await findModuleDependencies(vite, dep, deps, crawledFiles); + } } // Vite doesn't expose these so we just copy the list for now // https://github.com/vitejs/vite/blob/d6bde8b03d433778aaed62afc2be0630c8131908/packages/vite/src/node/constants.ts#L49C23-L50 -const cssFileRegExp = - /\.(css|less|sass|scss|styl|stylus|pcss|postcss|sss)$/; +const cssFileRegExp = /\.(css|less|sass|scss|styl|stylus|pcss|postcss|sss)$/; // https://github.com/vitejs/vite/blob/d6bde8b03d433778aaed62afc2be0630c8131908/packages/vite/src/node/plugins/css.ts#L160 const cssModulesRegExp = new RegExp(`\\.module${cssFileRegExp.source}`); const isCssFile = (file: string) => cssFileRegExp.test(file); export const isCssModulesFile = (file: string) => cssModulesRegExp.test(file); -export async function findStylesInModuleGraph( - vite: DevEnvironment, - id: string, -) { - const absolute = path.resolve(process.cwd(), id); - const dependencies = new Set(); - - try { - await findModuleDependencies(vite, absolute, dependencies); - } catch (e) { - console.error(e); - } - - const styles: Record = {}; - for (const dep of dependencies) { - if (dep.id && isCssFile(dep.url)) { - styles[dep.id] = dep.url; - } - } - - return styles; +export async function findStylesInModuleGraph(vite: DevEnvironment, id: string) { + const absolute = path.resolve(process.cwd(), id); + const dependencies = new Set(); + + try { + await findModuleDependencies(vite, absolute, dependencies); + } catch (e) { + console.error(e); + } + + const styles: Record = {}; + for (const dep of dependencies) { + if (dep.id && isCssFile(dep.url)) { + styles[dep.id] = dep.url; + } + } + + return styles; } diff --git a/packages/start/src/server/fetchEvent.spec.ts b/packages/start/src/server/fetchEvent.spec.ts index 9949eab22..6b59459d0 100644 --- a/packages/start/src/server/fetchEvent.spec.ts +++ b/packages/start/src/server/fetchEvent.spec.ts @@ -1,89 +1,85 @@ import * as h3 from "h3"; import { beforeEach, describe, expect, it, vi } from "vitest"; -import { - createFetchEvent, - getFetchEvent, - mergeResponseHeaders, -} from "./fetchEvent.ts"; - -vi.mock(import("h3"), async (mod) => { - return { - ...(await mod()), - getRequestIP: vi.fn(), - }; +import { createFetchEvent, getFetchEvent, mergeResponseHeaders } from "./fetchEvent.ts"; + +vi.mock(import("h3"), async mod => { + return { + ...(await mod()), + getRequestIP: vi.fn(), + }; }); const mockedH3 = vi.mocked(h3); const createMockH3Event = (): h3.H3Event => { - const event = new h3.H3Event(new Request("http://localhost/test")); + const event = new h3.H3Event(new Request("http://localhost/test")); - event.res.status = 200; - event.res.statusText = "OK"; + event.res.status = 200; + event.res.statusText = "OK"; - return event; + return event; }; describe("fetchEvent", () => { - let mockH3Event: h3.H3Event; - - beforeEach(() => { - mockH3Event = createMockH3Event(); - vi.clearAllMocks(); - - mockedH3.getRequestIP.mockReturnValue("127.0.0.1"); - }); - - describe("createFetchEvent", () => { - it("should create a FetchEvent from H3Event", () => { - const fetchEvent = createFetchEvent(mockH3Event); - - expect(fetchEvent).toEqual({ - request: mockH3Event.req, - response: expect.any(Object), - clientAddress: "127.0.0.1", - locals: {}, - nativeEvent: mockH3Event, - }); - }); - - it("should create response stub with correct properties", () => { - const fetchEvent = createFetchEvent(mockH3Event); - - expect(fetchEvent.response).toHaveProperty("status"); - expect(fetchEvent.response).toHaveProperty("statusText"); - expect(fetchEvent.response).toHaveProperty("headers"); - }); - }); - - describe("getFetchEvent", () => { - it("should create and cache FetchEvent on first call", () => { - const fetchEvent = getFetchEvent(mockH3Event); - - expect(mockH3Event.context.solidFetchEvent).toBe(fetchEvent); - expect(fetchEvent.nativeEvent).toBe(mockH3Event); - }); - - it("should return cached FetchEvent on subsequent calls", () => { - const firstCall = getFetchEvent(mockH3Event); - const secondCall = getFetchEvent(mockH3Event); - - expect(firstCall).toBe(secondCall); - }); - }); - - describe("mergeResponseHeaders", () => { - it("should merge headers from Headers object to H3Event", () => { - const headers = new Headers({ - "content-type": "application/json", - "x-custom": "value", - }); - - mergeResponseHeaders(mockH3Event, headers); - - expect(headers.get("content-type")).toBe("application/json"); - expect(headers.get("x-custom")).toBe("value"); - }); - }); + let mockH3Event: h3.H3Event; + + beforeEach(() => { + mockH3Event = createMockH3Event(); + vi.clearAllMocks(); + + mockedH3.getRequestIP.mockReturnValue("127.0.0.1"); + }); + + describe("createFetchEvent", () => { + it("should create a FetchEvent from H3Event", () => { + const fetchEvent = createFetchEvent(mockH3Event); + + expect(fetchEvent).toEqual({ + request: mockH3Event.req, + response: expect.any(Object), + clientAddress: "127.0.0.1", + locals: {}, + nativeEvent: mockH3Event, + }); + }); + + it("should create response stub with correct properties", () => { + const fetchEvent = createFetchEvent(mockH3Event); + + expect(fetchEvent.response).toHaveProperty("status"); + expect(fetchEvent.response).toHaveProperty("statusText"); + expect(fetchEvent.response).toHaveProperty("headers"); + }); + }); + + describe("getFetchEvent", () => { + it("should create and cache FetchEvent on first call", () => { + const fetchEvent = getFetchEvent(mockH3Event); + + expect(mockH3Event.context.solidFetchEvent).toBe(fetchEvent); + expect(fetchEvent.nativeEvent).toBe(mockH3Event); + }); + + it("should return cached FetchEvent on subsequent calls", () => { + const firstCall = getFetchEvent(mockH3Event); + const secondCall = getFetchEvent(mockH3Event); + + expect(firstCall).toBe(secondCall); + }); + }); + + describe("mergeResponseHeaders", () => { + it("should merge headers from Headers object to H3Event", () => { + const headers = new Headers({ + "content-type": "application/json", + "x-custom": "value", + }); + + mergeResponseHeaders(mockH3Event, headers); + + expect(headers.get("content-type")).toBe("application/json"); + expect(headers.get("x-custom")).toBe("value"); + }); + }); }); diff --git a/packages/start/src/server/fetchEvent.ts b/packages/start/src/server/fetchEvent.ts index ffbae0080..d3a415cc6 100644 --- a/packages/start/src/server/fetchEvent.ts +++ b/packages/start/src/server/fetchEvent.ts @@ -5,32 +5,32 @@ import type { FetchEvent } from "./types.ts"; const FETCH_EVENT_CONTEXT = "solidFetchEvent"; export function createFetchEvent(event: H3Event): FetchEvent { - return { - request: event.req, - response: event.res, - clientAddress: getRequestIP(event), - locals: {}, - nativeEvent: event, - }; + return { + request: event.req, + response: event.res, + clientAddress: getRequestIP(event), + locals: {}, + nativeEvent: event, + }; } export function getFetchEvent(h3Event: H3Event): FetchEvent { - if (!h3Event.context[FETCH_EVENT_CONTEXT]) { - const fetchEvent = createFetchEvent(h3Event); - h3Event.context[FETCH_EVENT_CONTEXT] = fetchEvent; - } + if (!h3Event.context[FETCH_EVENT_CONTEXT]) { + const fetchEvent = createFetchEvent(h3Event); + h3Event.context[FETCH_EVENT_CONTEXT] = fetchEvent; + } - return h3Event.context[FETCH_EVENT_CONTEXT] as any; + return h3Event.context[FETCH_EVENT_CONTEXT] as any; } export function mergeResponseHeaders(h3Event: H3Event, headers: Headers) { - for (const [key, value] of headers.entries()) { - h3Event.res.headers.append(key, value); - } + for (const [key, value] of headers.entries()) { + h3Event.res.headers.append(key, value); + } } export const decorateHandler = (fn: T) => - (event => provideRequestEvent(getFetchEvent(event), () => fn(event))) as T; + (event => provideRequestEvent(getFetchEvent(event), () => fn(event))) as T; export const decorateMiddleware = (fn: T) => - ((event, next) => provideRequestEvent(getFetchEvent(event), () => fn(event, next))) as T; + ((event, next) => provideRequestEvent(getFetchEvent(event), () => fn(event, next))) as T; diff --git a/packages/start/src/server/handler.ts b/packages/start/src/server/handler.ts index 271b4de06..333ad60f4 100644 --- a/packages/start/src/server/handler.ts +++ b/packages/start/src/server/handler.ts @@ -10,88 +10,79 @@ import { decorateHandler, decorateMiddleware } from "./fetchEvent.ts"; import { getSsrManifest } from "./manifest/ssr-manifest.ts"; import { matchAPIRoute } from "./routes.ts"; import { handleServerFunction } from "./server-functions-handler.ts"; -import type { - APIEvent, - FetchEvent, - HandlerOptions, - PageEvent, -} from "./types.ts"; +import type { APIEvent, FetchEvent, HandlerOptions, PageEvent } from "./types.ts"; import { getExpectedRedirectStatus } from "./util.ts"; const SERVER_FN_BASE = "/_server"; export function createBaseHandler( - createPageEvent: (e: FetchEvent) => Promise, - fn: (context: PageEvent) => JSX.Element, - options: - | HandlerOptions - | ((context: PageEvent) => HandlerOptions | Promise) = {}, + createPageEvent: (e: FetchEvent) => Promise, + fn: (context: PageEvent) => JSX.Element, + options: HandlerOptions | ((context: PageEvent) => HandlerOptions | Promise) = {}, ) { - const handler = defineHandler({ - middleware: middleware.length ? middleware.map(decorateMiddleware): undefined, - handler: decorateHandler(async (e: H3Event) => { - const event = getRequestEvent()!; - const url = new URL(event.request.url); - const pathname = stripBaseUrl(url.pathname); + const handler = defineHandler({ + middleware: middleware.length ? middleware.map(decorateMiddleware) : undefined, + handler: decorateHandler(async (e: H3Event) => { + const event = getRequestEvent()!; + const url = new URL(event.request.url); + const pathname = stripBaseUrl(url.pathname); - if (pathname.startsWith(SERVER_FN_BASE)) { - const serverFnResponse = await handleServerFunction(e); + if (pathname.startsWith(SERVER_FN_BASE)) { + const serverFnResponse = await handleServerFunction(e); if (serverFnResponse instanceof Response) return produceResponseWithEventHeaders(serverFnResponse); - return new Response(serverFnResponse as any, { - headers: e.res.headers, - }); - } - - const match = matchAPIRoute(pathname, event.request.method); - if (match) { - const mod = await match.handler.import(); - const fn = - event.request.method === "HEAD" - ? mod["HEAD"] || mod["GET"] - : mod[event.request.method]; - (event as APIEvent).params = match.params || {}; - // @ts-expect-error - sharedConfig.context = { event }; - const res = await fn!(event); + return new Response(serverFnResponse as any, { + headers: e.res.headers, + }); + } + + const match = matchAPIRoute(pathname, event.request.method); + if (match) { + const mod = await match.handler.import(); + const fn = + event.request.method === "HEAD" ? mod["HEAD"] || mod["GET"] : mod[event.request.method]; + (event as APIEvent).params = match.params || {}; + // @ts-expect-error + sharedConfig.context = { event }; + const res = await fn!(event); if (res !== undefined) { - if(res instanceof Response) return produceResponseWithEventHeaders(res) + if (res instanceof Response) return produceResponseWithEventHeaders(res); return res; } - if (event.request.method !== "GET") { - throw new Error( - `API handler for ${event.request.method} "${event.request.url}" did not return a response.`, - ); - } - if (!match.isPage) return; - } - - const context = await createPageEvent(event); - - const resolvedOptions = - typeof options === "function" ? await options(context) : { ...options }; - const mode = resolvedOptions.mode || "stream"; - if (resolvedOptions.nonce) context.nonce = resolvedOptions.nonce; - - if (mode === "sync" || !import.meta.env.START_SSR) { - const html = renderToString(() => { - (sharedConfig.context as any).event = context; - return fn(context); - }); - context.complete = true; - - if (context.response && context.response.headers.get("Location")) { + if (event.request.method !== "GET") { + throw new Error( + `API handler for ${event.request.method} "${event.request.url}" did not return a response.`, + ); + } + if (!match.isPage) return; + } + + const context = await createPageEvent(event); + + const resolvedOptions = + typeof options === "function" ? await options(context) : { ...options }; + const mode = resolvedOptions.mode || "stream"; + if (resolvedOptions.nonce) context.nonce = resolvedOptions.nonce; + + if (mode === "sync" || !import.meta.env.START_SSR) { + const html = renderToString(() => { + (sharedConfig.context as any).event = context; + return fn(context); + }); + context.complete = true; + + if (context.response && context.response.headers.get("Location")) { const status = getExpectedRedirectStatus(context.response); return redirect(context.response.headers.get("Location")!, status); } - return html; - } + return html; + } - if (resolvedOptions.onCompleteAll) { + if (resolvedOptions.onCompleteAll) { const og = resolvedOptions.onCompleteAll; resolvedOptions.onCompleteAll = options => { handleStreamCompleteRedirect(context)(options); @@ -106,10 +97,10 @@ export function createBaseHandler( }; } else resolvedOptions.onCompleteShell = handleShellCompleteRedirect(context, e); - const _stream = renderToStream(() => { - (sharedConfig.context as any).event = context; - return fn(context); - }, resolvedOptions); + const _stream = renderToStream(() => { + (sharedConfig.context as any).event = context; + return fn(context); + }, resolvedOptions); const stream = _stream as typeof _stream & PromiseLike; // stream has a hidden 'then' method if (context.response && context.response.headers.get("Location")) { @@ -117,48 +108,46 @@ export function createBaseHandler( return redirect(context.response.headers.get("Location")!, status); } - if (mode === "async") return await stream + if (mode === "async") return await stream; delete (stream as any).then; // using TransformStream in dev can cause solid-start-dev-server to crash // when stream is cancelled - if(globalThis.USING_SOLID_START_DEV_SERVER) return stream + if (globalThis.USING_SOLID_START_DEV_SERVER) return stream; // returning stream directly breaks cloudflare workers const { writable, readable } = new TransformStream(); stream.pipeTo(writable); - return readable - }), - }); + return readable; + }), + }); - const app = new H3(); + const app = new H3(); app.use(handler); - return app; + return app; } export function createHandler( - fn: (context: PageEvent) => JSX.Element, - options: - | HandlerOptions - | ((context: PageEvent) => HandlerOptions | Promise) = {}, + fn: (context: PageEvent) => JSX.Element, + options: HandlerOptions | ((context: PageEvent) => HandlerOptions | Promise) = {}, ) { - return createBaseHandler(createPageEvent, fn, options); + return createBaseHandler(createPageEvent, fn, options); } export async function createPageEvent(ctx: FetchEvent) { - ctx.response.headers.set("Content-Type", "text/html"); - // const prevPath = ctx.request.headers.get("x-solid-referrer"); - // const mutation = ctx.request.headers.get("x-solid-mutation") === "true"; - const manifest = getSsrManifest(import.meta.env.SSR && import.meta.env.DEV ? "ssr": "client"); + ctx.response.headers.set("Content-Type", "text/html"); + // const prevPath = ctx.request.headers.get("x-solid-referrer"); + // const mutation = ctx.request.headers.get("x-solid-mutation") === "true"; + const manifest = getSsrManifest(import.meta.env.SSR && import.meta.env.DEV ? "ssr" : "client"); - // Handle Vite build.cssCodeSplit - // When build.cssCodeSplit is false, a single CSS file is generated with the key style.css - const mergedCSS = import.meta.env.PROD ? await manifest.getAssets('style.css'): []; + // Handle Vite build.cssCodeSplit + // When build.cssCodeSplit is false, a single CSS file is generated with the key style.css + const mergedCSS = import.meta.env.PROD ? await manifest.getAssets("style.css") : []; - const assets = [ + const assets = [ ...mergedCSS, ...(await manifest.getAssets(import.meta.env.START_CLIENT_ENTRY)), ...(await manifest.getAssets(import.meta.env.START_APP_ENTRY)), @@ -168,52 +157,49 @@ export async function createPageEvent(ctx: FetchEvent) { // ) // : []) ]; - const pageEvent: PageEvent = Object.assign(ctx, { - assets, - router: { - submission: initFromFlash(ctx) as any, - }, - routes: createRoutes(), - // prevUrl: prevPath || "", - // mutation: mutation, - // $type: FETCH_EVENT, - complete: false, - $islands: new Set(), - }); - - return pageEvent; + const pageEvent: PageEvent = Object.assign(ctx, { + assets, + router: { + submission: initFromFlash(ctx) as any, + }, + routes: createRoutes(), + // prevUrl: prevPath || "", + // mutation: mutation, + // $type: FETCH_EVENT, + complete: false, + $islands: new Set(), + }); + + return pageEvent; } function initFromFlash(ctx: FetchEvent) { - const flash = getCookie(ctx.nativeEvent, "flash"); - if (!flash) return; - try { - const param = JSON.parse(flash); - if (!param || !param.result) return; - const input = [ - ...param.input.slice(0, -1), - new Map(param.input[param.input.length - 1]), - ]; - const result = param.error ? new Error(param.result) : param.result; - return { - input, - url: param.url, - pending: false, - result: param.thrown ? undefined : result, - error: param.thrown ? result : undefined, - }; - } catch (e) { - console.error(e); - } finally { - setCookie(ctx.nativeEvent, "flash", "", { maxAge: 0 }); - } + const flash = getCookie(ctx.nativeEvent, "flash"); + if (!flash) return; + try { + const param = JSON.parse(flash); + if (!param || !param.result) return; + const input = [...param.input.slice(0, -1), new Map(param.input[param.input.length - 1])]; + const result = param.error ? new Error(param.result) : param.result; + return { + input, + url: param.url, + pending: false, + result: param.thrown ? undefined : result, + error: param.thrown ? result : undefined, + }; + } catch (e) { + console.error(e); + } finally { + setCookie(ctx.nativeEvent, "flash", "", { maxAge: 0 }); + } } function handleShellCompleteRedirect(context: PageEvent, e: H3Event) { return () => { if (context.response && context.response.headers.get("Location")) { const status = getExpectedRedirectStatus(context.response); - e.res.status = status + e.res.status = status; e.res.headers.set("Location", context.response.headers.get("Location")!); } }; @@ -233,16 +219,16 @@ function produceResponseWithEventHeaders(res: Response) { let ret = res; // Response.redirect returns an immutable value, so we clone on any redirect just in case - if(300 <= res.status && res.status < 400) { + if (300 <= res.status && res.status < 400) { const cookies = res.headers.getSetCookie?.() ?? []; const headers = new Headers(); res.headers.forEach((value, key) => { - if (key.toLowerCase() !== 'set-cookie') { + if (key.toLowerCase() !== "set-cookie") { headers.set(key, value); } }); for (const cookie of cookies) { - headers.append('Set-Cookie', cookie); + headers.append("Set-Cookie", cookie); } ret = new Response(res.body, { status: res.status, @@ -253,19 +239,19 @@ function produceResponseWithEventHeaders(res: Response) { const eventCookies = event.response.headers.getSetCookie?.() ?? []; for (const cookie of eventCookies) { - ret.headers.append('Set-Cookie', cookie); + ret.headers.append("Set-Cookie", cookie); } - for(const [name, value] of event.response.headers) { - if (name.toLowerCase() !== 'set-cookie') { + for (const [name, value] of event.response.headers) { + if (name.toLowerCase() !== "set-cookie") { ret.headers.set(name, value); } } - return ret + return ret; } function stripBaseUrl(path: string) { - if(import.meta.env.BASE_URL === "/" || import.meta.env.BASE_URL === "") return path; + if (import.meta.env.BASE_URL === "/" || import.meta.env.BASE_URL === "") return path; return path.slice(import.meta.env.BASE_URL.length); } diff --git a/packages/start/src/server/manifest/client-manifest.ts b/packages/start/src/server/manifest/client-manifest.ts index e511d5627..c9702bb7a 100644 --- a/packages/start/src/server/manifest/client-manifest.ts +++ b/packages/start/src/server/manifest/client-manifest.ts @@ -2,7 +2,7 @@ import { getClientDevManifest } from "./dev-client-manifest.ts"; import { getClientProdManifest } from "./prod-client-manifest.ts"; export function getClientManifest() { - return import.meta.env.DEV ? getClientDevManifest() : getClientProdManifest(); + return import.meta.env.DEV ? getClientDevManifest() : getClientProdManifest(); } export { getClientManifest as getManifest }; diff --git a/packages/start/src/server/manifest/dev-client-manifest.ts b/packages/start/src/server/manifest/dev-client-manifest.ts index 0d15c4d54..480693955 100644 --- a/packages/start/src/server/manifest/dev-client-manifest.ts +++ b/packages/start/src/server/manifest/dev-client-manifest.ts @@ -3,17 +3,19 @@ import { join } from "pathe"; export function getClientDevManifest() { return { import(id) { - return import(/* @vite-ignore */ join("/", id)) + return import(/* @vite-ignore */ join("/", id)); }, async getAssets(id) { const assetsPath = `/@manifest/client/${Date.now()}/assets?id=${id}`; const assets = (await import(/* @vite-ignore */ assetsPath)).default; - return await Promise.all(assets.map(async (v: any) => ({ - ...v, - children: await v.children() - }))); + return await Promise.all( + assets.map(async (v: any) => ({ + ...v, + children: await v.children(), + })), + ); }, } satisfies StartManifest & { import(id: string): Promise }; } diff --git a/packages/start/src/server/manifest/dev-ssr-manifest.ts b/packages/start/src/server/manifest/dev-ssr-manifest.ts index 735ec073f..4fefff1c5 100644 --- a/packages/start/src/server/manifest/dev-ssr-manifest.ts +++ b/packages/start/src/server/manifest/dev-ssr-manifest.ts @@ -8,14 +8,16 @@ export function getSsrDevManifest(environment: "client" | "ssr") { const assets = (await import(/* @vite-ignore */ assetsPath)).default; - return await Promise.all(assets.map(async (v: any) => ({ - ...v, - children: await v.children() - }))); + return await Promise.all( + assets.map(async (v: any) => ({ + ...v, + children: await v.children(), + })), + ); }, } satisfies StartManifest & { - path(id: string): string; - }; + path(id: string): string; + }; } export { getSsrDevManifest as getSsrManifest }; diff --git a/packages/start/src/server/manifest/prod-client-manifest.ts b/packages/start/src/server/manifest/prod-client-manifest.ts index 2487abaf7..03f477780 100644 --- a/packages/start/src/server/manifest/prod-client-manifest.ts +++ b/packages/start/src/server/manifest/prod-client-manifest.ts @@ -2,17 +2,20 @@ export function getClientProdManifest() { return { import(id) { // @ts-ignore - return import(/* @vite-ignore */ window.manifest[id].output) + return import(/* @vite-ignore */ window.manifest[id].output); }, async getAssets(id) { if (id.startsWith("./")) id = id.slice(2); // @ts-ignore - return window.manifest[id]?.assets ?? [] + return window.manifest[id]?.assets ?? []; }, async json() { // @ts-ignore - return window.manifest + return window.manifest; }, - } satisfies StartManifest & { json(): Promise>, import(id: string): Promise; } + } satisfies StartManifest & { + json(): Promise>; + import(id: string): Promise; + }; } diff --git a/packages/start/src/server/manifest/prod-ssr-manifest.ts b/packages/start/src/server/manifest/prod-ssr-manifest.ts index 6d3ccd732..89d4f02ef 100644 --- a/packages/start/src/server/manifest/prod-ssr-manifest.ts +++ b/packages/start/src/server/manifest/prod-ssr-manifest.ts @@ -5,121 +5,105 @@ import type { Asset } from "../assets/render.tsx"; // Only reads from client manifest atm, might need server support for islands export function getSsrProdManifest() { - const viteManifest = clientViteManifest; - return { - path(id: string) { + const viteManifest = clientViteManifest; + return { + path(id: string) { if (id.startsWith("./")) id = id.slice(2); - const viteManifestEntry = - clientViteManifest[id /*import.meta.env.START_CLIENT_ENTRY*/]; - if (!viteManifestEntry) - throw new Error(`No entry found in vite manifest for '${id}'`); + const viteManifestEntry = clientViteManifest[id /*import.meta.env.START_CLIENT_ENTRY*/]; + if (!viteManifestEntry) throw new Error(`No entry found in vite manifest for '${id}'`); - return join("/", viteManifestEntry.file); - }, - async getAssets(id) { + return join("/", viteManifestEntry.file); + }, + async getAssets(id) { if (id.startsWith("./")) id = id.slice(2); - return createHtmlTagsForAssets( - findAssetsInViteManifest(clientViteManifest, id), - ); - }, - async json() { - const json: Record = {}; + return createHtmlTagsForAssets(findAssetsInViteManifest(clientViteManifest, id)); + }, + async json() { + const json: Record = {}; - const entryKeys = Object.keys(viteManifest) - .filter((id) => viteManifest[id]?.isEntry || viteManifest[id]?.isDynamicEntry) - .map((id) => id); + const entryKeys = Object.keys(viteManifest) + .filter(id => viteManifest[id]?.isEntry || viteManifest[id]?.isDynamicEntry) + .map(id => id); - for (const entryKey of entryKeys) { - json[entryKey] = { - output: join("/", viteManifest[entryKey]!.file), - assets: await this.getAssets(entryKey), - }; - } + for (const entryKey of entryKeys) { + json[entryKey] = { + output: join("/", viteManifest[entryKey]!.file), + assets: await this.getAssets(entryKey), + }; + } - return json; - }, - } satisfies StartManifest & { - json(): Promise>; - path(id: string): string; - }; + return json; + }, + } satisfies StartManifest & { + json(): Promise>; + path(id: string): string; + }; } function createHtmlTagsForAssets(assets: string[]) { - return assets - .filter( - (asset) => - asset.endsWith(".css") || - asset.endsWith(".js") || - asset.endsWith(".ts") || - asset.endsWith(".mjs"), - ) - .map((asset) => ({ - tag: "link", - attrs: { - href: '/' + asset, - key: asset, - ...(asset.endsWith(".css") - ? { rel: "stylesheet" } - : { rel: "modulepreload" }), - }, - })); + return assets + .filter( + asset => + asset.endsWith(".css") || + asset.endsWith(".js") || + asset.endsWith(".ts") || + asset.endsWith(".mjs"), + ) + .map(asset => ({ + tag: "link", + attrs: { + href: "/" + asset, + key: asset, + ...(asset.endsWith(".css") ? { rel: "stylesheet" } : { rel: "modulepreload" }), + }, + })); } const entryId = import.meta.env.START_CLIENT_ENTRY.slice(2); let entryImports: string[] | undefined = undefined; function findAssetsInViteManifest( - manifest: Manifest, - id: string, - assetMap = new Map(), - stack: string[] = [], + manifest: Manifest, + id: string, + assetMap = new Map(), + stack: string[] = [], ) { - if (stack.includes(id)) { - return []; - } + if (stack.includes(id)) { + return []; + } - const cached = assetMap.get(id); - if (cached) { - return cached; - } - const chunk = manifest[id]; - if (!chunk) { - return []; - } + const cached = assetMap.get(id); + if (cached) { + return cached; + } + const chunk = manifest[id]; + if (!chunk) { + return []; + } - if (!entryImports) { - entryImports = [ - entryId, - ...(manifest[entryId]?.imports ?? []) - ]; - } + if (!entryImports) { + entryImports = [entryId, ...(manifest[entryId]?.imports ?? [])]; + } - // Only include entry imports, if we are specifically crawling the entry - // Chunks (e.g. routes) that import something from entry, should not render entry css redundantly - const excludeEntryImports = id !== entryId; + // Only include entry imports, if we are specifically crawling the entry + // Chunks (e.g. routes) that import something from entry, should not render entry css redundantly + const excludeEntryImports = id !== entryId; - const assets = chunk.css?.filter(Boolean) || []; - if (chunk.imports) { - stack.push(id); - for (let i = 0, l = chunk.imports.length; i < l; i++) { - const importId = chunk.imports[i]; - if (!importId || (excludeEntryImports && entryImports.includes(importId))) continue; - assets.push( - ...findAssetsInViteManifest( - manifest, - importId, - assetMap, - stack, - ), - ); - } - stack.pop(); - } - assets.push(chunk.file); - const all = Array.from(new Set(assets)); - assetMap.set(id, all); + const assets = chunk.css?.filter(Boolean) || []; + if (chunk.imports) { + stack.push(id); + for (let i = 0, l = chunk.imports.length; i < l; i++) { + const importId = chunk.imports[i]; + if (!importId || (excludeEntryImports && entryImports.includes(importId))) continue; + assets.push(...findAssetsInViteManifest(manifest, importId, assetMap, stack)); + } + stack.pop(); + } + assets.push(chunk.file); + const all = Array.from(new Set(assets)); + assetMap.set(id, all); - return all; + return all; } diff --git a/packages/start/src/server/manifest/ssr-manifest.ts b/packages/start/src/server/manifest/ssr-manifest.ts index 8c83fe246..8dd622128 100644 --- a/packages/start/src/server/manifest/ssr-manifest.ts +++ b/packages/start/src/server/manifest/ssr-manifest.ts @@ -1,8 +1,10 @@ import { getSsrDevManifest } from "./dev-ssr-manifest.ts"; import { getSsrProdManifest } from "./prod-ssr-manifest.ts"; -export function getSsrManifest(target: "client" | "ssr"): ReturnType | ReturnType { - return import.meta.env.DEV ? getSsrDevManifest(target) : getSsrProdManifest(); +export function getSsrManifest( + target: "client" | "ssr", +): ReturnType | ReturnType { + return import.meta.env.DEV ? getSsrDevManifest(target) : getSsrProdManifest(); } export { getSsrManifest as getManifest }; diff --git a/packages/start/src/server/routes.ts b/packages/start/src/server/routes.ts index 0c6807908..0b44eb18d 100644 --- a/packages/start/src/server/routes.ts +++ b/packages/start/src/server/routes.ts @@ -5,134 +5,122 @@ import { createRouter } from "radix3"; import type { FetchEvent } from "./types.ts"; interface Route { - path: string; - id: string; - children?: Route[]; - page?: boolean; - $component?: any; - $HEAD?: any; - $GET?: any; - $POST?: any; - $PUT?: any; - $PATCH?: any; - $DELETE?: any; + path: string; + id: string; + children?: Route[]; + page?: boolean; + $component?: any; + $HEAD?: any; + $GET?: any; + $POST?: any; + $PUT?: any; + $PATCH?: any; + $DELETE?: any; } -export const pageRoutes = defineRoutes( - (fileRoutes as unknown as Route[]).filter((o) => o.page), -); +export const pageRoutes = defineRoutes((fileRoutes as unknown as Route[]).filter(o => o.page)); function defineRoutes(fileRoutes: Route[]) { - function processRoute( - routes: Route[], - route: Route, - id: string, - full: string, - ) { - const parentRoute = Object.values(routes).find((o) => { - return id.startsWith(o.id + "/"); - }); + function processRoute(routes: Route[], route: Route, id: string, full: string) { + const parentRoute = Object.values(routes).find(o => { + return id.startsWith(o.id + "/"); + }); - if (!parentRoute) { - routes.push({ - ...route, - id, - path: id.replace(/\([^)/]+\)/g, "").replace(/\/+/g, "/"), - }); - return routes; - } - processRoute( - parentRoute.children || (parentRoute.children = []), - route, - id.slice(parentRoute.id.length), - full, - ); + if (!parentRoute) { + routes.push({ + ...route, + id, + path: id.replace(/\([^)/]+\)/g, "").replace(/\/+/g, "/"), + }); + return routes; + } + processRoute( + parentRoute.children || (parentRoute.children = []), + route, + id.slice(parentRoute.id.length), + full, + ); - return routes; - } + return routes; + } - return fileRoutes - .sort((a, b) => a.path.length - b.path.length) - .reduce((prevRoutes: Route[], route) => { - return processRoute(prevRoutes, route, route.path, route.path); - }, []); + return fileRoutes + .sort((a, b) => a.path.length - b.path.length) + .reduce((prevRoutes: Route[], route) => { + return processRoute(prevRoutes, route, route.path, route.path); + }, []); } const router = createRouter({ - routes: (fileRoutes as unknown as Route[]).reduce( - (memo, route) => { - if (!containsHTTP(route)) return memo; - const path = route.path - .replace(/\([^)/]+\)/g, "") - .replace(/\/+/g, "/") - .replace(/\*([^/]*)/g, (_, m) => `**:${m}`) - .split("/") - .map((s) => - s.startsWith(":") || s.startsWith("*") ? s : encodeURIComponent(s), - ) - .join("/"); - if (/:[^/]*\?/g.test(path)) { - throw new Error( - `Optional parameters are not supported in API routes: ${path}`, - ); - } - if (memo[path]) { - throw new Error( - `Duplicate API routes for "${path}" found at "${memo[path]!.route.path}" and "${ - route.path - }"`, - ); - } - memo[path] = { route }; - return memo; - }, - {} as Record, - ), + routes: (fileRoutes as unknown as Route[]).reduce( + (memo, route) => { + if (!containsHTTP(route)) return memo; + const path = route.path + .replace(/\([^)/]+\)/g, "") + .replace(/\/+/g, "/") + .replace(/\*([^/]*)/g, (_, m) => `**:${m}`) + .split("/") + .map(s => (s.startsWith(":") || s.startsWith("*") ? s : encodeURIComponent(s))) + .join("/"); + if (/:[^/]*\?/g.test(path)) { + throw new Error(`Optional parameters are not supported in API routes: ${path}`); + } + if (memo[path]) { + throw new Error( + `Duplicate API routes for "${path}" found at "${memo[path]!.route.path}" and "${ + route.path + }"`, + ); + } + memo[path] = { route }; + return memo; + }, + {} as Record, + ), }); function containsHTTP(route: Route) { - return ( - route["$HEAD"] || - route["$GET"] || - route["$POST"] || - route["$PUT"] || - route["$PATCH"] || - route["$DELETE"] - ); + return ( + route["$HEAD"] || + route["$GET"] || + route["$POST"] || + route["$PUT"] || + route["$PATCH"] || + route["$DELETE"] + ); } export function matchAPIRoute( - path: string, - method: string, + path: string, + method: string, ): - | { - params?: Record; - handler: { - import: () => Promise Promise>>; - }; - isPage: boolean; - } - | undefined { - const match = router.lookup(path); - if (match && match.route) { - const route = match.route; + | { + params?: Record; + handler: { + import: () => Promise Promise>>; + }; + isPage: boolean; + } + | undefined { + const match = router.lookup(path); + if (match && match.route) { + const route = match.route; - // Find the appropriate handler for the HTTP method - const handler = - method === "HEAD" ? route.$HEAD || route.$GET : route[`$${method}`]; + // Find the appropriate handler for the HTTP method + const handler = method === "HEAD" ? route.$HEAD || route.$GET : route[`$${method}`]; - if (handler === undefined) return; + if (handler === undefined) return; - // Check if this is a page route - const isPage = route.page === true && route.$component !== undefined; + // Check if this is a page route + const isPage = route.page === true && route.$component !== undefined; - // Return comprehensive route information - return { - handler, - params: match.params, - isPage, - }; - } + // Return comprehensive route information + return { + handler, + params: match.params, + isPage, + }; + } - return undefined; + return undefined; } diff --git a/packages/start/src/server/server-fns-runtime.ts b/packages/start/src/server/server-fns-runtime.ts index f56fdad72..a287670b9 100644 --- a/packages/start/src/server/server-fns-runtime.ts +++ b/packages/start/src/server/server-fns-runtime.ts @@ -5,7 +5,7 @@ export function createServerReference(fn: Function, id: string) { if (typeof fn !== "function") throw new Error("Export from a 'use server' module must be a function"); let baseURL = import.meta.env.BASE_URL ?? "/"; - if(!baseURL.endsWith("/")) baseURL += "/" + if (!baseURL.endsWith("/")) baseURL += "/"; return new Proxy(fn, { get(target, prop, receiver) { @@ -20,12 +20,12 @@ export function createServerReference(fn: Function, id: string) { if (!ogEvt) throw new Error("Cannot call server function outside of a request"); const evt = { ...ogEvt }; evt.locals.serverFunctionMeta = { - id + id, }; evt.serverOnly = true; return provideRequestEvent(evt, () => { return fn.apply(thisArg, args); }); - } + }, }); } diff --git a/packages/start/src/server/server-functions-handler.ts b/packages/start/src/server/server-functions-handler.ts index 2466567d0..160672684 100644 --- a/packages/start/src/server/server-functions-handler.ts +++ b/packages/start/src/server/server-functions-handler.ts @@ -1,22 +1,18 @@ import { getServerFnById } from "solidstart:server-fn-manifest"; import { parseSetCookie } from "cookie-es"; import { type H3Event, parseCookies } from "h3"; +import { crossSerializeStream, fromJSON, getCrossReferenceHeader } from "seroval"; import { - crossSerializeStream, - fromJSON, - getCrossReferenceHeader, -} from "seroval"; -import { - CustomEventPlugin, - DOMExceptionPlugin, - EventPlugin, - FormDataPlugin, - HeadersPlugin, - ReadableStreamPlugin, - RequestPlugin, - ResponsePlugin, - URLPlugin, - URLSearchParamsPlugin, + CustomEventPlugin, + DOMExceptionPlugin, + EventPlugin, + FormDataPlugin, + HeadersPlugin, + ReadableStreamPlugin, + RequestPlugin, + ResponsePlugin, + URLPlugin, + URLSearchParamsPlugin, } from "seroval-plugins/web"; import { sharedConfig } from "solid-js"; import { renderToString } from "solid-js/web"; @@ -28,337 +24,316 @@ import type { FetchEvent, PageEvent } from "./types.ts"; import { getExpectedRedirectStatus } from "./util.ts"; function createChunk(data: string) { - const encodeData = new TextEncoder().encode(data); - const bytes = encodeData.length; - const baseHex = bytes.toString(16); - const totalHex = "00000000".substring(0, 8 - baseHex.length) + baseHex; // 32-bit - const head = new TextEncoder().encode(`;0x${totalHex};`); + const encodeData = new TextEncoder().encode(data); + const bytes = encodeData.length; + const baseHex = bytes.toString(16); + const totalHex = "00000000".substring(0, 8 - baseHex.length) + baseHex; // 32-bit + const head = new TextEncoder().encode(`;0x${totalHex};`); - const chunk = new Uint8Array(12 + bytes); - chunk.set(head); - chunk.set(encodeData, 12); - return chunk; + const chunk = new Uint8Array(12 + bytes); + chunk.set(head); + chunk.set(encodeData, 12); + return chunk; } function serializeToStream(id: string, value: any) { - return new ReadableStream({ - start(controller) { - crossSerializeStream(value, { - scopeId: id, - plugins: [ - CustomEventPlugin, - DOMExceptionPlugin, - EventPlugin, - FormDataPlugin, - HeadersPlugin, - ReadableStreamPlugin, - RequestPlugin, - ResponsePlugin, - URLSearchParamsPlugin, - URLPlugin, - ], - onSerialize(data: string, initial: boolean) { - controller.enqueue( - createChunk( - initial ? `(${getCrossReferenceHeader(id)},${data})` : data, - ), - ); - }, - onDone() { - controller.close(); - }, - onError(error: any) { - controller.error(error); - }, - }); - }, - }); + return new ReadableStream({ + start(controller) { + crossSerializeStream(value, { + scopeId: id, + plugins: [ + CustomEventPlugin, + DOMExceptionPlugin, + EventPlugin, + FormDataPlugin, + HeadersPlugin, + ReadableStreamPlugin, + RequestPlugin, + ResponsePlugin, + URLSearchParamsPlugin, + URLPlugin, + ], + onSerialize(data: string, initial: boolean) { + controller.enqueue( + createChunk(initial ? `(${getCrossReferenceHeader(id)},${data})` : data), + ); + }, + onDone() { + controller.close(); + }, + onError(error: any) { + controller.error(error); + }, + }); + }, + }); } export async function handleServerFunction(h3Event: H3Event) { - const event = getFetchEvent(h3Event); - const request = event.request; + const event = getFetchEvent(h3Event); + const request = event.request; - const serverReference = request.headers.get("X-Server-Id"); - const instance = request.headers.get("X-Server-Instance"); - const singleFlight = request.headers.has("X-Single-Flight"); - const url = new URL(request.url); - let functionId: string | undefined | null; - if (serverReference) { - // invariant(typeof serverReference === "string", "Invalid server function"); - [functionId] = serverReference.split("#"); - } else { - functionId = url.searchParams.get("id"); + const serverReference = request.headers.get("X-Server-Id"); + const instance = request.headers.get("X-Server-Instance"); + const singleFlight = request.headers.has("X-Single-Flight"); + const url = new URL(request.url); + let functionId: string | undefined | null; + if (serverReference) { + // invariant(typeof serverReference === "string", "Invalid server function"); + [functionId] = serverReference.split("#"); + } else { + functionId = url.searchParams.get("id"); - if (!functionId) { - return process.env.NODE_ENV === "development" - ? new Response("Server function not found", { status: 404 }) - : new Response(null, { status: 404 }); - } - } + if (!functionId) { + return process.env.NODE_ENV === "development" + ? new Response("Server function not found", { status: 404 }) + : new Response(null, { status: 404 }); + } + } - const serverFunction = await getServerFnById(functionId!); + const serverFunction = await getServerFnById(functionId!); - let parsed: any[] = []; + let parsed: any[] = []; - // grab bound arguments from url when no JS - if (!instance || h3Event.method === "GET") { - const args = url.searchParams.get("args"); - if (args) { - const json = JSON.parse(args); - (json.t - ? (fromJSON(json, { - plugins: [ - CustomEventPlugin, - DOMExceptionPlugin, - EventPlugin, - FormDataPlugin, - HeadersPlugin, - ReadableStreamPlugin, - RequestPlugin, - ResponsePlugin, - URLSearchParamsPlugin, - URLPlugin, - ], - }) as any) - : json - ).forEach((arg: any) => { - parsed.push(arg); - }); - } - } - if (h3Event.method === "POST") { - const contentType = request.headers.get("content-type"); + // grab bound arguments from url when no JS + if (!instance || h3Event.method === "GET") { + const args = url.searchParams.get("args"); + if (args) { + const json = JSON.parse(args); + (json.t + ? (fromJSON(json, { + plugins: [ + CustomEventPlugin, + DOMExceptionPlugin, + EventPlugin, + FormDataPlugin, + HeadersPlugin, + ReadableStreamPlugin, + RequestPlugin, + ResponsePlugin, + URLSearchParamsPlugin, + URLPlugin, + ], + }) as any) + : json + ).forEach((arg: any) => { + parsed.push(arg); + }); + } + } + if (h3Event.method === "POST") { + const contentType = request.headers.get("content-type"); - if ( - contentType?.startsWith("multipart/form-data") || - contentType?.startsWith("application/x-www-form-urlencoded") - ) { - parsed.push(await event.request.formData()); - } else if (contentType?.startsWith("application/json")) { - parsed = fromJSON(await event.request.json(), { - plugins: [ - CustomEventPlugin, - DOMExceptionPlugin, - EventPlugin, - FormDataPlugin, - HeadersPlugin, - ReadableStreamPlugin, - RequestPlugin, - ResponsePlugin, - URLSearchParamsPlugin, - URLPlugin, - ], - }); - } - } - try { - let result = await provideRequestEvent(event, async () => { - /* @ts-expect-error */ - sharedConfig.context = { event }; - event.locals.serverFunctionMeta = { - id: functionId - }; - return serverFunction(...parsed); - }); + if ( + contentType?.startsWith("multipart/form-data") || + contentType?.startsWith("application/x-www-form-urlencoded") + ) { + parsed.push(await event.request.formData()); + } else if (contentType?.startsWith("application/json")) { + parsed = fromJSON(await event.request.json(), { + plugins: [ + CustomEventPlugin, + DOMExceptionPlugin, + EventPlugin, + FormDataPlugin, + HeadersPlugin, + ReadableStreamPlugin, + RequestPlugin, + ResponsePlugin, + URLSearchParamsPlugin, + URLPlugin, + ], + }); + } + } + try { + let result = await provideRequestEvent(event, async () => { + /* @ts-expect-error */ + sharedConfig.context = { event }; + event.locals.serverFunctionMeta = { + id: functionId, + }; + return serverFunction(...parsed); + }); - if (singleFlight && instance) { - result = await handleSingleFlight(event, result); - } + if (singleFlight && instance) { + result = await handleSingleFlight(event, result); + } - // handle responses - if (result instanceof Response) { - if (result.headers && result.headers.has("X-Content-Raw")) return result; - if (instance) { - // forward headers - if (result.headers) mergeResponseHeaders(h3Event, result.headers); - // forward non-redirect statuses - if (result.status && (result.status < 300 || result.status >= 400)) - h3Event.res.status = result.status; - if ((result as any).customBody) { - result = await (result as any).customBody(); - } else if (result.body == undefined) result = null; - } - } + // handle responses + if (result instanceof Response) { + if (result.headers && result.headers.has("X-Content-Raw")) return result; + if (instance) { + // forward headers + if (result.headers) mergeResponseHeaders(h3Event, result.headers); + // forward non-redirect statuses + if (result.status && (result.status < 300 || result.status >= 400)) + h3Event.res.status = result.status; + if ((result as any).customBody) { + result = await (result as any).customBody(); + } else if (result.body == undefined) result = null; + } + } - // handle no JS success case - if (!instance) return handleNoJS(result, request, parsed); + // handle no JS success case + if (!instance) return handleNoJS(result, request, parsed); - h3Event.res.headers.set("content-type", "text/javascript"); + h3Event.res.headers.set("content-type", "text/javascript"); - return serializeToStream(instance, result); - } catch (x) { - if (x instanceof Response) { - if (singleFlight && instance) { - x = await handleSingleFlight(event, x); - } - // forward headers - if ((x as any).headers) mergeResponseHeaders(h3Event, (x as any).headers); - // forward non-redirect statuses - if ( - (x as any).status && - (!instance || (x as any).status < 300 || (x as any).status >= 400) - ) - h3Event.res.status = (x as any).status; - if ((x as any).customBody) { - x = (x as any).customBody(); - } else if ((x as any).body === undefined) x = null; - h3Event.res.headers.set("X-Error", "true"); - } else if (instance) { - const error = - x instanceof Error ? x.message : typeof x === "string" ? x : "true"; + return serializeToStream(instance, result); + } catch (x) { + if (x instanceof Response) { + if (singleFlight && instance) { + x = await handleSingleFlight(event, x); + } + // forward headers + if ((x as any).headers) mergeResponseHeaders(h3Event, (x as any).headers); + // forward non-redirect statuses + if ((x as any).status && (!instance || (x as any).status < 300 || (x as any).status >= 400)) + h3Event.res.status = (x as any).status; + if ((x as any).customBody) { + x = (x as any).customBody(); + } else if ((x as any).body === undefined) x = null; + h3Event.res.headers.set("X-Error", "true"); + } else if (instance) { + const error = x instanceof Error ? x.message : typeof x === "string" ? x : "true"; - h3Event.res.headers.set("X-Error", error.replace(/[\r\n]+/g, "")); - } else { - x = handleNoJS(x, request, parsed, true); - } - if (instance) { - h3Event.res.headers.set("content-type", "text/javascript"); - return serializeToStream(instance, x); - } - return x; - } + h3Event.res.headers.set("X-Error", error.replace(/[\r\n]+/g, "")); + } else { + x = handleNoJS(x, request, parsed, true); + } + if (instance) { + h3Event.res.headers.set("content-type", "text/javascript"); + return serializeToStream(instance, x); + } + return x; + } } -function handleNoJS( - result: any, - request: Request, - parsed: any[], - thrown?: boolean, -) { - const url = new URL(request.url); - const isError = result instanceof Error; - let statusCode = 302; - let headers: Headers; - if (result instanceof Response) { - headers = new Headers(result.headers); - if (result.headers.has("Location")) { - headers.set( - `Location`, - new URL( - result.headers.get("Location")!, - url.origin + import.meta.env.BASE_URL, - ).toString(), - ); - statusCode = getExpectedRedirectStatus(result); - } - } else - headers = new Headers({ - Location: new URL(request.headers.get("referer")!).toString(), - }); - if (result) { - headers.append( - "Set-Cookie", - `flash=${encodeURIComponent( - JSON.stringify({ - url: url.pathname + url.search, - result: isError ? result.message : result, - thrown: thrown, - error: isError, - input: [ - ...parsed.slice(0, -1), - [...parsed[parsed.length - 1].entries()], - ], - }), - )}; Secure; HttpOnly;`, - ); - } - return new Response(null, { - status: statusCode, - headers, - }); +function handleNoJS(result: any, request: Request, parsed: any[], thrown?: boolean) { + const url = new URL(request.url); + const isError = result instanceof Error; + let statusCode = 302; + let headers: Headers; + if (result instanceof Response) { + headers = new Headers(result.headers); + if (result.headers.has("Location")) { + headers.set( + `Location`, + new URL(result.headers.get("Location")!, url.origin + import.meta.env.BASE_URL).toString(), + ); + statusCode = getExpectedRedirectStatus(result); + } + } else + headers = new Headers({ + Location: new URL(request.headers.get("referer")!).toString(), + }); + if (result) { + headers.append( + "Set-Cookie", + `flash=${encodeURIComponent( + JSON.stringify({ + url: url.pathname + url.search, + result: isError ? result.message : result, + thrown: thrown, + error: isError, + input: [...parsed.slice(0, -1), [...parsed[parsed.length - 1].entries()]], + }), + )}; Secure; HttpOnly;`, + ); + } + return new Response(null, { + status: statusCode, + headers, + }); } let App: any; function createSingleFlightHeaders(sourceEvent: FetchEvent) { - // cookie handling logic is pretty simplistic so this might be imperfect - // unclear if h3 internals are available on all platforms but we need a way to - // update request headers on the underlying H3 event. + // cookie handling logic is pretty simplistic so this might be imperfect + // unclear if h3 internals are available on all platforms but we need a way to + // update request headers on the underlying H3 event. - const headers = sourceEvent.request.headers; - const cookies = parseCookies(sourceEvent.nativeEvent); - const SetCookies = sourceEvent.response.headers.getSetCookie(); - headers.delete("cookie"); - // let useH3Internals = false; - // if (sourceEvent.nativeEvent.node?.req) { - // useH3Internals = true; - // sourceEvent.nativeEvent.node.req.headers.cookie = ""; - // } - SetCookies.forEach((cookie) => { - if (!cookie) return; - const { maxAge, expires, name, value } = parseSetCookie(cookie); - if (maxAge != null && maxAge <= 0) { - delete cookies[name]; - return; - } - if (expires != null && expires.getTime() <= Date.now()) { - delete cookies[name]; - return; - } - cookies[name] = value; - }); - Object.entries(cookies).forEach(([key, value]) => { - headers.append("cookie", `${key}=${value}`); - // useH3Internals && - // (sourceEvent.nativeEvent.node.req.headers.cookie += `${key}=${value};`); - }); + const headers = sourceEvent.request.headers; + const cookies = parseCookies(sourceEvent.nativeEvent); + const SetCookies = sourceEvent.response.headers.getSetCookie(); + headers.delete("cookie"); + // let useH3Internals = false; + // if (sourceEvent.nativeEvent.node?.req) { + // useH3Internals = true; + // sourceEvent.nativeEvent.node.req.headers.cookie = ""; + // } + SetCookies.forEach(cookie => { + if (!cookie) return; + const { maxAge, expires, name, value } = parseSetCookie(cookie); + if (maxAge != null && maxAge <= 0) { + delete cookies[name]; + return; + } + if (expires != null && expires.getTime() <= Date.now()) { + delete cookies[name]; + return; + } + cookies[name] = value; + }); + Object.entries(cookies).forEach(([key, value]) => { + headers.append("cookie", `${key}=${value}`); + // useH3Internals && + // (sourceEvent.nativeEvent.node.req.headers.cookie += `${key}=${value};`); + }); - return headers; + return headers; } -async function handleSingleFlight( - sourceEvent: FetchEvent, - result: any, -): Promise { - let revalidate: string[]; - let url = new URL(sourceEvent.request.headers.get("referer")!).toString(); - if (result instanceof Response) { - if (result.headers.has("X-Revalidate")) - revalidate = result.headers.get("X-Revalidate")!.split(","); - if (result.headers.has("Location")) - url = new URL( - result.headers.get("Location")!, - new URL(sourceEvent.request.url).origin + - import.meta.env.BASE_URL, - ).toString(); - } - const event = { ...sourceEvent } as PageEvent; - event.request = new Request(url, { - headers: createSingleFlightHeaders(sourceEvent), - }); - return await provideRequestEvent(event, async () => { - await createPageEvent(event); - App || (App = (await import("solid-start:app")).default); - /* @ts-expect-error */ - event.router.dataOnly = revalidate || true; - /* @ts-expect-error */ - event.router.previousUrl = sourceEvent.request.headers.get("referer"); - try { - renderToString(() => { - /* @ts-expect-error */ - sharedConfig.context.event = event; - App(); - }); - } catch (e) { - console.log(e); - } +async function handleSingleFlight(sourceEvent: FetchEvent, result: any): Promise { + let revalidate: string[]; + let url = new URL(sourceEvent.request.headers.get("referer")!).toString(); + if (result instanceof Response) { + if (result.headers.has("X-Revalidate")) + revalidate = result.headers.get("X-Revalidate")!.split(","); + if (result.headers.has("Location")) + url = new URL( + result.headers.get("Location")!, + new URL(sourceEvent.request.url).origin + import.meta.env.BASE_URL, + ).toString(); + } + const event = { ...sourceEvent } as PageEvent; + event.request = new Request(url, { + headers: createSingleFlightHeaders(sourceEvent), + }); + return await provideRequestEvent(event, async () => { + await createPageEvent(event); + App || (App = (await import("solid-start:app")).default); + /* @ts-expect-error */ + event.router.dataOnly = revalidate || true; + /* @ts-expect-error */ + event.router.previousUrl = sourceEvent.request.headers.get("referer"); + try { + renderToString(() => { + /* @ts-expect-error */ + sharedConfig.context.event = event; + App(); + }); + } catch (e) { + console.log(e); + } - /* @ts-expect-error */ - const body = event.router.data; - if (!body) return result; - let containsKey = false; - for (const key in body) { - if (body[key] === undefined) delete body[key]; - else containsKey = true; - } - if (!containsKey) return result; - if (!(result instanceof Response)) { - body["_$value"] = result; - result = new Response(null, { status: 200 }); - } else if ((result as any).customBody) { - body["_$value"] = (result as any).customBody(); - } - result.customBody = () => body; - result.headers.set("X-Single-Flight", "true"); - return result; - }); + /* @ts-expect-error */ + const body = event.router.data; + if (!body) return result; + let containsKey = false; + for (const key in body) { + if (body[key] === undefined) delete body[key]; + else containsKey = true; + } + if (!containsKey) return result; + if (!(result instanceof Response)) { + body["_$value"] = result; + result = new Response(null, { status: 200 }); + } else if ((result as any).customBody) { + body["_$value"] = (result as any).customBody(); + } + result.customBody = () => body; + result.headers.set("X-Single-Flight", "true"); + return result; + }); } diff --git a/packages/start/src/server/server-runtime.ts b/packages/start/src/server/server-runtime.ts index 0af198aea..8bbabe40b 100644 --- a/packages/start/src/server/server-runtime.ts +++ b/packages/start/src/server/server-runtime.ts @@ -11,7 +11,7 @@ import { RequestPlugin, ResponsePlugin, URLPlugin, - URLSearchParamsPlugin + URLSearchParamsPlugin, } from "seroval-plugins/web"; import { type Component } from "solid-js"; @@ -46,7 +46,7 @@ class SerovalChunkReader { if (this.done) { return { done: true, - value: undefined + value: undefined, }; } // Otherwise, read a new chunk @@ -77,7 +77,7 @@ class SerovalChunkReader { // Deserialize the chunk return { done: false, - value: deserialize(partial) + value: deserialize(partial), }; } @@ -107,7 +107,7 @@ async function deserializeStream(id: string, response: Response) { }, () => { // no-op - } + }, ); } @@ -123,8 +123,8 @@ function createRequest(base: string, id: string, instance: string, options: Requ headers: { ...options.headers, "X-Server-Id": id, - "X-Server-Instance": instance - } + "X-Server-Instance": instance, + }, }); } @@ -138,14 +138,14 @@ const plugins = [ RequestPlugin, ResponsePlugin, URLSearchParamsPlugin, - URLPlugin + URLPlugin, ]; async function fetchServerFunction( base: string, id: string, options: Omit, - args: any[] + args: any[], ) { const instance = `server-fn:${INSTANCE++}`; const response = await (args.length === 0 @@ -156,12 +156,12 @@ async function fetchServerFunction( ? createRequest(base, id, instance, { ...options, body: args[0], - headers: { ...options.headers, "Content-Type": "application/x-www-form-urlencoded" } + headers: { ...options.headers, "Content-Type": "application/x-www-form-urlencoded" }, }) : createRequest(base, id, instance, { ...options, body: JSON.stringify(await Promise.resolve(toJSONAsync(args, { plugins }))), - headers: { ...options.headers, "Content-Type": "application/json" } + headers: { ...options.headers, "Content-Type": "application/json" }, })); if ( @@ -195,7 +195,7 @@ async function fetchServerFunction( export function createServerReference(id: string) { let baseURL = import.meta.env.BASE_URL ?? "/"; - if(!baseURL.endsWith("/")) baseURL += "/" + if (!baseURL.endsWith("/")) baseURL += "/"; const fn = (...args: any[]) => fetchServerFunction(`${baseURL}_server`, id, {}, args); @@ -217,13 +217,13 @@ export function createServerReference(id: string) { ? url + (args.length ? `&args=${encodeURIComponent( - JSON.stringify(await Promise.resolve(toJSONAsync(args, { plugins }))) + JSON.stringify(await Promise.resolve(toJSONAsync(args, { plugins }))), )}` : "") : `${baseURL}_server`, id, options, - encodeArgs ? [] : args + encodeArgs ? [] : args, ); }; fn.url = url; diff --git a/packages/start/src/server/spa/handler.ts b/packages/start/src/server/spa/handler.ts index a9234d0c8..baf9ebd53 100644 --- a/packages/start/src/server/spa/handler.ts +++ b/packages/start/src/server/spa/handler.ts @@ -9,21 +9,21 @@ import type { FetchEvent, HandlerOptions, PageEvent } from "../types.ts"; * Read more: https://docs.solidjs.com/solid-start/reference/server/create-handler */ export function createHandler( - fn: (context: PageEvent) => JSX.Element, - options?: HandlerOptions | ((context: PageEvent) => HandlerOptions), + fn: (context: PageEvent) => JSX.Element, + options?: HandlerOptions | ((context: PageEvent) => HandlerOptions), ) { - return createBaseHandler(createPageEvent, fn, options); + return createBaseHandler(createPageEvent, fn, options); } async function createPageEvent(ctx: FetchEvent) { - const manifest = getSsrManifest("ssr"); - const pageEvent: PageEvent = Object.assign(ctx, { - manifest: "json" in manifest ? await manifest.json() : {}, - assets: await manifest.getAssets(import.meta.env.START_CLIENT_ENTRY), - routes: [], - complete: false, - $islands: new Set(), - }); + const manifest = getSsrManifest("ssr"); + const pageEvent: PageEvent = Object.assign(ctx, { + manifest: "json" in manifest ? await manifest.json() : {}, + assets: await manifest.getAssets(import.meta.env.START_CLIENT_ENTRY), + routes: [], + complete: false, + $islands: new Set(), + }); - return pageEvent; + return pageEvent; } diff --git a/packages/start/src/server/types.ts b/packages/start/src/server/types.ts index 52998b597..b59467f78 100644 --- a/packages/start/src/server/types.ts +++ b/packages/start/src/server/types.ts @@ -5,78 +5,78 @@ import type { RequestEvent } from "solid-js/web"; // export const FETCH_EVENT = "$FETCH"; export type DocumentComponentProps = { - assets?: JSX.Element; - scripts: JSX.Element; - children?: JSX.Element; + assets?: JSX.Element; + scripts: JSX.Element; + children?: JSX.Element; }; export type Asset = - | { - tag: "style"; - attrs: JSX.StyleHTMLAttributes & { key?: string }; - children?: JSX.Element; - } - | { - tag: "script"; - attrs: JSX.ScriptHTMLAttributes & { key?: string }; - } - | { - tag: "link"; - attrs: JSX.LinkHTMLAttributes & { key?: string }; - }; + | { + tag: "style"; + attrs: JSX.StyleHTMLAttributes & { key?: string }; + children?: JSX.Element; + } + | { + tag: "script"; + attrs: JSX.ScriptHTMLAttributes & { key?: string }; + } + | { + tag: "link"; + attrs: JSX.LinkHTMLAttributes & { key?: string }; + }; export type HandlerOptions = { - mode?: "sync" | "async" | "stream"; - nonce?: string; - renderId?: string; - onCompleteAll?: (options: { write: (v: any) => void }) => void; - onCompleteShell?: (options: { write: (v: any) => void }) => void; + mode?: "sync" | "async" | "stream"; + nonce?: string; + renderId?: string; + onCompleteAll?: (options: { write: (v: any) => void }) => void; + onCompleteShell?: (options: { write: (v: any) => void }) => void; }; export type ContextMatches = { - originalPath: string; - pattern: string; - path: string; - params: unknown; + originalPath: string; + pattern: string; + path: string; + params: unknown; }; export interface ResponseStub { - status?: number; - statusText?: string; - headers: Headers; + status?: number; + statusText?: string; + headers: Headers; } export interface FetchEvent { - request: Request; - response: ResponseStub; - clientAddress?: string; - locals: App.RequestEventLocals; - nativeEvent: H3Event; + request: Request; + response: ResponseStub; + clientAddress?: string; + locals: App.RequestEventLocals; + nativeEvent: H3Event; } export interface PageEvent extends RequestEvent { - assets: any[]; - routes: any[]; - // prevUrl: string | null; - // $type: typeof FETCH_EVENT; - $islands: Set; - complete: boolean; - nonce?: string; - // mutation: boolean; + assets: any[]; + routes: any[]; + // prevUrl: string | null; + // $type: typeof FETCH_EVENT; + $islands: Set; + complete: boolean; + nonce?: string; + // mutation: boolean; } export interface APIEvent extends FetchEvent { - params: { [key: string]: string }; + params: { [key: string]: string }; } export type APIHandler = (event: APIEvent) => Promise; export interface ServerFunctionMeta { - id: string; + id: string; } declare module "solid-js/web" { - interface RequestEvent extends FetchEvent { - serverOnly?: boolean; - } + interface RequestEvent extends FetchEvent { + serverOnly?: boolean; + } } diff --git a/packages/start/src/server/util.ts b/packages/start/src/server/util.ts index 5aa7f0d07..da325bf49 100644 --- a/packages/start/src/server/util.ts +++ b/packages/start/src/server/util.ts @@ -4,9 +4,9 @@ import type { ResponseStub } from "./types.ts"; const validRedirectStatuses = new Set([301, 302, 303, 307, 308]); export function getExpectedRedirectStatus(response: ResponseStub): number { - if (response.status && validRedirectStatuses.has(response.status)) { - return response.status; - } + if (response.status && validRedirectStatuses.has(response.status)) { + return response.status; + } - return 302; + return 302; } diff --git a/packages/start/src/shared/ErrorBoundary.tsx b/packages/start/src/shared/ErrorBoundary.tsx index cc3f84217..6be12348e 100644 --- a/packages/start/src/shared/ErrorBoundary.tsx +++ b/packages/start/src/shared/ErrorBoundary.tsx @@ -37,7 +37,7 @@ export const TopErrorBoundary = (props: ParentProps) => { err => { console.error(err); isError = !!err; - } + }, ); return isError ? ( <> diff --git a/packages/start/src/shared/HttpHeader.tsx b/packages/start/src/shared/HttpHeader.tsx index 30e061a4c..6dcb504b6 100644 --- a/packages/start/src/shared/HttpHeader.tsx +++ b/packages/start/src/shared/HttpHeader.tsx @@ -17,26 +17,26 @@ export interface HttpHeaderProps { */ export const HttpHeader = isServer ? (props: HttpHeaderProps) => { - const event = getRequestEvent() as PageEvent; + const event = getRequestEvent() as PageEvent; - if (props.append) appendHeader(props.name, props.value); - else setHeader(props.name, props.value); + if (props.append) appendHeader(props.name, props.value); + else setHeader(props.name, props.value); - onCleanup(() => { - // @ts-expect-error - if (event.nativeEvent.handled || event.complete) return; - const value = event.response.headers.get(props.name); - if (!value) return; - if (!value.includes(", ")) { - if (value === props.value) event.response.headers.delete(props.name); - return; - } - const values = value.split(", "); - const index = values.indexOf(props.value); - index !== -1 && values.splice(index, 1); - if (values.length) event.response.headers.set(props.name, values.join(",")); - else event.response.headers.delete(props.name); - }); - return null; - } + onCleanup(() => { + // @ts-expect-error + if (event.nativeEvent.handled || event.complete) return; + const value = event.response.headers.get(props.name); + if (!value) return; + if (!value.includes(", ")) { + if (value === props.value) event.response.headers.delete(props.name); + return; + } + const values = value.split(", "); + const index = values.indexOf(props.value); + index !== -1 && values.splice(index, 1); + if (values.length) event.response.headers.set(props.name, values.join(",")); + else event.response.headers.delete(props.name); + }); + return null; + } : (_props: HttpHeaderProps) => null; diff --git a/packages/start/src/shared/HttpStatusCode.ts b/packages/start/src/shared/HttpStatusCode.ts index b378f93cb..56a86b06d 100644 --- a/packages/start/src/shared/HttpStatusCode.ts +++ b/packages/start/src/shared/HttpStatusCode.ts @@ -4,8 +4,8 @@ import { getRequestEvent, isServer } from "solid-js/web"; import type { PageEvent } from "../server/types.ts"; export interface HttpStatusCodeProps { - code: number; - text?: string; + code: number; + text?: string; } /** @@ -13,15 +13,15 @@ export interface HttpStatusCodeProps { * Read more: https://docs.solidjs.com/solid-start/reference/server/http-status-code */ export const HttpStatusCode = isServer - ? (props: HttpStatusCodeProps) => { - const event = getRequestEvent() as PageEvent; - event.response.status = props.code; - event.response.statusText = props.text; - onCleanup( - () => - // !event.nativeEvent.handled && - !event.complete && (event.response.status = 200), - ); - return null; - } - : (_props: HttpStatusCodeProps) => null; + ? (props: HttpStatusCodeProps) => { + const event = getRequestEvent() as PageEvent; + event.response.status = props.code; + event.response.statusText = props.text; + onCleanup( + () => + // !event.nativeEvent.handled && + !event.complete && (event.response.status = 200), + ); + return null; + } + : (_props: HttpStatusCodeProps) => null; diff --git a/packages/start/src/shared/clientOnly.ts b/packages/start/src/shared/clientOnly.ts index bee0711db..899a1d545 100644 --- a/packages/start/src/shared/clientOnly.ts +++ b/packages/start/src/shared/clientOnly.ts @@ -12,7 +12,7 @@ export default function clientOnly>( fn: () => Promise<{ default: T; }>, - options: { lazy?: boolean } = {} + options: { lazy?: boolean } = {}, ) { if (isServer) return (props: ComponentProps & { fallback?: JSX.Element }) => props.fallback; @@ -29,7 +29,7 @@ export default function clientOnly>( return createMemo( () => ( (Comp = comp()), (m = mounted()), untrack(() => (Comp && m ? Comp(rest) : props.fallback)) - ) + ), ); }; } @@ -38,7 +38,7 @@ function load( fn: () => Promise<{ default: T; }>, - setComp: Setter + setComp: Setter, ) { fn().then(m => setComp(() => m.default)); } diff --git a/packages/start/src/shared/dev-overlay/CodeView.tsx b/packages/start/src/shared/dev-overlay/CodeView.tsx index 3116ce5e5..5af80976a 100644 --- a/packages/start/src/shared/dev-overlay/CodeView.tsx +++ b/packages/start/src/shared/dev-overlay/CodeView.tsx @@ -1,31 +1,24 @@ // @refresh skip -import { getSingletonHighlighter, type BuiltinLanguage, type Highlighter } from 'shiki'; -import { loadWasm } from 'shiki/engine/oniguruma'; -import { createEffect, createResource, type JSX } from 'solid-js'; +import { getSingletonHighlighter, type BuiltinLanguage, type Highlighter } from "shiki"; +import { loadWasm } from "shiki/engine/oniguruma"; +import { createEffect, createResource, type JSX } from "solid-js"; -import url from 'shiki/onig.wasm?url'; +import url from "shiki/onig.wasm?url"; -import langJS from 'shiki/langs/javascript.mjs'; -import langJSX from 'shiki/langs/jsx.mjs'; -import langTSX from 'shiki/langs/tsx.mjs'; -import langTS from 'shiki/langs/typescript.mjs'; -import darkPlus from 'shiki/themes/dark-plus.mjs'; +import langJS from "shiki/langs/javascript.mjs"; +import langJSX from "shiki/langs/jsx.mjs"; +import langTSX from "shiki/langs/tsx.mjs"; +import langTS from "shiki/langs/typescript.mjs"; +import darkPlus from "shiki/themes/dark-plus.mjs"; let HIGHLIGHTER: Highlighter; async function loadHighlighter() { if (!HIGHLIGHTER) { - await loadWasm(await fetch(url)) + await loadWasm(await fetch(url)); HIGHLIGHTER = await getSingletonHighlighter({ - themes: [ - darkPlus, - ], - langs: [ - langJS, - langJSX, - langTS, - langTSX, - ], + themes: [darkPlus], + langs: [langJS, langJSX, langTS, langTSX], }); } return HIGHLIGHTER; @@ -41,7 +34,7 @@ const RANGE = 8; export function CodeView(props: CodeViewProps): JSX.Element | null { const lines = () => - props.content.split('\n').map((item, index) => ({ + props.content.split("\n").map((item, index) => ({ index: index + 1, line: item, })); @@ -51,27 +44,25 @@ export function CodeView(props: CodeViewProps): JSX.Element | null { let ref: HTMLDivElement | undefined; - const [data] = createResource(() => ( - lines() - .slice(minLine(), maxLine()) - .map(item => item.line) - .join('\n') - ) ,async (value) => { - const highlighter = await loadHighlighter(); - const fileExtension = props.fileName - .split(/[#?]/)[0]! - .split('.') - .pop() - ?.trim(); - let lang = fileExtension as BuiltinLanguage; - if (fileExtension === 'mjs' || fileExtension === 'cjs') { - lang = 'js'; - } - return highlighter.codeToHtml(value, { - theme: 'dark-plus', - lang, - }); - }); + const [data] = createResource( + () => + lines() + .slice(minLine(), maxLine()) + .map(item => item.line) + .join("\n"), + async value => { + const highlighter = await loadHighlighter(); + const fileExtension = props.fileName.split(/[#?]/)[0]!.split(".").pop()?.trim(); + let lang = fileExtension as BuiltinLanguage; + if (fileExtension === "mjs" || fileExtension === "cjs") { + lang = "js"; + } + return highlighter.codeToHtml(value, { + theme: "dark-plus", + lang, + }); + }, + ); createEffect(() => { const result = data(); @@ -82,14 +73,20 @@ export function CodeView(props: CodeViewProps): JSX.Element | null { for (let i = 0, len = lines.length; i < len; i++) { const el = lines[i] as HTMLElement; - if ((props.line - minLine() - 1) === i) { - el.classList.add('dev-overlay-error-line'); + if (props.line - minLine() - 1 === i) { + el.classList.add("dev-overlay-error-line"); } } } }); - return
    ; + return ( +
    + ); } diff --git a/packages/start/src/shared/dev-overlay/DevOverlayDialog.tsx b/packages/start/src/shared/dev-overlay/DevOverlayDialog.tsx index 4d10d90d0..e3b836d7a 100644 --- a/packages/start/src/shared/dev-overlay/DevOverlayDialog.tsx +++ b/packages/start/src/shared/dev-overlay/DevOverlayDialog.tsx @@ -19,7 +19,7 @@ import { RefreshIcon, SolidStartIcon, ViewCompiledIcon, - ViewOriginalIcon + ViewOriginalIcon, } from "./icons.tsx"; import "./styles.css"; @@ -134,7 +134,7 @@ function StackFramesContent(props: StackFramesContentProps) { content: "", line: current.getLineNumber()!, column: current.getColumnNumber()!, - name: current.getFunctionName() + name: current.getFunctionName(), })}
    @@ -225,8 +225,8 @@ export default function DevOverlayDialog(props: DevOverlayDialogProps): JSX.Elem htmlToImage .toPng(current, { style: { - transform: "scale(0.75)" - } + transform: "scale(0.75)", + }, }) .then(url => { download(url, "start-screenshot.png"); diff --git a/packages/start/src/shared/dev-overlay/createStackFrame.ts b/packages/start/src/shared/dev-overlay/createStackFrame.ts index 7d5bd8e90..df03cc76f 100644 --- a/packages/start/src/shared/dev-overlay/createStackFrame.ts +++ b/packages/start/src/shared/dev-overlay/createStackFrame.ts @@ -2,76 +2,73 @@ import { type Accessor, createMemo, createResource } from "solid-js"; import getSourceMap from "./get-source-map.ts"; export interface StackFrameSource { - content: string; - source: string; - name?: string; - line: number; - column: number; + content: string; + source: string; + name?: string; + line: number; + column: number; } function getActualFileSource(path: string): string { - if (path.startsWith("file://")) { - return "/_build/@fs" + path.substring("file://".length); - } - return path; + if (path.startsWith("file://")) { + return "/_build/@fs" + path.substring("file://".length); + } + return path; } -export function createStackFrame( - stackframe: StackFrame, - isCompiled: () => boolean, -) { - const [data] = createResource( - () => ({ - fileName: stackframe.fileName, - line: stackframe.lineNumber, - column: stackframe.columnNumber, - functionName: stackframe.functionName, - }), - async (source) => { - if (!source.fileName) { - return null; - } - const response = await fetch(getActualFileSource(source.fileName)); - if (!response.ok) { - return null; - } - const content = await response.text(); - const sourceMap = await getSourceMap(source.fileName, content); - return { - source, - content, - sourceMap, - }; - }, - ); +export function createStackFrame(stackframe: StackFrame, isCompiled: () => boolean) { + const [data] = createResource( + () => ({ + fileName: stackframe.fileName, + line: stackframe.lineNumber, + column: stackframe.columnNumber, + functionName: stackframe.functionName, + }), + async source => { + if (!source.fileName) { + return null; + } + const response = await fetch(getActualFileSource(source.fileName)); + if (!response.ok) { + return null; + } + const content = await response.text(); + const sourceMap = await getSourceMap(source.fileName, content); + return { + source, + content, + sourceMap, + }; + }, + ); - const info = createMemo(() => { - const current = data(); - if (!current) { - return undefined; - } - const { source, content, sourceMap } = current; + const info = createMemo(() => { + const current = data(); + if (!current) { + return undefined; + } + const { source, content, sourceMap } = current; - if (!isCompiled() && source.line && source.column && sourceMap) { - const result = sourceMap.originalPositionFor({ - line: source.line, - column: source.column, - }); + if (!isCompiled() && source.line && source.column && sourceMap) { + const result = sourceMap.originalPositionFor({ + line: source.line, + column: source.column, + }); - return { - ...result, - content: sourceMap.sourceContentFor(result.source), - } as StackFrameSource; - } + return { + ...result, + content: sourceMap.sourceContentFor(result.source), + } as StackFrameSource; + } - return { - source: source.fileName, - line: source.line, - column: source.column, - name: source.functionName, - content, - } as StackFrameSource; - }); + return { + source: source.fileName, + line: source.line, + column: source.column, + name: source.functionName, + content, + } as StackFrameSource; + }); - return info as Accessor; + return info as Accessor; } diff --git a/packages/start/src/shared/dev-overlay/env.d.ts b/packages/start/src/shared/dev-overlay/env.d.ts index 8c54073db..ac68ef8f4 100644 --- a/packages/start/src/shared/dev-overlay/env.d.ts +++ b/packages/start/src/shared/dev-overlay/env.d.ts @@ -1,4 +1,4 @@ -declare module '*.json' { +declare module "*.json" { const data: Record; export default data; -} \ No newline at end of file +} diff --git a/packages/start/src/shared/dev-overlay/get-source-map.ts b/packages/start/src/shared/dev-overlay/get-source-map.ts index 2b8b164d6..0447a10d1 100644 --- a/packages/start/src/shared/dev-overlay/get-source-map.ts +++ b/packages/start/src/shared/dev-overlay/get-source-map.ts @@ -1,4 +1,4 @@ -import { RawSourceMap, SourceMapConsumer } from 'source-map-js'; +import { RawSourceMap, SourceMapConsumer } from "source-map-js"; const INLINE_SOURCEMAP_REGEX = /^data:application\/json[^,]+base64,/; const SOURCEMAP_REGEX = @@ -8,7 +8,7 @@ export default async function getSourceMap( url: string, content: string, ): Promise { - const lines = content.split('\n'); + const lines = content.split("\n"); let sourceMapUrl: string | undefined; for (let i = lines.length - 1; i >= 0 && !sourceMapUrl; i--) { const result = lines[i]!.match(SOURCEMAP_REGEX); @@ -21,13 +21,11 @@ export default async function getSourceMap( return null; } - if ( - !(INLINE_SOURCEMAP_REGEX.test(sourceMapUrl) || sourceMapUrl.startsWith('/')) - ) { + if (!(INLINE_SOURCEMAP_REGEX.test(sourceMapUrl) || sourceMapUrl.startsWith("/"))) { // Resolve path if it's a relative access - const parsedURL = url.split('/'); + const parsedURL = url.split("/"); parsedURL[parsedURL.length - 1] = sourceMapUrl; - sourceMapUrl = parsedURL.join('/'); + sourceMapUrl = parsedURL.join("/"); } const response = await fetch(sourceMapUrl); const rawSourceMap: RawSourceMap = await response.json(); diff --git a/packages/start/src/shared/dev-overlay/icons.tsx b/packages/start/src/shared/dev-overlay/icons.tsx index 210d328fa..dd5598b3e 100644 --- a/packages/start/src/shared/dev-overlay/icons.tsx +++ b/packages/start/src/shared/dev-overlay/icons.tsx @@ -1,8 +1,8 @@ // @refresh skip -import type { JSX } from 'solid-js'; +import type { JSX } from "solid-js"; export function ArrowRightIcon( - props: JSX.IntrinsicElements['svg'] & { title: string }, + props: JSX.IntrinsicElements["svg"] & { title: string }, ): JSX.Element { return ( {props.title} - + {props.title} - + ); } export function SolidStartIcon( - props: JSX.IntrinsicElements['svg'] & { title: string }, + props: JSX.IntrinsicElements["svg"] & { title: string }, ): JSX.Element { return ( - + {props.title} - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + ); } - diff --git a/packages/start/src/shared/dev-overlay/index.tsx b/packages/start/src/shared/dev-overlay/index.tsx index e581e9520..e14e3c1c3 100644 --- a/packages/start/src/shared/dev-overlay/index.tsx +++ b/packages/start/src/shared/dev-overlay/index.tsx @@ -6,7 +6,7 @@ import { createSignal, onCleanup, resetErrorBoundaries, - type JSX + type JSX, } from "solid-js"; import { HttpStatusCode } from "../HttpStatusCode.ts"; import clientOnly from "../clientOnly.ts"; diff --git a/packages/start/src/shared/lazy.ts b/packages/start/src/shared/lazy.ts index df961921b..4904e57ba 100644 --- a/packages/start/src/shared/lazy.ts +++ b/packages/start/src/shared/lazy.ts @@ -38,14 +38,15 @@ const withAssets = function Promise<{ default: Component } useAssets(assets, nonce); return mod.default(props); - } + }, }; }; return wrapper as T; }; -const lazy = !isServer ? solidLazy: >(fn: () => Promise<{ default: T }>) => - solidLazy(withAssets(fn)); +const lazy = !isServer + ? solidLazy + : >(fn: () => Promise<{ default: T }>) => solidLazy(withAssets(fn)); export default lazy; diff --git a/packages/start/src/shared/serverFunction.ts b/packages/start/src/shared/serverFunction.ts index 394d4e533..8de3b0a3b 100644 --- a/packages/start/src/shared/serverFunction.ts +++ b/packages/start/src/shared/serverFunction.ts @@ -6,5 +6,5 @@ import type { ServerFunctionMeta } from "../server/types.ts"; * Read more: https://docs.solidjs.com/solid-start/reference/server/get-server-function-meta */ export function getServerFunctionMeta(): ServerFunctionMeta | undefined { - return getRequestEvent()?.locals.serverFunctionMeta; + return getRequestEvent()?.locals.serverFunctionMeta; } diff --git a/packages/start/src/virtual.d.ts b/packages/start/src/virtual.d.ts index 68ab708ee..53083c11d 100644 --- a/packages/start/src/virtual.d.ts +++ b/packages/start/src/virtual.d.ts @@ -1,27 +1,27 @@ declare module "solid-start:client-vite-manifest" { - export const clientViteManifest: Record< - string, - { css?: Array; file: string; [key: string]: unknown } - >; + export const clientViteManifest: Record< + string, + { css?: Array; file: string; [key: string]: unknown } + >; } interface StartManifest { - getAssets(id: string): Promise; + getAssets(id: string): Promise; } declare module "solid-start:get-client-manifest" { - export const getClientManifest: () => StartManifest; + export const getClientManifest: () => StartManifest; } declare module "solid-start:get-manifest" { - export const getManifest: (target: "client" | "ssr") => StartManifest; + export const getManifest: (target: "client" | "ssr") => StartManifest; } declare module "solid-start:app" { - export default App as import("solid-js").Component; + export default App as import("solid-js").Component; } declare module "solid-start:middleware" { - type MaybeArray = T | Array; - export default Middleware as import("h3").Middleware[]; + type MaybeArray = T | Array; + export default Middleware as import("h3").Middleware[]; } diff --git a/packages/start/vitest.config.ts b/packages/start/vitest.config.ts index 8cac8fda7..0cf5c2332 100644 --- a/packages/start/vitest.config.ts +++ b/packages/start/vitest.config.ts @@ -4,6 +4,6 @@ export default defineConfig({ test: { globals: true, environment: "node", - mockReset: true - } + mockReset: true, + }, }); diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index bf2c2914b..549f6ed49 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -19,6 +19,9 @@ importers: citty: specifier: ^0.1.5 version: 0.1.6 + oxfmt: + specifier: ^0.14.0 + version: 0.14.0 tinyglobby: specifier: ^0.2.2 version: 0.2.15 @@ -1560,6 +1563,46 @@ packages: cpu: [x64] os: [win32] + '@oxfmt/darwin-arm64@0.14.0': + resolution: {integrity: sha512-g8FANFTuzEcB2KLsE2IcQYXjdgDi8x9GUf8c4t7iyFD7oj0HGucKHhoMz3XsRWTx8szBZoJJD+t4bzs4+AMFdQ==} + cpu: [arm64] + os: [darwin] + + '@oxfmt/darwin-x64@0.14.0': + resolution: {integrity: sha512-Hd/DI+a8PKhl33CmaR3eQdx2P4zpaeoUwjOJQd090cFEbEX9auoCo6+t4LLm/JlzgnRSnFq0EaZOWJVK3wDu8Q==} + cpu: [x64] + os: [darwin] + + '@oxfmt/linux-arm64-gnu@0.14.0': + resolution: {integrity: sha512-JzMyMKuDY9UmxRRnHWFdzYIXYZ2SK2/RmJKhYS47lcRELQC64nzuFe0d5JkRnt7KIJoM7RdUmYqEr+UKdiZ5mg==} + cpu: [arm64] + os: [linux] + + '@oxfmt/linux-arm64-musl@0.14.0': + resolution: {integrity: sha512-sPLA+jvL5kArpnUpxlt6v/EZCGvIhe8Z6hnNuGUa4gdYfzVf2XhAK1jO6hrrjFMT+2cnkbqmxa5iuZqCT6Y5xg==} + cpu: [arm64] + os: [linux] + + '@oxfmt/linux-x64-gnu@0.14.0': + resolution: {integrity: sha512-2hWVwIBa756NHs3+19om1mQp2C4vnGhFGe75AmsRRlkLq1vttC1iuQoF6slQQTS6n0/d7o4Pg1wGY3kldY6Msw==} + cpu: [x64] + os: [linux] + + '@oxfmt/linux-x64-musl@0.14.0': + resolution: {integrity: sha512-bk98BFY2wArfWTVJ+T0zTCqF1deFfcyzrJ0/zppupQDFVdKUTga2XeKY8S1ImdyG+N7XcixdCrcT9db+SVNKMA==} + cpu: [x64] + os: [linux] + + '@oxfmt/win32-arm64@0.14.0': + resolution: {integrity: sha512-UxctetV+XOXGXYcJp8uwP+/cUq3GSTAxq6UBbeGUq+0j1je3zC4XCQ6Kh/dm6kxOnAzrmMkX1uG4Su4bwvMq8w==} + cpu: [arm64] + os: [win32] + + '@oxfmt/win32-x64@0.14.0': + resolution: {integrity: sha512-P/Cw1aP0UIf42HDH6t3+5IY0xx8e4HxCfDHQz/PDbJ+Vc2JhAB6rzkVFAOEo9inQsXoZVdApF/xklp4NJtrkqQ==} + cpu: [x64] + os: [win32] + '@parcel/watcher-android-arm64@2.4.1': resolution: {integrity: sha512-LOi/WTbbh3aTn2RYddrO8pnapixAziFl6SMxHM69r3tvdSm94JtCenaKgk1GRg5FJ5wpMCpHeW+7yqPlvZv7kg==} engines: {node: '>= 10.0.0'} @@ -4305,6 +4348,11 @@ packages: resolution: {integrity: sha512-dQPNIF+gHpSkmC0+Vg9IktNyhcn28Y8R3eTLyzn52UNymkasLicl3sFAtz7oEVuFmCpgGjaUTKkwk+jW2cHpDQ==} engines: {node: ^20.19.0 || >=22.12.0} + oxfmt@0.14.0: + resolution: {integrity: sha512-cZpXmiiEIHxEWq1bkqgCM/9vMv4TQ8RCSA9l+5PPEuCewL+2qNP/7vZynIrQnByvUnJc/G3YOj95XH+GXYLaKg==} + engines: {node: ^20.19.0 || >=22.12.0} + hasBin: true + p-event@5.0.1: resolution: {integrity: sha512-dd589iCQ7m1L0bmC5NLlVYfy3TbBEsMUfWx9PyAgPeIcFZ/E2yaTZ4Rz4MiBmmJShviiftHVXOqfnfzJ6kyMrQ==} engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} @@ -7022,6 +7070,30 @@ snapshots: '@oxc-transform/binding-win32-x64-msvc@0.96.0': optional: true + '@oxfmt/darwin-arm64@0.14.0': + optional: true + + '@oxfmt/darwin-x64@0.14.0': + optional: true + + '@oxfmt/linux-arm64-gnu@0.14.0': + optional: true + + '@oxfmt/linux-arm64-musl@0.14.0': + optional: true + + '@oxfmt/linux-x64-gnu@0.14.0': + optional: true + + '@oxfmt/linux-x64-musl@0.14.0': + optional: true + + '@oxfmt/win32-arm64@0.14.0': + optional: true + + '@oxfmt/win32-x64@0.14.0': + optional: true + '@parcel/watcher-android-arm64@2.4.1': optional: true @@ -9911,6 +9983,17 @@ snapshots: '@oxc-transform/binding-win32-arm64-msvc': 0.96.0 '@oxc-transform/binding-win32-x64-msvc': 0.96.0 + oxfmt@0.14.0: + optionalDependencies: + '@oxfmt/darwin-arm64': 0.14.0 + '@oxfmt/darwin-x64': 0.14.0 + '@oxfmt/linux-arm64-gnu': 0.14.0 + '@oxfmt/linux-arm64-musl': 0.14.0 + '@oxfmt/linux-x64-gnu': 0.14.0 + '@oxfmt/linux-x64-musl': 0.14.0 + '@oxfmt/win32-arm64': 0.14.0 + '@oxfmt/win32-x64': 0.14.0 + p-event@5.0.1: dependencies: p-timeout: 5.1.0 diff --git a/scripts/bump.js b/scripts/bump.js index 6e8304f58..bd67097e0 100644 --- a/scripts/bump.js +++ b/scripts/bump.js @@ -7,8 +7,8 @@ import { promisify } from "util"; const command = defineCommand({ args: { vinxi: { - description: "Bump vinxi packages to latest version" - } + description: "Bump vinxi packages to latest version", + }, }, async run({ args }) { const extPackageNames = ["solid-js"]; @@ -20,8 +20,8 @@ const command = defineCommand({ "@vinxi/plugin-directives", "@vinxi/server-components", "@vinxi/server-functions", - "@vinxi/plugin-mdx" - ] + "@vinxi/plugin-mdx", + ], ); } @@ -30,12 +30,12 @@ const command = defineCommand({ extPackageNames.map(async name => { const proc = await execAsync(`npm view ${name} version`); return { name, version: proc.stdout.toString().trim() }; - }) + }), ); await Promise.all( - globSync(["package.json", "packages/*/package.json", "examples/*/package.json"]) - .map(async path => { + globSync(["package.json", "packages/*/package.json", "examples/*/package.json"]).map( + async path => { const packageJson = JSON.parse(await fs.readFile(path)); let deps = packages; for (const dep of deps) { @@ -45,12 +45,13 @@ const command = defineCommand({ (packageJson.devDependencies[dep.name] = `^${dep.version}`); } await fs.writeFile(path, JSON.stringify(packageJson, null, 2) + "\n"); - }) + }, + ), ); console.log("Updating lock file...\n"); spawnSync("pnpm i", { shell: true, stdio: "inherit" }); - } + }, }); runMain(command);