Monorepo Common Package Issues and Solutions Using TypeScript Project References

This article describes the challenges of using shared TypeScript packages in a monorepo—such as ignored package.json, tsconfig, and phantom dependencies—and presents two practical solutions, focusing on Project References to enable reliable compilation and integration of common packages.

ByteFE
ByteFE
ByteFE
Monorepo Common Package Issues and Solutions Using TypeScript Project References

Since December 2018 the ByteFront team has been using a monorepo, now containing 154 business packages, 11 shared packages, and 7 tooling packages. This article records the journey, problems encountered, and solutions for handling shared TypeScript packages.

Introduction

A teammate ("B") complained that changes to tsconfig in a shared package ( common-A) had no effect. The maintainer ( A) explained that the business package was aliasing the source of common-A, so the package's own tsconfig and package.json were ignored.

What Went Wrong?

Monorepos allow developers to reference shared packages without publishing them, encouraging rapid iteration. However, most teams treat the shared code as part of the business source by adding aliases and loader includes, which leads to three hidden problems:

Package.json Ignored

The entry point defined in the shared package's package.json is not used; the business package can import any file from the shared package, making maintenance difficult.

TSConfig Ignored

The shared package's tsconfig.json is bypassed, and the business package's configuration is applied instead, forcing the shared package to adapt to every consumer's settings.

Phantom Dependency

Dependencies declared only in the shared package are not declared in the consuming business package, creating implicit dependencies and version uncertainty.

In short, when source code is directly referenced, the shared package behaves like a plain folder, and its package.json and tsconfig.json become ineffective.

Possible Solutions

The goal is to treat the shared TypeScript package like an npm‑published package. Two viable approaches emerged:

Automatic compilation via a Git hook.

Using TypeScript Project References .

Automatic Compilation – Git Hook

A Git pull hook runs a script that compiles all shared packages locally. This works for developers who only modify business packages, but when both shared and business packages are edited simultaneously, developers must manually restart the dev server, which hurts productivity.

Project References

Introduced in TypeScript 3.0, they allow fine‑grained compilation of sub‑projects.

They handle chains of tsconfig.json dependencies (e.g., A → B → C). ts-loader supports Project References from version 5.2.0, passing the references to the TypeScript compiler.

With Project References, both Node server projects and Webpack‑bundled web projects can be compiled correctly.

Implementation Steps

1. Ensure the shared package has a proper configuration.

{
  "name": "@monorepo_workspace/common-a",
  "version": "1.0.0",
  "description": "A common package",
  "sideEffects": false,
  "exports": {
    ".": {
      "import": "./es/index.js",
      "require": "./lib/index.js"
    }
  },
  "main": "./lib/index.js",
  "module": "./es/index.js",
  "typings": "./es/index.d.ts"
}

2. Adjust the business package configuration:

// some js config
{
  tsLoader: (config) => {
    config.projectReferences = true;
    config.compilerOptions = undefined; // override framework defaults that would break projectReferences
  }
}
Setting compilerOptions to undefined prevents default options from overriding the Project References configuration.

3. Ensure package.json lists the shared package as a dependency and that the package manager resolves it as a real package.

4. Add references in the business package's tsconfig.json:

{
  "references": [
    { "path": "../../common/common-a/tsconfig.es.json" },
    { "path": "../../common/common-b/tsconfig.json" }
  ]
}
The path can point directly to a tsconfig.json file or to a package folder, in which case the folder's tsconfig.json will be used.

After these changes, running the development server triggers a TypeScript build of the referenced shared packages before the actual bundling step, and the bundler now treats the shared code as a real package.

Can We Go Further?

While Project References work well, adding reference entries manually still adds overhead. A future idea is to create a Webpack plugin or extend ts-loader with an autoReference feature that automatically treats local packages as Project References.

To Be Continued

Beyond the common package issue, our monorepo also faces challenges such as node deployment, yarn.lock review hell, and excessive resolution conflicts. Stay tuned for more detailed summaries.

Original Source

Signed-in readers can open the original source through BestHub's protected redirect.

Sign in to view source
Republication Notice

This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactadmin@besthub.devand we will review it promptly.

monorepoproject-referencests-loader
ByteFE
Written by

ByteFE

Cutting‑edge tech, article sharing, and practical insights from the ByteDance frontend team.

0 followers
Reader feedback

How this landed with the community

Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.