news 2026/6/21 13:28:31

Node.js + TypeScript 项目脚手架搭建指南:45分钟落地实践

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
Node.js + TypeScript 项目脚手架搭建指南:45分钟落地实践

1. 项目概述:为什么一个“空的 Node + TypeScript 项目”值得花 45 分钟认真搭?

你有没有过这种经历:接到一个新需求,要写个轻量 API 服务,或者做个 CLI 工具,甚至只是想验证一个算法逻辑——第一反应是mkdir my-project && cd my-project && npm init -y,然后随手npm install typescript @types/node --save-dev,跑个tsc --init,改两行tsconfig.json,再写个index.tsnpx tsc编译完发现node ./dist/index.js报错:Cannot find module 'fs'或者require is not defined?接着翻文档、查 Stack Overflow、试了三版tsconfig.json配置,最后发现是"module": "es2015""moduleResolution": "node"没配对,或者"typeRoots"指向错了……折腾一小时,连 Hello World 都没跑通。

这就是绝大多数人踩进的第一个坑:把 TypeScript 当成“带类型的 JavaScript”来用,却忽略了它是一套需要主动协商的编译契约系统。Node.js 运行的是 CommonJS(或 ESM)模块,而 TypeScript 默认生成 ES 模块代码;Node.js 的全局类型(如process,__dirname)不是开箱即用的,得靠@types/node显式声明;ESLint 不是摆设,它和 Prettier 的冲突会直接卡死你的git commit流程;而tsconfig.json里那 37 个字段,90% 的人只改过"target""outDir"—— 其余 35 个,全靠玄学生效。

我从 2016 年开始在生产环境用 TS 写 Node 服务,经手过 12 个不同规模的后端项目,从日均请求 200 的内部工具,到支撑百万级并发的实时消息网关。所有项目上线前的第一道门槛,从来不是业务逻辑,而是这个“最基础的脚手架”。它不炫技,但一旦出问题,排查成本远超业务代码本身。这篇文章,就是我把这十多年踩过的坑、验证过的配置、团队内部沉淀的 checklist,全部摊开讲透。不讲“TypeScript 是什么”,不讲“Node 是怎么运行的”,只聚焦一件事:如何在 45 分钟内,从零搭建一个可立即投入开发、CI 可直跑、团队成员拉下来就能npm run dev启动、且未来半年不会因配置问题被叫起来救火的 Node + TypeScript 项目。核心关键词就五个:Node、TypeScript、Express、tsconfig.json、ESLint—— 它们不是并列关系,而是层层咬合的齿轮:Node 是引擎,TS 是燃料标准,Express 是常用载具,tsconfig.json是油料配比表,ESLint 是出厂质检线。下面,我们一个齿轮一个齿轮地拧紧。

2. 整体架构设计与方案选型:为什么放弃“一键脚手架”,坚持手动搭建?

很多人看到标题第一反应是:“直接用create-node-appts-node-dev不香吗?”—— 确实香,但香在短期,苦在长期。我见过太多团队用ts-node-dev起步,三个月后因为热重载内存泄漏导致本地开发机风扇狂转;也见过用create-express-typescript-app初始化的项目,半年后升级 Node 18,ts-node因为不支持--enable-source-maps参数直接崩掉,整个调试流程瘫痪。这些“便利”背后,是大量隐藏的耦合和模糊的责任边界。所以我的方案非常明确:不依赖任何第三方脚手架,所有配置文件手写,所有依赖显式声明,所有构建步骤可追溯、可替换、可审计。这不是教条主义,而是基于三个硬性事实:

第一,Node 版本演进太快,脚手架维护永远滞后。Node 16 引入--enable-source-maps,Node 18 支持--watch原生监听,Node 20 推出--conditions自定义条件导出。而主流脚手架平均更新周期是 3-5 个月。你不可能为了等一个create-*包更新,卡住整个团队的 Node 升级节奏。手动搭建意味着你可以今天下午升级 Node 20,晚上就跑通tsc --build+node --watch组合,完全不受外部包约束。

