Repository: rollup/rollup (2025-07-28T16:02:32Z)

Issue:
Importing workspace projects in TS config files
### Feature Use Case

When working in TS monorepos, they are implemented using `npm`/`yarn`/`pnpm`/etc workspaces.
There is typically more than one bundlable package, and often the Rollup configuration contains some commonly shared properties or organization-specific rules or guidelines.

These shared features of the config files are intuitive to expose via another workspace package, which is used as development dependency only during bundling.
However, `import`ing such package in `rollup.config.ts` will cause errors, because the `external` check in `cli/run/loadConfigFile.ts` will think that any import which doesn't look like relative or absolute path is external.
As a result, the final CommonJS-transpiled configuration file will include the `import` statement, and will fail, since there is no JS code in the target package .

### Feature Proposal

Because using relative or absolute path imports is not always feasible (think of package included via `git submodule`), it is important to have support for this usecase of complex monorepo setup.
The support should be conservatively implemented - that is, activated only via some configuration which is missing or not active by default.
The support should be agnostic to the TypeScript usage, and instead utilize some generic way to find out whether import which doesn't look like path should be marked as non-external after all.


Discussion:
If I understand correctly, this issue only happens when the imported workspace package hasn’t been built yet — for example, when it only contains TypeScript source files.
Once the workspace package has been built, the problem no longer occurs.
Given that, I’m not sure this should be considered an "issue" with Rollup, and it might not require any special handling.
TLDR: I think it's issue with Rollup because:
- It involves recursive dependency crawling which otherwise would be manually upkept "hidden information", and
- Rollup already handles relative imports within `rollup.config.ts` correctly, so it should also handle imports to workspace TS projects. 

@TrickyPi Yes, your understanding is correct! 👍 It is also true that if I build that specific package into `.js` files, the problem no longer occurs - assuming that package itself doesn't reference other workspace packages. You can probably see where this is going, but let me just elaborate with a real-life situation that I am facing at my job, which prompted me to make this issue and PR.

In the project I am currently working, we never use `tsc` for building to `.js` files. We are faced with only the following situations (all part of our CICD pipelines):
- Run `tsc` in `noEmit` mode to ensure typesafety of code being deployed
- Run `vitest` for `.ts` files to perform unit testing
- Run `rollup` for bundling final applications (we have several microservices) to be deployed to cloud
- Run `tsx` for for IaC pipelines written in TypeScript which handle actual cloud deployment

So running `tsc` just to make Rollup read our `rollup.config.ts` files for several applications is not very nice, especially since we never emit any `.js` files at any point except via Rollup itself. But it still could be one possible solution.

However, things become exceedingly more difficult when the workspace package referenced by `rollup.config.ts` references  other workspace packages. This is _simplified_ example of the current situation we have:
- We have a library for handling logging-related things, in order for all our application and IaC code to handle logging in uniform way. Let's say it resides in `packages/our-logging-library` directory.
- We also have a library for handling common Rollup-related things since we want for all our microservice applications always use same uniform way to handle their Rollup configuration - which is never trivial for any of them. Let's say it resides in `packages/our-rollup-library` directory.
- This Rollup-related library needs to log things, and thus, it uses the logging library from `packages/our-logging-library` via workspace reference.
- When our `packages/some-microservice-application` wants to use `rollup.config.ts` which utilizes `packages/our-rollup-library`, simply building `packages/our-rollup-library` is not enough - the `packages/our-logging-library` would need to be built too.

So, in order to circumvent this problem by producing `.js` files via `tsc`, our applications need to be aware of ALL the workspace dependencies that `packages/our-rollup-library` has, and run `tsc` first for all of them, before invoking their own bundling process.

Since this has now become conceptually a problem of crawling through dependencies of TS code, I think this is actually exactly why _this is issue with Rollup_. Rollup already performs this dependency crawling and bundling for the `rollup.config.ts`, and it can handle _relative_ imports to TS code, and IMO it should also handle workspace imports as well.
I also want to add that PR I have created is extremely noninvasive, and its changes activate only when a flag is supplied via CLI. I just had two questions/issues with providing `PluginContext` for plugins when querying externality, as well as handling potential asyncness. I am more than happy to improve work there if given few pointers or ideas 👍 
Hmmm, that makes sense. Thanks for the explanation!
This issue has been resolved via #6038 as part of rollup@4.49.0. You can test it via `npm install rollup`.



Patch:
diff --git a/cli/run/loadConfigFile.ts b/cli/run/loadConfigFile.ts
index 6bd243c9740..7ea79b43761 100644
--- a/cli/run/loadConfigFile.ts
+++ b/cli/run/loadConfigFile.ts
@@ -3,7 +3,11 @@ import path from 'node:path';
 import process from 'node:process';
 import { pathToFileURL } from 'node:url';
 import * as rollup from '../../src/node-entry';
