diff --git a/deno.lock b/deno.lock index 476e8585fe7..b0c99ebecd8 100644 --- a/deno.lock +++ b/deno.lock @@ -1,6 +1,7 @@ { "version": "5", "specifiers": { + "jsr:@astral/astral@0.5.6": "0.5.6", "jsr:@astral/astral@~0.5.6": "0.5.6", "jsr:@deno-library/progress@^1.5.1": "1.5.1", "jsr:@deno/cache-dir@0.14": "0.14.0", @@ -6110,6 +6111,17 @@ "npm:vite@^7.1.4" ] } + }, + "links": { + "npm:@deno/vite-plugin@2.0.2": { + "dependencies": [ + "npm:@jsr/deno__loader@0.5", + "npm:@jsr/std__jsonc@1" + ], + "peerDependencies": [ + "npm:vite@5 || 6 || 7 || 8" + ] + } } } } diff --git a/docs/latest/concepts/islands.md b/docs/latest/concepts/islands.md index b2f88a7efbc..57d10edd28b 100644 --- a/docs/latest/concepts/islands.md +++ b/docs/latest/concepts/islands.md @@ -118,12 +118,34 @@ import OtherIsland from "../islands/other-island.tsx"; ; ``` -## Rendering islands on client only +## Client-only islands -When using client-only APIs, like `EventSource` or `navigator.getUserMedia`, the -component would error during server-side rendering. Use the `IS_BROWSER` -constant from `fresh/runtime` to guard browser-only code. It is `false` on the -server and `true` in the browser: +Some libraries (e.g. Monaco Editor, certain charting libraries) reference +browser globals like `document` at the module top level, which crashes during +server-side rendering. You can mark an island as **client-only** by adding +`export const clientOnly = true`. Fresh will skip executing the component on the +server and render an empty placeholder instead. On the client, the component +renders normally. + +```tsx islands/my-editor.tsx +export const clientOnly = true; + +export default function MyEditor() { + // Safe to use document, window, etc. — this code never runs on the server. + return
{/* ... */}
; +} +``` + +> [warn]: Client-only islands produce no meaningful HTML on the server. This +> means search engines will not see their content, and users will see an empty +> placeholder until JavaScript loads. Use this only when the component truly +> cannot run on the server. + +### Using `IS_BROWSER` for a custom fallback + +If the module itself can be loaded on the server but you only need to guard +certain API calls, use the `IS_BROWSER` constant from `fresh/runtime` instead. +This lets you return a meaningful SSR fallback: ```tsx islands/my-island.tsx import { IS_BROWSER } from "fresh/runtime"; diff --git a/packages/fresh/src/build_cache.ts b/packages/fresh/src/build_cache.ts index 6a8161a2b77..2af9bd794a7 100644 --- a/packages/fresh/src/build_cache.ts +++ b/packages/fresh/src/build_cache.ts @@ -103,7 +103,9 @@ export class IslandPreparer { chunkName: string, modName: string, css: string[], + clientOnly?: boolean, ) { + const isClientOnly = clientOnly ?? mod.clientOnly === true; for (const [name, value] of Object.entries(mod)) { if (typeof value !== "function") continue; @@ -117,6 +119,7 @@ export class IslandPreparer { fn, name: uniqueName, css, + clientOnly: isClientOnly, }); } } diff --git a/packages/fresh/src/context.ts b/packages/fresh/src/context.ts index 3c2d9c07815..3af466a0007 100644 --- a/packages/fresh/src/context.ts +++ b/packages/fresh/src/context.ts @@ -91,6 +91,7 @@ export interface Island { exportName: string; fn: ComponentType; css: string[]; + clientOnly: boolean; } export type ServerIslandRegistry = Map; diff --git a/packages/fresh/src/dev/dev_build_cache.ts b/packages/fresh/src/dev/dev_build_cache.ts index dd985551113..c69ed660f65 100644 --- a/packages/fresh/src/dev/dev_build_cache.ts +++ b/packages/fresh/src/dev/dev_build_cache.ts @@ -35,6 +35,7 @@ export interface IslandModChunk { server: string; browser: string | null; css: string[]; + clientOnly?: boolean; } export type FsRouteFileNoMod = Omit, "mod"> & { @@ -493,6 +494,9 @@ export async function generateSnapshotServer( const browser = JSON.stringify(item.browser); const name = JSON.stringify(item.name); const css = JSON.stringify(item.css); + if (item.clientOnly) { + return `islandPreparer.prepare(islands, ${item.name}, ${browser}, ${name}, ${css}, true);`; + } return `islandPreparer.prepare(islands, ${item.name}, ${browser}, ${name}, ${css});`; }).join("\n"); diff --git a/packages/fresh/src/runtime/client/reviver.ts b/packages/fresh/src/runtime/client/reviver.ts index dda11e8aa4b..83d32e15b91 100644 --- a/packages/fresh/src/runtime/client/reviver.ts +++ b/packages/fresh/src/runtime/client/reviver.ts @@ -21,6 +21,7 @@ interface IslandReq { name: string; propsIdx: number; key: string | null; + clientOnly: boolean; start: Comment | Text; end: Comment | Text | null; } @@ -269,11 +270,13 @@ function _walkInner( const name = parts[2]; const propsIdx = parts[3]; const key = parts[4]; + const clientOnly = parts[5] === "c"; const found: IslandReq = { kind: RootKind.Island, name, propsIdx: Number(propsIdx), key: key === "" ? null : key, + clientOnly, start: node as Comment, end: null, }; diff --git a/packages/fresh/src/runtime/server/preact_hooks.ts b/packages/fresh/src/runtime/server/preact_hooks.ts index 8070db536a6..d8dda1de445 100644 --- a/packages/fresh/src/runtime/server/preact_hooks.ts +++ b/packages/fresh/src/runtime/server/preact_hooks.ts @@ -300,15 +300,19 @@ options[OptionsType.DIFF] = (vnode) => { } const propsIdx = islandProps.push({ slots: [], props }) - 1; - const child = h(originalType, props); + const key = normalizeKey(vnode.key); + const markerData = island!.clientOnly + ? `${island!.name}:${propsIdx}:${key}:c` + : `${island!.name}:${propsIdx}:${key}`; + + // For client-only islands, render an empty placeholder + // instead of executing the component on the server. + const child = island!.clientOnly + ? h("div", null) + : h(originalType, props); PATCHED.add(child); - const key = normalizeKey(vnode.key); - return wrapWithMarker( - child, - "island", - `${island!.name}:${propsIdx}:${key}`, - ); + return wrapWithMarker(child, "island", markerData); }; } } else if (typeof vnode.type === "string") { diff --git a/packages/fresh/tests/fixtures_islands/ClientOnlyIsland.tsx b/packages/fresh/tests/fixtures_islands/ClientOnlyIsland.tsx new file mode 100644 index 00000000000..adfe3338722 --- /dev/null +++ b/packages/fresh/tests/fixtures_islands/ClientOnlyIsland.tsx @@ -0,0 +1,25 @@ +import { useSignal } from "@preact/signals"; +import { useEffect } from "preact/hooks"; + +export const clientOnly = true; + +export default function ClientOnlyIsland( + props: { id?: string; label?: string }, +) { + const active = useSignal(false); + useEffect(() => { + active.value = true; + }, []); + + return ( +
+

{props.label ?? "rendered on client"}

+

+ {typeof document !== "undefined" ? "has-document" : "no-document"} +

+
+ ); +} diff --git a/packages/fresh/tests/islands_test.tsx b/packages/fresh/tests/islands_test.tsx index b5031df0d95..c9512177f2c 100644 --- a/packages/fresh/tests/islands_test.tsx +++ b/packages/fresh/tests/islands_test.tsx @@ -30,6 +30,7 @@ import { FakeServer } from "../src/test_utils.ts"; import { PARTIAL_SEARCH_PARAM } from "../src/constants.ts"; import { ComputedSignal } from "./fixtures_islands/Computed.tsx"; import { EnvIsland } from "./fixtures_islands/EnvIsland.tsx"; +import ClientOnlyIsland from "./fixtures_islands/ClientOnlyIsland.tsx"; Deno.env.set("FRESH_PUBLIC_TEST_FOO", "test-env-value"); Deno.env.set("FRESH_PRIVATE_TEST_FOO", "i-should-not-be-visible"); @@ -847,3 +848,58 @@ Deno.test({ }); }, }); + +Deno.test({ + name: "islands - client-only island renders placeholder in SSR", + fn: async () => { + const app = testApp() + .get("/", (ctx) => { + return ctx.render( + + + , + ); + }); + + const server = new FakeServer(app.handler()); + const res = await server.get("/"); + const html = await res.text(); + + // SSR should contain a placeholder div, not the island's actual content + const doc = parseHtml(html); + expect(doc.querySelector(".client-only")).toBeNull(); + expect(doc.querySelector(".label")).toBeNull(); + + // The marker comment should have the :c flag for client-only + expect(html).toContain("::c-->"); + }, +}); + +Deno.test({ + name: "islands - client-only island renders on client", + fn: async () => { + const app = testApp() + .get("/", (ctx) => { + return ctx.render( + + + , + ); + }); + + await withBrowserApp(app, async (page, address) => { + await page.goto(address, { waitUntil: "load" }); + await page.locator("#co.ready").wait(); + + const label = await page.evaluate(() => { + return document.querySelector("#co .label")?.textContent; + }); + expect(label).toEqual("hello"); + + const check = await page.evaluate(() => { + return document.querySelector("#co .check")?.textContent; + }); + expect(check).toEqual("has-document"); + }); + }, +}); diff --git a/packages/plugin-vite/demo/fixtures/commonjs_mod.cjs b/packages/plugin-vite/demo/fixtures/commonjs_mod.cjs deleted file mode 100644 index 1f6a76bc478..00000000000 --- a/packages/plugin-vite/demo/fixtures/commonjs_mod.cjs +++ /dev/null @@ -1 +0,0 @@ -exports.value = "ok"; diff --git a/packages/plugin-vite/demo/fixtures/commonjs_mod.js b/packages/plugin-vite/demo/fixtures/commonjs_mod.js new file mode 100644 index 00000000000..44d44847d02 --- /dev/null +++ b/packages/plugin-vite/demo/fixtures/commonjs_mod.js @@ -0,0 +1 @@ +export const value = "ok"; diff --git a/packages/plugin-vite/demo/fixtures/maxmind.cjs b/packages/plugin-vite/demo/fixtures/maxmind.cjs deleted file mode 100644 index bbef3806916..00000000000 --- a/packages/plugin-vite/demo/fixtures/maxmind.cjs +++ /dev/null @@ -1,8 +0,0 @@ -"use strict"; -// deno-lint-ignore no-var -var __importDefault = (this && this.__importDefault) || function (mod) { - return (mod && mod.__esModule) ? mod : { "default": mod }; -}; -Object.defineProperty(exports, "__esModule", { value: true }); -const assert_1 = __importDefault(require("assert")); -(0, assert_1.default)(true); diff --git a/packages/plugin-vite/demo/fixtures/maxmind.js b/packages/plugin-vite/demo/fixtures/maxmind.js new file mode 100644 index 00000000000..8710fa40a4c --- /dev/null +++ b/packages/plugin-vite/demo/fixtures/maxmind.js @@ -0,0 +1,2 @@ +import assert from "node:assert"; +assert(true); diff --git a/packages/plugin-vite/demo/routes/tests/commonjs.tsx b/packages/plugin-vite/demo/routes/tests/commonjs.tsx index 8f73c79b934..32308633baf 100644 --- a/packages/plugin-vite/demo/routes/tests/commonjs.tsx +++ b/packages/plugin-vite/demo/routes/tests/commonjs.tsx @@ -1,4 +1,4 @@ -import { value } from "../../fixtures/commonjs_mod.cjs"; +import { value } from "../../fixtures/commonjs_mod.js"; export default function Page() { return

{value}

; diff --git a/packages/plugin-vite/demo/routes/tests/maxmind.tsx b/packages/plugin-vite/demo/routes/tests/maxmind.tsx index b42a8ada37f..8ac0424445b 100644 --- a/packages/plugin-vite/demo/routes/tests/maxmind.tsx +++ b/packages/plugin-vite/demo/routes/tests/maxmind.tsx @@ -1,4 +1,4 @@ -import * as maxmind from "../../fixtures/maxmind.cjs"; +import * as maxmind from "../../fixtures/maxmind.js"; export default function Page() { // deno-lint-ignore no-console diff --git a/packages/plugin-vite/src/mod.ts b/packages/plugin-vite/src/mod.ts index c49c244526c..8fde99478c6 100644 --- a/packages/plugin-vite/src/mod.ts +++ b/packages/plugin-vite/src/mod.ts @@ -82,15 +82,43 @@ export function fresh(config?: FreshViteConfig): Plugin[] { }); let isDev = false; + let freshMode = "development"; const plugins: Plugin[] = [ { name: "fresh", sharedDuringBuild: true, - config(config, env) { + async config(config, env) { isDev = env.command === "serve"; + freshMode = isDev ? "development" : "production"; + + // Load env files early so define entries are available + const root = config.root ? path.resolve(config.root) : Deno.cwd(); + const envDir = config.envDir ? path.resolve(root, config.envDir) : root; + await loadEnvFile(path.join(envDir, ".env")); + await loadEnvFile(path.join(envDir, ".env.local")); + await loadEnvFile(path.join(envDir, `.env.${freshMode}`)); + await loadEnvFile(path.join(envDir, `.env.${freshMode}.local`)); + + // Build define map for FRESH_PUBLIC_* env vars + // Replaces the Babel inlineEnvVarsPlugin with Vite's native define + const envDefine: Record = {}; + for (const [key, value] of Object.entries(Deno.env.toObject())) { + if (key.startsWith("FRESH_PUBLIC_")) { + envDefine[`process.env.${key}`] = JSON.stringify(value); + envDefine[`import.meta.env.${key}`] = JSON.stringify(value); + } + } return { + define: envDefine, + ssr: { + // Bundle all deps in SSR so that resolve.alias + // (react -> preact/compat) is applied consistently. + // CJS packages are handled by the deno plugin's load + // hook which wraps them in an ESM-compatible shim. + noExternal: true, + }, server: { watch: { // Ignore temp files, editor swap files, and Vite timestamp @@ -119,14 +147,15 @@ export function fresh(config?: FreshViteConfig): Plugin[] { "react-dom": "preact/compat", react: "preact/compat", }, - // Disallow externals, because it leads to duplicate - // modules with `preact` vs `npm:preact@*` in the server - // environment. - noExternal: true, }, + optimizeDeps: { - // Optimize deps somehow leads to duplicate modules or them - // being placed in the wrong chunks... + // Disable dep optimizer because deno.ts handles all + // module resolution. The optimizer causes duplicate + // module instances when remote (JSR) islands resolve + // deps to /@fs/ paths while the optimizer bundles to + // /.vite/deps/. CJS packages in client-side islands + // are handled by deno.ts's load hook. noDiscovery: true, }, @@ -192,14 +221,6 @@ export function fresh(config?: FreshViteConfig): Plugin[] { return; } - // Ignore commonjs optional exports - if ( - warning.code === "MISSING_EXPORT" && - warning.message.includes("__require") - ) { - return; - } - // Ignore this warnings if (warning.code === "THIS_IS_UNDEFINED") { return; @@ -221,7 +242,7 @@ export function fresh(config?: FreshViteConfig): Plugin[] { }, }; }, - async configResolved(vConfig) { + configResolved(vConfig) { // Run update check in background updateCheck(UPDATE_INTERVAL).catch(() => {}); @@ -236,19 +257,45 @@ export function fresh(config?: FreshViteConfig): Plugin[] { const name = fConfig.namer.getUniqueName(specName); fConfig.islandSpecifiers.set(spec, name); }); + }, + }, + // Lightweight replacement for Deno.env.get() calls with FRESH_PUBLIC_* + // and NODE_ENV values. Replaces the Babel inlineEnvVarsPlugin for this + // pattern which can't be handled by Vite's define (it's a call expression). + { + name: "fresh:deno-env", + sharedDuringBuild: true, + applyToEnvironment() { + return true; + }, + transform: { + filter: { + id: /\.([tj]sx?|[mc]?[tj]s)(\?.*)?$/, + }, + handler(code) { + if (!code.includes("Deno.env.get(")) return; - const envDir = pathWithRoot( - vConfig.envDir || vConfig.root, - vConfig.root, - ); + const allEnv = Deno.env.toObject(); + let modified = false; + const result = code.replace( + /Deno\.env\.get\(\s*["']([^"']+)["']\s*\)/g, + (match: string, name: string) => { + if (name === "NODE_ENV") { + modified = true; + return JSON.stringify(freshMode); + } + if (name.startsWith("FRESH_PUBLIC_") && name in allEnv) { + modified = true; + return JSON.stringify(allEnv[name]); + } + return match; + }, + ); - await loadEnvFile(path.join(envDir, ".env")); - await loadEnvFile(path.join(envDir, ".env.local")); - const mode = isDev ? "development" : "production"; - await loadEnvFile(path.join(envDir, `.env.${mode}`)); - await loadEnvFile(path.join(envDir, `.env.${mode}.local`)); + if (modified) return { code: result }; + }, }, - }, + } satisfies Plugin, serverEntryPlugin(fConfig), patches(), ...serverSnapshot(fConfig), diff --git a/packages/plugin-vite/src/plugins/deno.ts b/packages/plugin-vite/src/plugins/deno.ts index 6cefed7e463..016529bb593 100644 --- a/packages/plugin-vite/src/plugins/deno.ts +++ b/packages/plugin-vite/src/plugins/deno.ts @@ -9,7 +9,7 @@ import { import * as path from "@std/path"; import * as babel from "@babel/core"; import { httpAbsolute } from "./patches/http_absolute.ts"; -import { JS_REG, JSX_REG } from "../utils.ts"; +import { JSX_REG } from "../utils.ts"; import { builtinModules } from "node:module"; // @ts-ignore Workaround for https://github.com/denoland/deno/issues/30850 @@ -17,10 +17,6 @@ const { default: babelReact } = await import("@babel/preset-react"); const BUILTINS = new Set(builtinModules); -interface DenoState { - type: RequestedModuleType; -} - export function deno(): Plugin { let ssrLoader: Loader; let browserLoader: Loader; @@ -85,6 +81,21 @@ export function deno(): Plugin { id = `${url.origin}${id}`; } + // Apply resolve.alias before Deno resolution so that + // react -> preact/compat works even in externalized packages. + // Vite normalizes alias config to { find, replacement }[] format. + const aliases = this.environment?.config?.resolve?.alias; + if (aliases) { + const list = Array.isArray(aliases) ? aliases : []; + for (const alias of list) { + const find = alias.find; + if (typeof find === "string" ? find === id : find?.test?.(id)) { + id = typeof alias.replacement === "string" ? alias.replacement : id; + break; + } + } + } + // We still want to allow other plugins to participate in // resolution, with us being in front due to `enforce: "pre"`. // But we still want to ignore everything `vite:resolve` does @@ -155,14 +166,13 @@ export function deno(): Plugin { resolved = path.fromFileUrl(resolved); } - return { - id: resolved, - meta: { - deno: { - type, - }, - }, - }; + // For file:// resolved modules (npm packages in node_modules, + // local files), let Vite handle loading natively. This allows + // Vite to externalize CJS packages in SSR mode (Node.js handles + // them with native require()) and avoids needing a custom CJS + // transform. Only \0deno:: virtual modules (jsr:, non-default + // types) need Fresh's custom load hook. + return { id: resolved }; } catch { // ignore } @@ -172,6 +182,80 @@ export function deno(): Plugin { ? ssrLoader : browserLoader; + // In dev mode, CJS files need to be wrapped in an ESM shim: + // - SSR: module runner evaluates as ESM, needs module/exports/require + // - Client: browser evaluates as ESM, needs module/exports + // In build mode, Rollup's @rollup/plugin-commonjs handles CJS. + if ( + isDev && + !id.startsWith("\0") && + id.includes("node_modules") && + /\.(c?js|cjs)$/.test(id) + ) { + try { + const code = await Deno.readTextFile(id); + // Quick heuristic: if file has CJS patterns and no ESM + if ( + !code.includes("export ") && + !code.includes("import ") && + (code.includes("module.exports") || + code.includes("exports.") || + code.includes("require(")) + ) { + const isServer = this.environment.config.consumer === "server"; + + if (isServer) { + // SSR: use Node.js createRequire for full CJS compat + const wrapped = ` +import { createRequire as __cjs_createRequire } from "node:module"; +import { fileURLToPath as __cjs_fileURLToPath } from "node:url"; +import { dirname as __cjs_dirname } from "node:path"; +var __filename = __cjs_fileURLToPath(import.meta.url); +var __dirname = __cjs_dirname(__filename); +var require = __cjs_createRequire(import.meta.url); +var module = { exports: {} }; +var exports = module.exports; + +${code} + +export default module.exports; +`; + return { code: wrapped }; + } + + // Client: convert require() calls to ESM imports so + // browsers can load them. Hoist static require() calls + // to import statements at the top. + const imports: string[] = []; + let idx = 0; + const transformed = code.replace( + /\brequire\(["']([^"']+)["']\)/g, + (_match: string, spec: string) => { + const varName = `__cjs_import_${idx++}`; + imports.push( + `import ${varName} from ${JSON.stringify(spec)};`, + ); + return `(${varName}.default ?? ${varName})`; + }, + ); + + const wrapped = `${imports.join("\n")} +var module = { exports: {} }; +var exports = module.exports; +var __filename = ""; +var __dirname = ""; + +${transformed} + +export default module.exports; +`; + return { code: wrapped }; + } + } catch { + // Fall through to default loading + } + } + if (isDenoSpecifier(id)) { const { type, specifier } = parseDenoSpecifier(id); @@ -197,49 +281,6 @@ export function deno(): Plugin { code, }; } - - if (id.startsWith("\0")) { - id = id.slice(1); - } - - const meta = this.getModuleInfo(id)?.meta.deno as - | DenoState - | undefined - | null; - - if (meta === null || meta === undefined) return; - - // Skip for non-js files like `.css` - if ( - meta.type === RequestedModuleType.Default && - !JS_REG.test(id) - ) { - return; - } - - const url = path.toFileUrl(id); - - const result = await loader.load(url.href, meta.type); - if (result.kind === "external") { - return null; - } - - const code = new TextDecoder().decode(result.code); - - const maybeJsx = babelTransform({ - ssr: this.environment.config.consumer === "server", - media: result.mediaType, - id, - code, - isDev, - }); - if (maybeJsx) { - return maybeJsx; - } - - return { - code, - }; }, transform: { filter: { diff --git a/packages/plugin-vite/src/plugins/patches.ts b/packages/plugin-vite/src/plugins/patches.ts index 167ac7bc862..b6970bdbc42 100644 --- a/packages/plugin-vite/src/plugins/patches.ts +++ b/packages/plugin-vite/src/plugins/patches.ts @@ -1,8 +1,6 @@ import type { Plugin } from "vite"; import * as babel from "@babel/core"; -import { cjsPlugin } from "./patches/commonjs.ts"; import { jsxComments } from "./patches/jsx_comment.ts"; -import { inlineEnvVarsPlugin } from "./patches/inline_env_vars.ts"; import { removePolyfills } from "./patches/remove_polyfills.ts"; import { JS_REG, JSX_REG } from "../utils.ts"; import { codeEvalPlugin } from "./patches/code_eval.ts"; @@ -41,10 +39,8 @@ export function patches(): Plugin { const plugins: babel.PluginItem[] = [ codeEvalPlugin(this.environment.config.consumer, env), - cjsPlugin, removePolyfills, jsxComments, - inlineEnvVarsPlugin(env, Deno.env.toObject()), ]; const res = babel.transformSync(code, { diff --git a/packages/plugin-vite/src/plugins/patches/commonjs.ts b/packages/plugin-vite/src/plugins/patches/commonjs.ts deleted file mode 100644 index 83a58bb2d5b..00000000000 --- a/packages/plugin-vite/src/plugins/patches/commonjs.ts +++ /dev/null @@ -1,959 +0,0 @@ -import type { NodePath, PluginObj, types } from "@babel/core"; -import { builtinModules } from "node:module"; - -const BUILTINS = new Set(builtinModules); - -export function cjsPlugin( - { types: t }: { types: typeof types }, -): PluginObj { - const HAS_ES_MODULE = "esModule"; - const REQUIRE_CALLS = "requireCalls"; - const ROOT_SCOPE = "rootScope"; - const EXPORTED = "exported"; - const EXPORTED_NAMESPACES = "exported_namespaces"; - const ALIASED = "aliased"; - const REEXPORT = "re-export"; - const NEEDS_REQUIRE_IMPORT = "needsRequireImport"; - const NEEDS_DIRNAME_IMPORT = "needsDirnameImport"; - const IS_ESM = "isESM"; - - return { - name: "fresh-cjs-esm", - pre(file) { - const filename = file.opts.filename; - if (filename) { - if (filename.endsWith(".mjs") || filename.endsWith(".mts")) { - this.set(IS_ESM, true); - } else if (filename.endsWith(".cjs") || filename.endsWith(".cts")) { - this.set(IS_ESM, false); - } - } - }, - visitor: { - Program: { - enter(path, state) { - state.set(ROOT_SCOPE, path.scope); - state.set(EXPORTED, new Set()); - state.set(EXPORTED_NAMESPACES, new Set()); - state.set(REEXPORT, null); - - path.traverse({ - Import(_path, state) { - state.set(IS_ESM, true); - }, - ImportDeclaration(_path, state) { - state.set(IS_ESM, true); - }, - ExportAllDeclaration(_path, state) { - state.set(IS_ESM, true); - }, - ExportDefaultDeclaration(_path, state) { - state.set(IS_ESM, true); - }, - ExportNamedDeclaration(_path, state) { - state.set(IS_ESM, true); - }, - }, state); - }, - exit(path, state) { - const isESM = state.get(IS_ESM); - if (isESM) return; - - const body = path.get("body"); - const requires = state.get(REQUIRE_CALLS); - if (requires !== undefined) { - for (let i = 0; i < requires.length; i++) { - const { specifier, id } = requires[i]; - path.unshiftContainer( - "body", - t.importDeclaration( - [t.importNamespaceSpecifier(id)], - specifier, - ), - ); - } - } - - const reexport = state.get(REEXPORT); - const exported = state.get(EXPORTED); - const exportedNs = state.get(EXPORTED_NAMESPACES); - const needsRequireImport = state.get(NEEDS_REQUIRE_IMPORT); - const hasEsModule = state.get(HAS_ES_MODULE); - - if (needsRequireImport) { - // Inject: - // ```ts - // import { createRequire } from "node:module"; - // const require = createRequire(import.meta.url); - // ``` - const id = t.identifier("createRequire"); - path.unshiftContainer( - "body", - t.variableDeclaration("const", [ - t.variableDeclarator( - t.identifier("require"), - t.callExpression(t.identifier("createRequire"), [ - t.memberExpression( - t.metaProperty( - t.identifier("import"), - t.identifier("meta"), - ), - t.identifier("url"), - ), - ]), - ), - ]), - ); - path.unshiftContainer( - "body", - t.importDeclaration( - [t.importSpecifier(id, id)], - t.stringLiteral("node:module"), - ), - ); - } - - const needsDirnameImport = state.get(NEEDS_DIRNAME_IMPORT); - if (needsDirnameImport) { - // Inject: - // ```ts - // import { fileURLToPath as __cjs_fileURLToPath } from "node:url"; - // import { dirname as __cjs_dirname } from "node:path"; - // const __filename = __cjs_fileURLToPath(import.meta.url); - // const __dirname = __cjs_dirname(__filename); - // ``` - const fileURLToPathId = t.identifier("__cjs_fileURLToPath"); - const dirnameId = t.identifier("__cjs_dirname"); - const importMetaUrl = t.memberExpression( - t.metaProperty( - t.identifier("import"), - t.identifier("meta"), - ), - t.identifier("url"), - ); - - path.unshiftContainer( - "body", - t.variableDeclaration("var", [ - t.variableDeclarator( - t.identifier("__dirname"), - t.callExpression(dirnameId, [t.identifier("__filename")]), - ), - ]), - ); - path.unshiftContainer( - "body", - t.variableDeclaration("var", [ - t.variableDeclarator( - t.identifier("__filename"), - t.callExpression(fileURLToPathId, [importMetaUrl]), - ), - ]), - ); - path.unshiftContainer( - "body", - t.importDeclaration( - [t.importSpecifier(dirnameId, t.identifier("dirname"))], - t.stringLiteral("node:path"), - ), - ); - path.unshiftContainer( - "body", - t.importDeclaration( - [ - t.importSpecifier( - fileURLToPathId, - t.identifier("fileURLToPath"), - ), - ], - t.stringLiteral("node:url"), - ), - ); - } - - if (reexport !== null) { - path.unshiftContainer( - "body", - t.exportAllDeclaration(t.cloneNode(reexport, true)), - ); - } - - const mappedNs: string[] = []; - - for (const spec of exportedNs.values()) { - const id = path.scope.generateUidIdentifier("__ns"); - mappedNs.push(id.name); - - path.unshiftContainer( - "body", - t.importDeclaration( - [t.importNamespaceSpecifier(id)], - t.stringLiteral(spec), - ), - ); - } - - if (exported.size > 0 || exportedNs.size > 0 || hasEsModule) { - path.unshiftContainer( - "body", - t.expressionStatement( - t.callExpression( - t.memberExpression( - t.identifier("Object"), - t.identifier("defineProperty"), - ), - [ - t.identifier("exports"), - t.stringLiteral("__esModule"), - t.objectExpression([ - t.objectProperty( - t.identifier("value"), - t.booleanLiteral(true), - ), - ]), - ], - ), - ), - ); - path.unshiftContainer( - "body", - t.expressionStatement( - t.callExpression( - t.memberExpression( - t.identifier("Object"), - t.identifier("defineProperty"), - ), - [ - t.identifier("module"), - t.stringLiteral("exports"), - t.objectExpression([ - t.objectMethod( - "method", - t.identifier("get"), - [], - t.blockStatement([ - t.returnStatement(t.identifier("exports")), - ]), - ), - t.objectMethod( - "method", - t.identifier("set"), - [t.identifier("value")], - t.blockStatement([ - t.expressionStatement( - t.assignmentExpression( - "=", - t.identifier("exports"), - t.identifier("value"), - ), - ), - ]), - ), - ]), - ], - ), - ), - ); - path.unshiftContainer( - "body", - t.variableDeclaration("var", [ - t.variableDeclarator( - t.identifier("exports"), - t.objectExpression([]), - ), - t.variableDeclarator( - t.identifier("module"), - t.objectExpression([]), - ), - ]), - ); - } - - const idExports: types.ExportSpecifier[] = []; - for (const name of exported) { - if (name === "default") { - continue; - } - - const id = path.scope.generateUidIdentifier(name); - - path.pushContainer( - "body", - t.variableDeclaration( - "var", - [t.variableDeclarator( - id, - t.memberExpression( - t.identifier("exports"), - t.identifier(name), - ), - )], - ), - ); - idExports.push( - t.exportSpecifier(id, t.identifier(name)), - ); - } - - if (idExports.length > 0) { - path.pushContainer( - "body", - t.exportNamedDeclaration(null, idExports), - ); - } - - if (exported.size > 0 || exportedNs.size > 0 || hasEsModule) { - const id = path.scope.generateUidIdentifier("__default"); - - // Use `var` instead of `const` to avoid TDZ errors when - // Rollup reorders declarations in the bundled output. - path.pushContainer( - "body", - t.variableDeclaration("var", [ - t.variableDeclarator( - id, - ), - ]), - ); - - path.pushContainer( - "body", - t.ifStatement( - t.logicalExpression( - "&&", - t.logicalExpression( - "&&", - t.binaryExpression( - "===", - t.unaryExpression("typeof", t.identifier("exports")), - t.stringLiteral("object"), - ), - t.binaryExpression( - "!==", - t.identifier("exports"), - t.nullLiteral(), - ), - ), - t.binaryExpression( - "in", - t.stringLiteral("default"), - t.identifier("exports"), - ), - ), - t.blockStatement([ - t.expressionStatement( - t.assignmentExpression( - "=", - id, - t.memberExpression( - t.identifier("exports"), - t.identifier("default"), - ), - ), - ), - ]), - t.blockStatement([ - t.expressionStatement( - t.assignmentExpression("=", id, t.identifier("exports")), - ), - ]), - ), - ); - - for (let i = 0; i < mappedNs.length; i++) { - const mapped = mappedNs[i]; - - const key = path.scope.generateUid("k"); - // Only spread namespace properties when the module has no - // explicit default export (i.e. "default" not in exports). - path.pushContainer( - "body", - t.ifStatement( - t.logicalExpression( - "&&", - t.logicalExpression( - "&&", - t.binaryExpression( - "===", - t.unaryExpression("typeof", t.identifier("exports")), - t.stringLiteral("object"), - ), - t.binaryExpression( - "!==", - t.identifier("exports"), - t.nullLiteral(), - ), - ), - t.unaryExpression( - "!", - t.binaryExpression( - "in", - t.stringLiteral("default"), - t.identifier("exports"), - ), - ), - ), - t.forInStatement( - t.variableDeclaration("var", [ - t.variableDeclarator(t.identifier(key)), - ]), - t.identifier(mapped), - t.ifStatement( - t.logicalExpression( - "&&", - t.logicalExpression( - "&&", - t.binaryExpression( - "!==", - t.identifier(key), - t.stringLiteral("default"), - ), - t.binaryExpression( - "!==", - t.identifier(key), - t.stringLiteral("__esModule"), - ), - ), - t.callExpression( - t.memberExpression( - t.memberExpression( - t.memberExpression( - t.identifier("Object"), - t.identifier("prototype"), - ), - t.identifier("hasOwnProperty"), - ), - t.identifier("call"), - ), - [t.identifier(mapped), t.identifier(key)], - ), - ), - t.expressionStatement( - t.assignmentExpression( - "=", - t.memberExpression( - t.cloneNode(id, true), - t.identifier(key), - true, - ), - t.memberExpression( - t.identifier(mapped), - t.identifier(key), - true, - ), - ), - ), - ), - ), - ), - ); - } - - path.pushContainer("body", t.exportDefaultDeclaration(id)); - path.pushContainer( - "body", - t.exportNamedDeclaration( - t.variableDeclaration("var", [ - t.variableDeclarator( - t.identifier("__require"), - t.identifier("exports"), - ), - ]), - ), - ); - } - - if (body.length === 0 && hasEsModule) { - path.pushContainer("body", t.exportNamedDeclaration(null)); - } else if (hasEsModule) { - path.pushContainer( - "body", - t.exportNamedDeclaration( - t.variableDeclaration( - "var", - [t.variableDeclarator( - t.identifier("__esModule"), - t.memberExpression( - t.identifier("exports"), - t.identifier("__esModule"), - ), - )], - ), - ), - ); - } - }, - }, - CallExpression(path, state) { - if (state.get(IS_ESM)) return; - const exported = state.get(EXPORTED); - - if (isObjEsModuleFlag(t, path.node)) { - state.set(HAS_ES_MODULE, true); - return; - } - - // Handle require.resolve() by injecting createRequire - if ( - t.isMemberExpression(path.node.callee) && - t.isIdentifier(path.node.callee.object) && - path.node.callee.object.name === "require" && - t.isIdentifier(path.node.callee.property) && - path.node.callee.property.name === "resolve" - ) { - state.set(NEEDS_REQUIRE_IMPORT, true); - return; - } - - if ( - t.isIdentifier(path.node.callee) && - path.node.callee.name === "require" - ) { - const root = state.get(ROOT_SCOPE); - const id = root.generateUidIdentifier("mod"); - - const mods = state.get(REQUIRE_CALLS) ?? []; - state.set(REQUIRE_CALLS, mods); - - const source = path.node.arguments[0]; - if (t.isStringLiteral(source)) { - // Check if we can hoist it or if we need to keep it. - let canImport = true; - let parent: NodePath | null = path.parentPath; - while (parent !== null) { - if ( - t.isTryStatement(parent.node) || t.isIfStatement(parent.node) || - t.isConditionalExpression(parent.node) - ) { - canImport = false; - break; - } - parent = parent.parentPath; - } - - if (!canImport) { - state.set(NEEDS_REQUIRE_IMPORT, true); - return; - } - - mods.push({ - id, - specifier: t.cloneNode(path.node.arguments[0], true), - }); - - if ( - path.parentPath?.isVariableDeclarator() && - path.parentPath?.get("id").isIdentifier() || - path.parentPath?.isCallExpression() - ) { - // Vite json processing always adds a default property. - if (source.value.endsWith(".json")) { - path.replaceWith( - t.logicalExpression( - "??", - t.memberExpression( - t.cloneNode(id, true), - t.identifier("default"), - ), - t.cloneNode(id, true), - ), - ); - } else if ( - path.parentPath?.isCallExpression() && - t.isIdentifier(path.parentPath.node.callee) && - path.parentPath.node.callee.name === "__importDefault" - ) { - if (isNodeBuiltin(source.value)) { - path.replaceWith(t.objectExpression([ - t.objectProperty( - t.identifier("__esModule"), - t.booleanLiteral(true), - ), - t.objectProperty( - t.identifier("default"), - t.logicalExpression( - "??", - t.memberExpression( - t.cloneNode(id, true), - t.identifier("default"), - ), - t.cloneNode(id, true), - ), - ), - ])); - } else { - path.replaceWith(t.cloneNode(id, true)); - } - } else { - path.replaceWith( - t.logicalExpression( - "??", - t.memberExpression( - t.cloneNode(id, true), - t.identifier("__require"), - ), - t.logicalExpression( - "??", - t.memberExpression( - t.cloneNode(id, true), - t.identifier("default"), - ), - t.cloneNode(id, true), - ), - ), - ); - } - return; - } - - path.replaceWith(t.cloneNode(id, true)); - } else { - state.set(NEEDS_REQUIRE_IMPORT, true); - } - } else if ( - t.isMemberExpression(path.node.callee) && - t.isIdentifier(path.node.callee.object) && - path.node.callee.object.name === "Object" && - t.isIdentifier(path.node.callee.property) && - path.node.callee.property.name === "defineProperty" && - path.node.arguments.length > 0 && - t.isIdentifier(path.node.arguments[0]) && - path.node.arguments[0].name === "exports" && - t.isStringLiteral(path.node.arguments[1]) - ) { - const name = path.node.arguments[1].value; - exported.add(name); - } - }, - EmptyStatement(path) { - path.remove(); - }, - MemberExpression: { - exit(path, state) { - if (state.get(IS_ESM)) return; - if ( - t.isIdentifier(path.node.property) && - path.node.property.name !== "__esModule" - ) { - // Track both `exports.X` and `module.exports.X` - if ( - t.isIdentifier(path.node.object) && - path.node.object.name === "exports" - ) { - state.get(EXPORTED).add(path.node.property.name); - } else if ( - t.isMemberExpression(path.node.object) && - isModuleExports(t, path.node.object) - ) { - state.get(EXPORTED).add(path.node.property.name); - } - } - }, - }, - ExpressionStatement: { - enter(path, state) { - if (state.get(IS_ESM)) return; - // Check: Object.defineProperty(module.exports) "__esModule" ...) - // Check: Object.defineProperty(exports) "__esModule" ...) - // Check: a({}, "__esModule", ...) - if ( - t.isCallExpression(path.node.expression) && - path.node.expression.arguments.length === 3 && - t.isStringLiteral(path.node.expression.arguments[1]) && - path.node.expression.arguments[1].value === "__esModule" - ) { - state.set(HAS_ES_MODULE, true); - return; - } - - if ( - t.isExpressionStatement(path.node) && - t.isCallExpression(path.node.expression) && - t.isIdentifier(path.node.expression.callee) && - path.node.expression.callee.name === "__exportStar" && - path.node.expression.arguments.length > 0 && - t.isCallExpression(path.node.expression.arguments[0]) && - t.isIdentifier(path.node.expression.arguments[0].callee) && - path.node.expression.arguments[0].callee.name === "require" && - t.isStringLiteral(path.node.expression.arguments[0].arguments[0]) - ) { - const spec = t.cloneNode( - path.node.expression.arguments[0].arguments[0], - true, - ); - state.get(EXPORTED_NAMESPACES).add(spec.value); - path.replaceWith(t.exportAllDeclaration(spec)); - } else if ( - t.isExpressionStatement(path.node) && - t.isCallExpression(path.node.expression) && - t.isFunctionExpression(path.node.expression.callee) - ) { - if ( - path.node.expression.callee.params.length > 0 && - t.isIdentifier(path.node.expression.callee.params[0]) - ) { - const alias = path.node.expression.callee.params[0].name; - state.set(ALIASED, alias); - } - } else if ( - // Check: Object.defineProperty(exports, "foo", { enumerable: true, get: function () { return foo; } }); - t.isCallExpression(path.node.expression) && - t.isMemberExpression(path.node.expression.callee) && - t.isIdentifier(path.node.expression.callee.object) && - path.node.expression.callee.object.name === "Object" && - t.isIdentifier(path.node.expression.callee.property) && - path.node.expression.callee.property.name === "defineProperty" && - path.node.expression.arguments.length >= 2 && - t.isIdentifier(path.node.expression.arguments[0]) && - path.node.expression.arguments[0].name === "exports" && - t.isStringLiteral(path.node.expression.arguments[1]) && - t.isObjectExpression(path.node.expression.arguments[2]) - ) { - const exported = path.node.expression.arguments[1].value; - const obj = path.node.expression.arguments[2]; - for (let i = 0; i < obj.properties.length; i++) { - const prop = obj.properties[i]; - - if ( - t.isObjectProperty(prop) && t.isIdentifier(prop.key) && - prop.key.name === "get" && t.isFunctionExpression(prop.value) && - t.isBlockStatement(prop.value.body) && - prop.value.body.body.length === 1 && - t.isReturnStatement(prop.value.body.body[0]) - ) { - const expr = prop.value.body.body[0].argument; - if (expr !== null && expr !== undefined) { - path.replaceWith( - t.assignmentExpression( - "=", - t.memberExpression( - t.identifier("exports"), - t.identifier(exported), - ), - t.cloneNode(expr, true), - ), - ); - } - } else if ( - t.isObjectMethod(prop) && t.isIdentifier(prop.key) && - prop.key.name === "get" && t.isBlockStatement(prop.body) && - prop.body.body.length === 1 && - t.isReturnStatement(prop.body.body[0]) - ) { - const expr = prop.body.body[0].argument; - if (expr !== null && expr !== undefined) { - path.replaceWith( - t.assignmentExpression( - "=", - t.memberExpression( - t.identifier("exports"), - t.identifier(exported), - ), - t.cloneNode(expr, true), - ), - ); - } - } - } - } else if ( - // Check: module.exports = require(...) - t.isAssignmentExpression(path.node.expression) && - t.isMemberExpression(path.node.expression.left) && - t.isIdentifier(path.node.expression.left.object) && - t.isIdentifier(path.node.expression.left.property) && - path.node.expression.left.object.name === "module" && - path.node.expression.left.property.name === "exports" && - t.isCallExpression(path.node.expression.right) && - t.isIdentifier(path.node.expression.right.callee) && - path.node.expression.right.callee.name === "require" && - path.node.expression.right.arguments.length === 1 && - t.isStringLiteral(path.node.expression.right.arguments[0]) - ) { - const source = path.node.expression.right.arguments[0]; - state.set(REEXPORT, source); - } else { - let depth = 0; - let current = path.node.expression; - - while ( - t.isAssignmentExpression(current) && - t.isMemberExpression(current.left) && - t.isIdentifier(current.left.object) && - current.left.object.name === "exports" - ) { - if ( - t.isUnaryExpression(current.right) && - current.right.operator === "void" && - t.isNumericLiteral(current.right.argument) && - current.right.argument.value === 0 - ) { - if (depth > 0) { - path.remove(); - } - - break; - } - - depth++; - current = current.right; - } - } - }, - exit(path, state) { - if (state.get(IS_ESM)) return; - const exported = state.get(EXPORTED); - const expr = path.get("expression"); - - if (expr.isAssignmentExpression()) { - const left = expr.get("left"); - - if (isEsModuleFlag(t, expr.node)) { - state.set(HAS_ES_MODULE, true); - } else if (left.isMemberExpression()) { - if (isModuleExports(t, left.node)) { - // Should always try to create synthetic default export in this case. - exported.add("default"); - - if (t.isObjectExpression(expr.node.right)) { - const properties = expr.node.right.properties; - for (let i = 0; i < properties.length; i++) { - const prop = properties[i]; - if (t.isObjectProperty(prop)) { - if (t.isIdentifier(prop.key)) { - if (prop.key.name === "__esModule") { - continue; - } - - exported.add(prop.key.name); - } - } - } - } - } else { - const named = getExportsAssignName(t, left.node); - if (named === null) return; - exported.add(named); - } - } - } else if (expr.isCallExpression()) { - if (isObjEsModuleFlag(t, expr.node)) { - state.set(HAS_ES_MODULE, true); - } - } - }, - }, - VariableDeclaration(path) { - if (path.node.declarations.length === 0) { - path.remove(); - } - }, - ConditionalExpression(path, state) { - if (state.get(IS_ESM)) return; - - if ( - t.isBinaryExpression(path.node.test) && - t.isUnaryExpression(path.node.test.left) && - path.node.test.left.operator === "typeof" && - t.isIdentifier(path.node.test.left.argument) && - path.node.test.left.argument.name === "exports" && - path.node.test.operator === "===" - ) { - path.replaceWith(t.cloneNode(path.node.alternate, true)); - } - }, - Identifier(path, state) { - if (state.get(IS_ESM)) return; - - const name = path.node.name; - if (name !== "__dirname" && name !== "__filename") return; - - // Skip if this is already a declaration (e.g. our own polyfill) - if ( - path.parentPath?.isVariableDeclarator() && - path.parentPath.get("id") === path - ) return; - - state.set(NEEDS_DIRNAME_IMPORT, true); - }, - AssignmentExpression(path, state) { - if (state.get(IS_ESM)) return; - - const exported = state.get(EXPORTED); - const aliased = state.get(ALIASED); - if (aliased === undefined) return; - - if ( - path.node.operator === "=" && t.isMemberExpression(path.node.left) && - t.isIdentifier(path.node.left.object) && - path.node.left.object.name === aliased && - t.isIdentifier(path.node.left.property) - ) { - const name = path.node.left.property.name; - exported.add(name); - } - }, - }, - }; -} - -function isModuleExports( - 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 types, - node: types.MemberExpression, -): string | null { - if ( - (t.isMemberExpression(node.object) && - isModuleExports(t, node.object) || - t.isIdentifier(node.object) && node.object.name === "exports") && - t.isIdentifier(node.property) - ) { - return node.property.name; - } - - return null; -} - -/** - * Detect `exports.__esModule = true;` - */ -function isEsModuleFlag( - t: typeof types, - node: types.AssignmentExpression, -): boolean { - if (!t.isMemberExpression(node.left)) return false; - - const { left, right } = node; - return (t.isMemberExpression(left.object) && - isModuleExports(t, left.object) || - t.isIdentifier(left.object) && left.object.name === "exports") && - t.isIdentifier(left.property) && left.property.name === "__esModule" && - t.isBooleanLiteral(right); -} - -/** - * Check for `Object.defineProperty(exports, '__esModule', { value: true })` - */ -function isObjEsModuleFlag( - t: typeof types, - node: types.CallExpression, -): boolean { - return node.arguments.length === 3 && - t.isStringLiteral(node.arguments[1]) && - node.arguments[1].value === "__esModule" && - t.isObjectExpression(node.arguments[2]); -} - -function isNodeBuiltin(specifier: string): boolean { - return BUILTINS.has(specifier) || ( - specifier.startsWith("node:") - ? BUILTINS.has(specifier.slice("node:".length)) - : BUILTINS.has(`node:${specifier}`) - ); -} diff --git a/packages/plugin-vite/src/plugins/patches/commonjs_test.ts b/packages/plugin-vite/src/plugins/patches/commonjs_test.ts deleted file mode 100644 index 4c337babca2..00000000000 --- a/packages/plugin-vite/src/plugins/patches/commonjs_test.ts +++ /dev/null @@ -1,835 +0,0 @@ -import { expect } from "@std/expect/expect"; -import * as babel from "@babel/core"; -import { cjsPlugin } from "../patches/commonjs.ts"; - -function runTest( - options: { input: string; expected: string; filename?: string }, -) { - const res = babel.transformSync(options.input, { - filename: options.filename ?? "foo.js", - babelrc: false, - plugins: [cjsPlugin], - }); - - const output = res?.code ?? ""; - expect(output).toEqual(options.expected); -} - -const INIT = `var exports = {}, - module = {}; -Object.defineProperty(module, "exports", { - get() { - return exports; - }, - set(value) { - exports = value; - } -}); -Object.defineProperty(exports, "__esModule", { - value: true -});`; - -const DEFAULT_EXPORT = `var _default; -if (typeof exports === "object" && exports !== null && "default" in exports) { - _default = exports.default; -} else { - _default = exports; -}`; - -const DEFAULT_EXPORT_END = `export default _default; -export var __require = exports;`; -const IMPORT_REQUIRE = `import { createRequire } from "node:module"; -const require = createRequire(import.meta.url);`; -const EXPORT_ES_MODULE = `export var __esModule = exports.__esModule;`; - -Deno.test("commonjs - module.exports default", () => { - runTest({ - input: `module.exports = async function () {};`, - expected: `${INIT} -module.exports = async function () {}; -${DEFAULT_EXPORT} -${DEFAULT_EXPORT_END}`, - }); -}); - -Deno.test("commonjs - module.exports default primitive", () => { - runTest({ - input: `module.exports = 42;`, - expected: `${INIT} -module.exports = 42; -${DEFAULT_EXPORT} -${DEFAULT_EXPORT_END}`, - }); -}); - -Deno.test("commonjs - exports with default + named", () => { - runTest({ - input: `exports.__esModule = true; -exports.default = 'x'; -exports.foo = 'foo';`, - expected: `${INIT} -exports.__esModule = true; -exports.default = 'x'; -exports.foo = 'foo'; -var _foo = exports.foo; -export { _foo as foo }; -${DEFAULT_EXPORT} -${DEFAULT_EXPORT_END} -${EXPORT_ES_MODULE}`, - }); -}); - -Deno.test("commonjs - module.exports with default + named", () => { - runTest({ - input: `module.exports.__esModule = true; -module.exports.default = 'x'; -module.exports.foo = 'foo';`, - expected: `${INIT} -module.exports.__esModule = true; -module.exports.default = 'x'; -module.exports.foo = 'foo'; -var _foo = exports.foo; -export { _foo as foo }; -${DEFAULT_EXPORT} -${DEFAULT_EXPORT_END} -${EXPORT_ES_MODULE}`, - }); -}); - -Deno.test("commonjs - Object es module flag with named clash", () => { - runTest({ - input: `Object.defineProperty(exports, '__esModule', { value: true }); -exports.foo = 'bar'; -const foo = 'also bar'; -`, - expected: `${INIT} -Object.defineProperty(exports, '__esModule', { - value: true -}); -exports.foo = 'bar'; -const foo = 'also bar'; -var _foo = exports.foo; -export { _foo as foo }; -${DEFAULT_EXPORT} -${DEFAULT_EXPORT_END} -${EXPORT_ES_MODULE}`, - }); -}); - -Deno.test("commonjs - Object es module flag with named + default", () => { - runTest({ - input: `Object.defineProperty(exports, '__esModule', { value: true }); -exports.default = 'foo'; -exports.foo = 'bar'; -`, - expected: `${INIT} -Object.defineProperty(exports, '__esModule', { - value: true -}); -exports.default = 'foo'; -exports.foo = 'bar'; -var _foo = exports.foo; -export { _foo as foo }; -${DEFAULT_EXPORT} -${DEFAULT_EXPORT_END} -${EXPORT_ES_MODULE}`, - }); -}); - -Deno.test("commonjs - esModule flag only", () => { - runTest({ - input: `Object.defineProperty(exports, "__esModule", { value: true });`, - expected: `${INIT} -Object.defineProperty(exports, "__esModule", { - value: true -}); -${DEFAULT_EXPORT} -${DEFAULT_EXPORT_END} -${EXPORT_ES_MODULE}`, - }); -}); - -Deno.test("commonjs - esModule flag only #2", () => { - runTest({ - input: - `Object.defineProperty(module.exports, "__esModule", { value: true });`, - expected: `${INIT} -Object.defineProperty(module.exports, "__esModule", { - value: true -}); -${DEFAULT_EXPORT} -${DEFAULT_EXPORT_END} -${EXPORT_ES_MODULE}`, - }); -}); - -Deno.test("commonjs - esModule flag only minified #3", () => { - runTest({ - input: `Object.defineProperty(exports, '__esModule', { value: !0 });`, - expected: `${INIT} -Object.defineProperty(exports, '__esModule', { - value: !0 -}); -${DEFAULT_EXPORT} -${DEFAULT_EXPORT_END} -${EXPORT_ES_MODULE}`, - }); -}); - -Deno.test("commonjs - exports only named", () => { - runTest({ - input: `Object.defineProperty(exports, '__esModule', { value: true }); -exports.foo = 'bar'; -exports.bar = 'foo'; -`, - expected: `${INIT} -Object.defineProperty(exports, '__esModule', { - value: true -}); -exports.foo = 'bar'; -exports.bar = 'foo'; -var _foo = exports.foo; -var _bar = exports.bar; -export { _foo as foo, _bar as bar }; -${DEFAULT_EXPORT} -${DEFAULT_EXPORT_END} -${EXPORT_ES_MODULE}`, - }); -}); - -Deno.test("commonjs - require", () => { - runTest({ - input: `var foo = require("tape"); -console.log(foo); -`, - expected: `import * as _mod from "tape"; -var foo = _mod.__require ?? _mod.default ?? _mod; -console.log(foo);`, - }); -}); - -Deno.test("commonjs - require destructure", () => { - runTest({ - input: `var { foo } = require("tape"); -console.log(foo); -`, - expected: `import * as _mod from "tape"; -var { - foo -} = _mod; -console.log(foo);`, - }); -}); - -Deno.test("commonjs - require assign", () => { - runTest({ - input: `foo = require("tape"); -console.log(foo); -`, - expected: `import * as _mod from "tape"; -foo = _mod; -console.log(foo);`, - }); -}); - -Deno.test("commonjs - require assign pattern", () => { - runTest({ - input: `foo = require("tape"); -console.log(foo); -`, - expected: `import * as _mod from "tape"; -foo = _mod; -console.log(foo);`, - }); -}); - -Deno.test("commonjs - require function call", () => { - runTest({ - input: `var a = require('./a')()`, - expected: `import * as _mod from './a'; -var a = (_mod.__require ?? _mod.default ?? _mod)();`, - }); -}); - -Deno.test("commonjs - require var decls", () => { - runTest({ - input: `var a = require('./a'), b = 42;`, - expected: `import * as _mod from './a'; -var a = _mod.__require ?? _mod.default ?? _mod, - b = 42;`, - }); -}); - -Deno.test("commonjs - duplicate exports", () => { - runTest({ - input: `Object.defineProperty(exports, "__esModule", { value: true }); -exports.trace = void 0; -exports.trace = 'foo'`, - expected: `${INIT} -Object.defineProperty(exports, "__esModule", { - value: true -}); -exports.trace = void 0; -exports.trace = 'foo'; -var _trace = exports.trace; -export { _trace as trace }; -${DEFAULT_EXPORT} -${DEFAULT_EXPORT_END} -${EXPORT_ES_MODULE}`, - }); -}); - -Deno.test("commonjs - cleared exports", () => { - runTest({ - input: `Object.defineProperty(exports, "__esModule", { value: true }); -exports.foo = exports.bar = void 0; -exports.foo = 'foo'`, - expected: `${INIT} -Object.defineProperty(exports, "__esModule", { - value: true -}); -exports.foo = 'foo'; -var _foo = exports.foo; -export { _foo as foo }; -${DEFAULT_EXPORT} -${DEFAULT_EXPORT_END} -${EXPORT_ES_MODULE}`, - }); -}); - -Deno.test("commonjs - define exports", () => { - runTest({ - input: `var utils_1 = require("./bar"); -Object.defineProperty(exports, "foo", { enumerable: true, get: function () { return utils_1.foo; } });`, - expected: `${INIT} -import * as _mod from "./bar"; -var utils_1 = _mod.__require ?? _mod.default ?? _mod; -exports.foo = utils_1.foo; -var _foo = exports.foo; -export { _foo as foo }; -${DEFAULT_EXPORT} -${DEFAULT_EXPORT_END}`, - }); -}); - -Deno.test("commonjs - define exports #2", () => { - runTest({ - input: `var utils_1 = require("./bar"); -Object.defineProperty(exports, "foo", { enumerable: true, get() { return utils_1.foo; } });`, - expected: `${INIT} -import * as _mod from "./bar"; -var utils_1 = _mod.__require ?? _mod.default ?? _mod; -exports.foo = utils_1.foo; -var _foo = exports.foo; -export { _foo as foo }; -${DEFAULT_EXPORT} -${DEFAULT_EXPORT_END}`, - }); -}); - -Deno.test("commonjs - define exports #3", () => { - runTest({ - input: `Object.defineProperty(exports, "__esModule", { value: true }); -exports._globalThis = void 0; -exports._globalThis = typeof globalThis === 'object' ? globalThis : global;`, - expected: `${INIT} -Object.defineProperty(exports, "__esModule", { - value: true -}); -exports._globalThis = void 0; -exports._globalThis = typeof globalThis === 'object' ? globalThis : global; -var _globalThis = exports._globalThis; -export { _globalThis }; -${DEFAULT_EXPORT} -${DEFAULT_EXPORT_END} -${EXPORT_ES_MODULE}`, - }); -}); - -Deno.test("commonjs - named function", () => { - runTest({ - input: `Object.defineProperty(exports, "__esModule", { value: true }); -function foo() {}; -exports.foo = foo;`, - expected: `${INIT} -Object.defineProperty(exports, "__esModule", { - value: true -}); -function foo() {} -exports.foo = foo; -var _foo = exports.foo; -export { _foo as foo }; -${DEFAULT_EXPORT} -${DEFAULT_EXPORT_END} -${EXPORT_ES_MODULE}`, - }); -}); - -Deno.test("commonjs - detect esbuild shims", () => { - runTest({ - input: `__exportStar(require("./globalThis"), exports);`, - expected: `${INIT} -import * as _ns from "./globalThis"; -export * from "./globalThis"; -${DEFAULT_EXPORT} -if (typeof exports === "object" && exports !== null && !("default" in exports)) for (var _k in _ns) if (_k !== "default" && _k !== "__esModule" && Object.prototype.hasOwnProperty.call(_ns, _k)) _default[_k] = _ns[_k]; -${DEFAULT_EXPORT_END}`, - }); -}); - -Deno.test("commonjs - exports.default", () => { - runTest({ - input: `exports.default = {}`, - expected: `${INIT} -exports.default = {}; -${DEFAULT_EXPORT} -${DEFAULT_EXPORT_END}`, - }); -}); - -Deno.test("commonjs - multiple same name", () => { - runTest({ - input: `exports.VERSION = void 0; -exports.VERSION = '1.9.0';`, - expected: `${INIT} -exports.VERSION = void 0; -exports.VERSION = '1.9.0'; -var _VERSION = exports.VERSION; -export { _VERSION as VERSION }; -${DEFAULT_EXPORT} -${DEFAULT_EXPORT_END}`, - }); -}); - -Deno.test("commonjs - export enum", () => { - runTest({ - input: `Object.defineProperty(exports, "__esModule", { value: true }); -exports.DiagLogLevel = void 0; -var DiagLogLevel; -(function (DiagLogLevel) { - DiagLogLevel[DiagLogLevel["ALL"] = 9999] = "ALL"; -})(DiagLogLevel = exports.DiagLogLevel || (exports.DiagLogLevel = {}));`, - expected: `${INIT} -Object.defineProperty(exports, "__esModule", { - value: true -}); -exports.DiagLogLevel = void 0; -var DiagLogLevel; -(function (DiagLogLevel) { - DiagLogLevel[DiagLogLevel["ALL"] = 9999] = "ALL"; -})(DiagLogLevel = exports.DiagLogLevel || (exports.DiagLogLevel = {})); -var _DiagLogLevel = exports.DiagLogLevel; -export { _DiagLogLevel as DiagLogLevel }; -${DEFAULT_EXPORT} -${DEFAULT_EXPORT_END} -${EXPORT_ES_MODULE}`, - }); -}); - -Deno.test("commonjs - require", () => { - runTest({ - input: `module.exports = { __esModule: true, default: { foo: 'bar' }}`, - expected: `${INIT} -module.exports = { - __esModule: true, - default: { - foo: 'bar' - } -}; -${DEFAULT_EXPORT} -${DEFAULT_EXPORT_END}`, - }); -}); - -Deno.test("commonjs - export default object", () => { - runTest({ - input: `Object.defineProperty(exports, '__esModule', { value: true }); -module.exports = { foo: 'bar' }; -`, - expected: `${INIT} -Object.defineProperty(exports, '__esModule', { - value: true -}); -module.exports = { - foo: 'bar' -}; -var _foo = exports.foo; -export { _foo as foo }; -${DEFAULT_EXPORT} -${DEFAULT_EXPORT_END} -${EXPORT_ES_MODULE}`, - }); -}); - -Deno.test("commonjs - detect iife wrapper", () => { - runTest({ - input: `;(function (sax) { - sax.foo = "foo"; -})(typeof exports === 'undefined' ? this.sax = {} : exports);`, - expected: `${INIT} -(function (sax) { - sax.foo = "foo"; -})(exports); -var _foo = exports.foo; -export { _foo as foo }; -${DEFAULT_EXPORT} -${DEFAULT_EXPORT_END}`, - }); -}); - -Deno.test("commonjs - re-export", () => { - runTest({ - input: - `;var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { - if (k2 === undefined) k2 = k; - Object.defineProperty(o, k2, { enumerable: true, get: function() { return m[k]; } }); -}) : (function(o, m, k, k2) { - if (k2 === undefined) k2 = k; - o[k2] = m[k]; -})); -var __exportStar = (this && this.__exportStar) || function(m, exports) { - for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p); -}; -Object.defineProperty(exports, "__esModule", { value: true }); -__exportStar(require("./node"), exports);`, - expected: `${INIT} -import * as _ns from "./node"; -var __createBinding = this && this.__createBinding || (Object.create ? function (o, m, k, k2) { - if (k2 === undefined) k2 = k; - Object.defineProperty(o, k2, { - enumerable: true, - get: function () { - return m[k]; - } - }); -} : function (o, m, k, k2) { - if (k2 === undefined) k2 = k; - o[k2] = m[k]; -}); -var __exportStar = this && this.__exportStar || function (m, exports) { - for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p); -}; -Object.defineProperty(exports, "__esModule", { - value: true -}); -export * from "./node"; -${DEFAULT_EXPORT} -if (typeof exports === "object" && exports !== null && !("default" in exports)) for (var _k in _ns) if (_k !== "default" && _k !== "__esModule" && Object.prototype.hasOwnProperty.call(_ns, _k)) _default[_k] = _ns[_k]; -${DEFAULT_EXPORT_END} -${EXPORT_ES_MODULE}`, - }); -}); - -Deno.test("commonjs - assign module.exports", () => { - runTest({ - input: `module.exports = { foo: 1 };`, - expected: `${INIT} -module.exports = { - foo: 1 -}; -var _foo = exports.foo; -export { _foo as foo }; -${DEFAULT_EXPORT} -${DEFAULT_EXPORT_END}`, - }); -}); - -Deno.test("commonjs - require non-analyzable arg", () => { - runTest({ - input: `const pkg = require(path.join(basedir, "package.json"))`, - expected: `import { createRequire } from "node:module"; -const require = createRequire(import.meta.url); -const pkg = require(path.join(basedir, "package.json"));`, - }); -}); - -Deno.test("commonjs - keep binding", () => { - runTest({ - input: `export var __createBinding = Object.create ? 1 : 2;`, - expected: `export var __createBinding = Object.create ? 1 : 2;`, - }); -}); - -Deno.test("commonjs - require lazy import", () => { - runTest({ - input: `if (typeof process.env.NODE_PG_FORCE_NATIVE !== 'undefined') { - module.exports = new PG(require('./native')) -} else { - module.exports = new PG(Client) - - // lazy require native module...the native module may not have installed - Object.defineProperty(module.exports, 'native', { - configurable: true, - enumerable: false, - get() { - let native = null - try { - native = new PG(require('./native')) - } catch (err) { - if (err.code !== 'MODULE_NOT_FOUND') { - throw err - } - } - - // overwrite module.exports.native so that getter is never called again - Object.defineProperty(module.exports, 'native', { - value: native, - }) - - return native - }, - }) -}`, - expected: `${INIT} -${IMPORT_REQUIRE} -if (typeof process.env.NODE_PG_FORCE_NATIVE !== 'undefined') { - module.exports = new PG(require('./native')); -} else { - module.exports = new PG(Client); - - // lazy require native module...the native module may not have installed - Object.defineProperty(module.exports, 'native', { - configurable: true, - enumerable: false, - get() { - let native = null; - try { - native = new PG(require('./native')); - } catch (err) { - if (err.code !== 'MODULE_NOT_FOUND') { - throw err; - } - } - - // overwrite module.exports.native so that getter is never called again - Object.defineProperty(module.exports, 'native', { - value: native - }); - return native; - } - }); -} -${DEFAULT_EXPORT} -${DEFAULT_EXPORT_END}`, - }); -}); - -Deno.test("commonjs - wrapped iife binding", () => { - runTest({ - input: `"production" !== process.env.NODE_ENV && (function() { - exports.foo = 123 -})()`, - expected: `${INIT} -"production" !== process.env.NODE_ENV && function () { - exports.foo = 123; -}(); -var _foo = exports.foo; -export { _foo as foo }; -${DEFAULT_EXPORT} -${DEFAULT_EXPORT_END}`, - }); -}); - -Deno.test("commonjs - re-export #2", () => { - runTest({ - input: `module.exports = require("foo");`, - expected: `${INIT} -export * from "foo"; -import * as _mod from "foo"; -module.exports = _mod; -${DEFAULT_EXPORT} -${DEFAULT_EXPORT_END}`, - }); -}); - -Deno.test("commonjs - keep require() in .mjs", () => { - runTest({ - filename: "foo.mjs", - input: `try { require("foo"); } catch {}`, - expected: `try { - require("foo"); -} catch {}`, - }); -}); - -Deno.test("commonjs - keep conditional require() in ESM file", () => { - runTest({ - filename: "foo.mjs", - input: `try { require("foo"); } catch {}; -export {};`, - expected: `try { - require("foo"); -} catch {} -export {};`, - }); -}); - -Deno.test("commonjs - CJS turned ESM module", () => { - runTest({ - filename: "foo.mjs", - input: `module.exports.create = confettiCannon; -export default module.exports; -export var create = module.exports.create;`, - expected: `module.exports.create = confettiCannon; -export default module.exports; -export var create = module.exports.create;`, - }); -}); - -Deno.test("commonjs - minified __esModule", () => { - runTest({ - filename: "foo.js", - input: ` -const m = module.exports; -const a = Object.defineProperty; -a(m, "__esModule", { value: !0 });`, - expected: `${INIT} -const m = module.exports; -const a = Object.defineProperty; -a(m, "__esModule", { - value: !0 -}); -${DEFAULT_EXPORT} -${DEFAULT_EXPORT_END} -export var __esModule = exports.__esModule;`, - }); -}); - -Deno.test("commonjs - esbuild __importDefault", () => { - runTest({ - input: - `var __importDefault = (this && this.__importDefault) || function (mod) { - return (mod && mod.__esModule) ? mod : { "default": mod }; -}; -const node_events_1 = __importDefault(require("node:events"));`, - expected: `import * as _mod from "node:events"; -var __importDefault = this && this.__importDefault || function (mod) { - return mod && mod.__esModule ? mod : { - "default": mod - }; -}; -const node_events_1 = __importDefault({ - __esModule: true, - default: _mod.default ?? _mod -});`, - }); -}); - -// --- New tests for CJS transform fixes --- - -Deno.test("commonjs - require.resolve injects createRequire", () => { - runTest({ - input: `var resolved = require.resolve("some-package");`, - expected: `${IMPORT_REQUIRE} -var resolved = require.resolve("some-package");`, - }); -}); - -Deno.test("commonjs - require.resolve with require() both inject createRequire once", () => { - runTest({ - input: `var resolved = require.resolve("some-package"); -if (true) { - var mod = require("other"); -}`, - expected: `${IMPORT_REQUIRE} -var resolved = require.resolve("some-package"); -if (true) { - var mod = require("other"); -}`, - }); -}); - -Deno.test("commonjs - .mts file treated as ESM", () => { - runTest({ - filename: "foo.mts", - input: `export const x = 1;`, - expected: `export const x = 1;`, - }); -}); - -Deno.test("commonjs - .cts file treated as CJS", () => { - runTest({ - filename: "foo.cts", - input: `module.exports = 42;`, - expected: `${INIT} -module.exports = 42; -${DEFAULT_EXPORT} -${DEFAULT_EXPORT_END}`, - }); -}); - -Deno.test("commonjs - __dirname and __filename polyfill", () => { - const DIRNAME_IMPORT = - `import { fileURLToPath as __cjs_fileURLToPath } from "node:url"; -import { dirname as __cjs_dirname } from "node:path"; -var __filename = __cjs_fileURLToPath(import.meta.url); -var __dirname = __cjs_dirname(__filename);`; - runTest({ - input: `var dir = __dirname; -var file = __filename; -module.exports = { dir: dir, file: file };`, - expected: `${INIT} -${DIRNAME_IMPORT} -var dir = __dirname; -var file = __filename; -module.exports = { - dir: dir, - file: file -}; -var _dir = exports.dir; -var _file = exports.file; -export { _dir as dir, _file as file }; -${DEFAULT_EXPORT} -${DEFAULT_EXPORT_END}`, - }); -}); - -Deno.test("commonjs - module.exports.X tracked as named export", () => { - runTest({ - input: `module.exports = function parse() {}; -module.exports.parse = module.exports; -module.exports.stringify = function stringify() {};`, - expected: `${INIT} -module.exports = function parse() {}; -module.exports.parse = module.exports; -module.exports.stringify = function stringify() {}; -var _parse = exports.parse; -var _stringify = exports.stringify; -export { _parse as parse, _stringify as stringify }; -${DEFAULT_EXPORT} -${DEFAULT_EXPORT_END}`, - }); -}); - -Deno.test("commonjs imitating esm - default export exists", () => { - runTest({ - input: `module.exports = { - 'default': 'string', - otherExport: 1 -}; -`, - expected: `${INIT} -module.exports = { - 'default': 'string', - otherExport: 1 -}; -var _otherExport = exports.otherExport; -export { _otherExport as otherExport }; -${DEFAULT_EXPORT} -${DEFAULT_EXPORT_END}`, - }); -}); - -Deno.test("commonjs - primitive module.exports with namespace re-export guards assignment", () => { - runTest({ - input: `__exportStar(require("./utils"), exports); -module.exports = "RFC3986";`, - expected: `${INIT} -import * as _ns from "./utils"; -export * from "./utils"; -module.exports = "RFC3986"; -${DEFAULT_EXPORT} -if (typeof exports === "object" && exports !== null && !("default" in exports)) for (var _k in _ns) if (_k !== "default" && _k !== "__esModule" && Object.prototype.hasOwnProperty.call(_ns, _k)) _default[_k] = _ns[_k]; -${DEFAULT_EXPORT_END}`, - }); -}); diff --git a/packages/plugin-vite/src/plugins/patches/inline_env_vars.ts b/packages/plugin-vite/src/plugins/patches/inline_env_vars.ts deleted file mode 100644 index 820247d1345..00000000000 --- a/packages/plugin-vite/src/plugins/patches/inline_env_vars.ts +++ /dev/null @@ -1,78 +0,0 @@ -import type { NodePath, PluginObj, types } from "@babel/core"; - -export function inlineEnvVarsPlugin(mode: string, env: Record) { - const allowed = new Map(); - for (const [name, value] of Object.entries(env)) { - if (name.startsWith("FRESH_PUBLIC_")) { - allowed.set(name, value); - } - } - - allowed.set("NODE_ENV", mode); - - return ( - { types: t }: { types: typeof types }, - ): PluginObj => { - function replace(path: NodePath, name: string) { - if (allowed.has(name)) { - const value = allowed.get(name); - - if (value !== undefined) { - path.replaceWith(t.stringLiteral(value)); - } else { - path.replaceWith(t.identifier("undefined")); - } - } - } - - return { - name: "fresh-env-var", - visitor: { - MemberExpression(path) { - // Check: process.env.* - if ( - t.isMemberExpression(path.node.object) && - t.isIdentifier(path.node.object.object) && - path.node.object.object.name === "process" && - t.isIdentifier(path.node.object.property) && - path.node.object.property.name === "env" && - t.isIdentifier(path.node.property) - ) { - const name = path.node.property.name; - replace(path, name); - } - - // Check: import.meta.env.* - if ( - t.isIdentifier(path.node.property) && - t.isMemberExpression(path.node.object) && - t.isIdentifier(path.node.object.property) && - path.node.object.property.name === "env" && - t.isMetaProperty(path.node.object.object) - ) { - const name = path.node.property.name; - replace(path, name); - } - }, - CallExpression(path) { - // Check: Deno.env.get("") - if ( - t.isMemberExpression(path.node.callee) && - t.isMemberExpression(path.node.callee.object) && - t.isIdentifier(path.node.callee.object.object) && - path.node.callee.object.object.name === "Deno" && - t.isIdentifier(path.node.callee.object.property) && - path.node.callee.object.property.name === "env" && - t.isIdentifier(path.node.callee.property) && - path.node.callee.property.name === "get" && - path.node.arguments.length > 0 && - t.isStringLiteral(path.node.arguments[0]) - ) { - const name = path.node.arguments[0].value; - replace(path, name); - } - }, - }, - }; - }; -} diff --git a/packages/plugin-vite/src/plugins/patches/inline_env_vars_test.ts b/packages/plugin-vite/src/plugins/patches/inline_env_vars_test.ts deleted file mode 100644 index 424fb2501fd..00000000000 --- a/packages/plugin-vite/src/plugins/patches/inline_env_vars_test.ts +++ /dev/null @@ -1,79 +0,0 @@ -import { expect } from "@std/expect/expect"; -import * as babel from "@babel/core"; -import { inlineEnvVarsPlugin } from "./inline_env_vars.ts"; - -function runTest( - options: { - input: string; - expected: string; - mode?: string; - env?: Record; - }, -) { - const res = babel.transformSync(options.input, { - filename: "foo.js", - babelrc: false, - plugins: [ - inlineEnvVarsPlugin(options.mode ?? "development", options.env ?? {}), - ], - }); - - const output = res?.code ?? ""; - expect(output).toEqual(options.expected); -} - -Deno.test("env vars - inline NODE_ENV mode", () => { - runTest({ - input: `() => process.env.NODE_ENV`, - expected: `() => "asdf";`, - mode: "asdf", - }); -}); - -Deno.test("env vars - inline custom process.env.*", () => { - runTest({ - input: `() => process.env.FRESH_PUBLIC_FOO`, - expected: `() => "a";`, - env: { - FRESH_PUBLIC_FOO: "a", - }, - }); -}); - -Deno.test("env vars - inline Deno.env.get()", () => { - runTest({ - input: `() => Deno.env.get("FRESH_PUBLIC_FOO")`, - expected: `() => "b";`, - env: { - FRESH_PUBLIC_FOO: "b", - }, - }); -}); - -Deno.test("env vars - inline Deno.env.get(NODE_ENV)", () => { - runTest({ - input: `() => Deno.env.get("NODE_ENV")`, - expected: `() => "c";`, - mode: "c", - }); -}); - -Deno.test("env vars - inline const _ = Deno.env.get()", () => { - runTest({ - input: `const deno = Deno.env.get("FRESH_PUBLIC_FOO");`, - expected: `const deno = "test";`, - env: { - FRESH_PUBLIC_FOO: "test", - }, - }); -}); - -Deno.test("env vars - inline import.meta.env.FRESH_PUBLIC_FOO", () => { - runTest({ - input: `() => import.meta.env.FRESH_PUBLIC_FOO;`, - expected: `() => "test";`, - env: { - FRESH_PUBLIC_FOO: "test", - }, - }); -}); diff --git a/packages/plugin-vite/tests/config_test.ts b/packages/plugin-vite/tests/config_test.ts index cd650105de8..366275c4bd6 100644 --- a/packages/plugin-vite/tests/config_test.ts +++ b/packages/plugin-vite/tests/config_test.ts @@ -2,7 +2,7 @@ import { expect } from "@std/expect"; import { fresh } from "../src/mod.ts"; import type { Plugin } from "vite"; -Deno.test("fresh plugin - sets server.watch.ignored patterns", () => { +Deno.test("fresh plugin - sets server.watch.ignored patterns", async () => { const plugins = fresh() as Plugin[]; const freshPlugin = plugins.find((p) => p.name === "fresh"); expect(freshPlugin).toBeDefined(); @@ -10,7 +10,7 @@ Deno.test("fresh plugin - sets server.watch.ignored patterns", () => { // Call the config hook as Vite would during dev // deno-lint-ignore no-explicit-any const configFn = freshPlugin!.config as any; - const result = configFn({}, { command: "serve" }); + const result = await configFn({}, { command: "serve" }); const ignored = result?.server?.watch?.ignored; expect(ignored).toBeDefined();