diff --git a/.github/workflows/nodejs.yml b/.github/workflows/nodejs.yml index 482976de74a..2a0b63099e2 100644 --- a/.github/workflows/nodejs.yml +++ b/.github/workflows/nodejs.yml @@ -61,7 +61,7 @@ jobs: fail-fast: true matrix: os: [ubuntu-latest, windows-latest, macos-latest] - node-version: [18.x, 20.x, 22.x, 24.x] + node-version: [20.x, 22.x, 24.x, 25.x] webpack-version: [latest] dev-server-version: [latest] diff --git a/package-lock.json b/package-lock.json index 17c38bd5894..cac2835a01f 100644 --- a/package-lock.json +++ b/package-lock.json @@ -23,7 +23,7 @@ "concat-stream": "^2.0.0", "cspell": "^9.4.0", "css-loader": "^7.1.2", - "del-cli": "^6.0.0", + "del-cli": "^7.0.0", "eslint": "^9.29.0", "eslint-config-webpack": "^4.5.0", "execa": "^9.6.1", @@ -48,7 +48,7 @@ "webpack-dev-server": "^5.1.0" }, "engines": { - "node": ">=18.12.0" + "node": ">=20.9.0" }, "funding": { "type": "opencollective", @@ -12285,14 +12285,15 @@ } }, "node_modules/del-cli": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/del-cli/-/del-cli-6.0.0.tgz", - "integrity": "sha512-9nitGV2W6KLFyya4qYt4+9AKQFL+c0Ehj5K7V7IwlxTc6RMCfQUGY9E9pLG6e8TQjtwXpuiWIGGZb3mfVxyZkw==", + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/del-cli/-/del-cli-7.0.0.tgz", + "integrity": "sha512-fRl4pWJYu9WFQH8jXdQUYvcD0IMtij9WEc7qmB7xOyJEweNJNuE7iKmqNeoOT1DbBUjtRjxlw8Y63qKBI/NQ1g==", "dev": true, "license": "MIT", "dependencies": { - "del": "^8.0.0", - "meow": "^13.2.0" + "del": "^8.0.1", + "meow": "^14.0.0", + "presentable-error": "^0.0.1" }, "bin": { "del": "cli.js", @@ -12305,6 +12306,19 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/del-cli/node_modules/meow": { + "version": "14.1.0", + "resolved": "https://registry.npmjs.org/meow/-/meow-14.1.0.tgz", + "integrity": "sha512-EDYo6VlmtnumlcBCbh1gLJ//9jvM/ndXHfVXIFrZVr6fGcwTUyCTFNTLCKuY3ffbK8L/+3Mzqnd58RojiZqHVw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=20" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/delayed-stream": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", @@ -21939,6 +21953,7 @@ "dev": true, "hasInstallScript": true, "license": "MIT", + "peer": true, "dependencies": { "@napi-rs/wasm-runtime": "0.2.4", "@yarnpkg/lockfile": "^1.1.0", @@ -28916,7 +28931,7 @@ "@types/ejs": "^3.1.5" }, "engines": { - "node": ">=18.12.0" + "node": ">=20.9.0" }, "funding": { "type": "opencollective", @@ -28955,7 +28970,7 @@ "@types/envinfo": "^7.8.1" }, "engines": { - "node": ">=18.12.0" + "node": ">=20.9.0" }, "funding": { "type": "opencollective", diff --git a/package.json b/package.json index 2b0aab8292e..24473d17682 100644 --- a/package.json +++ b/package.json @@ -59,7 +59,7 @@ "concat-stream": "^2.0.0", "cspell": "^9.4.0", "css-loader": "^7.1.2", - "del-cli": "^6.0.0", + "del-cli": "^7.0.0", "eslint": "^9.29.0", "eslint-config-webpack": "^4.5.0", "execa": "^9.6.1", @@ -87,6 +87,6 @@ "webpack": "5.x.x" }, "engines": { - "node": ">=18.12.0" + "node": ">=20.9.0" } } diff --git a/packages/create-webpack-app/package.json b/packages/create-webpack-app/package.json index 9d10088ae3e..9cfcbda9eb2 100644 --- a/packages/create-webpack-app/package.json +++ b/packages/create-webpack-app/package.json @@ -52,6 +52,6 @@ "@types/ejs": "^3.1.5" }, "engines": { - "node": ">=18.12.0" + "node": ">=20.9.0" } } diff --git a/packages/create-webpack-app/tsconfig.json b/packages/create-webpack-app/tsconfig.json index a41c8c48bc9..e8d69d9dc5f 100644 --- a/packages/create-webpack-app/tsconfig.json +++ b/packages/create-webpack-app/tsconfig.json @@ -3,10 +3,9 @@ "exclude": ["src/utils/__tests__"], "compilerOptions": { "outDir": "lib", - "rootDir": "src", - "module": "esnext" + "rootDir": "src" }, - "include": ["src"], + "include": ["./src"], "references": [ { "path": "../webpack-cli" diff --git a/packages/webpack-cli/package.json b/packages/webpack-cli/package.json index 985648902c9..0195e9a963d 100644 --- a/packages/webpack-cli/package.json +++ b/packages/webpack-cli/package.json @@ -62,7 +62,7 @@ } }, "engines": { - "node": ">=18.12.0" + "node": ">=20.9.0" }, "gitHead": "fb50f766851f500ca12867a2aa9de81fa6e368f9" } diff --git a/packages/webpack-cli/src/plugins/cli-plugin.ts b/packages/webpack-cli/src/plugins/cli-plugin.ts index 0f4796a3ea1..f304271d4f8 100644 --- a/packages/webpack-cli/src/plugins/cli-plugin.ts +++ b/packages/webpack-cli/src/plugins/cli-plugin.ts @@ -11,7 +11,8 @@ export default class CLIPlugin { } async setupBundleAnalyzerPlugin(compiler: Compiler) { - const { BundleAnalyzerPlugin } = require("webpack-bundle-analyzer"); + // @ts-expect-error No types right now + const { BundleAnalyzerPlugin } = (await import("webpack-bundle-analyzer")).default; const bundleAnalyzerPlugin = compiler.options.plugins.some( (plugin) => plugin instanceof BundleAnalyzerPlugin, @@ -25,7 +26,7 @@ export default class CLIPlugin { static #progressStates: [number, ...string[]][] = []; setupProgressPlugin(compiler: Compiler) { - const { ProgressPlugin } = compiler.webpack || require("webpack"); + const { ProgressPlugin } = compiler.webpack; const progressPlugin = compiler.options.plugins.some( (plugin) => plugin instanceof ProgressPlugin, ); diff --git a/packages/webpack-cli/src/types.ts b/packages/webpack-cli/src/types.ts index 0c91f03162f..ff53a762a18 100644 --- a/packages/webpack-cli/src/types.ts +++ b/packages/webpack-cli/src/types.ts @@ -47,7 +47,7 @@ interface IWebpackCLI { capitalizeFirstLetter: StringFormatter; checkPackageExists(packageName: string): boolean; getAvailablePackageManagers(): PackageManager[]; - getDefaultPackageManager(): PackageManager | undefined; + getDefaultPackageManager(): Promise; doInstall(packageName: string, options?: PackageInstallOptions): Promise; loadJSONFile(path: Path, handleError: boolean): Promise; tryRequireThenImport(module: ModuleName, handleError: boolean): Promise; diff --git a/packages/webpack-cli/src/webpack-cli.ts b/packages/webpack-cli/src/webpack-cli.ts index 39b2a793513..dd97feca1a0 100644 --- a/packages/webpack-cli/src/webpack-cli.ts +++ b/packages/webpack-cli/src/webpack-cli.ts @@ -226,6 +226,7 @@ class WebpackCLI implements IWebpackCLI { return false; } + // TODO remove me getAvailablePackageManagers(): PackageManager[] { const { sync } = require("cross-spawn"); @@ -252,9 +253,10 @@ class WebpackCLI implements IWebpackCLI { return availableInstallers; } - getDefaultPackageManager(): PackageManager | undefined { - const { sync } = require("cross-spawn"); + async getDefaultPackageManager(): Promise { + const { sync } = await import("cross-spawn"); + // TODO use async methods const hasLocalNpm = fs.existsSync(path.resolve(process.cwd(), "package-lock.json")); if (hasLocalNpm) { @@ -307,7 +309,7 @@ class WebpackCLI implements IWebpackCLI { } async doInstall(packageName: string, options: PackageInstallOptions = {}): Promise { - const packageManager = this.getDefaultPackageManager(); + const packageManager = await this.getDefaultPackageManager(); if (!packageManager) { this.logger.error("Can't find package manager"); @@ -319,10 +321,10 @@ class WebpackCLI implements IWebpackCLI { options.preMessage(); } - const prompt = ({ message, defaultResponse, stream }: PromptOptions) => { - const readline = require("node:readline"); + const { createInterface } = await import("node:readline"); - const rl = readline.createInterface({ + const prompt = ({ message, defaultResponse, stream }: PromptOptions) => { + const rl = createInterface({ input: process.stdin, output: stream, }); @@ -470,6 +472,7 @@ class WebpackCLI implements IWebpackCLI { return result || {}; } + // TODO remove me loadJSONFile(pathToFile: Path, handleError = true): T { let result; @@ -559,7 +562,7 @@ class WebpackCLI implements IWebpackCLI { defaultInformation.npmPackages = `{${defaultPackages.map((item) => `*${item}*`).join(",")}}`; - const envinfo = await import("envinfo"); + const envinfo = (await import("envinfo")).default; let info = await envinfo.run(defaultInformation, envinfoConfig); @@ -1231,8 +1234,8 @@ class WebpackCLI implements IWebpackCLI { }, ); } else if (this.#isCommand(commandName, WebpackCLI.#commands.serve)) { - const loadDevServerOptions = () => { - const devServer = require(WEBPACK_DEV_SERVER_PACKAGE); + const loadDevServerOptions = async () => { + const devServer = (await import(WEBPACK_DEV_SERVER_PACKAGE)).default; const options: Record = this.webpack.cli.getArguments( devServer.schema, @@ -1253,7 +1256,7 @@ class WebpackCLI implements IWebpackCLI { let devServerOptions = []; try { - devServerOptions = loadDevServerOptions(); + devServerOptions = await loadDevServerOptions(); } catch (error) { this.logger.error( `You need to install 'webpack-dev-server' for running 'webpack serve'.\n${error}`, @@ -1270,7 +1273,7 @@ class WebpackCLI implements IWebpackCLI { let devServerFlags: WebpackCLIBuiltInOption[] = []; try { - devServerFlags = loadDevServerOptions(); + devServerFlags = await loadDevServerOptions(); } catch { // Nothing, to prevent future updates } @@ -1312,7 +1315,19 @@ class WebpackCLI implements IWebpackCLI { return; } - const servers: (typeof DevServer)[] = []; + type DevServerConstructor = typeof import("webpack-dev-server"); + let DevServer: DevServerConstructor; + + try { + DevServer = (await import(WEBPACK_DEV_SERVER_PACKAGE)).default; + } catch (err) { + this.logger.error( + `You need to install 'webpack-dev-server' for running 'webpack serve'.\n${err}`, + ); + process.exit(2); + } + + const servers: InstanceType[] = []; if (this.needWatchStdin(compiler)) { process.stdin.on("end", () => { @@ -1323,18 +1338,6 @@ class WebpackCLI implements IWebpackCLI { process.stdin.resume(); } - const DevServer = require(WEBPACK_DEV_SERVER_PACKAGE); - - try { - // eslint-disable-next-line @typescript-eslint/no-unused-expressions - require(`${WEBPACK_DEV_SERVER_PACKAGE}/package.json`).version; - } catch (err) { - this.logger.error( - `You need to install 'webpack-dev-server' for running 'webpack serve'.\n${err}`, - ); - process.exit(2); - } - const compilers = this.isMultipleCompiler(compiler) ? compiler.compilers : [compiler]; const possibleCompilers = compilers.filter((compiler) => compiler.options.devServer); const compilersForDevServer = @@ -1426,7 +1429,7 @@ class WebpackCLI implements IWebpackCLI { await server.start(); - servers.push(server); + servers.push(server as unknown as InstanceType); } catch (error) { if (this.isValidationError(error as Error)) { this.logger.error((error as Error).message); @@ -2483,7 +2486,7 @@ class WebpackCLI implements IWebpackCLI { process.exit(2); } - const CLIPlugin = (await import("./plugins/cli-plugin.js")).default; + const { default: CLIPlugin } = (await import("./plugins/cli-plugin.js")).default; const internalBuildConfig = (item: Configuration) => { const originalWatchValue = item.watch; diff --git a/test/api/do-install.test.js b/test/api/do-install.test.js index 1f924476b2f..cf939d0ff47 100644 --- a/test/api/do-install.test.js +++ b/test/api/do-install.test.js @@ -5,7 +5,7 @@ const CLI = require("../../packages/webpack-cli/lib/webpack-cli"); const readlineQuestionMock = jest.fn(); -jest.mock("node:readline", () => ({ +jest.unstable_mockModule("node:readline", () => ({ createInterface: jest.fn().mockReturnValue({ question: readlineQuestionMock, close: jest.fn().mockReturnValue(undefined), @@ -34,7 +34,7 @@ describe("doInstall", () => { it("should prompt to install using npm if npm is package manager", async () => { readlineQuestionMock.mockImplementation((_questionTest, cb) => cb("y")); - getDefaultPackageManagerSpy.mockReturnValue("npm"); + getDefaultPackageManagerSpy.mockResolvedValue("npm"); const installResult = await cli.doInstall("test-package"); diff --git a/test/api/get-default-package-manager.test.js b/test/api/get-default-package-manager.test.js index 4e30c089abd..f471acfd7e1 100644 --- a/test/api/get-default-package-manager.test.js +++ b/test/api/get-default-package-manager.test.js @@ -42,63 +42,63 @@ describe("getPackageManager", () => { syncMock.mockClear(); }); - it("should find yarn.lock", () => { + it("should find yarn.lock", async () => { cwdSpy.mockReturnValue(testYarnLockPath); - expect(cli.getDefaultPackageManager()).toBe("yarn"); + expect(await cli.getDefaultPackageManager()).toBe("yarn"); expect(syncMock).not.toHaveBeenCalled(); }); - it("should find package-lock.json", () => { + it("should find package-lock.json", async () => { cwdSpy.mockReturnValue(testNpmLockPath); - expect(cli.getDefaultPackageManager()).toBe("npm"); + expect(await cli.getDefaultPackageManager()).toBe("npm"); expect(syncMock).not.toHaveBeenCalled(); }); - it("should find pnpm-lock.yaml", () => { + it("should find pnpm-lock.yaml", async () => { cwdSpy.mockReturnValue(testPnpmLockPath); - expect(cli.getDefaultPackageManager()).toBe("pnpm"); + expect(await cli.getDefaultPackageManager()).toBe("pnpm"); expect(syncMock).not.toHaveBeenCalled(); }); - it("should prioritize npm over pnpm", () => { + it("should prioritize npm over pnpm", async () => { cwdSpy.mockReturnValue(testNpmAndPnpmPath); - expect(cli.getDefaultPackageManager()).toBe("npm"); + expect(await cli.getDefaultPackageManager()).toBe("npm"); expect(syncMock).not.toHaveBeenCalled(); }); - it("should prioritize npm over yarn", () => { + it("should prioritize npm over yarn", async () => { cwdSpy.mockReturnValue(testNpmAndYarnPath); - expect(cli.getDefaultPackageManager()).toBe("npm"); + expect(await cli.getDefaultPackageManager()).toBe("npm"); expect(syncMock).not.toHaveBeenCalled(); }); - it("should prioritize yarn over pnpm", () => { + it("should prioritize yarn over pnpm", async () => { cwdSpy.mockReturnValue(testYarnAndPnpmPath); - expect(cli.getDefaultPackageManager()).toBe("yarn"); + expect(await cli.getDefaultPackageManager()).toBe("yarn"); expect(syncMock).not.toHaveBeenCalled(); }); - it("should prioritize npm with many lock files", () => { + it("should prioritize npm with many lock files", async () => { cwdSpy.mockReturnValue(testAllPath); - expect(cli.getDefaultPackageManager()).toBe("npm"); + expect(await cli.getDefaultPackageManager()).toBe("npm"); expect(syncMock).not.toHaveBeenCalled(); }); - it("should prioritize global npm over other package managers", () => { + it("should prioritize global npm over other package managers", async () => { cwdSpy.mockReturnValue(noLockPath); - expect(cli.getDefaultPackageManager()).toBe("npm"); + expect(await cli.getDefaultPackageManager()).toBe("npm"); expect(syncMock).toHaveBeenCalledTimes(1); }); - it("should throw error if no package manager is found", () => { + it("should throw error if no package manager is found", async () => { cwdSpy.mockReturnValue(noLockPath); syncMock.mockImplementation(() => { throw new Error("error"); @@ -107,7 +107,7 @@ describe("getPackageManager", () => { // Do not print warning in CI const consoleMock = jest.spyOn(console, "error").mockImplementation(() => {}); - expect(cli.getDefaultPackageManager()).toBeFalsy(); + expect(await cli.getDefaultPackageManager()).toBeFalsy(); expect(mockExit).toHaveBeenCalledWith(2); expect(consoleMock).toHaveBeenCalledTimes(1); expect(syncMock).toHaveBeenCalledTimes(3); // 3 calls for npm, yarn and pnpm diff --git a/tsconfig.json b/tsconfig.json index a07a3706fc7..2d2dca5aff7 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -2,17 +2,19 @@ "exclude": ["node_modules", "lib", "__tests__"], "files": [], "compilerOptions": { - "skipLibCheck": true, + "lib": ["es2023"], + "module": "nodenext", "target": "ES2022", - "module": "commonjs", - "lib": ["ES2022"], + "strict": true, - "rootDir": ".", - "outDir": "lib", - "moduleResolution": "node", + "esModuleInterop": true, + "skipLibCheck": true, + "moduleResolution": "nodenext", "resolveJsonModule": true, "allowSyntheticDefaultImports": true, - "esModuleInterop": true, + + "rootDir": ".", + "outDir": "lib", "baseUrl": ".", "paths": { "@webpack-cli/*": ["packages/*/src"]