Skip to content

Commit 4b07573

Browse files
fix: support file protocol in configuration options
1 parent 37e4270 commit 4b07573

18 files changed

Lines changed: 174 additions & 13 deletions

.changeset/slow-oranges-repair.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"webpack-cli": patch
3+
---
4+
5+
The `file` protocol for configuration options (`--config`/`--extends`) is supported.

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

Lines changed: 8 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import fs from "node:fs";
22
import path from "node:path";
33
import { type Readable as ReadableType } from "node:stream";
4-
import { pathToFileURL } from "node:url";
4+
import { fileURLToPath, pathToFileURL } from "node:url";
55
import util from "node:util";
66
import { type stringifyChunked as stringifyChunkedType } from "@discoveryjs/json-ext";
77
import {
@@ -2163,12 +2163,14 @@ class WebpackCLI {
21632163
): Promise<{ options: Configuration | MultiConfiguration; path: string }> => {
21642164
let options: LoadableWebpackConfiguration | undefined;
21652165

2166+
const isFileURL = configPath.startsWith("file://");
2167+
21662168
try {
21672169
let loadingError;
21682170

21692171
try {
2170-
// eslint-disable-next-line no-eval
2171-
options = (await eval(`import("${pathToFileURL(configPath)}")`)).default;
2172+
options = // eslint-disable-next-line no-eval
2173+
(await eval(`import("${isFileURL ? configPath : pathToFileURL(configPath)}")`)).default;
21722174
} catch (err) {
21732175
if (this.isValidationError(err) || process.env?.WEBPACK_CLI_FORCE_LOAD_ESM_CONFIG) {
21742176
throw err;
@@ -2209,7 +2211,7 @@ class WebpackCLI {
22092211
}
22102212

22112213
try {
2212-
options = require(configPath);
2214+
options = require(isFileURL ? fileURLToPath(configPath) : configPath);
22132215
} catch (err) {
22142216
if (this.isValidationError(err)) {
22152217
throw err;
@@ -2301,9 +2303,7 @@ class WebpackCLI {
23012303

23022304
if (options.config && options.config.length > 0) {
23032305
const loadedConfigs = await Promise.all(
2304-
options.config.map((configPath: string) =>
2305-
loadConfigByPath(path.resolve(configPath), options.argv),
2306-
),
2306+
options.config.map((configPath: string) => loadConfigByPath(configPath, options.argv)),
23072307
);
23082308

23092309
if (loadedConfigs.length === 1) {
@@ -2406,9 +2406,7 @@ class WebpackCLI {
24062406
delete config.extends;
24072407

24082408
const loadedConfigs = await Promise.all(
2409-
extendsPaths.map((extendsPath) =>
2410-
loadConfigByPath(path.resolve(extendsPath), options.argv),
2411-
),
2409+
extendsPaths.map((extendsPath) => loadConfigByPath(extendsPath, options.argv)),
24122410
);
24132411

24142412
const { merge } = await import("webpack-merge");

test/build/config/basic/basic-config.test.js

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,31 @@
11
"use strict";
22

33
const { resolve } = require("node:path");
4+
const { pathToFileURL } = require("node:url");
45
const { run } = require("../../../utils/test-utils");
56

67
describe("basic config file", () => {
7-
it("should build and not throw error with a basic configuration file", async () => {
8+
it("should build and not throw error with a basic configuration file using relative path", async () => {
9+
const { exitCode, stderr, stdout } = await run(__dirname, ["-c", "webpack.config.js"]);
10+
expect(exitCode).toBe(0);
11+
expect(stderr).toBeFalsy();
12+
expect(stdout).toBeTruthy();
13+
});
14+
15+
it("should build and not throw error with a basic configuration file using absolute path", async () => {
816
const { exitCode, stderr, stdout } = await run(__dirname, [
917
"-c",
1018
resolve(__dirname, "webpack.config.js"),
19+
]);
20+
expect(exitCode).toBe(0);
21+
expect(stderr).toBeFalsy();
22+
expect(stdout).toBeTruthy();
23+
});
24+
25+
it("should build and not throw error with a basic configuration file using file protocol", async () => {
26+
const { exitCode, stderr, stdout } = await run(__dirname, [
27+
"-c",
28+
pathToFileURL(resolve(__dirname, "webpack.config.js")).toString(),
1129
"--output-path",
1230
"./binary",
1331
]);

test/build/extends/extends.test.js

Lines changed: 49 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
"use strict";
22

33
const path = require("node:path");
4+
const { pathToFileURL } = require("node:url");
45
const { run } = require("../../utils/test-utils");
56

67
describe("extends property", () => {
@@ -15,6 +16,38 @@ describe("extends property", () => {
1516
expect(stdout).toContain("mode: 'development'");
1617
});
1718

19+
it("extends a provided webpack config correctly using file protocol", async () => {
20+
const { exitCode, stderr, stdout } = await run(path.resolve(__dirname, "./file-protocol"));
21+
22+
expect(exitCode).toBe(0);
23+
expect(stderr).toBeFalsy();
24+
expect(stdout).toContain("base.webpack.config.js");
25+
expect(stdout).toContain("derived.webpack.config.js");
26+
expect(stdout).toContain("name: 'base_config'");
27+
expect(stdout).toContain("mode: 'development'");
28+
});
29+
30+
it("extends a provided webpack config correctly using `require.resolve`", async () => {
31+
const { exitCode, stderr, stdout } = await run(path.resolve(__dirname, "./require-resolve"));
32+
33+
expect(exitCode).toBe(0);
34+
expect(stderr).toBeFalsy();
35+
expect(stdout).toContain("base.webpack.config.js");
36+
expect(stdout).toContain("derived.webpack.config.js");
37+
expect(stdout).toContain("name: 'base_config'");
38+
expect(stdout).toContain("mode: 'development'");
39+
});
40+
41+
it("extends a provided webpack config correctly using `JSON` format", async () => {
42+
const { exitCode, stderr, stdout } = await run(path.resolve(__dirname, "./json"));
43+
44+
expect(exitCode).toBe(0);
45+
expect(stderr).toBeFalsy();
46+
expect(stdout).toContain("derived.webpack.config.js");
47+
expect(stdout).toContain("name: 'base_config'");
48+
expect(stdout).toContain("mode: 'development'");
49+
});
50+
1851
it("extends a provided array of webpack configs correctly", async () => {
1952
const { exitCode, stderr, stdout } = await run(path.resolve(__dirname, "./multiple-extends"));
2053

@@ -89,7 +122,7 @@ describe("extends property", () => {
89122
"--extends",
90123
"./base.webpack.config.js",
91124
"--extends",
92-
"./other.config.js",
125+
path.resolve(__dirname, "./multiple-configs1/other.config.js"),
93126
]);
94127

95128
expect(exitCode).toBe(0);
@@ -102,7 +135,7 @@ describe("extends property", () => {
102135
expect(stdout).toContain("topLevelAwait: true");
103136
});
104137

105-
it("cLI `extends` should override `extends` in a configuration", async () => {
138+
it("`extends` should override `extends` in a configuration", async () => {
106139
const { exitCode, stderr, stdout } = await run(path.resolve(__dirname, "./simple-case"), [
107140
"--extends",
108141
"./override.config.js",
@@ -116,6 +149,20 @@ describe("extends property", () => {
116149
expect(stdout).toContain("mode: 'development'");
117150
});
118151

152+
it("`extends` should override `extends` in a configuration using file protocol", async () => {
153+
const { exitCode, stderr, stdout } = await run(path.resolve(__dirname, "./simple-case"), [
154+
"--extends",
155+
pathToFileURL(path.resolve(__dirname, "./simple-case/override.config.js")).toString(),
156+
]);
157+
158+
expect(exitCode).toBe(0);
159+
expect(stderr).toBeFalsy();
160+
expect(stdout).toContain("override.config.js");
161+
expect(stdout).toContain("derived.webpack.config.js");
162+
expect(stdout).toContain("name: 'override_config'");
163+
expect(stdout).toContain("mode: 'development'");
164+
});
165+
119166
it("should throw an error on recursive", async () => {
120167
const { exitCode, stderr, stdout } = await run(path.resolve(__dirname, "./recursive-extends"));
121168

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
export default () => {
2+
console.log("base.webpack.config.js");
3+
4+
return {
5+
name: "base_config",
6+
mode: "development",
7+
entry: "./src/index.js",
8+
};
9+
};
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
export default () => {
2+
console.log("override.config.js");
3+
4+
return {
5+
name: "override_config",
6+
mode: "development",
7+
entry: "./src/index.js",
8+
};
9+
};
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
{
2+
"name": "test",
3+
"version": "1.0.0",
4+
"type": "module"
5+
}
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
console.log("index.js")
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
import WebpackCLITestPlugin from "../../../utils/webpack-cli-test-plugin.js";
2+
3+
export default () => {
4+
console.log("derived.webpack.config.js");
5+
6+
return {
7+
extends: [import.meta.resolve("./base.webpack.config.mjs")],
8+
plugins: [new WebpackCLITestPlugin()],
9+
};
10+
};
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
{
2+
"name": "base_config",
3+
"mode": "development",
4+
"entry": "./src/index.js"
5+
}

0 commit comments

Comments
 (0)