Frontend Development 19 min read

Designing and Publishing Module Packages in a Monorepo: Best Practices for Shared Config, UI Libraries, and Native Modules

This article explains how to design, build, and publish reusable module packages—including shared configuration libraries, UI component libraries, and native language modules—within a monorepo, covering package.json configuration, multiple entry formats (ESM, CJS, UMD), tooling choices, and publishing workflows for both frontend and Node.js projects.

ByteDance ADFE Team
ByteDance ADFE Team
ByteDance ADFE Team
Designing and Publishing Module Packages in a Monorepo: Best Practices for Shared Config, UI Libraries, and Native Modules

Why Design Module Packages?

As a monorepo grows and more developers collaborate, problems such as duplicate definitions, dependency management, and cross‑project usage arise, especially when modules need to run on both frontend and backend environments.

Types of Module Packages

Historically JavaScript module systems evolved from CJS → AMD → CMD → UMD → ESM, making cross‑application module design challenging. The recommended practices are:

Shared configuration modules (frontend & Node.js) should output both ESM and CJS formats.

UI component libraries should output ESM , CJS , and UMD (UMD enables external bundling to reduce build time).

Node‑only packages can output only CJS .

package.json Module Declaration

Think of package.json as the specification for a module package. Key fields include name , version , files , private , and dependency sections ( dependencies , devDependencies , peerDependencies ).

Versioning

Follow semver ( {major}.{minor}.{patch} ) and avoid common pitfalls such as publishing beta versions with the wrong minor number or using ^0.x.y which can cause mismatched installs.

Dependencies

dependencies : runtime dependencies that consumers will install. Do not place heavy tools like webpack or framework libraries such as react here unless the package bundles them.

devDependencies : build‑time tools used only by the package author.

peerDependencies : host dependencies that the consuming project must provide (e.g., react , vue ).

Private Packages

Set "private": true to prevent accidental publishing.

Single‑Entry Packages (main, module, browser)

For a single entry point, configure main (CJS), module (ESM), browser (UMD for older bundlers), and sideEffects for tree‑shaking.

{
  "main": "lib/index.js",
  "module": "es/index.js",
  "typings": "typings/index.d.ts",
  "browser": "dist/index.js",
  "sideEffects": false
}

Multi‑Entry Packages (exports, browser)

Use the exports field to map sub‑paths to different formats, enabling both ESM and CJS consumption.

{
  "exports": {
    "./react": {
      "import": "dist/react/index.mjs",
      "require": "dist/react/index.js"
    },
    "./vue": {
      "import": "dist/vue/index.mjs",
      "require": "dist/vue/index.js"
    }
  },
  "browser": {
    "./react": "dist/react/index.mjs",
    "./vue": "dist/vue/index.mjs"
  }
}

Designing Specific Packages

Shared Configuration Module (packages/shared)

Provides reusable configs, types, and utils across all apps. Usage example:

// apps/*/package.json
{
  "dependencies": {
    "@infras/shared": "workspace:*"
  }
}

// apps/*/src/index.ts
import { AppType } from '@infras/shared/types';
import { sum } from '@infras/shared/utils';
console.log(AppType.Web);
console.log(sum(1, 1));

Build with tsup to generate both ESM and CJS outputs.

// packages/shared/tsup.config.ts
import { defineConfig } from 'tsup';
export default defineConfig({
  entry: ['utils/index.ts', 'types/index.ts'],
  clean: true,
  dts: true,
  outDir: 'dist',
  format: ['cjs', 'esm']
});

UI Component Library (packages/ui)

Supports React and Vue components, built with Rollup to output ESM, CJS, and UMD. The dependenciesMeta field (pnpm ≥ 6.20) solves the React multiple‑instance problem by hard‑linking the library into the consuming app.

// packages/ui/rollup.config.js (simplified)
import { defineConfig } from 'rollup';
export default defineConfig([
  {
    input: 'react/index.tsx',
    external: ['react', 'react-dom'],
    output: [
      { file: 'dist/react/index.js', format: 'umd', name: 'UIReact', globals: { react: 'React', 'react-dom': 'ReactDOM' } },
      { file: 'es/react/index.js', format: 'es' },
      { file: 'lib/react/index.cjs', format: 'cjs' }
    ]
  },
  {
    input: 'vue/index.ts',
    external: ['vue'],
    output: [
      { file: 'dist/vue/index.js', format: 'umd', name: 'UIVue', globals: { vue: 'Vue' } },
      { file: 'es/vue/index.js', format: 'es' },
      { file: 'lib/vue/index.cjs', format: 'cjs' }
    ]
  }
]);

Native Language Module (packages/native)

Demonstrates using Rust (via napi‑rs ) to create a high‑performance npm package. The generated package is CJS‑only but works in ESM projects through Node's interop.

// packages/rs/package.json (excerpt)
{
  "name": "@infras/rs",
  "version": "0.0.0",
  "type": "commonjs",
  "main": "index.js",
  "types": "index.d.ts",
  "scripts": {
    "prepare": "npm run build",
    "build": "napi build --platform --release"
  },
  "devDependencies": { "@napi-rs/cli": "^2.0.0" }
}

Running the Packages

Frontend apps: pnpm start --filter "react-app" or pnpm start --filter "vite-app" .

Node.js apps: pnpm start --filter "node-app" (includes SSR example).

Publishing Module Packages

Two approaches:

Command‑line: pnpm -r publish --tag latest (skip packages with private: true ).

Platform‑side: Use internal AddOne platform for standardized builds and version management.

Conclusion

Key takeaways:

Adopt an ESM‑first strategy; use exports and browser for multi‑format support.

Shared config modules should be built with tsup to produce ESM and CJS.

Component libraries benefit from Rollup, producing ESM, CJS, and UMD, while handling peer‑dependency version mismatches via dependenciesMeta .

Native modules (Rust, C++, Go) can be integrated via napi‑rs for performance‑critical tasks.

TypeScriptReactmonorepoNode.jsModule Designpackage.jsonRolluptsup
ByteDance ADFE Team
Written by

ByteDance ADFE Team

Official account of ByteDance Advertising Frontend Team

0 followers
Reader feedback

How this landed with the community

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