diff --git a/src/build.ts b/src/build.ts index 94aa026..6aae9c7 100644 --- a/src/build.ts +++ b/src/build.ts @@ -9,7 +9,13 @@ import { defu } from "defu"; import { createHooks } from "hookable"; import prettyBytes from "pretty-bytes"; import { glob } from "tinyglobby"; -import { dumpObject, rmdir, resolvePreset, removeExtension } from "./utils"; +import { + dumpObject, + rmdir, + resolvePreset, + removeExtension, + inferPkgExternals, +} from "./utils"; import type { BuildContext, BuildConfig, BuildOptions } from "./types"; import { validatePackage, validateDependencies } from "./validate"; import { rollupBuild } from "./builders/rollup"; @@ -228,7 +234,8 @@ async function _build( options.devDependencies = Object.keys(pkg.devDependencies || {}); // Add all dependencies as externals - options.externals.push(...options.dependencies, ...options.peerDependencies); + options.externals.push(...inferPkgExternals(pkg)); + options.externals = [...new Set(options.externals)]; // Call build:before await ctx.hooks.callHook("build:before", ctx); diff --git a/src/utils.ts b/src/utils.ts index 7dee9bb..b2f1702 100644 --- a/src/utils.ts +++ b/src/utils.ts @@ -162,3 +162,42 @@ export function arrayIncludes( export function removeExtension(filename: string): string { return filename.replace(/\.(js|mjs|cjs|ts|mts|cts|json|jsx|tsx)$/, ""); } + +export function inferPkgExternals(pkg: PackageJson): (string | RegExp)[] { + const externals: (string | RegExp)[] = [ + ...Object.keys(pkg.dependencies || {}), + ...Object.keys(pkg.peerDependencies || {}), + ...Object.keys(pkg.devDependencies || {}), + ...Object.keys(pkg.optionalDependencies || {}), + ]; + + if (pkg.name) { + externals.push(pkg.name); + if (pkg.exports) { + for (const subpath of Object.keys(pkg.exports)) { + if (subpath.startsWith("./")) { + externals.push(pathToRegex(`${pkg.name}/${subpath.slice(2)}`)); + } + } + } + } + + if (pkg.imports) { + for (const importName of Object.keys(pkg.imports)) { + if (importName.startsWith("#")) { + externals.push(pathToRegex(importName)); + } + } + } + + return [...new Set(externals)]; +} + +function pathToRegex(path: string): string | RegExp { + if (!path.includes("*")) { + return path; + } + return new RegExp( + path.replace(/\./g, String.raw`\.`).replace(/\*/g, ".*") + "$", + ); +} diff --git a/test/utils.test.ts b/test/utils.test.ts index d308381..2f38684 100644 --- a/test/utils.test.ts +++ b/test/utils.test.ts @@ -3,6 +3,7 @@ import { arrayIncludes, extractExportFilenames, inferExportType, + inferPkgExternals, } from "../src/utils"; describe("inferExportType", () => { @@ -52,3 +53,38 @@ describe("arrayIncludes", () => { expect(arrayIncludes([/t3$/, "test2"], "test1")).to.eq(false); }); }); + +describe("inferPkgExternals", () => { + it("infers externals from package.json", () => { + expect( + inferPkgExternals({ + name: "test", + dependencies: { react: "17.0.0" }, + peerDependencies: { "react-dom": "17.0.0" }, + devDependencies: { "@types/react": "17.0.0" }, + optionalDependencies: { test: "1.0.0", optional: "1.0.0" }, + exports: { + ".": "index.js", + "./extra/utils": "utils.js", + "./drivers/*.js": "drivers/*.js", + invalid: "invalid.js", + }, + imports: { + "#*": "src/*", + "#test": "test.js", + invalid: "invalid.js", + }, + }), + ).to.deep.equal([ + "react", + "react-dom", + "@types/react", + "test", + "optional", + "test/extra/utils", + /test\/drivers\/.*\.js$/, + /#.*$/, + "#test", + ]); + }); +});