第二,TypeScript 的配置粒度,决定了项目的可维护上限tsconfig.json不是开关列表,而是一张类型系统的“宪法”。比如"strict": true开启后,any类型会被禁止,但如果你同时没配"skipLibCheck": true,那么@types/node里的 2000+ 行声明文件就会被逐行检查,编译时间从 800ms 暴涨到 4.2s。再比如"module": "commonjs""moduleResolution": "node"必须成对出现,否则import fs from 'fs'在 TS 层能过,但tsc输出的 JS 里却是import fs from 'fs',而 Node 14+ 默认不支持这种语法,直接报错SyntaxError: Cannot use import statement outside a module。这些细节,没有哪个脚手架会在初始化时给你解释清楚,它们只会默默帮你选一个“看起来合理”的默认值,然后等你某天深夜收到报警才意识到问题根源。

第三,ESLint 和 Prettier 的协作,本质是工程规范的落地执行器。很多团队把 ESLint 当成“代码格式化工具”,这是巨大误解。ESLint 的核心价值在于静态规则拦截:比如@typescript-eslint/no-explicit-any能在let data: any = fetch()这行代码写下的瞬间就标红,而不是等 Code Review 时被人指出。而 Prettier 只负责代码风格统一:缩进用 2 还是 4,单引号还是双引号,对象属性换行位置。两者必须解耦:ESLint 检查逻辑正确性,Prettier 处理视觉一致性。如果用eslint-config-prettier一把梭哈禁掉所有格式规则,等于把“是否该用any”和“分号要不要”混为一谈,最终导致规则形同虚设。手动搭建,就是让你亲手把这两条线理清楚,让每一条规则都有明确归属和不可绕过的执行路径。

所以最终方案定为:四层结构,五项核心依赖。四层是:① Node 运行时(v18.17+ LTS);② TypeScript 编译器(v5.2+);③ Express 框架(v4.18+,不升级 v5 因其 ESM-only 设计尚未成熟);④ 构建与开发流(tsc+nodemonnode --watch)。五项核心依赖是:typescript(编译器)、@types/node(Node 全局类型)、@types/express(Express 类型)、eslint(规则引擎)、prettier(格式化器)。所有其他依赖,如ts-nodets-jestconcurrently,全部按需引入,绝不预装。这个结构看似“复古”,但它像一台老式机械表——每个齿轮的位置、齿数、咬合角度都清晰可见,出了问题,你能立刻定位到是游丝松了,还是擒纵叉卡住了,而不是对着一块黑屏智能手表干瞪眼。

3. 核心细节解析与实操要点:tsconfig.json的 12 个关键字段详解

tsconfig.json是整个项目的“心脏起搏器”,它不直接执行代码,但决定了 TypeScript 如何理解、检查、转换每一行代码。网上教程常把它当配置清单罗列,但真正的问题在于:为什么是这个值,而不是那个值?改了它,下游会发生什么连锁反应?下面我以一个经过 7 个生产项目验证的最小可行配置为蓝本,逐字段拆解其背后的工程逻辑。注意:这不是一份“抄了就能用”的模板,而是一份“改了就知道后果”的说明书。

3.1"compilerOptions":编译器行为的总开关

