Skip to content

Commit 8001931

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

File tree

21 files changed

+280
-24
lines changed

21 files changed

+280
-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+
// 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
974+
? path.resolve(root, userOutBase)
975+
: lcp === null
976+
? root
977+
: lcp;
978+
return outBase;
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

@@ -1415,14 +1426,15 @@ async function composeLibRsbuildConfig(
14151426
pkgJson,
14161427
userExternals: config.output?.externals,
14171428
});
1418-
const { entryConfig, lcp } = await composeEntryConfig(
1429+
const { entryConfig, outBase } = await composeEntryConfig(
14191430
config.source?.entry!,
14201431
config.bundle,
14211432
rootPath,
14221433
cssModulesAuto,
1434+
config.outBase,
14231435
);
14241436
const cssConfig = composeCssConfig(
1425-
lcp,
1437+
outBase,
14261438
config.bundle,
14271439
banner?.css,
14281440
footer?.css,
@@ -1431,7 +1443,7 @@ async function composeLibRsbuildConfig(
14311443

14321444
const entryChunkConfig = composeEntryChunkConfig({
14331445
enabledImportMetaUrlShim: enabledShims.cjs['import.meta.url'],
1434-
contextToWatch: lcp,
1446+
contextToWatch: outBase,
14351447
});
14361448
const dtsConfig = await composeDtsConfig(config, dtsExtension);
14371449
const externalsWarnConfig = composeExternalsWarnConfig(
@@ -1548,6 +1560,7 @@ export async function composeCreateRsbuildConfig(
15481560
dts: true,
15491561
shims: true,
15501562
umdName: true,
1563+
outBase: true,
15511564
}),
15521565
),
15531566
};

packages/core/src/types/config.ts

Lines changed: 6 additions & 0 deletions
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

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: 34 additions & 0 deletions
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+
});
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: 47 additions & 0 deletions
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+
});
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: 26 additions & 0 deletions
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+
});
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
]
Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
# lib.outBase
2+
3+
- **Type:** `boolean`
4+
- **Default Value:** `undefined`
5+
6+
:::info
7+
8+
`outBase` is a specific configuration for [bundleless mode](/guide/basic/output-structure#bundle--bundleless). This configuration does not take effect in bundle mode because all output files are bundled into a single file, so there's no need to determine the base output directory.
9+
10+
:::
11+
12+
When building a bundleless project where source files exist across multiple directories, the output directory structure will be replicated relative to the `outBase` directory in the output directory. If no base output directory is specified, the [lowest common ancestor](https://en.wikipedia.org/wiki/Lowest_common_ancestor) of all input entry points is used by default.
13+
14+
Configuring `outBase` will change the base output directory path. The path of `outBase` is relative to the current process directory.
15+
16+
For example, we have the following directory structure:
17+
18+
```txt
19+
.
20+
├── package.json
21+
├── rslib.config.ts
22+
└── src
23+
└── utils
24+
├── bar
25+
│ └── index.ts
26+
├── foo
27+
│ └── index.ts
28+
└── index.ts
29+
```
30+
31+
If the output base directory is not specified, the lowest common ancestor of all input entry point paths, i.e. `src/utils`, is used by default, and the final file output structure is:
32+
33+
```txt
34+
dist
35+
├── bar
36+
│ └── index.js
37+
├── foo
38+
│ └── index.js
39+
└── index.js
40+
```
41+
42+
When `outBase` is configured as `./src`, the output directory structure is:
43+
44+
```txt
45+
dist
46+
└── utils
47+
├── bar
48+
│ └── index.js
49+
├── foo
50+
│ └── index.js
51+
└── index.js
52+
```

website/docs/en/guide/migration/tsup.mdx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
# Tsup
1+
# tsup
22

33
This section introduces how to migrate a project using tsup to Rslib.
44

website/docs/zh/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
]

website/docs/zh/config/lib/bundle.mdx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
# lib.bundle
22

33
- **类型:** `boolean`
4-
- **默认值:** `undefined`
4+
- **默认值:** `true`
55

66
指定是否打包库,当 `bundle` 设置为 `true` 时称为 bundle 模式,设置为 `false` 时称为 bundleless 模式。更多详情请参考 [bundle / bundleless](/guide/basic/output-structure#bundle--bundleless)
77

0 commit comments

Comments
 (0)