Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
57 changes: 57 additions & 0 deletions packages/core/src/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1391,6 +1391,7 @@ const composeBundlelessExternalConfig = (
cssModulesAuto: CssLoaderOptionsAuto,
bundle: boolean,
outBase: string | null,
pkgJson?: PkgJson,
): {
config: EnvironmentConfig;
resolvedJsRedirect?: DeepRequired<JsRedirect>;
Expand All @@ -1405,6 +1406,60 @@ const composeBundlelessExternalConfig = (
const assetRedirectExtension = redirect.asset?.extension ?? true;

let resolver: RspackResolver | undefined;
const devDependencyNames = new Set(
Object.keys(pkgJson?.devDependencies ?? {}),
);
const warnedDevDependencies = new Set<string>();

const devDependencyList = Array.from(devDependencyNames);

const matchDevDependency = (request: string): string | undefined => {
if (!request || devDependencyList.length === 0) {
return undefined;
}

for (const name of devDependencyList) {
if (request === name || request.startsWith(`${name}/`)) {
return name;
}
}

return undefined;
};

const warnDevDependency = (request: string, issuer?: string) => {
const matched = matchDevDependency(request);
if (!matched || warnedDevDependencies.has(matched)) {
return;
}

// Only warn when we have an issuer and outBase is a string and the issuer
// is located inside the outBase (root) directory. Otherwise skip warning.
if (!issuer || typeof outBase !== 'string') {
return;
}

const normalizedIssuer = normalizeSlash(issuer);
const normalizedOutBase = normalizeSlash(outBase);
const isIssuerInsideOutBase =
normalizedIssuer === normalizedOutBase ||
normalizedIssuer.startsWith(`${normalizedOutBase}/`);

if (!isIssuerInsideOutBase) {
return;
}

warnedDevDependencies.add(matched);

const relativeSource = normalizeSlash(path.relative(outBase, issuer));
const sourceInfo = relativeSource
? ` from ${color.green(relativeSource)}`
: '';

logger.warn(
`The externalized request ${color.green(`"${request}"`)}${sourceInfo} is declared in ${color.blue('"devDependencies"')} in package.json. Bundleless mode does not include devDependencies in the output, consider moving it to ${color.blue('"dependencies"')} or ${color.blue('"peerDependencies"')}.`,
);
};

return {
resolvedJsRedirect: {
Expand Down Expand Up @@ -1478,6 +1533,7 @@ const composeBundlelessExternalConfig = (
// Prevent from externalizing entry modules here.
if (issuer) {
let resolvedRequest: string = request;
warnDevDependency(request, issuer);

const redirectedPath = await redirectPath(resolvedRequest);
const cssExternal = await cssExternalHandler(
Expand Down Expand Up @@ -1778,6 +1834,7 @@ async function composeLibRsbuildConfig(
cssModulesAuto,
bundle,
outBase,
pkgJson,
);
const {
config: targetConfig,
Expand Down
20 changes: 20 additions & 0 deletions pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

10 changes: 10 additions & 0 deletions tests/integration/externals/dev-dependency-warning/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
{
"name": "bundleless-dev-dependency-warning",
"version": "0.0.0",
"private": true,
"type": "module",
"devDependencies": {
"left-pad": "1.0.0",
"normalize.css": "8.0.1"
}
}
18 changes: 18 additions & 0 deletions tests/integration/externals/dev-dependency-warning/rslib.config.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import { defineConfig } from '@rslib/core';
import { generateBundleEsmConfig } from 'test-helper';

export default defineConfig({
lib: [
generateBundleEsmConfig({
bundle: false,
source: {
entry: {
index: 'src/index.ts',
},
},
output: {
distPath: 'dist',
},
}),
],
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
import leftPad from 'left-pad';
import leftPadLib from 'left-pad/lib';
import 'normalize.css';

export const primary = leftPad('foo', 5, ' ');
export const secondary = leftPadLib;
21 changes: 21 additions & 0 deletions tests/integration/externals/index.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -112,3 +112,24 @@
"
`);
});

test('warn when bundleless external depends on devDependencies', async () => {
const fixturePath = join(__dirname, 'dev-dependency-warning');
const { logs, restore } = proxyConsole();

await buildAndGetResults({ fixturePath });

restore();
const warnLogs = logs.map((log) => stripAnsi(String(log)));
console.log(warnLogs);
Copy link

Copilot AI Oct 28, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Remove debug console.log statement before merging to production. This appears to be leftover debugging code.

Suggested change
console.log(warnLogs);

Copilot uses AI. Check for mistakes.
const jsMatchingLog = warnLogs.filter(
(log) =>
log.includes('The externalized request "left-pad/lib" from index.ts is declared in "devDependencies" in package.json. Bundleless mode does not include devDependencies in the output, consider moving it to "dependencies" or "peerDependencies".')
Copy link

Copilot AI Oct 28, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Extract the repeated warning message text into a constant to avoid duplication and make future updates easier. The same message is duplicated on line 132 with only the package name difference.

Copilot uses AI. Check for mistakes.
);
expect(jsMatchingLog.length).toBe(1);

Check failure on line 129 in tests/integration/externals/index.test.ts

View workflow job for this annotation

GitHub Actions / integration (ubuntu-latest, 18) / test

tests/integration/externals/index.test.ts > warn when bundleless external depends on devDependencies

expected +0 to be 1 // Object.is equality - Expected + Received - 1 + 0
const cssMatchingLog = warnLogs.filter(
(log) =>
log.includes('The externalized request "normalize.css" from index.ts is declared in "devDependencies" in package.json. Bundleless mode does not include devDependencies in the output, consider moving it to "dependencies" or "peerDependencies".')
);
expect(cssMatchingLog.length).toBe(1);
});
Loading