{ "compilerOptions": { "target": "ES2020", "lib": ["ES2020", "DOM"], "module": "commonjs", "skipLibCheck": true, "forceConsistentCasingInFileNames": true, "strict": true, "noImplicitReturns": true, "noFallthroughCasesInSwitch": true, "resolveJsonModule": true, "esModuleInterop": true, "allowSyntheticDefaultImports": true, "sourceMap": true, "outDir": "./dist", "rootDir": "./src", "declaration": true, "removeComments": false, "incremental": true, "tsBuildInfoFile": "./dist/.tsbuildinfo" } }
  • "target": "ES2020":这是最常被误配的字段。很多人设为"ESNext",以为能用最新语法。但 Node.js 的 V8 引擎版本和 TS 的target是两回事。Node 18.17 对应 V8 10.2,原生支持ES2022Array.prototype.at(),但不支持ES2023Array.fromAsync()"ES2020"是一个安全交集:它允许你使用BigIntglobalThisoptional chaining(?.)、nullish coalescing(??),同时确保tsc输出的 JS 代码能在 Node 16+ 上 100% 运行。计算依据很简单:查 Node.js 官方兼容表 ,找到你最低支持的 Node 版本(我们定为 16.14),取其支持的最高 ES 标准,再降一级作为target,留出缓冲空间。

  • "lib": ["ES2020", "DOM"]:这里有个反直觉点——为什么 Node 项目要加"DOM"?因为@types/node本身依赖 DOM 类型(如AbortSignalBlob),如果不加,tsc会报Cannot find name 'AbortController'。这不是 bug,而是@types/node的设计选择。"DOM"库在这里的作用,是提供这些跨环境的 Web API 类型定义,而非让你真去操作浏览器 DOM。实测下来,加了它,编译速度无损,类型覆盖率提升 15%。

  • "module": "commonjs":这是与 Node 运行时对齐的生死线。Node 14+ 已支持 ESM,但require()仍是事实标准,尤其在node_modules里 95% 的包都是 CJS 格式。设为"ES2020"会导致tsc输出import fs from 'fs',而 Node 默认不识别,必须加--experimental-specifier-resolution=node才能勉强运行,但 CI 环境往往不允许实验性参数。"commonjs"则输出const fs = require('fs'),与 Node 天然兼容。代价是无法使用顶层await,但对后端服务而言,async function main() { await startServer(); }完全够用。

  • "skipLibCheck": true:这是性能与安全的平衡点。@types/node有 2000+ 行,@types/express有 800+ 行,tsc默认会对它们逐行做类型检查。开启此选项,tsc只检查你写的代码,跳过node_modules/@types/*中的声明文件。实测:项目含 120 个.ts文件时,编译时间从 3.8s 降至 0.9s,而类型安全性几乎无损——因为@types包本身已由其维护者做了充分测试,你只需信任它的接口契约,无需重复校验其实现细节。

  • "strict": true:这是 TS 类型系统的“全功能模式”。它等价于同时开启 9 个子规则:"noImplicitAny""noImplicitThis""alwaysStrict""strictBindCallApply""strictNullChecks""strictFunctionTypes""strictPropertyInitialization""strictCheckedInference""noUncheckedIndexedAccess"。其中最关键的是"strictNullChecks": true,它让string | nullstring成为两个完全不同的类型,避免user.name.toUpperCase()user.namenull时崩溃。我坚持开启,因为关闭它等于主动放弃 TS 最核心的价值——在编译期捕获空值错误。代价是初期编码速度略慢,但一周后,你会习惯写if (user?.name)而不是if (user.name),这种习惯带来的稳定性提升,远超那几分钟的“手速”。

  • "esModuleInterop": true"allowSyntheticDefaultImports": true:这是解决 CJS/ESM 混合导入的经典组合。假设你import express from 'express',而express包是 CJS 格式(module.exports = function(){}),TS 默认会报错:“Module 'express' has no default export”。开启这两个选项后,TS 会自动为你生成一个合成的默认导出,等价于import * as express from 'express'; const app = express.default();。这是目前最平滑的兼容方案,比手动写import * as express from 'express'更符合直觉,也比require('express')更类型安全。

  • "sourceMap": true:这是调试体验的生命线。它让 Chrome DevTools 或 VS Code 能直接在.ts文件上打断点,而不是在编译后的.js文件里。关键点在于:"sourceMap"必须和"outDir""rootDir"配合使用。"rootDir": "./src"告诉 TS “源码从这里开始”,"outDir": "./dist"告诉它“编译结果放这里”,"sourceMap": true则生成./dist/index.js.map,其中包含从./dist/index.js行号映射回./src/index.ts行号的完整映射表。漏掉任何一个,断点都会失效。

  • "declaration": true:这个字段常被忽略,但它决定了你的项目能否被其他 TS 项目“优雅引用”。开启后,tsc不仅生成.js,还会生成.d.ts声明文件。比如你写了一个工具库my-utils,别人import { formatDate } from 'my-utils'时,IDE 才能自动提示formatDate的参数类型。对于纯应用项目,它非必需,但开启成本极低(编译时间+5%),且为未来可能的模块拆分埋下伏笔。

  • "incremental": true"tsBuildInfoFile": "./dist/.tsbuildinfo":这是大型项目的编译加速器。开启后,tsc会记录每次编译的依赖图和文件状态,下次只重新编译被修改的文件及其依赖者。实测:一个含 300 个文件的项目,首次全量编译 8.2s,第二次修改一个文件后增量编译仅 0.3s。".tsbuildinfo"文件必须指定路径,且不能放在node_modulesgit忽略目录里,否则增量失效。

提示:"resolveJsonModule": true允许你import data from './config.json',JSON 文件会被自动推导为Record<string, unknown>类型,避免手动写require('./config.json') as MyConfig。这是现代 Node 项目处理配置文件的推荐方式。

3.2"include""exclude":精准控制编译范围

{ "include": ["src/**/*"], "exclude": ["node_modules", "dist", "**/*.spec.ts", "**/__tests__/*"] }
  • "include": ["src/**/*"]:明确告诉 TS “只编译src目录下的所有.ts文件”。不要用"**/*.ts",否则node_modules里的.ts文件(如某些包的源码)也会被纳入,引发类型冲突。"src/**/*"是最安全、最易读的写法。

  • "exclude":这里有两个关键排除项。"dist"是必须的,否则tsc会尝试编译自己生成的.js文件(虽然它会跳过,但扫描耗时)。"**/*.spec.ts"是约定俗成的测试文件排除,因为测试文件通常不需要生成.d.ts,且jestvitest有自己的 TS 处理流程。注意:"exclude"不是“黑名单”,而是“编译器忽略列表”,它不影响tsc --noEmit的类型检查——也就是说,即使你排除了test/目录,tsc --noEmit仍会检查里面的类型错误,确保测试代码本身是类型安全的。

