Skip to content

Commit

Permalink
feat: better build output + options for brotli / chunk size warning
Browse files Browse the repository at this point in the history
  • Loading branch information
yyx990803 committed Feb 1, 2021
1 parent c575004 commit da1b06f
Show file tree
Hide file tree
Showing 3 changed files with 202 additions and 46 deletions.
16 changes: 13 additions & 3 deletions packages/vite/src/node/build.ts
Original file line number Diff line number Diff line change
Expand Up @@ -165,6 +165,16 @@ export interface BuildOptions {
* directives in production.
*/
ssrManifest?: boolean
/**
* Set to false to disable brotli compressed size reporting for build.
* Can slightly improve build speed.
*/
brotliSize?: boolean

This comment has been minimized.

Copy link
@bompus

bompus Feb 2, 2021

Contributor

This option doesn't appear to affect any downstream logic?

This comment has been minimized.

Copy link
@yyx990803

yyx990803 Feb 2, 2021

Author Member

Ah good catch 😅

fixed in 1d5437d

/**
* Adjust chunk size warning limit (in kbs).
* @default 500
*/
chunkSizeWarningLimit?: number
}

export interface LibraryOptions {
Expand Down Expand Up @@ -201,6 +211,8 @@ export function resolveBuildOptions(raw?: BuildOptions): ResolvedBuildOptions {
lib: false,
ssr: false,
ssrManifest: false,
brotliSize: true,
chunkSizeWarningLimit: 500,
...raw
}

Expand Down Expand Up @@ -245,9 +257,7 @@ export function resolveBuildPlugins(
: []),
...(options.manifest ? [manifestPlugin(config)] : []),
...(options.ssrManifest ? [ssrManifestPlugin(config)] : []),
...(!config.logLevel || config.logLevel === 'info'
? [buildReporterPlugin(config)]
: [])
buildReporterPlugin(config)
]
}
}
Expand Down
7 changes: 5 additions & 2 deletions packages/vite/src/node/logger.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ export interface LogOptions {
timestamp?: boolean
}

