vite
Version:
Native-ESM powered web dev build tool
241 lines (218 loc) • 6.49 kB
text/typescript
import path from 'path'
import chalk from 'chalk'
import { Plugin } from 'rollup'
import { ResolvedConfig } from '../config'
import size from 'brotli-size'
import { normalizePath } from '../utils'
import { LogLevels } from '../logger'
const enum WriteType {
JS,
CSS,
ASSET,
HTML,
SOURCE_MAP
}
const writeColors = {
[WriteType.JS]: chalk.cyan,
[WriteType.CSS]: chalk.magenta,
[WriteType.ASSET]: chalk.green,
[WriteType.HTML]: chalk.blue,
[WriteType.SOURCE_MAP]: chalk.gray
}
export function buildReporterPlugin(config: ResolvedConfig): Plugin {
const chunkLimit = config.build.chunkSizeWarningLimit
function isLarge(code: string | Uint8Array): boolean {
// bail out on particularly large chunks
return code.length / 1024 > chunkLimit
}
async function getCompressedSize(code: string | Uint8Array): Promise<string> {
if (config.build.ssr || !config.build.brotliSize) {
return ''
}
if (isLarge(code)) {
return ' / brotli: skipped (large chunk)'
}
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,
compressedSize = ''
) {
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)
)} ${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: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 (shouldLogInfo) {
if (tty) {
process.stdout.clearLine(0)
process.stdout.cursorTo(0)
}
config.logger.info(
`${chalk.green(`✓`)} ${transformedCount} modules transformed.`
)
}
},
renderStart() {
chunkCount = 0
},
renderChunk() {
chunkCount++
if (shouldLogInfo) {
if (!tty) {
if (!hasRenderedChunk) {
config.logger.info('rendering chunks...')
}
} else {
writeLine(`rendering chunks (${chunkCount})...`)
}
hasRenderedChunk = true
}
return null
},
generateBundle() {
if (shouldLogInfo && 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') {
const log = async () => {
printFileInfo(
chunk.fileName,
chunk.code,
WriteType.JS,
longest,
await getCompressedSize(chunk.code)
)
if (chunk.map) {
printFileInfo(
chunk.fileName + '.map',
chunk.map.toString(),
WriteType.SOURCE_MAP,
longest
)
}
}
if (isLarge(chunk.code)) {
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)
}
}