Skip to content

Commit 50c4980

Browse files
committed
refactor(client): inline ansi stripping and drop the strip-ansi dependency (#2350)
The client runtime runs in the browser, so Node's util.stripVTControlCharacters is not an option. Inline the ansi-regex pattern (verified against strip-ansi@6 output) in a small util instead of shipping the dependency. Ref webpack/webpack-hot-middleware#465 Ref webpack/webpack-hot-middleware#474
1 parent 60b9653 commit 50c4980

6 files changed

Lines changed: 71 additions & 7 deletions

File tree

client-src/index.js

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,9 @@
11
/* global __resourceQuery, __webpack_public_path__ */
22

3-
import stripAnsi from "strip-ansi";
4-
53
import configureOverlay from "./overlay.js";
64
import applyUpdate from "./process-update.js";
75
import { log, setLogLevel } from "./utils/log.js";
6+
import stripAnsi from "./utils/strip-ansi.js";
87

98
/** @typedef {import("./utils/log.js").LogLevel} LogLevel */
109

client-src/utils/strip-ansi.js

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
// Matches ANSI escape sequences. Ported from ansi-regex
2+
// (https://github.com/chalk/ansi-regex), inlined so the browser client does
3+
// not need the strip-ansi dependency.
4+
5+
// Valid string terminator sequences are BEL, ESC\, and 0x9c
6+
const ST = "(?:\\u0007|\\u001B\\u005C|\\u009C)";
7+
8+
// OSC sequences only: ESC ] ... ST (non-greedy until the first ST)
9+
const OSC = `(?:\\u001B\\][\\s\\S]*?${ST})`;
10+
11+
// CSI and related: ESC/C1, optional intermediates, optional params (supports ; and :) then final byte
12+
const CSI =
13+
"[\\u001B\\u009B][[\\]()#;?]*(?:\\d{1,4}(?:[;:]\\d{0,4})*)?[\\dA-PR-TZcf-nq-uy=><~]";
14+
15+
const ANSI_REGEX = new RegExp(`${OSC}|${CSI}`, "g");
16+
17+
/**
18+
* @param {string} string string possibly containing ANSI escape sequences
19+
* @returns {string} the string without ANSI escape sequences
20+
*/
21+
export default function stripAnsi(string) {
22+
return string.replace(ANSI_REGEX, "");
23+
}

cspell.config.json

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,8 @@
1616
"eslintcache",
1717
"esmodules",
1818
"noopener",
19-
"noreferrer"
19+
"noreferrer",
20+
"mred",
21+
"mbold"
2022
]
2123
}

package-lock.json

Lines changed: 3 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -62,8 +62,7 @@
6262
"memfs": "^4.56.10",
6363
"mime-types": "^3.0.2",
6464
"range-parser": "^1.2.1",
65-
"schema-utils": "^4.3.3",
66-
"strip-ansi": "^6.0.1"
65+
"schema-utils": "^4.3.3"
6766
},
6867
"devDependencies": {
6968
"@babel/cli": "^7.16.7",

test/strip-ansi.test.js

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
import stripAnsi from "../client-src/utils/strip-ansi";
2+
3+
const ESC = String.fromCharCode(27);
4+
const BEL = String.fromCharCode(7);
5+
6+
describe("stripAnsi", () => {
7+
it("strips color sequences", () => {
8+
expect(stripAnsi(`${ESC}[31mred${ESC}[39m plain`)).toBe("red plain");
9+
});
10+
11+
it("strips style and background sequences", () => {
12+
expect(stripAnsi(`${ESC}[1m${ESC}[42mbold-bg${ESC}[0m`)).toBe("bold-bg");
13+
});
14+
15+
it("strips 256-color sequences", () => {
16+
expect(stripAnsi(`${ESC}[38;5;196mred${ESC}[0m`)).toBe("red");
17+
});
18+
19+
it("strips cursor and erase sequences", () => {
20+
expect(stripAnsi(`${ESC}[2K${ESC}[1A${ESC}[2K${ESC}[G`)).toBe("");
21+
});
22+
23+
it("strips terminal hyperlinks", () => {
24+
expect(
25+
stripAnsi(`${ESC}]8;;https://example.com${BEL}link${ESC}]8;;${BEL}`),
26+
).toBe("link");
27+
});
28+
29+
it("keeps plain strings untouched", () => {
30+
expect(stripAnsi("no ansi at all")).toBe("no ansi at all");
31+
});
32+
33+
it("strips ansi from a webpack-style error line", () => {
34+
expect(
35+
stripAnsi(
36+
`webpack ${ESC}[1m${ESC}[31mERROR${ESC}[39m${ESC}[22m in ./src/index.js 3:10`,
37+
),
38+
).toBe("webpack ERROR in ./src/index.js 3:10");
39+
});
40+
});

0 commit comments

Comments
 (0)