解决端口被占用问题的 Webpack 启动脚本

一、使用背景

在前端开发过程中,经常会遇到如下错误:

perl 复制代码
Error: listen EADDRINUSE: address already in use :::3000

这意味着端口 3000 已被其他进程占用,Webpack 开发服务器无法启动。

为了解决这一问题,编写了一个启动脚本 start-dev.js,可在启动时自动检测端口状态,并根据策略自动选择下一个可用端口或强制释放占用端口,从而确保开发服务器顺利启动。

二、使用方法

1️⃣ 在 package.json 中添加启动脚本

json 复制代码
{
  "scripts": {
    "start:dev": "node scripts/start-dev.js"
  }
}

2️⃣ 默认启动方式

arduino 复制代码
npm run start:dev

三、命令行参数说明

参数名 格式 默认值 说明
--port --port=<number> 3000 指定开发服务器启动端口
--mode --mode=<increment|kill> increment 端口冲突处理模式

模式一:increment(默认模式)

检测到端口被占用时,自动递增选择下一个空闲端口。

css 复制代码
node scripts/start-dev.js --port=3000 --mode=increment

输出:

yaml 复制代码
⚠️ 端口 3000 已被占用,自动切换到 3001
🚀 启动 webpack 开发服务器 (端口 3001)...

模式二:kill

检测到端口被占用时,自动终止占用该端口的进程后重新启动。

bash 复制代码
node scripts/start-dev.js --port=3000 --mode=kill

输出:

yaml 复制代码
✅ 已终止占用进程 PID: 45822
🚀 启动 webpack 开发服务器 (端口 3000)...

⚠️ 注意: kill 模式仅建议在本地开发环境使用,避免误关闭其他关键程序。

🧾 四、代码

javascript 复制代码
#!/usr/bin/env node
// scripts/start-dev.js
/**
 * 自动启动 webpack 开发服务器脚本(支持端口冲突处理模式)
 * 功能:
 * 1. 支持命令行指定端口 (--port=3000)
 * 2. 支持端口冲突处理模式:(--mode=increment)
 *    - increment(默认):端口被占用时自动选择下一个空闲端口
 *    - kill:端口被占用时尝试 kill 占用进程再使用该端口
 * 3. 输出局域网 IP + 本地访问 URL
 */

const { execSync } = require("child_process");
const portfinder = require("portfinder");
const os = require("os");

// ===== 1️⃣ 解析命令行参数 =====
let TARGET_PORT = 3000;
let MODE = "increment"; // 默认行为:端口递增
process.argv.forEach((arg) => {
  if (arg.startsWith("--port=")) {
    TARGET_PORT = parseInt(arg.split("=")[1], 10);
  } else if (arg.startsWith("--mode=")) {
    const m = arg.split("=")[1];
    if (m === "increment" || m === "kill") MODE = m;
  }
});

// ===== 2️⃣ 获取本机局域网 IP =====
function getLocalIP() {
  const nets = os.networkInterfaces();
  for (const name of Object.keys(nets)) {
    for (const net of nets[name]) {
      if (net.family === "IPv4" && !net.internal) return net.address;
    }
  }
  return "localhost";
}

// ===== 3️⃣ 启动逻辑 =====
(async () => {
  try {
    let port = TARGET_PORT;

    if (MODE === "increment") {
      // 自动递增模式
      const freePort = await portfinder.getPortPromise({ port });
      if (freePort !== port) {
        console.log(`⚠️ 端口 ${port} 已被占用,自动切换到 ${freePort}`);
        port = freePort;
      }
    } else if (MODE === "kill") {
      // 强制 kill 模式
      try {
        const pidResult = execSync(`lsof -ti tcp:${port} || true`)
          .toString()
          .trim();
        if (pidResult) {
          const pidList = pidResult.split("\n");
          pidList.forEach((pid) => {
            if (pid) {
              execSync(`kill -9 ${pid}`);
              console.log(`✅ 已终止占用进程 PID: ${pid}`);
            }
          });
        } else {
          console.log(`⚠️ 端口 ${port} 未被占用`);
        }
      } catch (e) {
        console.warn("⚠️ 自动关闭进程失败,请手动检查");
      }
    }

    const localIP = getLocalIP();
    console.log(`🚀 启动 webpack 开发服务器 (端口 ${port})...`);
    console.log(`🔗 本地访问:http://localhost:${port}`);
    console.log(`🔗 局域网访问:http://${localIP}:${port}`);

    execSync(`npx webpack serve --port ${port}`, { stdio: "inherit" });
  } catch (err) {
    console.error("❌ 启动失败:", err.message);
  }
})();
相关推荐
沢田纲吉2 小时前
《LLVM IR 学习手记(六):break 语句与 continue 语句的实现与解析》
前端·c++·llvm
火锅小王子2 小时前
目标筑基:从0到1学习GoLang (入门 Go语言+GoFrame开发服务端+ langchain接入)
前端·后端·openai
温宇飞2 小时前
CSS 属性分类
前端
鹏多多2 小时前
使用React-OAuth进行Google/GitHub登录的教程和案例
前端·javascript·react.js
晓得迷路了3 小时前
栗子前端技术周刊第 101 期 - React 19.2、Next.js 16 Beta、pnpm 10.18...
前端·javascript·react.js
玲小珑3 小时前
LangChain.js 完全开发手册(十四)生产环境部署与 DevOps 实践
前端·langchain·ai编程
亿元程序员3 小时前
有了AI,游戏开发新人还有必要学Cocos游戏开发吗?
前端
Mike_jia3 小时前
Alist终极指南:一键聚合20+云存储,打造私有化文件管理中枢
前端
IT_陈寒4 小时前
Redis性能翻倍秘籍:10个99%开发者不知道的冷门配置优化技巧
前端·人工智能·后端