news 2026/9/19 10:25:50

2026年可删的5个npm包:原生Node.js替代方案全解析

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
2026年可删的5个npm包:原生Node.js替代方案全解析

2026 年,很多项目的 package.json 还是一副臃肿的样子:几百个依赖躺在里面,其中真正在业务代码里 import 过的,可能不到三分之一。每次npm install跑完,node_modules 动辄几百 MB,npm audit一检又是一堆 outdated 提醒,看着就头大。

这两年我陆续清理过几个中大型前端和 Node 项目,发现一个很明显的趋势:早些年必须靠 npm 包才能搞定的能力,现在 Node.js 和浏览器原生已经给得很完整了。有些包当年是神兵利器,放到 2026 年再看,反而成了必须维护的"历史包袱"——引入它们不仅增加安装体积、升级风险,还得在脚手架和 CI 里多养一个依赖。

这篇文章我把真正可以动手删掉的 5 个 npm 包整理了出来,每个都给了原生替代方案、版本要求和实操中的坑。适合正在做依赖瘦身、想要降低项目复杂度、或者单纯被 node_modules 体积逼疯的开发者参考。

1. 先说结论:2026 年,哪些包真的可以删了

先说清楚,我不是无脑"零依赖"党。像 React、Vue、Express 这种核心框架不在讨论范围里,因为它们解决的问题本身足够复杂,自研替代的成本远高于收益。但我下面列出的这 5 个包,属于"原生能力已经覆盖了 90% 以上使用场景"的典型,大多数项目删掉之后几乎感觉不到变化。

包名典型用途原生替代方案备注
rimraf递归删除目录/文件fs.rmSync/fs.promises.rmNode 14.14+ 可用
dotenv加载 .env 环境变量node --env-file/process.loadEnvFile()Node 20.6+ 可用
nodemon文件变更自动重启node --watchNode 18.11+ 可用,22 稳定
uuid生成 UUIDcrypto.randomUUID()Node 14.17+ / 现代浏览器
axios(部分场景)HTTP 请求原生fetchNode 18+ / 所有现代浏览器

为什么是这 5 个?我判断一个包该不该删,主要看三条标准。

第一,原生 API 的功能覆盖度是否足够。如果一个包 80% 的用法在原生环境里都有等价写法,那就值得认真评估替换;如果只是 API 风格不同,比如 Promise 回调的写法差异,那更是可以平滑迁移。

第二,这个包是否已经成为"维护负担"。很多包本身没问题,但它会带一堆传递依赖,比如早期版本 rimraf 会依赖 glob、graceful-fs 等一长串包,每次npm audit都提示这个那个要升级。删掉一个包,等于顺便清掉了一整棵依赖子树。

第三,替换的迁移成本是否可控。如果替换涉及大规模重写业务代码,那不管原生多好都先放一放;如果替换只需要改一个工具函数、一个配置项,那就是动刀的最佳时机。下面这 5 个都符合"迁移成本低、收益明显"的特征。

2. 逐个拆解:5 个包的替代方案与实操细节

2.1 用 fs.rmSync 替换 rimraf

rimraf 几乎是前端项目的标配工具,主要用途就一个:递归删除文件或目录。比如打包前的 dist 清理、测试临时目录清理、构建脚本里的rm -rf跨平台替代。当年它的存在是因为 Node.js 的fs.rmdir对非空目录直接报错,想删整个目录只能自己递归遍历,或者靠 rimraf 这种 C 库封装。

但 Node 14.14.0 开始,原生fs.rmSync就支持了recursive: trueforce: true,Node 16 之后更是稳定可用。也就是说,你完全可以在构建脚本里这样写:

import { rmSync } from 'node:fs'; rmSync('dist', { recursive: true, force: true });

这一行就等价于老的 rimraf 用法。force: true对应rm -rf里的-f,意思是目标不存在时也不抛错。我见过不少人的构建脚本还在专门调rimraf ./dist,其实换成上面的写法,不仅能少装一个包,还能避免 rimraf 在某些环境下 shell 命令解析带来的 Windows 兼容性问题。

如果你习惯用 Promise 风格,那就用fs/promisesrm

