Skip to content

Commit

Permalink
feat: allow to custom manifest JSON (#4289)
Browse files Browse the repository at this point in the history
  • Loading branch information
chenjiahan authored Dec 29, 2024
1 parent 9d922d0 commit 3b6594d
Show file tree
Hide file tree
Showing 4 changed files with 181 additions and 35 deletions.
78 changes: 78 additions & 0 deletions e2e/cases/output/manifest-generate/index.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
import { build, dev } from '@e2e/helper';
import { expect, test } from '@playwright/test';
import type { RsbuildConfig } from '@rsbuild/core';

const fixtures = __dirname;

const rsbuildConfig: RsbuildConfig = {
output: {
manifest: {
filename: 'my-manifest.json',
generate: ({ files, manifestData }) => {
return {
filesCount: files.length,
data: manifestData,
};
},
},
legalComments: 'none',
sourceMap: false,
filenameHash: false,
},
performance: {
chunkSplit: {
strategy: 'all-in-one',
},
},
};

test('should allow to custom generate manifest data in production build', async () => {
const rsbuild = await build({
cwd: fixtures,
rsbuildConfig,
});

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

expect(manifest.filesCount).toBe(2);
expect(manifest.data.allFiles.length).toBe(2);
expect(manifest.data.entries.index).toMatchObject({
initial: {
js: ['/static/js/index.js'],
},
html: ['/index.html'],
});
});

test('should allow to custom generate manifest data in dev', async ({
page,
}) => {
const rsbuild = await dev({
cwd: fixtures,
page,
rsbuildConfig,
});

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

expect(manifest.filesCount).toBe(2);
expect(manifest.data.allFiles.length).toBe(2);
expect(manifest.data.entries.index).toMatchObject({
initial: {
js: ['/static/js/index.js'],
},
html: ['/index.html'],
});

await rsbuild.close();
});
1 change: 1 addition & 0 deletions e2e/cases/output/manifest-generate/src/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
console.log('hello!');
87 changes: 54 additions & 33 deletions packages/core/src/plugins/manifest.ts
Original file line number Diff line number Diff line change
@@ -1,34 +1,16 @@
import type { FileDescriptor } from 'rspack-manifest-plugin';
import { isObject } from '../helpers';
import { recursiveChunkEntryNames } from '../rspack/preload/helpers';
import type { RsbuildPlugin } from '../types';

type FilePath = string;

type ManifestByEntry = {
initial?: {
js?: FilePath[];
css?: FilePath[];
};
async?: {
js?: FilePath[];
css?: FilePath[];
};
/** other assets (e.g. png、svg、source map) related to the current entry */
assets?: FilePath[];
html?: FilePath[];
};

type ManifestList = {
entries: {
/** relate to rsbuild source.entry */
[entryName: string]: ManifestByEntry;
};
/** Flatten all assets */
allFiles: FilePath[];
};
import type {
ManifestByEntry,
ManifestConfig,
ManifestData,
ManifestObjectConfig,
RsbuildPlugin,
} from '../types';

const generateManifest =
(htmlPaths: Record<string, string>) =>
(htmlPaths: Record<string, string>, manifestOptions: ManifestObjectConfig) =>
(_seed: Record<string, any>, files: FileDescriptor[]) => {
const chunkEntries = new Map<string, FileDescriptor[]>();

Expand All @@ -50,7 +32,7 @@ const generateManifest =
return file.path;
});

const entries: ManifestList['entries'] = {};
const entries: ManifestData['entries'] = {};

for (const [name, chunkFiles] of chunkEntries) {
const assets = new Set<string>();
Expand Down Expand Up @@ -126,11 +108,51 @@ const generateManifest =
entries[name] = entryManifest;
}

return {
const manifestData: ManifestData = {
allFiles,
entries,
};

if (manifestOptions.generate) {
const generatedManifest = manifestOptions.generate({
files,
manifestData,
});

if (isObject(generatedManifest)) {
return generatedManifest;
}

throw new Error(
'[rsbuild:manifest] `manifest.generate` function must return a valid manifest object.',
);
}

return manifestData;
};

function normalizeManifestObjectConfig(
manifest?: ManifestConfig,
): ManifestObjectConfig {
if (typeof manifest === 'string') {
return {
filename: manifest,
};
}

const defaultOptions: ManifestObjectConfig = {
filename: 'manifest.json',
};

if (typeof manifest === 'boolean') {
return defaultOptions;
}

return {
...defaultOptions,
...manifest,
};
}

export const pluginManifest = (): RsbuildPlugin => ({
name: 'rsbuild:manifest',
Expand All @@ -146,8 +168,7 @@ export const pluginManifest = (): RsbuildPlugin => ({
return;
}

const fileName =
typeof manifest === 'string' ? manifest : 'manifest.json';
const manifestOptions = normalizeManifestObjectConfig(manifest);

const { RspackManifestPlugin } = await import(
'../../compiled/rspack-manifest-plugin/index.js'
Expand All @@ -156,9 +177,9 @@ export const pluginManifest = (): RsbuildPlugin => ({

chain.plugin(CHAIN_ID.PLUGIN.MANIFEST).use(RspackManifestPlugin, [
{
fileName,
fileName: manifestOptions.filename,
writeToFileEmit: isDev && writeToDisk !== true,
generate: generateManifest(htmlPaths),
generate: generateManifest(htmlPaths, manifestOptions),
},
]);
});
Expand Down
50 changes: 48 additions & 2 deletions packages/core/src/types/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -889,6 +889,47 @@ export type InlineChunkConfig =
| InlineChunkTest
| { enable?: boolean | 'auto'; test: InlineChunkTest };

export type ManifestByEntry = {
initial?: {
js?: string[];
css?: string[];
};
async?: {
js?: string[];
css?: string[];
};
/** other assets (e.g. png、svg、source map) related to the current entry */
assets?: string[];
html?: string[];
};

export type ManifestData = {
entries: {
/** relate to Rsbuild's source.entry config */
[entryName: string]: ManifestByEntry;
};
/** Flatten all assets */
allFiles: string[];
};

export type ManifestObjectConfig = {
/**
* The filename or path of the manifest file.
* The manifest file will be emitted to the output directory.
* @default 'manifest.json'
*/
filename?: string;
/**
* A custom function to generate the content of the manifest file.
*/
generate?: (params: {
files: import('rspack-manifest-plugin').FileDescriptor[];
manifestData: ManifestData;
}) => Record<string, unknown>;
};

export type ManifestConfig = string | boolean | ManifestObjectConfig;

export interface OutputConfig {
/**
* Specify build target to run in specified environment.
Expand Down Expand Up @@ -958,10 +999,14 @@ export interface OutputConfig {
*/
minify?: Minify;
/**
* Whether to generate manifest file.
* Configure how to generate the manifest file.
* - `true`: Generate a manifest file named `manifest.json` in the output directory.
* - `false`: Do not generate the manifest file.
* - `string`: Generate a manifest file with the specified filename or path.
* - `object`: Generate a manifest file with the specified options.
* @default false
*/
manifest?: string | boolean;
manifest?: ManifestConfig;
/**
* Whether to generate source map files, and which format of source map to generate.
*
Expand Down Expand Up @@ -1037,6 +1082,7 @@ export interface NormalizedOutputConfig extends OutputConfig {
filenameHash: boolean | string;
assetPrefix: string;
dataUriLimit: number | NormalizedDataUriLimit;
manifest: ManifestConfig;
minify: Minify;
inlineScripts: InlineChunkConfig;
inlineStyles: InlineChunkConfig;
Expand Down

0 comments on commit 3b6594d

Please sign in to comment.