Skip to content

Commit

Permalink
Add the filename option to allow changing the report output path
Browse files Browse the repository at this point in the history
  • Loading branch information
filipsobol committed Nov 11, 2024
1 parent ab65fb3 commit 0e92901
Show file tree
Hide file tree
Showing 7 changed files with 155 additions and 28 deletions.
5 changes: 5 additions & 0 deletions .changeset/thin-foxes-relax.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"sonda": minor
---

Add the `filename` option to allow changing the report output path
10 changes: 10 additions & 0 deletions packages/sonda/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -143,6 +143,7 @@ Each plugin accepts an optional configuration object with the following options.
```javascript
SondaRollupPlugin( {
format: 'html',
filename: 'sonda-report.html',
open: true,
detailed: true,
gzip: true,
Expand All @@ -160,6 +161,15 @@ Determines the output format of the report. The following formats are supported:
* `'html'` - HTML file with treemap
* `'json'` - JSON file

### `filename`

* **Type:** `string`
* **Default:** `'sonda-report.html'` or `'sonda-report.json'` depending on the `format` option

Determines the path of the generated report. The values can be either a filename, a relative path, or an absolute path.

By default, the report is saved in the current working directory.

### `open`

* **Type:** `boolean`
Expand Down
36 changes: 19 additions & 17 deletions packages/sonda/src/report/generate.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { join } from 'path';
import { writeFileSync } from 'fs';
import { dirname } from 'path';
import { existsSync, mkdirSync, writeFileSync } from 'fs';
import { generateHtmlReport, generateJsonReport } from '../report.js';
import type { Options, JsonReport } from '../types.js';
import { normalizeOptions } from '../utils.js';
Expand All @@ -11,9 +11,18 @@ export async function generateReportFromAssets(
): Promise<void> {
const options = normalizeOptions( userOptions );
const handler = options.format === 'html' ? saveHtml : saveJson;
const path = handler( assets, inputs, options );
const report = handler( assets, inputs, options );
const outputDirectory = dirname( options.filename );

if ( !options.open || !path ) {
// Ensure the output directory exists
if ( !existsSync( outputDirectory ) ) {
mkdirSync( outputDirectory, { recursive: true } );
}

// Write the report to the file system
writeFileSync( options.filename, report );

if ( !options.open ) {
return;
}

Expand All @@ -23,31 +32,24 @@ export async function generateReportFromAssets(
*/
const { default: open } = await import( 'open' );

open( path );
// Open the report in the default program for the file extension
open( options.filename );
}

function saveHtml(
assets: string[],
inputs: JsonReport[ 'inputs' ],
options: Options
): string | null {
const report = generateHtmlReport( assets, inputs, options );
const path = join( process.cwd(), 'sonda-report.html' );

writeFileSync( path, report );

return path;
): string {
return generateHtmlReport( assets, inputs, options );
}

function saveJson(
assets: string[],
inputs: JsonReport[ 'inputs' ],
options: Options
): string | null {
): string {
const report = generateJsonReport( assets, inputs, options );
const path = join( process.cwd(), 'sonda-report.json' );

writeFileSync( path, JSON.stringify( report, null, 2 ) );

return path;
return JSON.stringify( report, null, 2 );
}
8 changes: 8 additions & 0 deletions packages/sonda/src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,14 @@ export interface Options {
*/
format: 'html' | 'json';

/**
* Determines the path of the generated report. The values can be either
* a filename, a relative path, or an absolute path.
*
* @default 'sonda-report.html' or 'sonda-report.json' depending on the `format` option
*/
filename: string;

/**
* Determines whether to open the report in the default program for given file
* extension (`.html` or `.json` depending on the `format` option) after the build.
Expand Down
41 changes: 37 additions & 4 deletions packages/sonda/src/utils.ts
Original file line number Diff line number Diff line change
@@ -1,21 +1,31 @@
import { relative, win32, posix } from 'path';
import { join, relative, win32, posix, extname, isAbsolute, format, parse } 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> ) {
export function normalizeOptions( options?: Partial<Options> ): Options {
const format = options?.format
|| options?.filename?.split( '.' ).at( -1 ) as Options['format']
|| 'html';

const defaultOptions: Options = {
format,
filename: 'sonda-report.' + format,
open: true,
format: 'html',
detailed: false,
sources: false,
gzip: false,
brotli: false,
};

return Object.assign( {}, defaultOptions, options ) as Options;
// Merge user options with the defaults
const normalizedOptions = Object.assign( {}, defaultOptions, options ) satisfies Options;

normalizedOptions.filename = normalizeOutputPath( normalizedOptions );

return normalizedOptions;
}

export function normalizePath( pathToNormalize: string ): string {
Expand All @@ -28,3 +38,26 @@ export function normalizePath( pathToNormalize: string ): string {
// Ensure paths are POSIX-compliant - https://stackoverflow.com/a/63251716/4617687
return relativized.replaceAll( win32.sep, posix.sep );
}

function normalizeOutputPath( options: Options ): string {
let path = options.filename;
const expectedExtension = '.' + options.format;

// Ensure the filename is an absolute path
if ( !isAbsolute( path ) ) {
path = join( process.cwd(), path );
}

// Ensure that the `filename` extension matches the `format` option
if ( expectedExtension !== extname( path ) ) {
console.warn(
'\x1b[0;33m' + // Make the message yellow
`Sonda: The file extension specified in the 'filename' does not match the 'format' option. ` +
`The extension will be changed to '${ expectedExtension }'.`
);

path = format( { ...parse( path ), base: '', ext: expectedExtension } )
}

return path;
}
8 changes: 8 additions & 0 deletions packages/sonda/tests/report.generate.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,14 @@ const mocks = vi.hoisted( () => ( {
generateHtmlReport: vi.fn(),
generateJsonReport: vi.fn(),
writeFileSync: vi.fn(),
existsSync: vi.fn(),
mkdirSync: vi.fn(),
} ) );

vi.mock( 'fs', () => ( {
writeFileSync: mocks.writeFileSync,
existsSync: mocks.existsSync,
mkdirSync: mocks.mkdirSync,
} ) );

vi.mock( 'open', () => ( {
Expand All @@ -30,6 +34,8 @@ describe( 'generate.ts', () => {
it( 'saves HTML report by default', async () => {
await generateReportFromAssets( [], {}, normalizeOptions() );

expect( mocks.existsSync ).toHaveBeenCalled();
expect( mocks.mkdirSync ).toHaveBeenCalled();
expect( mocks.writeFileSync ).toHaveBeenCalled();
expect( mocks.generateHtmlReport ).toHaveBeenCalled();
expect( mocks.generateJsonReport ).not.toHaveBeenCalled();
Expand All @@ -38,6 +44,8 @@ describe( 'generate.ts', () => {
it( 'saves JSON report', async () => {
await generateReportFromAssets( [], {}, normalizeOptions( { format: 'json' } ) );

expect( mocks.existsSync ).toHaveBeenCalled();
expect( mocks.mkdirSync ).toHaveBeenCalled();
expect( mocks.writeFileSync ).toHaveBeenCalled();
expect( mocks.generateJsonReport ).toHaveBeenCalled();
expect( mocks.generateHtmlReport ).not.toHaveBeenCalled();
Expand Down
75 changes: 68 additions & 7 deletions packages/sonda/tests/utils.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,8 +30,9 @@ describe('utils.ts', () => {
describe( 'normalizeOptions', () => {
it( 'should return default options when no options are provided', () => {
expect( normalizeOptions() ).toEqual( {
open: true,
format: 'html',
filename: process.cwd() + '/sonda-report.html',
open: true,
detailed: false,
sources: false,
gzip: false,
Expand All @@ -40,9 +41,10 @@ describe('utils.ts', () => {
});

it( 'merges defaults with provided values', () => {
expect( normalizeOptions( { format: 'json' } ) ).toEqual( {
open: true,
format: 'json',
expect( normalizeOptions( { open: false } ) ).toEqual( {
format: 'html',
filename: process.cwd() + '/sonda-report.html',
open: false,
detailed: false,
sources: false,
gzip: false,
Expand All @@ -52,21 +54,80 @@ describe('utils.ts', () => {

it( 'allows overriding all options', () => {
expect( normalizeOptions( {
open: false,
format: 'json',
filename: __dirname + '/sonda-report.json',
open: false,
detailed: true,
sources: true,
gzip: true,
brotli: true,
} ) ).toEqual( {
open: false,
format: 'json',
filename: __dirname + '/sonda-report.json',
open: false,
detailed: true,
sources: true,
gzip: true,
brotli: true,
} );
} )
} );

it( 'ensures the `filename` is an absolute path', () => {
expect( normalizeOptions( { filename: './dist/sonda.json' } ) ).toEqual( {
format: 'json',
filename: process.cwd() + '/dist/sonda.json',
open: true,
detailed: false,
sources: false,
gzip: false,
brotli: false,
} );
} );

it( 'matches the `filename` when `format` is provided', () => {
expect( normalizeOptions( { format: 'json' } ) ).toEqual( {
format: 'json',
filename: process.cwd() + '/sonda-report.json',
open: true,
detailed: false,
sources: false,
gzip: false,
brotli: false,
} );
} );

it( 'matches the `format` when `filename` is provided', () => {
expect( normalizeOptions( { filename: 'sonda.json' } ) ).toEqual( {
format: 'json',
filename: process.cwd() + '/sonda.json',
open: true,
detailed: false,
sources: false,
gzip: false,
brotli: false,
} );
} );

it( 'warns and fixes `format` and `filename` mismatch', () => {
const spy = vi.spyOn( console, 'warn' ).mockImplementationOnce( () => {} );

const options = normalizeOptions( {
format: 'json',
filename: __dirname + '/sonda-report.html',
} );

expect( spy ).toHaveBeenCalledOnce();

expect( options ).toEqual( {
format: 'json',
filename: __dirname + '/sonda-report.json',
open: true,
detailed: false,
sources: false,
gzip: false,
brotli: false,
} );
} );
} );

describe( 'normalizePath', () => {
Expand Down

0 comments on commit 0e92901

Please sign in to comment.