You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何搭建含共享包的Deno与Node.js(Vite) Monorepo并解决类型问题?

Deno/Node.js(Vite) Monorepo 搭建方案(共享包双环境兼容+类型安全)

核心结论

完全可以用Deno的**dnt(deno-to-node)**替代tsc构建共享包,这是解决你当前前端类型丢失、配置繁琐问题的最优方案之一。dnt能自动将Deno风格的TypeScript代码转换为Node.js兼容的ES模块,并生成完整的类型定义文件,同时Deno后端仍可直接引用原始TS文件。


1. 调整目录结构(适配构建流程)

在现有基础上,给每个共享包新增构建输出目录,示例结构:

repo/
├── packages/
│   ├── pkg-a/           # 共享包A
│   │   ├── src/         # 源码目录
│   │   ├── dist/        # dnt构建输出(自动生成)
│   │   └── build.ts     # dnt构建脚本
│   └── pkg-b/           # 依赖pkg-a的共享包B
│       ├── src/
│       ├── dist/
│       └── build.ts
├── backend/
│   ├── src/
│   └── deno.jsonc
├── frontend/
│   ├── src/
│   ├── vite.config.mts
│   ├── tsconfig.json
│   └── package.json
└── scripts/
    └── build-all-packages.ts  # 批量构建脚本

2. 用dnt配置共享包构建

每个共享包的build.ts负责配置dnt的构建规则,以pkg-a为例:

// packages/pkg-a/build.ts
import { build, emptyDir } from "https://deno.land/x/dnt@0.39.0/mod.ts";

await emptyDir("./dist");

await build({
  entryPoints: ["./src/index.ts"], // 共享包入口文件
  outDir: "./dist",
  shims: {
    // 根据需要添加Deno特有的API垫片(如Deno.env)
    deno: true,
  },
  package: {
    // 生成的package.json配置
    name: "@repo/pkg-a",
    version: "0.1.0",
    type: "module",
    exports: {
      ".": "./index.js",
    },
  },
  compilerOptions: {
    lib: ["ESNext"],
    strict: true, // 保证类型严格性
  },
});

// 复制额外文件(如README、LICENSE)到dist目录
await Deno.copyFile("README.md", "./dist/README.md");

如果共享包之间有依赖(如pkg-b依赖pkg-a),只需在pkg-b的build.ts中添加本地依赖声明:

// packages/pkg-b/build.ts
await build({
  // ...其他配置
  dependencies: {
    "@repo/pkg-a": "file:../pkg-a/dist",
  },
});

执行构建:在共享包目录下运行deno run -A build.ts,dist目录会生成Node兼容的.js文件和完整的.d.ts类型定义。

3. Deno后端配置:直接引用原始TS文件

通过import maps简化导入路径,避免冗长的相对路径,同时保持类型安全:

// backend/deno.jsonc
{
  "imports": {
    "@repo/pkg-a/": "../packages/pkg-a/src/",
    "@repo/pkg-b/": "../packages/pkg-b/src/"
  },
  "compilerOptions": {
    "strict": true
  }
}

后端代码中直接导入:

// backend/src/main.ts
import { foo } from "@repo/pkg-a/index.ts";
import { bar } from "@repo/pkg-b/utils.ts";

4. Vite前端配置:导入构建后的共享包

步骤1:添加本地依赖

在前端的package.json中添加共享包的本地路径依赖:

// frontend/package.json
{
  "dependencies": {
    "@repo/pkg-a": "file:../packages/pkg-a/dist",
    "@repo/pkg-b": "file:../packages/pkg-b/dist"
  }
}

步骤2:Vite配置(确保解析本地包)

// frontend/vite.config.mts
import { defineConfig } from "vite";

export default defineConfig({
  resolve: {
    // 如果有需要,添加别名映射(可选)
    alias: {
      "@repo": "../packages",
    },
  },
});

步骤3:TS配置(保证类型识别)

// frontend/tsconfig.json
{
  "compilerOptions": {
    "strict": true,
    "types": ["vite/client"],
    // 自动识别共享包的类型定义
    "paths": {
      "@repo/pkg-a": ["../packages/pkg-a/dist/index.d.ts"],
      "@repo/pkg-b": ["../packages/pkg-b/dist/index.d.ts"]
    }
  },
  "include": ["src/**/*", "../packages/**/dist/*.d.ts"]
}

前端代码中直接导入:

// frontend/src/App.tsx
import { foo } from "@repo/pkg-a";
import type { FooType } from "@repo/pkg-a"; // 类型导入也能正常识别

5. 自动化批量构建

为了避免逐个构建共享包,在根目录编写批量构建脚本:

// scripts/build-all-packages.ts
import { walk } from "https://deno.land/std@0.208.0/fs/walk.ts";

async function buildPackages() {
  for await (const entry of walk("./packages", {
    includeFiles: ["build.ts"],
  })) {
    console.log(`Building ${entry.path}...`);
    // 执行每个共享包的构建脚本
    await new Deno.Command(Deno.execPath(), {
      args: ["run", "-A", entry.path],
      cwd: entry.parent,
      stdout: "inherit",
      stderr: "inherit",
    }).output();
  }
}

await buildPackages();

在根目录的deno.jsonc中添加任务:

{
  "tasks": {
    "build:packages": "deno run -A scripts/build-all-packages.ts"
  }
}

执行deno task build:packages即可一键构建所有共享包。


关键优势

  • 类型安全:dnt生成的.d.ts文件完整保留了TS类型约束,前端不会出现any类型丢失的问题。
  • 双环境兼容:Deno后端直接用源码,前端用构建后的Node模块,无需修改共享包代码。
  • 配置简洁:避免了Vite直接引用TS文件时的复杂配置,依赖管理更清晰。

内容的提问来源于stack exchange,提问作者andy-bc

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.15 15:56:11