Skip to content

Commit a141fdc

Browse files
authored
feat: add option to disable stage messages (#356)
* feat: add option to disable stage messages * build: support optional chaining * style: lint fix * test: no stage messages * test: unname parametrized snapshot
1 parent f907061 commit a141fdc

File tree

10 files changed

+165
-42
lines changed

10 files changed

+165
-42
lines changed

README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,7 @@ module.exports = {
5353
buildStart: () => execSync("echo 'hello'"),
5454
buildEnd: () => execSync("echo 'bye bye'"),
5555
},
56+
stageMessages: null, // to disable stage messages
5657
}),
5758
],
5859
};

package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -87,6 +87,7 @@
8787
"devDependencies": {
8888
"@babel/cli": "^7.8.4",
8989
"@babel/core": "^7.9.0",
90+
"@babel/plugin-proposal-optional-chaining": "^7.9.0",
9091
"@babel/preset-env": "^7.9.5",
9192
"@babel/preset-typescript": "^7.9.0",
9293
"@commitlint/cli": "^8.3.5",

src/constants.ts

Lines changed: 15 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,30 +1,29 @@
1-
const enterStageMessage = (m: TemplateStringsArray) =>
2-
`\n------${String(m) ? `\n${m}` : ""}`;
1+
const stageMessage = (m: TemplateStringsArray) => (m ? `\n${String(m)}` : "");
32

4-
const endStageMessage = (m: TemplateStringsArray) =>
5-
`${String(m) ? `${m}\n` : ""}------`;
6-
7-
export const stageMessages: Record<Stage, { enter?: string; exit?: string }> = {
3+
export const defaultStageMessages: Record<
4+
Stage,
5+
{ enter?: string; exit?: string }
6+
> = {
87
buildEnd: {
9-
enter: enterStageMessage`🌇 Build exiting...`,
10-
exit: endStageMessage``,
8+
enter: stageMessage`🌇 Build exiting 🌇`,
9+
exit: stageMessage`🌇 🌇 🌇 🌇 🌇`,
1110
},
1211
buildError: {
13-
enter: enterStageMessage`🚒 Build failed.`,
12+
enter: stageMessage`🚒 Build failed 🚒`,
1413
},
1514
buildStart: {
16-
enter: enterStageMessage`🌅 Build starting...`,
17-
exit: endStageMessage``,
15+
enter: stageMessage`🌅 Build starting 🌅`,
16+
exit: stageMessage`🌅 🌅 🌅 🌅 🌅`,
1817
},
1918
compileEnd: {
20-
enter: enterStageMessage`⌛ Code compiled.`,
21-
exit: endStageMessage``,
19+
enter: stageMessage`⌛ Code compiled`,
20+
exit: stageMessage`⌛ ⌛ ⌛ ⌛ ⌛`,
2221
},
2322
compileStart: {
24-
enter: enterStageMessage`⏳ Code compiling...`,
25-
exit: endStageMessage``,
23+
enter: stageMessage`⏳ Code compiling`,
24+
exit: stageMessage`⏳ ⏳ ⏳ ⏳ ⏳`,
2625
},
2726
interrupt: {
28-
enter: enterStageMessage`🚧 Build interrupted.`,
27+
enter: stageMessage`🚧 Build interrupted 🚧`,
2928
},
3029
};

src/globals.d.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,9 +7,11 @@ type Stage =
77
| "compileEnd"
88
| "compileStart"
99
| "interrupt";
10-
type StageListeners = { [key in Stage]: Listener };
10+
type StageListeners = Record<Stage, Listener>;
11+
type StageMessages = Record<Stage, { enter?: string; exit?: string }>;
1112

1213
interface Options {
1314
name: string;
15+
stageMessages?: Partial<StageMessages>;
1416
listeners: Partial<StageListeners>;
1517
}

src/index.ts

Lines changed: 36 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,11 @@
11
import { Compiler } from "webpack";
22

3-
import { stageMessages } from "./constants";
3+
import { logger } from "./logger";
4+
import { defaultStageMessages } from "./constants";
45

5-
const log = console.log.bind(console); // eslint-disable-line no-console
66
const defaultListeners: Partial<StageListeners> = {
77
buildError: (e: Error) => {
8-
console.error(e); // eslint-disable-line no-console
8+
logger.error(e); // eslint-disable-line no-console
99
process.exit(1);
1010
},
1111
interrupt: () => {
@@ -20,30 +20,51 @@ export class WebpackCompilerPlugin {
2020
this.options = this.validate(options);
2121
}
2222

23-
private validate(options: Options) {
24-
const validOptions: Options = { ...options, listeners: {} };
25-
for (const stage of Object.keys(stageMessages) as Stage[]) {
26-
const listener = options.listeners[stage];
23+
private validate({
24+
stageMessages = defaultStageMessages,
25+
...options
26+
}: Options) {
27+
const validOptions: Options = {
28+
...options,
29+
stageMessages,
30+
};
31+
const listeners = { ...defaultListeners, ...options.listeners };
32+
for (const stage of Object.keys(listeners) as Stage[]) {
33+
const enterMessage = stageMessages?.[stage]?.enter;
34+
const exitMessage = stageMessages?.[stage]?.exit;
35+
const listener = listeners[stage];
2736
const validListener =
2837
typeof listener === "function"
2938
? listener
3039
: defaultListeners[stage];
3140
validOptions.listeners[stage] = async (...args) => {
32-
log(stageMessages[stage].enter);
41+
enterMessage && logger.info(enterMessage);
3342
validListener && validListener(...args);
34-
log(stageMessages[stage].exit);
43+
exitMessage && logger.info(exitMessage);
3544
};
3645
}
3746
return validOptions;
3847
}
3948

4049
public apply(compiler: Compiler) {
4150
const { name, listeners } = this.options;
42-
compiler.hooks.afterPlugins.tap(name, listeners.buildStart);
43-
process.on("exit", listeners.buildEnd);
44-
process.on("uncaughtException", listeners.buildError);
45-
compiler.hooks.compilation.tap(name, listeners.compileStart);
46-
compiler.hooks.done.tap(name, listeners.compileEnd);
47-
process.on("SIGINT", listeners.interrupt);
51+
if (listeners.buildStart) {
52+
compiler.hooks.afterPlugins.tap(name, listeners.buildStart);
53+
}
54+
if (listeners.buildEnd) {
55+
process.on("exit", listeners.buildEnd);
56+
}
57+
if (listeners.buildError) {
58+
process.on("uncaughtException", listeners.buildError);
59+
}
60+
if (listeners.compileStart) {
61+
compiler.hooks.compilation.tap(name, listeners.compileStart);
62+
}
63+
if (listeners.compileEnd) {
64+
compiler.hooks.done.tap(name, listeners.compileEnd);
65+
}
66+
if (listeners.interrupt) {
67+
process.on("SIGINT", listeners.interrupt);
68+
}
4869
}
4970
}

src/logger.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
export const logger = console;

tests/__snapshots__/index.test.ts.snap

Lines changed: 61 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,14 @@ Array [
88
]
99
`;
1010

11-
exports[`default listeners loads with default buildError listener: console log 1`] = `Array []`;
11+
exports[`default listeners loads with default buildError listener: console log 1`] = `
12+
Array [
13+
Array [
14+
"
15+
🚒 Build failed 🚒",
16+
],
17+
]
18+
`;
1219

1320
exports[`default listeners loads with default buildError listener: process exit 1`] = `
1421
Array [
@@ -20,7 +27,14 @@ Array [
2027

2128
exports[`default listeners loads with default interrupt listener: console error 1`] = `Array []`;
2229

23-
exports[`default listeners loads with default interrupt listener: console log 1`] = `Array []`;
30+
exports[`default listeners loads with default interrupt listener: console log 1`] = `
31+
Array [
32+
Array [
33+
"
34+
🚧 Build interrupted 🚧",
35+
],
36+
]
37+
`;
2438

2539
exports[`default listeners loads with default interrupt listener: process exit 1`] = `
2640
Array [
@@ -29,3 +43,48 @@ Array [
2943
],
3044
]
3145
`;
46+
47+
exports[`stage messages does not use default state messages buildStart 1`] = `Array []`;
48+
49+
exports[`stage messages does not use default state messages compileEnd 1`] = `Array []`;
50+
51+
exports[`stage messages does not use default state messages compileStart 1`] = `Array []`;
52+
53+
exports[`stage messages uses default state messages buildStart 1`] = `
54+
Array [
55+
Array [
56+
"
57+
🌅 Build starting 🌅",
58+
],
59+
Array [
60+
"
61+
🌅 🌅 🌅 🌅 🌅",
62+
],
63+
]
64+
`;
65+
66+
exports[`stage messages uses default state messages compileEnd 1`] = `
67+
Array [
68+
Array [
69+
"
70+
⌛ Code compiled ⌛",
71+
],
72+
Array [
73+
"
74+
⌛ ⌛ ⌛ ⌛ ⌛",
75+
],
76+
]
77+
`;
78+
79+
exports[`stage messages uses default state messages compileStart 1`] = `
80+
Array [
81+
Array [
82+
"
83+
⏳ Code compiling ⏳",
84+
],
85+
Array [
86+
"
87+
⏳ ⏳ ⏳ ⏳ ⏳",
88+
],
89+
]
90+
`;

tests/index.test.ts

Lines changed: 45 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,14 @@
11
import { Compiler } from "webpack";
22
import { WebpackCompilerPlugin } from "index";
3+
import { logger } from "logger";
4+
import { listeners } from "cluster";
35

46
const processExitSpy = jest
57
.spyOn(process, "exit")
68
.mockImplementation(() => undefined as never);
79

8-
const consoleLogSpy = jest.spyOn(console, "log");
9-
const consoleErrorSpy = jest.spyOn(console, "error");
10+
const consoleLogSpy = jest.spyOn(logger, "info");
11+
const consoleErrorSpy = jest.spyOn(logger, "error");
1012

1113
const mockCompiler = {
1214
hooks: {
@@ -16,10 +18,17 @@ const mockCompiler = {
1618
},
1719
};
1820

19-
const newPlugin = (listeners: Partial<StageListeners> = {}) => {
21+
const newPlugin = ({
22+
listeners = {},
23+
stageMessages,
24+
}: {
25+
listeners?: Partial<StageListeners>;
26+
stageMessages?: Partial<StageMessages>;
27+
}) => {
2028
const plugin = new WebpackCompilerPlugin({
2129
name: "webpack-compiler-plugin",
2230
listeners,
31+
stageMessages,
2332
});
2433
plugin.apply((mockCompiler as unknown) as Compiler);
2534
return plugin;
@@ -28,7 +37,7 @@ const newPlugin = (listeners: Partial<StageListeners> = {}) => {
2837
afterEach(() => {
2938
jest.clearAllMocks();
3039
const signals = ["exit", "SIGINT", "uncaughtException"];
31-
signals.map(s => process.removeAllListeners(s));
40+
signals.map(process.removeAllListeners.bind(process));
3241
});
3342
afterAll(jest.restoreAllMocks);
3443

@@ -37,7 +46,7 @@ describe("default listeners", () => {
3746
["interrupt", ["SIGINT"]],
3847
["buildError", ["uncaughtException", new Error("Uncaught Error")]],
3948
])("loads with default %s listener", (_, [event, arg]): void => {
40-
newPlugin();
49+
newPlugin({});
4150
process.emit(
4251
event as NodeJS.Signals,
4352
(arg as unknown) as NodeJS.Signals,
@@ -58,7 +67,7 @@ describe("apply listeners", () => {
5867
// eslint-disable-next-line @typescript-eslint/no-explicit-any
5968
(stage: Stage, [event, arg]: [NodeJS.Signals, any]) => {
6069
const mockHandler = jest.fn();
61-
newPlugin({ [stage]: mockHandler });
70+
newPlugin({ listeners: { [stage]: mockHandler } });
6271
process.emit(event, arg);
6372
expect(mockHandler).toHaveBeenCalledTimes(1);
6473
},
@@ -70,7 +79,36 @@ describe("apply listeners", () => {
7079
["compileStart", mockCompiler.hooks.compilation.tap],
7180
])("applies handler for stage %s", (stage, tapFn) => {
7281
const mockHandler = jest.fn();
73-
newPlugin({ [stage as Stage]: mockHandler });
82+
newPlugin({ listeners: { [stage as Stage]: mockHandler } });
7483
expect(tapFn).toHaveBeenCalledTimes(1);
84+
tapFn.mock.calls[0][1]();
85+
expect(mockHandler).toHaveBeenCalledTimes(1);
86+
});
87+
});
88+
89+
describe("stage messages", () => {
90+
it.each([
91+
["buildStart", mockCompiler.hooks.afterPlugins.tap],
92+
["compileEnd", mockCompiler.hooks.done.tap],
93+
["compileStart", mockCompiler.hooks.compilation.tap],
94+
])("uses default state messages %s", (stage, tapFn) => {
95+
const mockHandler = jest.fn();
96+
newPlugin({ listeners: { [stage as Stage]: mockHandler } });
97+
tapFn.mock.calls[0][1]();
98+
expect(consoleLogSpy.mock.calls).toMatchSnapshot();
99+
});
100+
101+
it.each([
102+
["buildStart", mockCompiler.hooks.afterPlugins.tap],
103+
["compileEnd", mockCompiler.hooks.done.tap],
104+
["compileStart", mockCompiler.hooks.compilation.tap],
105+
])("does not use default state messages %s", (stage, tapFn) => {
106+
const mockHandler = jest.fn();
107+
newPlugin({
108+
listeners: { [stage as Stage]: mockHandler },
109+
stageMessages: null,
110+
});
111+
tapFn.mock.calls[0][1]();
112+
expect(consoleLogSpy.mock.calls).toMatchSnapshot();
75113
});
76114
});

tsconfig.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,5 +11,5 @@
1111
"noFallthroughCasesInSwitch": true,
1212
"noImplicitAny": true
1313
},
14-
"exclude": ["node_modules", "lib"]
14+
"exclude": ["node_modules", "lib", "built"]
1515
}

webpack.config.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ const configuration: Configuration = {
1515
loader: "babel-loader",
1616
options: {
1717
presets: ["@babel/preset-typescript"],
18+
plugins: ["@babel/plugin-proposal-optional-chaining"],
1819
},
1920
},
2021
},

0 commit comments

Comments
 (0)