tochibuild
Version:
An extremely fast JavaScript/Typescript and CSS bundler with minimal configuration.
406 lines (325 loc) • 17.2 kB
Markdown
# tochibuild
`tochibuild` is a safer, smarter, tsup-powered bundler designed to reduce mistakes and enhance configurability — whether you build single packages, monorepos, or libraries.
Powered by [tsup](https://github.com/egoist/tsup) and [esbuild](https://github.com/evanw/esbuild).
## Table of Contents
- [tochibuild](#tochibuild)
- [Table of Contents](#table-of-contents)
- [Features](#features)
- [Installation](#installation)
- [Usage](#usage)
- [Configuration](#configuration)
- [Config Helpers](#config-helpers)
- [`defineConfig(options, config?)`](#defineconfigoptions-config)
- [`defineConfigWithIndexEntries(options, config?)`](#defineconfigwithindexentriesoptions-config)
- [`defineConfigWithCommonEntries(options, config?)`](#defineconfigwithcommonentriesoptions-config)
- [BuildUtils](#buildutils)
- [Entrypoint patterns](#entrypoint-patterns)
- [Utility Methods](#utility-methods)
- [Build Lifecycles](#build-lifecycles)
- [CLI Commands](#cli-commands)
- [`tochibuild`](#tochibuild-1)
- [Tip](#tip)
- [`tochibuild config`](#tochibuild-config)
- [`tochibuild pack`](#tochibuild-pack)
- [`tochibuild merge`](#tochibuild-merge)
- [Type Safety \& Defaults](#type-safety--defaults)
- [Troubleshooting](#troubleshooting)
- [`Error: Cannot find module 'typescript'`](#error-cannot-find-module-typescript)
- [Docs \& Reference](#docs--reference)
- [Credits](#credits)
## Features
- [x] Safer `clean` defaults to protect your project
- [x] Build lifecycle support (pre/post hooks, publish, deprecate)
- [x] Smarter `defineConfig` helpers for common entry patterns
- [x] Custom config support (`.ts`, `.js`, `.json`, `package.json`)
- [x] Utilities for merging configs, creating tarballs, and manipulating package.json
- [x] CLI with useful commands like `config`, `merge` and `pack`
- [x] Powered by `tsup`, extended by `tochibuild`
## Installation
```bash
npm i -D tochibuild
```
## Usage
```bash
tochibuild [options]
```
- If no options are passed, `tochibuild` looks for a config file or `tochibuild` key in `package.json`.
- Use `--config <file>` to specify a config file, or `--no-config` to skip config files entirely.
## Configuration
You can optionally configure `tochibuild` via:
- `tochibuild.config.ts`
- `tochibuild.config.js`
- `tochibuild.config.json`
- `tochibuild` property in `package.json`
```ts
// tochibuild.config.ts
import { defineConfig } from 'tochibuild';
export default defineConfig({
entry: ['src/index.ts'],
clean: false,
splitting: true,
});
```
or with hooks
```ts
// tochibuild.config.ts
import { defineConfig } from 'tochibuild';
import packageJson from './package.json';
export default defineConfig({
entry: ['src/index.ts'],
clean: false,
splitting: true,
{
lifecycle: {
packageManager: 'bun',
hooks: {
publish: {
command: 'publish',
args: ['--access', 'public', '--no-private'],
},
deprecate: {
packageManager: 'npm',
args: [
`${packageJson.name}@"<${packageJson.version}"`,
`"Deprecated: Please upgrade to version ${packageJson.version} or higher."`,
],
},
custom: [
{
position: 'before.build',
cli: {
name: 'node',
args: ['scripts/prebuild.js']
}
},
{
name: 'copy-types',
position: 'after.build',
action: async utils => {
await utils.mergeFiles(
['dist/index.d.mts', 'types/*.d.ts'],
'dist/index.d.mts'
);
},
},
],
},
// use `packageJson` to manipulate the package.json
// that will be included in the published package
packageJson: data => ({
...data,
main: 'dist/index.mjs',
types: 'dist/index.d.mts',
exports: {
'.': {
types: './dist/index.d.mts',
require: './dist/index.mjs',
import: './dist/index.mjs',
},
},
}),
},
}
});
```
> `clean`, `splitting`, and `entry` are required to avoid accidental destructive behavior.
## Config Helpers
### `defineConfig(options, config?)`
Main entrypoint for config with fully customizable options.
### `defineConfigWithIndexEntries(options, config?)`
Uses `BuildUtils.defaultEntriesIndex` as `entry`.
### `defineConfigWithCommonEntries(options, config?)`
Uses `BuildUtils.defaultEntriesCommon` as `entry`.
Each helper includes default values like:
```ts
{
tsconfig: "tsconfig.json",
dts: true,
format: ["esm"],
minify: true,
}
```
## BuildUtils
Utility class that powers `tochibuild`.
```ts
import { BuildUtils } from 'tochibuild';
```
### Entrypoint patterns
- `BuildUtils.entriesIndex` → `['**/index.{js,ts}']`
- `BuildUtils.entriesCommon` → `['**/*.{js,ts}']`
- `BuildUtils.ignoredEntries` → `['!*.d.ts', '!build/**', '!dist/**', '!node_modules/**', ...]`
- `BuildUtils.defaultEntriesIndex` → Combined index + ignored
- `BuildUtils.defaultEntriesCommon` → Combined common + ignored
### Utility Methods
- `generateConfig()` – Creates config file programmatically
- `ensureEntries()` – Ensures required entries are included
- `getPackageJson()` – Loads `package.json` file + data
- `getPackageJsonDeps()` – Retrieves dependencies from a package.json
- `mergeFiles()` – Merge multiple files into a single output
- `createTarball()` – Build a publish-ready tarball from a package
## Build Lifecycles
You can run hooks before and after certain events.
The order of the hooks are:
1. `before.build`
2. `after.build`
3. `before.publish`
4. `publish`
5. `after.publish`
6. `before.deprecate`
7. `deprecate`
8. `after.deprecate`
Define lifecycle scripts for `publish`, `deprecate`, or custom commands/actions.
```ts
lifecycle: {
packageManager: 'npm',
hooks: {
publish: {
args: ['--no-private']
},
custom: [
{
position: 'before.build',
cli: {
name: 'node',
args: ['scripts/prebuild.js']
}
},
{
name: 'copy-types',
position: 'after.build',
action: async utils => {
await utils.mergeFiles(
['dist/index.d.mts', 'types/*.d.ts'],
'dist/index.d.mts'
);
},
},
]
}
}
```
## CLI Commands
### `tochibuild`
Basic build command:
```bash
# uses configuration from tochibuild.config or package.json
tochibuild
```
```bash
# uses configuration from tochibuild.config or package.json
# but overrides format, target and splitting
tochibuild --format esm --target node --splitting
```
```bash
# uses a custom configuration path
tochibuild --config custom/path/to/config.ts
```
```bash
# uses a custom configuration path
# appends entries
tochibuild --config custom/path/to/config.ts --entry somefile.ts --entry anotherfile.ts
```
Refer to `tsup` for more info: https://tsup.egoist.dev/#usage
| Option | Description | Default |
|-------------------------------|----------------------------------------------------------------------------------------------|------------------|
| `[...files]` | Files to bundle | - |
| `--entry <file>` | Use a key-value pair as entry files | - |
| `-d, --out-dir <dir>` | Output directory | - |
| `--format <...format>` | Bundle format: `esm`, `cjs`, `iife` | - |
| `--minify [terser]` | Minify bundle | `false` |
| `--minify-whitespace` | Minify whitespace only | `false` |
| `--minify-identifiers` | Minify identifiers only | `false` |
| `--minify-syntax` | Minify syntax only | `false` |
| `--keep-names` | Preserve function/class names in minified code | `false` |
| `--target <...target>` | Bundle target: `esnext`, `es2022`, etc. | `esnext` |
| `--legacy-output` | Output formats to separate folders instead of using different extensions | `false` |
| `--dts [entry]` | Generate declaration file (`.d.ts`) | `false` |
| `--dts-resolve` | Resolve external types for `.d.ts` files | `false` |
| `--dts-only` | Emit only `.d.ts` files | `false` |
| `--experimental-dts [entry]` | Experimental `.d.ts` generation | - |
| `--sourcemap [inline]` | Generate sourcemap or inline sourcemap | `false` |
| `--watch [...path]` | Enable watch mode (defaults to `.` if path omitted) | `false` |
| `--ignore-watch <...path>` | Ignore paths in watch mode | - |
| `--onSuccess <command>` | Run a command after a successful build | - |
| `--env.* <value>` | Define compile-time environment variables | - |
| `--inject <file>` | Replace a global with an import from another file | - |
| `--define.* <value>` | Define compile-time constants | - |
| `--external <name>` | Mark packages or deps as external | - |
| `--global-name <name>` | Global var name for IIFE builds | - |
| `--jsxFactory <name>` | Name of JSX factory function | `React.createElement` |
| `--jsxFragment <name>` | Name of JSX fragment function | `React.Fragment` |
| `--replaceNodeEnv` | Replace `process.env.NODE_ENV` | `false` |
| `--no-splitting` | Disable code splitting | `false` |
| `--clean` | Clean output directory before build | `false` |
| `--silent` | Suppress logs except errors and onSuccess output | `false` |
| `--pure <...expr>` | Mark expressions as pure for tree-shaking | - |
| `--metafile` | Emit esbuild metafile (`.json`) | `false` |
| `--platform <platform>` | Target platform: `node`, `browser`, `neutral` | `node` |
| `--loader <ext=loader>` | Custom loader for specific file extensions | - |
| `--tsconfig <filename>` | Use a specific `tsconfig.json` | `tsconfig.json` |
| `--config <filename>` | Use a custom `tochibuild.config` file | - |
| `--no-config` | Disable config file loading | `false` |
| `--shims` | Enable CJS and ESM shims | `true` |
| `--inject-style` | Inject CSS via style tag in the document head | `false` |
| `--treeshake [strategy]` | Treeshaking: `recommended`, `smallest`, `safest` | `recommended` |
| `--publicDir [dir]` | Copy contents of a public directory to output | `public` |
| `--killSignal <signal>` | Signal to kill child process: `SIGTERM`, `SIGKILL` | `SIGTERM` |
| `--cjsInterop` | Enable CommonJS interop | `true` |
---
#### Tip
For most options like `--env.*`, `--define.*`, etc., you can repeat the flag or use dot notation:
```bash
tochibuild --env.API_URL=https://api.com --define.DEBUG=true
```
### `tochibuild config`
Generate a template config file.
```bash
tochibuild config --ext .ts -f custom.config --dir ./configs --overwrite
```
| Flag | Description | Default |
| ------------------- | ----------------------------------------------------------- | ------------------------- |
| `--ext` | File extension to use (`.ts`, `.js`, `.cjs`, `.json`) | `.ts` |
| `--filename`, `-f` | Name of the config file (excluding extension) | `tochibuild.config` |
| `--outDir` | Output directories (absolute or relative, multiple allowed) | Current working directory |
| `--overwrite`, `-o` | Overwrite file if it already exists | `false` |
---
### `tochibuild pack`
Generate a `.tgz` tarball for publishing.
```bash
tochibuild pack --dir . --pkg ./package.json --out ./dist
```
| Flag | Description | Default |
| ------------- | ------------------------------------------------------------- | ------------------------- |
| `--dir` | Path to the original package directory (relative or absolute) | Current working directory |
| `--pkg`, `-p` | Path to `package.json` (relative or absolute) | `package.json` |
| `--out`, `-o` | Output tarball path (relative or absolute) | `tochibuild.package.tgz` |
---
### `tochibuild merge`
Merge the contents of all files into a single file.
```bash
tochibuild merge <outFile> <...files>
tochibuild merge dist/merged.d.ts types/a.d.ts types/b.d.ts types/c.d.ts
```
| Argument | Description |
| ------------ | --------------------------------------------------------------------- |
| `<outFile>` | Output file path (relative or absolute) |
| `<...files>` | One or more file paths to merge into the output file (glob supported) |
## Type Safety & Defaults
All config helpers enforce required properties (`entry`, `clean`, `splitting`) and provide smart defaults to prevent accidents.
> `tochibuild` exists to keep you from `rm -rf ./`-ing your soul again.
## Troubleshooting
### `Error: Cannot find module 'typescript'`
Happens when using `npx`. Use a local install instead:
```bash
npm i -D tochibuild typescript
```
More: [tsup troubleshooting](https://tsup.egoist.dev/#troubleshooting)
## Docs & Reference
- Full API: see the type definitions in `index.d.mts`
- tsup options: https://www.jsdocs.io/package/tsup#Options
## Credits
Built on top of:
- [tsup](https://github.com/egoist/tsup)
- [esbuild](https://github.com/evanw/esbuild)
- [npm-packlist](https://github.com/npm/npm-packlist)
- [@npmcli/arborist](https://github.com/npm/arborist)