Build JavaScript and declarations

Use one TypeScript configuration to check the repository and another to emit the files consumers need.

Type checking and building are different jobs

Typecheck

Checks source and tests. It should create no files.

Build

Checks package source and writes JavaScript, declarations, and maps to dist.

One large emitting configuration often compiles tests and unrelated workspace packages. Separate configurations make the publication boundary visible.

Create the root typecheck configuration

{
  "compilerOptions": {
    "target": "ES2022",
    "module": "NodeNext",
    "moduleResolution": "NodeNext",
    "strict": true,
    "exactOptionalPropertyTypes": true,
    "noUncheckedIndexedAccess": true,
    "verbatimModuleSyntax": true,
    "isolatedModules": true,
    "noUncheckedSideEffectImports": true,
    "skipLibCheck": true,
    "noEmit": true
  },
  "include": ["packages/**/*.ts"]
}

strict is the baseline for a library whose types become someone else's editor experience. NodeNext makes TypeScript follow Node.js module rules, including explicit .js extensions in relative ESM imports.

ES2022 is a compatibility target, not the current year. Pick the oldest JavaScript level supported by every runtime you promise to support, then test those runtimes in CI.

Create a package build configuration

Place this file at packages/sdk/tsconfig.build.json:

{
  "extends": "../../tsconfig.json",
  "compilerOptions": {
    "rootDir": "src",
    "outDir": "dist",
    "noEmit": false,
    "declaration": true,
    "declarationMap": true,
    "sourceMap": true
  },
  "include": ["src/index.ts"]
}
extends
Reuses strict rules instead of duplicating them.
rootDir and outDir
Map package source into a clean distribution directory.
noEmit: false
Overrides the root check-only setting and permits file generation.
declaration
Creates .d.ts files so TypeScript consumers receive the public API.
declarationMap
Lets editor navigation trace declarations back to TypeScript source.
sourceMap
Lets debuggers map running JavaScript back to source.
include
Starts the build at the package entry point. Tests are not emitted because the entry point does not import them.

Add and run the build script

{
  "scripts": {
    "build": "tsc -p tsconfig.build.json"
  }
}
cd packages/sdk
pnpm build
dist/ ├── index.js ├── index.js.map ├── index.d.ts └── index.d.ts.map

Know when you do not need a bundler

tsc preserves imports. If the SDK imports ArkType or a contracts package, those remain dependencies in the emitted JavaScript. The package manager installs them for consumers.

Add a bundler only for a measured requirement such as browser bundles, multiple module formats, code splitting, or reducing many internal files. A bundler adds configuration, declaration handling, and another compatibility layer.

Inspect output instead of trusting the command

sed -n '1,120p' dist/index.js
sed -n '1,160p' dist/index.d.ts

Confirm that JavaScript imports use resolvable package names and that declarations expose only intended public types. A successful compiler exit proves type correctness, not package correctness.

Official references

Check your understanding

  1. Why does the root configuration use noEmit: true?

    Show answer

    The root command checks all source and tests without scattering build output through the workspace. Each publishable package controls its own emitted files.

  2. Does pnpm pack automatically run a script named build?

    Show answer

    No. A later lesson adds the special prepack lifecycle script, which pnpm runs before packing.