@papit/bundle-ts
v0.0.3
Published
Rolls a package's TypeScript declarations up into one .d.ts file per entry point, and skips the work when no source file has changed since the last run.
Maintainers
Readme
@papit/bundle-ts
Rolls a package's TypeScript declarations up into one .d.ts file per entry point, and skips the work when no source file has changed since the last run.
Installation
npm install @papit/bundle-ts typescripttypescript is a peer dependency. Most packages never call this directly: npx @papit/build runs it for every package whose tsconfig has declaration: true.
Usage
Roll up the declarations of the package in the current folder:
import { tsBundle } from "@papit/bundle-ts";
const result = await tsBundle(new Set(), process.cwd());
if (result === "skipped") console.log("nothing changed");
else if (result) result.forEach(e => console.error(e.input, e.logs));With package.json "types": "./lib/bundle.d.ts" and src/index.ts exporting hello, lib/bundle.d.ts ends up as:
export declare const hello: (n: string) => string;
export { }Build explicit entries instead of the ones in package.json:
await tsBundle(new Set(["force"]), process.cwd(), {
entryPointsArray: [{ input: "/abs/pkg/src/index.ts", output: "/abs/pkg/lib/bundle.d.ts" }],
});API
tsBundle
function tsBundle(
args: { has(key: string): boolean },
location: string,
options?: Partial<{
tsconfig: ts.ParsedCommandLine,
entryPoints: ReturnType<typeof getEntryPoints>,
entryPointsArray: { input: string, output: string, name?: string }[],
packageJSON: PackageJson,
}>,
): Promise<"skipped" | Array<{ input: string, logs: string[] }> | undefined>Emits declarations for every file under src/ into .temp/ts-bundle, then rolls each types entry up into its output file. A .ts or .tsx source maps to its emitted .d.ts. Resolves "skipped" when nothing changed and every output exists, the entries that failed or logged warnings, or undefined on success. On a TypeScript emit error (with noEmitOnError, any type error) it prints the diagnostics and resolves every entry with them as logs.
Entry points come from package.json through getEntryPoints in @papit/bundle-js, the same way the JavaScript bundle finds them. options lets a caller skip reading them again.
Every entry except the main one (bundle) also keeps the package's own name external, so an extra entry like ./react that imports @papit/x gets import { X } from "@papit/x" instead of a copy of the class, which wouldn't be assignable to the real one.
API Extractor leaves module augmentations out of a rollup, so the entry file's own top-level declare module "x" { ... } and declare global { ... } blocks are copied into its .d.ts afterwards. That's how a React wrapper's JSX.IntrinsicElements reach consumers.
Flags
The args passed in are checked for these keys:
| Flag | Effect |
| --- | --- |
| force, f | build even when nothing changed |
| dev | also emit declaration maps |
| prod | use tsconfig.prod.json when it exists |
Design notes
- The roll-up currently runs on
@microsoft/api-extractor. The package exists to replace it with a from-scratch bundler, so the dependency is expected to go away. - Change detection shares
hasChangedwith@papit/bundle-js, stored as.temp/mstime.papit-bundle-ts.json.
License
Licensed under the @Papit License 1.0 Copyright (c) 2024 Henry Pap (@onkelhoy)
Key points
- ✅ Free to use in commercial projects
- ✅ Free to modify and distribute
- ✅ Attribution required
- ❌ Cannot resell the component itself as a standalone product
See the LICENSE file for full details.
Related
- @papit/build - runs this and
@papit/bundle-jsfor every package - @papit/bundle-js - the matching JavaScript bundler, and where entry points are resolved
