dart2esm 0.2.0
dart2esm: ^0.2.0 copied to clipboard
A Dart-to-native-ESM compiler that emits clean modern JavaScript modules.
dart2esm #
A Dart-to-native-ESM compiler built on Dart's Common Front End and Kernel IR.
dart2esm emits modern ECMAScript modules directly. It uses JavaScript, Web
APIs, and Node primitives where their behavior matches Dart, then links only the
small runtime helpers required by the program.
Highlights #
- Native ESM output with real
importandexportbindings. - ES2022, ES2023, and ESNext syntax targets.
- Neutral ECMAScript, browser, and Node host profiles.
- Readable debug output and optimized release output.
- Member reachability, runtime tree shaking, safe name mangling, and constant compaction in release builds.
- Node-backed
dart:ioand Worker-backed isolate support where host semantics exist. - Structured diagnostics and raw/gzip output metrics.
- Dart VM versus Node behavior tests and a pinned conformance manifest.
Important
dart2esm is a 0.x compiler, not a byte-for-byte Dart VM replacement. FFI,
VM service APIs, native extensions, and full reflection require runtime
facilities that native ESM does not provide.
Requirements #
dart2esm 0.2.x requires Dart 3.12.x. The compiler reads the Dart 3.12 Kernel
binary format directly, so newer SDK versions are intentionally rejected until
their Kernel format is adopted and tested.
Generated modules require an ES2022-capable host. Host-specific APIs additionally require the selected browser or Node capabilities.
Installation #
Install the command globally from pub.dev:
dart pub global activate dart2esm
Verify the installation:
dart2esm --version
Quick start #
Given hello.dart:
void main() {
print('Hello from native ESM');
}
Compile and run it:
dart2esm hello.dart -o hello.mjs --mode=release
node hello.mjs
By default, the generated module invokes main() at module evaluation time.
Use --no-run-main when the output will be imported by another ESM module:
dart2esm lib/api.dart -o dist/api.mjs --mode=release --no-run-main
import { calculate } from "./dist/api.mjs";
console.log(calculate());
Only declarations exported by the Dart entry library become public ESM exports. Imported Dart libraries are bundled into the same output module.
Compiler modes #
Debug #
--mode=debug is the default. It keeps stable, readable names and formatted
output for inspection.
Release #
--mode=release applies behavior-preserving production transforms:
- unreachable control-flow removal;
- conservative member reachability;
- local, internal, private-member, and runtime binding shortening;
- canonical constant and frozen-object compaction;
- runtime dependency relinking;
- compact structured ESM and runtime printing.
Exports, runtime ABI contracts, dynamic selector names, raw JavaScript properties, and user-visible strings remain protected.
Command reference #
dart2esm <input.dart|input.dill> -o <output.mjs> [options]
| Option | Description |
|---|---|
-o, --output |
Required output .mjs path. |
--packages |
Explicit package_config.json path. |
-D, --define |
Compile-time environment declaration; repeatable. |
--platform |
auto, neutral, browser, or node. |
--target |
es2022, es2023, or esnext. |
--mode |
debug or release. |
--[no-]run-main |
Enable or disable the top-level main() invocation. |
--metrics |
Print raw/gzip size, line count, and runtime helper count. |
--compare-dart2js |
Compile a dart2js -O2 baseline and print size ratios. |
--version |
Print the package version. |
-h, --help |
Print complete CLI help. |
Existing Kernel components can also be compiled, but they require an explicit host profile:
dart2esm build/input.dill -o dist/input.mjs --platform=node
Platform profiles #
| Profile | Intended host | Behavior |
|---|---|---|
auto |
CLI convenience | Uses VM Kernel when possible and selects the concrete host profile; retries the browser frontend for web-only libraries. |
neutral |
Portable ECMAScript | Rejects Node-only and Web-only runtime capabilities. |
browser |
Modern browsers | Uses MDN Web API semantics and browser module Workers. |
node |
Modern Node.js | Enables node: imports, filesystem/process APIs, Web APIs, and module Workers. |
dart:io #
dart:io lowering is capability-based:
- Value APIs such as
FileMode,HeaderValue,ContentType, andCookieemit ordinary ECMAScript. - Client HTTP maps to Fetch and Headers.
- Client WebSockets map to the host WebSocket API.
- Files, directories, links, processes, and standard IO require the Node
profile and emit static
node:imports.
Unsupported raw sockets, HTTP servers, custom TLS contexts, and browser handshake features fail explicitly instead of receiving approximate behavior.
Isolates #
Top-level Isolate.spawn and Isolate.run entries map to Node Workers or
browser module Workers. Ports use MessageChannel, and messages use structured
cloning. Closures, custom object prototypes, paused startup, and VM-specific
error/exit-port behavior remain outside the supported Worker subset.
Compatibility boundary #
The compiler covers Dart features that can be represented with ECMAScript, Web APIs, Node APIs, and a finite runtime library.
| Area | Status |
|---|---|
| Classes, functions, records, enums, patterns, control flow | Supported |
| Imports and packages | Bundled into one ESM output module |
| SDK libraries | Host-dependent |
| JavaScript numbers and guarded safe integers | Supported |
| 64-bit operations requiring exact width | BigInt where required |
dart:io |
Portable subset plus Node capabilities |
dart:isolate |
Worker-compatible subset |
dart:ffi |
Rejected; requires a WebAssembly or Node native-addon ABI |
dart:mirrors |
Rejected; closed-world ESM has no VM reflection service |
| VM service and native extensions | Rejected |
| Source maps | Not emitted yet |
The authoritative machine-readable matrix is
spec/conformance.json.
Dart API #
The compiler is also available as a library:
import 'dart:io';
import 'package:dart2esm/dart2esm.dart';
Future<void> main() async {
final result = await compileDartToEsm(
Dart2EsmOptions(
inputPath: 'bin/main.dart',
outputPath: 'dist/main.mjs',
workingDirectory: Directory.current,
platform: Dart2EsmPlatform.node,
optimization: Dart2EsmOptimizationLevel.release,
),
);
if (!result.success) {
for (final diagnostic in result.diagnosticEntries) {
stderr.writeln(diagnostic.render());
}
exitCode = 1;
}
}
The public API exposes compiler profiles, structured diagnostics, code-size
metrics, results, and the CompilerDriver entrypoint.
Output size #
Use the repository benchmark instead of assuming that one compiler is always smaller:
dart run tool/benchmark.dart --compare-dart2js
On the checked-in YAML fixture with Dart 3.12.2:
| Compiler | Raw | Gzip |
|---|---|---|
| dart2esm release | 175,963 B | 38,532 B |
dart2js -O2 |
196,930 B | 61,256 B |
| Ratio | 0.894x | 0.629x |
This fixture is a regression benchmark, not a universal size guarantee.
Architecture #
The production pipeline is:
Dart source / Kernel
-> frontend and profile resolution
-> Kernel validation
-> semantic analysis and reachability
-> structured ESM lowering
-> release transforms
-> runtime contract linking
-> ESM code generation
See doc/compiler-architecture.md for stage
contracts and invariants.
Development #
dart analyze
dart test
npm run test:esm
dart run tool/benchmark.dart --compare-dart2js
dart pub publish --dry-run