diff --git a/lib/Server.js b/lib/Server.js index cb7da1a6de..ea9716c280 100644 --- a/lib/Server.js +++ b/lib/Server.js @@ -17,7 +17,7 @@ const schema = require("./options.json"); /** @typedef {import("webpack").Stats} Stats */ /** @typedef {import("webpack").MultiStats} MultiStats */ /** @typedef {import("os").NetworkInterfaceInfo} NetworkInterfaceInfo */ -/** @typedef {import("chokidar").WatchOptions} WatchOptions */ +/** @typedef {import("chokidar").ChokidarOptions} WatchOptions */ /** @typedef {import("chokidar").FSWatcher} FSWatcher */ /** @typedef {import("connect-history-api-fallback").Options} ConnectHistoryApiFallbackOptions */ /** @typedef {import("bonjour-service").Bonjour} Bonjour */ @@ -833,7 +833,7 @@ class Server { const usePolling = getPolling(); const interval = getInterval(); - const { poll, ...rest } = watchOptions; + const { poll: _poll, interval: _interval, ...rest } = watchOptions; return { ignoreInitial: true, @@ -844,9 +844,7 @@ class Server { ignorePermissionErrors: true, // Respect options from compiler watchOptions usePolling, - interval, - ignored: watchOptions.ignored, - // TODO: we respect these options for all watch options and allow developers to pass them to chokidar, but chokidar doesn't have these options maybe we need revisit that in future + ...(interval !== undefined ? { interval } : {}), ...rest, }; }; @@ -3188,10 +3186,30 @@ class Server { * @param {string | string[]} watchPath watch path * @param {WatchOptions=} watchOptions watch options */ - watchFiles(watchPath, watchOptions) { + watchFiles(watchPath, watchOptions = {}) { const chokidar = require("chokidar"); + const { globSync, isDynamicPattern } = require("tinyglobby"); - const watcher = chokidar.watch(watchPath, watchOptions); + const resolveGlobs = (/** @type {string | string[]} */ input) => + (Array.isArray(input) ? input : [input]).flatMap((path) => + typeof path === "string" && isDynamicPattern(path) + ? globSync(path, { cwd: watchOptions.cwd, absolute: true }) + : path, + ); + + const resolvedPaths = resolveGlobs(watchPath); + + if (typeof watchOptions.ignored === "string") { + watchOptions.ignored = resolveGlobs(watchOptions.ignored); + } else if (Array.isArray(watchOptions.ignored)) { + watchOptions.ignored = watchOptions.ignored.flatMap((item) => + typeof item === "string" && isDynamicPattern(item) + ? globSync(item, { cwd: watchOptions.cwd, absolute: true }) + : item, + ); + } + + const watcher = chokidar.watch(resolvedPaths, watchOptions); // disabling refreshing on changing the content if (this.options.liveReload) { diff --git a/package-lock.json b/package-lock.json index 9c63a967bc..4647869111 100644 --- a/package-lock.json +++ b/package-lock.json @@ -18,7 +18,7 @@ "@types/ws": "^8.18.1", "ansi-html-community": "^0.0.8", "bonjour-service": "^1.3.0", - "chokidar": "^3.6.0", + "chokidar": "^4.0.3", "compression": "^1.8.1", "connect-history-api-fallback": "^2.0.0", "express": "^5.2.1", @@ -31,6 +31,7 @@ "schema-utils": "^4.3.3", "selfsigned": "^5.5.0", "serve-index": "^1.9.2", + "tinyglobby": "^0.2.15", "webpack-dev-middleware": "^8.0.2", "ws": "^8.20.0" }, @@ -53,6 +54,7 @@ "@types/graceful-fs": "^4.1.9", "@types/node": "^24.0.14", "@types/node-forge": "^1.3.1", + "@types/picomatch": "^4.0.2", "@types/trusted-types": "^2.0.7", "acorn": "^8.14.0", "babel-jest": "^30.0.4", @@ -147,6 +149,74 @@ "@babel/core": "^7.0.0-0" } }, + "node_modules/@babel/cli/node_modules/chokidar": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.6.0.tgz", + "integrity": "sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "anymatch": "~3.1.2", + "braces": "~3.0.2", + "glob-parent": "~5.1.2", + "is-binary-path": "~2.1.0", + "is-glob": "~4.0.1", + "normalize-path": "~3.0.0", + "readdirp": "~3.6.0" + }, + "engines": { + "node": ">= 8.10.0" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + }, + "optionalDependencies": { + "fsevents": "~2.3.2" + } + }, + "node_modules/@babel/cli/node_modules/glob-parent": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", + "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", + "dev": true, + "license": "ISC", + "optional": true, + "dependencies": { + "is-glob": "^4.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/@babel/cli/node_modules/picomatch": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.2.tgz", + "integrity": "sha512-V7+vQEJ06Z+c5tSye8S+nHUfI51xoXIXjHQ99cQtKUkQqqO1kO/KCJUfZXuB47h/YBlDhah2H3hdUGXn8ie0oA==", + "dev": true, + "license": "MIT", + "optional": true, + "engines": { + "node": ">=8.6" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/@babel/cli/node_modules/readdirp": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", + "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "picomatch": "^2.2.1" + }, + "engines": { + "node": ">=8.10.0" + } + }, "node_modules/@babel/code-frame": { "version": "7.29.0", "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.29.0.tgz", @@ -5270,6 +5340,13 @@ "dev": true, "license": "MIT" }, + "node_modules/@types/picomatch": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/@types/picomatch/-/picomatch-4.0.2.tgz", + "integrity": "sha512-qHHxQ+P9PysNEGbALT8f8YOSHW0KJu6l2xU8DYY0fu/EmGxXdVnuTLvFUvBgPJMSqXq29SYHveejeAha+4AYgA==", + "dev": true, + "license": "MIT" + }, "node_modules/@types/qs": { "version": "6.14.0", "resolved": "https://registry.npmjs.org/@types/qs/-/qs-6.14.0.tgz", @@ -6340,6 +6417,7 @@ "version": "3.1.3", "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz", "integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==", + "dev": true, "license": "ISC", "dependencies": { "normalize-path": "^3.0.0", @@ -6353,6 +6431,7 @@ "version": "2.3.2", "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.2.tgz", "integrity": "sha512-V7+vQEJ06Z+c5tSye8S+nHUfI51xoXIXjHQ99cQtKUkQqqO1kO/KCJUfZXuB47h/YBlDhah2H3hdUGXn8ie0oA==", + "dev": true, "license": "MIT", "engines": { "node": ">=8.6" @@ -7044,7 +7123,9 @@ "version": "2.3.0", "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.3.0.tgz", "integrity": "sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw==", + "dev": true, "license": "MIT", + "optional": true, "engines": { "node": ">=8" }, @@ -7436,27 +7517,18 @@ } }, "node_modules/chokidar": { - "version": "3.6.0", - "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.6.0.tgz", - "integrity": "sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==", + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-4.0.3.tgz", + "integrity": "sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA==", "license": "MIT", "dependencies": { - "anymatch": "~3.1.2", - "braces": "~3.0.2", - "glob-parent": "~5.1.2", - "is-binary-path": "~2.1.0", - "is-glob": "~4.0.1", - "normalize-path": "~3.0.0", - "readdirp": "~3.6.0" + "readdirp": "^4.0.1" }, "engines": { - "node": ">= 8.10.0" + "node": ">= 14.16.0" }, "funding": { "url": "https://paulmillr.com/funding/" - }, - "optionalDependencies": { - "fsevents": "~2.3.2" } }, "node_modules/chrome-trace-event": { @@ -11428,19 +11500,6 @@ "node": ">=16" } }, - "node_modules/eslint/node_modules/glob-parent": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", - "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==", - "dev": true, - "license": "ISC", - "dependencies": { - "is-glob": "^4.0.3" - }, - "engines": { - "node": ">=10.13.0" - } - }, "node_modules/eslint/node_modules/json-schema-traverse": { "version": "0.4.1", "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", @@ -11932,7 +11991,6 @@ "version": "6.5.0", "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz", "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==", - "dev": true, "license": "MIT", "engines": { "node": ">=12.0.0" @@ -12274,6 +12332,7 @@ "version": "2.3.3", "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "dev": true, "hasInstallScript": true, "license": "MIT", "optional": true, @@ -13103,15 +13162,16 @@ } }, "node_modules/glob-parent": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", - "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", + "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==", + "dev": true, "license": "ISC", "dependencies": { - "is-glob": "^4.0.1" + "is-glob": "^4.0.3" }, "engines": { - "node": ">= 6" + "node": ">=10.13.0" } }, "node_modules/glob-to-regex.js": { @@ -13932,7 +13992,9 @@ "version": "2.1.0", "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==", + "dev": true, "license": "MIT", + "optional": true, "dependencies": { "binary-extensions": "^2.0.0" }, @@ -18637,6 +18699,7 @@ "version": "3.0.0", "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", + "dev": true, "license": "MIT", "engines": { "node": ">=0.10.0" @@ -19412,7 +19475,6 @@ "version": "4.0.4", "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.4.tgz", "integrity": "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==", - "dev": true, "license": "MIT", "engines": { "node": ">=12" @@ -20224,27 +20286,16 @@ } }, "node_modules/readdirp": { - "version": "3.6.0", - "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", - "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==", - "license": "MIT", - "dependencies": { - "picomatch": "^2.2.1" - }, - "engines": { - "node": ">=8.10.0" - } - }, - "node_modules/readdirp/node_modules/picomatch": { - "version": "2.3.2", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.2.tgz", - "integrity": "sha512-V7+vQEJ06Z+c5tSye8S+nHUfI51xoXIXjHQ99cQtKUkQqqO1kO/KCJUfZXuB47h/YBlDhah2H3hdUGXn8ie0oA==", + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-4.1.2.tgz", + "integrity": "sha512-GDhwkLfywWL2s6vEjyhri+eXmfH6j1L7JE27WhqLeYzoh/A3DBaYGEj2H/HFZCn/kMfim73FXxEJTw06WtxQwg==", "license": "MIT", "engines": { - "node": ">=8.6" + "node": ">= 14.18.0" }, "funding": { - "url": "https://github.com/sponsors/jonschlinkert" + "type": "individual", + "url": "https://paulmillr.com/funding/" } }, "node_modules/rechoir": { @@ -22515,7 +22566,6 @@ "version": "0.2.15", "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.15.tgz", "integrity": "sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ==", - "dev": true, "license": "MIT", "dependencies": { "fdir": "^6.5.0", diff --git a/package.json b/package.json index 83bd4d3513..521f2e716f 100644 --- a/package.json +++ b/package.json @@ -53,7 +53,7 @@ "@types/ws": "^8.18.1", "ansi-html-community": "^0.0.8", "bonjour-service": "^1.3.0", - "chokidar": "^3.6.0", + "chokidar": "^4.0.3", "compression": "^1.8.1", "connect-history-api-fallback": "^2.0.0", "express": "^5.2.1", @@ -66,6 +66,7 @@ "schema-utils": "^4.3.3", "selfsigned": "^5.5.0", "serve-index": "^1.9.2", + "tinyglobby": "^0.2.15", "webpack-dev-middleware": "^8.0.2", "ws": "^8.20.0" }, @@ -85,6 +86,7 @@ "@types/graceful-fs": "^4.1.9", "@types/node": "^24.0.14", "@types/node-forge": "^1.3.1", + "@types/picomatch": "^4.0.2", "@types/trusted-types": "^2.0.7", "acorn": "^8.14.0", "babel-jest": "^30.0.4", diff --git a/test/__snapshots__/normalize-options.test.js.snap.webpack5 b/test/__snapshots__/normalize-options.test.js.snap.webpack5 index 06c223f976..52099cfd75 100644 --- a/test/__snapshots__/normalize-options.test.js.snap.webpack5 +++ b/test/__snapshots__/normalize-options.test.js.snap.webpack5 @@ -39,8 +39,6 @@ exports[`normalize options allowedHosts is array 1`] = ` "followSymlinks": false, "ignoreInitial": true, "ignorePermissionErrors": true, - "ignored": undefined, - "interval": undefined, "persistent": true, "usePolling": false, }, @@ -95,8 +93,6 @@ exports[`normalize options allowedHosts is string 1`] = ` "followSymlinks": false, "ignoreInitial": true, "ignorePermissionErrors": true, - "ignored": undefined, - "interval": undefined, "persistent": true, "usePolling": false, }, @@ -152,8 +148,6 @@ exports[`normalize options client custom webSocketTransport path 1`] = ` "followSymlinks": false, "ignoreInitial": true, "ignorePermissionErrors": true, - "ignored": undefined, - "interval": undefined, "persistent": true, "usePolling": false, }, @@ -211,8 +205,6 @@ exports[`normalize options client host and port 1`] = ` "followSymlinks": false, "ignoreInitial": true, "ignorePermissionErrors": true, - "ignored": undefined, - "interval": undefined, "persistent": true, "usePolling": false, }, @@ -270,8 +262,6 @@ exports[`normalize options client host and string port 1`] = ` "followSymlinks": false, "ignoreInitial": true, "ignorePermissionErrors": true, - "ignored": undefined, - "interval": undefined, "persistent": true, "usePolling": false, }, @@ -328,8 +318,6 @@ exports[`normalize options client path 1`] = ` "followSymlinks": false, "ignoreInitial": true, "ignorePermissionErrors": true, - "ignored": undefined, - "interval": undefined, "persistent": true, "usePolling": false, }, @@ -386,8 +374,6 @@ exports[`normalize options client path without leading/ending slashes 1`] = ` "followSymlinks": false, "ignoreInitial": true, "ignorePermissionErrors": true, - "ignored": undefined, - "interval": undefined, "persistent": true, "usePolling": false, }, @@ -443,8 +429,6 @@ exports[`normalize options client.webSocketTransport ws string 1`] = ` "followSymlinks": false, "ignoreInitial": true, "ignorePermissionErrors": true, - "ignored": undefined, - "interval": undefined, "persistent": true, "usePolling": false, }, @@ -500,8 +484,6 @@ exports[`normalize options client.webSocketTransport ws string and webSocketServ "followSymlinks": false, "ignoreInitial": true, "ignorePermissionErrors": true, - "ignored": undefined, - "interval": undefined, "persistent": true, "usePolling": false, }, @@ -559,8 +541,6 @@ exports[`normalize options client.webSocketTransport ws string and webSocketServ "followSymlinks": false, "ignoreInitial": true, "ignorePermissionErrors": true, - "ignored": undefined, - "interval": undefined, "persistent": true, "usePolling": false, }, @@ -618,8 +598,6 @@ exports[`normalize options client.webSocketTransport ws string and webSocketServ "followSymlinks": false, "ignoreInitial": true, "ignorePermissionErrors": true, - "ignored": undefined, - "interval": undefined, "persistent": true, "usePolling": false, }, @@ -676,8 +654,6 @@ exports[`normalize options dev is set 1`] = ` "followSymlinks": false, "ignoreInitial": true, "ignorePermissionErrors": true, - "ignored": undefined, - "interval": undefined, "persistent": true, "usePolling": false, }, @@ -732,8 +708,6 @@ exports[`normalize options hot is false 1`] = ` "followSymlinks": false, "ignoreInitial": true, "ignorePermissionErrors": true, - "ignored": undefined, - "interval": undefined, "persistent": true, "usePolling": false, }, @@ -788,8 +762,6 @@ exports[`normalize options hot is only 1`] = ` "followSymlinks": false, "ignoreInitial": true, "ignorePermissionErrors": true, - "ignored": undefined, - "interval": undefined, "persistent": true, "usePolling": false, }, @@ -844,8 +816,6 @@ exports[`normalize options hot is true 1`] = ` "followSymlinks": false, "ignoreInitial": true, "ignorePermissionErrors": true, - "ignored": undefined, - "interval": undefined, "persistent": true, "usePolling": false, }, @@ -900,8 +870,6 @@ exports[`normalize options liveReload is false 1`] = ` "followSymlinks": false, "ignoreInitial": true, "ignorePermissionErrors": true, - "ignored": undefined, - "interval": undefined, "persistent": true, "usePolling": false, }, @@ -956,8 +924,6 @@ exports[`normalize options liveReload is true 1`] = ` "followSymlinks": false, "ignoreInitial": true, "ignorePermissionErrors": true, - "ignored": undefined, - "interval": undefined, "persistent": true, "usePolling": false, }, @@ -1012,8 +978,6 @@ exports[`normalize options multi compiler client.logging should override infrast "followSymlinks": false, "ignoreInitial": true, "ignorePermissionErrors": true, - "ignored": undefined, - "interval": undefined, "persistent": true, "usePolling": false, }, @@ -1068,8 +1032,6 @@ exports[`normalize options multi compiler client.logging should respect infrastr "followSymlinks": false, "ignoreInitial": true, "ignorePermissionErrors": true, - "ignored": undefined, - "interval": undefined, "persistent": true, "usePolling": false, }, @@ -1124,8 +1086,6 @@ exports[`normalize options multi compiler client.logging should respect infrastr "followSymlinks": false, "ignoreInitial": true, "ignorePermissionErrors": true, - "ignored": undefined, - "interval": undefined, "persistent": true, "usePolling": false, }, @@ -1180,8 +1140,6 @@ exports[`normalize options multi compiler client.logging should respect infrastr "followSymlinks": false, "ignoreInitial": true, "ignorePermissionErrors": true, - "ignored": undefined, - "interval": undefined, "persistent": true, "usePolling": false, }, @@ -1236,8 +1194,6 @@ exports[`normalize options multi compiler watchOptions is set 1`] = ` "followSymlinks": false, "ignoreInitial": true, "ignorePermissionErrors": true, - "ignored": undefined, - "interval": undefined, "persistent": true, "usePolling": false, }, @@ -1292,8 +1248,6 @@ exports[`normalize options no options 1`] = ` "followSymlinks": false, "ignoreInitial": true, "ignorePermissionErrors": true, - "ignored": undefined, - "interval": undefined, "persistent": true, "usePolling": false, }, @@ -1348,8 +1302,6 @@ exports[`normalize options port string 1`] = ` "followSymlinks": false, "ignoreInitial": true, "ignorePermissionErrors": true, - "ignored": undefined, - "interval": undefined, "persistent": true, "usePolling": false, }, @@ -1404,8 +1356,6 @@ exports[`normalize options single compiler client.logging should default to infr "followSymlinks": false, "ignoreInitial": true, "ignorePermissionErrors": true, - "ignored": undefined, - "interval": undefined, "persistent": true, "usePolling": false, }, @@ -1460,8 +1410,6 @@ exports[`normalize options single compiler client.logging should override to inf "followSymlinks": false, "ignoreInitial": true, "ignorePermissionErrors": true, - "ignored": undefined, - "interval": undefined, "persistent": true, "usePolling": false, }, @@ -1516,8 +1464,6 @@ exports[`normalize options single compiler watchOptions is object 1`] = ` "followSymlinks": false, "ignoreInitial": true, "ignorePermissionErrors": true, - "ignored": undefined, - "interval": undefined, "persistent": true, "usePolling": false, }, @@ -1572,7 +1518,6 @@ exports[`normalize options single compiler watchOptions is object with static wa "followSymlinks": false, "ignoreInitial": true, "ignorePermissionErrors": true, - "ignored": undefined, "interval": 500, "persistent": true, "usePolling": true, @@ -1628,8 +1573,6 @@ exports[`normalize options single compiler watchOptions is object with static wa "followSymlinks": false, "ignoreInitial": true, "ignorePermissionErrors": true, - "ignored": undefined, - "interval": undefined, "persistent": true, "usePolling": false, }, @@ -1684,8 +1627,6 @@ exports[`normalize options single compiler watchOptions is object with watch fal "followSymlinks": false, "ignoreInitial": true, "ignorePermissionErrors": true, - "ignored": undefined, - "interval": undefined, "persistent": true, "usePolling": false, }, @@ -1740,8 +1681,6 @@ exports[`normalize options static is an array of static objects 1`] = ` "followSymlinks": false, "ignoreInitial": true, "ignorePermissionErrors": true, - "ignored": undefined, - "interval": undefined, "persistent": true, "usePolling": false, }, @@ -1761,8 +1700,6 @@ exports[`normalize options static is an array of static objects 1`] = ` "followSymlinks": false, "ignoreInitial": true, "ignorePermissionErrors": true, - "ignored": undefined, - "interval": undefined, "persistent": true, "usePolling": false, }, @@ -1817,8 +1754,6 @@ exports[`normalize options static is an array of strings 1`] = ` "followSymlinks": false, "ignoreInitial": true, "ignorePermissionErrors": true, - "ignored": undefined, - "interval": undefined, "persistent": true, "usePolling": false, }, @@ -1838,8 +1773,6 @@ exports[`normalize options static is an array of strings 1`] = ` "followSymlinks": false, "ignoreInitial": true, "ignorePermissionErrors": true, - "ignored": undefined, - "interval": undefined, "persistent": true, "usePolling": false, }, @@ -1894,8 +1827,6 @@ exports[`normalize options static is an array of strings and static objects 1`] "followSymlinks": false, "ignoreInitial": true, "ignorePermissionErrors": true, - "ignored": undefined, - "interval": undefined, "persistent": true, "usePolling": false, }, @@ -1915,8 +1846,6 @@ exports[`normalize options static is an array of strings and static objects 1`] "followSymlinks": false, "ignoreInitial": true, "ignorePermissionErrors": true, - "ignored": undefined, - "interval": undefined, "persistent": true, "usePolling": false, }, @@ -1971,8 +1900,6 @@ exports[`normalize options static is an object 1`] = ` "followSymlinks": false, "ignoreInitial": true, "ignorePermissionErrors": true, - "ignored": undefined, - "interval": undefined, "persistent": true, "usePolling": false, }, @@ -2030,8 +1957,6 @@ exports[`normalize options static is an object with staticOptions 1`] = ` "followSymlinks": false, "ignoreInitial": true, "ignorePermissionErrors": true, - "ignored": undefined, - "interval": undefined, "persistent": true, "usePolling": false, }, @@ -2120,8 +2045,6 @@ exports[`normalize options static is string 1`] = ` "followSymlinks": false, "ignoreInitial": true, "ignorePermissionErrors": true, - "ignored": undefined, - "interval": undefined, "persistent": true, "usePolling": false, }, @@ -2176,8 +2099,6 @@ exports[`normalize options static is true 1`] = ` "followSymlinks": false, "ignoreInitial": true, "ignorePermissionErrors": true, - "ignored": undefined, - "interval": undefined, "persistent": true, "usePolling": false, }, @@ -2232,8 +2153,6 @@ exports[`normalize options static publicPath is a string 1`] = ` "followSymlinks": false, "ignoreInitial": true, "ignorePermissionErrors": true, - "ignored": undefined, - "interval": undefined, "persistent": true, "usePolling": false, }, @@ -2289,8 +2208,6 @@ exports[`normalize options static publicPath is an array 1`] = ` "followSymlinks": false, "ignoreInitial": true, "ignorePermissionErrors": true, - "ignored": undefined, - "interval": undefined, "persistent": true, "usePolling": false, }, @@ -2348,8 +2265,6 @@ exports[`normalize options static serveIndex is an object more options 1`] = ` "followSymlinks": false, "ignoreInitial": true, "ignorePermissionErrors": true, - "ignored": undefined, - "interval": undefined, "persistent": true, "usePolling": false, }, @@ -2404,8 +2319,6 @@ exports[`normalize options static serveIndex is an object with icons false 1`] = "followSymlinks": false, "ignoreInitial": true, "ignorePermissionErrors": true, - "ignored": undefined, - "interval": undefined, "persistent": true, "usePolling": false, }, @@ -2458,8 +2371,6 @@ exports[`normalize options static serveIndex is false 1`] = ` "followSymlinks": false, "ignoreInitial": true, "ignorePermissionErrors": true, - "ignored": undefined, - "interval": undefined, "persistent": true, "usePolling": false, }, @@ -2514,8 +2425,6 @@ exports[`normalize options static serveIndex is true 1`] = ` "followSymlinks": false, "ignoreInitial": true, "ignorePermissionErrors": true, - "ignored": undefined, - "interval": undefined, "persistent": true, "usePolling": false, }, @@ -2570,7 +2479,6 @@ exports[`normalize options static watch is an object 1`] = ` "followSymlinks": false, "ignoreInitial": true, "ignorePermissionErrors": true, - "ignored": undefined, "interval": 500, "persistent": true, "usePolling": true, @@ -2672,8 +2580,6 @@ exports[`normalize options static watch is true 1`] = ` "followSymlinks": false, "ignoreInitial": true, "ignorePermissionErrors": true, - "ignored": undefined, - "interval": undefined, "persistent": true, "usePolling": false, }, @@ -2731,8 +2637,6 @@ exports[`normalize options username and password 1`] = ` "followSymlinks": false, "ignoreInitial": true, "ignorePermissionErrors": true, - "ignored": undefined, - "interval": undefined, "persistent": true, "usePolling": false, }, @@ -2787,8 +2691,6 @@ exports[`normalize options webSocketServer custom server class 1`] = ` "followSymlinks": false, "ignoreInitial": true, "ignorePermissionErrors": true, - "ignored": undefined, - "interval": undefined, "persistent": true, "usePolling": false, }, @@ -2843,8 +2745,6 @@ exports[`normalize options webSocketServer custom server path 1`] = ` "followSymlinks": false, "ignoreInitial": true, "ignorePermissionErrors": true, - "ignored": undefined, - "interval": undefined, "persistent": true, "usePolling": false, }, diff --git a/test/e2e/__snapshots__/watch-files.test.js.snap.webpack5 b/test/e2e/__snapshots__/watch-files.test.js.snap.webpack5 index cd385cfd38..adf5ea5d50 100644 --- a/test/e2e/__snapshots__/watch-files.test.js.snap.webpack5 +++ b/test/e2e/__snapshots__/watch-files.test.js.snap.webpack5 @@ -20,6 +20,52 @@ exports[`watchFiles option should work with array config should reload when file exports[`watchFiles option should work with array config should reload when file content is changed: response status 1`] = `200`; +exports[`watchFiles option should work with array of globs should reload when file content is changed: console messages 1`] = ` +[ + "Hey.", +] +`; + +exports[`watchFiles option should work with array of globs should reload when file content is changed: page errors 1`] = `[]`; + +exports[`watchFiles option should work with array of globs should reload when file content is changed: response status 1`] = `200`; + +exports[`watchFiles option should work with directory and ignored option to filter files should not reload when a non-matching file is changed: response status 1`] = `200`; + +exports[`watchFiles option should work with directory and ignored option to filter files should reload when file content is changed: console messages 1`] = ` +[ + "Hey.", +] +`; + +exports[`watchFiles option should work with directory and ignored option to filter files should reload when file content is changed: page errors 1`] = `[]`; + +exports[`watchFiles option should work with directory and ignored option to filter files should reload when file content is changed: response status 1`] = `200`; + +exports[`watchFiles option should work with ignored option using glob array should not reload when an ignored glob file is changed: response status 1`] = `200`; + +exports[`watchFiles option should work with ignored option using glob array should reload when file content is changed: console messages 1`] = ` +[ + "Hey.", +] +`; + +exports[`watchFiles option should work with ignored option using glob array should reload when file content is changed: page errors 1`] = `[]`; + +exports[`watchFiles option should work with ignored option using glob array should reload when file content is changed: response status 1`] = `200`; + +exports[`watchFiles option should work with ignored option using glob string should not reload when an ignored glob file is changed: response status 1`] = `200`; + +exports[`watchFiles option should work with ignored option using glob string should reload when file content is changed: console messages 1`] = ` +[ + "Hey.", +] +`; + +exports[`watchFiles option should work with ignored option using glob string should reload when file content is changed: page errors 1`] = `[]`; + +exports[`watchFiles option should work with ignored option using glob string should reload when file content is changed: response status 1`] = `200`; + exports[`watchFiles option should work with object with multiple paths should reload when file content is changed: console messages 1`] = ` [ "Hey.", @@ -44,10 +90,12 @@ exports[`watchFiles option should work with options {"interval":400,"poll":200} { "alwaysStat": true, "atomic": false, + "awaitWriteFinish": false, + "binaryInterval": 300, "followSymlinks": false, "ignoreInitial": true, "ignorePermissionErrors": true, - "ignored": undefined, + "ignored": [], "interval": 400, "persistent": true, "usePolling": true, @@ -68,10 +116,12 @@ exports[`watchFiles option should work with options {"poll":200} should reload w { "alwaysStat": true, "atomic": false, + "awaitWriteFinish": false, + "binaryInterval": 300, "followSymlinks": false, "ignoreInitial": true, "ignorePermissionErrors": true, - "ignored": undefined, + "ignored": [], "interval": 200, "persistent": true, "usePolling": true, @@ -92,11 +142,13 @@ exports[`watchFiles option should work with options {"poll":true} should reload { "alwaysStat": true, "atomic": false, + "awaitWriteFinish": false, + "binaryInterval": 300, "followSymlinks": false, "ignoreInitial": true, "ignorePermissionErrors": true, - "ignored": undefined, - "interval": undefined, + "ignored": [], + "interval": 100, "persistent": true, "usePolling": true, } @@ -116,13 +168,15 @@ exports[`watchFiles option should work with options {"usePolling":false,"interva { "alwaysStat": true, "atomic": false, + "awaitWriteFinish": false, + "binaryInterval": 300, "followSymlinks": false, "ignoreInitial": true, "ignorePermissionErrors": true, - "ignored": undefined, + "ignored": [], "interval": 200, "persistent": true, - "usePolling": false, + "usePolling": true, } `; @@ -140,13 +194,15 @@ exports[`watchFiles option should work with options {"usePolling":false,"poll":2 { "alwaysStat": true, "atomic": false, + "awaitWriteFinish": false, + "binaryInterval": 300, "followSymlinks": false, "ignoreInitial": true, "ignorePermissionErrors": true, - "ignored": undefined, + "ignored": [], "interval": 200, "persistent": true, - "usePolling": false, + "usePolling": true, } `; @@ -164,13 +220,15 @@ exports[`watchFiles option should work with options {"usePolling":false,"poll":t { "alwaysStat": true, "atomic": false, + "awaitWriteFinish": false, + "binaryInterval": 300, "followSymlinks": false, "ignoreInitial": true, "ignorePermissionErrors": true, - "ignored": undefined, - "interval": undefined, + "ignored": [], + "interval": 100, "persistent": true, - "usePolling": false, + "usePolling": true, } `; @@ -188,13 +246,15 @@ exports[`watchFiles option should work with options {"usePolling":false} should { "alwaysStat": true, "atomic": false, + "awaitWriteFinish": false, + "binaryInterval": 300, "followSymlinks": false, "ignoreInitial": true, "ignorePermissionErrors": true, - "ignored": undefined, - "interval": undefined, + "ignored": [], + "interval": 100, "persistent": true, - "usePolling": false, + "usePolling": true, } `; @@ -212,10 +272,12 @@ exports[`watchFiles option should work with options {"usePolling":true,"interval { "alwaysStat": true, "atomic": false, + "awaitWriteFinish": false, + "binaryInterval": 300, "followSymlinks": false, "ignoreInitial": true, "ignorePermissionErrors": true, - "ignored": undefined, + "ignored": [], "interval": 200, "persistent": true, "usePolling": true, @@ -236,10 +298,12 @@ exports[`watchFiles option should work with options {"usePolling":true,"poll":20 { "alwaysStat": true, "atomic": false, + "awaitWriteFinish": false, + "binaryInterval": 300, "followSymlinks": false, "ignoreInitial": true, "ignorePermissionErrors": true, - "ignored": undefined, + "ignored": [], "interval": 200, "persistent": true, "usePolling": true, @@ -260,11 +324,13 @@ exports[`watchFiles option should work with options {"usePolling":true} should r { "alwaysStat": true, "atomic": false, + "awaitWriteFinish": false, + "binaryInterval": 300, "followSymlinks": false, "ignoreInitial": true, "ignorePermissionErrors": true, - "ignored": undefined, - "interval": undefined, + "ignored": [], + "interval": 100, "persistent": true, "usePolling": true, } @@ -280,6 +346,215 @@ exports[`watchFiles option should work with options {"usePolling":true} should r exports[`watchFiles option should work with options {"usePolling":true} should reload when file content is changed: response status 1`] = `200`; +exports[`watchFiles option should work with options {} should reload when file content is changed 1`] = ` +{ + "alwaysStat": true, + "atomic": false, + "awaitWriteFinish": false, + "binaryInterval": 300, + "followSymlinks": false, + "ignoreInitial": true, + "ignorePermissionErrors": true, + "ignored": [], + "interval": 100, + "persistent": true, + "usePolling": true, +} +`; + +exports[`watchFiles option should work with options {} should reload when file content is changed 2`] = ` +{ + "alwaysStat": true, + "atomic": false, + "awaitWriteFinish": false, + "binaryInterval": 300, + "followSymlinks": false, + "ignoreInitial": true, + "ignorePermissionErrors": true, + "ignored": [], + "interval": 100, + "persistent": true, + "usePolling": true, +} +`; + +exports[`watchFiles option should work with options {} should reload when file content is changed 3`] = ` +{ + "alwaysStat": true, + "atomic": false, + "awaitWriteFinish": false, + "binaryInterval": 300, + "followSymlinks": false, + "ignoreInitial": true, + "ignorePermissionErrors": true, + "ignored": [], + "interval": 100, + "persistent": undefined, + "usePolling": true, +} +`; + +exports[`watchFiles option should work with options {} should reload when file content is changed 4`] = ` +{ + "alwaysStat": true, + "atomic": false, + "awaitWriteFinish": false, + "binaryInterval": 300, + "followSymlinks": undefined, + "ignoreInitial": true, + "ignorePermissionErrors": true, + "ignored": [], + "interval": 100, + "persistent": true, + "usePolling": true, +} +`; + +exports[`watchFiles option should work with options {} should reload when file content is changed 5`] = ` +{ + "alwaysStat": true, + "atomic": true, + "awaitWriteFinish": false, + "binaryInterval": 300, + "followSymlinks": false, + "ignoreInitial": true, + "ignorePermissionErrors": true, + "ignored": [], + "interval": 100, + "persistent": true, + "usePolling": true, +} +`; + +exports[`watchFiles option should work with options {} should reload when file content is changed 6`] = ` +{ + "alwaysStat": undefined, + "atomic": false, + "awaitWriteFinish": false, + "binaryInterval": 300, + "followSymlinks": false, + "ignoreInitial": true, + "ignorePermissionErrors": true, + "ignored": [], + "interval": 100, + "persistent": true, + "usePolling": true, +} +`; + +exports[`watchFiles option should work with options {} should reload when file content is changed 7`] = ` +{ + "alwaysStat": true, + "atomic": false, + "awaitWriteFinish": false, + "binaryInterval": 300, + "depth": undefined, + "followSymlinks": false, + "ignoreInitial": true, + "ignorePermissionErrors": true, + "ignored": [], + "interval": 100, + "persistent": true, + "usePolling": true, +} +`; + +exports[`watchFiles option should work with options {} should reload when file content is changed 8`] = ` +{ + "alwaysStat": true, + "atomic": false, + "awaitWriteFinish": false, + "binaryInterval": 300, + "followSymlinks": false, + "ignoreInitial": true, + "ignorePermissionErrors": undefined, + "ignored": [], + "interval": 100, + "persistent": true, + "usePolling": true, +} +`; + +exports[`watchFiles option should work with options {} should reload when file content is changed: console messages 1`] = ` +[ + "Hey.", +] +`; + +exports[`watchFiles option should work with options {} should reload when file content is changed: console messages 2`] = ` +[ + "Hey.", +] +`; + +exports[`watchFiles option should work with options {} should reload when file content is changed: console messages 3`] = ` +[ + "Hey.", +] +`; + +exports[`watchFiles option should work with options {} should reload when file content is changed: console messages 4`] = ` +[ + "Hey.", +] +`; + +exports[`watchFiles option should work with options {} should reload when file content is changed: console messages 5`] = ` +[ + "Hey.", +] +`; + +exports[`watchFiles option should work with options {} should reload when file content is changed: console messages 6`] = ` +[ + "Hey.", +] +`; + +exports[`watchFiles option should work with options {} should reload when file content is changed: console messages 7`] = ` +[ + "Hey.", +] +`; + +exports[`watchFiles option should work with options {} should reload when file content is changed: console messages 8`] = ` +[ + "Hey.", +] +`; + +exports[`watchFiles option should work with options {} should reload when file content is changed: page errors 1`] = `[]`; + +exports[`watchFiles option should work with options {} should reload when file content is changed: page errors 2`] = `[]`; + +exports[`watchFiles option should work with options {} should reload when file content is changed: page errors 3`] = `[]`; + +exports[`watchFiles option should work with options {} should reload when file content is changed: page errors 4`] = `[]`; + +exports[`watchFiles option should work with options {} should reload when file content is changed: page errors 5`] = `[]`; + +exports[`watchFiles option should work with options {} should reload when file content is changed: page errors 6`] = `[]`; + +exports[`watchFiles option should work with options {} should reload when file content is changed: page errors 7`] = `[]`; + +exports[`watchFiles option should work with options {} should reload when file content is changed: page errors 8`] = `[]`; + +exports[`watchFiles option should work with options {} should reload when file content is changed: response status 1`] = `200`; + +exports[`watchFiles option should work with options {} should reload when file content is changed: response status 2`] = `200`; + +exports[`watchFiles option should work with options {} should reload when file content is changed: response status 3`] = `200`; + +exports[`watchFiles option should work with options {} should reload when file content is changed: response status 4`] = `200`; + +exports[`watchFiles option should work with options {} should reload when file content is changed: response status 5`] = `200`; + +exports[`watchFiles option should work with options {} should reload when file content is changed: response status 6`] = `200`; + +exports[`watchFiles option should work with options {} should reload when file content is changed: response status 7`] = `200`; + +exports[`watchFiles option should work with options {} should reload when file content is changed: response status 8`] = `200`; + exports[`watchFiles option should work with string and glob should reload when file content is changed: console messages 1`] = ` [ "Hey.", diff --git a/test/e2e/watch-files.test.js b/test/e2e/watch-files.test.js index c3785fbbec..e06126ef92 100644 --- a/test/e2e/watch-files.test.js +++ b/test/e2e/watch-files.test.js @@ -1,7 +1,6 @@ "use strict"; const path = require("node:path"); -const chokidar = require("chokidar"); const fs = require("graceful-fs"); const webpack = require("webpack"); const Server = require("../../lib/Server"); @@ -228,6 +227,420 @@ describe("watchFiles option", () => { }); }); + describe("should work with array of globs", () => { + const file = path.join(watchDir, "assets/example.txt"); + const other = path.join(watchDir, "assets/other.txt"); + let compiler; + let server; + let page; + let browser; + let pageErrors; + let consoleMessages; + + beforeEach(async () => { + compiler = webpack(config); + + server = new Server( + { + watchFiles: [`${watchDir}/**/*.txt`, `${watchDir}/**/*.js`], + port, + }, + compiler, + ); + + await server.start(); + + ({ page, browser } = await runBrowser()); + + pageErrors = []; + consoleMessages = []; + }); + + afterEach(async () => { + await browser.close(); + await server.stop(); + fs.truncateSync(file); + fs.truncateSync(other); + }); + + it("should reload when file content is changed", async () => { + page + .on("console", (message) => { + consoleMessages.push(message); + }) + .on("pageerror", (error) => { + pageErrors.push(error); + }); + + const response = await page.goto(`http://localhost:${port}/`, { + waitUntil: "networkidle0", + }); + + expect(response.status()).toMatchSnapshot("response status"); + + expect(consoleMessages.map((message) => message.text())).toMatchSnapshot( + "console messages", + ); + + expect(pageErrors).toMatchSnapshot("page errors"); + + // change file content + fs.writeFileSync(file, "Kurosaki Ichigo", "utf8"); + + await new Promise((resolve) => { + server.staticWatchers[0].on("change", async (changedPath) => { + // page reload + await page.waitForNavigation({ waitUntil: "networkidle0" }); + + expect(changedPath).toBe(file); + + resolve(); + }); + }); + }); + }); + + describe("should work with directory and ignored option to filter files", () => { + const file = path.join(watchDir, "assets/example.txt"); + let compiler; + let server; + let page; + let browser; + let pageErrors; + let consoleMessages; + + beforeEach(async () => { + compiler = webpack(config); + + server = new Server( + { + watchFiles: { + paths: watchDir, + options: { + ignored: (filePath, stats) => + stats?.isFile() && !filePath.endsWith(".txt"), + }, + }, + port, + }, + compiler, + ); + + await server.start(); + + ({ page, browser } = await runBrowser()); + + pageErrors = []; + consoleMessages = []; + }); + + afterEach(async () => { + await browser.close(); + await server.stop(); + fs.truncateSync(file); + }); + + it("should reload when file content is changed", async () => { + page + .on("console", (message) => { + consoleMessages.push(message); + }) + .on("pageerror", (error) => { + pageErrors.push(error); + }); + + const response = await page.goto(`http://localhost:${port}/`, { + waitUntil: "networkidle0", + }); + + expect(response.status()).toMatchSnapshot("response status"); + + expect(consoleMessages.map((message) => message.text())).toMatchSnapshot( + "console messages", + ); + + expect(pageErrors).toMatchSnapshot("page errors"); + + // change file content + fs.writeFileSync(file, "Kurosaki Ichigo", "utf8"); + + await new Promise((resolve) => { + server.staticWatchers[0].on("change", async (changedPath) => { + // page reload + await page.waitForNavigation({ waitUntil: "networkidle0" }); + + expect(changedPath).toBe(file); + + resolve(); + }); + }); + }); + + it("should not reload when a non-matching file is changed", async () => { + const ignoredFile = path.join(watchDir, "assets/example.js"); + + page + .on("console", (message) => { + consoleMessages.push(message); + }) + .on("pageerror", (error) => { + pageErrors.push(error); + }); + + const response = await page.goto(`http://localhost:${port}/`, { + waitUntil: "networkidle0", + }); + + expect(response.status()).toMatchSnapshot("response status"); + + // change ignored file content + fs.writeFileSync(ignoredFile, "// changed", "utf8"); + + // wait a bit to ensure no reload happens + await new Promise((resolve) => { + let changed = false; + + server.staticWatchers[0].on("change", () => { + changed = true; + }); + + setTimeout(() => { + expect(changed).toBe(false); + resolve(); + }, 2000); + }); + + // restore file + fs.writeFileSync(ignoredFile, "// test file\n", "utf8"); + }); + }); + + describe("should work with ignored option using glob string", () => { + const file = path.join(watchDir, "assets/example.txt"); + const ignoredFile = path.join(watchDir, "assets/example.js"); + let compiler; + let server; + let page; + let browser; + let pageErrors; + let consoleMessages; + + beforeEach(async () => { + compiler = webpack(config); + + server = new Server( + { + watchFiles: { + paths: watchDir, + options: { + ignored: `${watchDir}/**/*.js`, + }, + }, + port, + }, + compiler, + ); + + await server.start(); + + ({ page, browser } = await runBrowser()); + + pageErrors = []; + consoleMessages = []; + }); + + afterEach(async () => { + await browser.close(); + await server.stop(); + fs.truncateSync(file); + }); + + it("should reload when file content is changed", async () => { + page + .on("console", (message) => { + consoleMessages.push(message); + }) + .on("pageerror", (error) => { + pageErrors.push(error); + }); + + const response = await page.goto(`http://localhost:${port}/`, { + waitUntil: "networkidle0", + }); + + expect(response.status()).toMatchSnapshot("response status"); + + expect(consoleMessages.map((message) => message.text())).toMatchSnapshot( + "console messages", + ); + + expect(pageErrors).toMatchSnapshot("page errors"); + + // change file content + fs.writeFileSync(file, "Kurosaki Ichigo", "utf8"); + + await new Promise((resolve) => { + server.staticWatchers[0].on("change", async (changedPath) => { + // page reload + await page.waitForNavigation({ waitUntil: "networkidle0" }); + + expect(changedPath).toBe(file); + + resolve(); + }); + }); + }); + + it("should not reload when an ignored glob file is changed", async () => { + page + .on("console", (message) => { + consoleMessages.push(message); + }) + .on("pageerror", (error) => { + pageErrors.push(error); + }); + + const response = await page.goto(`http://localhost:${port}/`, { + waitUntil: "networkidle0", + }); + + expect(response.status()).toMatchSnapshot("response status"); + + // change ignored file content + fs.writeFileSync(ignoredFile, "// changed", "utf8"); + + // wait a bit to ensure no reload happens + await new Promise((resolve) => { + let changed = false; + + server.staticWatchers[0].on("change", () => { + changed = true; + }); + + setTimeout(() => { + expect(changed).toBe(false); + resolve(); + }, 2000); + }); + + // restore file + fs.writeFileSync(ignoredFile, "// test file\n", "utf8"); + }); + }); + + describe("should work with ignored option using glob array", () => { + const file = path.join(watchDir, "assets/example.txt"); + const ignoredFile = path.join(watchDir, "assets/example.js"); + let compiler; + let server; + let page; + let browser; + let pageErrors; + let consoleMessages; + + beforeEach(async () => { + compiler = webpack(config); + + server = new Server( + { + watchFiles: { + paths: watchDir, + options: { + ignored: [`${watchDir}/**/*.js`], + }, + }, + port, + }, + compiler, + ); + + await server.start(); + + ({ page, browser } = await runBrowser()); + + pageErrors = []; + consoleMessages = []; + }); + + afterEach(async () => { + await browser.close(); + await server.stop(); + fs.truncateSync(file); + }); + + it("should reload when file content is changed", async () => { + page + .on("console", (message) => { + consoleMessages.push(message); + }) + .on("pageerror", (error) => { + pageErrors.push(error); + }); + + const response = await page.goto(`http://localhost:${port}/`, { + waitUntil: "networkidle0", + }); + + expect(response.status()).toMatchSnapshot("response status"); + + expect(consoleMessages.map((message) => message.text())).toMatchSnapshot( + "console messages", + ); + + expect(pageErrors).toMatchSnapshot("page errors"); + + // change file content + fs.writeFileSync(file, "Kurosaki Ichigo", "utf8"); + + await new Promise((resolve) => { + server.staticWatchers[0].on("change", async (changedPath) => { + // page reload + await page.waitForNavigation({ waitUntil: "networkidle0" }); + + expect(changedPath).toBe(file); + + resolve(); + }); + }); + }); + + it("should not reload when an ignored glob file is changed", async () => { + page + .on("console", (message) => { + consoleMessages.push(message); + }) + .on("pageerror", (error) => { + pageErrors.push(error); + }); + + const response = await page.goto(`http://localhost:${port}/`, { + waitUntil: "networkidle0", + }); + + expect(response.status()).toMatchSnapshot("response status"); + + // change ignored file content + fs.writeFileSync(ignoredFile, "// changed", "utf8"); + + // wait a bit to ensure no reload happens + await new Promise((resolve) => { + let changed = false; + + server.staticWatchers[0].on("change", () => { + changed = true; + }); + + setTimeout(() => { + expect(changed).toBe(false); + resolve(); + }, 2000); + }); + + // restore file + fs.writeFileSync(ignoredFile, "// test file\n", "utf8"); + }); + }); + describe("should not crash if file doesn't exist", () => { const nonExistFile = path.join(watchDir, "assets/non-exist.txt"); let compiler; @@ -556,9 +969,31 @@ describe("watchFiles option", () => { describe("should work with options", () => { const file = path.join(watchDir, "assets/example.txt"); - const chokidarMock = jest.spyOn(chokidar, "watch"); - const optionCases = [ + { + interval: undefined, + }, + { + usePolling: undefined, + }, + { + persistent: undefined, + }, + { + followSymlinks: undefined, + }, + { + atomic: undefined, + }, + { + alwaysStat: undefined, + }, + { + depth: undefined, + }, + { + ignorePermissionErrors: undefined, + }, { poll: true, }, @@ -609,8 +1044,6 @@ describe("watchFiles option", () => { let consoleMessages; beforeEach(async () => { - chokidarMock.mockClear(); - compiler = webpack(config); server = new Server( @@ -652,7 +1085,7 @@ describe("watchFiles option", () => { }); // should pass correct options to chokidar config - expect(chokidarMock.mock.calls[0][1]).toMatchSnapshot(); + expect(server.staticWatchers[0].options).toMatchSnapshot(); expect(response.status()).toMatchSnapshot("response status"); diff --git a/test/fixtures/watch-files-config/public/assets/example.js b/test/fixtures/watch-files-config/public/assets/example.js new file mode 100644 index 0000000000..346e384d2b --- /dev/null +++ b/test/fixtures/watch-files-config/public/assets/example.js @@ -0,0 +1 @@ +// test file diff --git a/types/lib/Server.d.ts b/types/lib/Server.d.ts index dd895e2e3c..80a7fbe2df 100644 --- a/types/lib/Server.d.ts +++ b/types/lib/Server.d.ts @@ -1493,7 +1493,7 @@ type StatsCompilation = import("webpack").StatsCompilation; type Stats = import("webpack").Stats; type MultiStats = import("webpack").MultiStats; type NetworkInterfaceInfo = import("os").NetworkInterfaceInfo; -type WatchOptions = import("chokidar").WatchOptions; +type WatchOptions = import("chokidar").ChokidarOptions; type FSWatcher = import("chokidar").FSWatcher; type ConnectHistoryApiFallbackOptions = import("connect-history-api-fallback").Options;