From 1f1f432e31a2d49d3165277abdb358be784ba62a Mon Sep 17 00:00:00 2001 From: Marvin Hagemeister Date: Fri, 8 Aug 2025 00:07:40 +0200 Subject: [PATCH] fix: workaround vite treating `npm:` as built-ins Upstream fix: https://github.com/vitejs/vite/pull/20558 --- deno.lock | 16 +++++-- packages/plugin-vite/src/mod.ts | 4 +- packages/plugin-vite/src/plugins/commonjs.ts | 44 ++++------------- .../plugin-vite/src/plugins/npm_workaround.ts | 39 +++++++++++++++ .../src/plugins/npm_workaround_test.ts | 47 +++++++++++++++++++ packages/plugin-vite/src/plugins/patches.ts | 32 +++++++++++++ 6 files changed, 143 insertions(+), 39 deletions(-) create mode 100644 packages/plugin-vite/src/plugins/npm_workaround.ts create mode 100644 packages/plugin-vite/src/plugins/npm_workaround_test.ts create mode 100644 packages/plugin-vite/src/plugins/patches.ts diff --git a/deno.lock b/deno.lock index e64ff2b7f7d..87cc3a93990 100644 --- a/deno.lock +++ b/deno.lock @@ -43,6 +43,7 @@ "jsr:@std/internal@^1.0.9": "1.0.10", "jsr:@std/io@0.225": "0.225.2", "jsr:@std/io@0.225.0": "0.225.0", + "jsr:@std/json@^1.0.2": "1.0.2", "jsr:@std/jsonc@1": "1.0.2", "jsr:@std/media-types@1": "1.1.0", "jsr:@std/media-types@^1.1.0": "1.1.0", @@ -71,6 +72,7 @@ "npm:cssnano@^6.1.2": "6.1.2_postcss@8.5.6", "npm:esbuild-wasm@0.25.7": "0.25.7", "npm:esbuild@0.25.7": "0.25.7", + "npm:esbuild@~0.25.5": "0.25.7", "npm:fflate@~0.8.2": "0.8.2", "npm:github-slugger@2": "2.0.0", "npm:linkedom@~0.18.10": "0.18.11", @@ -87,6 +89,7 @@ "npm:tailwindcss@^4.1.10": "4.1.11", "npm:ts-morph@26": "26.0.0", "npm:vite-plugin-inspect@^11.3.2": "11.3.2_vite@7.0.6__@types+node@24.2.0__picomatch@4.0.3_@types+node@24.2.0_@types+node@22.15.15", + "npm:vite@*": "7.0.6_@types+node@24.2.0_picomatch@4.0.3_@types+node@22.15.15", "npm:vite@7.0.6": "7.0.6_@types+node@24.2.0_picomatch@4.0.3_@types+node@22.15.15", "npm:vite@^7.0.6": "7.0.6_@types+node@24.2.0_picomatch@4.0.3_@types+node@22.15.15" }, @@ -127,7 +130,8 @@ "integrity": "c9cde95990b97802a0da6c73c26ab4a48f30d286818845e365dddcd8297abd7d", "dependencies": [ "jsr:@deno/loader@~0.3.3", - "jsr:@std/path@^1.1.1" + "jsr:@std/path@^1.1.1", + "npm:esbuild@~0.25.5" ] }, "@deno/loader@0.3.4": { @@ -155,8 +159,8 @@ "jsr:@std/uuid", "npm:@opentelemetry/api", "npm:@preact/signals@^2.2.1", - "npm:esbuild", "npm:esbuild-wasm", + "npm:esbuild@0.25.7", "npm:preact-render-to-string", "npm:preact@^10.27.0" ] @@ -257,8 +261,14 @@ "jsr:@std/bytes@^1.0.5" ] }, + "@std/json@1.0.2": { + "integrity": "d9e5497801c15fb679f55a2c01c7794ad7a5dfda4dd1bebab5e409cb5e0d34d4" + }, "@std/jsonc@1.0.2": { - "integrity": "909605dae3af22bd75b1cbda8d64a32cf1fd2cf6efa3f9e224aba6d22c0f44c7" + "integrity": "909605dae3af22bd75b1cbda8d64a32cf1fd2cf6efa3f9e224aba6d22c0f44c7", + "dependencies": [ + "jsr:@std/json" + ] }, "@std/media-types@1.1.0": { "integrity": "c9d093f0c05c3512932b330e3cc1fe1d627b301db33a4c2c2185c02471d6eaa4" diff --git a/packages/plugin-vite/src/mod.ts b/packages/plugin-vite/src/mod.ts index 184d7ba58c0..48e7cea2247 100644 --- a/packages/plugin-vite/src/mod.ts +++ b/packages/plugin-vite/src/mod.ts @@ -12,7 +12,7 @@ import { devServer } from "./plugins/dev_server.ts"; import { buildIdPlugin } from "./plugins/build_id.ts"; import { clientSnapshot } from "./plugins/client_snapshot.ts"; import { serverSnapshot } from "./plugins/server_snapshot.ts"; -import { commonjs } from "./plugins/commonjs.ts"; +import { patches } from "./plugins/patches.ts"; export function fresh(config?: FreshViteConfig): Plugin[] { const fConfig: ResolvedFreshViteConfig = { @@ -109,7 +109,7 @@ export function fresh(config?: FreshViteConfig): Plugin[] { fConfig.routeDir = pathWithRoot(fConfig.routeDir, config.root); }, }, - commonjs(), + patches(), serverEntryPlugin(fConfig), ...serverSnapshot(fConfig), clientEntryPlugin(), diff --git a/packages/plugin-vite/src/plugins/commonjs.ts b/packages/plugin-vite/src/plugins/commonjs.ts index 3b08a82d696..41170d65960 100644 --- a/packages/plugin-vite/src/plugins/commonjs.ts +++ b/packages/plugin-vite/src/plugins/commonjs.ts @@ -1,31 +1,7 @@ -import type { Plugin } from "vite"; -import * as babel from "@babel/core"; - -export function commonjs(): Plugin { - return { - name: "cjs", - applyToEnvironment() { - return true; - }, - transform(code, id) { - const res = babel.transformSync(code, { - filename: id, - babelrc: false, - plugins: [cjsPlugin], - }); - - if (res?.code) { - return { - code: res.code, - map: res.map, - }; - } - }, - }; -} +import type { types } from "@babel/core"; export function cjsPlugin( - { types: t }: { types: typeof babel.types }, + { types: t }: { types: typeof types }, ): babel.PluginObj { const HAS_ES_MODULE = "esModule"; const REQUIRE_CALLS = "requireCalls"; @@ -143,16 +119,16 @@ export function cjsPlugin( } function isModuleExports( - t: typeof babel.types, - node: babel.types.MemberExpression, + t: typeof types, + node: types.MemberExpression, ): boolean { return t.isIdentifier(node.object) && node.object.name === "module" && t.isIdentifier(node.property) && node.property.name === "exports"; } function getExportsAssignName( - t: typeof babel.types, - node: babel.types.MemberExpression, + t: typeof types, + node: types.MemberExpression, ): string | null { if ( (t.isMemberExpression(node.object) && @@ -170,8 +146,8 @@ function getExportsAssignName( * Detect `exports.__esModule = true;` */ function isEsModuleFlag( - t: typeof babel.types, - node: babel.types.AssignmentExpression, + t: typeof types, + node: types.AssignmentExpression, ): boolean { if (!t.isMemberExpression(node.left)) return false; @@ -187,8 +163,8 @@ function isEsModuleFlag( * Check for `Object.defineProperty(exports, '__esModule', { value: true })` */ function isObjEsModuleFlag( - t: typeof babel.types, - node: babel.types.CallExpression, + t: typeof types, + node: types.CallExpression, ): boolean { return t.isMemberExpression(node.callee) && t.isIdentifier(node.callee.object) && diff --git a/packages/plugin-vite/src/plugins/npm_workaround.ts b/packages/plugin-vite/src/plugins/npm_workaround.ts new file mode 100644 index 00000000000..d24f21dc319 --- /dev/null +++ b/packages/plugin-vite/src/plugins/npm_workaround.ts @@ -0,0 +1,39 @@ +import type { types } from "@babel/core"; + +// Workaround until upstream PR is merged and released, +// see: https://github.com/vitejs/vite/pull/20558 +export function npmWorkaround( + { types: t }: { types: typeof types }, +): babel.PluginObj { + return { + name: "fresh-npm-workaround", + visitor: { + ImportDeclaration(path) { + const source = path.node.source; + if (source.value.startsWith("npm:")) { + source.value = `deno-${source.value}`; + } + }, + ExportNamedDeclaration(path) { + const source = path.node.source; + if (source === null || source === undefined) return; + + if (source.value.startsWith("npm:")) { + source.value = `deno-${source.value}`; + } + }, + CallExpression(path) { + if (!t.isImport(path.node.callee)) return; + if (path.node.arguments.length < 1) return; + + const source = path.node.arguments[0]; + if (t.isStringLiteral(source)) { + if (source.value.startsWith("npm:")) { + source.value = `deno-${source.value}`; + return; + } + } + }, + }, + }; +} diff --git a/packages/plugin-vite/src/plugins/npm_workaround_test.ts b/packages/plugin-vite/src/plugins/npm_workaround_test.ts new file mode 100644 index 00000000000..0eda7f738f9 --- /dev/null +++ b/packages/plugin-vite/src/plugins/npm_workaround_test.ts @@ -0,0 +1,47 @@ +import { expect } from "@std/expect/expect"; +import * as babel from "@babel/core"; +import { npmWorkaround } from "./npm_workaround.ts"; + +function runTest(options: { input: string; expected: string }) { + const res = babel.transformSync(options.input, { + filename: "foo.js", + babelrc: false, + plugins: [npmWorkaround], + }); + + const output = res?.code ?? ""; + expect(output).toEqual(options.expected); +} + +Deno.test("npm workaround - import declaration", () => { + runTest({ + input: `import * as foo from "npm:foo"; +import foo2 from "npm:foo"; +import { bar } from "npm:foo"; +import baz, { bar2 } from "npm:foo";`, + expected: `import * as foo from "deno-npm:foo"; +import foo2 from "deno-npm:foo"; +import { bar } from "deno-npm:foo"; +import baz, { bar2 } from "deno-npm:foo";`, + }); +}); + +Deno.test("npm workaround - export declaration", () => { + runTest({ + input: `export * as foo from "npm:foo"; +export { bar } from "npm:foo";`, + expected: `export * as foo from "deno-npm:foo"; +export { bar } from "deno-npm:foo";`, + }); +}); + +Deno.test("npm workaround - import()", () => { + runTest({ + input: `import("npm:foo"); +import("npm:foo", { type: "json" })`, + expected: `import("deno-npm:foo"); +import("deno-npm:foo", { + type: "json" +});`, + }); +}); diff --git a/packages/plugin-vite/src/plugins/patches.ts b/packages/plugin-vite/src/plugins/patches.ts new file mode 100644 index 00000000000..63b8a056655 --- /dev/null +++ b/packages/plugin-vite/src/plugins/patches.ts @@ -0,0 +1,32 @@ +import type { Plugin } from "vite"; +import * as babel from "@babel/core"; +import { cjsPlugin } from "./commonjs.ts"; +import { npmWorkaround } from "./npm_workaround.ts"; + +export function patches(): Plugin { + return { + name: "fresh:patches", + applyToEnvironment() { + return true; + }, + resolveId(id) { + if (id.startsWith("deno-npm:")) { + return id.slice("deno-".length); + } + }, + transform(code, id) { + const res = babel.transformSync(code, { + filename: id, + babelrc: false, + plugins: [npmWorkaround, cjsPlugin], + }); + + if (res?.code) { + return { + code: res.code, + map: res.map, + }; + } + }, + }; +}