From d37fe304d3dcd840ca7010a952cb13bf22f1d5a2 Mon Sep 17 00:00:00 2001 From: Viorel Cojocaru Date: Mon, 10 Jun 2024 21:24:50 +0200 Subject: [PATCH] feat: Add support for excludeAssets and excludeModules options --- README.md | 3 + src/index.ts | 9 + src/transform.ts | 26 +- src/types.d.ts | 4 +- src/utils.ts | 24 +- test/unit/fixtures/rollup-bundle-stats.ts | 176 +++++++++++ test/unit/transform.test.ts | 365 ++++++++++++---------- 7 files changed, 415 insertions(+), 192 deletions(-) create mode 100644 test/unit/fixtures/rollup-bundle-stats.ts diff --git a/README.md b/README.md index 27dd044..45e33ba 100644 --- a/README.md +++ b/README.md @@ -86,6 +86,9 @@ export default defineConfig((env) => ({ ### Options - `fileName` - JSON stats file inside rollup/vite output directory +- `excludeAssets` - exclude matching assets: `string | RegExp | ((filepath: string) => boolean) | Array boolean)>` +- `excludeModules` - exclude matching modules: `string | RegExp | ((filepath: string) => boolean) | Array boolean)>` + ## Resources diff --git a/src/index.ts b/src/index.ts index 4568f9a..5c07d35 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,5 +1,6 @@ import { Plugin, OutputOptions } from 'rollup'; +import type { ExcludeFilepathOption } from './types'; import { BundleTransformOptions, bundleToWebpackStats } from './transform'; export { bundleToWebpackStats } from './transform'; @@ -12,6 +13,14 @@ interface WebpackStatsOptions extends BundleTransformOptions { * default: webpack-stats.json */ fileName?: string; + /** + * Exclude matching assets + */ + excludeAssets?: ExcludeFilepathOption; + /** + * Exclude matching modules + */ + excludeModules?: ExcludeFilepathOption; } type WebpackStatsOptionsOrBuilder = diff --git a/src/transform.ts b/src/transform.ts index 626726d..e299412 100644 --- a/src/transform.ts +++ b/src/transform.ts @@ -1,8 +1,8 @@ import path from 'path'; -import { OutputBundle } from 'rollup'; +import type { OutputBundle } from 'rollup'; -import type { ExcludeFilepathConfig } from "./types"; -import { getByteSize, getChunkId } from "./utils"; +import type { ExcludeFilepathOption } from "./types"; +import { checkExcludeFilepath, getByteSize, getChunkId } from "./utils"; // https://github.com/relative-ci/bundle-stats/blob/master/packages/plugin-webpack-filter/src/index.ts export type WebpackStatsFilteredAsset = { @@ -51,20 +51,20 @@ export type BundleTransformOptions = { /** * Exclude asset */ - excludeAssets?: ExcludeFilepathConfig | Array; + excludeAssets?: ExcludeFilepathOption; /** * Exclude module */ - excludeModules?: ExcludeFilepathConfig | Array; + excludeModules?: ExcludeFilepathOption; }; export const bundleToWebpackStats = ( bundle: OutputBundle, - customOptions?: BundleTransformOptions + pluginOptions?: BundleTransformOptions ): WebpackStatsFiltered => { const options = { moduleOriginalSize: false, - ...customOptions, + ...pluginOptions, }; const items = Object.values(bundle); @@ -76,6 +76,10 @@ export const bundleToWebpackStats = ( items.forEach(item => { if (item.type === 'chunk') { + if (checkExcludeFilepath(item.fileName, options.excludeAssets)) { + return; + } + assets.push({ name: item.fileName, size: getByteSize(item.code), @@ -92,6 +96,10 @@ export const bundleToWebpackStats = ( }); Object.entries(item.modules).forEach(([modulePath, moduleInfo]) => { + if (checkExcludeFilepath(modulePath, options.excludeModules)) { + return; + } + // Remove unexpected rollup null prefix const normalizedModulePath = modulePath.replace('\u0000', ''); @@ -120,6 +128,10 @@ export const bundleToWebpackStats = ( } }); } else if (item.type === 'asset') { + if (checkExcludeFilepath(item.fileName, options.excludeAssets)) { + return; + } + assets.push({ name: item.fileName, size: getByteSize(item.source.toString()), diff --git a/src/types.d.ts b/src/types.d.ts index 0b3a0f9..8255ead 100644 --- a/src/types.d.ts +++ b/src/types.d.ts @@ -1 +1,3 @@ -export type ExcludeFilepathConfig = string | RegExp | ((filepath: string) => boolean); +export type ExcludeFilepathParam = string | RegExp | ((filepath: string) => boolean); + +export type ExcludeFilepathOption = ExcludeFilepathParam | Array; diff --git a/src/utils.ts b/src/utils.ts index e020ede..8eef676 100644 --- a/src/utils.ts +++ b/src/utils.ts @@ -2,7 +2,7 @@ import path from 'path'; import crypto from 'crypto'; import type { OutputChunk } from 'rollup'; -import type { ExcludeFilepathConfig } from './types'; +import type { ExcludeFilepathOption } from './types'; const HASH_LENGTH = 7; @@ -42,32 +42,32 @@ export function getChunkId(chunk: OutputChunk): string { */ export function checkExcludeFilepath( filepath: string, - config?: ExcludeFilepathConfig | Array + option?: ExcludeFilepathOption, ): boolean { - if (!config) { + if (!option) { return false; } - if (Array.isArray(config)) { + if (Array.isArray(option)) { let res = false; - for (let i = 0; i <= config.length - 1 && res === false; i++) { - res = checkExcludeFilepath(filepath, config[i]); + for (let i = 0; i <= option.length - 1 && res === false; i++) { + res = checkExcludeFilepath(filepath, option[i]); } return res; } - if (typeof config === 'function') { - return config(filepath); + if (typeof option === 'function') { + return option(filepath); } - if (typeof config === 'string') { - return Boolean(filepath.match(config)); + if (typeof option === 'string') { + return Boolean(filepath.match(option)); } - if ('test' in config) { - return config.test(filepath); + if ('test' in option) { + return option.test(filepath); } return false; diff --git a/test/unit/fixtures/rollup-bundle-stats.ts b/test/unit/fixtures/rollup-bundle-stats.ts new file mode 100644 index 0000000..0ed03db --- /dev/null +++ b/test/unit/fixtures/rollup-bundle-stats.ts @@ -0,0 +1,176 @@ +import path from 'path'; +import type { OutputBundle } from 'rollup'; + +const ROOT_DIR = path.join(__dirname, '../../../'); + +export default { + 'assets/logo-abcd1234.svg': { + name: undefined, + fileName: 'assets/logo-abcd1234.svg', + type: 'asset', + source: '', + needsCodeReference: true, + }, + 'assets/main-abcd1234.js': { + name: 'main', + fileName: 'assets/main-abcd1234.js', + preliminaryFileName: 'assets/main-abcd1234.js', + sourcemapFileName: 'assets/main-abcd1234.js.map', + type: 'chunk', + code: 'export default function () {}', + isEntry: true, + isDynamicEntry: false, + facadeModuleId: null, + map: null, + isImplicitEntry: false, + implicitlyLoadedBefore: [], + importedBindings: {}, + referencedFiles: [], + moduleIds: [ + path.join(ROOT_DIR, 'src/component-a.js'), + path.join(ROOT_DIR, 'src/index.js'), + ], + modules: { + [path.join(ROOT_DIR, 'src/component-a.js')]: { + code: 'export default A = 1;', + originalLength: 10, + renderedLength: 8, + removedExports: [], + renderedExports: [], + }, + [path.join(ROOT_DIR, 'src/index.js')]: { + code: '', + originalLength: 100, + renderedLength: 80, + removedExports: [], + renderedExports: [], + }, + }, + imports: [], + exports: [], + dynamicImports: [], + }, + 'assets/vendors-abcd1234.js': { + name: 'vendors', + fileName: 'assets/vendors-abcd1234.js', + preliminaryFileName: 'assets/vendors-abcd1234.js', + sourcemapFileName: 'assets/vendors-abcd1234.js.map', + type: 'chunk', + code: 'export default function () {}', + isEntry: true, + isDynamicEntry: false, + facadeModuleId: null, + map: null, + isImplicitEntry: false, + implicitlyLoadedBefore: [], + importedBindings: {}, + referencedFiles: [], + moduleIds: [ + path.join(ROOT_DIR, 'node_modules', 'package-a', 'index.js'), + path.join(ROOT_DIR, 'node_modules', 'package-b', 'index.js'), + ], + modules: { + [path.join( + ROOT_DIR, + 'node_modules', + 'package-a', + 'index.js' + )]: { + code: '', + originalLength: 10, + renderedLength: 8, + removedExports: [], + renderedExports: [], + }, + [path.join( + ROOT_DIR, + 'node_modules', + 'package-b', + 'index.js' + )]: { + code: '', + originalLength: 100, + renderedLength: 80, + removedExports: [], + renderedExports: [], + }, + }, + imports: [], + exports: [], + dynamicImports: [], + }, + 'assets/index-abcd1234.js': { + name: 'index', + fileName: 'assets/index-abcd1234.js', + preliminaryFileName: 'assets/index-abcd1234.js', + sourcemapFileName: 'assets/index-abcd1234.js.map', + type: 'chunk', + code: 'export default function () {}', + isEntry: false, + isDynamicEntry: true, + facadeModuleId: null, + map: null, + isImplicitEntry: false, + implicitlyLoadedBefore: [], + importedBindings: {}, + referencedFiles: [], + moduleIds: [ + path.join(ROOT_DIR, 'src', 'components/component-b/index.js'), + ], + modules: { + [path.join( + ROOT_DIR, + 'src', + 'components', + 'component-b', + 'index.js', + )]: { + code: '', + originalLength: 10, + renderedLength: 8, + removedExports: [], + renderedExports: [], + }, + }, + imports: [], + exports: [], + dynamicImports: [], + }, + 'assets/index-efab5678.js': { + name: 'index', + fileName: 'assets/index-efab5678.js', + preliminaryFileName: 'assets/index-efab5678.js', + sourcemapFileName: 'assets/index-efab5678.js.map', + type: 'chunk', + code: 'export default function () {}', + isEntry: false, + isDynamicEntry: true, + facadeModuleId: null, + map: null, + isImplicitEntry: false, + implicitlyLoadedBefore: [], + importedBindings: {}, + referencedFiles: [], + moduleIds: [ + path.join(ROOT_DIR, 'src', 'components/component-c/index.js'), + ], + modules: { + [path.join( + ROOT_DIR, + 'src', + 'components', + 'component-c', + 'index.js', + )]: { + code: '', + originalLength: 10, + renderedLength: 8, + removedExports: [], + renderedExports: [], + }, + }, + imports: [], + exports: [], + dynamicImports: [], + }, +} satisfies OutputBundle; diff --git a/test/unit/transform.test.ts b/test/unit/transform.test.ts index 0ebfed4..b980ead 100644 --- a/test/unit/transform.test.ts +++ b/test/unit/transform.test.ts @@ -1,181 +1,13 @@ import { describe, test, expect } from 'vitest'; import path from 'path'; -const ROOT_DIR = path.join(__dirname, '../..'); - import { bundleToWebpackStats } from '../../src/transform'; +import fixtures from './fixtures/rollup-bundle-stats'; + describe('bundleToWebpackStats', () => { - test('converts rollup bundle to webpack stats', () => { - expect( - bundleToWebpackStats({ - 'assets/logo-abcd1234.svg': { - name: undefined, - fileName: 'assets/logo-abcd1234.svg', - type: 'asset', - source: '', - needsCodeReference: true, - }, - 'assets/main-abcd1234.js': { - name: 'main', - fileName: 'assets/main-abcd1234.js', - preliminaryFileName: 'assets/main-abcd1234.js', - type: 'chunk', - code: 'export default function () {}', - isEntry: true, - isDynamicEntry: false, - facadeModuleId: null, - map: null, - isImplicitEntry: false, - implicitlyLoadedBefore: [], - importedBindings: {}, - referencedFiles: [], - moduleIds: [ - path.join(ROOT_DIR, 'src/component-a.js'), - path.join(ROOT_DIR, 'src/index.js'), - ], - modules: { - [path.join(ROOT_DIR, 'src/component-a.js')]: { - code: 'export default A = 1;', - originalLength: 10, - renderedLength: 8, - removedExports: [], - renderedExports: [], - }, - [path.join(ROOT_DIR, 'src/index.js')]: { - code: '', - originalLength: 100, - renderedLength: 80, - removedExports: [], - renderedExports: [], - }, - }, - imports: [], - exports: [], - dynamicImports: [], - }, - 'assets/vendors-abcd1234.js': { - name: 'vendors', - fileName: 'assets/vendors-abcd1234.js', - preliminaryFileName: 'assets/vendors-abcd1234.js', - type: 'chunk', - code: 'export default function () {}', - isEntry: true, - isDynamicEntry: false, - facadeModuleId: null, - map: null, - isImplicitEntry: false, - implicitlyLoadedBefore: [], - importedBindings: {}, - referencedFiles: [], - moduleIds: [ - path.join(ROOT_DIR, 'node_modules', 'package-a', 'index.js'), - path.join(ROOT_DIR, 'node_modules', 'package-b', 'index.js'), - ], - modules: { - [path.join( - ROOT_DIR, - 'node_modules', - 'package-a', - 'index.js' - )]: { - code: '', - originalLength: 10, - renderedLength: 8, - removedExports: [], - renderedExports: [], - }, - [path.join( - ROOT_DIR, - 'node_modules', - 'package-b', - 'index.js' - )]: { - code: '', - originalLength: 100, - renderedLength: 80, - removedExports: [], - renderedExports: [], - }, - }, - imports: [], - exports: [], - dynamicImports: [], - }, - 'assets/index-abcd1234.js': { - name: 'index', - fileName: 'assets/index-abcd1234.js', - preliminaryFileName: 'assets/index-abcd1234.js', - type: 'chunk', - code: 'export default function () {}', - isEntry: false, - isDynamicEntry: true, - facadeModuleId: null, - map: null, - isImplicitEntry: false, - implicitlyLoadedBefore: [], - importedBindings: {}, - referencedFiles: [], - moduleIds: [ - path.join(ROOT_DIR, 'src', 'components/component-b/index.js'), - ], - modules: { - [path.join( - ROOT_DIR, - 'src', - 'components', - 'component-b', - 'index.js', - )]: { - code: '', - originalLength: 10, - renderedLength: 8, - removedExports: [], - renderedExports: [], - }, - }, - imports: [], - exports: [], - dynamicImports: [], - }, - 'assets/index-efab5678.js': { - name: 'index', - fileName: 'assets/index-efab5678.js', - preliminaryFileName: 'assets/index-efab5678.js', - type: 'chunk', - code: 'export default function () {}', - isEntry: false, - isDynamicEntry: true, - facadeModuleId: null, - map: null, - isImplicitEntry: false, - implicitlyLoadedBefore: [], - importedBindings: {}, - referencedFiles: [], - moduleIds: [ - path.join(ROOT_DIR, 'src', 'components/component-c/index.js'), - ], - modules: { - [path.join( - ROOT_DIR, - 'src', - 'components', - 'component-c', - 'index.js', - )]: { - code: '', - originalLength: 10, - renderedLength: 8, - removedExports: [], - renderedExports: [], - }, - }, - imports: [], - exports: [], - dynamicImports: [], - }, - }) - ).toMatchObject({ + test('transforms rollup bundle stats to webpack stats', () => { + expect(bundleToWebpackStats(fixtures)).toMatchObject({ assets: [ { name: 'assets/logo-abcd1234.svg', @@ -286,4 +118,193 @@ describe('bundleToWebpackStats', () => { ], }); }); + + test('transforms rollup bundle stats to webpack stats with excludeAssets option', () => { + expect(bundleToWebpackStats(fixtures, { excludeAssets: 'assets/vendors' })).toMatchObject({ + assets: [ + { + name: 'assets/logo-abcd1234.svg', + size: 11, + }, + { + name: 'assets/main-abcd1234.js', + size: 29, + }, + { + name: 'assets/index-abcd1234.js', + size: 29, + }, + { + name: 'assets/index-efab5678.js', + size: 29, + }, + ], + chunks: [ + { + id: 'e1c35b4', + initial: true, + entry: true, + names: ['main'], + files: ['assets/main-abcd1234.js'], + }, + { + id: 'e7b195f', + initial: false, + entry: false, + names: ['index'], + files: ['assets/index-abcd1234.js'], + }, + { + id: '7cd4868', + initial: false, + entry: false, + names: ['index'], + files: ['assets/index-efab5678.js'], + }, + ], + modules: [ + { + chunks: ['e1c35b4'], + name: `.${path.sep}${path.join( + 'src', + 'component-a.js', + )}`, + size: 8, + }, + { + chunks: ['e1c35b4'], + name: `.${path.sep}${path.join( + 'src', + 'index.js' + )}`, + size: 80, + }, + { + chunks: ['e7b195f'], + name: `.${path.sep}${path.join( + 'src', + 'components', + 'component-b', + 'index.js', + )}`, + size: 8, + }, + { + chunks: ['7cd4868'], + name: `.${path.sep}${path.join( + 'src', + 'components', + 'component-c', + 'index.js', + )}`, + size: 8, + }, + ], + }); + }); + + test('transforms rollup bundle stats to webpack stats with excludeModules option', () => { + expect(bundleToWebpackStats(fixtures, { excludeModules: '/node_modules/package-b/' })).toMatchObject({ + assets: [ + { + name: 'assets/logo-abcd1234.svg', + size: 11, + }, + { + name: 'assets/main-abcd1234.js', + size: 29, + }, + { + name: 'assets/vendors-abcd1234.js', + size: 29, + }, + { + name: 'assets/index-abcd1234.js', + size: 29, + }, + { + name: 'assets/index-efab5678.js', + size: 29, + }, + ], + chunks: [ + { + id: 'e1c35b4', + initial: true, + entry: true, + names: ['main'], + files: ['assets/main-abcd1234.js'], + }, + { + + id: '95848fd', + initial: true, + entry: true, + names: ['vendors'], + files: ['assets/vendors-abcd1234.js'], + }, + { + id: 'e7b195f', + initial: false, + entry: false, + names: ['index'], + files: ['assets/index-abcd1234.js'], + }, + { + id: '7cd4868', + initial: false, + entry: false, + names: ['index'], + files: ['assets/index-efab5678.js'], + }, + ], + modules: [ + { + chunks: ['e1c35b4'], + name: `.${path.sep}${path.join( + 'src', + 'component-a.js', + )}`, + size: 8, + }, + { + chunks: ['e1c35b4'], + name: `.${path.sep}${path.join( + 'src', + 'index.js' + )}`, + size: 80, + }, + { + chunks: ['95848fd'], + name: `.${path.sep}${path.join( + 'node_modules', + 'package-a', + 'index.js' + )}`, + size: 8, + }, + { + chunks: ['e7b195f'], + name: `.${path.sep}${path.join( + 'src', + 'components', + 'component-b', + 'index.js', + )}`, + size: 8, + }, + { + chunks: ['7cd4868'], + name: `.${path.sep}${path.join( + 'src', + 'components', + 'component-c', + 'index.js', + )}`, + size: 8, + }, + ], + }); + }); });