Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/slow-oranges-repair.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"webpack-cli": patch
---

The `file` protocol for configuration options (`--config`/`--extends`) is supported.
18 changes: 8 additions & 10 deletions packages/webpack-cli/src/webpack-cli.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import fs from "node:fs";
import path from "node:path";
import { type Readable as ReadableType } from "node:stream";
import { pathToFileURL } from "node:url";
import { fileURLToPath, pathToFileURL } from "node:url";
import util from "node:util";
import { type stringifyChunked as stringifyChunkedType } from "@discoveryjs/json-ext";
import {
Expand Down Expand Up @@ -2163,12 +2163,14 @@ class WebpackCLI {
): Promise<{ options: Configuration | MultiConfiguration; path: string }> => {
let options: LoadableWebpackConfiguration | undefined;

const isFileURL = configPath.startsWith("file://");

try {
let loadingError;

try {
// eslint-disable-next-line no-eval
options = (await eval(`import("${pathToFileURL(configPath)}")`)).default;
options = // eslint-disable-next-line no-eval
(await eval(`import("${isFileURL ? configPath : pathToFileURL(configPath)}")`)).default;
} catch (err) {
if (this.isValidationError(err) || process.env?.WEBPACK_CLI_FORCE_LOAD_ESM_CONFIG) {
throw err;
Expand Down Expand Up @@ -2209,7 +2211,7 @@ class WebpackCLI {
}

try {
options = require(configPath);
options = require(isFileURL ? fileURLToPath(configPath) : path.resolve(configPath));
} catch (err) {
if (this.isValidationError(err)) {
throw err;
Expand Down Expand Up @@ -2301,9 +2303,7 @@ class WebpackCLI {

if (options.config && options.config.length > 0) {
const loadedConfigs = await Promise.all(
options.config.map((configPath: string) =>
loadConfigByPath(path.resolve(configPath), options.argv),
),
options.config.map((configPath: string) => loadConfigByPath(configPath, options.argv)),
);

if (loadedConfigs.length === 1) {
Expand Down Expand Up @@ -2406,9 +2406,7 @@ class WebpackCLI {
delete config.extends;

const loadedConfigs = await Promise.all(
extendsPaths.map((extendsPath) =>
loadConfigByPath(path.resolve(extendsPath), options.argv),
),
extendsPaths.map((extendsPath) => loadConfigByPath(extendsPath, options.argv)),
);

const { merge } = await import("webpack-merge");
Expand Down
4 changes: 1 addition & 3 deletions test/build/basic/basic.test.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
"use strict";

const { resolve } = require("node:path");
const { run } = require("../../utils/test-utils");

describe("bundle command", () => {
Expand Down Expand Up @@ -181,11 +180,10 @@ describe("bundle command", () => {

it("should log supplied config when logging level is log", async () => {
const { exitCode, stderr, stdout } = await run(__dirname, ["--config", "./log.config.js"]);
const configPath = resolve(__dirname, "./log.config.js");

expect(exitCode).toBe(0);
expect(stderr).toContain("Compiler starting...");
expect(stderr).toContain(`Compiler is using config: '${configPath}'`);
expect(stderr).toContain("Compiler is using config: './log.config.js'");
expect(stderr).toContain("Compiler finished");
expect(stdout).toBeTruthy();
});
Expand Down
6 changes: 1 addition & 5 deletions test/build/config-format/failure/failure.test.js
Original file line number Diff line number Diff line change
@@ -1,15 +1,11 @@
const path = require("node:path");

const { run } = require("../../../utils/test-utils");

describe("failure", () => {
it("should log error on not installed registers", async () => {
const { exitCode, stderr, stdout } = await run(__dirname, ["-c", "webpack.config.iced"]);

expect(exitCode).toBe(2);
expect(stderr).toContain(
`Failed to load '${path.resolve(__dirname, "./webpack.config.iced")}'`,
);
expect(stderr).toContain("Failed to load 'webpack.config.iced'");
expect(stdout).toBeFalsy();
});
});
4 changes: 0 additions & 4 deletions test/build/config-format/failure/webpack.config.json5

This file was deleted.

39 changes: 37 additions & 2 deletions test/build/config/basic/basic-config.test.js
Original file line number Diff line number Diff line change
@@ -1,18 +1,53 @@
"use strict";

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

describe("basic config file", () => {
it("should build and not throw error with a basic configuration file", async () => {
const { exitCode, stderr, stdout } = await run(__dirname, ["-c", "webpack.config.js"]);
console.log(stdout);
expect(exitCode).toBe(0);
expect(stderr).toBeFalsy();
expect(stdout).toBeTruthy();
});

it("should build and not throw error with a basic configuration file using relative path", async () => {
const { exitCode, stderr, stdout } = await run(__dirname, ["-c", "./webpack.config.js"]);
expect(exitCode).toBe(0);
expect(stderr).toBeFalsy();
expect(stdout).toBeTruthy();
});

it("should build and not throw error with a basic configuration file using absolute path", async () => {
const { exitCode, stderr, stdout } = await run(__dirname, [
"-c",
resolve(__dirname, "webpack.config.js"),
"--output-path",
"./binary",
]);
expect(exitCode).toBe(0);
expect(stderr).toBeFalsy();
expect(stdout).toBeTruthy();
});

it("should build and not throw error with a basic configuration file using file protocol", async () => {
const { exitCode, stderr, stdout } = await run(__dirname, [
"-c",
pathToFileURL(resolve(__dirname, "webpack.config.js")).toString(),
]);
expect(exitCode).toBe(0);
expect(stderr).toBeFalsy();
expect(stdout).toBeTruthy();
});

it("should build and not throw error with a basic using weird path", async () => {
const { exitCode, stderr, stdout } = await run(__dirname, [
"-c",
"../basic/./webpack.config.js",
]);
console.log(stdout);
expect(exitCode).toBe(0);
expect(stderr).toBeFalsy();
expect(stdout).toBeTruthy();
});
});
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
// Jest Snapshot v1, https://jestjs.io/docs/snapshot-testing

exports[`config with invalid array syntax should throw syntax error and exit with non-zero exit code when even 1 object has syntax error 1`] = `
"[webpack-cli] Failed to load '<cwd>/test/build/config/error-array/webpack.config.js' config
"[webpack-cli] Failed to load './webpack.config.js' config
▶ ESM (\`import\`) failed:
Unexpected token ';'

Expand Down
51 changes: 49 additions & 2 deletions test/build/extends/extends.test.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
"use strict";

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

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

it("extends a provided webpack config correctly using file protocol", async () => {
const { exitCode, stderr, stdout } = await run(path.resolve(__dirname, "./file-protocol"));

expect(exitCode).toBe(0);
expect(stderr).toBeFalsy();
expect(stdout).toContain("base.webpack.config.js");
expect(stdout).toContain("derived.webpack.config.js");
expect(stdout).toContain("name: 'base_config'");
expect(stdout).toContain("mode: 'development'");
});

it("extends a provided webpack config correctly using `require.resolve`", async () => {
const { exitCode, stderr, stdout } = await run(path.resolve(__dirname, "./require-resolve"));

expect(exitCode).toBe(0);
expect(stderr).toBeFalsy();
expect(stdout).toContain("base.webpack.config.js");
expect(stdout).toContain("derived.webpack.config.js");
expect(stdout).toContain("name: 'base_config'");
expect(stdout).toContain("mode: 'development'");
});

it("extends a provided webpack config correctly using `JSON` format", async () => {
const { exitCode, stderr, stdout } = await run(path.resolve(__dirname, "./json"));

expect(exitCode).toBe(0);
expect(stderr).toBeFalsy();
expect(stdout).toContain("derived.webpack.config.js");
expect(stdout).toContain("name: 'base_config'");
expect(stdout).toContain("mode: 'development'");
});

it("extends a provided array of webpack configs correctly", async () => {
const { exitCode, stderr, stdout } = await run(path.resolve(__dirname, "./multiple-extends"));

Expand Down Expand Up @@ -89,7 +122,7 @@ describe("extends property", () => {
"--extends",
"./base.webpack.config.js",
"--extends",
"./other.config.js",
path.resolve(__dirname, "./multiple-configs1/other.config.js"),
]);

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

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

it("`extends` should override `extends` in a configuration using file protocol", async () => {
const { exitCode, stderr, stdout } = await run(path.resolve(__dirname, "./simple-case"), [
"--extends",
pathToFileURL(path.resolve(__dirname, "./simple-case/override.config.js")).toString(),
]);

expect(exitCode).toBe(0);
expect(stderr).toBeFalsy();
expect(stdout).toContain("override.config.js");
expect(stdout).toContain("derived.webpack.config.js");
expect(stdout).toContain("name: 'override_config'");
expect(stdout).toContain("mode: 'development'");
});

it("should throw an error on recursive", async () => {
const { exitCode, stderr, stdout } = await run(path.resolve(__dirname, "./recursive-extends"));

Expand Down
9 changes: 9 additions & 0 deletions test/build/extends/file-protocol/base.webpack.config.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
export default () => {
console.log("base.webpack.config.js");

return {
name: "base_config",
mode: "development",
entry: "./src/index.js",
};
};
9 changes: 9 additions & 0 deletions test/build/extends/file-protocol/override.config.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
export default () => {
console.log("override.config.js");

return {
name: "override_config",
mode: "development",
entry: "./src/index.js",
};
};
5 changes: 5 additions & 0 deletions test/build/extends/file-protocol/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
{
"name": "test",
"version": "1.0.0",
"type": "module"
}
1 change: 1 addition & 0 deletions test/build/extends/file-protocol/src/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
console.log("index.js")
10 changes: 10 additions & 0 deletions test/build/extends/file-protocol/webpack.config.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import WebpackCLITestPlugin from "../../../utils/webpack-cli-test-plugin.js";

export default () => {
console.log("derived.webpack.config.js");

return {
extends: [import.meta.resolve("./base.webpack.config.mjs")],
plugins: [new WebpackCLITestPlugin()],
};
};
5 changes: 5 additions & 0 deletions test/build/extends/json/base.webpack.config.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
{
"name": "base_config",
"mode": "development",
"entry": "./src/index.js"
}
9 changes: 9 additions & 0 deletions test/build/extends/json/override.config.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
export default () => {
console.log("override.config.js");

return {
name: "override_config",
mode: "development",
entry: "./src/index.js",
};
};
5 changes: 5 additions & 0 deletions test/build/extends/json/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
{
"name": "test",
"version": "1.0.0",
"type": "module"
}
1 change: 1 addition & 0 deletions test/build/extends/json/src/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
console.log("index.js")
10 changes: 10 additions & 0 deletions test/build/extends/json/webpack.config.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import WebpackCLITestPlugin from "../../../utils/webpack-cli-test-plugin.js";

export default () => {
console.log("derived.webpack.config.js");

return {
extends: [import.meta.resolve("./base.webpack.config.json")],
plugins: [new WebpackCLITestPlugin()],
};
};
9 changes: 9 additions & 0 deletions test/build/extends/require-resolve/base.webpack.config.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
module.exports = () => {
console.log("base.webpack.config.js");

return {
name: "base_config",
mode: "development",
entry: "./src/index.js",
};
};
9 changes: 9 additions & 0 deletions test/build/extends/require-resolve/override.config.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
module.exports = () => {
console.log("override.config.js");

return {
name: "override_config",
mode: "development",
entry: "./src/index.js",
};
};
1 change: 1 addition & 0 deletions test/build/extends/require-resolve/src/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
console.log("index.js")
10 changes: 10 additions & 0 deletions test/build/extends/require-resolve/webpack.config.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
const WebpackCLITestPlugin = require("../../../utils/webpack-cli-test-plugin");

module.exports = () => {
console.log("derived.webpack.config.js");

return {
extends: [require.resolve("./base.webpack.config.js")],
plugins: [new WebpackCLITestPlugin()],
};
};
4 changes: 1 addition & 3 deletions test/build/merge/config-absent/merge-config-absent.test.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,5 @@
"use strict";

const path = require("node:path");

const { run } = require("../../../utils/test-utils");

describe("merge flag configuration", () => {
Expand All @@ -19,6 +17,6 @@ describe("merge flag configuration", () => {
// Since the process will exit, nothing on stdout
expect(stdout).toBeFalsy();
// Confirm that the user is notified
expect(stderr).toContain(`Failed to load '${path.resolve(__dirname, "./2.js")}' config`);
expect(stderr).toContain("Failed to load './2.js' config");
});
});
Loading
Loading