3.3"references":为多包项目预留的扩展接口

{ "references": [ { "path": "../shared-types" }, { "path": "../utils" } ] }

这个字段现在可以留空,但必须知道它的存在。当你项目增长到需要拆分为api-serverworkershared-types多个子包时,"references"就是连接它们的桥梁。比如api-server/tsconfig.json里写"references": [{ "path": "../shared-types" }],那么tsc --build会自动先编译shared-types,再编译api-server,并复用其生成的.d.ts文件,避免重复编译。这是 Lerna/Yarn Workspaces 项目的基石配置。现在不配,是为了保持单包项目的简洁;但知道它,是为了未来拆分时不踩坑。

4. 实操过程与核心环节实现:从npm initnpm run dev的完整链路

现在,我们把前面所有理论,变成可执行的命令流。整个过程严格控制在 45 分钟内,每一步都有明确目的和失败回滚方案。请打开终端,跟我一起操作。不要复制粘贴整段命令,要理解每一步在做什么

4.1 环境准备:Node 与 npm 的最小安全基线

首先确认你的 Node 版本。执行:

node -v npm -v

必须满足:node >= 18.17.0npm >= 9.6.7。如果不是,请先升级。不要用 nvm 安装多个版本来回切换——这是新手最大误区。生产项目必须锁定一个稳定版本。Node 18.x 是当前最成熟的 LTS,V8 引擎稳定,生态兼容性好,且官方支持到 2025 年 4 月。升级命令(macOS/Linux):

# 如果用 Homebrew brew update && brew upgrade node # 如果用官方安装包,去 https://nodejs.org/ 下载 .pkg/.tar.xz

Windows 用户请下载node-v18.17.0-x64.msi安装包,全程下一步即可。升级后,npm会自动更新到匹配版本。验证:

node -v # 应输出 v18.17.0 npm list -g npm # 应输出 9.6.7 或更高

注意:npm的全局安装路径必须可写。如果npm install -g typescriptEACCES错误,说明权限不足。不要用sudo npm install -g!正确做法是配置 npm 使用本地目录:

mkdir ~/.npm-global npm config set prefix '~/.npm-global' echo 'export PATH=~/.npm-global/bin:$PATH' >> ~/.bashrc source ~/.bashrc

这样所有全局包都装在你家目录下,彻底规避权限问题。

4.2 初始化项目与核心依赖安装

创建项目目录,进入并初始化:

mkdir my-node-ts-app && cd my-node-ts-app npm init -y

-y参数跳过所有交互,因为我们手动配置一切。此时package.json是最简形态。接下来安装五大核心依赖:

npm install --save-dev typescript @types/node @types/express eslint prettier npm install express

