From 5cdf61d79e3f49f0d722cf44b080277921f16cb4 Mon Sep 17 00:00:00 2001 From: Severin Ibarluzea Date: Mon, 1 Dec 2025 20:38:38 -0800 Subject: [PATCH] Bundle dependencies in transpile output --- cli/build/transpile/index.ts | 30 +++++++++++++-- .../transpile/transpile-dependencies.test.ts | 38 +++++++++++++++++++ 2 files changed, 65 insertions(+), 3 deletions(-) create mode 100644 tests/cli/transpile/transpile-dependencies.test.ts diff --git a/cli/build/transpile/index.ts b/cli/build/transpile/index.ts index 22a3697d..bdb23638 100644 --- a/cli/build/transpile/index.ts +++ b/cli/build/transpile/index.ts @@ -1,5 +1,6 @@ -import path from "node:path" import fs from "node:fs" +import { builtinModules } from "node:module" +import path from "node:path" import { rollup } from "rollup" import typescript from "@rollup/plugin-typescript" import resolve from "@rollup/plugin-node-resolve" @@ -13,6 +14,19 @@ import { STATIC_ASSET_EXTENSIONS, } from "./static-asset-plugin" +const isNodeBuiltin = (id: string) => { + const withoutNodePrefix = id.replace(/^node:/, "") + + return ( + builtinModules.includes(id) || + builtinModules.includes(withoutNodePrefix) || + builtinModules.includes(`node:${withoutNodePrefix}`) + ) +} + +const isTscircuitDependency = (id: string) => + id === "tscircuit" || id.startsWith("tscircuit/") + const createExternalFunction = (projectDir: string, tsconfigPath?: string) => (id: string): boolean => { @@ -71,8 +85,18 @@ const createExternalFunction = return false // This is a local file, don't externalize } - // Everything else (npm packages like 'react', 'tscircuit', etc.) is external - return true + // Keep Node.js built-ins external + if (isNodeBuiltin(id)) { + return true + } + + // Always externalize tscircuit so it's not bundled with user output + if (isTscircuitDependency(id)) { + return true + } + + // Bundle all other dependencies into the output + return false } export const transpileFile = async ({ diff --git a/tests/cli/transpile/transpile-dependencies.test.ts b/tests/cli/transpile/transpile-dependencies.test.ts new file mode 100644 index 00000000..9a9da6d2 --- /dev/null +++ b/tests/cli/transpile/transpile-dependencies.test.ts @@ -0,0 +1,38 @@ +import { test, expect } from "bun:test" +import { readFile, symlink, writeFile } from "node:fs/promises" +import path from "node:path" +import { getCliTestFixture } from "../../fixtures/get-cli-test-fixture" + +test("transpile bundles dependencies except for tscircuit", async () => { + const { tmpDir, runCommand } = await getCliTestFixture() + const circuitPath = path.join(tmpDir, "with-deps.ts") + + await writeFile( + circuitPath, + `import kleur from "kleur" +import * as tscircuit from "tscircuit" + +export const coloredText = kleur.red("ok") +export const usesTscircuit = () => + typeof tscircuit === "object" ? "tscircuit-present" : "missing" +`, + ) + + await writeFile(path.join(tmpDir, "package.json"), "{}") + + await symlink( + path.resolve(process.cwd(), "node_modules"), + path.join(tmpDir, "node_modules"), + "dir", + ) + + await runCommand(`tsci transpile ${circuitPath}`) + + const esmPath = path.join(tmpDir, "dist", "index.js") + const esmContent = await readFile(esmPath, "utf-8") + + expect(esmContent).toMatch(/from ['\"]tscircuit['\"]/) + expect(esmContent).not.toContain('from "kleur"') + expect(esmContent).not.toContain('require("kleur")') + expect(esmContent).toContain("tscircuit-present") +}, 30_000)