[dart2wasm] Document standalone platform and include it in SDK

Given that the standalone target for dart2wasm is feature-complete now,
it makes sense to include it in released SDKs.

This adds the platform and outline files to built SDKs and exposes the
`--standalone` flag in `dart compile wasm`.
It also documents the standalone target in `pkg/dart2wasm/doc`, which
should be helpful as a starting point to use these compiled modules.

Change-Id: I5bd86e9670f03f2955e31789095dd5c462bf149e
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/506920
Reviewed-by: Slava Egorov <vegorov@google.com>
Reviewed-by: Martin Kustermann <kustermann@google.com>
Commit-Queue: Martin Kustermann <kustermann@google.com>
This commit is contained in:
Simon Binder
2026-06-02 00:56:35 -07:00
committed by dart-scoped@luci-project-accounts.iam.gserviceaccount.com
parent 7749454698
commit 567bf3337b
5 changed files with 101 additions and 5 deletions
+4
View File
@@ -144,6 +144,10 @@ class Sdk {
'dart2wasm_platform.dill',
);
String get wasmStandalonePlatformDill => _dillPathFor(
'dart2wasm_standalone_platform.dill',
);
String _dillPathFor(String dillName) => path.absolute(
runFromBuildRoot
? sdkPath
+34
View File
@@ -0,0 +1,34 @@
# Standalone mode
By default, dart2wasm generates WebAssembly modules meant to run on platforms
with JavaScript support (mainly, browsers).
Both the SDK and application code can rely on `dart:js_interop` libraries to
access external JavaScript APIs.
For WebAssembly targets without a JavaScript engine (like wasmtime or similar
runtimes), dart2wasm supports a standalone target too. This target is enabled
with the `--standalone` compiler flag, and makes `dart:js_interop` unavailable.
Even in standalone mode, the SDK needs host imports for functionality like
timers, stack traces, regular expressions, `dart:math` or number formatting.
These imports are referenced in [this file](https://github.com/dart-lang/sdk/blob/main/sdk/lib/_internal/wasm/standalone/embedder.dart).
Note that these imports are not stable, and might change in future Dart
versions.
Additional definitions can be imported via [imports and exports](./imports_and_exports.md).
## Running standalone modules
There are two plausible ways to run standalone modules:
1. By using APIs from a WebAssembly engine to define definitions for imports in
native code. There is no example for this in the SDK, but test definitions
for browser tests (`dart2WasmStandaloneHtml` in `pkg/test_runner/lib/src/browser.dart`)
might help as a starting point.
2. By re-implementing a subset of imports in WebAssembly, and then using tools
like `wasm-merge` from Binaryen to link two modules into a module with fewer
or no dependencies. Implementing Dart imports by delegating to WASI
definitions would allow running these modules without custom native imports,
for example.
Examples for both options are discussed in the [tracking issue](https://github.com/dart-lang/sdk/issues/53884).
+31 -5
View File
@@ -894,6 +894,14 @@ class CompileWasmCommand extends CompileSubcommandCommand {
negatable: false,
help: enableAssertsOption.help,
)
..addFlag(
'standalone',
help:
'Compile to a WebAssembly module without JavaScript interop. '
'Dart-specific host imports are necessary to load these modules.',
negatable: false,
hide: !verbose,
)
..addOption(
'shared-memory',
help:
@@ -1046,10 +1054,14 @@ class CompileWasmCommand extends CompileSubcommandCommand {
final generateSourceMap = args.flag('source-maps');
final enabledExperiments = args.enabledExperiments;
final standalone = args.flag('standalone');
final platform = standalone
? sdk.wasmStandalonePlatformDill
: sdk.wasmPlatformDill;
final dart2wasmCommand = [
sdk.dartAotRuntime,
sdk.dart2wasmSnapshot,
'--platform=${sdk.wasmPlatformDill}',
'--platform=$platform',
if (verbose) '--verbose',
if (packages != null) '--packages=$packages',
if (args.flag('print-wasm')) '--print-wasm',
@@ -1060,6 +1072,7 @@ class CompileWasmCommand extends CompileSubcommandCommand {
if (args.flag('minify')) '--minify',
if (!args.flag('strip-wasm')) '--no-strip-wasm',
if (args.flag('enable-deferred-loading')) '--enable-deferred-loading',
if (standalone) '--standalone',
if (args.option(recordedUsesOption.flag) != null)
'--recorded-uses=${args.option(recordedUsesOption.flag)}',
for (final define in defines) '-D$define',
@@ -1091,10 +1104,23 @@ class CompileWasmCommand extends CompileSubcommandCommand {
if (isDryRun) return 0;
final mjsFile = '$outputFileBasename.mjs';
log.stdout(
"Generated wasm module '$outputFile', and JS init file '$mjsFile'.",
);
if (standalone) {
log.stdout(
"Generated wasm module '$outputFile'. See "
'https://github.com/dart-lang/sdk/blob/main/pkg/dart2wasm/docs/standalone.md '
'for usage instructions.',
);
log.stdout(
'The standalone option is experimental, and used imports may change'
'across Dart SDK releases.',
);
} else {
final mjsFile = '$outputFileBasename.mjs';
log.stdout(
"Generated wasm module '$outputFile', and JS init file '$mjsFile'.",
);
}
return 0;
}
}
@@ -883,6 +883,35 @@ void main() {
);
}, skip: isRunningOnIA32);
test('Compile wasm standalone', () async {
final p = project(
mainSrc: '''
void main() {
print('hello world');
}
''',
);
final inFile = path.canonicalize(path.join(p.dirPath, p.relativeFilePath));
final outFile = path.canonicalize(path.join(p.dirPath, 'standalone.wasm'));
final result = await p.run([
'compile',
'wasm',
'--standalone',
'-o',
outFile,
inFile,
]);
expect(result.stdout, isNot(contains('.mjs')));
expect(result.stdout, contains('standalone option is experimental'));
expect(
File(outFile).existsSync(),
true,
reason: 'Expected output file to exist: $outFile',
);
}, skip: isRunningOnIA32);
test('Compile JS with sound null safety', () async {
final p = project(mainSrc: '''void main() {}''');
final inFile = path.canonicalize(path.join(p.dirPath, p.relativeFilePath));
+3
View File
@@ -670,12 +670,15 @@ copy("copy_dart2wasm_platform") {
"../:dart2wasm_platform",
"../utils/dart2wasm:compile_dart2wasm_js_compatibility_platform",
"../utils/dart2wasm:compile_dart2wasm_platform",
"../utils/dart2wasm:compile_dart2wasm_standalone_platform",
]
sources = [
"$root_out_dir/dart2wasm_js_compatibility_outline.dill",
"$root_out_dir/dart2wasm_js_compatibility_platform.dill",
"$root_out_dir/dart2wasm_outline.dill",
"$root_out_dir/dart2wasm_platform.dill",
"$root_out_dir/dart2wasm_standalone_outline.dill",
"$root_out_dir/dart2wasm_standalone_platform.dill",
]
outputs =
[ "$root_out_dir/$dart_sdk_output/lib/_internal/{{source_file_part}}" ]