关键点解析:

  • --save-devtypescript@types/*eslintprettier都是开发时依赖,运行时不需要,所以加-D
  • @types/node@types/express必须与你安装的nodeexpress版本严格对应。npm install @types/node会自动安装最新兼容版,但建议锁死:npm install @types/node@18.14.6(对应 Node 18.17)。
  • express是运行时依赖,不加-D,因为它要被打包进生产镜像。

安装完成后,检查node_modules结构:

ls node_modules/@types # 应看到 node/ express/ 两个文件夹 ls node_modules/typescript # 应看到 lib/ bin/ 等目录

如果@types/node缺失,tsc会报Cannot find name 'process';如果@types/express缺失,import express from 'express'会报Could not find a declaration file for module 'express'。这是两个最常见的“找不到类型”错误,根源都在这一步。

4.3 手写tsconfig.json:一行一行敲出来的安全感

在项目根目录,创建tsconfig.json文件。不要用tsc --init生成!它的默认配置过于宽泛,且包含大量注释和废弃字段,干扰判断。我们手写一个精简版:

{ "compilerOptions": { "target": "ES2020", "lib": ["ES2020", "DOM"], "module": "commonjs", "skipLibCheck": true, "forceConsistentCasingInFileNames": true, "strict": true, "noImplicitReturns": true, "noFallthroughCasesInSwitch": true, "resolveJsonModule": true, "esModuleInterop": true, "allowSyntheticDefaultImports": true, "sourceMap": true, "outDir": "./dist", "rootDir": "./src", "declaration": true, "removeComments": false, "incremental": true, "tsBuildInfoFile": "./dist/.tsbuildinfo" }, "include": ["src/**/*"], "exclude": ["node_modules", "dist", "**/*.spec.ts", "**/__tests__/*"] }

保存后,执行首次编译测试:

npx tsc --noEmit

--noEmit参数表示“只检查类型,不生成 JS 文件”。如果终端没有任何输出,恭喜,TS 配置通过!如果有报错,90% 是@types缺失或路径错误。常见错误及修复:

  • error TS2307: Cannot find module 'fs'→ 检查@types/node是否安装,"lib"是否包含"ES2020"
  • error TS2688: Cannot find type definition file for 'node'→ 检查node_modules/@types/node是否存在,"typeRoots"未被意外覆盖。
  • error TS18003: No inputs were found in config file→ 检查"include"路径是否拼写错误,src/目录是否存在。

4.4 创建源码骨架与首个可运行服务

创建src目录及入口文件:

mkdir src touch src/index.ts

编辑src/index.ts,写入最简 Express 服务:

import express from 'express'; const app = express(); const PORT = process.env.PORT ? parseInt(process.env.PORT, 10) : 3000; app.get('/', (req, res) => { res.json({ message: 'Hello from TypeScript!' }); }); app.listen(PORT, () => { console.log(`Server running on http://localhost:${PORT}`); });

注意:这里用了import express from 'express',得益于"esModuleInterop": true。如果不用此配置,就得写import * as express from 'express',然后const app = express(),代码更冗长。

现在,编译并运行:

npx tsc # 生成 dist/index.js node dist/index.js

访问http://localhost:3000,应看到 JSON 响应。这是第一个里程碑:TypeScript 代码成功编译为 Node 可执行的 JS,并正确运行

4.5 配置 ESLint 与 Prettier:让代码规范成为肌肉记忆

ESLint 不是锦上添花,而是防止团队代码风格撕裂的底线。我们采用业界最稳定的组合:eslint+@typescript-eslint/parser+prettier。安装依赖:

npm install --save-dev eslint @typescript-eslint/parser @typescript-eslint/eslint-plugin prettier eslint-config-prettier eslint-plugin-prettier

创建.eslintrc.cjs(注意是.cjs,不是.js,因为 ESLint 6+ 要求配置文件为 CommonJS 格式):

module.exports = { root: true, parser: '@typescript-eslint/parser', parserOptions: { ecmaVersion: 2020, sourceType: 'module', project: './tsconfig.json', }, plugins: ['@typescript-eslint', 'prettier'], extends: [ 'eslint:recommended', 'plugin:@typescript-eslint/recommended', 'plugin:prettier/recommended', ], rules: { // 关键规则:禁止 any,强制函数返回类型,禁止 console '@typescript-eslint/no-explicit-any': 'error', '@typescript-eslint/explicit-function-return-type': ['warn', { allowExpressions: true }], 'no-console': ['warn', { allow: ['warn', 'error'] }], // Prettier 规则已由 plugin:prettier/recommended 统一管理 }, ignorePatterns: ['dist/', 'node_modules/'], };

创建.prettierrc

{ "semi": true, "singleQuote": true, "tabWidth": 2, "printWidth": 80, "trailingComma": "es5" }

创建.prettierignore

dist/ node_modules/

现在,添加 npm script 到package.json

"scripts": { "lint": "eslint \"src/**/*.{ts,tsx}\"", "lint:fix": "eslint \"src/**/*.{ts,tsx}\" --fix", "prettier:check": "prettier --check \"src/**/*.{ts,tsx}\"", "prettier:write": "prettier --write \"src/**/*.{ts,tsx}\"" }

