Skip to content

Commit e21266f

Browse files
committed
feat(webpack-cli): support non-JS config formats via interpret (json5, yaml)
Gate the interpret/rechoir lookup on `interpret.extensions` instead of `interpret.jsVariants` so non-JavaScript config formats such as `.json5`, `.yaml` and `.yml` are loadable, in addition to the JS variants that already worked. This also removes the now-redundant `.cts` special case, since `.cts` is present in `interpret.extensions`. The parsers for these formats are intentionally not shipped by webpack-cli. `rechoir` loads the matching registration module (e.g. `json5/lib/register`, `yaml-hook/register`) on demand only when the developer has installed it; when it is missing, webpack-cli reports which package to install. Tests: - json5/yaml/yml configs load successfully when the parser is installed (`json5` and `yaml-hook` are added as dev dependencies for the test run). - toml config reports the `Please install` guidance because its parser (`toml-require`) is intentionally not installed. Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]> Claude-Session: https://claude.ai/code/session_013AushhUmU7F2dKWRE94XCg
1 parent eff73c1 commit e21266f

7 files changed

Lines changed: 113 additions & 55 deletions

File tree

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
---
2+
"webpack-cli": minor
3+
---
4+
5+
feat: support non-JavaScript config formats (e.g. `.json5`, `.yaml`/`.yml`) via `interpret`
6+
7+
webpack-cli now recognizes every config extension known to `interpret`, not only the JavaScript variants. The parsers for these formats are intentionally not shipped — install the matching package yourself (for example `json5` or `yaml-hook`) and it will be picked up automatically. When the parser is missing, webpack-cli reports which package to install instead of failing with an opaque error.

package-lock.json

