解决端口被占用问题的 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);
  }
})();
相关推荐
学如逆水,不进则退9 小时前
在线免费音频剪辑:NBTools 可视化波形裁剪 MP3/WAV,本地处理免上传
前端·音视频
Mh10 小时前
虚拟滚动真的比普通滚动性能更好吗?
前端·javascript·性能优化
pe7er10 小时前
React + Ant Design 中的 IME (输入法合成)安全输入组件
前端
Super 含11 小时前
Android 启动优化(二):TTID、TTFD 与 Macrobenchmark 启动性能测量
前端
Python私教11 小时前
别急着加 llms.txt:企业官网面向 AI 搜索的工程清单
前端·人工智能·seo
东方小月11 小时前
从零开发一个 Coding Agent(十三):实现安全的 read 文件读取工具
前端·人工智能·全栈
东方小月11 小时前
从零开发一个 Coding Agent(十二):实现版本化 JSONL 与真实 CLI 入口
前端·设计模式·前端框架
FL162386312912 小时前
室内易燃物识别易燃评估室内易燃程度识别分割数据集labelme格式1015张85类别
java·服务器·前端
用户0595401744612 小时前
把 AI 长期记忆去重测试从 20 分钟压到 40 秒,重复率从 18% 干到 1.5%
前端·css
kyriewen12 小时前
我把 AI 写的并发请求控制器手写了一遍——3 个语义我当时根本讲不清
前端·javascript·面试