Ember Support
tsdown can build Ember v2 addons (libraries) with @nullvoxpopuli/ember-rolldown. This meta-plugin compiles .gts and .gjs files, including their <template> tags, into publishable output. A single ember() call replaces the usual @embroider/* externalization setup, content-tag preprocessing, and Babel integration.
NOTE
The plugin currently targets Ember libraries (v2 addons); building Ember apps has not been tested.
Minimal Example
Configure an Ember library in tsdown.config.ts as follows:
import { ember } from '@nullvoxpopuli/ember-rolldown'
import { defineConfig } from 'tsdown'
export default defineConfig({
entry: ['./src/index.ts'],
dts: true,
plugins: [ember()],
})Create a typical Ember component:
import type { TOC } from '@ember/component/template-only'
export interface BadgeSignature {
Element: HTMLSpanElement
Args: { label: string }
Blocks: { default: [] }
}
export const Badge: TOC<BadgeSignature> = <template>
<span class="badge" ...attributes>
{{@label}}
{{yield}}
</span>
</template>And export it from your entry file:
export { Badge } from './components/badge.gts'The build writes .js and .d.ts files for each entry to dist/ and emits source maps for the JavaScript output. Ember virtual packages (such as @ember/component and @glimmer/tracking) and packages listed in dependencies or peerDependencies remain external and are resolved by the consuming app.
Install the required dependency:
npm install -D @nullvoxpopuli/ember-rolldownpnpm add -D @nullvoxpopuli/ember-rolldownyarn add -D @nullvoxpopuli/ember-rolldownbun add -D @nullvoxpopuli/ember-rolldownNOTE
@nullvoxpopuli/ember-rolldown requires Node.js 24+. Because the package ships TypeScript source, type-checking a project that uses it also requires TypeScript 6+ and a lib setting that includes es2025 (for example, esnext).
Declarations
.gts and .gjs template-tag modules exist only in the bundler's module graph, so their declarations must be generated with isolated declarations. The tsconfig used for the build must enable isolatedDeclarations; otherwise, ember() reports an error:
{
"compilerOptions": {
"isolatedDeclarations": true,
},
}isolatedDeclarations requires sufficient type annotations on exports so declarations can be generated without cross-file type inference. For example, the component above uses the explicit TOC<BadgeSignature> type annotation. If your package also contains development-only code that should not be subject to this constraint, such as a demo app or in-package tests, point the tsdown tsconfig option to a publish-only config:
export default defineConfig({
entry: ['./src/index.ts'],
tsconfig: './tsconfig.publish.json',
plugins: [ember()],
})How It Works
ember() returns a set of Rolldown plugins that:
- Keep packages from
dependenciesandpeerDependencies, along with Ember virtual packages, external for the consuming app to resolve. - Preprocess
<template>tags withcontent-tagand map.gts/.gjsto.ts/.jsso Rolldown can process them. - Run Babel only on files that need it, such as files with template tags or decorators. All other files use Rolldown's fast native transforms.
- Verify that the
tsconfigenablesisolatedDeclarations.
A Babel configuration is optional. Without one, ember() uses built-in defaults to handle templates, decorators, and TypeScript. If you provide one, ember() uses it instead.
CSS
If a component imports co-located CSS (import './badge.css'), install @tsdown/css in your project. tsdown detects the package automatically and bundles imported stylesheets into a single CSS file in dist/. Set css: { inject: true } to preserve an import for that generated CSS file in the JavaScript output, allowing consuming apps to load the styles through the module graph.
For advanced topics such as app re-exports for classic name-based resolution, ember-scoped-css integration, and publish-specific Babel configurations, see the plugin documentation.