Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Compile with dart2js and dart2wasm #3737

Merged
merged 14 commits into from
Sep 10, 2024
23 changes: 23 additions & 0 deletions _test/build.both.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
targets:
$default:
builders:
build_web_compilers:entrypoint:
options:
compilers:
dart2wasm:
dart2js:
loader: .dart.js
generate_for:
- web/main.dart
- web/sub/main.dart
- test/configurable_uri_test.dart
- test/configurable_uri_test.dart.browser_test.dart
- test/hello_world_test.dart
- test/hello_world_test.dart.browser_test.dart
- test/hello_world_deferred_test.dart
- test/hello_world_deferred_test.dart.browser_test.dart
- test/hello_world_custom_html_test.dart
- test/hello_world_custom_html_test.dart.browser_test.dart
- test/other_test.dart.browser_test.dart
- test/sub-dir/subdir_test.dart
- test/sub-dir/subdir_test.dart.browser_test.dart
23 changes: 23 additions & 0 deletions _test/test/dart2wasm_integration_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,29 @@ void main() {
);
await _expectWasCompiledWithDart2Wasm();
}, onPlatform: {'windows': const Skip('flaky on windows')});

test('when also enabling dart2js', () async {
await expectTestsPass(
usePrecompiled: true,
buildArgs: [
'--release',
'--config=both',
'--output=${d.sandbox}',
],
testArgs: _testArgs,
);
await _expectWasCompiledWithDart2Wasm();

await d.dir(
'test',
[
d.file(
'hello_world_deferred_test.dart.browser_test.dart2js.js',
startsWith('// Generated by dart2js'),
),
],
).validate();
}, onPlatform: {'windows': const Skip('flaky on windows')});
});
}

Expand Down
6 changes: 4 additions & 2 deletions build_web_compilers/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
## 4.0.12-wip
## 4.1.0-wip

- Account for dartdevc snapshot in the Dart SDK changing to an AOT snapshot.
- Support compiling to WebAssembly by using `dart2wasm` as a value for the
compiler option.
- Support compiling with multiple compilers (e.g. `dart2js` and `dart2wasm`)
with a new `compilers` option.
- Account for dartdevc snapshot in the Dart SDK changing to an AOT snapshot.
- Bump the min sdk to 3.6.0-165.0.dev.

## 4.0.11
Expand Down
202 changes: 173 additions & 29 deletions build_web_compilers/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -39,14 +39,35 @@ then all you need is the `dev_dependency` listed above.

## Configuration

### Configuring the default compiler

By default, this package uses the [Dart development compiler][] (_dartdevc_,
also known as _DDC_) to compile Dart to JavaScript. In release builds (running
the build tool with `--release`, the default compiler is `dart2js`).
the build tool with `--release`), `dart2js` is used as a compiler.

In addition to compiling to JavaScript, this package also supports compiling to
WebAssembly. Currently, `dart2wasm` needs to be enabled with builder options.
To understand the impact of these options, be aware of differences between
compiling to JavaScript and compiling to WebAssembly:

1. Dart has two compilers emitting JavaScript: `dart2js` and `dartdevc` (which
supports incremental rebuilds but is typically only used for development).
For both JavaScript compilers, `build_web_compilers` generates a primary
entrypoint script and additional module files or source maps depending on
compiler options.
2. Compiling with `dart2wasm` generates a WebAssembly module (a `.wasm` file)
and a JavaScript module (a `.mjs` file) exporting functions to instantiate
that module. `dart2wasm` alone generates no entrypoint file that could be
added as a `<script>` tag.

In addition to invoking compilers, `build_web_compilers` can emit an entrypoint
file suitable for `dart2wasm`. When both `dart2wasm` and a JavaScript compiler
are enabled, the entrypoint file runs a feature detection for WasmGC and loads
either the WebAssembly module or the JavaScript file depending on what the
browser supports.

### Compiler arguments

If you would like to opt into dart2js for all builds, you will need to add a
`build.yaml` file, which should look roughly like the following:
To customize compilers, you will need to add a `build.yaml` file configuring
the `build_web_compilers:entrypoint` builder, similar to the following:

