一、使用背景
在前端开发过程中,经常会遇到如下错误:
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);
}
})();