执行检查:

npm run lint npm run prettier:check

首次运行,大概率会报一堆格式错误(如分号缺失、引号不一致)。执行自动修复:

npm run lint:fix npm run prettier:write

再次npm run lint,应无错误。此时,你的代码已通过双重校验:ESLint 确保逻辑安全,Prettier 确保视觉统一。

4.6 开发工作流:npm run dev的三种实现与选型逻辑

生产环境用node dist/index.js,但开发时需要热重载。这里有三个方案,我逐一分析其适用场景:

方案一:nodemon+tsc --watch(推荐新手)

npm install --save-dev nodemon

package.json中添加:

"scripts": { "dev": "concurrently \"npm run build:watch\" \"npm run serve\"", "build:watch": "tsc --watch", "serve": "nodemon dist/index.js" }

concurrently让两个进程并行:tsc --watch监听.ts文件变化并编译,nodemon监听dist/下的.js文件变化并重启。优点:逻辑清晰,各司其职;缺点:需要额外安装concurrently,且nodemon重启有 200ms 延迟。

方案二:ts-node-dev(适合快速原型)

npm install --save-dev ts-node-dev
"scripts": { "dev": "ts-node-dev --respawn --transpile-only --ignore-watch node_modules src/index.ts" }

ts-node-devts-node的增强版,它直接在 Node 进程中编译 TS,无需先生成.js。优点:启动快,修改即生效;缺点:--transpile-only跳过类型检查,可能掩盖类型错误,且内存占用高,长时间运行后易 OOM。

方案三:Node 20+ 原生--watch(未来首选)如果你的 Node 版本 >= 20.0,这是最干净的方案:

"scripts": { "dev": "node --watch --loader ts-node/esm src/index.ts" }

Node 原生--watch监听文件变化,ts-node/esm作为 loader 动态编译。优点:零依赖,启动最快;缺点:要求 Node 20+,且ts-node需要额外配置tsconfig.json"module": "ES2020"(与之前commonjs冲突),故不推荐在当前项目中混用。

我的选择是方案一。理由:tsc --watch是 TypeScript 官方推荐的增量编译方案,类型检查不丢失;nodemon是 Node 生态最成熟的进程管理器,稳定可靠;concurrently虽多一个依赖,但换来的是清晰的职责分离和可预测的重启行为。执行:

npm run dev

修改src/index.ts中的message,保存,终端会显示restarting due to changes...,几秒后刷新页面,新内容即生效。

4.7 构建与部署脚本:从npm run build到 Docker 镜像

一个可交付的项目,必须有确定的构建产物。npm run build应该生成一个纯净的dist/目录,里面只有.js.js.map.d.ts文件,不含任何.ts源码。我们的package.json脚本:

"scripts": { "build": "tsc --build", "start": "node dist/index.js", "prepare": "npm run build" }

"prepare"是 npm 的生命周期钩子,在npm install时自动执行,确保任何人git clonenpm install,就自动生成dist/"build"使用tsc --build,它会读取tsconfig.json"incremental""tsBuildInfoFile",实现极速增量编译。

对于 Docker 部署,Dockerfile极简:

FROM node:18-alpine WORKDIR /app COPY package*.json ./ RUN npm ci --only=production COPY dist ./ EXPOSE 3000 CMD ["node", "index.js"]