```yaml
targets:
Expand All @@ -58,28 +79,103 @@ targets:
- test/**.browser_test.dart
- web/**.dart
options:
compiler: dart2js
# List any dart2js specific args here, or omit it.
dart2js_args:
- -O2
compilers:
# All compilers listed here are enabled:
dart2js:
# List any dart2js specific args here, or omit it.
args:
- O2
dart2wasm:
args:
- O3
```

`build_runner` runs development builds by default, but can use builders with a
different configuration for `--release` builds. For instance, if you wanted to
compile with `dartdevc` only on development and `dart2js` + `dart2wasm` for
release builds, you can use this configuration as a starting point:

```yaml
targets:
$default:
builders:
build_web_compilers:entrypoint:
generate_for:
- test/**.browser_test.dart
- web/**.dart
dev_options:
compilers:
dartdevc:
release_options:
compilers:
dart2js:
args:
- O2
dart2wasm:
args:
- O3
```

In addition to DDC and dart2js, this package also supports the dart2wasm
compiler for compiling to WebAssembly with a JavaScript loader. It can be
enabled by using `compiler: dart2wasm` in the build configuration:
### Customizing emitted file names

The file names emitted by `build_web_compilers` can be changed. The default
names depend on which compilers are enabled:

1. When only `dart2js` or `dartdevc` is enabled, a single `.dart.js` entrypoint
is emitted.
2. When only `dart2wasm` is enabled, a single `.dart.js` entrypoint (loading
a generated `.wasm` module through a `.mjs` helper file) is generated.
3. When both `dart2wasm` and a JavaScript compiler are enabled, then:
- The output of the JavaScript compiler is named `.dart2js.js` or `.ddc.js`
depending on the compiler.
- `dart2wasm` continues to emit a `.wasm` and a `.mjs` file.
- An entrypoint loader named `.dart.js` that loads the appropriate output
depending on browser features is generated.

All names can be overridden by using the `loader` option or the `extension`
flag in compiler options:

```yaml
targets:
$default:
builders:
build_web_compilers:entrypoint:
options:
compiler: dart2wasm
# List flags that should be forwarded to `dart compile wasm`
dart2wasm_args:
- -O2
loader: .load.js
compilers:
dart2js:
extension: .old.js
dart2wasm:
extension: .new.mjs
```

This configuration uses both `dart2js` and `dart2wasm`, but names the final
entrypoint for a `main.dart` file `main.load.js`. That loader will either load
a `.new.mjs` file for WebAssembly or a `.old.js` for pure JavaScript.

Note that the `loader` option is ignored when `dart2wasm` is not enabled, as
it's the compiler requiring an additional loader to be emitted.

### Customizing the WebAssembly loader

In some cases, for instance when targeting Node.JS, the generated loader for
`dart2wasm` may be unsuitable. The builtin loader can be disabled by setting
the option to null:

```yaml
targets:
$default:
builders:
build_web_compilers:entrypoint:
options:
loader: null
compilers:
dart2js:
dart2wasm:
```

In this case, you need to use another builder or a predefined loader instead.

### Configuring -D environment variables

