Skip to content

Commit

Permalink
feat: allow to filter files included in the manifest (#4307)
Browse files Browse the repository at this point in the history
  • Loading branch information
chenjiahan authored Dec 31, 2024
1 parent f0a4162 commit 616d7d5
Show file tree
Hide file tree
Showing 8 changed files with 203 additions and 19 deletions.
1 change: 0 additions & 1 deletion e2e/cases/output/manifest-async-chunks/index.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,6 @@ test('should generate manifest for async chunks correctly', async () => {
rsbuildConfig: {
output: {
manifest: true,
legalComments: 'none',
filenameHash: false,
},
performance: {
Expand Down
1 change: 0 additions & 1 deletion e2e/cases/output/manifest-generate/index.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,6 @@ const rsbuildConfig: RsbuildConfig = {
};
},
},
legalComments: 'none',
sourceMap: false,
filenameHash: false,
},
Expand Down
91 changes: 77 additions & 14 deletions e2e/cases/output/manifest/index.test.ts
Original file line number Diff line number Diff line change
@@ -1,17 +1,16 @@
import { readFileSync } from 'node:fs';
import { join } from 'node:path';
import { build, dev } from '@e2e/helper';
import { build, dev, rspackOnlyTest } from '@e2e/helper';
import { expect, test } from '@playwright/test';

const fixtures = __dirname;

test('output.manifest', async () => {
test('should generate manifest file in output', async () => {
const rsbuild = await build({
cwd: fixtures,
rsbuildConfig: {
output: {
manifest: true,
legalComments: 'none',
filenameHash: false,
},
performance: {
Expand Down Expand Up @@ -42,13 +41,12 @@ test('output.manifest', async () => {
});
});

test('output.manifest set a path', async () => {
test('should generate manifest file at specified path', async () => {
await build({
cwd: fixtures,
rsbuildConfig: {
output: {
manifest: './custom/my-manifest.json',
legalComments: 'none',
filenameHash: false,
},
performance: {
Expand All @@ -69,7 +67,7 @@ test('output.manifest set a path', async () => {
expect(Object.keys(parsed.allFiles).length).toBe(2);
});

test('output.manifest when target is node', async () => {
test('should generate manifest file when target is node', async () => {
const rsbuild = await build({
cwd: fixtures,
rsbuildConfig: {
Expand All @@ -79,14 +77,8 @@ test('output.manifest when target is node', async () => {
},
target: 'node',
manifest: true,
legalComments: 'none',
filenameHash: false,
},
performance: {
chunkSplit: {
strategy: 'all-in-one',
},
},
},
});

Expand All @@ -109,7 +101,7 @@ test('output.manifest when target is node', async () => {
});
});

test('output.manifest should always write to disk when dev', async ({
test('should always write manifest to disk when in dev mode', async ({
page,
}) => {
const rsbuild = await dev({
Expand All @@ -121,7 +113,6 @@ test('output.manifest should always write to disk when dev', async ({
root: 'dist-dev',
},
manifest: true,
legalComments: 'none',
filenameHash: false,
},
performance: {
Expand All @@ -141,3 +132,75 @@ test('output.manifest should always write to disk when dev', async ({

await rsbuild.close();
});

test('should allow to filter files in manifest', async () => {
const rsbuild = await build({
cwd: fixtures,
rsbuildConfig: {
output: {
manifest: {
filter: (file) => file.name.endsWith('.js'),
},
filenameHash: false,
},
performance: {
chunkSplit: {
strategy: 'all-in-one',
},
},
},
});

const files = await rsbuild.unwrapOutputJSON();

const manifestContent =
files[Object.keys(files).find((file) => file.endsWith('manifest.json'))!];
const manifest = JSON.parse(manifestContent);

// main.js
expect(Object.keys(manifest.allFiles).length).toBe(1);

expect(manifest.entries.index).toMatchObject({
initial: {
js: ['/static/js/index.js'],
},
});
});

rspackOnlyTest(
'should allow to include license files in manifest',
async () => {
const rsbuild = await build({
cwd: fixtures,
rsbuildConfig: {
output: {
manifest: {
filter: () => true,
},
filenameHash: false,
},
performance: {
chunkSplit: {
strategy: 'all-in-one',
},
},
},
});

const files = await rsbuild.unwrapOutputJSON();

const manifestContent =
files[Object.keys(files).find((file) => file.endsWith('manifest.json'))!];
const manifest = JSON.parse(manifestContent);

expect(Object.keys(manifest.allFiles).length).toBe(3);

expect(manifest.entries.index).toMatchObject({
initial: {
js: ['/static/js/index.js'],
},
html: ['/index.html'],
assets: ['/static/js/index.js.LICENSE.txt'],
});
},
);
4 changes: 3 additions & 1 deletion e2e/cases/output/manifest/src/index.ts
Original file line number Diff line number Diff line change
@@ -1 +1,3 @@
console.log('hello!');
import React from 'react';

console.log('hello!', React);
8 changes: 7 additions & 1 deletion packages/core/src/plugins/manifest.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import type { FileDescriptor } from 'rspack-manifest-plugin';
import type { FileDescriptor } from '../../compiled/rspack-manifest-plugin';
import { isObject } from '../helpers';
import { recursiveChunkEntryNames } from '../rspack/preload/helpers';
import type {
Expand Down Expand Up @@ -175,9 +175,15 @@ export const pluginManifest = (): RsbuildPlugin => ({
);
const { htmlPaths } = environment;

// Exclude `*.LICENSE.txt` files by default
const filter =
manifestOptions.filter ??
((file: FileDescriptor) => !file.name.endsWith('.LICENSE.txt'));

chain.plugin(CHAIN_ID.PLUGIN.MANIFEST).use(RspackManifestPlugin, [
{
fileName: manifestOptions.filename,
filter,
writeToFileEmit: isDev && writeToDisk !== true,
generate: generateManifest(htmlPaths, manifestOptions),
},
Expand Down
9 changes: 8 additions & 1 deletion packages/core/src/types/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ import type {
Filter as ProxyFilter,
} from '../../compiled/http-proxy-middleware/index.js';
import type RspackChain from '../../compiled/rspack-chain/index.js';
import type { FileDescriptor } from '../../compiled/rspack-manifest-plugin';
import type { BundleAnalyzerPlugin } from '../../compiled/webpack-bundle-analyzer/index.js';
import type {
ModifyBundlerChainUtils,
Expand Down Expand Up @@ -926,9 +927,15 @@ export type ManifestObjectConfig = {
* A custom function to generate the content of the manifest file.
*/
generate?: (params: {
files: import('rspack-manifest-plugin').FileDescriptor[];
files: FileDescriptor[];
manifestData: ManifestData;
}) => Record<string, unknown>;
/**
* Allows you to filter the files included in the manifest.
* The function receives a `file` parameter and returns `true` to keep the file, or `false` to exclude it.
* @default (file: FileDescriptor) => !file.name.endsWith('.LICENSE.txt')
*/
filter?: (file: FileDescriptor) => boolean;
};

export type ManifestConfig = string | boolean | ManifestObjectConfig;
Expand Down
54 changes: 54 additions & 0 deletions website/docs/en/config/output/manifest.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -184,3 +184,57 @@ const files = [
},
];
```

### filter

- **Type:**

```ts
type ManifestFilter = (file: FileDescriptor) => boolean;
```

- **Default:** `file => !file.name.endsWith('.LICENSE.txt')`
- **Version:** `>= 1.2.0`

Allows you to filter the files included in the manifest. The function receives a `file` parameter and returns `true` to keep the file, or `false` to exclude it.

By default, `*.LICENSE.txt` files are excluded from the manifest, as these license files are only used to declare open source licenses and are not used at runtime.

For example, to only keep `*.js` files:

```ts title="rsbuild.config.ts"
export default {
output: {
manifest: {
filter: (file) => file.name.endsWith('.js'),
},
},
};
```

The generated manifest file will only include `*.js` files:

```json title="dist/manifest.json"
{
"allFiles": ["/static/js/index.[hash].js"],
"entries": {
"index": {
"initial": {
"js": ["/static/js/index.[hash].js"]
}
}
}
}
```

Or include all files:

```ts title="rsbuild.config.ts"
export default {
output: {
manifest: {
filter: () => true,
},
},
};
```
54 changes: 54 additions & 0 deletions website/docs/zh/config/output/manifest.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -184,3 +184,57 @@ const files = [
},
];
```

### filter

- **类型:**

```ts
type ManifestFilter = (file: FileDescriptor) => boolean;
```

- **默认值:** `file => !file.name.endsWith('.LICENSE.txt')`
- **版本:** `>= 1.2.0`

允许你过滤包含在 manifest 中的文件。该函数接收一个 `file` 参数,返回 `true` 表示保留该文件,返回 `false` 表示不保留该文件。

默认情况下,`*.LICENSE.txt` 文件不会被包含在 manifest 文件中,因为这些许可证文件仅用于声明开源协议,不会在运行时被使用。

例如,仅保留 `*.js` 文件:

```ts title="rsbuild.config.ts"
export default {
output: {
manifest: {
filter: (file) => file.name.endsWith('.js'),
},
},
};
```

生成的 manifest 文件中仅会包含 `*.js` 文件:

```json title="dist/manifest.json"
{
"allFiles": ["/static/js/index.[hash].js"],
"entries": {
"index": {
"initial": {
"js": ["/static/js/index.[hash].js"]
}
}
}
}
```

或者是包含所有文件:

```ts title="rsbuild.config.ts"
export default {
output: {
manifest: {
filter: () => true,
},
},
};
```

1 comment on commit 616d7d5

@github-actions
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

📝 Ran ecosystem CI: Open

suite result
modernjs ❌ failure
plugins ❌ failure
rspress ✅ success
rslib ❌ failure
examples ❌ failure

Please sign in to comment.