Skip to content

Latest commit

 

History

History
497 lines (337 loc) · 13.4 KB

README.md

File metadata and controls

497 lines (337 loc) · 13.4 KB
import { html } from 'docup'

export default () => {
  let isPreview = location.hostname !== 'tsup.egoist.dev'

  if (!isPreview) return null

  return html`
    <div class="message message_type__warning">
      This is a preview version of the docs.
    </div>
  `
}

Bundle your TypeScript library with no config, powered by esbuild.

What can it bundle?

Anything that's supported by Node.js natively, namely .js, .json, .mjs. And TypeScript .ts, .tsx. CSS support is experimental.

Install

Install it locally in your project folder:

npm i tsup -D
# Or Yarn
yarn add tsup --dev

You can also install it globally but it's not recommended.

Usage

Bundle files

tsup [...files]

Files are written into ./dist.

You can bundle multiple files in one go:

tsup src/index.ts src/cli.ts

This will output dist/index.js and dist/cli.js.

Excluding packages

By default tsup bundles all import-ed modules but dependencies and peerDependencies in your packages.json are always excluded, you can also use --external <module|pkgJson> flag to mark other packages or other special package.json's dependencies and peerDependencies as external.

Excluding all packages

If you are using tsup to build for Node.js applications/APIs, usually bundling dependencies is not needed, and it can even break things, for instance, while outputting to ESM.

tsup automatically excludes packages specified in the dependencies and peerDependencies fields in the packages.json, but if it somehow doesn't exclude some packages, this library also has a special executable tsup-node that automatically skips bundling any Node.js package.

tsup-node src/index.ts

All other CLI flags still apply to this command.

If the regular tsup command doesn't work for you, please submit an issue with a link to your repo so we can make the default command better.

Using custom configuration

You can also use tsup using file configurations or in a property inside your package.json, and you can even use TypeScript and have type-safety while you are using it.

INFO: Most of these options can be overwritten using the CLI options

You can use any of these files:

  • tsup.config.ts
  • tsup.config.js
  • tsup.config.cjs
  • tsup.config.json
  • tsup property in your package.json

INFO: In all the custom files you can export the options either as tsup, default or module.exports =

You can also specify a custom filename using the --config flag, or passing --no-config to disable config files.

Check out all available options.

TypeScript / JavaScript

import { defineConfig } from 'tsup'

export default defineConfig({
  entry: ['src/index.ts'],
  splitting: false,
  sourcemap: true,
  clean: true,
})

Conditional config

If the config needs to be conditionally determined based on CLI flags, it can export a function instead:

import { defineConfig } from 'tsup'

export default defineConfig((options) => {
  return {
    minify: !options.watch,
  }
})

The options here is derived from CLI flags.

package.json

{
  "tsup": {
    "entry": ["src/index.ts"],
    "splitting": false,
    "sourcemap": true,
    "clean": true
  },
  "scripts": {
    "build": "tsup"
  }
}

Multiple entrypoints

Beside using positional arguments tsup [...files] to specify multiple entrypoints, you can also use the cli flag --entry:

# Outputs `dist/a.js` and `dist/b.js`.
tsup --entry src/a.ts --entry src/b.ts

# Outputs `dist/foo.js` and `dist/bar.js`.
tsup --entry.foo src/a.ts --entry.bar src/b.ts

It's equivalent to the following tsup.config.ts:

export default defineConfig({
  // Outputs `dist/a.js` and `dist/b.js`.
  entry: ['src/a.ts', 'src/b.ts'],
  // Outputs `dist/foo.js` and `dist/bar.js`
  entry: {
    foo: 'src/a.ts',
    bar: 'src/b.ts',
  },
})

Generate declaration file

tsup index.ts --dts

This will emit ./dist/index.js and ./dist/index.d.ts.

If you have multiple entry files, each entry will get a corresponding .d.ts file. So when you only want to generate declaration file for a single entry, use --dts <entry> format, e.g. --dts src/index.ts.

Note that --dts does not resolve external (aka in node_modules) types used in the .d.ts file, if that's somehow a requirement, try the experimental --dts-resolve flag instead.

Emit declaration file only

The --dts-only flag is the equivalent of the emitDeclarationOnly option in tsc. Using this flag will only emit the declaration file, without the JavaScript files.

Generate TypeScript declaration maps (.d.ts.map)

TypeScript declaration maps are mainly used to quickly jump to type definitions in the context of a monorepo (see source issue and official documentation).

They should not be included in a published NPM package and should not be confused with sourcemaps.

Tsup is not able to generate those files. Instead, you should use the TypeScript compiler directly, by running the following command after the build is done: tsc --emitDeclarationOnly --declaration.

You can combine this command with Tsup onSuccess callback.

Generate sourcemap file

tsup index.ts --sourcemap

This will emit ./dist/index.js and ./dist/index.js.map.

If you set multiple entry files, each entry will get a corresponding .map file.

If you want to inline sourcemap, you can try:

tsup index.ts --sourcemap inline

Warning: Note that inline sourcemap is solely used for development, e.g. when developing a browser extension and the access to .map file is not allowed, and it's not recommended for production.

Warning: Source map is not supported in --dts build.

Bundle formats

Supported format: esm, cjs, (default) and iife.

You can bundle in multiple formats in one go:

tsup src/index.ts --format esm,cjs,iife

That will output files in following folder structure:

dist
├── index.mjs         # esm
├── index.global.js   # iife
└── index.js          # cjs

If the type field in your package.json is set to module, the filenames will be slightly different:

dist
├── index.js          # esm
├── index.global.js   # iife
└── index.cjs         # cjs

