Skip to content

Commit be3a8cd

Browse files
committed
feat: support outBase config
1 parent 36dea47 commit be3a8cd

File tree

14 files changed

+167
-19
lines changed

14 files changed

+167
-19
lines changed

packages/core/src/config.ts

+28-19
Original file line numberDiff line numberDiff line change
@@ -889,13 +889,14 @@ const composeEntryConfig = async (
889889
bundle: LibConfig['bundle'],
890890
root: string,
891891
cssModulesAuto: CssLoaderOptionsAuto,
892-
): Promise<{ entryConfig: EnvironmentConfig; lcp: string | null }> => {
892+
userOutBase?: string,
893+
): Promise<{ entryConfig: EnvironmentConfig; outBase: string | null }> => {
893894
let entries: RsbuildConfigEntry = rawEntry;
894895

895896
if (!entries) {
896897
// In bundle mode, return directly to let Rsbuild apply default entry to './src/index.ts'
897898
if (bundle !== false) {
898-
return { entryConfig: {}, lcp: null };
899+
return { entryConfig: {}, outBase: null };
899900
}
900901

901902
// In bundleless mode, set default entry to './src/**'
@@ -957,13 +958,22 @@ const composeEntryConfig = async (
957958
entry: appendEntryQuery(resolveEntryPath(entries, root)),
958959
},
959960
},
960-
lcp: null,
961+
outBase: null,
961962
};
962963
}
963964