import { rm } from 'node:fs/promises'; await rm('dist', { recursive: true, force: true });

我实际在几个项目里替换后,package.json的 scripts 从这样:

{ "clean": "rimraf dist && rimraf coverage" }

变成了这样:

{ "clean": "node -e \"require('fs').rmSync('dist',{recursive:true,force:true}); require('fs').rmSync('coverage',{recursive:true,force:true})\"" }

不过说实在的,直接写在node -e里可读性很差。更推荐放在项目根目录的scripts/clean.mjs这种统一脚本里,或者用 Node 22+ 支持的node --run配合一个简单的清理函数,维护起来会舒服很多。

注意:fs.rmSyncrecursive选项在 Node 14 里还带实验性警告,Node 16 之后才彻底稳定。如果你的 CI 镜像还停留在 Node 14.13 以下,建议先升级运行时再删 rimraf,别为了省一个包把线上构建搞挂了。

2.2 用 --env-file 替换 dotenv

dotenv 是另一个"曾经必需、现在可以删"的典型。它的作用是从项目根目录的.env文件里读取键值对,注入到process.env中。几乎所有 Node 项目都会用,尤其是微服务、后端服务、需要区分开发/测试/生产环境的场景。

Node.js 从 20.6.0 开始原生支持了--env-file参数,20.12.0 又补了--env-file-if-exists。用法非常简单,原来你在入口文件里写:

import 'dotenv/config'; const port = process.env.PORT ?? 3000;

现在可以直接在启动命令里指定:

node --env-file=.env src/index.js

如果.env文件可能不存在(比如 CI 环境用真实环境变量注入),那就用:

node --env-file-if-exists=.env src/index.js

如果项目用的是 CommonJS,进入文件同样直接读process.env就行,不需要任何改动。更关键的是,Node 20.12+ 还提供了一个运行时 APIprocess.loadEnvFile(),可以在代码里按需加载:

import { loadEnvFile } from 'node:process'; loadEnvFile(); // 等价于 node --env-file=.env 的运行时版

我在一个 Express 老项目里验证过,把dotenv删掉、启动命令加上--env-file后,日志、配置读取、各种环境分支全部正常。唯一要注意的是,原生--env-file默认不会覆盖已存在的环境变量,这一点和 dotenv 的默认行为一致,所以临时在 shell 里注入变量覆盖配置的用法不受影响。

注意:如果你项目里用了类似dotenv-expand的功能(.env文件里支持变量引用,比如DB_URL=$BASE_URL/db),原生--env-file目前不支持这种扩展语法。这种情况可以保留 dotenv,或者自己在加载后做一层字符串替换。判断标准很简单:你的.env里有没有$引用?没有就直接删。

2.3 用 node --watch 替换 nodemon

本地开发时改完代码自动重启服务,几乎成了 Node 开发者的肌肉记忆。nodemon 是这十年来最流行的方案,但它本质上就是"监听文件变化 + 杀掉旧进程 + 拉起新进程"。Node.js 18.11.0 开始试验性地内置了--watch模式,Node 22 之后标记为稳定,不需要任何额外依赖。

原来你这么启动开发服务:

{ "scripts": { "dev": "nodemon src/index.js" } }

现在直接:

{ "scripts": { "dev": "node --watch src/index.js" } }

node --watch默认监听入口文件及其依赖的整个模块图,也就是你项目里被importrequire过的文件,改了都会触发重启。这一点其实比 nodemon 默认行为更精准,因为它不会因为你在src目录里放了一个没被引用的临时文件就重启服务。

还有一个好用的小参数叫--watch-path,可以指定额外监听的路径,比如:

node --watch --watch-path=./config src/index.js

这样只会监听config目录下的文件变化,适合配置文件独立于源码目录的场景。

我自己的体会是,node --watch在大多数 Node 服务、脚本、CLI 工具开发场景里已经完全够用。但有几个 nodemon 才有的功能它暂时没有:比如 "delay" 防抖配置、按扩展名忽略文件(默认只 watch 模块图,某些.env变更不会触发)、以及当子进程崩溃时的--exitcrash行为差异。如果你的开发工作流重度依赖这些特性,可以再观望;但如果只是"改了代码自动重启",那完全没有必要继续养着一个依赖。

