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...

相关推荐
Martin -Tang3 分钟前
vite和webpack的区别
前端·webpack·node.js·vite
迷途小码农零零发4 分钟前
解锁微前端的优秀库
前端
王解1 小时前
webpack loader全解析,从入门到精通(10)
前端·webpack·node.js
我不当帕鲁谁当帕鲁1 小时前
arcgis for js实现FeatureLayer图层弹窗展示所有field字段
前端·javascript·arcgis
那一抹阳光多灿烂1 小时前
工程化实战内功修炼测试题
前端·javascript
放逐者-保持本心,方可放逐2 小时前
微信小程序=》基础=》常见问题=》性能总结
前端·微信小程序·小程序·前端框架
毋若成4 小时前
前端三大组件之CSS,三大选择器,游戏网页仿写
前端·css
红中马喽4 小时前
JS学习日记(webAPI—DOM)
开发语言·前端·javascript·笔记·vscode·学习
Black蜡笔小新5 小时前
网页直播/点播播放器EasyPlayer.js播放器OffscreenCanvas这个特性是否需要特殊的环境和硬件支持
前端·javascript·html
秦jh_6 小时前
【Linux】多线程(概念,控制)
linux·运维·前端