Skip to content

Commit

Permalink
feat: do not clean dist path if it is outside root (#1433)
Browse files Browse the repository at this point in the history
  • Loading branch information
chenjiahan authored Jan 25, 2024
1 parent b426cb5 commit f90c127
Show file tree
Hide file tree
Showing 10 changed files with 149 additions and 50 deletions.
5 changes: 5 additions & 0 deletions .changeset/unlucky-jars-drop.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@rsbuild/core': patch
---

release: 0.3.9
1 change: 1 addition & 0 deletions cspell.json
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@
"selfsign",
"sirv",
"stacktracey",
"subdir",
"subpage",
"svgr",
"systemjs",
Expand Down
61 changes: 61 additions & 0 deletions e2e/cases/output/clean-dist-path/index.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
import { join } from 'node:path';
import { expect, test } from '@playwright/test';
import { fse } from '@rsbuild/shared';
import { build, proxyConsole } from '@e2e/helper';

const cwd = __dirname;
const testDistFile = join(cwd, 'dist/test.json');

test('should clean dist path by default', async () => {
await fse.outputFile(testDistFile, `{ "test": 1 }`);

await build({
cwd,
});

expect(fse.existsSync(testDistFile)).toBeFalsy();
fse.removeSync(testDistFile);
});

test('should not clean dist path if it is outside root', async () => {
const { logs, restore } = proxyConsole();
const testOutsideFile = join(cwd, '../node_modules/test.json');
await fse.outputFile(testOutsideFile, `{ "test": 1 }`);

await build({
cwd,
rsbuildConfig: {
output: {
distPath: {
root: '../node_modules',
},
},
},
});

expect(
logs.find((log) => log.includes('dist path is not a subdir of root path')),
).toBeTruthy();

expect(fse.existsSync(testOutsideFile)).toBeTruthy();

fse.removeSync(testOutsideFile);
restore();
});

test('should allow to disable cleanDistPath', async () => {
await fse.outputFile(testDistFile, `{ "test": 1 }`);

await build({
cwd,
rsbuildConfig: {
output: {
cleanDistPath: false,
},
},
});

expect(fse.existsSync(testDistFile)).toBeTruthy();

fse.removeSync(testDistFile);
});
1 change: 1 addition & 0 deletions e2e/cases/output/clean-dist-path/src/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
console.log('hello');
34 changes: 0 additions & 34 deletions e2e/cases/output/output.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -40,10 +40,6 @@ test.describe('output configure multi', () => {
await rsbuild.clean();
});

test('cleanDistPath default (enable)', async () => {
expect(fse.existsSync(distFilePath)).toBeFalsy();
});

test('copy', async () => {
expect(fse.existsSync(join(fixtures, 'rem/dist-1/icon.png'))).toBeTruthy();
});
Expand All @@ -56,33 +52,3 @@ test.describe('output configure multi', () => {
expect(fse.existsSync(join(fixtures, 'rem/dist-1/aa/js'))).toBeTruthy();
});
});

test('cleanDistPath disable', async () => {
const distFilePath = join(fixtures, 'rem/dist-2/test.json');

await fse.mkdir(dirname(distFilePath), { recursive: true });
await fse.writeFile(
distFilePath,
`{
"test": 1
}`,
);

const rsbuild = await build({
cwd: join(fixtures, 'rem'),
plugins: [pluginReact()],
rsbuildConfig: {
output: {
distPath: {
root: 'dist-2',
},
cleanDistPath: false,
},
},
});

expect(fse.existsSync(distFilePath)).toBeTruthy();

await rsbuild.close();
rsbuild.clean();
});
33 changes: 30 additions & 3 deletions packages/core/src/plugins/cleanOutput.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { fse } from '@rsbuild/shared';
import { sep } from 'node:path';
import { fse, color, logger } from '@rsbuild/shared';
import type { RsbuildPlugin } from '../types';

const emptyDir = async (dir: string) => {
Expand All @@ -7,15 +8,41 @@ const emptyDir = async (dir: string) => {
}
};

const addTrailingSep = (dir: string) => (dir.endsWith(sep) ? dir : dir + sep);

const isStrictSubdir = (parent: string, child: string) => {
const parentDir = addTrailingSep(parent);
const childDir = addTrailingSep(child);
return parentDir !== childDir && childDir.startsWith(parentDir);
};