注意:node --watch是在 Node 进程层级做文件监听,不是 shell 级别的while循环。监听的文件句柄在 macOS 和 Linux 上默认够用,但如果你用 Docker 挂载宿主机目录到容器里开发,文件系统事件可能不会正确传递,这种情况 nodemon 的轮询模式反而更可靠。容器开发场景先验证再替换。

2.4 用 crypto.randomUUID 替换 uuid

uuid 包是"小工具依赖"的典型代表。很多项目引入它只是为了生成不重复的 ID,可能整个代码库里就一行uuid.v4()。这种"为了一个函数引入一个包"的模式,恰好是原生crypto.randomUUID()最适合终结的。

Node.js 从 14.17.0 开始全局提供了crypto.randomUUID(),现代浏览器从 2021 年起也全部支持。用法和uuid.v4()完全等价:

import { randomUUID } from 'node:crypto'; const id = randomUUID(); // 格式:xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx

浏览器环境更简单,直接用全局crypto.randomUUID()就行,不需要 import。

我最近重构一个内部工具项目,把uuid删掉后,代码改动量大概是 0——因为项目里已经封装了一个genId()工具函数,只需要改里面的实现。大多数项目如果把uuid的调用点都收敛到一个工具文件里,替换成本会低到忽略不计。

这里要提醒一下:如果你用的是uuid包里的非 v4 版本,比如v1(基于时间戳)或者v3/v5(基于命名空间哈希),那原生randomUUID就不完全等价,因为它是纯随机 v4。实际业务里 99% 的场景用 v4 就够了,但如果有"同一输入必须生成同一 ID"的幂等需求,那还是需要保留 uuid 或者换一个专门的实现。

注意:crypto.randomUUID只在安全的上下文(secure context)里可用。浏览器如果通过http://非 localhost 地址访问页面,全局crypto.randomUUID可能不存在。Node 服务端不受这个限制。前端项目替换前先确认一下部署环境是否走 HTTPS。

2.5 用原生 fetch 重写 axios 的常用场景

axios 是这 5 个里面最有争议的,我先说清楚边界:axios 没有死,也没必要全项目推翻重写。但它确实有一个非常典型的"重度使用场景"——只把 axios 当 fetch 用——这种场景现在可以完全用原生 fetch 代替。

Node.js 18 开始内置了全局fetch,浏览器更是不用说。如果你的 axios 用法停留在下面这个层面:

const res = await axios.get('/api/users'); const data = res.data;

等价的原生 fetch 写法是:

const res = await fetch('/api/users'); const data = await res.json();

看起来只是 API 风格差异,但考虑到 axios 打包体积、拦截器机制带来的复杂度、以及它在 Node 环境里底层依赖http模块、浏览器里依赖XMLHttpRequest的双实现成本,能少一个包也是实打实的收益。

更关键的是,新的运行时特性都往 fetch 生态倾斜。比如 Node 的undici底层一直在优化,fetch的性能和稳定性逐年提升;而 axios 在 Node 端仍然有自己的适配层。我实测过一个内部服务,把 axios 改成 fetch 后,依赖树减少了十几个包,启动速度也快了 100ms 左右。

如果你已经用了原生 fetch,但有拦截器、取消请求、超时控制这些需求,也完全可以自己封装,不必依赖 axios:

async function request(url, options = {}) { const controller = new AbortController(); const timeout = setTimeout(() => controller.abort(), options.timeout ?? 5000); try { const res = await fetch(url, { ...options, signal: controller.signal, }); if (!res.ok) { throw new Error(`HTTP ${res.status}: ${res.statusText}`); } return res.json(); } finally { clearTimeout(timeout); } }

这已经覆盖了大部分人用 axios 时的核心诉求:统一前缀、超时、错误处理、自动 JSON 解析。

那什么时候不该删 axios?我的判断标准是:如果项目里已经大量使用 axios 的拦截器(请求拦截、响应拦截)、onUploadProgress上传进度、取消令牌机制,或者团队里所有人都熟悉 axios 的生态写法,那强行重构反而引入不必要的风险。这种情况下留着它没什么问题。axios 的问题是"过于常用导致大家忘了可以不用",而不是它本身有多糟糕。

