From 770b4563496626103b2cef45550dc8df2db61dca Mon Sep 17 00:00:00 2001 From: Hiroshi Ogawa Date: Fri, 15 Nov 2024 16:16:14 +0900 Subject: [PATCH] wip: sourcemap --- .../examples/ssr-simple/e2e/basic.test.ts | 31 +++++++++++ viteroll/examples/ssr-simple/package.json | 17 ++++++ .../examples/ssr-simple/playwright.config.ts | 1 + viteroll/examples/ssr-simple/src/app.tsx | 21 ++++++++ .../examples/ssr-simple/src/entry-client.tsx | 7 +++ .../examples/ssr-simple/src/entry-server.tsx | 24 +++++++++ viteroll/examples/ssr-simple/tsconfig.json | 16 ++++++ viteroll/examples/ssr-simple/vite.config.ts | 54 +++++++++++++++++++ viteroll/examples/ssr/src/entry-server.tsx | 3 ++ viteroll/viteroll.ts | 45 ++++++++++++---- 10 files changed, 208 insertions(+), 11 deletions(-) create mode 100644 viteroll/examples/ssr-simple/e2e/basic.test.ts create mode 100644 viteroll/examples/ssr-simple/package.json create mode 100644 viteroll/examples/ssr-simple/playwright.config.ts create mode 100644 viteroll/examples/ssr-simple/src/app.tsx create mode 100644 viteroll/examples/ssr-simple/src/entry-client.tsx create mode 100644 viteroll/examples/ssr-simple/src/entry-server.tsx create mode 100644 viteroll/examples/ssr-simple/tsconfig.json create mode 100644 viteroll/examples/ssr-simple/vite.config.ts diff --git a/viteroll/examples/ssr-simple/e2e/basic.test.ts b/viteroll/examples/ssr-simple/e2e/basic.test.ts new file mode 100644 index 0000000..4002877 --- /dev/null +++ b/viteroll/examples/ssr-simple/e2e/basic.test.ts @@ -0,0 +1,31 @@ +import { expect, test } from "@playwright/test"; +import { createEditor, testNoJs } from "../../../e2e/helper"; + +testNoJs("ssr", async ({ page }) => { + await page.goto("/"); + await page.getByText("hydrated: false").click(); +}); + +test("csr", async ({ page }) => { + await page.goto("/"); + await page.getByText("hydrated: true").click(); +}); + +test("hmr", async ({ page, request }) => { + await page.goto("/"); + await page.getByText("hydrated: true").click(); + + await page.getByRole("button", { name: "Count: 0" }).click(); + + using file = createEditor("./src/app.tsx"); + file.edit((s) => s.replace("Count:", "Count-EDIT:")); + + await page.getByRole("button", { name: "Count-EDIT: 1" }).click(); + + file.edit((s) => s.replace("Count-EDIT:", "Count-EDIT-EDIT:")); + await page.getByRole("button", { name: "Count-EDIT-EDIT: 2" }).click(); + + // server module is also invalidated + const res = await request.get("/"); + expect(await res.text()).toContain("Count-EDIT-EDIT"); +}); diff --git a/viteroll/examples/ssr-simple/package.json b/viteroll/examples/ssr-simple/package.json new file mode 100644 index 0000000..c221cf0 --- /dev/null +++ b/viteroll/examples/ssr-simple/package.json @@ -0,0 +1,17 @@ +{ + "name": "@hiogawa/viteroll-example-ssr", + "private": true, + "type": "module", + "scripts": { + "dev": "vite", + "test-e2e": "playwright test" + }, + "dependencies": { + "react": "latest", + "react-dom": "latest" + }, + "devDependencies": { + "@types/react": "latest", + "@types/react-dom": "latest" + } +} diff --git a/viteroll/examples/ssr-simple/playwright.config.ts b/viteroll/examples/ssr-simple/playwright.config.ts new file mode 100644 index 0000000..d0f2112 --- /dev/null +++ b/viteroll/examples/ssr-simple/playwright.config.ts @@ -0,0 +1 @@ +export { default } from "../../playwright.config.ts"; diff --git a/viteroll/examples/ssr-simple/src/app.tsx b/viteroll/examples/ssr-simple/src/app.tsx new file mode 100644 index 0000000..12e7520 --- /dev/null +++ b/viteroll/examples/ssr-simple/src/app.tsx @@ -0,0 +1,21 @@ +import React from "react"; + +export function App() { + const [count, setCount] = React.useState(0); + return ( +
+

Rolldown SSR

+ + +
+ ); +} + +function Hydrated() { + const hydrated = React.useSyncExternalStore( + React.useCallback(() => () => {}, []), + () => true, + () => false, + ); + return

hydrated: {String(hydrated)}

; +} diff --git a/viteroll/examples/ssr-simple/src/entry-client.tsx b/viteroll/examples/ssr-simple/src/entry-client.tsx new file mode 100644 index 0000000..3e5c48f --- /dev/null +++ b/viteroll/examples/ssr-simple/src/entry-client.tsx @@ -0,0 +1,7 @@ +import React from "react"; +import ReactDOMClient from "react-dom/client"; +import { App } from "./app"; + +React.startTransition(() => { + ReactDOMClient.hydrateRoot(document.getElementById("root")!, ); +}); diff --git a/viteroll/examples/ssr-simple/src/entry-server.tsx b/viteroll/examples/ssr-simple/src/entry-server.tsx new file mode 100644 index 0000000..e93fdc6 --- /dev/null +++ b/viteroll/examples/ssr-simple/src/entry-server.tsx @@ -0,0 +1,24 @@ +import type { Connect } from "vite"; + +const handler: Connect.SimpleHandleFunction = (req, res) => { + const url = new URL(req.url ?? "/", "https://vite.dev"); + console.log(`[SSR] ${req.method} ${url.pathname}`); + if (url.pathname === "/crash-ssr") { + throw new Error("crash-ssr"); + } + res.setHeader("content-type", "text/html"); + // TODO: transformIndexHtml? + res.end(`\ + + + + + + + Test SSR + + +`); +}; + +export default handler; diff --git a/viteroll/examples/ssr-simple/tsconfig.json b/viteroll/examples/ssr-simple/tsconfig.json new file mode 100644 index 0000000..510cef1 --- /dev/null +++ b/viteroll/examples/ssr-simple/tsconfig.json @@ -0,0 +1,16 @@ +{ + "include": ["src", "*.ts"], + "compilerOptions": { + "strict": true, + "noUnusedLocals": true, + "noUnusedParameters": true, + "verbatimModuleSyntax": true, + "allowImportingTsExtensions": true, + "moduleResolution": "Bundler", + "module": "ESNext", + "target": "ESNext", + "lib": ["ESNext", "DOM"], + "jsx": "react-jsx", + "noEmit": true + } +} diff --git a/viteroll/examples/ssr-simple/vite.config.ts b/viteroll/examples/ssr-simple/vite.config.ts new file mode 100644 index 0000000..2902a53 --- /dev/null +++ b/viteroll/examples/ssr-simple/vite.config.ts @@ -0,0 +1,54 @@ +import { defineConfig } from "vite"; +import { RolldownEnvironment, viteroll } from "../../viteroll"; + +process.setSourceMapsEnabled(true); + +export default defineConfig({ + environments: { + client: { + build: { + outDir: "dist/client", + rollupOptions: { + input: "./src/entry-client", + }, + }, + }, + ssr: { + build: { + outDir: "dist/server", + rollupOptions: { + input: { + index: "./src/entry-server", + }, + }, + }, + }, + }, + plugins: [ + viteroll({ + reactRefresh: true, + ssrModuleRunner: true, + }), + { + name: "ssr-middleware", + config() { + return { + appType: "custom", + }; + }, + configureServer(server) { + return () => { + const devEnv = server.environments.ssr as RolldownEnvironment; + server.middlewares.use(async (req, res, next) => { + try { + const mod = (await devEnv.import("src/entry-server.tsx")) as any; + await mod.default(req, res); + } catch (e) { + next(e); + } + }); + }; + }, + }, + ], +}); diff --git a/viteroll/examples/ssr/src/entry-server.tsx b/viteroll/examples/ssr/src/entry-server.tsx index f536333..fadbfb0 100644 --- a/viteroll/examples/ssr/src/entry-server.tsx +++ b/viteroll/examples/ssr/src/entry-server.tsx @@ -6,6 +6,9 @@ import { App } from "./app"; const handler: Connect.SimpleHandleFunction = (req, res) => { const url = new URL(req.url ?? "/", "https://vite.dev"); console.log(`[SSR] ${req.method} ${url.pathname}`); + if (url.pathname === "/crash-ssr") { + throw new Error("crash-ssr"); + } const ssrHtml = ReactDOMServer.renderToString(); res.setHeader("content-type", "text/html"); // TODO: transformIndexHtml? diff --git a/viteroll/viteroll.ts b/viteroll/viteroll.ts index ded6e04..7922daa 100644 --- a/viteroll/viteroll.ts +++ b/viteroll/viteroll.ts @@ -342,16 +342,36 @@ class RolldownModuleRunner { self: this.context, ...this.context, }; - // TODO: sourcemap - code = code.replace(/^\/\/# sourceMapping.*$/m, ""); - const wrapped = `'use strict';(${Object.keys(context).join(",")})=>{{ - ${code}; - // TODO: need to re-expose runtime utilities for now - self.__toCommonJS = __toCommonJS; - self.__export = __export; - self.__toESM = __toESM; - }}`; - const fn = (0, eval)(wrapped); + // TODO: sourcemap not working? + // extract sourcemap + const sourcemap = code.match(/^\/\/# sourceMappingURL=.*/m)?.[0] ?? ""; + if (sourcemap) { + code = code.replace(sourcemap, ""); + } + // as eval + code = `\ +'use strict';(${Object.keys(context).join(",")})=>{{${code} +// TODO: need to re-expose runtime utilities for now +self.__toCommonJS = __toCommonJS; +self.__export = __export; +self.__toESM = __toESM; +}} +//# sourceMappingSource=rolldown-module-runner +${sourcemap} +`; + // as new Function + // code = `\ + // ${code} + // // TODO: need to re-expose runtime utilities for now + // self.__toCommonJS = __toCommonJS; + // self.__export = __export; + // self.__toESM = __toESM; + // //# sourceMappingSource=rolldown-module-runner + // ${sourcemap} + // `; + fs.writeFileSync("dump.js", code); + const fn = (0, eval)(code); + // const fn = new Function(...Object.keys(context), code); try { fn(...Object.values(context)); } catch (e) { @@ -440,7 +460,10 @@ function viterollEntryPlugin( if (viterollOptions.reactRefresh) { output.prepend(getReactRefreshRuntimeCode()); } - return { code: output.toString(), map: output.generateMap() }; + return { + code: output.toString(), + map: output.generateMap({ hires: "boundary" }), + }; } }, generateBundle(_options, bundle) {