Skip to content

Commit

Permalink
Compile with dart2js and dart2wasm (#3737)
Browse files Browse the repository at this point in the history
  • Loading branch information
simolus3 committed Sep 10, 2024
1 parent 1b33ba9 commit 49d83f4
Show file tree
Hide file tree
Showing 13 changed files with 786 additions and 164 deletions.
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

0 comments on commit 49d83f4

Please sign in to comment.