964-
const scanGlobEntries = async (calcLcp: boolean) => {
965+
const scanGlobEntries = async (tryResolveOutBase: boolean) => {
965966
// In bundleless mode, resolve glob patterns and convert them to entry object.
966967
const resolvedEntries: Record<string, string> = {};
968+
969+
const resolveOutBase = async (resolvedEntryFiles: string[]) => {
970+
// Similar to `rootDir` in tsconfig and `outbase` in esbuild.
971+
const lcp = await calcLongestCommonPath(resolvedEntryFiles);
972+
// Using the longest common path of all non-declaration input files by default.
973+
const outBase = userOutBase ?? (lcp === null ? root : lcp);
974+
return outBase;
975+
};
976+
967977
for (const key of Object.keys(entries)) {
968978
const entry = entries[key];
969979

@@ -998,10 +1008,7 @@ const composeEntryConfig = async (
9981008
throw new Error(`Cannot find ${resolvedEntryFiles}`);
9991009
}
10001010

1001-
// Similar to `rootDir` in tsconfig and `outbase` in esbuild.
1002-
const lcp = await calcLongestCommonPath(resolvedEntryFiles);
1003-
// Using the longest common path of all non-declaration input files by default.
1004-
const outBase = lcp === null ? root : lcp;
1011+
const outBase = await resolveOutBase(resolvedEntryFiles);
10051012

10061013
function getEntryName(file: string) {
10071014
const { dir, name } = path.parse(path.relative(outBase, file));
@@ -1021,7 +1028,7 @@ const composeEntryConfig = async (
10211028
const entryName = getEntryName(file);
10221029

10231030
if (resolvedEntries[entryName]) {
1024-
calcLcp &&
1031+
tryResolveOutBase &&
10251032
logger.warn(
10261033
`Duplicate entry ${color.cyan(entryName)} from ${color.cyan(
10271034
path.relative(root, file),
@@ -1035,15 +1042,15 @@ const composeEntryConfig = async (
10351042
}
10361043
}
10371044

1038-
if (calcLcp) {
1039-
const lcp = await calcLongestCommonPath(Object.values(resolvedEntries));
1040-
return { resolvedEntries, lcp };
1045+
if (tryResolveOutBase) {
1046+
const outBase = await resolveOutBase(Object.values(resolvedEntries));
1047+
return { resolvedEntries, outBase };
10411048
}
1042-
return { resolvedEntries, lcp: null };
1049+
return { resolvedEntries, outBase: null };
10431050
};
10441051

1045-
// LCP could only be determined at the first time of glob scan.
1046-
const { lcp } = await scanGlobEntries(true);
1052+
// OutBase could only be determined at the first time of glob scan.
1053+
const { outBase } = await scanGlobEntries(true);
10471054
const entryConfig: EnvironmentConfig = {
10481055
tools: {
10491056
rspack: {
@@ -1057,7 +1064,7 @@ const composeEntryConfig = async (
10571064

10581065
return {
10591066
entryConfig,
1060-
lcp,
1067+
outBase,
10611068
};
10621069
};
10631070

@@ -1415,14 +1422,15 @@ async function composeLibRsbuildConfig(
14151422
pkgJson,
14161423
userExternals: config.output?.externals,
14171424
});
1418-
const { entryConfig, lcp } = await composeEntryConfig(
1425+
const { entryConfig, outBase } = await composeEntryConfig(
14191426
config.source?.entry!,
14201427
config.bundle,
14211428
rootPath,
14221429
cssModulesAuto,
1430+
config.outBase,
14231431
);
14241432
const cssConfig = composeCssConfig(
1425-
lcp,
1433+
outBase,
14261434
config.bundle,
14271435
banner?.css,
14281436
footer?.css,
@@ -1431,7 +1439,7 @@ async function composeLibRsbuildConfig(
14311439

14321440
const entryChunkConfig = composeEntryChunkConfig({
14331441
enabledImportMetaUrlShim: enabledShims.cjs['import.meta.url'],
1434-
contextToWatch: lcp,
1442+
contextToWatch: outBase,
14351443
});
14361444
const dtsConfig = await composeDtsConfig(config, dtsExtension);
14371445
const externalsWarnConfig = composeExternalsWarnConfig(
@@ -1548,6 +1556,7 @@ export async function composeCreateRsbuildConfig(
15481556
dts: true,
15491557
shims: true,
15501558
umdName: true,
1559+
outBase: true,
15511560
}),
15521561
),
15531562
};

packages/core/src/types/config.ts

+6
Original file line numberDiff line numberDiff line change
@@ -285,6 +285,12 @@ export interface LibConfig extends EnvironmentConfig {
285285
* @see {@link https://lib.rsbuild.dev/config/lib/umd-name}
286286
*/
287287
umdName?: string;
288+
/**
289+
* The base directory of the output files.
290+
* @defaultValue `undefined`
291+
* @see {@link https://lib.rsbuild.dev/config/lib/out-base}
292+
*/
293+
outBase?: string;
288294
}
289295

290296
export type LibOnlyConfig = Omit<LibConfig, keyof EnvironmentConfig>;

pnpm-lock.yaml

+6
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
{
2+
"name": "outbase-custom-entry-dir-test",
3+
"version": "1.0.0",
4+
"private": true,
5+
"type": "module"
6+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
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/utils/foo',
11+
},
12+
},
13+
output: {
14+
distPath: {
15+
root: './dist/esm0',
16+
},
17+
},
18+
}),
19+
generateBundleEsmConfig({
20+
bundle: false,
21+
outBase: './src/utils',
22+
source: {
23+
entry: {
24+
index: './src/utils/foo',
25+
},
26+
},
27+
output: {
28+
distPath: {
29+
root: './dist/esm1',
30+
},
31+
},
32+
}),
33+
],
34+
});
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
export const bar = 'foo';
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
export const foo = 'foo';
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
export { foo } from './foo';
2+
export { bar } from './bar';
+47
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
import { join } from 'node:path';
2+
import { buildAndGetResults } from 'test-helper';
3+
import { describe, expect, test } from 'vitest';
4+
5+
describe('outBase', async () => {
6+
test('base', async () => {
7+
const fixturePath = join(__dirname, 'nested-dir');
8+
const { files } = await buildAndGetResults({
9+
fixturePath,
10+
});
11+
12+
expect(files.esm0!.sort()).toMatchInlineSnapshot(`
13+
[
14+
"<ROOT>/tests/integration/outBase/nested-dir/dist/esm0/bar/index.js",
15+
"<ROOT>/tests/integration/outBase/nested-dir/dist/esm0/foo/index.js",
16+
"<ROOT>/tests/integration/outBase/nested-dir/dist/esm0/index.js",
17+
]
18+
`);
19+
20+
expect(files.esm1!.sort()).toMatchInlineSnapshot(`
21+
[
22+
"<ROOT>/tests/integration/outBase/nested-dir/dist/esm1/utils/bar/index.js",
23+
"<ROOT>/tests/integration/outBase/nested-dir/dist/esm1/utils/foo/index.js",
24+
"<ROOT>/tests/integration/outBase/nested-dir/dist/esm1/utils/index.js",
25+
]
26+
`);
27+
});
28+
29+
test('with custom entry', async () => {
30+
const fixturePath = join(__dirname, 'custom-entry');
31+
const { files } = await buildAndGetResults({
32+
fixturePath,
33+
});
34+
35+
expect(files.esm0!.sort()).toMatchInlineSnapshot(`
36+
[
37+
"<ROOT>/tests/integration/outBase/custom-entry/dist/esm0/index.js",
38+
]
39+
`);
40+
41+
expect(files.esm1!.sort()).toMatchInlineSnapshot(`
42+
[
43+
"<ROOT>/tests/integration/outBase/custom-entry/dist/esm1/foo/index.js",
44+
]
45+
`);
46+
});
47+
});
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
{
2+
"name": "outbase-nested-dir-test",
3+
"version": "1.0.0",
4+
"private": true,
5+
"type": "module"
6+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
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+
dts: true,
9+
output: {
10+
distPath: {
11+
root: './dist/esm0',
12+
},
13+
},
14+
}),
15+
generateBundleEsmConfig({
16+
bundle: false,
17+
dts: true,
18+
outBase: './src',
19+
output: {
20+
distPath: {
21+
root: './dist/esm1',
22+
},
23+
},
24+
}),
25+
],
26+
});
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
export const bar = 'foo';
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
export const foo = 'foo';
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
export { foo } from './foo';
2+
export { bar } from './bar';

0 commit comments

Comments
 (0)