关键点:npm ci --only=production只安装dependencies,跳过devDependencies,镜像体积减少 60%;COPY dist ./只复制编译产物,不包含源码和node_modules,安全且轻量。构建并运行:

docker build -t my-node-ts-app . docker run -p 3000:3000 my-node-ts-app

5. 常见问题与排查技巧实录:那些让我凌晨三点爬起来的 Bug

再完美的配置,也逃不过现实世界的毒打。以下是我在真实项目中遇到的 7 个高频、隐蔽、且网上答案大多错误的典型问题,附带我的排查思路和终极解法。它们不是“可能遇到”,而是“一定会遇到”,早知道,少熬夜。

5.1 问题:tsc编译通过,但node dist/index.jsReferenceError: __dirname is not defined

现象:你在src/index.ts中写了console.log(__dirname)tsc无报错,但运行时报错。
原因__dirname是 Node.js 的 CommonJS 全局变量,而 TypeScript 默认不为其提供类型定义。虽然代码能运行,但 TS 不认识它,所以tsc不报错,但node运行时发现它是undefined
排查:执行node -p "console.log(__dirname)",确认__dirname在当前环境下可用。然后检查tsconfig.json"lib"是否包含"ES2020"(它隐含了 Node 环境类型)。
解法:在src/index.ts顶部添加类型声明:

declare const __dirname: string;

或者,更规范的做法是,在src目录下创建global.d.ts

// src/global.d.ts declare global { namespace NodeJS { interface Global { __dirname: string; __filename: string; require: NodeRequire; module: NodeModule; } } }

然后确保tsconfig.json"include"包含"src/global.d.ts"。这样,所有.ts文件都能识别__dirname

5.2 问题:npm run dev启动后,修改代码,nodemon不重启

现象:保存src/index.ts,终端无任何反应,nodemon像睡着

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/6/21 13:20:28

多阶段随机优化新突破:MLMC方法如何高效计算条件期望

1. 项目概述&#xff1a;当随机优化遇上“维度诅咒” 在金融衍生品定价、能源系统调度、供应链风险管理这些领域&#xff0c;我们经常需要解决一个核心问题&#xff1a;如何在充满不确定性的未来&#xff0c;做出最优的决策&#xff1f;这类问题通常被建模为 多阶段随机优化 …

作者头像 李华
网站建设 2026/6/21 13:11:59

Ubuntu 18.04 下用 apt 精准安装 OpenJDK 11 的避坑指南

1. 项目概述&#xff1a;为什么在 Ubuntu 18.04 上用apt装 Java 不是“点几下就完事”的事在 Ubuntu 18.04 环境下执行sudo apt install default-jre或sudo apt install openjdk-11-jdk&#xff0c;表面看只是敲两行命令的事——但如果你真这么干了&#xff0c;十有八九会在后续…

作者头像 李华
网站建设 2026/6/21 13:06:17

DeepSeek-Coder:从代码理解到智能生成的革命性AI编程助手

DeepSeek-Coder&#xff1a;从代码理解到智能生成的革命性AI编程助手 【免费下载链接】DeepSeek-Coder DeepSeek Coder: Let the Code Write Itself 项目地址: https://gitcode.com/GitHub_Trending/de/DeepSeek-Coder 在当今软件开发领域&#xff0c;代码生成工具正在从…

作者头像 李华
网站建设 2026/6/21 13:05:56

如何在Firefox浏览器中免费下载Sketchfab模型:开源工具完全指南

如何在Firefox浏览器中免费下载Sketchfab模型&#xff1a;开源工具完全指南 【免费下载链接】sketchfab sketchfab download userscipt for Tampermonkey by firefox only 项目地址: https://gitcode.com/gh_mirrors/sk/sketchfab 你是否曾在Sketchfab平台上看到惊艳的3…

作者头像 李华
网站建设 2026/6/21 13:03:29

DeepSeek本地部署实战:Ollama+ChatBox零基础跑通R1模型

1. 项目概述&#xff1a;为什么“DeepSeek本地部署”成了今年最值得动手的AI实践&#xff1f;最近三个月&#xff0c;我在技术社区和私聊里被问得最多的问题&#xff0c;不是“哪个大模型最强”&#xff0c;而是“DeepSeek怎么在自己电脑上跑起来”。不是用网页版&#xff0c;不…

作者头像 李华