Read more about esm support in Node.js.

If you don't want extensions like .mjs or .cjs, e.g. you want your library to be used in a bundler (or environment) that doesn't support those, you can enable --legacy-output flag:

tsup src/index.ts --format esm,cjs,iife --legacy-output

..which outputs to:

dist
├── esm
│   └── index.js
├── iife
│   └── index.js
└── index.js

Output extension

You can also change the output extension of the files by using outExtension option:

export default defineConfig({
  outExtension({ format }) {
    return {
      js: `.${format}.js`,
    }
  },
})

This will generate your files to [name].[format].js.

The signature of outExtension is:

type OutExtension = (ctx: Context) => Result

type Context = {
  options: NormalizedOptions
  format: Format
  /** "type" field in project's package.json */
  pkgType?: string
}

type Result = { js?: string }

Code Splitting

Code splitting currently only works with the esm output format, and it's enabled by default. If you want code splitting for cjs output format as well, try using --splitting flag which is an experimental feature to get rid of the limitation in esbuild.

To disable code splitting altogether, try the --no-splitting flag instead.

ES5 support

You can use --target es5 to compile the code down to es5, in this target your code will be transpiled by esbuild to es2020 first, and then transpiled to es5 by SWC.

Compile-time environment variables

You can use --env flag to define compile-time environment variables:

tsup src/index.ts --env.NODE_ENV production

Building CLI app

When an entry file like src/cli.ts contains hashbang like #!/bin/env node tsup will automatically make the outout file executable, so you don't have to run chmod +x dist/cli.js.

Watch mode

tsup src/index.ts --watch

Turn on watch mode. This means that after the initial build, tsup will continue to watch for changes in any of the resolved files.

INFO: By default it always ignores dist, node_modules & .git

tsup src/index.ts --watch --ignore-watch ignore-this-folder-too

INFO: You can specify more than a folder repeating "--ignore-watch", for example: tsup src src/index.ts --watch --ignore-watch folder1 --ignore-watch folder2

onSuccess

You can specify command to be executed after a successful build, specially useful for Watch mode

tsup src/index.ts --watch --onSuccess "node dist/index.js"

onSuccess can also be a function that returns Promise. For this to work, you need to use tsup.config.ts instead of the cli flag:

import { defineConfig } from 'tsup'

export default defineConfig({
  async onSuccess() {
    // Start some long running task
    // Like a server
  },
})

You can return a cleanup function in onSuccess:

import { defineConfig } from 'tsup'

export default defineConfig({
  async onSuccess() {
    const server = http.createServer((req, res) => {
      res.end('Hello World!')
    })
    server.listen(3000)
    return () => {
      server.close()
    }
  },
})

Minify output

You can also minify the output, resulting into lower bundle sizes by using the --minify flag.

tsup src/index.ts --minify

Custom loader

Esbuild loader list:

type Loader =
  | 'js'
  | 'jsx'
  | 'ts'
  | 'tsx'
  | 'css'
  | 'json'
  | 'text'
  | 'base64'
  | 'file'
  | 'dataurl'
  | 'binary'
  | 'default'

To use a custom loader via CLI flag:

tsup --loader ".jpg=base64" --loader ".webp=file"

Or via tsup.config.ts:

import { defineConfig } from 'tsup'

export default defineConfig({
  loader: {
    '.jpg': 'base64',
    '.webp': 'file',
  },
})

Tree shaking

esbuild has tree shaking enabled by default, but sometimes it's not working very well, see #1794 #1435, so tsup offers an additional option to let you use Rollup for tree shaking instead:

tsup src/index.ts --treeshake

This flag above will enable Rollup for tree shaking, and it's equivalent to the following tsup.config.ts:

import { defineConfig } from 'tsup'

export default defineConfig({
  treeshake: true,
})

This option has the same type as the treeshake option in Rollup, see more.

What about type checking?

esbuild is fast because it doesn't perform any type checking, you already get type checking from your IDE like VS Code or WebStorm.

Additionally, if you want type checking at build time, you can enable --dts, which will run a real TypeScript compiler to generate declaration file so you get type checking as well.

CSS support

esbuild has experimental CSS support, and tsup allows you to use PostCSS plugins on top of native CSS support.

To use PostCSS, you need to install PostCSS:

yarn add postcss --dev

..and populate a postcss.config.js in your project

module.exports = {
  plugins: [require('tailwindcss')(), require('autoprefixer')()],
}

Metafile

Passing --metafile flag to tell esbuild to produce some metadata about the build in JSON format. You can feed the output file to analysis tools like bundle buddy to visualize the modules in your bundle and how much space each one takes up.

The file outputs as metafile-{format}.json, e.g. tsup --format cjs,esm will generate metafile-cjs.json and metafile-esm.json.

Custom esbuild plugin and options

Use esbuildPlugins and esbuildOptions respectively in tsup.config.ts:

import { defineConfig } from 'tsup'

export default defineConfig({
  esbuildPlugins: [YourPlugin],
  esbuildOptions(options, context) {
    options.define.foo = '"bar"'
  },
})

The context argument for esbuildOptions:

  • context.format: cjs, esm, iife

See all options here, and how to write an esbuild plugin.


For more details:

tsup --help

Troubleshooting

error: No matching export in "xxx.ts" for import "xxx"

This usually happens when you have emitDecoratorMetadata enabled in your tsconfig.json, in this mode we use SWC to transpile decorators to JavaScript so exported types will be eliminated, that's why esbuild won't be able to find corresponding exports. You can fix this by changing your import statement from import { SomeType } to import { type SomeType } or import type { SomeType }.

License

MIT © EGOIST