diff --git a/packages/rspack/src/plugins/utils/apply-base-config.ts b/packages/rspack/src/plugins/utils/apply-base-config.ts index c4a0d82275..1077c12c08 100644 --- a/packages/rspack/src/plugins/utils/apply-base-config.ts +++ b/packages/rspack/src/plugins/utils/apply-base-config.ts @@ -19,6 +19,7 @@ import { getTerserEcmaVersion } from './get-terser-ecma-version'; import nodeExternals = require('webpack-node-externals'); import { NormalizedNxAppRspackPluginOptions } from './models'; import { isUsingTsSolutionSetup } from '@nx/js/src/utils/typescript/ts-solution-setup'; +import { isBuildableLibrary } from './is-lib-buildable'; const IGNORED_RSPACK_WARNINGS = [ /The comment file/i, @@ -350,7 +351,40 @@ function applyNxDependentConfig( options.externalDependencies === 'all' ) { const modulesDir = `${options.root}/node_modules`; - externals.push(nodeExternals({ modulesDir })); + const graph = options.projectGraph; + const projectName = options.projectName; + + const deps = graph?.dependencies?.[projectName] ?? []; + + // Collect non-buildable TS project references so that they are bundled + // in the final output. This is needed for projects that are not buildable + // but are referenced by buildable projects. This is needed for the new TS + // solution setup. + const nonBuildableWorkspaceLibs = isUsingTsSolution + ? deps + .filter((dep) => { + const node = graph.nodes?.[dep.target]; + if (!node || node.type !== 'lib') return false; + + const hasBuildTarget = 'build' in (node.data?.targets ?? {}); + + if (hasBuildTarget) { + return false; + } + + // If there is no build target we check the package exports to see if they reference + // source files + return !isBuildableLibrary(node); + }) + .map( + (dep) => graph.nodes?.[dep.target]?.data?.metadata?.js?.packageName + ) + .filter((name): name is string => !!name) + : []; + + externals.push( + nodeExternals({ modulesDir, allowlist: nonBuildableWorkspaceLibs }) + ); } else if (Array.isArray(options.externalDependencies)) { externals.push(function (ctx, callback: Function) { if (options.externalDependencies.includes(ctx.request)) { diff --git a/packages/rspack/src/plugins/utils/is-lib-buildable.ts b/packages/rspack/src/plugins/utils/is-lib-buildable.ts new file mode 100644 index 0000000000..e276296efa --- /dev/null +++ b/packages/rspack/src/plugins/utils/is-lib-buildable.ts @@ -0,0 +1,58 @@ +import { type ProjectGraphProjectNode } from '@nx/devkit'; + +function isSourceFile(path: string): boolean { + return ['.ts', '.tsx', '.mts', '.cts'].some((ext) => path.endsWith(ext)); +} + +function isBuildableExportMap(packageExports: any): boolean { + if (!packageExports || Object.keys(packageExports).length === 0) { + return false; // exports = {} → not buildable + } + + const isCompiledExport = (value: unknown): boolean => { + if (typeof value === 'string') { + return !isSourceFile(value); + } + if (typeof value === 'object' && value !== null) { + return Object.entries(value).some(([key, subValue]) => { + if ( + key === 'types' || + key === 'development' || + key === './package.json' + ) + return false; + return typeof subValue === 'string' && !isSourceFile(subValue); + }); + } + return false; + }; + + if (packageExports['.']) { + return isCompiledExport(packageExports['.']); + } + + return Object.entries(packageExports).some( + ([key, value]) => key !== '.' && isCompiledExport(value) + ); +} + +/** + * Check if the library is buildable. + * @param node from the project graph + * @returns boolean + */ +export function isBuildableLibrary(node: ProjectGraphProjectNode): boolean { + if (!node.data.metadata?.js) { + return false; + } + const { packageExports, packageMain } = node.data.metadata.js; + // if we have exports only check this else fallback to packageMain + if (packageExports) { + return isBuildableExportMap(packageExports); + } + return ( + typeof packageMain === 'string' && + packageMain !== '' && + !isSourceFile(packageMain) + ); +}