export const pluginCleanOutput = (): RsbuildPlugin => ({
name: 'rsbuild:clean-output',

setup(api) {
const clean = async () => {
const { distPath, rootPath } = api.context;
const config = api.getNormalizedConfig();

if (config.output.cleanDistPath) {
const { distPath } = api.context;
let { cleanDistPath } = config.output;

// only enable cleanDistPath when the dist path is a subdir of root path
if (cleanDistPath === undefined) {
cleanDistPath = isStrictSubdir(rootPath, distPath);

if (!cleanDistPath) {
logger.warn(
'The dist path is not a subdir of root path, Rsbuild will not empty it.',
);
logger.warn(
`Please set ${color.yellow('`output.cleanDistPath`')} config manually.`,
);
logger.warn(`Current root path: ${color.dim(rootPath)}`);
logger.warn(`Current dist path: ${color.dim(distPath)}`);
}
}

if (cleanDistPath) {
await emptyDir(distPath);
}
};
Expand Down
30 changes: 25 additions & 5 deletions packages/document/docs/en/config/output/clean-dist-path.mdx
Original file line number Diff line number Diff line change
@@ -1,21 +1,41 @@
# cleanDistPath

- **Type:** `boolean`
- **Default:** `true`
- **Default:** `undefined`

Whether to clean up all files under the output directory (default is `dist`) before the build starts.
Whether to clean up all files under the output directory before the build starts (the output directory defaults to `dist`).

By default, Rsbuild automatically cleans the files under the output directory. You can set `cleanDistPath` to `false` to disable this behavior.
## Default Behavior

By default, if the output directory is a subdir of the project root path, Rsbuild will automatically clean all files under the build directory.

When `output.distPath.root` is an external directory, or equals to the project root directory, `cleanDistPath` is not enabled by default, to avoid accidentally deleting files from other directories.

```js
export default {
output: {
distPath: {
root: '../../some-dir',
},
},
};
```

## Forced Switch

You can set `cleanDistPath` to `true` to force it to be enabled, or set it to `false` to force it to be disabled.

```js
export default {
output: {
cleanDistPath: false,
cleanDistPath: true,
},
};
```

If you only need to clean files before the production build, and not in the development build, you can configure it as follows:
## Conditional

If you only need to clean files before the production build, but not before the development build, you can configure it as:

```js
export default {
Expand Down
28 changes: 24 additions & 4 deletions packages/document/docs/zh/config/output/clean-dist-path.mdx
Original file line number Diff line number Diff line change
@@ -1,20 +1,40 @@
# cleanDistPath

- **类型:** `boolean`
- **默认值:** `true`
- **默认值:** `undefined`

是否在构建开始前清理产物目录(默认为 `dist`下的所有文件
是否在构建开始前清理产物目录下的所有文件(产物目录默认为 `dist`)。

默认情况下,Rsbuild 会自动清理产物目录下的文件,你可以把 `cleanDistPath` 设置为 `false` 来禁用该行为。
## 默认行为

默认情况下,如果产物目录是项目根路径的子目录,Rsbuild 会自动清空产物目录下的文件。

`output.distPath.root` 为外部目录,或等于项目根目录时,`cleanDistPath` 不会默认开启,这是为了避免误删其他目录的文件。

```js
export default {
output: {
distPath: {
root: '../../some-dir',
},
},
};
```

## 强制开关

你可以把 `cleanDistPath` 设置为 `true` 来强制开启,也可以设置为 `false` 来强制关闭该行为。

```js
export default {
output: {
cleanDistPath: false,
cleanDistPath: true,
},
};
```

## 条件判断

如果你只需要在生产环境构建前清理文件,而在开发环境构建前不需要,那么可以配置为:

```js
Expand Down
5 changes: 2 additions & 3 deletions packages/shared/src/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -127,7 +127,6 @@ export const getDefaultOutputConfig = (): NormalizedOutputConfig => ({
media: DEFAULT_DATA_URL_SIZE,
},
legalComments: 'linked',
cleanDistPath: true,
injectStyles: false,
disableMinimize: false,
sourceMap: {
Expand Down Expand Up @@ -216,8 +215,8 @@ export type GetTypeByPath<
> = T extends `${infer K}[${infer P}]${infer S}`
? GetTypeByPath<`${K}.${P}${S}`, C>
: T extends `${infer K}.${infer P}`
? GetTypeByPath<P, K extends '' ? C : NonNullable<C[K]>>
: C[T];
? GetTypeByPath<P, K extends '' ? C : NonNullable<C[K]>>
: C[T];

type MinifyOptions = NonNullable<Parameters<typeof minify>[1]>;

Expand Down
1 change: 0 additions & 1 deletion packages/shared/src/types/config/output.ts
Original file line number Diff line number Diff line change
Expand Up @@ -207,7 +207,6 @@ export interface NormalizedOutputConfig extends OutputConfig {
};
assetPrefix: string;
dataUriLimit: NormalizedDataUriLimit;
cleanDistPath: boolean;
disableMinimize: boolean;
disableFilenameHash: boolean;
enableLatestDecorators: boolean;
Expand Down

0 comments on commit f90c127

Please sign in to comment.