解决端口被占用问题的 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);
  }
})();
相关推荐
景早13 小时前
vue 记事本案例详解
前端·javascript·vue.js
wangjialelele14 小时前
Qt中的常用组件:QWidget篇
开发语言·前端·c++·qt
乔冠宇14 小时前
vue需要学习的点
前端·vue.js·学习
用户479492835691514 小时前
同样是 #,锚点和路由有什么区别
前端·javascript
Hero_112714 小时前
在pycharm中install不上需要的包
服务器·前端·pycharm
爱上妖精的尾巴15 小时前
5-26 WPS JS宏数组元素添加删除应用
开发语言·前端·javascript·wps·js宏
是谁眉眼15 小时前
wpsapi
前端·javascript·html
谅望者15 小时前
Flexbox vs Grid:先学哪一个?CSS 布局完全指南(附可视化示例)
前端·css·html·css3·css布局·css flexbox·css grid
老华带你飞15 小时前
商城推荐系统|基于SprinBoot+vue的商城推荐系统(源码+数据库+文档)
java·前端·数据库·vue.js·spring boot·毕设·商城推荐系统
JS.Huang15 小时前
【JavaScript】Pointer Events 与移动端交互
前端·javascript