dartdevc is a modular compiler, so in order to ensure consistent builds
Expand All @@ -96,22 +192,71 @@ global_options:
ANOTHER_VAR: false
```

For dart2js, use the `dart2js_args` option. This may be configured globally, or
per target.
These may also be specified on the command line with a `--define` argument.

```sh
webdev serve -- '--define=build_web_compilers:ddc=environment={"SOME_VAR":"changed"}'
```

For dart2js, use the `args` option within the `dart2js` compiler entry. This
may be configured globally, or per target.

```yaml
targets:
$default:
builders:
build_web_compilers:entrypoint:
options:
compilers:
dart2js:
args:
- -DSOME_VAR=some value
- -DANOTHER_VAR=true
```

To apply variables across multiple compilers, they have to be added to each
one:

```yaml
targets:
$default:
builders:
build_web_compilers:entrypoint:
options:
compilers:
dart2js:
args:
- -DSOME_VAR=some value
- -DANOTHER_VAR=true
dart2wasm:
args:
- -DSOME_VAR=some value
- -DANOTHER_VAR=true
```

### Legacy builder options

Previous versions of `build_web_compilers` only supported a single enabled
compiler that would be enabled with the `compiler` option.
If you only want to use `dart2js` for all builds, you can use that option:

```yaml
targets:
$default:
builders:
build_web_compilers:entrypoint:
# These are globs for the entrypoints you want to compile.
generate_for:
- test/**.browser_test.dart
- web/**.dart
options:
compiler: dart2js
# List any dart2js specific args here, or omit it.
dart2js_args:
- -DSOME_VAR=some value
- -DANOTHER_VAR=true
- -O2
```

Similarly, options are passed to `dart compile wasm` when using the
`dart2wasm_args` option:
Similarly, only compiling with `dart2wasm`:

```yaml
targets:
Expand All @@ -120,16 +265,15 @@ targets:
build_web_compilers:entrypoint:
options:
compiler: dart2wasm
# List flags that should be forwarded to `dart compile wasm`
dart2wasm_args:
- -DSOME_VAR=some value
- -DANOTHER_VAR=true
- -O2
```

These may also be specified on the command line with a `--define` argument.

```sh
webdev serve -- '--define=build_web_compilers:ddc=environment={"SOME_VAR":"changed"}'
```
When no option is set, the `compiler` option is implicitly set to `dart2js` on
release builds and to `dartdevc` otherwise.
Note that the `compilers` option takes precedence over the `compiler` option
when set.

## Manual Usage

Expand Down
16 changes: 11 additions & 5 deletions build_web_compilers/lib/src/dart2js_bootstrap.dart
Original file line number Diff line number Diff line change
Expand Up @@ -25,14 +25,20 @@ Future<void> bootstrapDart2Js(
BuildStep buildStep,
List<String> dart2JsArgs, {
required bool? nativeNullAssertions,
String entrypointExtension = jsEntrypointExtension,
}) =>
_resourcePool.withResource(() => _bootstrapDart2Js(buildStep, dart2JsArgs,
nativeNullAssertions: nativeNullAssertions));
_resourcePool.withResource(() => _bootstrapDart2Js(
buildStep,
dart2JsArgs,
nativeNullAssertions: nativeNullAssertions,
entrypointExtension: entrypointExtension,
));

Future<void> _bootstrapDart2Js(
BuildStep buildStep,
List<String> dart2JsArgs, {
required bool? nativeNullAssertions,
required String entrypointExtension,
}) async {
var dartEntrypointId = buildStep.inputId;
var moduleId =
Expand Down Expand Up @@ -74,7 +80,7 @@ https://github.com/dart-lang/build/blob/master/docs/faq.md#how-can-i-resolve-ski
var jsOutputPath = p.withoutExtension(dartUri.scheme == 'package'
? 'packages/${dartUri.path}'
: dartUri.path.substring(1)) +
jsEntrypointExtension;
entrypointExtension;
var librariesSpec = p.joinAll([sdkDir, 'lib', 'libraries.json']);
_validateUserArgs(dart2JsArgs);
args = dart2JsArgs.toList()
Expand Down Expand Up @@ -102,7 +108,7 @@ https://github.com/dart-lang/build/blob/master/docs/faq.md#how-can-i-resolve-ski
...args,
],
workingDirectory: scratchSpace.tempDir.path);
var jsOutputId = dartEntrypointId.changeExtension(jsEntrypointExtension);
var jsOutputId = dartEntrypointId.changeExtension(entrypointExtension);
var jsOutputFile = scratchSpace.fileFor(jsOutputId);
if (result.exitCode == 0 && await jsOutputFile.exists()) {
log.info('${result.stdout}\n${result.stderr}');
Expand All @@ -111,7 +117,7 @@ https://github.com/dart-lang/build/blob/master/docs/faq.md#how-can-i-resolve-ski
var fileGlob = Glob('$dartFile.js*');
var archive = Archive();
await for (var jsFile in fileGlob.list(root: rootDir)) {
if (jsFile.path.endsWith(jsEntrypointExtension) ||
if (jsFile.path.endsWith(entrypointExtension) ||
jsFile.path.endsWith(jsEntrypointSourceMapExtension)) {
// These are explicitly output, and are not part of the archive.
continue;
Expand Down
Loading
Loading