Skip to content

Commit da47931

Browse files
committed
feat: warn used dep in devDependencies under bundeless mode
1 parent e2dde90 commit da47931

File tree

6 files changed

+132
-0
lines changed

6 files changed

+132
-0
lines changed

packages/core/src/config.ts

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1391,6 +1391,7 @@ const composeBundlelessExternalConfig = (
13911391
cssModulesAuto: CssLoaderOptionsAuto,
13921392
bundle: boolean,
13931393
outBase: string | null,
1394+
pkgJson?: PkgJson,
13941395
): {
13951396
config: EnvironmentConfig;
13961397
resolvedJsRedirect?: DeepRequired<JsRedirect>;
@@ -1405,6 +1406,60 @@ const composeBundlelessExternalConfig = (
14051406
const assetRedirectExtension = redirect.asset?.extension ?? true;
14061407

14071408
let resolver: RspackResolver | undefined;
1409+
const devDependencyNames = new Set(
1410+
Object.keys(pkgJson?.devDependencies ?? {}),
1411+
);
1412+
const warnedDevDependencies = new Set<string>();
1413+
1414+
const devDependencyList = Array.from(devDependencyNames);
1415+
1416+
const matchDevDependency = (request: string): string | undefined => {
1417+
if (!request || devDependencyList.length === 0) {
1418+
return undefined;
1419+
}
1420+
1421+
for (const name of devDependencyList) {
1422+
if (request === name || request.startsWith(`${name}/`)) {
1423+
return name;
1424+
}
1425+
}
1426+
1427+
return undefined;
1428+
};
1429+
1430+
const warnDevDependency = (request: string, issuer?: string) => {
1431+
const matched = matchDevDependency(request);
1432+
if (!matched || warnedDevDependencies.has(matched)) {
1433+
return;
1434+
}
1435+
1436+
// Only warn when we have an issuer and outBase is a string and the issuer
1437+
// is located inside the outBase (root) directory. Otherwise skip warning.
1438+
if (!issuer || typeof outBase !== 'string') {
1439+
return;
1440+
}
1441+
1442+
const normalizedIssuer = normalizeSlash(issuer);
1443+
const normalizedOutBase = normalizeSlash(outBase);
1444+
const isIssuerInsideOutBase =
1445+
normalizedIssuer === normalizedOutBase ||
1446+
normalizedIssuer.startsWith(`${normalizedOutBase}/`);
1447+
1448+
if (!isIssuerInsideOutBase) {
1449+
return;
1450+
}
1451+
1452+
warnedDevDependencies.add(matched);
1453+
1454+
const relativeSource = normalizeSlash(path.relative(outBase, issuer));
1455+
const sourceInfo = relativeSource
1456+
? ` from ${color.green(relativeSource)}`
1457+
: '';
1458+
1459+
logger.warn(
1460+
`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"')}.`,
1461+
);
1462+
};
14081463

14091464
return {
14101465
resolvedJsRedirect: {
@@ -1478,6 +1533,7 @@ const composeBundlelessExternalConfig = (
14781533
// Prevent from externalizing entry modules here.
14791534
if (issuer) {
14801535
let resolvedRequest: string = request;
1536+
warnDevDependency(request, issuer);
14811537

14821538
const redirectedPath = await redirectPath(resolvedRequest);
14831539
const cssExternal = await cssExternalHandler(
@@ -1778,6 +1834,7 @@ async function composeLibRsbuildConfig(
17781834
cssModulesAuto,
17791835
bundle,
17801836
outBase,
1837+
pkgJson,
17811838
);
17821839
const {
17831840
config: targetConfig,

pnpm-lock.yaml

Lines changed: 20 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
{
2+
"name": "bundleless-dev-dependency-warning",
3+
"version": "0.0.0",
4+
"private": true,
5+
"type": "module",
6+
"devDependencies": {
7+
"left-pad": "1.0.0",
8+
"normalize.css": "8.0.1"
9+
}
10+
}
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
import { defineConfig } from '@rslib/core';
2+
import { generateBundleEsmConfig } from 'test-helper';
3+
4+
export default defineConfig({
5+
lib: [
6+
generateBundleEsmConfig({
7+
bundle: false,
8+
source: {
9+
entry: {
10+
index: 'src/index.ts',
11+
},
12+
},
13+
output: {
14+
distPath: 'dist',
15+
},
16+
}),
17+
],
18+
});
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
import leftPad from 'left-pad';
2+
import leftPadLib from 'left-pad/lib';
3+
import 'normalize.css';
4+
5+
export const primary = leftPad('foo', 5, ' ');
6+
export const secondary = leftPadLib;

tests/integration/externals/index.test.ts

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -112,3 +112,24 @@ test('user externals', async () => {
112112
"
113113
`);
114114
});
115+
116+
test('warn when bundleless external depends on devDependencies', async () => {
117+
const fixturePath = join(__dirname, 'dev-dependency-warning');
118+
const { logs, restore } = proxyConsole();
119+
120+
await buildAndGetResults({ fixturePath });
121+
122+
restore();
123+
const warnLogs = logs.map((log) => stripAnsi(String(log)));
124+
console.log(warnLogs);
125+
const jsMatchingLog = warnLogs.filter(
126+
(log) =>
127+
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".')
128+
);
129+
expect(jsMatchingLog.length).toBe(1);
130+
const cssMatchingLog = warnLogs.filter(
131+
(log) =>
132+
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".')
133+
);
134+
expect(cssMatchingLog.length).toBe(1);
135+
});

0 commit comments

Comments
 (0)