pnpm与tslib组合起来的坑

发表于 2025-06-26
更新于 2025-06-26
分类于 技术专栏
阅读量 26
字数统计 3741

1、背景

我们的电商网站用的nuxt3,5月份的一次平常不过的上线,显得异常坎坷,因为发布之后,立马告警起来了: image.png

这告警一看就知道是服务器挂了。

2、怎么办?

按照前端运维的思路,先止血,再排查原因。于是我们先操作了回滚。排查原因的话到服务器上看日志。

一开始,打开日志的时候,看到了很多undefined的告警,以为应该是没有做好判断导致的: image.png

负责此次发布的同学怀疑是缓存导致,于是根据这个截图日志以及怀疑的地方进行修改,二次发布,再次发布之后

image.png

同样的时间,我继续看一下日志,终于在往前回溯的日志上看到了致命的问题: image.png

再去服务器上确认,确实只有这么个目录: image.png

整个tslib只有tslib.es6.mjs,但是为什么Test和Uat环境都是好的: image.png

先不管了,先解决问题。

3、解决问题

找到了报错原因,那就好解决了,网上一搜索,竟然nuxt + tslib有这么多issue。最后找到了解决方案,那就是在nuxt.config.js中把tslib的编译写进去:

1export default defineNuxtConfig({ 2 ... 3 build: { 4 transpile: ['tslib'], 5 }, 6 ... 7})

transpile: ['tslib'] 的作用: 这个配置告诉 Nuxt 在构建过程中,使用 Babel 来转译 node_modules 里的 tslib 库。通常,构建工具为了速度会跳过对 node_modules 里依赖的转译,因为默认这些库已经是标准的 JS 格式。当某个库使用了比较新的语法,或者其模块导出方式(package.json 中的 exports 字段)不标准时,就需要手动将其加入 transpile 列表。

4、查找原因

问题解决之后,发布线上,果然没问题了,从build/.output/server/node_modules/目录下也找不到了tslib的库,说明被编译进去了。

那问题来了,为啥测试环境和uat环境都没问题呢?唯独线上发布就有问题。

于是,我就发挥起查找原因小能手的职责,看看究竟本质原因是啥?

因为我们的线上发布和测试发布,发布方式不一样,测试环境用的是Docker环境,每次都是全新代码发布,线上还停留在物理机发布。

于是第一个怀疑对象就是发布环境的差异。

  1. Node.js 版本: 测试环境是 18.16.1,线上是 18.17.1
  2. pnpm 版本: 测试环境是 10.10.1,线上是 9.12.1

于是我在我自己电脑上开始模拟,首先看能不能复现,我当前是Node22版本和最新的Pnpm,直接执行线上环境构建的命令,果然复现了,编译后的产物,tslib和线上一模一样,只有tslib.es6.mjs。

然后把自己电脑环境通过nvm切换到对应的测试环境Nodejs版本,和pnpm版本,重新删掉node_modules之后再次编译,还是一样,tslib的目录产物不对。

这可就神奇了。说明和Nodejs版本没关系?

为了双向验证,我把测试分支的Dockerfile指定的Node版本和pnpm版本写成成和线上的一致,发布到测试环境。

我去,测试环境还是正常的。那就说明确实和Node版本没关系。

接下去差异的因素还有两点:

  • 编译的环境变量不一样,一个是test,一个是production,会不会nuxt在二者的处理对于tslib有啥特殊?
  • 编译的环境不一样,一个是Docker,一个是在物理机

于是我开始一一排查,先看看环境变量是不是会导致这个问题,于是我清空node_modules,然后再本地电脑上执行test环境的构建命令,结果还是不行,那说明和环境变量也没关系。

那就只能是编译环境不一样了?

那有什么东西会导致这个呢?

我再次研究了Docker的命令:

# 编译镜像,node版本也保持和线上一致
FROM node:18.17.1 AS base

ARG APP_ENV
ARG REDIS_URL
ARG VERSION

# 设置工作目录
WORKDIR /src

# 引入源码
COPY package.json /src
COPY pnpm-lock.yaml /src

# 编译源码,指定pnpm和线上一致
RUN npm i -g pnpm@9.12.1 && pnpm i --production

FROM base AS builder

COPY . /src

# 执行构建 VERSION 是当前git节点的commit id
RUN echo ${VERSION}
RUN echo ${APP_ENV}
RUN APP_ENV=${APP_ENV} VITE_VERSION=${VERSION} NODE_OPTIONS=--max_old_space_size=4096 node_modules/.bin/nuxt build --dotenv .env.${APP_ENV}
RUN mkdir build/.output/server/node_modules/@popperjs && mv build/.output/server/node_modules/@sxzz/popperjs-es build/.output/server/node_modules/@popperjs/core

我在本地电脑还原一下操作步骤:

  • 新建一个测试目录
  • 拷贝package.json 和 pnpm-lock.yaml 到新目录
  • 在新目录执行pnpm i --production
  • 之后执行测试环境的构建命令

🤩,终于出现了和测试环境一样的tslib目录了,说明真的和Docker文件有关系!!

那究竟是什么原因呢?

于是我就想起把新目录和原有项目的目录进行个文件夹对比。结果发现了端倪:

  • 新目录的隐藏文件一个都没有,那看来Docker的 COPY命令 只复制了显性文件 image.png

  • node_modules里面多了很多包: image.png

于是我的第一个反应是:难道pnpm把所有包都打平了?我就赶紧去看了下.npmrc:

shamefully-hoist=true
strict-peer-dependencies=false
frozen-lockfile=true

果然如此!!shamefully-hoist 这个属性置为true,直接破坏掉了pnpm的优势,让他模拟npm的原理来搞事情,将所有的包都提升到了顶层目录了。

但是这个影响了我们的编译结果?

我果断进行验证,将这个置为false,重新编译,最后的结果验证了我的想法,果然tslib编译后的目录是全的了。

但是为什么呢?尝试去问了AI,AI给的答案是,tslib这种废弃的写法,在打平模式下会让Nitro混淆:

image.png image.png

解释得有点牵强,所以本质原因到现在我也不知道,也可能是Nitro的一个缺陷或者pnpm的bug?

有清楚的同学可以讲一下。

公众号关注一波~

微信公众号

关于评论和留言

如果对本文 pnpm与tslib组合起来的坑 的内容有疑问,请在下面的评论系统中留言,谢谢。

网站源码:linxiaowu66 · 豆米的博客

Follow:linxiaowu66 · Github