Lines changed: 39 additions & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,7 @@
7070
"get-port": "^7.1.0",
7171
"husky": "^9.1.4",
7272
"jest": "^30.2.0",
73+
"json5": "^2.2.3",
7374
"lint-staged": "^17.0.3",
7475
"mini-css-extract-plugin": "^2.6.1",
7576
"prettier": "^3.6.0",
@@ -83,7 +84,8 @@
8384
"typescript": "^6.0.2",
8485
"webpack": "^5.106.1",
8586
"webpack-bundle-analyzer": "^5.1.0",
86-
"webpack-dev-server": "^5.1.0"
87+
"webpack-dev-server": "^5.1.0",
88+
"yaml-hook": "^1.0.0"
8789
},
8890
"peerDependencies": {
8991
"webpack": "5.x.x"

packages/webpack-cli/src/webpack-cli.ts

Lines changed: 9 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -2592,14 +2592,17 @@ class WebpackCLI {
25922592

25932593
// Fallback logic when we can't use `import(...)`
25942594
if (loadingError) {
2595-
const { jsVariants, extensions } = await import("interpret");
2595+
const { extensions } = await import("interpret");
25962596
const ext = path.extname(configPath).toLowerCase();
25972597

2598-
let interpreted = Object.keys(jsVariants).find((variant) => variant === ext);
2599-
2600-
if (!interpreted && ext.endsWith(".cts")) {
2601-
interpreted = jsVariants[".ts"] as string;
2602-
}
2598+
// Match against every extension `interpret` knows about (not just the
2599+
// JavaScript variants) so non-JS formats such as `.json5`, `.yaml` and
2600+
// `.yml` are loadable too. webpack-cli intentionally does not ship the
2601+
// parsers for these formats; `rechoir` loads the matching registration
2602+
// module (e.g. `json5/lib/register`, `yaml-hook/register`) only when a
2603+
// developer has installed it themselves, otherwise it reports which
2604+
// package to install below.
2605+
const interpreted = Object.keys(extensions).find((variant) => variant === ext);
26032606

26042607
if (interpreted && !disableInterpret) {
26052608
const rechoir: Rechoir = (await import("rechoir")).default;
Lines changed: 21 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,22 +1,28 @@
1+
const { existsSync } = require("node:fs");
2+
const { resolve } = require("node:path");
3+
14
const { run } = require("../../../utils/test-utils");
25

3-
// Regression test for an unsupported config format.
4-
//
5-
// `interpret` lists `.json5` (via `json5/lib/register`), but webpack-cli only
6-
// runs `interpret`/`rechoir` for extensions present in `interpret.jsVariants`,
7-
// which contains JS variants only (`.ts`, `.coffee`, `.babel.js`, ...) and not
8-
// data formats such as `.json5`, `.yaml` or `.toml`. As a result a `.json5`
9-
// config is not loadable today, even when `json5` is installed.
10-
//
11-
// This test locks in that behavior so the planned move away from `interpret`
12-
// (towards Node.js' built-in loaders/`import`) does not silently change which
13-
// formats are accepted without a deliberate decision.
6+
// webpack-cli supports JSON5 config files through `interpret`/`rechoir`, but it
7+
// does not ship the parser. The `json5` package has to be installed by the user
8+
// (here it is available as a dev dependency) and `json5/lib/register` is loaded
9+
// on demand to register the `.json5` extension.
1410
describe("webpack cli", () => {
15-
it("should not support JSON5 file as flag", async () => {
11+
it("should support JSON5 file as flag", async () => {
1612
const { exitCode, stderr, stdout } = await run(__dirname, ["-c", "webpack.config.json5"]);
1713

18-
expect(exitCode).toBe(2);
19-
expect(stderr).toContain("Failed to load 'webpack.config.json5' config");
20-
expect(stdout).toBeFalsy();
14+
expect(exitCode).toBe(0);
15+
expect(stderr).toBeFalsy();
16+
expect(stdout).toBeTruthy();
17+
expect(existsSync(resolve(__dirname, "dist/foo.bundle.js"))).toBeTruthy();
18+
});
19+
20+
it("should load JSON5 file by default", async () => {
21+
const { exitCode, stderr, stdout } = await run(__dirname, []);
22+
23+
expect(exitCode).toBe(0);
24+
expect(stderr).toBeFalsy();
25+
expect(stdout).toBeTruthy();
26+
expect(existsSync(resolve(__dirname, "dist/foo.bundle.js"))).toBeTruthy();
2127
});
2228
});
Lines changed: 8 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,22 +1,17 @@
11
const { run } = require("../../../utils/test-utils");
22

3-
// Regression test for an unsupported config format.
4-
//
5-
// `interpret` lists `.toml` (via `toml-require`), but webpack-cli only runs
6-
// `interpret`/`rechoir` for extensions present in `interpret.jsVariants`, which
7-
// contains JS variants only (`.ts`, `.coffee`, `.babel.js`, ...) and not data
8-
// formats such as `.toml`, `.yaml` or `.json5`. As a result a `.toml` config is
9-
// not loadable today.
10-
//
11-
// This test locks in that behavior so the planned move away from `interpret`
12-
// (towards Node.js' built-in loaders/`import`) does not silently change which
13-
// formats are accepted without a deliberate decision.
3+
// webpack-cli recognizes TOML config files through `interpret`/`rechoir` but,
4+
// like the other non-JS formats, it does not ship the parser. When the required
5+
// `toml-require` package is not installed, webpack-cli should not crash silently
6+
// — it should tell the user which package to install. This pins that behavior
7+
// (the parser is intentionally absent from the project's dependencies).
148
describe("webpack cli", () => {
15-
it("should not support TOML file as flag", async () => {
9+
it("should ask to install the parser for a TOML file when it is missing", async () => {
1610
const { exitCode, stderr, stdout } = await run(__dirname, ["-c", "webpack.config.toml"]);
1711

1812
expect(exitCode).toBe(2);
19-
expect(stderr).toContain("Failed to load 'webpack.config.toml' config");
13+
expect(stderr).toContain("toml-require");
14+
expect(stderr).toContain("Please install one of them");
2015
expect(stdout).toBeFalsy();
2116
});
2217
});
Lines changed: 26 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1,30 +1,37 @@
1+
const { existsSync } = require("node:fs");
2+
const { resolve } = require("node:path");
3+
14
const { run } = require("../../../utils/test-utils");
25

3-
// Regression test for an unsupported config format.
4-
//
5-
// `interpret` lists `.yaml`/`.yml` (via `yaml-hook/register`), but webpack-cli
6-
// only runs `interpret`/`rechoir` for extensions present in
7-
// `interpret.jsVariants`, which contains JS variants only (`.ts`, `.coffee`,
8-
// `.babel.js`, ...) and not data formats such as `.yaml`, `.yml`, `.json5` or
9-
// `.toml`. As a result YAML configs are not loadable today.
10-
//
11-
// This test locks in that behavior so the planned move away from `interpret`
12-
// (towards Node.js' built-in loaders/`import`) does not silently change which
13-
// formats are accepted without a deliberate decision.
6+
// webpack-cli supports YAML config files through `interpret`/`rechoir`, but it
7+
// does not ship the parser. The `yaml-hook` package has to be installed by the
8+
// user (here it is available as a dev dependency) and `yaml-hook/register` is
9+
// loaded on demand to register the `.yaml`/`.yml` extensions.
1410
describe("webpack cli", () => {
15-
it("should not support YAML file as flag", async () => {
11+
it("should support YAML file as flag", async () => {
1612
const { exitCode, stderr, stdout } = await run(__dirname, ["-c", "webpack.config.yaml"]);
1713

18-
expect(exitCode).toBe(2);
19-
expect(stderr).toContain("Failed to load 'webpack.config.yaml' config");
20-
expect(stdout).toBeFalsy();
14+
expect(exitCode).toBe(0);
15+
expect(stderr).toBeFalsy();
16+
expect(stdout).toBeTruthy();
17+
expect(existsSync(resolve(__dirname, "dist/foo.bundle.js"))).toBeTruthy();
2118
});
2219

23-
it("should not support YML file as flag", async () => {
20+
it("should support YML file as flag", async () => {
2421
const { exitCode, stderr, stdout } = await run(__dirname, ["-c", "webpack.config.yml"]);
2522

26-
expect(exitCode).toBe(2);
27-
expect(stderr).toContain("Failed to load 'webpack.config.yml' config");
28-
expect(stdout).toBeFalsy();
23+
expect(exitCode).toBe(0);
24+
expect(stderr).toBeFalsy();
25+
expect(stdout).toBeTruthy();
26+
expect(existsSync(resolve(__dirname, "dist/foo.bundle.js"))).toBeTruthy();
27+
});
28+
29+
it("should load YAML file by default", async () => {
30+
const { exitCode, stderr, stdout } = await run(__dirname, []);
31+
32+
expect(exitCode).toBe(0);
33+
expect(stderr).toBeFalsy();
34+
expect(stdout).toBeTruthy();
35+
expect(existsSync(resolve(__dirname, "dist/foo.bundle.js"))).toBeTruthy();
2936
});
3037
});

0 commit comments

Comments
 (0)