Skip to content

Commit 53a8598

Browse files
feat: support outBase config (#745)
Co-authored-by: Timeless0911 <[email protected]>
1 parent 676cad1 commit 53a8598

File tree

21 files changed

+335
-24
lines changed

21 files changed

+335
-24
lines changed

packages/core/src/config.ts

Lines changed: 32 additions & 19 deletions
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,26 @@ 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+
if (userOutBase !== undefined) {
971+
return path.isAbsolute(userOutBase)
972+
? userOutBase
973+
: path.resolve(root, userOutBase);
974+
}
975+
// Similar to `rootDir` in tsconfig and `outbase` in esbuild.
976+
// Using the longest common path of all non-declaration input files if not specified.
977+
const lcp = (await calcLongestCommonPath(resolvedEntryFiles)) ?? root;
978+
return lcp;
979+
};
980+
967981
for (const key of Object.keys(entries)) {
968982
const entry = entries[key];
969983

@@ -998,10 +1012,7 @@ const composeEntryConfig = async (
9981012
throw new Error(`Cannot find ${resolvedEntryFiles}`);
9991013
}
10001014

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;
1015+
const outBase = await resolveOutBase(resolvedEntryFiles);
10051016

10061017
function getEntryName(file: string) {
10071018
const { dir, name } = path.parse(path.relative(outBase, file));
@@ -1021,7 +1032,7 @@ const composeEntryConfig = async (
10211032
const entryName = getEntryName(file);
10221033

10231034
if (resolvedEntries[entryName]) {
1024-
calcLcp &&
1035+
tryResolveOutBase &&
10251036
logger.warn(
10261037
`Duplicate entry ${color.cyan(entryName)} from ${color.cyan(
10271038
path.relative(root, file),
@@ -1035,15 +1046,15 @@ const composeEntryConfig = async (
10351046
}
10361047
}
10371048

1038-
if (calcLcp) {
1039-
const lcp = await calcLongestCommonPath(Object.values(resolvedEntries));
1040-
return { resolvedEntries, lcp };
1049+
if (tryResolveOutBase) {
1050+
const outBase = await resolveOutBase(Object.values(resolvedEntries));
1051+
return { resolvedEntries, outBase };
10411052
}
1042-
return { resolvedEntries, lcp: null };
1053+
return { resolvedEntries, outBase: null };
10431054
};
10441055

1045-
// LCP could only be determined at the first time of glob scan.
1046-
const { lcp } = await scanGlobEntries(true);
1056+
// OutBase could only be determined at the first time of glob scan.
1057+
const { outBase } = await scanGlobEntries(true);
10471058
const entryConfig: EnvironmentConfig = {
10481059
tools: {
10491060
rspack: {
@@ -1057,7 +1068,7 @@ const composeEntryConfig = async (
10571068

10581069
return {
10591070
entryConfig,
1060-
lcp,
1071+
outBase,
10611072
};
10621073
};
10631074

@@ -1416,14 +1427,15 @@ async function composeLibRsbuildConfig(
14161427
pkgJson,
14171428
userExternals: config.output?.externals,
14181429
});
1419-
const { entryConfig, lcp } = await composeEntryConfig(
1430+
const { entryConfig, outBase } = await composeEntryConfig(
14201431
config.source?.entry!,
14211432
config.bundle,
14221433
rootPath,
14231434
cssModulesAuto,
1435+
config.outBase,
14241436
);
14251437
const cssConfig = composeCssConfig(
1426-
lcp,
1438+
outBase,
14271439
config.bundle,
14281440
banner?.css,
14291441
footer?.css,
@@ -1432,7 +1444,7 @@ async function composeLibRsbuildConfig(
14321444

14331445
const entryChunkConfig = composeEntryChunkConfig({
14341446
enabledImportMetaUrlShim: enabledShims.cjs['import.meta.url'],
1435-
contextToWatch: lcp,
1447+
contextToWatch: outBase,
14361448
});
14371449
const dtsConfig = await composeDtsConfig(config, dtsExtension);
14381450
const externalsWarnConfig = composeExternalsWarnConfig(
@@ -1549,6 +1561,7 @@ export async function composeCreateRsbuildConfig(
15491561
dts: true,
15501562
shims: true,
15511563
umdName: true,
1564+
outBase: true,
15521565
}),
15531566
),
15541567
};

packages/core/src/types/config.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -293,6 +293,12 @@ export interface LibConfig extends EnvironmentConfig {
293293
* @see {@link https://lib.rsbuild.dev/config/lib/umd-name}
294294
*/
295295
umdName?: string;
296+
/**
297+
* The base directory of the output files.
298+
* @defaultValue `undefined`
299+
* @see {@link https://lib.rsbuild.dev/config/lib/out-base}
300+
*/
301+
outBase?: string;
296302
}
297303

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

pnpm-lock.yaml

Lines changed: 4 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.
Lines changed: 6 additions & 0 deletions
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+
}
Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
import path from 'node:path';
2+
import { defineConfig } from '@rslib/core';
3+
import { generateBundleEsmConfig } from 'test-helper';
4+
5+
export default defineConfig({
6+
lib: [
7+
// default
8+
generateBundleEsmConfig({
9+
bundle: false,
10+
source: {
11+
entry: {
12+
index: './src/utils/foo',
13+
},
14+
},
15+
output: {
16+
distPath: {
17+
root: './dist/esm0',
18+
},
19+
},
20+
}),
21+
// configured with relative outBase
22+
generateBundleEsmConfig({
23+
bundle: false,
24+
outBase: './src/utils',
25+
source: {
26+
entry: {
27+
index: './src/utils/foo',
28+
},
29+
},
30+
output: {
31+
distPath: {
32+
root: './dist/esm1',
33+
},
34+
},
35+
}),
36+
// configured with absolute outBase
37+
generateBundleEsmConfig({
38+
bundle: false,
39+
outBase: path.resolve(__dirname, 'src/utils'),
40+
source: {
41+
entry: {
42+
index: './src/utils/foo',
43+
},
44+
},
45+
output: {
46+
distPath: {
47+
root: './dist/esm2',
48+
},
49+
},
50+
}),
51+
],
52+
});
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
export const bar = 'foo';
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
export const foo = 'foo';
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
export { foo } from './foo';
2+
export { bar } from './bar';
Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
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+
expect(files.esm2!.sort()).toMatchInlineSnapshot(`
29+
[
30+
"<ROOT>/tests/integration/outBase/nested-dir/dist/esm2/utils/bar/index.js",
31+
"<ROOT>/tests/integration/outBase/nested-dir/dist/esm2/utils/foo/index.js",
32+
"<ROOT>/tests/integration/outBase/nested-dir/dist/esm2/utils/index.js",
33+
]
34+
`);
35+
});
36+
37+
test('with custom entry', async () => {
38+
const fixturePath = join(__dirname, 'custom-entry');
39+
const { files } = await buildAndGetResults({
40+
fixturePath,
41+
});
42+
43+
expect(files.esm0!.sort()).toMatchInlineSnapshot(`
44+
[
45+
"<ROOT>/tests/integration/outBase/custom-entry/dist/esm0/index.js",
46+
]
47+
`);
48+
49+
expect(files.esm1!.sort()).toMatchInlineSnapshot(`
50+
[
51+
"<ROOT>/tests/integration/outBase/custom-entry/dist/esm1/foo/index.js",
52+
]
53+
`);
54+
55+
expect(files.esm2!.sort()).toMatchInlineSnapshot(`
56+
[
57+
"<ROOT>/tests/integration/outBase/custom-entry/dist/esm2/foo/index.js",
58+
]
59+
`);
60+
});
61+
});
Lines changed: 6 additions & 0 deletions
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+
}
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
import path from 'node:path';
2+
import { defineConfig } from '@rslib/core';
3+
import { generateBundleEsmConfig } from 'test-helper';
4+
5+
export default defineConfig({
6+
lib: [
7+
// default
8+
generateBundleEsmConfig({
9+
bundle: false,
10+
output: {
11+
distPath: {
12+
root: './dist/esm0',
13+
},
14+
},
15+
}),
16+
// configured with relative outBase
17+
generateBundleEsmConfig({
18+
bundle: false,
19+
outBase: './src',
20+
output: {
21+
distPath: {
22+
root: './dist/esm1',
23+
},
24+
},
25+
}),
26+
// configured with absolute outBase
27+
generateBundleEsmConfig({
28+
bundle: false,
29+
outBase: path.resolve(__dirname, 'src'),
30+
output: {
31+
distPath: {
32+
root: './dist/esm2',
33+
},
34+
},
35+
}),
36+
],
37+
});
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
export const bar = 'foo';
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
export const foo = 'foo';
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
export { foo } from './foo';
2+
export { bar } from './bar';

website/docs/en/config/lib/_meta.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,5 +11,6 @@
1111
"dts",
1212
"shims",
1313
"id",
14-
"umd-name"
14+
"umd-name",
15+
"out-base"
1516
]

0 commit comments

Comments
 (0)