const LogLevels: Record<LogLevel, number> = {
export const LogLevels: Record<LogLevel, number> = {
silent: 0,
error: 1,
warn: 2,
Expand All @@ -41,7 +41,10 @@ export function createLogger(
allowClearScreen = true
): Logger {
const thresh = LogLevels[level]
const clear = allowClearScreen && !process.env.CI ? clearScreen : () => {}
const clear =
allowClearScreen && process.stdout.isTTY && !process.env.CI
? clearScreen
: () => {}

function output(type: LogType, msg: string, options: LogOptions = {}) {
if (thresh >= LogLevels[type]) {
Expand Down
225 changes: 184 additions & 41 deletions packages/vite/src/node/plugins/reporter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,9 @@ import path from 'path'
import chalk from 'chalk'
import { Plugin } from 'rollup'
import { ResolvedConfig } from '../config'
import { sync as brotliSizeSync } from 'brotli-size'
import size from 'brotli-size'
import { normalizePath } from '../utils'
import { LogLevels } from '../logger'

const enum WriteType {
JS,
Expand All @@ -22,70 +23,212 @@ const writeColors = {
}

export function buildReporterPlugin(config: ResolvedConfig): Plugin {
const chunkLimit = config.build.chunkSizeWarningLimit

async function getCompressedSize(code: string | Uint8Array): Promise<string> {
if (config.build.ssr) {
return ''
}
return ` / brotli: ${(
(await size(typeof code === 'string' ? code : Buffer.from(code))) / 1024
).toFixed(2)}kb`
}

function printFileInfo(
filePath: string,
content: string | Uint8Array,
type: WriteType,
maxLength: number
maxLength: number,
compressedSize = ''
) {
const needCompression =
!config.build.ssr &&
(type === WriteType.JS ||
type === WriteType.CSS ||
type === WriteType.HTML)

const compressed = needCompression
? ` / brotli: ${(
brotliSizeSync(
typeof content === 'string' ? content : Buffer.from(content)
) / 1024
).toFixed(2)}kb`
: ``

const outDir =
normalizePath(
path.relative(
config.root,
path.resolve(config.root, config.build.outDir)
)
) + '/'
const kbs = content.length / 1024
const sizeColor = kbs > chunkLimit ? chalk.yellow : chalk.dim
config.logger.info(
`${chalk.gray(chalk.white.dim(outDir))}${writeColors[type](
filePath.padEnd(maxLength + 2)
)} ${chalk.dim(`${(content.length / 1024).toFixed(2)}kb${compressed}`)}`
)} ${sizeColor(`${kbs.toFixed(2)}kb${compressedSize}`)}`
)
}

const tty = process.stdout.isTTY && !process.env.CI
const shouldLogInfo = LogLevels[config.logLevel || 'info'] >= LogLevels.info
let hasTransformed = false
let hasRenderedChunk = false
let transformedCount = 0
let chunkCount = 0

const logTransform = throttle((id: string) => {
writeLine(
`transforming (${transformedCount}) ${chalk.dim(
path.relative(config.root, id)
)}`
)
})

return {
name: 'vite:size',
writeBundle(_, output) {
let longest = 0
for (const file in output) {
const l = output[file].fileName.length
if (l > longest) longest = l
name: 'vite:reporter',

transform(_, id) {
transformedCount++
if (shouldLogInfo) {
if (!tty) {
if (!hasTransformed) {
config.logger.info(`transforming...`)
}
} else {
if (id.includes(`?`)) return
logTransform(id)
}
hasTransformed = true
}
return null
},

buildEnd() {
if (tty) {
process.stdout.clearLine(0)
process.stdout.cursorTo(0)
}
config.logger.info(
`${chalk.green(`✓`)} ${transformedCount} modules transformed.`
)
},

renderStart() {
chunkCount = 0
},

for (const file in output) {
const chunk = output[file]
if (chunk.type === 'chunk') {
printFileInfo(chunk.fileName, chunk.code, WriteType.JS, longest)
if (chunk.map) {
printFileInfo(
chunk.fileName + '.map',
chunk.map.toString(),
WriteType.SOURCE_MAP,
longest
)
renderChunk() {
chunkCount++
if (shouldLogInfo) {
if (!tty) {
if (!hasRenderedChunk) {
config.logger.info('rendering chunks...')
}
} else if (chunk.source) {
printFileInfo(
chunk.fileName,
chunk.source,
chunk.fileName.endsWith('.css') ? WriteType.CSS : WriteType.ASSET,
longest
)
} else {
writeLine(`rendering chunks (${chunkCount})...`)
}
hasRenderedChunk = true
}
return null
},

generateBundle() {
if (tty) {
process.stdout.clearLine(0)
process.stdout.cursorTo(0)
}
},

async writeBundle(_, output) {
let hasLargeChunks = false

if (shouldLogInfo) {
let longest = 0
for (const file in output) {
const l = output[file].fileName.length
if (l > longest) longest = l
}

// large chunks are deferred to be logged at the end so they are more
// visible.
const deferredLogs: (() => void)[] = []

await Promise.all(
Object.keys(output).map(async (file) => {
const chunk = output[file]
if (chunk.type === 'chunk') {
// bail out on particularly large chunks
const isLarge = chunk.code.length / 1024 > chunkLimit
const log = async () => {
printFileInfo(
chunk.fileName,
chunk.code,
WriteType.JS,
longest,
isLarge
? ' / brotli: skipped (large chunk)'
: await getCompressedSize(chunk.code)
)
if (chunk.map) {
printFileInfo(
chunk.fileName + '.map',
chunk.map.toString(),
WriteType.SOURCE_MAP,
longest
)
}
}
if (isLarge) {
hasLargeChunks = true
deferredLogs.push(log)
} else {
await log()
}
} else if (chunk.source) {
const isCSS = chunk.fileName.endsWith('.css')
printFileInfo(
chunk.fileName,
chunk.source,
isCSS ? WriteType.CSS : WriteType.ASSET,
longest,
isCSS ? await getCompressedSize(chunk.source) : undefined
)
}
})
)

await Promise.all(deferredLogs.map((l) => l()))
} else {
hasLargeChunks = Object.keys(output).some((file) => {
const chunk = output[file]
return chunk.type === 'chunk' && chunk.code.length / 1024 > chunkLimit
})
}

if (
hasLargeChunks &&
config.build.minify &&
!config.build.lib &&
!config.build.ssr
) {
config.logger.warn(
chalk.yellow(
`\n(!) Some chunks are larger than ${chunkLimit}kb after minification. Consider:\n` +
`- Using dynamic import() to code-split the application\n` +
`- Use build.rollupOptions.output.manualChunks to improve chunking: https://rollupjs.org/guide/en/#outputmanualchunks\n` +
`- Adjust chunk size limit for this warning via build.chunkSizeWarningLimit.`
)
)
}
}
}
}

function writeLine(output: string) {
process.stdout.clearLine(0)
process.stdout.cursorTo(0)
if (output.length < process.stdout.columns) {
process.stdout.write(output)
} else {
process.stdout.write(output.substring(0, process.stdout.columns - 1))
}
}

function throttle(fn: Function) {
let timerHandle: NodeJS.Timeout | null = null
return (...args: any[]) => {
if (timerHandle) return
fn(...args)
timerHandle = setTimeout(() => {
timerHandle = null
}, 100)
}
}

0 comments on commit da1b06f

Please sign in to comment.