Skip to content

Commit

Permalink
Add tests for plugins
Browse files Browse the repository at this point in the history
  • Loading branch information
filipsobol committed Oct 13, 2024
1 parent 9d5f87b commit e20ec92
Show file tree
Hide file tree
Showing 13 changed files with 323 additions and 48 deletions.
5 changes: 5 additions & 0 deletions .changeset/brave-onions-notice.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"sonda": patch
---

Rollup plugin: Fix detecting ESM files when `@rollup/plugin-commonjs` plugin is not installed
5 changes: 5 additions & 0 deletions .changeset/tame-cycles-hunt.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"sonda": patch
---

Webpack plugin: Fix reported files sizes, which sometimes included imported dependencies.
17 changes: 7 additions & 10 deletions packages/sonda/src/bundlers/esbuild.ts
Original file line number Diff line number Diff line change
@@ -1,27 +1,24 @@
import { resolve } from 'path';
import { normalizeOptions } from '../utils';
import { addSourcesToInputs } from '../sourcemap/map';
import { generateReportFromAssets } from '../report/generate';
import type { Plugin } from 'esbuild';
import type { Options, JsonReport } from '../types';
import { addSourcesToInputs } from '../sourcemap/map';

export function SondaEsbuildPlugin( options?: Partial<Options> ): Plugin {
export function SondaEsbuildPlugin( options: Partial<Options> = {} ): Plugin {
return {
name: 'sonda',
setup( build ) {
build.initialOptions.metafile = true;

// Esbuild already reads the existing source maps, so there's no need to do it again
options.detailed = false;

build.onEnd( result => {
if ( !result.metafile ) {
return console.error( 'Metafile is required for SondaEsbuildPlugin to work.' );
}

const cwd = process.cwd();
const normalizedOptions = normalizeOptions( options );

// Esbuild already reads the existing source maps, so there's no need to do it again
normalizedOptions.detailed = false;

const inputs = Object
.entries( result.metafile.inputs )
.reduce( ( acc, [ path, data ] ) => {
Expand All @@ -47,9 +44,9 @@ export function SondaEsbuildPlugin( options?: Partial<Options> ): Plugin {
}, {} as JsonReport[ 'inputs' ] );

return generateReportFromAssets(
Object.keys( result.metafile.outputs ),
Object.keys( result.metafile.outputs ).map( path => resolve( cwd, path ) ),
inputs,
normalizeOptions( options )
options
);
} );
}
Expand Down
11 changes: 4 additions & 7 deletions packages/sonda/src/bundlers/rollup.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,10 @@
import { join, resolve, dirname } from 'path';
import { normalizeOptions, normalizePath } from '../utils.js';
import { normalizePath, cjsRegex, jsRegexp } from '../utils.js';
import { generateReportFromAssets } from '../report/generate.js';
import type { Options, ModuleFormat, JsonReport } from '../types.js';
import type { Plugin, ModuleInfo, NormalizedOutputOptions, OutputBundle } from 'rollup';

const esmRegex = /\.m[tj]sx?$/;
const cjsRegex = /\.c[tj]sx?$/;

export function SondaRollupPlugin( options?: Partial<Options> ): Plugin {
export function SondaRollupPlugin( options: Partial<Options> = {} ): Plugin {
let inputs: JsonReport[ 'inputs' ] = {};

return {
Expand All @@ -23,7 +20,7 @@ export function SondaRollupPlugin( options?: Partial<Options> ): Plugin {
return generateReportFromAssets(
assets,
inputs,
normalizeOptions( options )
options
);
},

Expand All @@ -43,7 +40,7 @@ function getFormat( moduleId: string, isCommonJS: boolean | undefined ): ModuleF
return 'cjs';
}

if ( isCommonJS === false || esmRegex.test( moduleId ) ) {
if ( isCommonJS === false || jsRegexp.test( moduleId ) ) {
return 'esm';
}

Expand Down
18 changes: 10 additions & 8 deletions packages/sonda/src/bundlers/webpack.ts
Original file line number Diff line number Diff line change
@@ -1,16 +1,14 @@
import { join } from 'path';
import { normalizeOptions, normalizePath } from '../utils';
import { normalizePath, jsRegexp } from '../utils';
import { generateReportFromAssets } from '../report/generate';
import type { Compiler, StatsModule } from 'webpack';
import type { Options, ModuleFormat, JsonReport } from '../types';

const jsRegexp = /\.[c|m]?[t|j]s[x]?$/;

export class SondaWebpackPlugin {
options: Partial<Options>;

constructor ( options?: Partial<Options> ) {
this.options = options || {};
constructor ( options: Partial<Options> = {} ) {
this.options = options;
}

apply( compiler: Compiler ): void {
Expand All @@ -20,11 +18,15 @@ export class SondaWebpackPlugin {
const inputs: JsonReport[ 'inputs' ] = {};
const stats = compilation.getStats().toJson( {
modules: true,
providedExports: true
providedExports: true,
} );

const outputPath = stats.outputPath || compiler.outputPath;
const modules = stats.modules?.filter( mod => mod.nameForCondition && mod.moduleType !== 'asset/inline' ) || [];
const modules: Array<StatsModule> = stats.modules
?.flatMap( mod => mod.modules ? [ mod, ...mod.modules ] : mod )
.filter( mod => mod.nameForCondition && !mod.codeGenerated )
.filter( ( mod, index, self ) => self.findIndex( m => m.nameForCondition === mod.nameForCondition ) === index )
|| [];

modules.forEach( module => {
const imports = modules.reduce( ( acc, { nameForCondition, issuerName, reasons } ) => {
Expand All @@ -46,7 +48,7 @@ export class SondaWebpackPlugin {
return generateReportFromAssets(
stats.assets?.map( asset => join( outputPath, asset.name ) ) || [],
inputs,
normalizeOptions( this.options )
this.options
);
} );
}
Expand Down
21 changes: 14 additions & 7 deletions packages/sonda/src/report/generate.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,21 +2,28 @@ import { join } from 'path';
import { writeFileSync } from 'fs';
import { generateHtmlReport, generateJsonReport } from '../report.js';
import type { Options, JsonReport } from '../types.js';
import { normalizeOptions } from '../utils.js';

export async function generateReportFromAssets(
assets: string[],
inputs: JsonReport[ 'inputs' ],
options: Options
userOptions: Partial<Options>
): Promise<void> {
const { default: open } = await import( 'open' );
const options = normalizeOptions( userOptions );
const handler = options.format === 'html' ? saveHtml : saveJson;
const path = handler( assets, inputs, options );

const handler = options.format === 'html'
? saveHtml
: saveJson;
if ( !options.open || !path ) {
return;
}

const path = handler( assets, inputs, options );
/**
* `open` is ESM-only package, so we need to import it
* dynamically to make it work in CommonJS environment.
*/
const { default: open } = await import( 'open' );

options.open && path && open( path );
open( path );
}

function saveHtml(
Expand Down
4 changes: 4 additions & 0 deletions packages/sonda/src/utils.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,10 @@
import { relative, win32, posix } from 'path';
import type { Options } from './types';

export const esmRegex: RegExp = /\.m[tj]sx?$/;
export const cjsRegex: RegExp = /\.c[tj]sx?$/;
export const jsRegexp: RegExp = /\.[cm]?[tj]s[x]?$/;

export function normalizeOptions( options?: Partial<Options> ) {
const defaultOptions: Options = {
open: true,
Expand Down
84 changes: 84 additions & 0 deletions packages/sonda/tests/bundlers.esbuild.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
import { vi, describe, it, expect } from 'vitest';
import { join } from 'path';
import esbuild from 'esbuild';
import { SondaEsbuildPlugin } from '../src/bundlers/esbuild';
import type { Options } from '../src/types';

const mocks = vi.hoisted( () => ( {
generateReportFromAssets: vi.fn().mockResolvedValue( undefined )
} ) );

vi.mock( '../src/report/generate.js', () => ( {
generateReportFromAssets: mocks.generateReportFromAssets
} ) );

describe( 'SondaEsbuildPlugin', () => {
it( 'should transform the code correctly', async () => {
await esbuild.build( {
entryPoints: [ join( import.meta.dirname, 'fixtures/bundlers/index.js' ) ],
bundle: true,
outfile: join( import.meta.dirname, 'dist/esbuild_1.js' ),
sourcemap: true,
plugins: [ SondaEsbuildPlugin() ],
format: 'esm',
} );

expect( mocks.generateReportFromAssets ).toHaveBeenCalledWith(
[
join( import.meta.dirname, 'dist/esbuild_1.js.map' ),
join( import.meta.dirname, 'dist/esbuild_1.js' )
],
{
'tests/fixtures/bundlers/index.js': {
belongsTo: null,
bytes: 66,
format: 'esm',
imports: [ 'tests/fixtures/detailed/index.js' ]
},
'tests/fixtures/detailed/index.js': {
belongsTo: null,
bytes: 238,
format: 'esm',
imports: []
},
'tests/fixtures/detailed/src/maths.js': {
belongsTo: 'tests/fixtures/detailed/index.js',
bytes: 201,
format: 'esm',
imports: []
},
'tests/fixtures/detailed/src/pow.js': {
belongsTo: 'tests/fixtures/detailed/index.js',
bytes: 67,
format: 'esm',
imports: []
}
},
{
detailed: false
}
);
} );

it( 'passes options to the `generateReportFromAssets` function', async () => {
const options: Partial<Options> = {
format: 'json',
open: false
};

await esbuild.build( {
entryPoints: [ join( import.meta.dirname, 'fixtures/bundlers/index.js' ) ],
bundle: true,
outfile: join( import.meta.dirname, 'dist/esbuild_2.js' ),
sourcemap: true,
plugins: [ SondaEsbuildPlugin( options ) ],
format: 'esm',
} );

expect( mocks.generateReportFromAssets ).toHaveBeenCalledWith(
expect.any( Array ),
expect.any( Object ),
{ ...options, detailed: false }
);
} );
} );
84 changes: 84 additions & 0 deletions packages/sonda/tests/bundlers.rollup.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
import { vi, describe, it, expect } from 'vitest';
import { join } from 'path';
import { rollup } from 'rollup';
import { SondaRollupPlugin } from '../src/bundlers/rollup';
import type { Options } from '../src/types';

const mocks = vi.hoisted( () => ( {
generateReportFromAssets: vi.fn().mockResolvedValue( undefined )
} ) );

vi.mock( '../src/report/generate.js', () => ( {
generateReportFromAssets: mocks.generateReportFromAssets
} ) );

describe( 'SondaRollupPlugin', () => {
it( 'should transform the code correctly', async () => {
const bundle = await rollup( {
input: join( import.meta.dirname, 'fixtures/bundlers/index.js' ),
plugins: [
SondaRollupPlugin()
],
} );

/**
* `write` method is used instead of the `generate`, because
* the latter would not trigger the `writeBundle` hook.
*/
await bundle.write( {
file: join( import.meta.dirname, 'dist/rollup_1.js' ),
sourcemap: true,
format: 'es',
} );

expect( mocks.generateReportFromAssets ).toHaveBeenCalledWith(
[
join( import.meta.dirname, 'dist/rollup_1.js' ),
join( import.meta.dirname, 'dist/rollup_1.js.map' )
],
{
'tests/fixtures/bundlers/index.js': {
belongsTo: null,
bytes: 66,
format: 'esm',
imports: [ 'tests/fixtures/detailed/index.js' ]
},
'tests/fixtures/detailed/index.js': {
belongsTo: null,
bytes: 238,
format: 'esm',
imports: []
}
},
{}
);
} );

it( 'passes options to the `generateReportFromAssets` function', async () => {
const options: Partial<Options> = {
format: 'json',
open: false
};

const bundle = await rollup( {
input: join( import.meta.dirname, 'fixtures/bundlers/index.js' ),
plugins: [ SondaRollupPlugin( options ) ],
} );

/**
* `write` method is used instead of the `generate`, because
* the latter would not trigger the `writeBundle` hook.
*/
await bundle.write( {
file: join( import.meta.dirname, 'dist/rollup_2.js' ),
sourcemap: true,
format: 'es',
} );

expect( mocks.generateReportFromAssets ).toHaveBeenCalledWith(
expect.any( Array ),
expect.any( Object ),
options
);
} );
} );
Loading

0 comments on commit e20ec92

Please sign in to comment.