Skip to content

Commit 7d24b5f

Browse files
authoredNov 22, 2022
feat(build): cleaner logs output (#10895)
1 parent f777b55 commit 7d24b5f

File tree

1 file changed

+139
-114
lines changed

1 file changed

+139
-114
lines changed
 

‎packages/vite/src/node/plugins/reporter.ts

+139-114
Original file line numberDiff line numberDiff line change
@@ -4,71 +4,58 @@ import { promisify } from 'node:util'
44
import colors from 'picocolors'
55
import type { Plugin } from 'rollup'
66
import type { ResolvedConfig } from '../config'
7-
import { normalizePath } from '../utils'
7+
import { isDefined, normalizePath } from '../utils'
88
import { LogLevels } from '../logger'
99

10-
const enum WriteType {
11-
JS,
12-
CSS,
13-
ASSET,
14-
HTML,
15-
SOURCE_MAP
16-
}
17-
18-
const writeColors = {
19-
[WriteType.JS]: colors.cyan,
20-
[WriteType.CSS]: colors.magenta,
21-
[WriteType.ASSET]: colors.green,
22-
[WriteType.HTML]: colors.blue,
23-
[WriteType.SOURCE_MAP]: colors.gray
10+
const groups = [
11+
{ name: 'Assets', color: colors.green },
12+
{ name: 'CSS', color: colors.magenta },
13+
{ name: 'JS', color: colors.cyan }
14+
]
15+
type LogEntry = {
16+
name: string
17+
group: typeof groups[number]['name']
18+
size: number
19+
compressedSize: number | null
20+
mapSize: number | null
2421
}
2522

2623
export function buildReporterPlugin(config: ResolvedConfig): Plugin {
2724
const compress = promisify(gzip)
2825
const chunkLimit = config.build.chunkSizeWarningLimit
2926

30-
function isLarge(code: string | Uint8Array): boolean {
31-
// bail out on particularly large chunks
32-
return code.length / 1000 > chunkLimit
33-
}
34-
35-
async function getCompressedSize(code: string | Uint8Array): Promise<string> {
36-
if (config.build.ssr || !config.build.reportCompressedSize) {
37-
return ''
38-
}
39-
return ` / gzip: ${displaySize(
40-
(await compress(typeof code === 'string' ? code : Buffer.from(code)))
41-
.length / 1000
42-
)}`
43-
}
44-
45-
function printFileInfo(
46-
filePath: string,
47-
content: string | Uint8Array,
48-
type: WriteType,
49-
maxLength: number,
50-
outDir = config.build.outDir,
51-
compressedSize = ''
52-
) {
53-
outDir =
54-
normalizePath(
55-
path.relative(config.root, path.resolve(config.root, outDir))
56-
) + '/'
57-
const kB = content.length / 1000
58-
const sizeColor = kB > chunkLimit ? colors.yellow : colors.dim
59-
config.logger.info(
60-
`${colors.gray(colors.white(colors.dim(outDir)))}${writeColors[type](
61-
filePath.padEnd(maxLength + 2)
62-
)} ${sizeColor(`${displaySize(kB)}${compressedSize}`)}`
63-
)
64-
}
65-
6627
const tty = process.stdout.isTTY && !process.env.CI
6728
const shouldLogInfo = LogLevels[config.logLevel || 'info'] >= LogLevels.info
6829
let hasTransformed = false
6930
let hasRenderedChunk = false
31+
let hasCompressChunk = false
7032
let transformedCount = 0
7133
let chunkCount = 0
34+
let compressedCount = 0
35+
36+
async function getCompressedSize(
37+
code: string | Uint8Array
38+
): Promise<number | null> {
39+
if (config.build.ssr || !config.build.reportCompressedSize) {
40+
return null
41+
}
42+
if (shouldLogInfo && !hasCompressChunk) {
43+
if (!tty) {
44+
config.logger.info('computing gzip size...')
45+
} else {
46+
writeLine('computing gzip size (0)...')
47+
}
48+
hasCompressChunk = true
49+
}
50+
const compressed = await compress(
51+
typeof code === 'string' ? code : Buffer.from(code)
52+
)
53+
compressedCount++
54+
if (shouldLogInfo && tty) {
55+
writeLine(`computing gzip size (${compressedCount})...`)
56+
}
57+
return compressed.length
58+
}
7259

7360
const logTransform = throttle((id: string) => {
7461
writeLine(
@@ -111,6 +98,7 @@ export function buildReporterPlugin(config: ResolvedConfig): Plugin {
11198

11299
renderStart() {
113100
chunkCount = 0
101+
compressedCount = 0
114102
},
115103

116104
renderChunk() {
@@ -129,78 +117,111 @@ export function buildReporterPlugin(config: ResolvedConfig): Plugin {
129117
},
130118

131119
generateBundle() {
132-
if (shouldLogInfo && tty) {
133-
process.stdout.clearLine(0)
134-
process.stdout.cursorTo(0)
135-
}
120+
if (shouldLogInfo && tty) clearLine()
136121
},
137122

138123
async writeBundle({ dir: outDir }, output) {
139124
let hasLargeChunks = false
140125

141126
if (shouldLogInfo) {
127+
const entries = (
128+
await Promise.all(
129+
Object.values(output).map(
130+
async (chunk): Promise<LogEntry | null> => {
131+
if (chunk.type === 'chunk') {
132+
return {
133+
name: chunk.fileName,
134+
group: 'JS',
135+
size: chunk.code.length,
136+
compressedSize: await getCompressedSize(chunk.code),
137+
mapSize: chunk.map ? chunk.map.toString().length : null
138+
}
139+
} else {
140+
if (chunk.fileName.endsWith('.map')) return null
141+
const isCSS = chunk.fileName.endsWith('.css')
142+
return {
143+
name: chunk.fileName,
144+
group: isCSS ? 'CSS' : 'Assets',
145+
size: chunk.source.length,
146+
mapSize: null, // Rollup doesn't support CSS maps?
147+
compressedSize: isCSS
148+
? await getCompressedSize(chunk.source)
149+
: null
150+
}
151+
}
152+
}
153+
)
154+
)
155+
).filter(isDefined)
156+
if (tty) clearLine()
157+
142158
let longest = 0
143-
for (const file in output) {
144-
const l = output[file].fileName.length
145-
if (l > longest) longest = l
159+
let biggestSize = 0
160+
let biggestMap = 0
161+
let biggestCompressSize = 0
162+
for (const entry of entries) {
163+
if (entry.name.length > longest) longest = entry.name.length
164+
if (entry.size > biggestSize) biggestSize = entry.size
165+
if (entry.mapSize && entry.mapSize > biggestMap) {
166+
biggestMap = entry.mapSize
167+
}
168+
if (
169+
entry.compressedSize &&
170+
entry.compressedSize > biggestCompressSize
171+
) {
172+
biggestCompressSize = entry.compressedSize
173+
}
146174
}
147175

148-
// large chunks are deferred to be logged at the end so they are more
149-
// visible.
150-
const deferredLogs: (() => void)[] = []
151-
152-
await Promise.all(
153-
Object.keys(output).map(async (file) => {
154-
const chunk = output[file]
155-
if (chunk.type === 'chunk') {
156-
const log = async () => {
157-
printFileInfo(
158-
chunk.fileName,
159-
chunk.code,
160-
WriteType.JS,
161-
longest,
162-
outDir,
163-
await getCompressedSize(chunk.code)
176+
const sizePad = displaySize(biggestSize).length
177+
const mapPad = displaySize(biggestMap).length
178+
const compressPad = displaySize(biggestCompressSize).length
179+
180+
const relativeOutDir = normalizePath(
181+
path.relative(
182+
config.root,
183+
path.resolve(config.root, outDir ?? config.build.outDir)
184+
)
185+
)
186+
const assetsDir = `${config.build.assetsDir}/`
187+
188+
for (const group of groups) {
189+
const filtered = entries.filter((e) => e.group === group.name)
190+
if (!filtered.length) continue
191+
for (const entry of filtered.sort((a, z) => a.size - z.size)) {
192+
const isLarge =
193+
group.name === 'JS' && entry.size / 1000 > chunkLimit
194+
if (isLarge) hasLargeChunks = true
195+
const sizeColor = isLarge ? colors.yellow : colors.dim
196+
let log = colors.dim(relativeOutDir + '/')
197+
log += entry.name.startsWith(assetsDir)
198+
? colors.dim(assetsDir) +
199+
group.color(
200+
entry.name
201+
.slice(assetsDir.length)
202+
.padEnd(longest + 2 - assetsDir.length)
164203
)
165-
if (chunk.map) {
166-
printFileInfo(
167-
chunk.fileName + '.map',
168-
chunk.map.toString(),
169-
WriteType.SOURCE_MAP,
170-
longest,
171-
outDir
172-
)
173-
}
174-
}
175-
if (isLarge(chunk.code)) {
176-
hasLargeChunks = true
177-
deferredLogs.push(log)
178-
} else {
179-
await log()
180-
}
181-
} else if (chunk.source) {
182-
const isCSS = chunk.fileName.endsWith('.css')
183-
const isMap = chunk.fileName.endsWith('.js.map')
184-
printFileInfo(
185-
chunk.fileName,
186-
chunk.source,
187-
isCSS
188-
? WriteType.CSS
189-
: isMap
190-
? WriteType.SOURCE_MAP
191-
: WriteType.ASSET,
192-
longest,
193-
outDir,
194-
isCSS ? await getCompressedSize(chunk.source) : undefined
204+
: group.color(entry.name.padEnd(longest + 2))
205+
log += colors.bold(
206+
sizeColor(displaySize(entry.size).padStart(sizePad))
207+
)
208+
if (entry.compressedSize) {
209+
log += colors.dim(
210+
` │ gzip: ${displaySize(entry.compressedSize).padStart(
211+
compressPad
212+
)}`
195213
)
196214
}
197-
})
198-
)
199-
200-
await Promise.all(deferredLogs.map((l) => l()))
215+
if (entry.mapSize) {
216+
log += colors.dim(
217+
` │ map: ${displaySize(entry.mapSize).padStart(mapPad)}`
218+
)
219+
}
220+
config.logger.info(log)
221+
}
222+
}
201223
} else {
202-
hasLargeChunks = Object.keys(output).some((file) => {
203-
const chunk = output[file]
224+
hasLargeChunks = Object.values(output).some((chunk) => {
204225
return chunk.type === 'chunk' && chunk.code.length / 1000 > chunkLimit
205226
})
206227
}
@@ -225,15 +246,19 @@ export function buildReporterPlugin(config: ResolvedConfig): Plugin {
225246
}
226247

227248
function writeLine(output: string) {
228-
process.stdout.clearLine(0)
229-
process.stdout.cursorTo(0)
249+
clearLine()
230250
if (output.length < process.stdout.columns) {
231251
process.stdout.write(output)
232252
} else {
233253
process.stdout.write(output.substring(0, process.stdout.columns - 1))
234254
}
235255
}
236256

257+
function clearLine() {
258+
process.stdout.clearLine(0)
259+
process.stdout.cursorTo(0)
260+
}
261+
237262
function throttle(fn: Function) {
238263
let timerHandle: NodeJS.Timeout | null = null
239264
return (...args: any[]) => {
@@ -245,8 +270,8 @@ function throttle(fn: Function) {
245270
}
246271
}
247272

248-
function displaySize(kB: number) {
249-
return `${kB.toLocaleString('en', {
273+
function displaySize(bytes: number) {
274+
return `${(bytes / 1000).toLocaleString('en', {
250275
maximumFractionDigits: 2,
251276
minimumFractionDigits: 2
252277
})} kB`

0 commit comments

Comments
 (0)
Please sign in to comment.