pnpm monorepo开发环境构建cjs和esm共享包

前言

最近想把之前练习过的pnpm+turbo组合的monorepo项目实现一个完整的全栈项目,实现过程中发现共享的package会由于nestjs默认导出模块为commonjs,而现在大部分前端项目都是vite构建的esm模块,导致共享的package包就很难提供给不同模块的app共用。

查阅资料后找到一个不那么完美的解决方案,开发过程中始终别扭,然后在继续查阅资料的过程中,找到了今天的主角tsup、rollup,它们可以打包一个package为cjs、esm两种类型提供给app加载。

项目目录结构介绍

text 复制代码
├── apps  // apps(主要应用,admin为管理台ui、api为nestjs+fastify提供接口)
│   ├── admin
│   │   ├── README.md
│   │   ├── index.html
│   │   ├── node_modules
│   │   ├── package.json
│   │   ├── public
│   │   ├── src
│   │   ├── tsconfig.json
│   │   ├── tsconfig.node.json
│   │   └── vite.config.ts
│   └── api
│       ├── README.md
│       ├── dist
│       ├── nest-cli.json
│       ├── node_modules
│       ├── package.json
│       ├── src
│       ├── test
│       ├── tsconfig.build.json
│       └── tsconfig.json
├── node_modules
├── package.json
├── packages  // packages(公共包,drizzle为api提供数据库orm支持、rest-contract提供api合约)
│   ├── drizzle
│   │   ├── drizzle.config.ts
│   │   ├── kit
│   │   ├── node_modules
│   │   ├── package.json
│   │   ├── src
│   │   ├── tsconfig.json
│   │   └── tsup.config.ts
│   └── rest-contract
│       └── package.json
├── pnpm-lock.yaml
├── pnpm-workspace.yaml
└── turbo.json

package打包cjs和esm版本

tsup、rollup都可以打包cjs和esm模块,这里我暂时是用tsup实现的,以drizzle共享包为例,tsup提供打包功能,package.json暴露不同模块提供外部使用。

text 复制代码
├── drizzle
│   ├── drizzle.config.ts // drizzle-orm配置
│   ├── kit               // drizzle-kit相关操作如seed播种数据操作
│   │   │   ├── db.ts
│   │   │   ├── mock
│   │   │   │   └── data.ts
│   │   │   └── seed.ts
│   ├── node_modules
│   ├── package.json      // package配置
│   ├── src               // drizzle-orm相关,如schema
│   │   │   ├── enum.ts
│   │   │   ├── index.ts
│   │   │   ├── schema
│   │   │   │   ├── base.schema.ts
│   │   │   │   ├── index.ts
│   │   │   │   ├── relation.schema.ts
│   │   │   │   └── system.schema.ts
│   │   │   └── utils
│   │   │       └── password.ts
│   ├── tsconfig.json     // ts配置
│   └── tsup.config.ts    // tsup配置

package.json

看注释位置的主要配置,tsx提供nodejs的ts直接运行环境,下面是配置。

json 复制代码
{
  "name": "@repo/drizzle",
  "version": "0.0.1",
  "description": "共享drizzle包",
  
  // 主要配置:
  "type": "module",
  "main": "./dist/index.cjs",
  "module": "./dist/index.js",
  "exports": {
    ".": {
      "types": {
        "require": "./dist/index.d.cts",
        "default": "./dist/index.d.ts"
      },
      "default": {
        "require": "./dist/index.cjs",
        "import": "./dist/index.js"
      }
    }
  },
  
  "scripts": {
    "build:@repo/drizzle": "tsup src/*",
    "dev": "tsup src/* --watch",
    "db:migrate:deploy": "drizzle-kit migrate deploy",
    "db:migrate:dev": "drizzle-kit migrate dev",
    "db:push": "drizzle-kit push",
    "db:generate": "drizzle-kit generate",
    "studio": "drizzle-kit studio --verbose",
    "seed": "tsx kit/seed.ts"
  },
  "keywords": [],
  "author": "",
  "license": "ISC",
  "devDependencies": {
    "@types/bcrypt": "^5.0.2",
    "@types/node": "^20.3.1",
    "@types/pg": "^8.11.6",
    "drizzle-kit": "^0.22.7",
    "tsup": "^8.1.0",
    "tsx": "^4.15.2"
  },
  "dependencies": {
    "bcrypt": "^5.1.1",
    "drizzle-orm": "^0.31.2",
    "drizzle-zod": "^0.5.1",
    "pg": "^8.12.0",
    "zod": "^3.23.8"
  }
}

tsup.json

tsup提供打包cjs和esm的打包功能,下面是配置。

json 复制代码
import type { Options } from "tsup";

export const tsup: Options = {
  entry: ["src/*.ts"],
  format: ["cjs", "esm"],
  dts: true,
  // splitting: true,
  splitting: false,
  clean: true,
  outDir: "dist",
  sourcemap: "inline",
  cjsInterop: true,
};

构建效果图

踩坑记录

不完善的cjs和esm处理方法

monorepo项目中,pnpm vite commonjs和esm不兼容处理方法

json 复制代码
# 最简单的方法:
# 以shared-api为例,vite optimizeDeps配置,作用是:dev的时候vite预先把commonjs转换为esm包缓存在.vite目录
# 缺点是:如果shared-api包有修改,只能通过强制重新加载再次让vite编译esm缓存(暂时通过手动保存一下vite.config.ts实现再次强制编译)
# package.json内配置dev的时候加上--force参数

// package.json
{
  "dev": "vite --force"
}

// vite.config.ts
{
  optimizeDeps:{
    include: {
      // TODO: 由于加载的是commonjs,这里用vite预编译为esm模块
      "@repo/shared-api"
    }
  },
  build: {
    // TODO: 由于加载的是commonjs,这里用vite预编译为esm模块
    commonjsOptions: {
      include: ["@repo/shared-api", "node_modules"],
    },
  }
}

另一种不完善方案:

packages打包为esm模块,nestjs利用webpack编译加载esm模块,类似于上面的vite方法,当共享包发生改变时,也无法立即生效。

webpack加载esm

相关链接

tsup.egoist.dev/#using-cust...

rollupjs.org/introductio...

参考链接

js.work/posts/2b6dd...

pengzhanbo.cn/article/exp...

juejin.cn/post/703738...

github.com/axios/axios...

相关推荐
德莱厄斯3 分钟前
没开玩笑,全框架支持的 dialog 组件,支持响应式
前端·javascript·github
非凡ghost31 分钟前
Affinity Photo(图像编辑软件) 多语便携版
前端·javascript·后端
非凡ghost33 分钟前
VideoProc Converter AI(视频转换软件) 多语便携版
前端·javascript·后端
endlesskiller39 分钟前
3年前我不会实现的,现在靠ai辅助实现了
前端·javascript
用户9047066835741 分钟前
commonjs的本质
前端
Sailing1 小时前
5分钟搞定 DeepSeek API 配置:从配置到调用一步到位
前端·openai·ai编程
Pa2sw0rd丶1 小时前
如何在 React 中实现键盘快捷键管理器以提升用户体验
前端·react.js
非凡ghost1 小时前
ToDoList(开源待办事项列表) 中文绿色版
前端·javascript·后端
j七七1 小时前
5分钟搭微信自动回复机器人5分钟搭微信自动回复机器人
运维·服务器·开发语言·前端·python·微信
快起来别睡了1 小时前
TypeScript装饰器详解:像搭积木一样给代码加功能
前端·typescript