| |

Dart 98 🎯 Porting Node.js or Python Utilities to Cross-Platform Standalone Dart Binaries

⏱️ Estimated read: 13 min | |

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

TargetCommandHost Requirement
Host nativedart compile exe main.dartAny 64-bit OS
Linux x64--target-os=linux --target-arch=x64Any 64-bit OS
Linux ARM64--target-os=linux --target-arch=arm64Any 64-bit OS
Windows/macOSCompile on target platformTarget OS

Node.js to Dart Replacements

Node.jsDart
fsdart:io File, Directory
pathpackage:node_shims/path.dart or package:path
child_processProcess.run, Process.start
httppackage:http
process.argvmain(List<String> args)

Python to Dart Replacements

PythonDart
open()File.readAsStringSync()
os.pathpackage:path
subprocessProcess.run
jsondart:convert
sys.argvmain(List<String> args)

Compilation Limitations

LimitationDetail
dart:mirrorsNot supported
dart:developerNot supported
Build hooksUse dart build instead
Cross-compilation targetLinux 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

PitfallWhy It HappensFix
Binary fails on targetCross-compiled for wrong architectureVerify --target-arch matches target
Runtime error on dart:mirrorsExecutable does not support mirrorsRemove reflection; use code generation
Process output truncatedPipe buffer full, not readUse Process.start and read streams
Native library not foundDynamic library path incorrectBundle library or use absolute path
Build hook package failsdart compile exe does not run hooksUse 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

ItemValue
Compilation commanddart compile exe main.dart -o output
OutputSelf-contained native binary
Runtime dependencyNone (Dart runtime bundled)
Cross-compilationLinux targets only, from any 64-bit host
Supported architecturesarm, arm64, riscv64, x64
Node.js path shimpackage:node_shims/path.dart
Process spawningProcess.run, Process.start
Native C interopdart:ffi + ffigen
Build hooksUse dart build, not dart compile exe
Unsupported librariesdart:mirrors, dart:developer

Key takeaways:

  • dart compile exe produces 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, or x64. Windows and macOS binaries must be compiled on their respective platforms .
  • Node.js porting is mostly a library replacement. The node_shims package provides path and JavaScript utility shims. dart:io provides file, directory, and process operations. Process.run replaces child_process .
  • Python porting requires distinguishing simple from complex code. The py2many transpiler handles simple, idiomatic Python deterministically. Complex idioms like decorators, metaclasses, and generator expressions require LLM-assisted translation or manual rewriting .
  • Native interop uses dart:ffi and Native Assets. A utility that wraps a C library can call it through dart:ffi, with bindings generated by ffigen. The Native Assets build hooks bundle the compiled library with the Dart executable, but this requires dart build rather than dart 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.

Was this helpful?


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!