我有一支技术全面、经验丰富的小型团队,专注高效交付中等规模外包项目,有需要外包项目的可以联系我
每次克隆一个“看起来干净”的 starter 仓库,我都要浪费半天在同一批小陷阱上:版本漂移、脆弱的 fetch、混乱的模块、形同虚设的配置。 一台机器能跑,换台机器就炸。熟悉吗?
下面把我每次首日就会修的 10 个典型错误摊开讲透, 目的只有一个——让第一次运行,安静得像没事发生。
- 不固定 Node 版本,团队环境必漂移
今天能过、明天黄;你用的包管家不同、Node 小版本换个警告、原生扩展重编译,谁先崩谁先赢。 我现在一律钉死运行时,所有人跑同一套位。
.nvmrc 或 .node-version
18.20.3
// package.json 片段
{
“engines”: { “node”: “>=18.20 <19” }
}
在 CI 里强制版本,版本不对就打印清晰提示并 fail。 从此“works on my machine”回复显著减少,安装可复现。
- 别再随手混用 ESM 和 CommonJS
今天 require,明天 import,模块系统不是心情切换键。模块类型是包级或文件级,请选一个阵营站稳。
// 选 ESM
{ “type”: “module” }
// src/app.js
import express from “express”; // ESM
或者:
// 选 CJS
{ “type”: “commonjs” }
const express = require(“express”);
新代码优先 ESM,别用无扩展导入;测试与源码保持一致。 这样路径解析不迷路,构建错误也少很多。 - 没有超时的 fetch,就是隐形内存漏
默认 fetch 永不超时:重试排队、Tab 卡死、Lambda 拖爆。每次请求都带 AbortController 与预算毫秒。
// http.js
export async function getJson(url, ms = 3000, tries = 2) {
for (let i = 0; i <= tries; i++) { const c = new AbortController(); const t = setTimeout(() => c.abort(), ms);
try {
const r = await fetch(url, { signal: c.signal });
if (!r.ok) throw new Error(HTTP ${r.status});
return await r.json();
} catch (e) {
if (i === tries) throw e;
} finally {
clearTimeout(t);
}
}
}
冷启动稳了,丢包时 p95 延迟不再飙。按服务设默认超时,日志里记预算而不只是 URL。
图片 - 没有 env 模式/schema,配置全靠猜
process.env 散落各处,手抖一个 key,应用带着空值就起飞。把校验集中在启动做掉。
// config.js
const required = [“PORT”, “DB_URL”];
for (const k of required) {
if (!process.env[k]) throw new Error(Missing ${k});
}
export const cfg = {
port: Number(process.env.PORT),
dbUrl: process.env.DB_URL
};
从此误配回滚少。快速失败、一次性打印汇总配置,但记得不打敏感值。
- 没有 pre-commit 统一 lint,垃圾进主干
样式口水仗、微 bug 混进 main。一个钩子把格式 + 修复拦在本地。
// package.json
{
“scripts”: { “lint”: “eslint .”, “fmt”: “prettier -w .” },
“lint-staged”: { “*.{js,ts,jsx,tsx,json}”: [“prettier -w”, “eslint –fix”] }
}
PR 审核时间缩短,CI 队列不卡;同样命令在 CI 再跑一遍,避免“本地钩子能过、线上翻车”。 - 开发与生产构建不一致,“只在生产复现”的鬼
本地走 A 工具,线上走 B 工具;source map、env 标志、side effects 全不一样,追 bug 像捉迷藏。
+———————-+ +———————-+
| dev: vite + esbuild | -> | prod: webpack + Ters |
| ESM/HMR | | CJS/Minify |
+———————-+ +———————-+
选同一套链路打 dev/prod,或镜像一致的 flags,保证产物等价。 “只在 prod 崩”这类工单会明显减少。 - Promise 批处理不设防,错误直接掉地上
到处 await foo() 没边界;Promise.all 一炸整批失败且日志寂静。非全或无的场景请用 allSettled。
const tasks = urls.map(u => getJson(u));
const results = await Promise.allSettled(tasks);
const ok = results.filter(r => r.status === “fulfilled”).map(r => r.value);
const bad = results.filter(r => r.status === “rejected”);
if (bad.length) console.warn(“partial failure”, bad.length);
可降级任务继续出结果,错误率也有数可看。Promise.all 仅用于真·原子流程。
- 路由/目录无拓扑,后来一定烂
一开始“看着挺清爽”,再过两周循环依赖、死文件、加载时副作用全来了。
/src
/routes
users.js -> controllers/users.js
orders.js -> controllers/orders.js
/controllers -> services -> lib
/lib (routes 禁止反向引用)
单向依赖:routes → controllers → services → lib;lib 保持叶子、不进口上层。 冷路径延迟降低,因为加载期不再干多余活。 - JSON 解析不设限,CPU 被大包拖死
默认把巨型 payload 全丢给 JSON.parse,一个坏包就把 worker 卡住。限制体积 + 安全解析是标配。
// 限制请求体
app.use(express.json({ limit: “200kb” }));
export function safeJson(s) {
try { return JSON.parse(s); }
catch { return null; }
}
吞吐在流量峰值仍稳,事件循环不被拖慢。 按真实场景测量,在均值 +20%设上限,丢包只打一次警。
- 把“从未使用”的巨型依赖打进包
一个日期库 + 一个全量 UI 套件,首屏直接增肥。
pkg size
dayjs 7kb
date-fns 24kb
moment 67kb
优先小而可 tree-shake的包,按路径导入而非整包抱走。 移动端首屏时间明显改善,低端机不再嗡嗡叫。
上线前,别忘了这几件“无聊但要命”的事
钉死运行时:Node 版本明确、CI 强校验
给网络设预算:每个 fetch 都有超时和重试策略
配置集中校验:fail fast,打印可见、掩盖敏感
模块规则要“无聊”:不混风格,构建链 dev/prod 一致
Promise 用意图驱动:all = 原子,全局流;allSettled = 可降级,多源流
边缘字节要抠:小包、按需、tree-shake 友好
这些默认值到位,下一次 git clone 就会平平无奇(这是夸奖); 而你的生产监控,会更像一条直线,而不是过山车。
全栈AI·探索:涵盖动效、React Hooks、Vue 技巧、LLM 应用、Python 脚本等专栏,案例驱动实战学习,点击二维码了解更多详情。
声明:来自JavaScript 每日一练,仅代表创作者观点。链接:https://eyangzhen.com/3928.html