Skip to content
Draft
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
56 changes: 50 additions & 6 deletions lib/Server.js
Original file line number Diff line number Diff line change
Expand Up @@ -311,6 +311,8 @@ const DEFAULT_ALLOWED_PROTOCOLS = /^(file|.+-extension):/i;
* @property {typeof useFn} use
*/

const pluginName = "webpack-dev-server";

/**
* @template {BasicApplication} [A=ExpressApplication]
* @template {BasicServer} [S=HTTPServer]
Expand All @@ -326,11 +328,14 @@ class Server {
baseDataPath: "options",
});

this.compiler = compiler;
/**
* @type {ReturnType<Compiler["getInfrastructureLogger"]>}
*/
this.logger = this.compiler.getInfrastructureLogger("webpack-dev-server");
if (compiler) {
this.compiler = compiler;

/**
* @type {ReturnType<Compiler["getInfrastructureLogger"]>}
*/
this.logger = this.compiler.getInfrastructureLogger(pluginName);
}
this.options = options;
/**
* @type {FSWatcher[]}
Expand All @@ -357,6 +362,11 @@ class Server {
*/

this.currentHash = undefined;
/**
* @private
* @type {boolean}
*/
this.isPlugin = false;
}

static get schema() {
Expand Down Expand Up @@ -1587,6 +1597,9 @@ class Server {
* @returns {void}
*/
setupProgressPlugin() {
// In the case where there is no compiler and it’s not being used as a plugin.
if (this.compiler === undefined) return;

const { ProgressPlugin } =
/** @type {MultiCompiler} */
(this.compiler).compilers
Expand Down Expand Up @@ -1631,6 +1644,7 @@ class Server {
* @returns {Promise<void>}
*/
async initialize() {
if (this.compiler === undefined) return;
this.setupHooks();

await this.setupApp();
Expand Down Expand Up @@ -1706,7 +1720,7 @@ class Server {
needForceShutdown = true;

this.stopCallback(() => {
if (typeof this.compiler.close === "function") {
if (typeof this.compiler?.close === "function") {
this.compiler.close(() => {
// eslint-disable-next-line n/no-process-exit
process.exit();
Expand Down Expand Up @@ -1781,11 +1795,14 @@ class Server {
* @returns {void}
*/
setupHooks() {
if (this.compiler === undefined) return;

this.compiler.hooks.invalid.tap("webpack-dev-server", () => {
if (this.webSocketServer) {
this.sendMessage(this.webSocketServer.clients, "invalid");
}
});

this.compiler.hooks.done.tap(
"webpack-dev-server",
/**
Expand Down Expand Up @@ -1840,6 +1857,7 @@ class Server {
* @returns {void}
*/
setupMiddlewares() {
if (this.compiler === undefined) return;
/**
* @type {Array<Middleware>}
*/
Expand Down Expand Up @@ -2346,8 +2364,10 @@ class Server {
// middleware for serving webpack bundle
/** @type {import("webpack-dev-middleware").API<Request, Response>} */
this.middleware = webpackDevMiddleware(
// @ts-expect-error
this.compiler,
this.options.devMiddleware,
this.isPlugin,
);
}

Expand Down Expand Up @@ -3414,6 +3434,30 @@ class Server {
.then(() => callback(), callback)
.catch(callback);
}

/**
* @param {Compiler} compiler compiler
* @returns {void}
*/
apply(compiler) {
this.compiler = compiler;
this.isPlugin = true;
this.logger = this.compiler.getInfrastructureLogger(pluginName);

let started = false;

this.compiler.hooks.watchRun.tapPromise(pluginName, async () => {
if (!started) {
started = true;
await this.start();
}
});

this.compiler.hooks.shutdown.tapPromise(pluginName, async () => {
started = false;
await this.stop();
});
}
}

module.exports = Server;
10 changes: 10 additions & 0 deletions test/e2e/__snapshots__/api.test.js.snap.webpack5
Original file line number Diff line number Diff line change
Expand Up @@ -165,3 +165,13 @@ exports[`API latest async API should work with callback API: console messages 1`
`;

exports[`API latest async API should work with callback API: page errors 1`] = `[]`;

exports[`API should work with plugin API: console messages 1`] = `
[
"[webpack-dev-server] Server started: Hot Module Replacement enabled, Live Reloading enabled, Progress disabled, Overlay enabled.",
"[HMR] Waiting for update signal from WDS...",
"Hey.",
]
`;

exports[`API should work with plugin API: page errors 1`] = `[]`;
50 changes: 50 additions & 0 deletions test/e2e/__snapshots__/logging.test.js.snap.webpack5
Original file line number Diff line number Diff line change
@@ -1,5 +1,55 @@
// Jest Snapshot v1, https://jestjs.io/docs/snapshot-testing

exports[`logging plugin mode should work and do not log messages about hot and live reloading is enabled 1`] = `
[
"[webpack-dev-server] Server started: Hot Module Replacement disabled, Live Reloading disabled, Progress disabled, Overlay enabled.",
"Hey.",
]
`;

exports[`logging plugin mode should work and log errors by default 1`] = `
[
"[webpack-dev-server] Server started: Hot Module Replacement enabled, Live Reloading enabled, Progress disabled, Overlay enabled.",
"[HMR] Waiting for update signal from WDS...",
"Hey.",
"[webpack-dev-server] Errors while compiling. Reload prevented.",
"[webpack-dev-server] ERROR
Error from compilation",
]
`;

exports[`logging plugin mode should work and log message about live reloading is enabled 1`] = `
[
"[webpack-dev-server] Server started: Hot Module Replacement disabled, Live Reloading enabled, Progress disabled, Overlay enabled.",
"Hey.",
]
`;

exports[`logging plugin mode should work and log messages about hot and live reloading is enabled 1`] = `
[
"[webpack-dev-server] Server started: Hot Module Replacement enabled, Live Reloading enabled, Progress disabled, Overlay enabled.",
"[HMR] Waiting for update signal from WDS...",
"Hey.",
]
`;

exports[`logging plugin mode should work and log warnings by default 1`] = `
[
"[webpack-dev-server] Server started: Hot Module Replacement enabled, Live Reloading enabled, Progress disabled, Overlay enabled.",
"[HMR] Waiting for update signal from WDS...",
"Hey.",
"[webpack-dev-server] Warnings while compiling.",
"[webpack-dev-server] WARNING
Warning from compilation",
]
`;

exports[`logging plugin mode should work when the "client.logging" is "none" 1`] = `
[
"Hey.",
]
`;

exports[`logging should work and do not log messages about hot and live reloading is enabled (ws) 1`] = `
[
"[webpack-dev-server] Server started: Hot Module Replacement disabled, Live Reloading disabled, Progress disabled, Overlay enabled.",
Expand Down
83 changes: 83 additions & 0 deletions test/e2e/api.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,94 @@ const path = require("node:path");
const webpack = require("webpack");
const Server = require("../../lib/Server");
const config = require("../fixtures/client-config/webpack.config");
const compile = require("../helpers/compile");
const runBrowser = require("../helpers/run-browser");
const sessionSubscribe = require("../helpers/session-subscribe");
const port = require("../ports-map").api;

describe("API", () => {
it("should work with plugin API", async () => {
const compiler = webpack(config);
const server = new Server({ port });

server.apply(compiler);

await compile(compiler, port);

const { page, browser } = await runBrowser();

const pageErrors = [];
const consoleMessages = [];

page
.on("console", (message) => {
consoleMessages.push(message);
})
.on("pageerror", (error) => {
pageErrors.push(error);
});

await page.goto(`http://127.0.0.1:${port}/`, {
waitUntil: "networkidle0",
});

expect(consoleMessages.map((message) => message.text())).toMatchSnapshot(
"console messages",
);
expect(pageErrors).toMatchSnapshot("page errors");

await browser.close();
await new Promise((resolve) => {
compiler.close(resolve);
});
});

it("should not start the server multiple times on recompilation", async () => {
const compiler = webpack(config);
const server = new Server({ port });
const startSpy = jest.spyOn(server, "start");

server.apply(compiler);

const { watching } = await compile(compiler, port);

// Trigger a recompilation by invalidating
await new Promise((resolve) => {
watching.invalidate(() => {
resolve();
});
});

// Wait for the recompilation to finish
await new Promise((resolve) => {
setTimeout(resolve, 2000);
});

expect(startSpy).toHaveBeenCalledTimes(1);

startSpy.mockRestore();
await new Promise((resolve) => {
compiler.close(resolve);
});
});

it("should stop the server cleanly via compiler.close()", async () => {
const compiler = webpack(config);
const server = new Server({ port });
const stopSpy = jest.spyOn(server, "stop");

server.apply(compiler);

await compile(compiler, port);

await new Promise((resolve) => {
compiler.close(resolve);
});

expect(stopSpy).toHaveBeenCalledTimes(1);
stopSpy.mockRestore();
});

describe("WEBPACK_SERVE environment variable", () => {
const OLD_ENV = process.env;
let server;
Expand Down
Loading
Loading