Mata00616
Mata00616

Reputation: 27

How to build Typescript workspaces with internal packages?

I have a monorepo that is utilizing PNPM workspaces. I've been working in development mode for some time now and following the turborepo guide. It says that my internal packages, the ones that I intend to share can be exported using the main index.ts file. This has worked all throughout development and has resulted in an amazing development experience but now I wanted to build this and I noticed that when I build my application I get errors because my main application is trying to import TypeScript code.

I knew that I would have to go and change the export in the package but the problem with this is that now I have to actually build my internal package and export JavaScript files and whenever I need to work in development I have to switch it back to the TypeScript files.

Is there any way that I can still have this amazing development experience while also being able to run builds. Please help me I've been struggling with this all day.

Upvotes: 2

Views: 1420

Answers (1)

morganney
morganney

Reputation: 13580

Disclaimer: This example uses npm and tsx instead of ts-node and pnpm but the concepts apply to both stacks.

I've created an example repo on GitHub: https://github.com/morganney/monorepo-mix.

If you want a full-stack example where a ui package consumes an internal components package from a build, check out https://github.com/morganney/busmap.

This is difficult to answer for your specific use case as you have not provided any code, however, I'll provide a general solution not specific to turborepo, but instead general to TypeScript and monorepos.

The solution is primarily based on two things:

Project structure:

.
├── package.json
├── tsconfig.json
└── packages/
    ├── components/
    │   ├── src/
    │   │   ├── folder/
    │   │   │   └── file.ts
    │   │   └── index.ts
    │   ├── tsconfig.json
    │   └── package.json
    └── app/
        ├── src/
        │   └── index.ts
        ├── index.html
        ├── vite.config.ts
        ├── tsconfig.json
        └── package.json

packages/components/package.json

  "name": "components",
  "description": "Provides dependencies",
  "type": "module",
  "main": "dist",
  "exports": {
    ".": {
      "import": {
        "types": "./dist/index.d.ts",
        "development": "./src/index.ts",
        "default": "./dist/index.js"
      }
    },
    "./package.json": "./package.json"
  }

The key here is "development": "./src/index.ts".

packages/app/package.json

  "name": "app",
  "description": "Consumes dependencies.",
  "type": "module",
  "scripts": {
    "dev": "tsx --conditions=development ../../node_modules/.bin/vite",
    "dev:build": "vite"
  },
  "dependencies": {
    "components": "^1.0.0"
  }

The key here is "dev": "tsx --conditions=development ../../node_modules/.bin/vite"

packages/components/src/index.ts

I don't like barrel files, but just for the example:

export * from './folder/file.js'

packages/components/src/folder/file.ts

export const speak = (words: string[], ending: "." | "?" | "!") =>
  `${words.join(" ")}${ending}`;

packages/app/src/index.ts

import { speak } from "components";

console.log(speak(["Hello", "monorepo", "world"], "!"));

const p = document.createElement("p");
const text = document.createTextNode(
  speak(["Hello", "monorepo", "world"], "!"),
);

p.appendChild(text);
document.body.appendChild(p);

Now from the workspace root you can run npm run dev -w app and load http://localhost:5173/ to see Hello monorepo world! on the page or console logs. You can also edit packages/components/src/folder/file.ts and see a live update.

If you want to develop against a build of components, then first run npm run build -w components and then run npm run dev:build -w app.

Upvotes: 3

Related Questions