-import type { ImportAttributesKey, MergedRollupOptions } from '../../src/rollup/types';
+import type {
+	ImportAttributesKey,
+	InputOptionsWithPlugins,
+	MergedRollupOptions
+} from '../../src/rollup/types';
 import { bold } from '../../src/utils/colors';
 import {
 	error,
@@ -104,14 +108,27 @@ async function loadTranspiledConfigFile(
 ): Promise<unknown> {
 	const { bundleConfigAsCjs, configPlugin, configImportAttributesKey, silent } = commandOptions;
 	const warnings = batchWarnings(commandOptions);
-	const inputOptions = {
-		external: (id: string) => (id[0] !== '.' && !path.isAbsolute(id)) || id.slice(-5) === '.json',
+	const inputOptions: InputOptionsWithPlugins = {
+		// Do *not* specify external callback here - instead, perform the externality check it via fallback-plugin just below this comment.
+		// This allows config plugin to first decide whether some import is external or not, and only then trigger the check in fallback-plugin.
+		// Since the check is ultra-simple during this stage of transforming the config file itself, it should be fallback instead of primary check.
+		// That way, e.g. importing workspace packages will work as expected - the workspace package will be bundled.
 		input: fileName,
 		onwarn: warnings.add,
 		plugins: [],
 		treeshake: false
 	};
 	await addPluginsFromCommandOption(configPlugin, inputOptions);
+	// Add plugin as *last* item after addPluginsFromCommandOption is complete.
+	// This plugin will trigger for imports not resolved by config plugin, and mark all non-relative imports as external.
+	inputOptions.plugins.push({
+		name: 'external-fallback',
+		resolveId: source => {
+			const looksLikeExternal =
+				(source[0] !== '.' && !path.isAbsolute(source)) || source.slice(-5) === '.json';
+			return looksLikeExternal ? false : source;
+		}
+	});
 	const bundle = await rollup.rollup(inputOptions);
 	const {
 		output: [{ code }]


Test patch:
diff --git a/test/cli/samples/config-ts-with-workspace-refs/_config.js b/test/cli/samples/config-ts-with-workspace-refs/_config.js
new file mode 100644
index 00000000000..f91a9578d33
--- /dev/null
+++ b/test/cli/samples/config-ts-with-workspace-refs/_config.js
@@ -0,0 +1,5 @@
+module.exports = defineTest({
+	description: 'supports loading TypeScript config files referencing workspace projects',
+	spawnArgs: ['--config', 'rollup.config.ts', '--configPlugin', 'typescript'],
+	execute: false
+});
diff --git a/test/cli/samples/config-ts-with-workspace-refs/_expected.js b/test/cli/samples/config-ts-with-workspace-refs/_expected.js
new file mode 100644
index 00000000000..cc1d88a24fa
--- /dev/null
+++ b/test/cli/samples/config-ts-with-workspace-refs/_expected.js
@@ -0,0 +1,1 @@
+assert.ok(true);
diff --git a/test/cli/samples/config-ts-with-workspace-refs/input.js b/test/cli/samples/config-ts-with-workspace-refs/input.js
new file mode 100644
index 00000000000..cc1d88a24fa
--- /dev/null
+++ b/test/cli/samples/config-ts-with-workspace-refs/input.js
@@ -0,0 +1,1 @@
+assert.ok(true);
diff --git a/test/cli/samples/config-ts-with-workspace-refs/node_modules/referenced-link b/test/cli/samples/config-ts-with-workspace-refs/node_modules/referenced-link
new file mode 120000
index 00000000000..9fdaedb4fd6
--- /dev/null
+++ b/test/cli/samples/config-ts-with-workspace-refs/node_modules/referenced-link
@@ -0,0 +1,1 @@
+../packages/referenced
\ No newline at end of file
diff --git a/test/cli/samples/config-ts-with-workspace-refs/packages/referenced/index.ts b/test/cli/samples/config-ts-with-workspace-refs/packages/referenced/index.ts
new file mode 100644
index 00000000000..bc33164ac00
--- /dev/null
+++ b/test/cli/samples/config-ts-with-workspace-refs/packages/referenced/index.ts
@@ -0,0 +1,8 @@
+import type { RollupOptions } from '../../../../../../dist/rollup';
+
+export const MY_CUSTOM_OPTIONS: RollupOptions = {
+	input: 'input.js',
+	output: {
+		format: 'esm',
+	}
+};
diff --git a/test/cli/samples/config-ts-with-workspace-refs/packages/referenced/package.json b/test/cli/samples/config-ts-with-workspace-refs/packages/referenced/package.json
new file mode 100644
index 00000000000..c0b7a316979
--- /dev/null
+++ b/test/cli/samples/config-ts-with-workspace-refs/packages/referenced/package.json
@@ -0,0 +1,3 @@
+{
+  "main": "index.ts"
+}
\ No newline at end of file
diff --git a/test/cli/samples/config-ts-with-workspace-refs/rollup.config.ts b/test/cli/samples/config-ts-with-workspace-refs/rollup.config.ts
new file mode 100644
index 00000000000..00d67a64225
--- /dev/null
+++ b/test/cli/samples/config-ts-with-workspace-refs/rollup.config.ts
@@ -0,0 +1,3 @@
+import { MY_CUSTOM_OPTIONS } from "referenced-link";
+
+export default MY_CUSTOM_OPTIONS;