Version 3.14.0-110.0.dev Merge 0b79bdfb86dbbb3e18c232b45fa462a7293e7db4 into dev
diff --git a/build/toolchain/win/setup_toolchain.py b/build/toolchain/win/setup_toolchain.py index 434ca9f..dd11516 100644 --- a/build/toolchain/win/setup_toolchain.py +++ b/build/toolchain/win/setup_toolchain.py
@@ -22,7 +22,7 @@ import gn_helpers SCRIPT_DIR = os.path.dirname(__file__) -SDK_VERSION = '10.0.26100.0' +SDK_VERSION = '10.0.28000.0' def _ExtractImportantEnvironment(output_of_set):
diff --git a/build/vs_toolchain.py b/build/vs_toolchain.py index ee033b3..1830c72 100644 --- a/build/vs_toolchain.py +++ b/build/vs_toolchain.py
@@ -3,6 +3,14 @@ # Use of this source code is governed by a BSD-style license that can be # found in the LICENSE file. +# For Dart/Flutter developers: +# This file keeps the MSVC toolchain up-to-date for Google developers. +# It is copied from Chromium: +# https://cs.chromium.org/chromium/src/build/vs_toolchain.py +# with modifications that update paths, and remove dependencies on gyp. +# To update to a new MSVC toolchain, copy the updated script from the Chromium +# tree, and edit to make it work in the Dart tree by updating paths in the original script. + import collections import glob @@ -17,7 +25,7 @@ from gn_helpers import ToGNString -# VS 2022 17.13.4 with 10.0.26100.3323 SDK with ARM64 libraries and UWP support. +# VS 2022 17.13.4 with 10.0.26100.4654 SDK with ARM64 libraries and UWP support. # See go/win-toolchain-reference for instructions about how to update the # toolchain. # @@ -59,7 +67,7 @@ # * docs/windows_build_instructions.md # Make sure any version numbers in the documentation match the code. # -TOOLCHAIN_HASH = '076960eda6' +TOOLCHAIN_HASH = 'e4305f407e' SDK_VERSION = '10.0.26100.0' # Visual Studio versions are listed in descending order of priority. @@ -304,7 +312,7 @@ list_of_str_versions.sort(key=to_number_sequence, reverse=True) -def _CopyUCRTRuntime(target_dir, source_dir, target_cpu, suffix): +def _CopyUCRTRuntime(target_dir, source_dir, target_cpu, debug): """Copy both the msvcp and vccorlib runtime DLLs, only if the target doesn't exist, but the target directory does exist.""" if target_cpu == 'arm64': @@ -312,7 +320,7 @@ # {x.y.z}/[debug_nonredist/]arm64/Microsoft.VC14x.CRT/. # Select VC toolset directory based on Visual Studio version vc_redist_root = FindVCRedistRoot() - if suffix.startswith('.'): + if not debug: vc_toolset_dir = 'Microsoft.{}.CRT' \ .format(MSVC_TOOLSET_VERSION[GetVisualStudioVersion()]) source_dir = os.path.join(vc_redist_root, @@ -322,17 +330,25 @@ .format(MSVC_TOOLSET_VERSION[GetVisualStudioVersion()]) source_dir = os.path.join(vc_redist_root, 'debug_nonredist', 'arm64', vc_toolset_dir) - file_parts = ('msvcp140', 'vccorlib140', 'vcruntime140') + + # The filepaths may have an additional 'd' depending on whether we are in + # debug mode. + def d(s): + return s + 'd' if debug else s + + file_parts = (d('msvcp140'), d('msvcp140') + '_atomic_wait', d('vccorlib140'), + d('vcruntime140')) if target_cpu == 'x64' and GetVisualStudioVersion() != '2017': - file_parts = file_parts + ('vcruntime140_1', ) + file_parts = file_parts + (d('vcruntime140_1'), ) for file_part in file_parts: - dll = file_part + suffix + dll = file_part + '.dll' target = os.path.join(target_dir, dll) source = os.path.join(source_dir, dll) _CopyRuntimeImpl(target, source) + # We must copy ucrtbased.dll for all CPU types. The rest of the Universal CRT # is installed as part of the OS in Windows 10 and beyond. - if not suffix.startswith('.'): + if debug: win_sdk_dir = os.path.normpath( os.environ.get( 'WINDOWSSDKDIR', @@ -352,8 +368,10 @@ if not os.path.isdir(source_dir): continue break - _CopyRuntimeImpl(os.path.join(target_dir, 'ucrtbase' + suffix), - os.path.join(source_dir, 'ucrtbase' + suffix)) + _CopyRuntimeImpl(os.path.join(target_dir, + d('ucrtbase') + '.dll'), + os.path.join(source_dir, + d('ucrtbase') + '.dll')) def FindVCComponentRoot(component): @@ -389,9 +407,8 @@ def _CopyRuntime(target_dir, source_dir, target_cpu, debug): """Copy the VS runtime DLLs, only if the target doesn't exist, but the target directory does exist. Handles VS 2015, 2017 and 2019.""" - suffix = 'd.dll' if debug else '.dll' # VS 2015, 2017 and 2019 use the same CRT DLLs. - _CopyUCRTRuntime(target_dir, source_dir, target_cpu, suffix) + _CopyUCRTRuntime(target_dir, source_dir, target_cpu, debug) def CopyDlls(target_dir, configuration, target_cpu): @@ -529,7 +546,11 @@ # ciopfs not found in PATH; try the one downloaded from the DEPS hook. ciopfs = os.path.join(script_dir, 'ciopfs') if not os.path.isdir(toolchain_dir): - os.mkdir(toolchain_dir) + try: + os.mkdir(toolchain_dir) + except FileExistsError: + # ciopfsd died, but fuse is still mounted. + subprocess.check_call(["fusermount", "-u", toolchain_dir]) if not os.path.isdir(toolchain_dir + '.ciopfs'): os.mkdir(toolchain_dir + '.ciopfs') # Without use_ino, clang's #pragma once and Wnonportable-include-path
diff --git a/pkg/dart2wasm/lib/compile.dart b/pkg/dart2wasm/lib/compile.dart index 8da4f24..6c9601b 100644 --- a/pkg/dart2wasm/lib/compile.dart +++ b/pkg/dart2wasm/lib/compile.dart
@@ -656,6 +656,7 @@ translator.internalizedStringsForJSRuntime, translator.options.enableDeferredLoading || translator.options.enableMultiModuleStressTestMode, + options.supportsES6Modules, ); final supportJs = _generateSupportJs(options.translatorOptions);
diff --git a/pkg/dart2wasm/lib/compiler_options.dart b/pkg/dart2wasm/lib/compiler_options.dart index 2ac9451..7bcaba6 100644 --- a/pkg/dart2wasm/lib/compiler_options.dart +++ b/pkg/dart2wasm/lib/compiler_options.dart
@@ -61,6 +61,7 @@ Set<int> moduleIdsToOptimize = const {}; bool stripWasm = true; Uri? recordedUsesFile; + bool supportsES6Modules = true; List<CompilerPhase> phases = const [ CompilerPhase.cfe, CompilerPhase.tfa,
diff --git a/pkg/dart2wasm/lib/dart2wasm.dart b/pkg/dart2wasm/lib/dart2wasm.dart index ec6fc03..4b93893 100644 --- a/pkg/dart2wasm/lib/dart2wasm.dart +++ b/pkg/dart2wasm/lib/dart2wasm.dart
@@ -218,6 +218,12 @@ (o, value) => o.moduleIdsToOptimize = value.toSet(), ), UriOption("recorded-uses", (o, value) => o.recordedUsesFile = value), + Flag( + "supports-es6-modules", + (o, value) => o.supportsES6Modules = value, + defaultsTo: _d.supportsES6Modules, + negatable: true, + ), ]; Map<fe.ExperimentalFlag, bool> processFeExperimentalFlags(
diff --git a/pkg/dart2wasm/lib/js/runtime_blob.dart b/pkg/dart2wasm/lib/js/runtime_blob.dart index 47e3d5c..4294524 100644 --- a/pkg/dart2wasm/lib/js/runtime_blob.dart +++ b/pkg/dart2wasm/lib/js/runtime_blob.dart
@@ -1,26 +1,54 @@ // Copyright (c) 2023, the Dart project authors. Please see the AUTHORS file // for details. All rights reserved. Use of this source code is governed by a // BSD-style license that can be found in the LICENSE file. +final nonEs6MjsTemplate = Template(r'''(function() { +const exportObject = {}; -final jsRuntimeBlobTemplate = Template(r''' // Compiles a dart2wasm-generated main module from `source` which can then // be instantiated via the `instantiate` method. // // `source` needs to be a `Response` object (or promise thereof) e.g. created // via the `fetch()` JS API. -export async function compileStreaming(source) { - const builtins = {<<BUILTINS_MAP_BODY>>}; - return new CompiledApp( - await WebAssembly.compileStreaming(source, builtins), builtins); -} +exportObject.compileStreaming = <<COMPILE_STREAMING>>; // Compiles a dart2wasm-generated wasm module from `bytes` which is then // instantiable via the `instantiate` method. -export async function compile(bytes) { +exportObject.compile = <<COMPILE>>; + +<<REST>> + +return exportObject; +})'''); + +final es6MjsTemplate = Template( + r'''// Compiles a dart2wasm-generated main module from `source` which can then +// be instantiated via the `instantiate` method. +// +// `source` needs to be a `Response` object (or promise thereof) e.g. created +// via the `fetch()` JS API. +export <<COMPILE_STREAMING>> + +// Compiles a dart2wasm-generated wasm module from `bytes` which is then +// instantiable via the `instantiate` method. +export <<COMPILE>> + +<<REST>>''', +); + +final compileStreamingTemplate = Template( + r'''async function compileStreaming(source) { + const builtins = {<<BUILTINS_MAP_BODY>>}; + return new CompiledApp( + await WebAssembly.compileStreaming(source, builtins), builtins); +}''', +); + +final compileTemplate = Template(r'''async function compile(bytes) { const builtins = {<<BUILTINS_MAP_BODY>>}; return new CompiledApp(await WebAssembly.compile(bytes, builtins), builtins); -} +}'''); +final jsRuntimeBlobTemplate = Template(r''' class CompiledApp { constructor(module, builtins) { this.module = module;
diff --git a/pkg/dart2wasm/lib/js/runtime_generator.dart b/pkg/dart2wasm/lib/js/runtime_generator.dart index 4ac9706..aa7173d 100644 --- a/pkg/dart2wasm/lib/js/runtime_generator.dart +++ b/pkg/dart2wasm/lib/js/runtime_generator.dart
@@ -123,6 +123,7 @@ Iterable<Procedure> translatedProcedures, List<String> constantStrings, bool supportsAdditionalModuleLoading, + bool supportsES6Modules, ) { final jsMethods = generateJsMethods(translatedProcedures); @@ -148,18 +149,34 @@ 'MAIN_MODULE_NAME': mainModuleName, }) : ''; + final result = StringBuffer(); - return jsRuntimeBlobTemplate.instantiate({ - ...jsStringBuiltinPolyfillImportVars, - ...moduleLoadingImportVars, - 'BUILTINS_MAP_BODY': builtins.join(', '), - 'JS_METHODS': jsMethods, - 'INTERNAL_IMPORTS_MODULE_NAME': - _interopMemberNamer.interopHelperModuleName, - 'IMPORTED_JS_STRINGS_IN_MJS': internalizedStrings, - 'JS_STRING_POLYFILL_METHODS': jsPolyFillMethods, - 'DEFERRED_LIBRARY_HELPER_METHODS': moduleLoadingHelperMethods, - }); + final moduleTemplate = supportsES6Modules + ? es6MjsTemplate + : nonEs6MjsTemplate; + result.write( + moduleTemplate.instantiate({ + 'COMPILE_STREAMING': compileStreamingTemplate.instantiate({ + 'BUILTINS_MAP_BODY': builtins.join(', '), + }), + 'COMPILE': compileTemplate.instantiate({ + 'BUILTINS_MAP_BODY': builtins.join(', '), + }), + 'REST': jsRuntimeBlobTemplate.instantiate({ + ...jsStringBuiltinPolyfillImportVars, + ...moduleLoadingImportVars, + 'BUILTINS_MAP_BODY': builtins.join(', '), + 'JS_METHODS': jsMethods, + 'INTERNAL_IMPORTS_MODULE_NAME': + _interopMemberNamer.interopHelperModuleName, + 'IMPORTED_JS_STRINGS_IN_MJS': internalizedStrings, + 'JS_STRING_POLYFILL_METHODS': jsPolyFillMethods, + 'DEFERRED_LIBRARY_HELPER_METHODS': moduleLoadingHelperMethods, + }), + }), + ); + + return result.toString(); } }
diff --git a/pkg/dart2wasm/test/mjs_format/main.dart b/pkg/dart2wasm/test/mjs_format/main.dart new file mode 100644 index 0000000..2bfa7d3 --- /dev/null +++ b/pkg/dart2wasm/test/mjs_format/main.dart
@@ -0,0 +1,7 @@ +// Copyright (c) 2026, the Dart project authors. Please see the AUTHORS file +// for details. All rights reserved. Use of this source code is governed by a +// BSD-style license that can be found in the LICENSE file. + +void main() { + print('Hello, mjs!'); +}
diff --git a/pkg/dart2wasm/test/mjs_format/mjs_es6.mjs.golden b/pkg/dart2wasm/test/mjs_format/mjs_es6.mjs.golden new file mode 100644 index 0000000..72a6a5f --- /dev/null +++ b/pkg/dart2wasm/test/mjs_format/mjs_es6.mjs.golden
@@ -0,0 +1,186 @@ +// Compiles a dart2wasm-generated main module from `source` which can then +// be instantiated via the `instantiate` method. +// +// `source` needs to be a `Response` object (or promise thereof) e.g. created +// via the `fetch()` JS API. +export async function compileStreaming(source) { + const builtins = {builtins: ['js-string'], importedStringConstants: ''}; + return new CompiledApp( + await WebAssembly.compileStreaming(source, builtins), builtins); +} + +// Compiles a dart2wasm-generated wasm module from `bytes` which is then +// instantiable via the `instantiate` method. +export async function compile(bytes) { + const builtins = {builtins: ['js-string'], importedStringConstants: ''}; + return new CompiledApp(await WebAssembly.compile(bytes, builtins), builtins); +} + +class CompiledApp { + constructor(module, builtins) { + this.module = module; + this.builtins = builtins; + } + + // The second argument is an options object containing: + // `loadDeferredModules` is a JS function that takes an array of module names + // matching wasm files produced by the dart2wasm compiler. It also takes a + // callback that should be invoked for each loaded module with 2 arguments: + // (1) the module name, (2) the loaded module in a format supported by + // `WebAssembly.compile` or `WebAssembly.compileStreaming`. The callback + // returns a Promise that resolves when the module is instantiated. + // loadDeferredModules should return a Promise that resolves when all the + // modules have been loaded and the callback promises have resolved. + // `loadDeferredId` is a JS function that takes load ID produced by the + // compiler when the `use-load-ids` option is passed. Each load ID maps to + // one or more wasm files as specified in the emitted JSON file. It also + // takes a callback that should be invoked for each loaded module with 2 + // arguments: (1) the module name, (2) the loaded module in a format + // supported by `WebAssembly.compile` or `WebAssembly.compileStreaming`. + // The callback returns a Promise that resolves when the module is + // instantiated. + // loadDeferredId should return a Promise that resolves when all the + // modules have been loaded and the callback promises have resolved. + async instantiate(additionalImports, {loadDeferredModules, loadDeferredId} = {}) { + let dartInstance; + + // Prints to the console + function printToConsole(value) { + if (typeof dartPrint == "function") { + dartPrint(value); + return; + } + if (typeof console == "object" && typeof console.log != "undefined") { + console.log(value); + return; + } + if (typeof print == "function") { + print(value); + return; + } + + throw "Unable to print message: " + value; + } + + // A special symbol attached to functions that wrap Dart functions. + const jsWrappedDartFunctionSymbol = Symbol("JSWrappedDartFunction"); + + function finalizeWrapper(dartFunction, wrapped) { + wrapped.dartFunction = dartFunction; + wrapped[jsWrappedDartFunctionSymbol] = true; + return wrapped; + } + + // Imports + const dart2wasm = { + B: s => printToConsole(s), + C: Function.prototype.call.bind(Number.prototype.toString), + D: Function.prototype.call.bind(BigInt.prototype.toString), + E: (exn) => { + let stackString = exn.toString(); + let frames = stackString.split('\n'); + let drop = 4; + if (frames[0].startsWith('Error')) { + drop += 1; + } + return frames.slice(drop).join('\n'); + }, + F: () => new Error().stack, + G: s => JSON.stringify(s), + H: Function.prototype.call.bind(Number.prototype.toString), + I: Function.prototype.call.bind(String.prototype.indexOf), + J: (exn) => { + if (exn instanceof Error) { + return exn.stack; + } else { + return null; + } + }, + K: o => String(o), + L: o => o === undefined, + + }; + + const baseImports = { + dart2wasm: dart2wasm, + Math: Math, + Date: Date, + Object: Object, + Array: Array, + Reflect: Reflect, + WebAssembly: { + JSTag: WebAssembly.JSTag, + }, + "": new Proxy({}, { get(_, prop) { return prop; } }), + + }; + + const jsStringPolyfill = { + "charCodeAt": (s, i) => s.charCodeAt(i), + "compare": (s1, s2) => { + if (s1 < s2) return -1; + if (s1 > s2) return 1; + return 0; + }, + "concat": (s1, s2) => s1 + s2, + "equals": (s1, s2) => s1 === s2, + "fromCharCode": (i) => String.fromCharCode(i), + "length": (s) => s.length, + "substring": (s, a, b) => s.substring(a, b), + "fromCharCodeArray": (a, start, end) => { + if (end <= start) return ''; + + const read = dartInstance.exports.$wasmI16ArrayGet; + let result = ''; + let index = start; + const chunkLength = Math.min(end - index, 500); + let array = new Array(chunkLength); + while (index < end) { + const newChunkLength = Math.min(end - index, 500); + for (let i = 0; i < newChunkLength; i++) { + array[i] = read(a, index++); + } + if (newChunkLength < chunkLength) { + array = array.slice(0, newChunkLength); + } + result += String.fromCharCode(...array); + } + return result; + }, + "intoCharCodeArray": (s, a, start) => { + if (s === '') return 0; + + const write = dartInstance.exports.$wasmI16ArraySet; + for (var i = 0; i < s.length; ++i) { + write(a, start++, s.charCodeAt(i)); + } + return s.length; + }, + "test": (s) => typeof s == "string", + }; + + + + + dartInstance = await WebAssembly.instantiate(this.module, { + ...baseImports, + ...additionalImports, + + "wasm:js-string": jsStringPolyfill, + }); + + return new InstantiatedApp(this, dartInstance); + } +} + +class InstantiatedApp { + constructor(compiledApp, instantiatedModule) { + this.compiledApp = compiledApp; + this.instantiatedModule = instantiatedModule; + } + + // Call the main function with the given arguments. + invokeMain(...args) { + this.instantiatedModule.exports.$invokeMain(args); + } +}
diff --git a/pkg/dart2wasm/test/mjs_format/mjs_no_es6.mjs.golden b/pkg/dart2wasm/test/mjs_format/mjs_no_es6.mjs.golden new file mode 100644 index 0000000..410d03b --- /dev/null +++ b/pkg/dart2wasm/test/mjs_format/mjs_no_es6.mjs.golden
@@ -0,0 +1,193 @@ +(function() { +const exportObject = {}; + +// Compiles a dart2wasm-generated main module from `source` which can then +// be instantiated via the `instantiate` method. +// +// `source` needs to be a `Response` object (or promise thereof) e.g. created +// via the `fetch()` JS API. +exportObject.compileStreaming = async function compileStreaming(source) { + const builtins = {builtins: ['js-string'], importedStringConstants: ''}; + return new CompiledApp( + await WebAssembly.compileStreaming(source, builtins), builtins); +}; + +// Compiles a dart2wasm-generated wasm module from `bytes` which is then +// instantiable via the `instantiate` method. +exportObject.compile = async function compile(bytes) { + const builtins = {builtins: ['js-string'], importedStringConstants: ''}; + return new CompiledApp(await WebAssembly.compile(bytes, builtins), builtins); +}; + +class CompiledApp { + constructor(module, builtins) { + this.module = module; + this.builtins = builtins; + } + + // The second argument is an options object containing: + // `loadDeferredModules` is a JS function that takes an array of module names + // matching wasm files produced by the dart2wasm compiler. It also takes a + // callback that should be invoked for each loaded module with 2 arguments: + // (1) the module name, (2) the loaded module in a format supported by + // `WebAssembly.compile` or `WebAssembly.compileStreaming`. The callback + // returns a Promise that resolves when the module is instantiated. + // loadDeferredModules should return a Promise that resolves when all the + // modules have been loaded and the callback promises have resolved. + // `loadDeferredId` is a JS function that takes load ID produced by the + // compiler when the `use-load-ids` option is passed. Each load ID maps to + // one or more wasm files as specified in the emitted JSON file. It also + // takes a callback that should be invoked for each loaded module with 2 + // arguments: (1) the module name, (2) the loaded module in a format + // supported by `WebAssembly.compile` or `WebAssembly.compileStreaming`. + // The callback returns a Promise that resolves when the module is + // instantiated. + // loadDeferredId should return a Promise that resolves when all the + // modules have been loaded and the callback promises have resolved. + async instantiate(additionalImports, {loadDeferredModules, loadDeferredId} = {}) { + let dartInstance; + + // Prints to the console + function printToConsole(value) { + if (typeof dartPrint == "function") { + dartPrint(value); + return; + } + if (typeof console == "object" && typeof console.log != "undefined") { + console.log(value); + return; + } + if (typeof print == "function") { + print(value); + return; + } + + throw "Unable to print message: " + value; + } + + // A special symbol attached to functions that wrap Dart functions. + const jsWrappedDartFunctionSymbol = Symbol("JSWrappedDartFunction"); + + function finalizeWrapper(dartFunction, wrapped) { + wrapped.dartFunction = dartFunction; + wrapped[jsWrappedDartFunctionSymbol] = true; + return wrapped; + } + + // Imports + const dart2wasm = { + B: s => printToConsole(s), + C: Function.prototype.call.bind(Number.prototype.toString), + D: Function.prototype.call.bind(BigInt.prototype.toString), + E: (exn) => { + let stackString = exn.toString(); + let frames = stackString.split('\n'); + let drop = 4; + if (frames[0].startsWith('Error')) { + drop += 1; + } + return frames.slice(drop).join('\n'); + }, + F: () => new Error().stack, + G: s => JSON.stringify(s), + H: Function.prototype.call.bind(Number.prototype.toString), + I: Function.prototype.call.bind(String.prototype.indexOf), + J: (exn) => { + if (exn instanceof Error) { + return exn.stack; + } else { + return null; + } + }, + K: o => String(o), + L: o => o === undefined, + + }; + + const baseImports = { + dart2wasm: dart2wasm, + Math: Math, + Date: Date, + Object: Object, + Array: Array, + Reflect: Reflect, + WebAssembly: { + JSTag: WebAssembly.JSTag, + }, + "": new Proxy({}, { get(_, prop) { return prop; } }), + + }; + + const jsStringPolyfill = { + "charCodeAt": (s, i) => s.charCodeAt(i), + "compare": (s1, s2) => { + if (s1 < s2) return -1; + if (s1 > s2) return 1; + return 0; + }, + "concat": (s1, s2) => s1 + s2, + "equals": (s1, s2) => s1 === s2, + "fromCharCode": (i) => String.fromCharCode(i), + "length": (s) => s.length, + "substring": (s, a, b) => s.substring(a, b), + "fromCharCodeArray": (a, start, end) => { + if (end <= start) return ''; + + const read = dartInstance.exports.$wasmI16ArrayGet; + let result = ''; + let index = start; + const chunkLength = Math.min(end - index, 500); + let array = new Array(chunkLength); + while (index < end) { + const newChunkLength = Math.min(end - index, 500); + for (let i = 0; i < newChunkLength; i++) { + array[i] = read(a, index++); + } + if (newChunkLength < chunkLength) { + array = array.slice(0, newChunkLength); + } + result += String.fromCharCode(...array); + } + return result; + }, + "intoCharCodeArray": (s, a, start) => { + if (s === '') return 0; + + const write = dartInstance.exports.$wasmI16ArraySet; + for (var i = 0; i < s.length; ++i) { + write(a, start++, s.charCodeAt(i)); + } + return s.length; + }, + "test": (s) => typeof s == "string", + }; + + + + + dartInstance = await WebAssembly.instantiate(this.module, { + ...baseImports, + ...additionalImports, + + "wasm:js-string": jsStringPolyfill, + }); + + return new InstantiatedApp(this, dartInstance); + } +} + +class InstantiatedApp { + constructor(compiledApp, instantiatedModule) { + this.compiledApp = compiledApp; + this.instantiatedModule = instantiatedModule; + } + + // Call the main function with the given arguments. + invokeMain(...args) { + this.instantiatedModule.exports.$invokeMain(args); + } +} + + +return exportObject; +}) \ No newline at end of file
diff --git a/pkg/dart2wasm/test/mjs_test.dart b/pkg/dart2wasm/test/mjs_test.dart new file mode 100644 index 0000000..aac3000 --- /dev/null +++ b/pkg/dart2wasm/test/mjs_test.dart
@@ -0,0 +1,120 @@ +// Copyright (c) 2026, the Dart project authors. Please see the AUTHORS file +// for details. All rights reserved. Use of this source code is governed by a +// BSD-style license that can be found in the LICENSE file. + +import 'dart:io'; + +import 'package:args/args.dart'; +import 'package:expect/expect.dart'; +import 'package:path/path.dart' as path; + +import 'util.dart'; + +final String testDir = path.join( + path.dirname(Platform.script.toFilePath()), + 'mjs_format', +); +final String mainDart = path.join(testDir, 'main.dart'); +final String es6GoldenPath = path.join(testDir, 'mjs_es6.mjs.golden'); +final String noEs6GoldenPath = path.join(testDir, 'mjs_no_es6.mjs.golden'); + +Future<void> main(List<String> args) async { + if (!Platform.isLinux && !Platform.isMacOS) return; + + final parser = ArgParser() + ..addFlag('update-golden', abbr: 'g', negatable: false); + final argsResult = parser.parse(args); + final bool updateGolden = argsResult.flag('update-golden'); + + await withTempDir((String tempDir) async { + final es6WasmPath = path.join(tempDir, 'out_es6.wasm'); + final noEs6WasmPath = path.join(tempDir, 'out_no_es6.wasm'); + final es6MjsPath = path.join(tempDir, 'out_es6.mjs'); + final noEs6MjsPath = path.join(tempDir, 'out_no_es6.mjs'); + + // Compile with default (ES6 modules enabled) + await run([ + dartAotExecutable, + dart2wasmSnapshot, + '--platform=$platformDill', + '-O0', + mainDart, + es6WasmPath, + ]); + + // Compile with --no-supports-es6-modules + await run([ + dartAotExecutable, + dart2wasmSnapshot, + '--platform=$platformDill', + '--no-supports-es6-modules', + '-O0', + mainDart, + noEs6WasmPath, + ]); + + final es6Mjs = await File(es6MjsPath).readAsString(); + final noEs6Mjs = await File(noEs6MjsPath).readAsString(); + + final es6GoldenFile = File(es6GoldenPath); + final noEs6GoldenFile = File(noEs6GoldenPath); + + if (updateGolden) { + print('Updating golden files...'); + await es6GoldenFile.writeAsString(es6Mjs); + await noEs6GoldenFile.writeAsString(noEs6Mjs); + return; + } + + Expect.isTrue( + es6GoldenFile.existsSync(), + 'Expected golden file $es6GoldenPath to exist.', + ); + Expect.isTrue( + noEs6GoldenFile.existsSync(), + 'Expected golden file $noEs6GoldenPath to exist.', + ); + + final expectedEs6Mjs = await es6GoldenFile.readAsString(); + final expectedNoEs6Mjs = await noEs6GoldenFile.readAsString(); + + Expect.equals(expectedEs6Mjs, es6Mjs); + Expect.equals(expectedNoEs6Mjs, noEs6Mjs); + + _verifyMjsDifferences(es6Mjs, noEs6Mjs); + }); +} + +void _verifyMjsDifferences(String es6Mjs, String noEs6Mjs) { + Expect.isTrue( + es6Mjs.contains('export async function compileStreaming('), + 'ES6 mjs should contain export async function compileStreaming', + ); + Expect.isTrue( + es6Mjs.contains('export async function compile('), + 'ES6 mjs should contain export async function compile', + ); + Expect.isFalse( + es6Mjs.startsWith('(function() {\nconst exportObject = {};'), + 'ES6 mjs should not start with IIFE wrapper', + ); + + Expect.isTrue( + noEs6Mjs.startsWith('(function() {\nconst exportObject = {};'), + 'Non-ES6 mjs should start with IIFE wrapper', + ); + Expect.isTrue( + noEs6Mjs.endsWith('return exportObject;\n})'), + 'Non-ES6 mjs should end with IIFE wrapper closing', + ); + Expect.isTrue( + noEs6Mjs.contains( + 'exportObject.compileStreaming = async function compileStreaming(', + ), + 'Non-ES6 mjs should assign compileStreaming to exportObject', + ); + Expect.isTrue( + noEs6Mjs.contains('exportObject.compile = async function compile('), + 'Non-ES6 mjs should assign compile to exportObject', + ); +}
diff --git a/tools/VERSION b/tools/VERSION index bf649ba..ada45d4 100644 --- a/tools/VERSION +++ b/tools/VERSION
@@ -27,5 +27,5 @@ MAJOR 3 MINOR 14 PATCH 0 -PRERELEASE 109 +PRERELEASE 110 PRERELEASE_PATCH 0