From 92a4bdea6bf393e7d4459c84846a23f1713e3dec Mon Sep 17 00:00:00 2001 From: Ryan Christian <33403762+rschristian@users.noreply.github.com> Date: Sun, 18 Feb 2024 08:28:50 -0600 Subject: [PATCH 01/46] fix: Normalize URLs w/ trailing slashes --- src/prerender.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/prerender.ts b/src/prerender.ts index 2cda4ea..42845a0 100644 --- a/src/prerender.ts +++ b/src/prerender.ts @@ -291,7 +291,7 @@ export function PrerenderPlugin({ if (result.links) { for (let url of result.links) { const parsed = new URL(url, "http://localhost"); - url = parsed.pathname; + url = parsed.pathname.replace(/\/$/, '') || '/'; // ignore external links and ones we've already picked up if (seen.has(url) || parsed.origin !== "http://localhost") continue; seen.add(url); From 728a8f9ca7bc5d6689242b081bedc8f6881282c0 Mon Sep 17 00:00:00 2001 From: Ryan Christian Date: Mon, 19 Feb 2024 00:32:21 -0600 Subject: [PATCH 02/46] feat: Return `Response` from patched fetch --- src/prerender.ts | 23 ++++++++++++++--------- 1 file changed, 14 insertions(+), 9 deletions(-) diff --git a/src/prerender.ts b/src/prerender.ts index 42845a0..2a438ab 100644 --- a/src/prerender.ts +++ b/src/prerender.ts @@ -169,15 +169,20 @@ export function PrerenderPlugin({ // @ts-ignore globalThis.fetch = async (url: string, opts: RequestInit | undefined) => { if (/^\//.test(url)) { - const text = () => - fs.readFile( - `${path.join( - viteConfig.root, - viteConfig.build.outDir, - )}/${url.replace(/^\//, "")}`, - "utf-8", + try { + return new Response( + await fs.readFile( + `${path.join( + viteConfig.root, + viteConfig.build.outDir, + )}/${url.replace(/^\//, "")}`, + "utf-8", + ), ); - return { text, json: () => text().then(JSON.parse) }; + } catch (e: any) { + if (e.code !== "ENOENT") throw e; + return new Response(null, { status: 404 }); + } } return nodeFetch(url, opts); @@ -291,7 +296,7 @@ export function PrerenderPlugin({ if (result.links) { for (let url of result.links) { const parsed = new URL(url, "http://localhost"); - url = parsed.pathname.replace(/\/$/, '') || '/'; + url = parsed.pathname.replace(/\/$/, "") || "/"; // ignore external links and ones we've already picked up if (seen.has(url) || parsed.origin !== "http://localhost") continue; seen.add(url); From a551c03a8906674e3be986705c9d9e9f88f21b5f Mon Sep 17 00:00:00 2001 From: Ryan Christian Date: Mon, 19 Feb 2024 00:32:39 -0600 Subject: [PATCH 03/46] test: Revise fetch test to use response --- demo/src/components/LocalFetch.tsx | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/demo/src/components/LocalFetch.tsx b/demo/src/components/LocalFetch.tsx index 11eea19..6e636bc 100644 --- a/demo/src/components/LocalFetch.tsx +++ b/demo/src/components/LocalFetch.tsx @@ -4,7 +4,8 @@ const cache = new Map(); async function load(url: string) { const res = await fetch(url); - return await res.text(); + if (res.ok) return await res.text(); + throw new Error(`Failed to fetch ${url}!`); } function useFetch(url: string) { From 85e4bb4be9ead062327edb2dcb9ba8f6af2600ed Mon Sep 17 00:00:00 2001 From: Ryan Christian Date: Mon, 19 Feb 2024 23:56:12 -0600 Subject: [PATCH 04/46] feat: Form code frame from sourcemaps for prerender errors --- package-lock.json | 76 ++++++++++++++++++++++++++++++++++++++++------- package.json | 6 +++- src/prerender.ts | 56 ++++++++++++++++++++++++++++++---- 3 files changed, 121 insertions(+), 17 deletions(-) diff --git a/package-lock.json b/package-lock.json index f41ece7..044233a 100644 --- a/package-lock.json +++ b/package-lock.json @@ -18,15 +18,19 @@ "kolorist": "^1.8.0", "magic-string": "0.30.5", "node-html-parser": "^6.1.10", - "resolve": "^1.22.8" + "resolve": "^1.22.8", + "source-map": "^0.7.4", + "stack-trace": "^1.0.0-pre2" }, "devDependencies": { "@babel/core": "^7.15.8", + "@types/babel__code-frame": "^7.0.6", "@types/babel__core": "^7.1.14", "@types/debug": "^4.1.5", "@types/estree": "^0.0.50", "@types/node": "^14.14.33", "@types/resolve": "^1.20.1", + "@types/stack-trace": "^0.0.33", "lint-staged": "^10.5.4", "preact": "^10.19.2", "preact-iso": "^2.3.2", @@ -604,6 +608,12 @@ "node": ">= 8.0.0" } }, + "node_modules/@types/babel__code-frame": { + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/@types/babel__code-frame/-/babel__code-frame-7.0.6.tgz", + "integrity": "sha512-Anitqkl3+KrzcW2k77lRlg/GfLZLWXBuNgbEcIOU6M92yw42vsd3xV/Z/yAHEj8m+KUjL6bWOVOFqX8PFPJ4LA==", + "dev": true + }, "node_modules/@types/babel__core": { "version": "7.1.14", "resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.1.14.tgz", @@ -675,6 +685,12 @@ "integrity": "sha512-Ku5+GPFa12S3W26Uwtw+xyrtIpaZsGYHH6zxNbZlstmlvMYSZRzOwzwsXbxlVUbHyUucctSyuFtu6bNxwYomIw==", "dev": true }, + "node_modules/@types/stack-trace": { + "version": "0.0.33", + "resolved": "https://registry.npmjs.org/@types/stack-trace/-/stack-trace-0.0.33.tgz", + "integrity": "sha512-O7in6531Bbvlb2KEsJ0dq0CHZvc3iWSR5ZYMtvGgnHA56VgriAN/AU2LorfmcvAl2xc9N5fbCTRyMRRl8nd74g==", + "dev": true + }, "node_modules/aggregate-error": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/aggregate-error/-/aggregate-error-3.1.0.tgz", @@ -2421,12 +2437,11 @@ } }, "node_modules/source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "dev": true, + "version": "0.7.4", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.7.4.tgz", + "integrity": "sha512-l3BikUxvPOcn5E74dZiq5BGsTb5yEwhaTSzccU6t4sDOH8NWJCstKO5QT2CvtFoK6F0saL7p9xHAqHOlCPJygA==", "engines": { - "node": ">=0.10.0" + "node": ">= 8" } }, "node_modules/source-map-js": { @@ -2447,6 +2462,23 @@ "source-map": "^0.6.0" } }, + "node_modules/source-map-support/node_modules/source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/stack-trace": { + "version": "1.0.0-pre2", + "resolved": "https://registry.npmjs.org/stack-trace/-/stack-trace-1.0.0-pre2.tgz", + "integrity": "sha512-2ztBJRek8IVofG9DBJqdy2N5kulaacX30Nz7xmkYF6ale9WBVmIy6mFBchvGX7Vx/MyjBhx+Rcxqrj+dbOnQ6A==", + "engines": { + "node": ">=16" + } + }, "node_modules/string-argv": { "version": "0.3.1", "resolved": "https://registry.npmjs.org/string-argv/-/string-argv-0.3.1.tgz", @@ -3152,6 +3184,12 @@ "picomatch": "^2.2.2" } }, + "@types/babel__code-frame": { + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/@types/babel__code-frame/-/babel__code-frame-7.0.6.tgz", + "integrity": "sha512-Anitqkl3+KrzcW2k77lRlg/GfLZLWXBuNgbEcIOU6M92yw42vsd3xV/Z/yAHEj8m+KUjL6bWOVOFqX8PFPJ4LA==", + "dev": true + }, "@types/babel__core": { "version": "7.1.14", "resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.1.14.tgz", @@ -3223,6 +3261,12 @@ "integrity": "sha512-Ku5+GPFa12S3W26Uwtw+xyrtIpaZsGYHH6zxNbZlstmlvMYSZRzOwzwsXbxlVUbHyUucctSyuFtu6bNxwYomIw==", "dev": true }, + "@types/stack-trace": { + "version": "0.0.33", + "resolved": "https://registry.npmjs.org/@types/stack-trace/-/stack-trace-0.0.33.tgz", + "integrity": "sha512-O7in6531Bbvlb2KEsJ0dq0CHZvc3iWSR5ZYMtvGgnHA56VgriAN/AU2LorfmcvAl2xc9N5fbCTRyMRRl8nd74g==", + "dev": true + }, "aggregate-error": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/aggregate-error/-/aggregate-error-3.1.0.tgz", @@ -4364,10 +4408,9 @@ } }, "source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "dev": true + "version": "0.7.4", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.7.4.tgz", + "integrity": "sha512-l3BikUxvPOcn5E74dZiq5BGsTb5yEwhaTSzccU6t4sDOH8NWJCstKO5QT2CvtFoK6F0saL7p9xHAqHOlCPJygA==" }, "source-map-js": { "version": "1.0.2", @@ -4382,8 +4425,21 @@ "requires": { "buffer-from": "^1.0.0", "source-map": "^0.6.0" + }, + "dependencies": { + "source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true + } } }, + "stack-trace": { + "version": "1.0.0-pre2", + "resolved": "https://registry.npmjs.org/stack-trace/-/stack-trace-1.0.0-pre2.tgz", + "integrity": "sha512-2ztBJRek8IVofG9DBJqdy2N5kulaacX30Nz7xmkYF6ale9WBVmIy6mFBchvGX7Vx/MyjBhx+Rcxqrj+dbOnQ6A==" + }, "string-argv": { "version": "0.3.1", "resolved": "https://registry.npmjs.org/string-argv/-/string-argv-0.3.1.tgz", diff --git a/package.json b/package.json index b5687ac..0fe76df 100644 --- a/package.json +++ b/package.json @@ -42,7 +42,9 @@ "kolorist": "^1.8.0", "magic-string": "0.30.5", "node-html-parser": "^6.1.10", - "resolve": "^1.22.8" + "resolve": "^1.22.8", + "source-map": "^0.7.4", + "stack-trace": "^1.0.0-pre2" }, "peerDependencies": { "@babel/core": "7.x", @@ -50,11 +52,13 @@ }, "devDependencies": { "@babel/core": "^7.15.8", + "@types/babel__code-frame": "^7.0.6", "@types/babel__core": "^7.1.14", "@types/debug": "^4.1.5", "@types/estree": "^0.0.50", "@types/node": "^14.14.33", "@types/resolve": "^1.20.1", + "@types/stack-trace": "^0.0.33", "lint-staged": "^10.5.4", "preact": "^10.19.2", "preact-iso": "^2.3.2", diff --git a/src/prerender.ts b/src/prerender.ts index 2a438ab..dd26a7e 100644 --- a/src/prerender.ts +++ b/src/prerender.ts @@ -1,9 +1,11 @@ import path from "node:path"; - import { promises as fs } from "node:fs"; import MagicString from "magic-string"; import { parse as htmlParse } from "node-html-parser"; +import { SourceMapConsumer } from "source-map"; +import { parse as StackTraceParse } from "stack-trace"; +import { codeFrameColumns } from "@babel/code-frame"; import type { Plugin, ResolvedConfig } from "vite"; @@ -116,6 +118,9 @@ export function PrerenderPlugin({ apply: "build", enforce: "post", configResolved(config) { + // Enable sourcemaps at least for prerendering for usable error messages + config.build.sourcemap = true; + viteConfig = config; }, async options(opts) { @@ -212,7 +217,7 @@ export function PrerenderPlugin({ JSON.stringify({ type: "module" }), ); - let prerenderEntry; + let prerenderEntry: OutputChunk | undefined; for (const output of Object.keys(bundle)) { if (!/\.js$/.test(output) || bundle[output].type !== "chunk") continue; @@ -222,7 +227,7 @@ export function PrerenderPlugin({ ); if ((bundle[output] as OutputChunk).exports?.includes("prerender")) { - prerenderEntry = bundle[output]; + prerenderEntry = bundle[output] as OutputChunk; } } if (!prerenderEntry) { @@ -238,15 +243,18 @@ export function PrerenderPlugin({ ); prerender = m.prerender; } catch (e) { - const isReferenceError = e instanceof ReferenceError; + const stack = StackTraceParse(e as Error).find(s => + s.getFileName().includes(tmpDir), + ); - const message = ` + const isReferenceError = e instanceof ReferenceError; + let message = `\n ${e} This ${ isReferenceError ? "is most likely" : "could be" } caused by using DOM/Web APIs which are not available - available to the prerendering process which runs in Node. Consider + available to the prerendering process running in Node. Consider wrapping the offending code in a window check like so: if (typeof window !== "undefined") { @@ -254,6 +262,42 @@ export function PrerenderPlugin({ } `.replace(/^\t{5}/gm, ""); + const sourceMapContent = prerenderEntry.map; + if (stack && sourceMapContent) { + await SourceMapConsumer.with( + sourceMapContent, + null, + async consumer => { + let { source, line, column } = consumer.originalPositionFor({ + line: stack.getLineNumber(), + column: stack.getColumnNumber(), + }); + + if (!source || line == null || column == null) { + message += `\nUnable to locate source map for error!\n`; + this.error(message); + } + + // `source-map` returns 0-indexed column numbers + column += 1; + + const sourcePath = path.join( + viteConfig.root, + source.replace(/^(..\/)*/, ""), + ); + const sourceContent = await fs.readFile(sourcePath, "utf-8"); + + const frame = codeFrameColumns(sourceContent, { + start: { line, column }, + }); + message += ` + > ${sourcePath}:${line}:${column}\n + ${frame} + `.replace(/^\t{7}/gm, ""); + }, + ); + } + this.error(message); } From 41cfa32296e3e4276b0f6f5054e612daadcbb945 Mon Sep 17 00:00:00 2001 From: Ryan Christian Date: Mon, 19 Feb 2024 23:56:19 -0600 Subject: [PATCH 05/46] test: Fix demo --- demo/package.json | 3 +++ 1 file changed, 3 insertions(+) create mode 100644 demo/package.json diff --git a/demo/package.json b/demo/package.json new file mode 100644 index 0000000..bedb411 --- /dev/null +++ b/demo/package.json @@ -0,0 +1,3 @@ +{ + "type": "module" +} From b268bcf545d088957a6c9bca65b2c4e800c4b832 Mon Sep 17 00:00:00 2001 From: Ryan Christian <33403762+rschristian@users.noreply.github.com> Date: Tue, 20 Feb 2024 00:05:27 -0600 Subject: [PATCH 06/46] Update src/prerender.ts --- src/prerender.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/prerender.ts b/src/prerender.ts index dd26a7e..73a7922 100644 --- a/src/prerender.ts +++ b/src/prerender.ts @@ -118,7 +118,7 @@ export function PrerenderPlugin({ apply: "build", enforce: "post", configResolved(config) { - // Enable sourcemaps at least for prerendering for usable error messages + // Enable sourcemaps for generating more actionable error messages config.build.sourcemap = true; viteConfig = config; From f0c4e538afc80d7d516ea3411717dedcf3e99ed9 Mon Sep 17 00:00:00 2001 From: Ryan Christian <33403762+rschristian@users.noreply.github.com> Date: Wed, 21 Feb 2024 02:20:11 -0600 Subject: [PATCH 07/46] docs: Rewrite prerendering instructions --- README.md | 24 ++++++++++++++++++------ 1 file changed, 18 insertions(+), 6 deletions(-) diff --git a/README.md b/README.md index c30bfbd..2025c18 100644 --- a/README.md +++ b/README.md @@ -75,19 +75,31 @@ preact({ | Option | Type | Default | Description | |---|---|---|---| | `enabled` | `boolean` | `false` | Enables prerendering | -| `prerenderScript` | `string` | `undefined` | Absolute path to script containing exported `prerender()` function. If not provided, will try to find the prerender script in the scripts listed in your HTML entrypoint | | `renderTarget` | `string` | `"body"` | Query selector for where to insert prerender result in your HTML template | -| `additionalPrerenderRoutes` | `string` | `undefined` | Prerendering will automatically discover links to prerender, but if there are unliked pages that you want to prererender (such as a `/404` page), use this option to specify them | +| `prerenderScript` | `string` | `undefined` | Absolute path to script containing exported `prerender()` function. If not provided, will try to find the prerender script in the scripts listed in your HTML entrypoint | +| `additionalPrerenderRoutes` | `string[]` | `undefined` | Prerendering will crawl your site automatically, but you'd like to prerender some pages that may not be found (such as a `/404` page), use this option to specify them | + +To prerender your app, you'll need to do three things: +1. Enable prerendering in the plugin options +2. Specify your render target, if you want the HTML to be inserted anywhere other than the `document.body`. This location likely should match `render()`, i.e., `render(, document.querySelector('#app'))` -> `'#app'` +4. Create and export a `prerender` function from a script. You could add this to your app entrypoint or create a completely separate file for it, either will work. See below for a usage example +5. Specify where your `prerender` function is by either a) adding a `prerender` attribute to the script tag that contains it in your entry HTML (` + data: { url: data.url }, // Optionally configure and add elements to the `` of // the prerendered HTML document head: { diff --git a/demo/src/index.tsx b/demo/src/index.tsx index 3fe367a..7c4e7f0 100644 --- a/demo/src/index.tsx +++ b/demo/src/index.tsx @@ -29,11 +29,12 @@ if (typeof window !== "undefined") { hydrate(, document.getElementById("app")); } -export async function prerender() { +export async function prerender(data) { const { html, links } = await ssr(); return { html, links, + data: { url: data.url }, head: { lang: "en", title: "Prerendered Preact App", diff --git a/src/prerender.ts b/src/prerender.ts index 6152fe4..d3945c4 100644 --- a/src/prerender.ts +++ b/src/prerender.ts @@ -378,6 +378,11 @@ export function PrerenderPlugin({ if (result.head) { head = result.head; } + if (result.data) { + body += ``; + } } else { body = result; } diff --git a/test/build.test.mjs b/test/build.test.mjs index 2977033..6dba0da 100644 --- a/test/build.test.mjs +++ b/test/build.test.mjs @@ -23,6 +23,12 @@ test("builds demo successfully", async () => { assert.match(outputHtml, /Prerendered Preact App<\/title>/); assert.match(outputHtml, /<meta name="description" content="This is a prerendered Preact app">/); + // Prerender Data + assert.match( + outputHtml, + /<script type="application\/json" id="preact-prerender-data">{"url":"\/"}<\/script>/ + ); + // Local Fetch assert.match(outputHtml, /Local fetch works/); From e8e07182132be60a81d92e756bd67ae7dfaf9ec3 Mon Sep 17 00:00:00 2001 From: Marvin Hagemeister <hello@marvinh.dev> Date: Thu, 12 Sep 2024 15:41:33 +0200 Subject: [PATCH 32/46] fix: unable to find TS types under ESM --- package.json | 3 +-- tools/postbuild.mjs | 6 ++---- tsconfig.cjs.json | 4 +--- tsconfig.json | 1 + 4 files changed, 5 insertions(+), 9 deletions(-) diff --git a/package.json b/package.json index b4d70c8..8141ffc 100644 --- a/package.json +++ b/package.json @@ -6,13 +6,12 @@ "module": "./dist/esm/index.mjs", "exports": { ".": { - "types": "./dist/types/index.d.ts", "import": "./dist/esm/index.mjs", "require": "./dist/cjs/index.js" }, "./package.json": "./package.json" }, - "types": "dist/types/index.d.ts", + "types": "dist/cjs/index.d.ts", "scripts": { "dev": "vite demo", "dev:build": "vite build demo", diff --git a/tools/postbuild.mjs b/tools/postbuild.mjs index 53fe683..ca5120c 100644 --- a/tools/postbuild.mjs +++ b/tools/postbuild.mjs @@ -8,10 +8,8 @@ const __dirname = path.dirname(fileURLToPath(import.meta.url)); const dir = path.join(__dirname, "..", "dist", "esm"); for (const file of fs.readdirSync(dir)) { - const target = path.join( - dir, - path.basename(file, path.extname(file)) + ".mjs", - ); + const ext = file.endsWith(".d.ts") ? ".mts" : ".mjs"; + const target = path.join(dir, path.basename(file, path.extname(file)) + ext); fs.renameSync(path.join(dir, file), target); diff --git a/tsconfig.cjs.json b/tsconfig.cjs.json index d756c7d..f70045c 100644 --- a/tsconfig.cjs.json +++ b/tsconfig.cjs.json @@ -2,8 +2,6 @@ "extends": "./tsconfig.json", "compilerOptions": { "outDir": "dist/cjs/", - "module": "CommonJS", - "declaration": true, - "declarationDir": "dist/types/" + "module": "CommonJS" } } diff --git a/tsconfig.json b/tsconfig.json index 5d90ffe..2580247 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -5,6 +5,7 @@ "esModuleInterop": true, "module": "ESNext", "moduleResolution": "Node", + "declaration": true, "outDir": "dist/esm/" }, "files": ["./src/index.ts"] From ae3e7c815125ac2c7c1f031abc1b37aa68090ff7 Mon Sep 17 00:00:00 2001 From: Marvin Hagemeister <hello@marvinh.dev> Date: Thu, 12 Sep 2024 17:41:35 +0200 Subject: [PATCH 33/46] 2.9.1 --- package-lock.json | 4 ++-- package.json | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/package-lock.json b/package-lock.json index d3b8e64..db80e22 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "@preact/preset-vite", - "version": "2.9.0", + "version": "2.9.1", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "@preact/preset-vite", - "version": "2.9.0", + "version": "2.9.1", "license": "MIT", "dependencies": { "@babel/code-frame": "^7.22.13", diff --git a/package.json b/package.json index 8141ffc..0900b32 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@preact/preset-vite", - "version": "2.9.0", + "version": "2.9.1", "description": "Preact preset for the vite bundler", "main": "./dist/cjs/index.js", "module": "./dist/esm/index.mjs", From db487cf1d856f65bcc9e8fad470ebc1677038b8a Mon Sep 17 00:00:00 2001 From: Ryan Christian <33403762+rschristian@users.noreply.github.com> Date: Fri, 13 Sep 2024 04:11:41 -0500 Subject: [PATCH 34/46] refactor: Provide original, unpatched fetch impl on globalThis (#140) --- src/prerender.ts | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/prerender.ts b/src/prerender.ts index d3945c4..28aa11d 100644 --- a/src/prerender.ts +++ b/src/prerender.ts @@ -177,7 +177,8 @@ export function PrerenderPlugin({ globalThis.__VITE_PRELOAD__ = []; // Local, fs-based fetch implementation for prerendering - const nodeFetch = globalThis.fetch; + // @ts-ignore + globalThis.unpatchedFetch = globalThis.fetch; // @ts-ignore globalThis.fetch = async (url: string, opts: RequestInit | undefined) => { if (/^\//.test(url)) { @@ -197,7 +198,8 @@ export function PrerenderPlugin({ } } - return nodeFetch(url, opts); + // @ts-ignore + return globalThis.unpatchedFetch(url, opts); }; // Grab the generated HTML file, which we'll use as a template: From 11648652cc72dac436523f625fdbffc73f7b853e Mon Sep 17 00:00:00 2001 From: Ian Bobinac <ian.units@gmail.com> Date: Wed, 27 Nov 2024 14:56:41 +0100 Subject: [PATCH 35/46] feat: bump vite peer dep (#145) --- package-lock.json | 2 +- package.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/package-lock.json b/package-lock.json index db80e22..ccfad83 100644 --- a/package-lock.json +++ b/package-lock.json @@ -44,7 +44,7 @@ }, "peerDependencies": { "@babel/core": "7.x", - "vite": "2.x || 3.x || 4.x || 5.x" + "vite": "2.x || 3.x || 4.x || 5.x || 6.x" } }, "node_modules/@ampproject/remapping": { diff --git a/package.json b/package.json index 0900b32..105c403 100644 --- a/package.json +++ b/package.json @@ -51,7 +51,7 @@ }, "peerDependencies": { "@babel/core": "7.x", - "vite": "2.x || 3.x || 4.x || 5.x" + "vite": "2.x || 3.x || 4.x || 5.x || 6.x" }, "devDependencies": { "@babel/core": "^7.15.8", From 9deabbf3622cee3cc37d80cfa213f21725c3c93b Mon Sep 17 00:00:00 2001 From: Jovi De Croock <decroockjovi@gmail.com> Date: Wed, 27 Nov 2024 20:45:58 +0100 Subject: [PATCH 36/46] 2.9.2 (#146) --- package-lock.json | 4 ++-- package.json | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/package-lock.json b/package-lock.json index ccfad83..d67cf1f 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "@preact/preset-vite", - "version": "2.9.1", + "version": "2.9.2", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "@preact/preset-vite", - "version": "2.9.1", + "version": "2.9.2", "license": "MIT", "dependencies": { "@babel/code-frame": "^7.22.13", diff --git a/package.json b/package.json index 105c403..931c8f5 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@preact/preset-vite", - "version": "2.9.1", + "version": "2.9.2", "description": "Preact preset for the vite bundler", "main": "./dist/cjs/index.js", "module": "./dist/esm/index.mjs", From 4c3f396745625b27fa56cd9a78a999c9c6532da0 Mon Sep 17 00:00:00 2001 From: Ryan Christian <33403762+rschristian@users.noreply.github.com> Date: Fri, 6 Dec 2024 19:48:00 -0600 Subject: [PATCH 37/46] refactor: Ensure module preload polyfill is inlined into main bundle (#147) * refactor: Ensure module preload polyfill is inlined into main bundle * docs: Update patch comments * refactor: Bail out on multiple outputs --- src/prerender.ts | 63 ++++++++++++++++++++++++++++++++++++++++----- test/build.test.mjs | 5 ++++ 2 files changed, 62 insertions(+), 6 deletions(-) diff --git a/src/prerender.ts b/src/prerender.ts index 28aa11d..3d46270 100644 --- a/src/prerender.ts +++ b/src/prerender.ts @@ -10,7 +10,12 @@ import type { Plugin, ResolvedConfig } from "vite"; // Vite re-exports Rollup's type defs in newer versions, // merge into above type import when we bump the Vite devDep -import type { InputOption, OutputAsset, OutputChunk } from "rollup"; +import type { + InputOption, + OutputAsset, + OutputChunk, + OutputOptions, +} from "rollup"; interface HeadElement { type: string; @@ -74,6 +79,7 @@ export function PrerenderPlugin({ additionalPrerenderRoutes, }: PrerenderPluginOptions = {}): Plugin { const preloadHelperId = "vite/preload-helper"; + const preloadPolyfillId = "vite/modulepreload-polyfill"; let viteConfig = {} as ResolvedConfig; let userEnabledSourceMaps: boolean | undefined; @@ -123,6 +129,34 @@ export function PrerenderPlugin({ config.build.sourcemap = true; viteConfig = config; + + // With this plugin adding an additional input, Rollup/Vite tries to be smart + // and extract our prerender script (which is often their main bundle) to a separate + // chunk that the entry & prerender chunks can depend on. Unfortunately, this means the + // first script the browser loads is the module preload polyfill & a sync import of the main + // bundle. This is obviously less than ideal as the main bundle should be directly referenced + // by the user's HTML to speed up loading a bit. + + // We're only going to alter the chunking behavior in the default cases, where the user and/or + // other plugins haven't already configured this. It'd be impossible to avoid breakages otherwise. + if ( + Array.isArray(config.build.rollupOptions.output) || + (config.build.rollupOptions.output as OutputOptions)?.manualChunks + ) { + return; + } + + config.build.rollupOptions.output ??= {}; + (config.build.rollupOptions.output as OutputOptions).manualChunks = ( + id: string, + ) => { + if ( + id.includes(prerenderScript as string) || + id.includes(preloadPolyfillId) + ) { + return "index"; + } + }; }, async options(opts) { if (!opts.input) return; @@ -139,15 +173,15 @@ export function PrerenderPlugin({ : { ...opts.input, prerenderEntry: prerenderScript }; opts.preserveEntrySignatures = "allow-extension"; }, - // Injects a window check into Vite's preload helper, instantly resolving - // the module rather than attempting to add a <link> to the document. + // Injects window checks into Vite's preload helper & modulepreload polyfill transform(code, id) { - // Vite keeps changing up the ID, best we can do for cross-version - // compat is an `includes` if (id.includes(preloadHelperId)) { + // Injects a window check into Vite's preload helper, instantly resolving + // the module rather than attempting to add a <link> to the document. + const s = new MagicString(code); + // Through v5.0.4 // https://github.com/vitejs/vite/blob/b93dfe3e08f56cafe2e549efd80285a12a3dc2f0/packages/vite/src/node/plugins/importAnalysisBuild.ts#L95-L98 - const s = new MagicString(code); s.replace( `if (!__VITE_IS_MODERN__ || !deps || deps.length === 0) {`, `if (!__VITE_IS_MODERN__ || !deps || deps.length === 0 || typeof window === 'undefined') {`, @@ -162,6 +196,23 @@ export function PrerenderPlugin({ code: s.toString(), map: s.generateMap({ hires: true }), }; + } else if (id.includes(preloadPolyfillId)) { + const s = new MagicString(code); + // Replacement for `'link'` && `"link"` as the output from their tooling has + // differed over the years. Should be better than switching to regex. + // https://github.com/vitejs/vite/blob/20fdf210ee0ac0824b2db74876527cb7f378a9e8/packages/vite/src/node/plugins/modulePreloadPolyfill.ts#L62 + s.replace( + `const relList = document.createElement('link').relList;`, + `if (typeof window === "undefined") return;\n const relList = document.createElement('link').relList;`, + ); + s.replace( + `const relList = document.createElement("link").relList;`, + `if (typeof window === "undefined") return;\n const relList = document.createElement("link").relList;`, + ); + return { + code: s.toString(), + map: s.generateMap({ hires: true }), + }; } }, async generateBundle(_opts, bundle) { diff --git a/test/build.test.mjs b/test/build.test.mjs index 6dba0da..06b2f2a 100644 --- a/test/build.test.mjs +++ b/test/build.test.mjs @@ -2,6 +2,7 @@ import { execFile } from "node:child_process"; import { test } from "node:test"; import { promisify } from "node:util"; import { promises as fs } from "node:fs"; +import path from "node:path"; import assert from "node:assert"; import { dir } from "./util.mjs"; @@ -34,4 +35,8 @@ test("builds demo successfully", async () => { // `additionalPrerenderRoutes` config option assert.doesNotThrow(async () => await fs.access(dir("demo/dist/404/index.html"))); + + const outputFiles = await fs.readdir(path.join(dir("demo/dist"), 'assets')); + const outputIndexJS = outputFiles.filter(f => /^index\..+\.js$/.test(f)); + assert.strictEqual(outputIndexJS.length, 1); }); From 22bb6af2c2644e119eb1b7aabecad89898d72add Mon Sep 17 00:00:00 2001 From: Ryan Christian <33403762+rschristian@users.noreply.github.com> Date: Sat, 7 Dec 2024 07:06:28 -0600 Subject: [PATCH 38/46] 2.9.3 (#148) --- package-lock.json | 4 ++-- package.json | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/package-lock.json b/package-lock.json index d67cf1f..02d4528 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "@preact/preset-vite", - "version": "2.9.2", + "version": "2.9.3", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "@preact/preset-vite", - "version": "2.9.2", + "version": "2.9.3", "license": "MIT", "dependencies": { "@babel/code-frame": "^7.22.13", diff --git a/package.json b/package.json index 931c8f5..23c89f5 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@preact/preset-vite", - "version": "2.9.2", + "version": "2.9.3", "description": "Preact preset for the vite bundler", "main": "./dist/cjs/index.js", "module": "./dist/esm/index.mjs", From b49447b2fc5155dca3f775dce30a2714ef83f0a2 Mon Sep 17 00:00:00 2001 From: Ryan Christian <33403762+rschristian@users.noreply.github.com> Date: Tue, 31 Dec 2024 13:28:12 -0600 Subject: [PATCH 39/46] refactor: Fix source maps, error code frames, and other misc items (#149) --- src/prerender.ts | 68 ++++++++++++++++++++++++++++++------------------ 1 file changed, 43 insertions(+), 25 deletions(-) diff --git a/src/prerender.ts b/src/prerender.ts index 3d46270..edddc4c 100644 --- a/src/prerender.ts +++ b/src/prerender.ts @@ -78,14 +78,18 @@ export function PrerenderPlugin({ renderTarget, additionalPrerenderRoutes, }: PrerenderPluginOptions = {}): Plugin { - const preloadHelperId = "vite/preload-helper"; - const preloadPolyfillId = "vite/modulepreload-polyfill"; let viteConfig = {} as ResolvedConfig; let userEnabledSourceMaps: boolean | undefined; renderTarget ||= "body"; additionalPrerenderRoutes ||= []; + const preloadHelperId = "vite/preload-helper"; + const preloadPolyfillId = "vite/modulepreload-polyfill"; + // PNPM, Yalc, and anything else utilizing symlinks mangle the file + // path a bit so we need a minimal, fairly unique ID to check against + const tmpDirId = "headless-prerender"; + /** * From the non-external scripts in entry HTML document, find the one (if any) * that provides a `prerender` export @@ -123,13 +127,16 @@ export function PrerenderPlugin({ name: "preact:prerender", apply: "build", enforce: "post", - configResolved(config) { - userEnabledSourceMaps = !!config.build.sourcemap; + // As of Vite 6, `sourcemap` can *only* be set in `config` and + // `manualChunks` can *only* be set in `configResolved`. + config(config) { + userEnabledSourceMaps = !!config.build?.sourcemap; + // Enable sourcemaps for generating more actionable error messages + config.build ??= {}; config.build.sourcemap = true; - - viteConfig = config; - + }, + configResolved(config) { // With this plugin adding an additional input, Rollup/Vite tries to be smart // and extract our prerender script (which is often their main bundle) to a separate // chunk that the entry & prerender chunks can depend on. Unfortunately, this means the @@ -157,6 +164,8 @@ export function PrerenderPlugin({ return "index"; } }; + + viteConfig = config; }, async options(opts) { if (!opts.input) return; @@ -263,7 +272,7 @@ export function PrerenderPlugin({ viteConfig.root, "node_modules", "@preact/preset-vite", - "headless-prerender", + tmpDirId, ); try { await fs.rm(tmpDir, { recursive: true }); @@ -279,18 +288,6 @@ export function PrerenderPlugin({ let prerenderEntry: OutputChunk | undefined; for (const output of Object.keys(bundle)) { - // Clean up source maps if the user didn't enable them themselves - if (!userEnabledSourceMaps) { - if (output.endsWith(".map")) { - delete bundle[output]; - continue; - } - if (output.endsWith(".js") && bundle[output].type == "chunk") { - (bundle[output] as OutputChunk).code = (bundle[ - output - ] as OutputChunk).code.replace(/\n\/\/#\ssourceMappingURL=.*/, ""); - } - } if (!output.endsWith(".js") || bundle[output].type !== "chunk") continue; @@ -316,10 +313,6 @@ export function PrerenderPlugin({ ); prerender = m.prerender; } catch (e) { - const stack = await import("stack-trace").then(({ parse }) => - parse(e as Error).find(s => s.getFileName().includes(tmpDir)), - ); - const isReferenceError = e instanceof ReferenceError; let message = `\n ${e} @@ -335,6 +328,10 @@ export function PrerenderPlugin({ } `.replace(/^\t{5}/gm, ""); + const stack = await import("stack-trace").then(({ parse }) => + parse(e as Error).find(s => s.getFileName().includes(tmpDirId)), + ); + const sourceMapContent = prerenderEntry.map; if (stack && sourceMapContent) { await SourceMapConsumer.with( @@ -407,7 +404,10 @@ export function PrerenderPlugin({ } const result = await prerender({ ssr: true, url: route.url, route }); - if (result == null) continue; + if (result == null) { + this.warn(`No result returned for route "${route.url}"`); + continue; + } // Reset HTML doc & head data htmlDoc = htmlParse(tpl); @@ -485,6 +485,24 @@ export function PrerenderPlugin({ fileName: assetName, source: htmlDoc.toString(), }); + + // Clean up source maps if the user didn't enable them themselves + if (!userEnabledSourceMaps) { + for (const output of Object.keys(bundle)) { + if (output.endsWith(".map")) { + delete bundle[output]; + continue; + } + if (output.endsWith(".js")) { + const codeOrSource = + bundle[output].type == "chunk" ? "code" : "source"; + // @ts-ignore + bundle[output][codeOrSource] = bundle[output][ + codeOrSource + ].replace(/\n\/\/#\ssourceMappingURL=.*/, ""); + } + } + } } }, }; From d12a511b022fb8486aa79326843942ae371a54d0 Mon Sep 17 00:00:00 2001 From: Ryan Christian <33403762+rschristian@users.noreply.github.com> Date: Tue, 31 Dec 2024 18:12:24 -0600 Subject: [PATCH 40/46] refactor: Silence Rollup warnings regarding source maps & use directives (#150) --- src/index.ts | 20 +++++++++++++++++--- 1 file changed, 17 insertions(+), 3 deletions(-) diff --git a/src/index.ts b/src/index.ts index 8063bcf..98b0edc 100644 --- a/src/index.ts +++ b/src/index.ts @@ -153,9 +153,23 @@ function preactPlugin({ build: { rollupOptions: { onwarn(warning, warn) { - // Silence Rollup's module-level directive warnings -- they're likely - // to all come from `node_modules` (RSCs) and won't be actionable. - if (warning.code === "MODULE_LEVEL_DIRECTIVE") return; + // Silence Rollup's module-level directive warnings re:"use client". + // They're likely to come from `node_modules` and won't be actionable. + if ( + warning.code === "MODULE_LEVEL_DIRECTIVE" && + warning.message.includes("use client") + ) + return; + // ESBuild seemingly doesn't include mappings for directives, causing + // Rollup to emit warnings about missing source locations. This too is + // likely to come from `node_modules` and won't be actionable. + // evanw/esbuild#3548 + if ( + warning.code === "SOURCEMAP_ERROR" && + warning.message.includes("resolve original location") && + warning.pos === 0 + ) + return; warn(warning); }, }, From 8f99887173818fce7f91ed87715be0671baf1962 Mon Sep 17 00:00:00 2001 From: Ryan Christian <33403762+rschristian@users.noreply.github.com> Date: Sat, 4 Jan 2025 02:00:28 -0600 Subject: [PATCH 41/46] 2.9.4 (#152) --- package-lock.json | 4 ++-- package.json | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/package-lock.json b/package-lock.json index 02d4528..77e4657 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "@preact/preset-vite", - "version": "2.9.3", + "version": "2.9.4", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "@preact/preset-vite", - "version": "2.9.3", + "version": "2.9.4", "license": "MIT", "dependencies": { "@babel/code-frame": "^7.22.13", diff --git a/package.json b/package.json index 23c89f5..1ccfb4d 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@preact/preset-vite", - "version": "2.9.3", + "version": "2.9.4", "description": "Preact preset for the vite bundler", "main": "./dist/cjs/index.js", "module": "./dist/esm/index.mjs", From 66b9069af56ecc08f00a9465c36efb1fb40da8d6 Mon Sep 17 00:00:00 2001 From: Ryan Christian <33403762+rschristian@users.noreply.github.com> Date: Thu, 9 Jan 2025 22:03:52 -0600 Subject: [PATCH 42/46] refactor: Switch to `vite-prerender-plugin` (#153) * refactor: Switch to `vite-prerender-plugin` * revert: Support `previewMiddlewareEnabled` yet * revert: Test script alterations for Node 22+ * chore: Bump vite-prerender-plugin --- package-lock.json | 55 +++-- package.json | 8 +- src/index.ts | 21 +- src/prerender.ts | 555 -------------------------------------------- test/build.test.mjs | 22 -- 5 files changed, 50 insertions(+), 611 deletions(-) delete mode 100644 src/prerender.ts diff --git a/package-lock.json b/package-lock.json index 77e4657..307598d 100644 --- a/package-lock.json +++ b/package-lock.json @@ -9,7 +9,6 @@ "version": "2.9.4", "license": "MIT", "dependencies": { - "@babel/code-frame": "^7.22.13", "@babel/plugin-transform-react-jsx": "^7.22.15", "@babel/plugin-transform-react-jsx-development": "^7.22.5", "@prefresh/vite": "^2.4.1", @@ -17,10 +16,7 @@ "babel-plugin-transform-hook-names": "^1.0.2", "debug": "^4.3.4", "kolorist": "^1.8.0", - "magic-string": "0.30.5", - "node-html-parser": "^6.1.10", - "source-map": "^0.7.4", - "stack-trace": "^1.0.0-pre2" + "vite-prerender-plugin": "^0.5.3" }, "devDependencies": { "@babel/core": "^7.15.8", @@ -548,9 +544,10 @@ } }, "node_modules/@jridgewell/sourcemap-codec": { - "version": "1.4.15", - "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.15.tgz", - "integrity": "sha512-eF2rxCRulEKXHTRiDrDy6erMYWqNw4LPdQ8UQA4huuxaQsVeRPFl2oM8oDGxMFhJUWZf9McpLtJasDDZb/Bpeg==" + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.0.tgz", + "integrity": "sha512-gv3ZRaISU3fjPAgNsriBRqGWQL6quFx04YMPW/zD8XMLsU32mhCCbfbO6KZFLjvYpCZ8zyDEgqsgf+PwPaM7GQ==", + "license": "MIT" }, "node_modules/@jridgewell/trace-mapping": { "version": "0.3.19", @@ -1943,17 +1940,6 @@ "yallist": "^3.0.2" } }, - "node_modules/magic-string": { - "version": "0.30.5", - "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.5.tgz", - "integrity": "sha512-7xlpfBaQaP/T6Vh8MO/EqXSW5En6INHEvEXQiuff7Gku0PWjU3uf6w/j9o7O+SpB5fOAkrI5HeoNgwjEO0pFsA==", - "dependencies": { - "@jridgewell/sourcemap-codec": "^1.4.15" - }, - "engines": { - "node": ">=12" - } - }, "node_modules/make-error": { "version": "1.3.6", "resolved": "https://registry.npmjs.org/make-error/-/make-error-1.3.6.tgz", @@ -2405,6 +2391,15 @@ "integrity": "sha512-VUJ49FC8U1OxwZLxIbTTrDvLnf/6TDgxZcK8wxR8zs13xpx7xbG60ndBlhNrFi2EMuFRoeDoJO7wthSLq42EjA==", "dev": true }, + "node_modules/simple-code-frame": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/simple-code-frame/-/simple-code-frame-1.3.0.tgz", + "integrity": "sha512-MB4pQmETUBlNs62BBeRjIFGeuy/x6gGKh7+eRUemn1rCFhqo7K+4slPqsyizCbcbYLnaYqaoZ2FWsZ/jN06D8w==", + "license": "MIT", + "dependencies": { + "kolorist": "^1.6.0" + } + }, "node_modules/simple-git-hooks": { "version": "2.9.0", "resolved": "https://registry.npmjs.org/simple-git-hooks/-/simple-git-hooks-2.9.0.tgz", @@ -2701,6 +2696,28 @@ } } }, + "node_modules/vite-prerender-plugin": { + "version": "0.5.3", + "resolved": "https://registry.npmjs.org/vite-prerender-plugin/-/vite-prerender-plugin-0.5.3.tgz", + "integrity": "sha512-l/9FFDEO2xK+g0z6X/YCi28Q+uDdrYct6Qqsp46pPxl+tnpPrLBsa4J/t0iwOFCqvi6oBmjRTiob7wsKBJtOOg==", + "license": "MIT", + "dependencies": { + "magic-string": "^0.30.6", + "node-html-parser": "^6.1.12", + "simple-code-frame": "^1.3.0", + "source-map": "^0.7.4", + "stack-trace": "^1.0.0-pre2" + } + }, + "node_modules/vite-prerender-plugin/node_modules/magic-string": { + "version": "0.30.17", + "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.17.tgz", + "integrity": "sha512-sNPKHvyjVf7gyjwS4xGTaW/mCnF8wnjtifKBEhxfZ7E/S8tQ0rssrwGNn6q8JH/ohItJfSQp9mBtQYuTlH5QnA==", + "license": "MIT", + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.5.0" + } + }, "node_modules/which": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", diff --git a/package.json b/package.json index 1ccfb4d..bee4f19 100644 --- a/package.json +++ b/package.json @@ -17,7 +17,7 @@ "dev:build": "vite build demo", "dev:preview": "vite preview demo", "build": "rimraf dist && tsc && tsc -p tsconfig.cjs.json && node tools/postbuild.mjs", - "test": "node --test test", + "test": "rimraf demo/node_modules && node --test test", "prepublishOnly": "npm run build" }, "keywords": [ @@ -36,7 +36,6 @@ "dist/" ], "dependencies": { - "@babel/code-frame": "^7.22.13", "@babel/plugin-transform-react-jsx": "^7.22.15", "@babel/plugin-transform-react-jsx-development": "^7.22.5", "@prefresh/vite": "^2.4.1", @@ -44,10 +43,7 @@ "babel-plugin-transform-hook-names": "^1.0.2", "debug": "^4.3.4", "kolorist": "^1.8.0", - "magic-string": "0.30.5", - "node-html-parser": "^6.1.10", - "source-map": "^0.7.4", - "stack-trace": "^1.0.0-pre2" + "vite-prerender-plugin": "^0.5.3" }, "peerDependencies": { "@babel/core": "7.x", diff --git a/src/index.ts b/src/index.ts index 98b0edc..145ffc6 100644 --- a/src/index.ts +++ b/src/index.ts @@ -6,7 +6,7 @@ import type { TransformOptions } from "@babel/core"; import prefresh from "@prefresh/vite"; import { preactDevtoolsPlugin } from "./devtools.js"; import { createFilter, parseId } from "./utils.js"; -import { PrerenderPlugin, HTMLRoutingMiddlewarePlugin } from "./prerender.js"; +import { vitePrerenderPlugin } from "vite-prerender-plugin"; import { transformAsync } from "@babel/core"; export type BabelOptions = Omit< @@ -145,6 +145,16 @@ function preactPlugin({ reactAliasesEnabled = reactAliasesEnabled ?? true; prerender = prerender ?? { enabled: false }; + const prerenderPlugin = vitePrerenderPlugin(prerender); + if (!prerender.previewMiddlewareEnabled) { + const idx = prerenderPlugin.findIndex( + p => p.name == "serve-prerendered-html", + ); + if (idx > -1) { + prerenderPlugin.splice(idx, 1); + } + } + const jsxPlugin: Plugin = { name: "vite:preact-jsx", enforce: "pre", @@ -275,14 +285,7 @@ function preactPlugin({ ...(prefreshEnabled ? [prefresh({ include, exclude, parserPlugins: baseParserOptions })] : []), - ...(prerender.enabled ? [PrerenderPlugin(prerender)] : []), - ...(prerender.previewMiddlewareEnabled - ? [ - HTMLRoutingMiddlewarePlugin({ - fallback: prerender.previewMiddlewareFallback, - }), - ] - : []), + ...(prerender.enabled ? prerenderPlugin : []), ]; } diff --git a/src/prerender.ts b/src/prerender.ts deleted file mode 100644 index edddc4c..0000000 --- a/src/prerender.ts +++ /dev/null @@ -1,555 +0,0 @@ -import path from "node:path"; -import { promises as fs } from "node:fs"; - -import MagicString from "magic-string"; -import { parse as htmlParse } from "node-html-parser"; -import { SourceMapConsumer } from "source-map"; -import { codeFrameColumns } from "@babel/code-frame"; - -import type { Plugin, ResolvedConfig } from "vite"; - -// Vite re-exports Rollup's type defs in newer versions, -// merge into above type import when we bump the Vite devDep -import type { - InputOption, - OutputAsset, - OutputChunk, - OutputOptions, -} from "rollup"; - -interface HeadElement { - type: string; - props: Record<string, string>; - children?: string; -} - -interface Head { - lang: string; - title: string; - elements: Set<HeadElement>; -} - -interface PrerenderedRoute { - url: string; - _discoveredBy?: PrerenderedRoute; -} - -function enc(str: string) { - return str - .replace(/&/g, "&") - .replace(/"/g, """) - .replace(/</g, "<") - .replace(/>/g, ">"); -} - -function serializeElement( - element: HeadElement | HeadElement[] | string, -): string { - if (element == null) return ""; - if (typeof element !== "object") return String(element); - if (Array.isArray(element)) return element.map(serializeElement).join(""); - const type = element.type; - let s = `<${type}`; - const props = element.props || {}; - let children = element.children; - for (const prop of Object.keys(props)) { - const value = props[prop]; - // Filter out empty values: - if (value == null) continue; - if (prop === "children" || prop === "textContent") children = value; - else s += ` ${prop}="${enc(value)}"`; - } - s += ">"; - if (!/link|meta|base/.test(type)) { - if (children) s += serializeElement(children); - s += `</${type}>`; - } - return s; -} - -interface PrerenderPluginOptions { - prerenderScript?: string; - renderTarget?: string; - additionalPrerenderRoutes?: string[]; -} - -export function PrerenderPlugin({ - prerenderScript, - renderTarget, - additionalPrerenderRoutes, -}: PrerenderPluginOptions = {}): Plugin { - let viteConfig = {} as ResolvedConfig; - let userEnabledSourceMaps: boolean | undefined; - - renderTarget ||= "body"; - additionalPrerenderRoutes ||= []; - - const preloadHelperId = "vite/preload-helper"; - const preloadPolyfillId = "vite/modulepreload-polyfill"; - // PNPM, Yalc, and anything else utilizing symlinks mangle the file - // path a bit so we need a minimal, fairly unique ID to check against - const tmpDirId = "headless-prerender"; - - /** - * From the non-external scripts in entry HTML document, find the one (if any) - * that provides a `prerender` export - */ - const getPrerenderScriptFromHTML = async (input: InputOption) => { - // prettier-ignore - const entryHtml = - typeof input === "string" - ? input - : Array.isArray(input) - ? input.find(i => /html$/.test(i)) - : Object.values(input).find(i => /html$/.test(i)); - - if (!entryHtml) throw new Error("Unable to detect entry HTML"); - - const htmlDoc = htmlParse(await fs.readFile(entryHtml, "utf-8")); - - const entryScriptTag = htmlDoc - .getElementsByTagName("script") - .find(s => s.hasAttribute("prerender")); - - if (!entryScriptTag) - throw new Error("Unable to detect prerender entry script"); - - const entrySrc = entryScriptTag.getAttribute("src"); - if (!entrySrc || /^https:/.test(entrySrc)) - throw new Error( - "Prerender entry script must have a `src` attribute and be local", - ); - - return path.join(viteConfig.root, entrySrc); - }; - - return { - name: "preact:prerender", - apply: "build", - enforce: "post", - // As of Vite 6, `sourcemap` can *only* be set in `config` and - // `manualChunks` can *only* be set in `configResolved`. - config(config) { - userEnabledSourceMaps = !!config.build?.sourcemap; - - // Enable sourcemaps for generating more actionable error messages - config.build ??= {}; - config.build.sourcemap = true; - }, - configResolved(config) { - // With this plugin adding an additional input, Rollup/Vite tries to be smart - // and extract our prerender script (which is often their main bundle) to a separate - // chunk that the entry & prerender chunks can depend on. Unfortunately, this means the - // first script the browser loads is the module preload polyfill & a sync import of the main - // bundle. This is obviously less than ideal as the main bundle should be directly referenced - // by the user's HTML to speed up loading a bit. - - // We're only going to alter the chunking behavior in the default cases, where the user and/or - // other plugins haven't already configured this. It'd be impossible to avoid breakages otherwise. - if ( - Array.isArray(config.build.rollupOptions.output) || - (config.build.rollupOptions.output as OutputOptions)?.manualChunks - ) { - return; - } - - config.build.rollupOptions.output ??= {}; - (config.build.rollupOptions.output as OutputOptions).manualChunks = ( - id: string, - ) => { - if ( - id.includes(prerenderScript as string) || - id.includes(preloadPolyfillId) - ) { - return "index"; - } - }; - - viteConfig = config; - }, - async options(opts) { - if (!opts.input) return; - if (!prerenderScript) { - prerenderScript = await getPrerenderScriptFromHTML(opts.input); - } - - // prettier-ignore - opts.input = - typeof opts.input === "string" - ? [opts.input, prerenderScript] - : Array.isArray(opts.input) - ? [...opts.input, prerenderScript] - : { ...opts.input, prerenderEntry: prerenderScript }; - opts.preserveEntrySignatures = "allow-extension"; - }, - // Injects window checks into Vite's preload helper & modulepreload polyfill - transform(code, id) { - if (id.includes(preloadHelperId)) { - // Injects a window check into Vite's preload helper, instantly resolving - // the module rather than attempting to add a <link> to the document. - const s = new MagicString(code); - - // Through v5.0.4 - // https://github.com/vitejs/vite/blob/b93dfe3e08f56cafe2e549efd80285a12a3dc2f0/packages/vite/src/node/plugins/importAnalysisBuild.ts#L95-L98 - s.replace( - `if (!__VITE_IS_MODERN__ || !deps || deps.length === 0) {`, - `if (!__VITE_IS_MODERN__ || !deps || deps.length === 0 || typeof window === 'undefined') {`, - ); - // 5.0.5+ - // https://github.com/vitejs/vite/blob/c902545476a4e7ba044c35b568e73683758178a3/packages/vite/src/node/plugins/importAnalysisBuild.ts#L93 - s.replace( - `if (__VITE_IS_MODERN__ && deps && deps.length > 0) {`, - `if (__VITE_IS_MODERN__ && deps && deps.length > 0 && typeof window !== 'undefined') {`, - ); - return { - code: s.toString(), - map: s.generateMap({ hires: true }), - }; - } else if (id.includes(preloadPolyfillId)) { - const s = new MagicString(code); - // Replacement for `'link'` && `"link"` as the output from their tooling has - // differed over the years. Should be better than switching to regex. - // https://github.com/vitejs/vite/blob/20fdf210ee0ac0824b2db74876527cb7f378a9e8/packages/vite/src/node/plugins/modulePreloadPolyfill.ts#L62 - s.replace( - `const relList = document.createElement('link').relList;`, - `if (typeof window === "undefined") return;\n const relList = document.createElement('link').relList;`, - ); - s.replace( - `const relList = document.createElement("link").relList;`, - `if (typeof window === "undefined") return;\n const relList = document.createElement("link").relList;`, - ); - return { - code: s.toString(), - map: s.generateMap({ hires: true }), - }; - } - }, - async generateBundle(_opts, bundle) { - // @ts-ignore - globalThis.location = {}; - // @ts-ignore - globalThis.self = globalThis; - - // As of Vite 5.3.0-beta.0, Vite injects an undefined `__VITE_PRELOAD__` var - // Swapping in an empty array is fine as we have no need to preload whilst prerendering - // https://github.com/vitejs/vite/pull/16562 - // @ts-ignore - globalThis.__VITE_PRELOAD__ = []; - - // Local, fs-based fetch implementation for prerendering - // @ts-ignore - globalThis.unpatchedFetch = globalThis.fetch; - // @ts-ignore - globalThis.fetch = async (url: string, opts: RequestInit | undefined) => { - if (/^\//.test(url)) { - try { - return new Response( - await fs.readFile( - `${path.join( - viteConfig.root, - viteConfig.build.outDir, - )}/${url.replace(/^\//, "")}`, - "utf-8", - ), - ); - } catch (e: any) { - if (e.code !== "ENOENT") throw e; - return new Response(null, { status: 404 }); - } - } - - // @ts-ignore - return globalThis.unpatchedFetch(url, opts); - }; - - // Grab the generated HTML file, which we'll use as a template: - const tpl = (bundle["index.html"] as OutputAsset).source as string; - let htmlDoc = htmlParse(tpl); - - // Create a tmp dir to allow importing & consuming the built modules, - // before Rollup writes them to the disk - const tmpDir = path.join( - viteConfig.root, - "node_modules", - "@preact/preset-vite", - tmpDirId, - ); - try { - await fs.rm(tmpDir, { recursive: true }); - } catch (e: any) { - if (e.code !== "ENOENT") throw e; - } - await fs.mkdir(tmpDir, { recursive: true }); - - await fs.writeFile( - path.join(tmpDir, "package.json"), - JSON.stringify({ type: "module" }), - ); - - let prerenderEntry: OutputChunk | undefined; - for (const output of Object.keys(bundle)) { - if (!output.endsWith(".js") || bundle[output].type !== "chunk") - continue; - - await fs.writeFile( - path.join(tmpDir, path.basename(output)), - (bundle[output] as OutputChunk).code, - ); - - if ((bundle[output] as OutputChunk).exports?.includes("prerender")) { - prerenderEntry = bundle[output] as OutputChunk; - } - } - if (!prerenderEntry) { - this.error("Cannot detect module with `prerender` export"); - } - - let head: Head = { lang: "", title: "", elements: new Set() }; - - let prerender; - try { - const m = await import( - `file://${path.join(tmpDir, path.basename(prerenderEntry!.fileName))}` - ); - prerender = m.prerender; - } catch (e) { - const isReferenceError = e instanceof ReferenceError; - let message = `\n - ${e} - - This ${ - isReferenceError ? "is most likely" : "could be" - } caused by using DOM/Web APIs which are not available - available to the prerendering process running in Node. Consider - wrapping the offending code in a window check like so: - - if (typeof window !== "undefined") { - // do something in browsers only - } - `.replace(/^\t{5}/gm, ""); - - const stack = await import("stack-trace").then(({ parse }) => - parse(e as Error).find(s => s.getFileName().includes(tmpDirId)), - ); - - const sourceMapContent = prerenderEntry.map; - if (stack && sourceMapContent) { - await SourceMapConsumer.with( - sourceMapContent, - null, - async consumer => { - let { source, line, column } = consumer.originalPositionFor({ - line: stack.getLineNumber(), - column: stack.getColumnNumber(), - }); - - if (!source || line == null || column == null) { - message += `\nUnable to locate source map for error!\n`; - this.error(message); - } - - // `source-map` returns 0-indexed column numbers - column += 1; - - const sourcePath = path.join( - viteConfig.root, - source.replace(/^(..\/)*/, ""), - ); - const sourceContent = await fs.readFile(sourcePath, "utf-8"); - - const frame = codeFrameColumns(sourceContent, { - start: { line, column }, - }); - message += ` - > ${sourcePath}:${line}:${column}\n - ${frame} - `.replace(/^\t{7}/gm, ""); - }, - ); - } - - this.error(message); - } - - if (typeof prerender !== "function") { - this.error("Detected `prerender` export, but it is not a function"); - } - - // We start by pre-rendering the home page. - // Links discovered during pre-rendering get pushed into the list of routes. - const seen = new Set(["/", ...additionalPrerenderRoutes!]); - - let routes: PrerenderedRoute[] = [...seen].map(link => ({ url: link })); - - for (const route of routes) { - if (!route.url) continue; - - const outDir = route.url.replace(/(^\/|\/$)/g, ""); - const assetName = path.join( - outDir, - outDir.endsWith(".html") ? "" : "index.html", - ); - - // Update `location` to current URL so routers can use things like `location.pathname` - const u = new URL(route.url, "http://localhost"); - for (const i in u) { - try { - // @ts-ignore - globalThis.location[i] = - i === "toString" - ? u[i].bind(u) - : // @ts-ignore - String(u[i]); - } catch {} - } - - const result = await prerender({ ssr: true, url: route.url, route }); - if (result == null) { - this.warn(`No result returned for route "${route.url}"`); - continue; - } - - // Reset HTML doc & head data - htmlDoc = htmlParse(tpl); - head = { lang: "", title: "", elements: new Set() }; - - // Add any discovered links to the list of routes to pre-render: - if (result.links) { - for (let url of result.links) { - const parsed = new URL(url, "http://localhost"); - url = parsed.pathname.replace(/\/$/, "") || "/"; - // ignore external links and ones we've already picked up - if (seen.has(url) || parsed.origin !== "http://localhost") continue; - seen.add(url); - routes.push({ url, _discoveredBy: route }); - } - } - - let body; - if (result && typeof result === "object") { - if (typeof result.html !== "undefined") body = result.html; - if (result.head) { - head = result.head; - } - if (result.data) { - body += `<script type="application/json" id="preact-prerender-data">${JSON.stringify( - result.data, - )}</script>`; - } - } else { - body = result; - } - - const htmlHead = htmlDoc.querySelector("head"); - if (htmlHead) { - if (head.title) { - const htmlTitle = htmlHead.querySelector("title"); - htmlTitle - ? htmlTitle.set_content(enc(head.title)) - : htmlHead.insertAdjacentHTML( - "afterbegin", - `<title>${enc(head.title)}`, - ); - } - - if (head.lang) { - htmlDoc.querySelector("html")!.setAttribute("lang", enc(head.lang)); - } - - if (head.elements) { - // Inject HTML links at the end of for any stylesheets injected during rendering of the page: - htmlHead.insertAdjacentHTML( - "beforeend", - Array.from( - new Set(Array.from(head.elements).map(serializeElement)), - ).join("\n"), - ); - } - } - - const target = htmlDoc.querySelector(renderTarget!); - if (!target) - this.error( - result.renderTarget == "body" - ? "`renderTarget` was not specified in plugin options and does not exist in input HTML template" - : `Unable to detect prerender renderTarget "${result.selector}" in input HTML template`, - ); - target.insertAdjacentHTML("afterbegin", body); - - // Add generated HTML to compilation: - if (route.url === "/") - (bundle["index.html"] as OutputAsset).source = htmlDoc.toString(); - else - this.emitFile({ - type: "asset", - fileName: assetName, - source: htmlDoc.toString(), - }); - - // Clean up source maps if the user didn't enable them themselves - if (!userEnabledSourceMaps) { - for (const output of Object.keys(bundle)) { - if (output.endsWith(".map")) { - delete bundle[output]; - continue; - } - if (output.endsWith(".js")) { - const codeOrSource = - bundle[output].type == "chunk" ? "code" : "source"; - // @ts-ignore - bundle[output][codeOrSource] = bundle[output][ - codeOrSource - ].replace(/\n\/\/#\ssourceMappingURL=.*/, ""); - } - } - } - } - }, - }; -} - -interface HTMLRoutingMiddlewareOptions { - fallback?: string; -} - -/** - * Vite's preview server won't route to anything but `/index.html` without - * a file extension, e.g., `/tutorial` won't serve `/tutorial/index.html`. - * This leads to some surprises & hydration issues, so we'll fix it ourselves. - */ -export function HTMLRoutingMiddlewarePlugin({ - fallback, -}: HTMLRoutingMiddlewareOptions = {}): Plugin { - let outDir: string; - - return { - name: "serve-prerendered-html", - configResolved(config) { - outDir = path.resolve(config.root, config.build.outDir); - }, - configurePreviewServer(server) { - server.middlewares.use(async (req, _res, next) => { - if (!req.url) return next(); - - const url = new URL(req.url, `http://${req.headers.host}`); - // If URL has a file extension, bail - if (url.pathname != url.pathname.split(".").pop()) return next(); - - const file = path.join( - outDir, - url.pathname.split(path.posix.sep).join(path.sep), - "index.html", - ); - - try { - await fs.access(file); - req.url = url.pathname + "/index.html" + url.search; - } catch { - req.url = (fallback || "") + "/index.html"; - } - - return next(); - }); - }, - }; -} diff --git a/test/build.test.mjs b/test/build.test.mjs index 06b2f2a..086cd15 100644 --- a/test/build.test.mjs +++ b/test/build.test.mjs @@ -2,7 +2,6 @@ import { execFile } from "node:child_process"; import { test } from "node:test"; import { promisify } from "node:util"; import { promises as fs } from "node:fs"; -import path from "node:path"; import assert from "node:assert"; import { dir } from "./util.mjs"; @@ -18,25 +17,4 @@ test("builds demo successfully", async () => { const outputHtml = await fs.readFile(dir("demo/dist/index.html"), "utf-8"); assert.match(outputHtml, /Get Started building Vite-powered Preact Apps/); - - // Head API - assert.match(outputHtml, //); - assert.match(outputHtml, /Prerendered Preact App<\/title>/); - assert.match(outputHtml, /<meta name="description" content="This is a prerendered Preact app">/); - - // Prerender Data - assert.match( - outputHtml, - /<script type="application\/json" id="preact-prerender-data">{"url":"\/"}<\/script>/ - ); - - // Local Fetch - assert.match(outputHtml, /Local fetch works/); - - // `additionalPrerenderRoutes` config option - assert.doesNotThrow(async () => await fs.access(dir("demo/dist/404/index.html"))); - - const outputFiles = await fs.readdir(path.join(dir("demo/dist"), 'assets')); - const outputIndexJS = outputFiles.filter(f => /^index\..+\.js$/.test(f)); - assert.strictEqual(outputIndexJS.length, 1); }); From 0a44feb8e7c6445810d77e1870abf4918d7c37f3 Mon Sep 17 00:00:00 2001 From: Marvin Hagemeister <hello@marvinh.dev> Date: Sun, 19 Jan 2025 19:37:58 +0100 Subject: [PATCH 43/46] fix: deno support (#154) --- src/index.ts | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/src/index.ts b/src/index.ts index 145ffc6..2f3cea1 100644 --- a/src/index.ts +++ b/src/index.ts @@ -8,6 +8,12 @@ import { preactDevtoolsPlugin } from "./devtools.js"; import { createFilter, parseId } from "./utils.js"; import { vitePrerenderPlugin } from "vite-prerender-plugin"; import { transformAsync } from "@babel/core"; +// @ts-ignore package doesn't ship with declaration files +import babelReactJsx from "@babel/plugin-transform-react-jsx"; +// @ts-ignore package doesn't ship with declaration files +import babelReactJsxDev from "@babel/plugin-transform-react-jsx-development"; +// @ts-ignore package doesn't ship with declaration files +import babelHookNames from "babel-plugin-transform-hook-names"; export type BabelOptions = Omit< TransformOptions, @@ -234,15 +240,13 @@ function preactPlugin({ plugins: [ ...babelOptions.plugins, [ - config.isProduction - ? "@babel/plugin-transform-react-jsx" - : "@babel/plugin-transform-react-jsx-development", + config.isProduction ? babelReactJsx : babelReactJsxDev, { runtime: "automatic", importSource: jsxImportSource ?? "preact", }, ], - ...(devToolsEnabled ? ["babel-plugin-transform-hook-names"] : []), + ...(devToolsEnabled ? [babelHookNames] : []), ], sourceMaps: true, inputSourceMap: false as any, From 7babf30c401a30fdc5e224758561752dca63b635 Mon Sep 17 00:00:00 2001 From: Marvin Hagemeister <hello@marvinh.dev> Date: Sun, 19 Jan 2025 21:56:56 +0100 Subject: [PATCH 44/46] 2.10.0 --- package-lock.json | 4 ++-- package.json | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/package-lock.json b/package-lock.json index 307598d..eceb9af 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "@preact/preset-vite", - "version": "2.9.4", + "version": "2.10.0", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "@preact/preset-vite", - "version": "2.9.4", + "version": "2.10.0", "license": "MIT", "dependencies": { "@babel/plugin-transform-react-jsx": "^7.22.15", diff --git a/package.json b/package.json index bee4f19..bdb527f 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@preact/preset-vite", - "version": "2.9.4", + "version": "2.10.0", "description": "Preact preset for the vite bundler", "main": "./dist/cjs/index.js", "module": "./dist/esm/index.mjs", From 304b0c200bc50c2add857812879cf260d289fb3e Mon Sep 17 00:00:00 2001 From: Florian Lefebvre <contact@florian-lefebvre.dev> Date: Sat, 1 Feb 2025 00:32:26 +0100 Subject: [PATCH 45/46] fix: do not set jsxImportSource when using babel (#156) * fix: do not set jsxImportSource when using babel * Apply suggestions from code review * revert: TS is dumb --------- Co-authored-by: Ryan Christian <33403762+rschristian@users.noreply.github.com> --- src/index.ts | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/src/index.ts b/src/index.ts index 2f3cea1..dbfd791 100644 --- a/src/index.ts +++ b/src/index.ts @@ -140,7 +140,7 @@ function preactPlugin({ babelOptions.parserOpts ||= {} as any; babelOptions.parserOpts.plugins ||= []; - let useBabel: boolean; + let useBabel = typeof babel !== "undefined"; const shouldTransform = createFilter( include || [/\.[cm]?[tj]sx?$/], exclude || [/node_modules/], @@ -190,11 +190,12 @@ function preactPlugin({ }, }, }, - // While this config is unconditional, it'll only be used if Babel is not - esbuild: { - jsx: "automatic", - jsxImportSource: jsxImportSource ?? "preact", - }, + esbuild: useBabel + ? undefined + : { + jsx: "automatic", + jsxImportSource: jsxImportSource ?? "preact", + }, optimizeDeps: { include: ["preact", "preact/jsx-runtime", "preact/jsx-dev-runtime"], }, @@ -204,8 +205,7 @@ function preactPlugin({ config = resolvedConfig; devToolsEnabled = devToolsEnabled ?? (!config.isProduction || devtoolsInProd); - useBabel = - !config.isProduction || devToolsEnabled || typeof babel !== "undefined"; + useBabel ||= !config.isProduction || !!devToolsEnabled; }, async transform(code, url) { // Ignore query parameters, as in Vue SFC virtual modules. From 43547a61f53da7a3d7609dd8b473debc9567e748 Mon Sep 17 00:00:00 2001 From: Ryan Christian <33403762+rschristian@users.noreply.github.com> Date: Sun, 2 Feb 2025 14:09:25 -0600 Subject: [PATCH 46/46] 2.10.1 (#157) --- package-lock.json | 4 ++-- package.json | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/package-lock.json b/package-lock.json index eceb9af..e346d3e 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "@preact/preset-vite", - "version": "2.10.0", + "version": "2.10.1", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "@preact/preset-vite", - "version": "2.10.0", + "version": "2.10.1", "license": "MIT", "dependencies": { "@babel/plugin-transform-react-jsx": "^7.22.15", diff --git a/package.json b/package.json index bdb527f..57217a1 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@preact/preset-vite", - "version": "2.10.0", + "version": "2.10.1", "description": "Preact preset for the vite bundler", "main": "./dist/cjs/index.js", "module": "./dist/esm/index.mjs",