Dart 98 🎯 Porting Node.js or Python Utilities to Cross-Platform Standalone Dart Binaries
Porting a utility from Node.js or Python to Dart is not a single translation step. It is a sequence of decisions: which Dart libraries replace the source language’s standard library, whether the runtime dependencies can be satisfied without a native addon, and how the final artifact is compiled and distributed. The destination is a standalone executable produced by dart compile exe, which bundles the Dart runtime and the application code into a single binary that runs on a machine without the Dart SDK installed . The porting work is in the gap between what the source language provides out of the box and what Dart provides, and in the native interop that fills that gap when it exists.
Key point: dart compile exe produces a self-contained native binary for Windows, macOS, and Linux. Cross-compilation is supported only for Linux targets, from any 64-bit host OS . The compiled binary cannot use dart:mirrors or dart:developer, and packages that rely on build hooks must use dart build instead .
Why porting to standalone Dart binaries is practical
The runtime dependency problem. A Node.js utility requires Node.js on the target machine. A Python utility requires the Python interpreter and its package dependencies. A Dart executable compiled with dart compile exe includes the Dart runtime and the application’s machine code in a single file . The target machine needs the operating system’s C library and nothing else. This is the same distribution model as a Go binary or a Rust binary, and it removes the installation step that complicates deployment of interpreted-language utilities.
The performance problem. Dart’s AOT compiler produces native machine code, which starts in milliseconds rather than the hundreds of milliseconds required to start a Node.js or Python interpreter and load the application’s modules. The Dart team’s announcement of the dart2native predecessor cited a website link checker that became 27 times faster after recompilation . The SASS implementation written in Dart and compiled to native code was reported to be competitive with the C++ implementation . The performance gain is not universal—it depends on the workload—but the startup time improvement is consistent.
The library gap problem. Dart’s standard library covers the common needs: dart:io for file and process operations, dart:convert for JSON and UTF-8, package:http for network requests, and package:args for command-line parsing . The gaps appear when the source utility uses a language-specific idiom. Node.js utilities often rely on the path module, the process object, or JavaScript’s array methods. Python utilities often rely on list comprehensions, os.path, or third-party packages with no Dart equivalent. The porting work is in these gaps.
a. Replacing Node.js Standard Library Dependencies
The node_shims package exists to ease the porting of Node.js and JavaScript libraries to Dart. It provides Dart implementations of common Node.js and JavaScript utilities: path manipulation (normalize, join, resolve, dirname, basename, extname), JavaScript-style array and string helpers, and process utilities . The package’s stated goal is that “behavior of libs should match the js implementation as closely as dart allows” .
// Node.js path.join
const path = require('path');
const fullPath = path.join(__dirname, 'data', 'file.txt');
// Dart equivalent using node_shims
import 'package:node_shims/path.dart' as path;
final fullPath = path.join([Directory.current.path, 'data', 'file.txt']);
For utilities that spawn child processes, Dart’s Process.run and Process.start replace Node.js’s child_process module. Process.run executes a process to completion and returns a ProcessResult with stdout, stderr, and exit code. Process.start returns a Process object that allows interaction with the running process’s stdin and stdout .
// Node.js: child_process.execSync('ls -l')
import 'dart:io';
final result = await Process.run('ls', ['-l']);
print(result.stdout);
The dart:io library also provides File, Directory, HttpClient, and Socket, which cover most of the operations that a Node.js utility would perform with the fs, http, and net modules.
b. Replacing Python Standard Library Dependencies
Python’s standard library is broader than Node.js’s, and the gap to Dart is correspondingly wider. The py2many project is a transpiler that converts Python source to Dart and other target languages, with “preliminary support” for Dart . The project’s documentation distinguishes between deterministic translation, which is suitable for simple, idiomatic Python with direct equivalents, and LLM-assisted translation, which is needed for complex idioms like decorators, metaclasses, and generator expressions .
The deterministic path is appropriate for utilities that use basic control flow, standard data structures (lists, dicts, sets), and simple functions and classes. The LLM-assisted path is appropriate when the Python code uses language-specific features that have no direct Dart equivalent, or when the deterministic output is non-idiomatic . The distinction matters because a Python utility that is ported mechanically may compile but produce unidiomatic Dart that is difficult to maintain.
For utilities that depend on native libraries, Dart’s dart:ffi provides C interop. The dart:ffi library allows Dart code to call C functions, read and write native memory, and allocate and deallocate memory . This is the path for porting a Python utility that wraps a C library through ctypes or cffi. The interop requires a C header and a dynamic library, and the bindings can be generated with package:ffigen . The native_toolchain_c package and the Native Assets build hooks automate the compilation and bundling of the C library with the Dart executable .
c. Compiling and Distributing the Standalone Binary
The compilation command is dart compile exe. The -o flag specifies the output path, and the input is the Dart entry point.
# Compile a standalone executable for the host platform
dart compile exe bin/main.dart -o build/myutil
The output is a self-contained binary. On macOS and Windows, the executable supports signing . The binary starts in milliseconds because the Dart runtime is already compiled into it and the application code is AOT-compiled to machine code.
Cross-compilation is available for Linux targets from any 64-bit host. The --target-os=linux flag and the --target-arch flag select the target. The supported architectures are arm, arm64, riscv64, and x64 .
# Cross-compile for Linux ARM64 from macOS or Windows
dart compile exe --target-os=linux --target-arch=arm64 bin/main.dart -o build/myutil-linux-arm64
The limitation is explicit: only Linux is supported as a cross-compilation target . A developer on macOS cannot produce a Windows executable, and a developer on Windows cannot produce a macOS executable. The binary for those platforms must be compiled on the target platform or in a CI pipeline that runs on that platform.
The known limitations of dart compile exe are also explicit. The compiled binary cannot use dart:mirrors or dart:developer . Packages that use build hooks—the Native Assets mechanism for bundling native code—must be compiled with dart build rather than dart compile exe . For utilities that do not use these features, the exe subcommand is the correct path.
Complete Example Session
// ============================================
// PART 1: A NODE.JS UTILITY TO PORT
// ============================================
// Original Node.js: lists files in a directory,
// filters by extension, prints sizes
// const fs = require('fs');
// const path = require('path');
// fs.readdirSync(dir)
// .filter(f => path.extname(f) === '.txt')
// .forEach(f => console.log(f, fs.statSync(path.join(dir, f)).size));
// ============================================
// PART 2: DART EQUIVALENT USING DART:IO
// ============================================
import 'dart:io';
void main(List<String> args) {
final dir = Directory(args.isNotEmpty ? args[0] : '.');
final files = dir.listSync()
.whereType<File>()
.where((f) => f.path.endsWith('.txt'));
for (final file in files) {
final name = file.uri.pathSegments.last;
print('$name ${file.lengthSync()}');
}
}
// ============================================
// PART 3: PROCESS SPAWNING (REPLACES child_process)
// ============================================
import 'dart:io';
Future<void> main() async {
final result = await Process.run('grep', ['-i', 'main', 'test.dart']);
stdout.write(result.stdout);
stderr.write(result.stderr);
exit(result.exitCode);
}
// ============================================
// PART 4: PATH MANIPULATION WITH node_shims
// ============================================
import 'package:node_shims/path.dart' as path;
void main() {
final full = path.join([Directory.current.path, 'data', 'file.txt']);
final base = path.basename(full);
final ext = path.extname(full);
print('$base ($ext)');
}
// ============================================
// PART 5: PYTHON UTILITY TO PORT
// ============================================
# Original Python: counts lines in files matching a pattern
# import glob, os
# for f in glob.glob('*.txt'):
# with open(f) as fh:
# print(f, len(fh.readlines()))
// ============================================
// PART 6: DART EQUIVALENT
// ============================================
import 'dart:io';
void main() {
for (final entity in Directory.current.listSync()) {
if (entity is File && entity.path.endsWith('.txt')) {
final lines = entity.readAsLinesSync();
print('${entity.uri.pathSegments.last} ${lines.length}');
}
}
}
// ============================================
// PART 7: NATIVE C LIBRARY VIA dart:ffi
// ============================================
import 'dart:ffi';
import 'package:ffi/ffi.dart';
typedef NativeAdd = Int32 Function(Int32 a, Int32 b);
typedef DartAdd = int Function(int a, int b);
void main() {
final lib = DynamicLibrary.open('libnative_add.so');
final add = lib.lookupFunction<NativeAdd, DartAdd>('native_add');
print(add(2, 3)); // 5
}
// ============================================
// PART 8: BUILD THE C LIBRARY WITH NATIVE ASSETS
// ============================================
// hook/build.dart defines the C library build
// The native_toolchain_c package compiles it
// dart build handles the bundling
// (dart compile exe does not support build hooks)
// ============================================
// PART 9: COMPILE THE STANDALONE EXECUTABLE
// ============================================
// Host platform compile
// dart compile exe bin/main.dart -o build/myutil
// Linux cross-compile from macOS/Windows
// dart compile exe --target-os=linux --target-arch=x64 bin/main.dart -o build/myutil-linux-x64
// ============================================
// PART 10: DISTRIBUTE AND VERIFY
// ============================================
// The output binary has no Dart SDK dependency
// Run it on a machine without Dart installed
// ./build/myutil
The ten parts trace the porting workflow: identify the source utility’s dependencies, replace them with Dart equivalents, handle native interop if needed, and compile the standalone binary. Parts 1 through 6 show the library replacement for Node.js and Python utilities. Parts 7 and 8 show the native interop path for C libraries.
Quick Reference
Compilation Targets
| Target | Command | Host Requirement |
|---|---|---|
| Host native | dart compile exe main.dart | Any 64-bit OS |
| Linux x64 | --target-os=linux --target-arch=x64 | Any 64-bit OS |
| Linux ARM64 | --target-os=linux --target-arch=arm64 | Any 64-bit OS |
| Windows/macOS | Compile on target platform | Target OS |
Node.js to Dart Replacements
| Node.js | Dart |
|---|---|
fs | dart:io File, Directory |
path | package:node_shims/path.dart or package:path |
child_process | Process.run, Process.start |
http | package:http |
process.argv | main(List<String> args) |
Python to Dart Replacements
| Python | Dart |
|---|---|
open() | File.readAsStringSync() |
os.path | package:path |
subprocess | Process.run |
json | dart:convert |
sys.argv | main(List<String> args) |
Compilation Limitations
| Limitation | Detail |
|---|---|
dart:mirrors | Not supported |
dart:developer | Not supported |
| Build hooks | Use dart build instead |
| Cross-compilation target | Linux only |
Best Practices
✅ Do This:
// Use Process.run for simple child process execution
final result = await Process.run('grep', ['pattern', 'file']); // ✅
// Use node_shims for Node.js path behavior compatibility
import 'package:node_shims/path.dart' as path; // ✅
# Compile to standalone binary for distribution
dart compile exe bin/main.dart -o build/myutil // ✅
# Cross-compile for Linux from any host
dart compile exe --target-os=linux --target-arch=arm64 bin/main.dart // ✅
❌ Don’t Do This:
// Use dart:mirrors in a compiled executable
import 'dart:mirrors'; // fails with dart compile exe // ❌
# Attempt cross-compilation to Windows or macOS
--target-os=windows # not supported // ❌
// Assume all Python idioms translate directly
// py2many deterministic mode handles simple code only // ❌
# Use dart compile exe with packages that have build hooks
# Use dart build instead // ❌
Common Pitfalls
| Pitfall | Why It Happens | Fix |
|---|---|---|
| Binary fails on target | Cross-compiled for wrong architecture | Verify --target-arch matches target |
Runtime error on dart:mirrors | Executable does not support mirrors | Remove reflection; use code generation |
| Process output truncated | Pipe buffer full, not read | Use Process.start and read streams |
| Native library not found | Dynamic library path incorrect | Bundle library or use absolute path |
| Build hook package fails | dart compile exe does not run hooks | Use dart build |
Real-World Examples
1. Compile Host Binary
dart compile exe bin/main.dart -o build/myutil
2. Cross-Compile Linux ARM64
dart compile exe --target-os=linux --target-arch=arm64 bin/main.dart
3. Run Child Process
final result = await Process.run('ls', ['-l']);
4. Path Join with node_shims
import 'package:node_shims/path.dart' as path;
path.join([dir, 'file.txt']);
5. Native C Function
final lib = DynamicLibrary.open('libnative.so');
final fn = lib.lookupFunction<Int32 Function(Int32), int Function(int)>('add');
6. Read File Lines
final lines = File('data.txt').readAsLinesSync();
7. List Directory
Directory.current.listSync().whereType<File>();
8. Exit Code
exit(result.exitCode);
9. Py2many Deterministic
py2many --dart=1 utility.py
10. Verify No SDK Dependency
ldd build/myutil # only system libraries
Visual
The Porting Pipeline
┌──────────────────────────────────────────────────────────────┐
│ SOURCE UTILITY (Node.js or Python) │
│ │ │
│ ▼ │
│ ┌─────────────────┐ │
│ │ Identify │ fs, path, child_process, os, etc. │
│ │ dependencies │ │
│ └────────┬────────┘ │
│ ▼ │
│ ┌─────────────────┐ │
│ │ Map to Dart │ dart:io, package:path, Process, │
│ │ equivalents │ node_shims, dart:ffi │
│ └────────┬────────┘ │
│ ▼ │
│ ┌─────────────────┐ │
│ │ Handle native │ C library? → dart:ffi + Native Assets │
│ │ dependencies │ Pure Dart? → no interop needed │
│ └────────┬────────┘ │
│ ▼ │
│ ┌─────────────────┐ │
│ │ Compile │ dart compile exe (or dart build) │
│ └────────┬────────┘ │
│ ▼ │
│ ┌─────────────────┐ │
│ │ Standalone │ No Dart SDK required on target │
│ │ binary │ │
│ └─────────────────┘ │
│ │
│ The port is a library replacement, not a translation. │
│ │
└──────────────────────────────────────────────────────────────┘
Cross-Compilation Matrix
┌──────────────────────────────────────────────────────────────┐
│ HOST OS TARGET OS SUPPORTED │
│ │
│ Linux Linux Yes │
│ macOS Linux Yes │
│ Windows Linux Yes │
│ │
│ Linux Windows No │
│ macOS macOS No (compile on macOS) │
│ Windows Windows No (compile on Windows) │
│ │
│ Only Linux targets are supported for cross-compilation. │
│ Other platforms must compile on the target OS. │
│ │
└──────────────────────────────────────────────────────────────┘
Native Interop Decision
┌──────────────────────────────────────────────────────────────┐
│ DOES THE UTILITY USE A NATIVE LIBRARY? │
│ │
│ ┌─────────────────┐ │
│ │ YES │────► dart:ffi + DynamicLibrary.open │
│ │ │ + ffigen for bindings │
│ │ │ + Native Assets for bundling │
│ └─────────────────┘ │
│ │
│ ┌─────────────────┐ │
│ │ NO │────► Pure Dart implementation │
│ │ │ dart compile exe │
│ └─────────────────┘ │
│ │
│ Native Assets requires dart build, not dart compile exe. │
│ │
└──────────────────────────────────────────────────────────────┘
Standalone Binary Distribution
┌──────────────────────────────────────────────────────────────┐
│ BUILD MACHINE TARGET MACHINE │
│ │
│ ┌─────────────────┐ ┌─────────────────┐ │
│ │ Dart SDK │ │ No Dart SDK │ │
│ │ dart compile exe│─────────────►│ ./myutil │ │
│ │ │ binary │ │ │
│ └─────────────────┘ └─────────────────┘ │
│ │
│ ┌─────────────────┐ ┌─────────────────┐ │
│ │ myutil │ │ System C library│ │
│ │ (self-contained)│ │ (libc) │ │
│ └─────────────────┘ └─────────────────┘ │
│ │
│ The binary includes the Dart runtime and app code. │
│ The target machine needs only the OS's C library. │
│ │
└──────────────────────────────────────────────────────────────┘
Summary
| Item | Value |
|---|---|
| Compilation command | dart compile exe main.dart -o output |
| Output | Self-contained native binary |
| Runtime dependency | None (Dart runtime bundled) |
| Cross-compilation | Linux targets only, from any 64-bit host |
| Supported architectures | arm, arm64, riscv64, x64 |
| Node.js path shim | package:node_shims/path.dart |
| Process spawning | Process.run, Process.start |
| Native C interop | dart:ffi + ffigen |
| Build hooks | Use dart build, not dart compile exe |
| Unsupported libraries | dart:mirrors, dart:developer |
Key takeaways:
dart compile exeproduces a self-contained binary that runs without the Dart SDK. The Dart runtime and the application’s AOT-compiled machine code are bundled into a single file. The target machine needs only the operating system’s C library .- Cross-compilation is limited to Linux targets. A developer on macOS or Windows can compile a Linux binary for
arm,arm64,riscv64, orx64. Windows and macOS binaries must be compiled on their respective platforms . - Node.js porting is mostly a library replacement. The
node_shimspackage provides path and JavaScript utility shims.dart:ioprovides file, directory, and process operations.Process.runreplaceschild_process. - Python porting requires distinguishing simple from complex code. The
py2manytranspiler handles simple, idiomatic Python deterministically. Complex idioms like decorators, metaclasses, and generator expressions require LLM-assisted translation or manual rewriting . - Native interop uses
dart:ffiand Native Assets. A utility that wraps a C library can call it throughdart:ffi, with bindings generated byffigen. The Native Assets build hooks bundle the compiled library with the Dart executable, but this requiresdart buildrather thandart compile exe.
Remember: Porting a utility to a standalone Dart binary is not a translation problem; it is a dependency problem. The source language’s standard library and runtime conventions must be replaced with Dart equivalents, and the places where no equivalent exists must be filled with native interop or a rewrite. The dart compile exe command is the final step, and it produces an artifact that is easier to distribute than the original utility because it has no runtime dependency. The porting work is in the gap between what the source language provides and what Dart provides, and that gap is narrow for Node.js utilities that rely on file and process operations, and wider for Python utilities that rely on the Python-specific idioms.
Stop using slow, ad-bloated tool sites! 🤮
🔎 Search “KandZ Tools” on Google to use many professional utilities for free.
KandZ.me is the ultimate minimalist hub for:
✅ Finance (Mortgage, Interest, Inflation)
✅ Tech (Base64, JSON, Dev Suite, IP)
✅ Health (BMI, BMR, TDEE)
✅ Productivity (Timer, Workspace, QR)
⚡️ Fast & Private
🔒 No data leaves your device
💎 100% Free
🔗 Use it now: https://tools.kandz.me
🔖 Bookmark it—you’ll need it later!