From 848ad00b267e4433aa6b328268a8d31c79ed3b4b Mon Sep 17 00:00:00 2001 From: Nikhil Saraf Date: Thu, 20 Oct 2022 01:03:40 -0700 Subject: [PATCH 001/117] feat: internal-fetch more powerful --- packages/start-node/server.js | 17 ++++- packages/start/api/internalFetch.ts | 22 +++--- packages/start/api/middleware.ts | 17 +++-- packages/start/dev/server.js | 99 +++++++++++++++------------ packages/start/entry-server/render.ts | 3 +- packages/start/server/middleware.ts | 3 +- packages/start/server/types.tsx | 5 +- test/api-routes-test.ts | 12 +++- test/helpers/create-fixture.ts | 45 ++++++++---- 9 files changed, 143 insertions(+), 80 deletions(-) diff --git a/packages/start-node/server.js b/packages/start-node/server.js index 336ef6759..9fdbedcd5 100644 --- a/packages/start-node/server.js +++ b/packages/start-node/server.js @@ -42,9 +42,24 @@ export function createServer({ handler, paths, env }) { }); }; + function internalFetch(route, init = {}) { + if (route.startsWith("http")) { + return fetch(route, init); + } + + let url = new URL(route, "http://internal"); + const request = new Request(url.href, init); + return handler({ + request: request, + env, + fetch: internalFetch + }); + } + const webRes = await handler({ request: createRequest(req), - env + env, + fetch: internalFetch }); res.statusCode = webRes.status; diff --git a/packages/start/api/internalFetch.ts b/packages/start/api/internalFetch.ts index f309c0221..707ac830d 100644 --- a/packages/start/api/internalFetch.ts +++ b/packages/start/api/internalFetch.ts @@ -17,14 +17,18 @@ export async function internalFetch(route: string, init: RequestInit) { const request = new Request(url.href, init); const handler = getRouteMatches(apiRoutes, url.pathname, request.method.toUpperCase() as Method); - let apiEvent: APIEvent = Object.freeze({ - request, - params: handler.params, - env: {}, - $type: FETCH_EVENT, - fetch: internalFetch - }); + if (handler) { + let apiEvent: APIEvent = Object.freeze({ + request, + params: handler.params, + env: {}, + $type: FETCH_EVENT, + fetch: internalFetch + }); - const response = await handler.handler(apiEvent); - return response; + const response = await handler.handler(apiEvent); + return response; + } else { + throw new Error(`No handler found for ${request.method} ${url.pathname}`); + } } diff --git a/packages/start/api/middleware.ts b/packages/start/api/middleware.ts index 583068bdb..9de850c81 100644 --- a/packages/start/api/middleware.ts +++ b/packages/start/api/middleware.ts @@ -1,6 +1,5 @@ import { FetchEvent, FETCH_EVENT } from "../server/types"; import { getApiHandler } from "./index"; -import { internalFetch } from "./internalFetch"; export const apiRoutes = ({ forward }) => { return async (event: FetchEvent) => { @@ -11,7 +10,7 @@ export const apiRoutes = ({ forward }) => { params: apiHandler.params, env: event.env, $type: FETCH_EVENT, - fetch: internalFetch + fetch: event.fetch }); try { return await apiHandler.handler(apiEvent); @@ -19,9 +18,17 @@ export const apiRoutes = ({ forward }) => { if (error instanceof Response) { return error; } - return new Response(JSON.stringify(error), { - status: 500 - }); + return new Response( + JSON.stringify({ + error: error.message + }), + { + headers: { + "Content-Type": "application/json" + }, + status: 500 + } + ); } } return await forward(event); diff --git a/packages/start/dev/server.js b/packages/start/dev/server.js index ea91d42d9..f6e5a48eb 100644 --- a/packages/start/dev/server.js +++ b/packages/start/dev/server.js @@ -36,58 +36,71 @@ export function createDevHandler(viteServer, config, options) { async function devFetch(request, env) { const entry = (await viteServer.ssrLoadModule("~start/entry-server")).default; - return await entry({ - request, - env: { - ...env, - __dev: { - manifest: options.router.getFlattenedPageRoutes(true), - collectStyles: async match => { - const styles = {}; - const deps = new Set(); - - try { - for (const file of match) { - const normalizedPath = path.resolve(file).replace(/\\/g, "/"); - let node = await viteServer.moduleGraph.getModuleById(normalizedPath); + const devEnv = { + ...env, + __dev: { + manifest: options.router.getFlattenedPageRoutes(true), + collectStyles: async match => { + const styles = {}; + const deps = new Set(); + + try { + for (const file of match) { + const normalizedPath = path.resolve(file).replace(/\\/g, "/"); + let node = await viteServer.moduleGraph.getModuleById(normalizedPath); + if (!node) { + const absolutePath = path.resolve(file); + await viteServer.ssrLoadModule(absolutePath); + node = await viteServer.moduleGraph.getModuleByUrl(absolutePath); + if (!node) { - const absolutePath = path.resolve(file); - await viteServer.ssrLoadModule(absolutePath); - node = await viteServer.moduleGraph.getModuleByUrl(absolutePath); - - if (!node) { - console.log("not found"); - return; - } + console.log("not found"); + return; } - - await find_deps(viteServer, node, deps); } - } catch (e) {} - - for (const dep of deps) { - const parsed = new URL(dep.url, "http://localhost/"); - const query = parsed.searchParams; - - if (style_pattern.test(dep.file)) { - try { - const mod = await viteServer.ssrLoadModule(dep.url); - if (/.module.css$/.test(dep.file)) { - styles[dep.url] = env.cssModules?.[dep.file]; - } else { - styles[dep.url] = mod.default; - } - } catch { - // this can happen with dynamically imported modules, I think - // because the Vite module graph doesn't distinguish between - // static and dynamic imports? TODO investigate, submit fix + + await find_deps(viteServer, node, deps); + } + } catch (e) {} + + for (const dep of deps) { + const parsed = new URL(dep.url, "http://localhost/"); + const query = parsed.searchParams; + + if (style_pattern.test(dep.file)) { + try { + const mod = await viteServer.ssrLoadModule(dep.url); + if (/.module.css$/.test(dep.file)) { + styles[dep.url] = env.cssModules?.[dep.file]; + } else { + styles[dep.url] = mod.default; } + } catch { + // this can happen with dynamically imported modules, I think + // because the Vite module graph doesn't distinguish between + // static and dynamic imports? TODO investigate, submit fix } } - return styles; } + return styles; } } + }; + + function internalFetch(route, init = {}) { + let url = new URL(route, "http://internal"); + const request = new Request(url.href, init); + return entry({ + request: request, + env: devEnv, + fetch: internalFetch + }); + } + + return await entry({ + request, + env: devEnv, + fetch: internalFetch }); } diff --git a/packages/start/entry-server/render.ts b/packages/start/entry-server/render.ts index 427e505eb..65c43a51a 100644 --- a/packages/start/entry-server/render.ts +++ b/packages/start/entry-server/render.ts @@ -1,6 +1,5 @@ import { JSX } from "solid-js"; import { renderToStream, renderToString, renderToStringAsync } from "solid-js/web"; -import { internalFetch } from "../api/internalFetch"; import { redirect } from "../server/responses"; import { FetchEvent, FETCH_EVENT, PageEvent } from "../server/types"; @@ -160,7 +159,7 @@ function createPageEvent(event: FetchEvent) { responseHeaders, setStatusCode: setStatusCode, getStatusCode: getStatusCode, - fetch: internalFetch + fetch: event.fetch }); return pageEvent; diff --git a/packages/start/server/middleware.ts b/packages/start/server/middleware.ts index b7ebb572f..71584148e 100644 --- a/packages/start/server/middleware.ts +++ b/packages/start/server/middleware.ts @@ -1,4 +1,3 @@ -import { internalFetch } from "../api/internalFetch"; import { Middleware as ServerMiddleware } from "../entry-server/StartServer"; import { ContentTypeHeader, XSolidStartContentTypeHeader, XSolidStartOrigin } from "./responses"; import { handleServerRequest, server$ } from "./server-functions/server"; @@ -33,7 +32,7 @@ export const inlineServerFunctions: ServerMiddleware = ({ forward }) => { let serverFunctionEvent = Object.freeze({ request: event.request, - fetch: internalFetch, + fetch: event.fetch, $type: FETCH_EVENT, env: event.env }); diff --git a/packages/start/server/types.tsx b/packages/start/server/types.tsx index 8437e66a9..016a51636 100644 --- a/packages/start/server/types.tsx +++ b/packages/start/server/types.tsx @@ -44,7 +44,7 @@ declare global { */ getStaticHTML?(path: string): Promise; /** - * BE CAREFUL WHILE USING. AVAILABLE IN PRODUCTION ONLY. + * BE CAREFUL WHILE USING. AVAILABLE IN DEVELOPMENT ONLY. */ __dev?: { /** @@ -59,10 +59,10 @@ declare global { export interface FetchEvent { request: Request; env: Env; + fetch(url: string, init: RequestInit): Promise; } export interface ServerFunctionEvent extends FetchEvent { - fetch(url: string, init: RequestInit): Promise; $type: typeof FETCH_EVENT; } @@ -73,6 +73,5 @@ export interface PageEvent extends FetchEvent { tags?: TagDescription[]; setStatusCode(code: number): void; getStatusCode(): number; - fetch(url: string, init: RequestInit): Promise; $type: typeof FETCH_EVENT; } diff --git a/test/api-routes-test.ts b/test/api-routes-test.ts index 7c0af4199..02a3e267d 100644 --- a/test/api-routes-test.ts +++ b/test/api-routes-test.ts @@ -89,6 +89,10 @@ test.describe("api routes", () => { import { json } from "solid-start/server"; export let GET = ({ request, fetch }) => fetch('/api/waterfall'); `, + "src/routes/api/rewrite.js": js` + import { json } from "solid-start/server"; + export let GET = ({ request, fetch }) => fetch('/redirectd'); + `, "src/routes/api/external-fetch.js": js` import { json } from "solid-start/server"; export let GET = ({ request, fetch }) => fetch('https://hogwarts.deno.dev/'); @@ -195,6 +199,12 @@ test.describe("api routes", () => { expect(await res.json()).toEqual({ welcome: "harry-potter" }); }); + test("should rewrite", async ({ page }) => { + let app = new PlaywrightFixture(appFixture, page); + await app.goto("/api/rewrite", true); + await page.waitForSelector("[data-testid='redirected']"); + }); + test("should return json from /api/greeting/[...unknown] API unmatched route", async () => { let res = await fixture.requestDocument("/api/greeting/he/who/must/not/be/named"); expect(res.headers.get("content-type")).toEqual("application/json; charset=utf-8"); @@ -211,8 +221,8 @@ test.describe("api routes", () => { test("should return json from internally fetched API route", async () => { let res = await fixture.requestDocument("/api/waterfall"); - expect(res.headers.get("content-type")).toEqual("application/json; charset=utf-8"); expect(await res.json()).toEqual({ welcome: "harry-potter" }); + expect(res.headers.get("content-type")).toEqual("application/json; charset=utf-8"); }); test("should return json from doubly internally fetched API route", async () => { diff --git a/test/helpers/create-fixture.ts b/test/helpers/create-fixture.ts index 897ccbc32..66efd696e 100644 --- a/test/helpers/create-fixture.ts +++ b/test/helpers/create-fixture.ts @@ -120,22 +120,39 @@ export async function createFixture(init: FixtureInit) { let app: EntryServer = await import(pathToFileURL(buildPath).toString()); let handler = async (request: Request) => { + const env = { + manifest, + getStaticHTML: async assetPath => { + let text = await readFile( + path.join(projectDir, "dist", "public", assetPath + ".html"), + "utf8" + ); + return new Response(text, { + headers: { + "content-type": "text/html" + } + }); + } + }; + + function internalFetch(route, init = {}) { + if (route.startsWith("http")) { + return fetch(route, init); + } + + let url = new URL(route, "http://internal"); + const request = new Request(url.href, init); + return app.default({ + request: request, + env, + fetch: internalFetch + }); + } + return await app.default({ request: request, - env: { - manifest, - getStaticHTML: async assetPath => { - let text = await readFile( - path.join(projectDir, "dist", "public", assetPath + ".html"), - "utf8" - ); - return new Response(text, { - headers: { - "content-type": "text/html" - } - }); - } - } + env, + fetch: internalFetch }); }; From b816b429ac5f776cb8c0ca67041082c2a6fa7833 Mon Sep 17 00:00:00 2001 From: Nikhil Saraf Date: Wed, 19 Oct 2022 22:25:58 -0700 Subject: [PATCH 002/117] fix (scss): fix scss/sass support for dev inline styles --- packages/start/dev/server.js | 4 +++- packages/start/root/InlineStyles.tsx | 6 ++++-- packages/start/router.tsx | 4 ++-- packages/start/vite/plugin.js | 3 ++- 4 files changed, 11 insertions(+), 6 deletions(-) diff --git a/packages/start/dev/server.js b/packages/start/dev/server.js index ea91d42d9..189cb74e4 100644 --- a/packages/start/dev/server.js +++ b/packages/start/dev/server.js @@ -10,6 +10,7 @@ globalThis.DEBUG = debug("start:server"); // Vite doesn't expose this so we just copy the list for now // https://github.com/vitejs/vite/blob/3edd1af56e980aef56641a5a51cf2932bb580d41/packages/vite/src/node/plugins/css.ts#L96 const style_pattern = /\.(css|less|sass|scss|styl|stylus|pcss|postcss)$/; +const module_style_pattern = /\.module\.(css|less|sass|scss|styl|stylus|pcss|postcss)$/; process.on("unhandledRejection", function (e) { if ( @@ -72,12 +73,13 @@ export function createDevHandler(viteServer, config, options) { if (style_pattern.test(dep.file)) { try { const mod = await viteServer.ssrLoadModule(dep.url); - if (/.module.css$/.test(dep.file)) { + if (module_style_pattern.test(dep.file)) { styles[dep.url] = env.cssModules?.[dep.file]; } else { styles[dep.url] = mod.default; } } catch { + console.warn(`Could not load ${dep.file}`); // this can happen with dynamically imported modules, I think // because the Vite module graph doesn't distinguish between // static and dynamic imports? TODO investigate, submit fix diff --git a/packages/start/root/InlineStyles.tsx b/packages/start/root/InlineStyles.tsx index 2fddbaa20..cd12cf08c 100644 --- a/packages/start/root/InlineStyles.tsx +++ b/packages/start/root/InlineStyles.tsx @@ -4,6 +4,8 @@ import type { PageEvent } from "../server"; import { ServerContext } from "../server/ServerContext"; import { routesConfig } from "./FileRoutes"; +const style_pattern = /\.(css|less|sass|scss|styl|stylus|pcss|postcss)$/; + async function getInlineStyles(env: PageEvent["env"], routerContext: PageEvent["routerContext"]) { const match = routerContext.matches.reduce((memo: string[], m) => { if (m.length) { @@ -54,11 +56,11 @@ export function InlineStyles() { return ( - {(resource) => { + {resource => { return (