Skip to content

Commit f973d8a

Browse files
authored
feat: progress bars (#1250)
1 parent 0afd84b commit f973d8a

12 files changed

Lines changed: 618 additions & 151 deletions

File tree

packages/repack/src/commands/rspack/Compiler.ts

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,7 @@ export class Compiler {
4040

4141
reporter.process({
4242
issuer: 'DevServer',
43-
message: [{ progress: { value, platform } }],
43+
message: [{ progress: { platform, value } }],
4444
timestamp: Date.now(),
4545
type: 'progress',
4646
});
@@ -200,24 +200,24 @@ export class Compiler {
200200

201201
stats.children?.forEach((childStats) => {
202202
const platform = childStats.name!;
203+
const time = childStats.time!;
203204
this.callPendingResolvers(platform);
204205
this.devServerContext.notifyBuildEnd(platform);
205206
this.devServerContext.broadcastToHmrClients<HMRMessage>({
206207
action: 'ok',
207208
body: { name: platform },
208209
});
210+
this.reporter.process({
211+
issuer: 'DevServer',
212+
message: [{ progress: { platform, time } }],
213+
timestamp: Date.now(),
214+
type: 'progress',
215+
});
209216
});
210217
});
211218
}
212219

213220
start() {
214-
this.reporter.process({
215-
type: 'info',
216-
issuer: 'DevServer',
217-
timestamp: Date.now(),
218-
message: ['Starting build for platforms:', this.platforms.join(', ')],
219-
});
220-
221221
this.compiler.watch(this.watchOptions, (error) => {
222222
if (!error) return;
223223
this.platforms.forEach((platform) => {

packages/repack/src/commands/webpack/Compiler.ts

Lines changed: 17 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -100,6 +100,13 @@ export class Compiler extends EventEmitter {
100100
),
101101
};
102102
this.emit(value.event, { platform, stats: value.stats });
103+
// Emit final progress with timing for this platform
104+
this.reporter.process({
105+
issuer: 'DevServer',
106+
timestamp: Date.now(),
107+
type: 'progress',
108+
message: [{ progress: { platform, time: value.stats.time } }],
109+
});
103110
callPendingResolvers();
104111
} else if (value.event === 'error') {
105112
this.emit(value.event, value.error);
@@ -108,12 +115,16 @@ export class Compiler extends EventEmitter {
108115
const percentage = Math.floor(value.percentage * 100);
109116
sendProgress({ completed: percentage, total: 100 });
110117
});
111-
this.reporter.process({
112-
issuer: 'DevServer',
113-
message: [{ progress: { value: value.percentage, platform } }],
114-
timestamp: Date.now(),
115-
type: 'progress',
116-
});
118+
// skip reporting progress for the final last 1%
119+
// rely on the done event from the `compiler.done` hook
120+
if (value.percentage < 0.99) {
121+
this.reporter.process({
122+
issuer: 'DevServer',
123+
message: [{ progress: { platform, value: value.percentage } }],
124+
timestamp: Date.now(),
125+
type: 'progress',
126+
});
127+
}
117128
} else {
118129
this.isCompilationInProgress[platform] = true;
119130
this.emit(value.event, { platform });

packages/repack/src/logging/compose.ts

Lines changed: 0 additions & 15 deletions
This file was deleted.
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
import type { LogEntry, LogType, Reporter } from './types.js';
2+
3+
export function makeLogEntryFromFastifyLog(data: any): LogEntry {
4+
const { level, time, pid, hostname, ...rest } = data;
5+
6+
const levelToTypeMapping: Record<number, LogType> = {
7+
10: 'debug',
8+
20: 'debug',
9+
30: 'info',
10+
40: 'warn',
11+
50: 'error',
12+
60: 'error',
13+
};
14+
15+
return {
16+
type: levelToTypeMapping[level],
17+
timestamp: time,
18+
issuer: '',
19+
message: [rest],
20+
};
21+
}
22+
23+
export function composeReporters(reporters: Reporter[]): Reporter {
24+
return {
25+
process: (logEntry) => {
26+
reporters.forEach((reporter) => reporter.process(logEntry));
27+
},
28+
flush: () => {
29+
reporters.forEach((reporter) => reporter.flush());
30+
},
31+
stop: () => {
32+
reporters.forEach((reporter) => reporter.stop());
33+
},
34+
};
35+
}
Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,3 @@
1-
export * from './reporters/FileReporter.js';
2-
export * from './reporters/ConsoleReporter.js';
3-
export * from './compose.js';
1+
export * from './helpers.js';
2+
export * from './reporters.js';
43
export * from './types.js';
5-
export * from './makeLogEntryFromFastifyLog.js';
Lines changed: 104 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,104 @@
1+
import * as colorette from 'colorette';
2+
3+
export interface ProgressBarOptions {
4+
width?: number;
5+
platform?: string;
6+
unicode?: boolean;
7+
}
8+
9+
export const IS_SYMBOL_SUPPORTED =
10+
process.platform !== 'win32' ||
11+
Boolean(process.env.CI) ||
12+
process.env.TERM === 'xterm-256color';
13+
14+
function colorizePlatform(text: string, platform?: string): string {
15+
if (!platform) return colorette.green(text);
16+
const p = platform.toLowerCase();
17+
if (p.includes('ios')) return colorette.blue(text);
18+
if (p.includes('android')) return colorette.green(text);
19+
return colorette.green(text);
20+
}
21+
22+
export function renderProgressBar(
23+
percentage: number,
24+
{
25+
width = 16,
26+
platform,
27+
unicode = IS_SYMBOL_SUPPORTED,
28+
}: ProgressBarOptions = {}
29+
): string {
30+
const clamped = Math.max(0, Math.min(100, Math.round(percentage)));
31+
const filled = Math.round((clamped / 100) * width);
32+
const empty = Math.max(0, width - filled);
33+
34+
const fullChar = unicode ? '=' : '#';
35+
const emptyChar = unicode ? '-' : '.';
36+
37+
const filledStrColored = colorizePlatform(fullChar.repeat(filled), platform);
38+
const emptyStr = emptyChar.repeat(empty);
39+
40+
return `[${filledStrColored}${emptyStr}]`;
41+
}
42+
43+
export function colorizePlatformLabel(platform: string, label: string): string {
44+
const p = platform.toLowerCase();
45+
if (p.includes('ios')) return colorette.blue(label);
46+
if (p.includes('android')) return colorette.green(label);
47+
return label;
48+
}
49+
50+
export class Spinner {
51+
private index = 0;
52+
getNext(): string {
53+
const frames = IS_SYMBOL_SUPPORTED
54+
? ['⠋', '⠙', '⠸', '⠴', '⠦', '⠇']
55+
: ['-', '\\', '|', '/'];
56+
const frame = frames[this.index % frames.length];
57+
this.index += 1;
58+
return frame;
59+
}
60+
}
61+
62+
export function formatSecondsOneDecimal(ms: number): string {
63+
return `${(ms / 1000).toFixed(1)}s`;
64+
}
65+
66+
export function formatElapsed(start: number, now: number): string {
67+
const ms = Math.max(0, now - start);
68+
if (ms < 1000) return `${(ms / 1000).toFixed(1)}s`;
69+
if (ms < 60_000) return `${(ms / 1000).toFixed(1)}s`;
70+
const minutes = Math.floor(ms / 60_000);
71+
const seconds = Math.floor((ms % 60_000) / 1000)
72+
.toString()
73+
.padStart(2, '0');
74+
return `${minutes}:${seconds}`;
75+
}
76+
77+
export function buildInProgressMessageParts(
78+
platform: string,
79+
percentage: number,
80+
options: { width?: number; maxPlatformNameWidth?: number } = {}
81+
): [string, string] {
82+
const { width = 16, maxPlatformNameWidth = platform.length } = options;
83+
const bar = renderProgressBar(percentage, { width, platform });
84+
const percentText = `${Math.floor(percentage).toString().padStart(3, ' ')}%`;
85+
const barAndPercent = `${bar}${percentText}`;
86+
const platformPadded = platform.padEnd(maxPlatformNameWidth, ' ');
87+
const platformColored = colorizePlatformLabel(platform, platformPadded);
88+
return [barAndPercent, platformColored];
89+
}
90+
91+
export function buildDoneMessageParts(
92+
platform: string,
93+
timeMs: number,
94+
options: { maxPlatformNameWidth?: number } = {}
95+
): [string, string, string, string] {
96+
const { maxPlatformNameWidth = platform.length } = options;
97+
const platformPadded = platform.padEnd(maxPlatformNameWidth, ' ');
98+
const platformColored = colorizePlatformLabel(platform, platformPadded);
99+
const timeColored = colorizePlatformLabel(
100+
platform,
101+
formatSecondsOneDecimal(timeMs)
102+
);
103+
return ['Compiled', platformColored, 'in', timeColored];
104+
}

0 commit comments

Comments
 (0)