Skip to content

Commit

Permalink
Browse files Browse the repository at this point in the history
feat: add bundle size reporter
  • Loading branch information
egoist committed Dec 10, 2021
1 parent 524470c commit 5edf9a8
Show file tree
Hide file tree
Showing 8 changed files with 116 additions and 24 deletions.
4 changes: 2 additions & 2 deletions package.json
Expand Up @@ -44,9 +44,9 @@
"postcss-load-config": "^3.0.1",
"resolve-from": "^5.0.0",
"rollup": "^2.60.0",
"tree-kill": "^1.2.2",
"source-map": "^0.7.3",
"sucrase": "^3.20.3"
"sucrase": "^3.20.3",
"tree-kill": "^1.2.2"
},
"devDependencies": {
"@rollup/plugin-json": "^4.1.0",
Expand Down
15 changes: 9 additions & 6 deletions src/esbuild/index.ts
Expand Up @@ -89,12 +89,19 @@ export async function runEsbuild(
const loader = options.loader || {}
const injectShims = options.shims !== false

pluginContainer.setContext({
format,
splitting,
options,
logger,
})

const esbuildPlugins: Array<EsbuildPlugin | false | undefined> = [
format === 'cjs' && nodeProtocolPlugin(),
{
name: 'modify-options',
setup(build) {
pluginContainer.modifyEsbuildOptions(build.initialOptions, { format })
pluginContainer.modifyEsbuildOptions(build.initialOptions)
if (options.esbuildOptions) {
options.esbuildOptions(build.initialOptions, { format })
}
Expand Down Expand Up @@ -231,11 +238,7 @@ export async function runEsbuild(

await pluginContainer.buildFinished({
files: result.outputFiles,
context: {
format,
splitting,
options,
},
metafile: result.metafile,
})
}

Expand Down
4 changes: 3 additions & 1 deletion src/index.ts
Expand Up @@ -20,6 +20,7 @@ import { shebang } from './plugins/shebang'
import { cjsSplitting } from './plugins/cjs-splitting'
import { PluginContainer } from './plugin'
import { es5 } from './plugins/es5'
import { sizeReporter } from './plugins/size-reporter'

export type { Format, Options }

Expand Down Expand Up @@ -185,9 +186,10 @@ export async function build(_options: Options) {
...options.format.map(async (format, index) => {
const pluginContainer = new PluginContainer([
shebang(),
...(options.plugins || []),
cjsSplitting(),
es5(),
...(options.plugins || []),
sizeReporter(),
])
await pluginContainer.buildStarted()
await runEsbuild(options, {
Expand Down
35 changes: 35 additions & 0 deletions src/lib/report-size.ts
@@ -0,0 +1,35 @@
import * as colors from 'colorette'
import { Logger } from '../log'

const prettyBytes = (bytes: number) => {
const unit = ['B', 'KB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB']
const exp = Math.floor(Math.log(bytes) / Math.log(1024))
return `${(bytes / Math.pow(1024, exp)).toFixed(2)} ${unit[exp]}`
}

const getLengthOfLongestString = (strings: string[]) => {
return strings.reduce((max, str) => {
return Math.max(max, str.length)
}, 0)
}

const padRight = (str: string, maxLength: number) => {
return str + ' '.repeat(maxLength - str.length)
}

export const reportSize = (
logger: Logger,
format: string,
files: { [name: string]: number }
) => {
const filenames = Object.keys(files)
const maxLength = getLengthOfLongestString(filenames) + 1
for (const name of filenames) {
logger.success(
format,
`${colors.bold(padRight(name, maxLength))}${colors.green(
prettyBytes(files[name])
)}`
)
}
}
4 changes: 2 additions & 2 deletions src/log.ts
Expand Up @@ -2,7 +2,7 @@ import * as colors from 'colorette'

type LOG_TYPE = 'info' | 'success' | 'error' | 'warn'

const colorize = (type: LOG_TYPE, data: any, onlyImportant = false) => {
export const colorize = (type: LOG_TYPE, data: any, onlyImportant = false) => {
if (onlyImportant && (type === 'info' || type === 'success')) return data

const color =
Expand All @@ -26,7 +26,7 @@ export const makeLabel = (
colorize(type, input.toUpperCase()),
]
.filter(Boolean)
.join(colors.dim(' '))
.join(' ')
}

let silent = false
Expand Down
39 changes: 27 additions & 12 deletions src/plugin.ts
@@ -1,8 +1,9 @@
import path from 'path'
import { OutputFile, BuildOptions as EsbuildOptions } from 'esbuild'
import { OutputFile, BuildOptions as EsbuildOptions, Metafile } from 'esbuild'
import { SourceMapConsumer, SourceMapGenerator, RawSourceMap } from 'source-map'
import { Format, NormalizedOptions } from '.'
import { outputFile } from './fs'
import { Logger } from './log'

export type ChunkInfo = {
type: 'chunk'
Expand Down Expand Up @@ -37,12 +38,15 @@ export type RenderChunk = (
| void
>

export type BuildStart = () => MaybePromise<void>
export type BuildEnd = () => MaybePromise<void>
export type BuildStart = (this: PluginContext) => MaybePromise<void>
export type BuildEnd = (
this: PluginContext,
ctx: { metafile?: Metafile }
) => MaybePromise<void>

export type ModifyEsbuildOptions = (
options: EsbuildOptions,
context: { format: Format }
this: PluginContext,
options: EsbuildOptions
) => void

export type Plugin = {
Expand All @@ -61,6 +65,7 @@ export type PluginContext = {
format: Format
splitting?: boolean
options: NormalizedOptions
logger: Logger
}

const parseSourceMap = (map?: string | object | null) => {
Expand All @@ -72,33 +77,43 @@ const isCSS = (path: string) => /\.css$/.test(path)

export class PluginContainer {
plugins: Plugin[]
context?: PluginContext

constructor(plugins: Plugin[]) {
this.plugins = plugins
}

modifyEsbuildOptions(options: EsbuildOptions, context: { format: Format }) {
setContext(context: PluginContext) {
this.context = context
}

getContext() {
if (!this.context) throw new Error(`Plugin context is not set`)
return this.context
}

modifyEsbuildOptions(options: EsbuildOptions) {
for (const plugin of this.plugins) {
if (plugin.esbuildOptions) {
plugin.esbuildOptions(options, context)
plugin.esbuildOptions.call(this.getContext(), options)
}
}
}

async buildStarted() {
for (const plugin of this.plugins) {
if (plugin.buildStart) {
await plugin.buildStart()
await plugin.buildStart.call(this.getContext())
}
}
}

async buildFinished({
files,
context,
metafile,
}: {
files: OutputFile[]
context: PluginContext
metafile?: Metafile
}) {
await Promise.all(
files.map(async (file) => {
Expand All @@ -118,7 +133,7 @@ export class PluginContainer {
for (const plugin of this.plugins) {
if (info.type === 'chunk' && plugin.renderChunk) {
const result = await plugin.renderChunk.call(
context,
this.getContext(),
info.code,
info
)
Expand Down Expand Up @@ -162,7 +177,7 @@ export class PluginContainer {

for (const plugin of this.plugins) {
if (plugin.buildEnd) {
await plugin.buildEnd()
await plugin.buildEnd.call(this.getContext(), { metafile })
}
}
}
Expand Down
22 changes: 22 additions & 0 deletions src/plugins/size-reporter.ts
@@ -0,0 +1,22 @@
import { Plugin } from '../plugin'
import { reportSize } from '../lib/report-size'

export const sizeReporter = (): Plugin => {
return {
name: 'size-reporter',

buildEnd({ metafile }) {
if (!metafile) return
reportSize(
this.logger,
this.format,
Object.keys(metafile.outputs).reduce((res, name) => {
return {
...res,
[name]: metafile!.outputs[name].bytes,
}
}, {})
)
},
}
}
17 changes: 16 additions & 1 deletion src/rollup.ts
Expand Up @@ -10,6 +10,7 @@ import { TsResolveOptions, tsResolvePlugin } from './rollup/ts-resolve'
import { createLogger, setSilent } from './log'
import { getDeps } from './load'
import path from 'path'
import { reportSize } from './lib/report-size'

const logger = createLogger()

Expand Down Expand Up @@ -177,8 +178,22 @@ async function runRollup(options: RollupConfig) {
}
logger.info('dts', 'Build start')
const bundle = await rollup(options.inputConfig)
await bundle.write(options.outputConfig)
const result = await bundle.write(options.outputConfig)
logger.success('dts', `⚡️ Build success in ${getDuration()}`)
reportSize(
logger,
'dts',
result.output.reduce((res, info) => {
const name = path.relative(
process.cwd(),
path.join(options.outputConfig.dir || '.', info.fileName)
)
return {
...res,
[name]: info.type === 'chunk' ? info.code.length : info.source.length,
}
}, {})
)
} catch (error) {
logger.error('dts', 'Build error')
parentPort?.postMessage('error')
Expand Down

0 comments on commit 5edf9a8

Please sign in to comment.