Bundling packages in a Monorepo

Unlike internal packages, external packages can be deployed to npm and used locally. In this guide, we'll be using a bundler to bundle a package to CommonJS, the most commonly used format used on npm.

Setting up a build script

Let's start with a package created using our internal packages tutorial.

There, we created a math-helpers package which contained a few helper functions for adding and subtracting. We've decided that this package is good enough for npm, so we're going to bundle it.

At the end of that tutorial, we had a package set up under /packages, which looked like this:

├── apps
│   └── web
│       └── package.json
├── packages
│   └── math-helpers
│       ├── src
│       │   └── index.ts
│       ├── tsconfig.json
│       └── package.json
├── package.json
└── turbo.json

We're going to add a build script to math-helpers, using a bundler. If you're unsure which one to choose, we recommend tsup.

First install, tsup inside packages/math-helpers using your package manager.

packages/math-helpers/package.json
{
  "scripts": {
    "build": "tsup src/index.ts --format cjs --dts"
  }
}

tsup outputs files to the dist directory by default, so you should:

  1. Add dist to your .gitignore files to make sure they aren't committed by git.
  2. Add dist to the outputs of build in your turbo.json.
turbo.json
{
  "pipeline": {
    "build": {
      "outputs": ["dist/**"]
    }
  }
}

That way, when tsup is run the outputs can be cached by Turborepo.

Finally, we should change main to point at ./dist/index.js inside package.json. types can point at ./dist/index.d.ts:

packages/math-helpers/package.json
{
  "main": "./dist/index.js",
  "types": "./dist/index.d.ts"
}

If you run into errors by using main and types, take a look at the tsup docs.

Bundling is a complicated topic, and we don't have space here to cover everything!

Building our package before our app

Before we can run turbo run build, there's one thing we need to consider. We've just added a task dependency into our monorepo. The build of packages/math-helpers needs to run before the build of apps/web.

Fortunately, we can use dependsOn to easily configure this.

turbo.json
{
  "pipeline": {
    "build": {
      "dependsOn": [
        // Run builds in workspaces I depend on first
        "^build"
      ]
    }
  }
}

Now, we can run turbo run build, and it'll automatically build our packages before it builds our app.

Setting up a dev script

There's a small issue with our setup. We are building our package just fine, but it's not working great in dev. Changes that we make to our math-helpers package aren't being reflected in our app.

That's because we don't have a dev script to rebuild our packages while we're working. We can add one easily:

packages/math-helpers/package.json
{
  "scripts": {
    "build": "tsup src/index.ts --format cjs --dts",
    "dev": "npm run build --watch"
  }
}

This passes the --watch flag to tsup, meaning it will watch for file changes.

If we've already set up dev scripts in our turbo.json, running turbo run dev will run our packages/math dev task in parallel with our apps/web dev task.

Summary

Our package is now in a spot where we can consider deploying to npm. In our versioning and publishing section, we'll do just that.