3. 替换不是无脑删:版本差异、行为边界与渐进迁移

3.1 先看环境:Node 版本与调用位置

上面提到的 5 个原生替代,都依赖具体的 Node.js 版本。我把版本要求整理成了下面这张表,方便你直接照着查:

依赖包原生替代最低 Node 版本推荐 Node 版本
rimraffs.rmSyncrecursive14.1416+
dotenv--env-file20.620.12+
dotenvprocess.loadEnvFile()20.1221.7+
nodemon--watch18.1122+
uuidcrypto.randomUUID()14.1716+
axios(部分)全局fetch1820+

替换前第一步,不是直接npm uninstall,而是先检查项目实际运行的 Node 版本。怎么看?

node -v

如果项目有.nvmrc.node-version文件,也别只看那个,确认 CI 里的node-version配置和 Dockerfile 里的基础镜像版本。我踩过最大的坑就是本地 Node 22 跑得好好的,CI 环境还在 Node 16,npm install之后跑node --env-file直接报 "bad option",当场回滚。

还有一个被忽略的点:调用位置。如果你的项目是纯前端(Vue/React 构建产物部署到 Nginx),那 Node 版本只影响构建过程,不影响运行时。比如rimraf只在构建脚本里用,那替换后只要构建环境的 Node 版本够新就行,线上用户完全感知不到。反过来,如果是 Node 服务端项目,删包后要重点验证的是生产环境的 Node 版本,而不只是开发机。

3.2 边界行为:转换后的坑

原生 API 和第三方包不是 100% 等价,替换后最容易踩的是下面这几个边界差异。

第一个坑是rimraffs.rmSync的报错行为。rimraf 默认会重试、会忽略某些 EPERM 错误,在 Windows 上还有文件句柄延迟释放的兼容逻辑。fs.rmSync遇到文件被占用会直接抛EBUSY/EPERM。我在 Windows 上清理 dist 目录时遇到过,因为打包进程没完全退出,fs.rmSync直接失败,而 rimraf 能靠重试机制扛过去。解决方案是删包前在 Windows CI 上先跑一遍完整构建流程验证。

第二个坑是dotenv--env-file对引号、注释的处理细节。dotenv 支持.env文件里的单引号、双引号、export前缀等多种写法,原生--env-file的解析规则更严格,遇到不合法行会直接抛错,而不是像 dotenv 那样宽容。如果你的.env文件里写了类似KEY=hello world(没有引号)的格式,dotenv 能解析,但原生--env-file可能会因为空格报错。替换后启动时会立刻暴露问题,反而帮助你清理历史债。

第三个坑藏在fetch和 axios 的默认行为差异里。axios 对非 2xx 状态码会走catch分支,而fetch只有网络层错误才 reject,HTTP 404、500 都能正常 resolve。如果你在业务代码里写的是:

try { const res = await axios.get('/api/item/1'); // 这里假设 res.data 一定存在 } catch (e) { // 错误处理 }

直接改成 fetch 之后,404 响应就进不了catch,而是返回一个res.ok === false的 Response 对象。不改错误处理逻辑的话,很容易出现"接口 404 但页面还继续渲染"的问题。我建议所有 fetch 封装里都像上面那样,先检查res.ok,不行就手动 throw。

3.3 渐进式替换的实操路径

不要试图在一个周末把所有包全删完,那是给自己找麻烦。我推荐的路径是四步走。

第一步,先做基线验证。替换前把项目的关键流程跑一遍,记下启动命令、构建命令、核心接口的返回数据。不需要自动化测试套件,只要保证"改动前和改动后,项目能正常跑起来"就行。

第二步,从影响最小的包开始。影响最小的是uuid,因为它就是一个纯函数替换,通常只涉及一个工具文件。其次是rimraf,一般只出现在 scripts 和 CI 脚本里。这两个包完成了,项目就已经开始变轻。

第三步,处理开发体验类替换dotenvnodemon属于开发/部署链路,替换后会改变启动命令,需要更新package.json的 scripts、README、还有团队内部的开发文档。这类改动建议单独提交,方便出问题时回滚。

第四步,最后评估 axios。axios 的替换涉及业务代码改造,要按模块分批来。先挑一个页面或一个服务,把 axios 调用换成 fetch 封装,对比响应数据和错误处理,确认没有回归后再铺开。

每完成一步,都跑一次npm uninstallnpm install,确保 lockfile 干净。

4. 删包的正确姿势:清理依赖与 npm 命令常见坑

4.1 一次干净的卸载流程

很多人的npm uninstall用得不够干净,删完包之后 node_modules 里还残留着一堆无效的传递依赖。我自己的标准流程是:

npm uninstall rimraf dotenv nodemon uuid axios npm install npm audit

npm uninstall不仅会从package.json移除依赖,还会更新package-lock.json,把对应的传递依赖从依赖树里摘出去。之后跑一次npm install,是为了让 lockfile 重新收敛,避免出现 "phantom dependency"(幽灵依赖)——也就是 package.json 里已经删了,但 node_modules 里因为缓存还能 require 到的情况。

如果你的项目用的是 pnpm,删除逻辑也类似,但额外推荐跑一下:

pnpm prune

它会清理掉 node_modules 中不再被任何依赖引用的包。pnpm 的硬链接机制下,pnpm install本身也会回收无用包,但prune更彻底。

删完包之后再打开package.json,你会发现 scripts 也要跟着改。搜索项目里还在引用这些包的文件,尤其是scripts/目录下的构建工具和 CI 里的命令。一个常见的遗漏是cron任务或者 pre-commit hook 里写的rimraf,删掉包之后这里的代码还留着就会直接报Cannot find module 'rimraf'

4.2 npm 命令 5 个高频报错的速查与解决

删包和换包的过程中,npm 自身的安装问题往往比替换逻辑更容易卡住。我把这几年工作中最常遇到的 npm 报错整理成了一张速查表,基本覆盖了热搜里频繁出现的那几个:

报错/症状常见原因解决办法
npm : 无法加载文件 ...\npm.ps1,因为在此系统上禁止运行脚本PowerShell 执行策略默认限制.ps1脚本以管理员身份运行Set-ExecutionPolicy RemoteSigned -Scope CurrentUser,或改用cmd执行 npm 命令
'npm' 不是内部或外部命令,也不是可运行的程序Node.js 安装后环境变量 PATH 未配置或未刷新重新安装 Node.js 并勾选 "Add to PATH";或手动把C:\Program Files\nodejs\加入 PATH,重开终端
npm WARN deprecated node-domexception@1.0.0: use your platform's native DOMException某个传递依赖使用了旧包,常见于form-data/@tootallnate/once属于无害警告;升级相关依赖或更新 lockfile 可消除,但不影响安装结果
npm ERR! code ERESOLVE/ERESOLVE overriding peer dependencypeerDependencies 版本冲突,常见于 React/Vue 插件与框架版本不匹配检查冲突包,手动统一版本;必要时用npm install --legacy-peer-deps临时绕过,但不要长期依赖
npm ERR! code EBUSY构建或安装时文件被占用,Windows 上最常见关闭所有 Node 进程(taskkill /F /IM node.exe或重启 IDE),再重试npm install

热搜里出现频率很高的node-domexception警告,很多人以为是自己项目的问题,其实它来自form-data这个包的老版本,原生环境早就支持DOMException了。这类 deprecated 警告在删包之后会自然消失一部分,尤其是删掉 axios 这种会传递引入form-data的包。

至于 PowerShell 报 "禁止运行脚本",本质是 Windows 默认的Restricted执行策略不允许任何.ps1脚本运行,npm 的 wrapper 恰好是用 PowerShell 写的。这里有个更轻量的做法:不用改全局执行策略,直接在项目里用npm.cmd代替npm,比如npm.cmd run dev。或者干脆用 nvm-windows 管理 Node 版本时选nvm use自动切换 PATH,也能避开一部分问题。

4.3 依赖瘦身之后怎么守住

删完这 5 个包不是终点。依赖是"熵增"的,只要你还在开发,package.json就会慢慢膨胀。我给自己和团队定了三条规则,实测能守住很长时间。

第一,新增依赖必须过审。哪怕最终决定要引入新包,也要先回答三个问题:原生 API 能不能做?手写 50 行函数能不能做?有没有更轻的替代包?回答不上来就不允许加。这不是限制自由,而是防止"装包一时爽,维护火葬场"。

第二,定期看一遍依赖树。我习惯每季度跑一次npm ls --depth=0,看看顶层依赖有哪些明显已经不用的;再用depcheck这类工具把"已安装但从未被 import"的包列出来,逐个确认。很多项目最大的浪费不是版本老旧,而是根本不再使用的包还一直留在依赖里。

第三,注意环境镜像源的问题。国内开发者经常遇到npm install慢或下载失败,热搜里"npm 换源""npm 国内镜像源"也一直热门。换源本身没问题,但团队协作时最好在项目根目录放一个.npmrc,统一镜像地址和 registry,避免不同人的安装结果不一致:

registry=https://registry.npmmirror.com

只修改镜像源不影响依赖版本,但一定要保证 lockfile 里的版本号稳定。npm ci是 CI 里更推荐使用的命令,它会严格按照 lockfile 安装,不会因为本地node_modules残留产生偏差。

5. 最后说点掏心窝的话:有的包该删,有的包不该省

我见过两种极端。一种是把所有依赖都视为罪恶,恨不得把项目改成零依赖;另一种是明知道某个包只用一个函数,却因为"大家都这么写"就一直留着。2026 年的现实是,Node.js 原生能力已经足够强,很多当年必须靠生态补足的基础设施已经内置,继续背着十几个包跑完全没必要。

但我也要泼一盆冷水:删包这件事,成本最低的其实是新建项目,而不是老项目。如果你手里是一个维护了三年的老系统,里面到处都是import axiosrequire('uuid')、启动命令写死在 README 里,那强行一口气清理反而容易引入回归。我自己的经验是"边改边删",碰到某个文件需要重构时,顺手把它的第三方依赖改成原生写法,而不是专门花两周做一次"依赖清理专项"。这样既不会影响业务节奏,又能让代码库越来越干净。

还有一个建议:别为了删包而删包。比如某个包虽然也可以删,但团队里五六个项目都在用它,你在这个项目里删掉、在另一个项目里保留,长期维护反而更累。这类情况不妨先在团队内部统一技术决策,达成共识后再全域推进。依赖清理的本质是降低认知负担和运维成本,而不是比拼谁的项目 dependency 数量更少。

我个人在实际操作中的体会是:删掉 rimraf 和 uuid 是最没痛感的,删掉 dotenv 需要改一下启动方式,删掉 nodemon 需要适应一下重启日志,而 axios 的替换最需要谨慎。如果你现在正被 node_modules 的体积和npm audit的告警困扰,不妨从这 5 个包里挑一个最小的开始。很多时候,一个包从package.json里消失,带来的不只是几百 KB 的磁盘空间,而是团队里少了一个需要维护、需要升级、需要担心漏洞的"变量"。

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

Homebrew 可视化工具 BrewUI:从 CLI 到 TUI 的开发实践

"你还在一个个执行brew outdated && brew upgrade?"同事那天看着我终端里滚动的日志,随口问了一句。我当时正盯着十几条更新记录,单线程地敲键盘,说实话也有点烦了。命令行当然强大,但包管理这件事本…

作者头像 李华
网站建设 2026/9/19 10:21:43

纵横交叉算法优化BP神经网络的电力负荷预测与Matlab实现

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/9/19 10:20:33

Node.js安装配置全攻略:从版本选择到环境变量与npm镜像源

1. 先搞明白:Node.js 是个运行时,不是一门语言很多人第一次接触 Node.js 时,会把它当成一门编程语言,其实不是。Node.js 本质上是一个基于 Chrome V8 引擎的 JavaScript 运行时环境,它的作用就是让 JavaScript 代码能在…

作者头像 李华
网站建设 2026/9/19 10:18:08

ESP32+MAX30102心率血氧监测实战:从硬件连接到信号处理

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/9/19 10:16:49

嵌入式系统设计与工程实践:从底层原理到量产落地

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华