Skip to content

Commit

Permalink
feat(bazel): support using es2020 in spec-bundled tests
Browse files Browse the repository at this point in the history
We currently set the default spec-bundle target to es2016 to work
around the ZoneJS async/await native syntax issue. We can default
to ES2020 consistently and just downlevel async/await, similar to
how the Angular CLI would do it.
  • Loading branch information
devversion committed Dec 12, 2022
1 parent 734fd03 commit 07c1c19
Show file tree
Hide file tree
Showing 5 changed files with 111 additions and 3 deletions.
6 changes: 6 additions & 0 deletions bazel/spec-bundling/bundle-config.bzl
Original file line number Diff line number Diff line change
@@ -1,12 +1,14 @@
def _spec_bundle_config_file_impl(ctx):
run_angular_linker = ctx.attr.run_angular_linker
downlevel_async_await = ctx.attr.downlevel_async_await
linker_unknown_declaration_handling = ctx.attr.linker_unknown_declaration_handling

ctx.actions.expand_template(
template = ctx.file._template,
output = ctx.outputs.output_name,
substitutions = {
"TMPL_RUN_LINKER": "true" if run_angular_linker else "false",
"TMPL_DOWNLEVEL_ASYNC_AWAIT": "true" if downlevel_async_await else "false",
"TMPL_LINKER_UNKNOWN_DECLARATION_HANDLING": ("'%s'" % linker_unknown_declaration_handling) if linker_unknown_declaration_handling else "undefined",
},
)
Expand All @@ -19,6 +21,10 @@ spec_bundle_config_file = rule(
doc = "Whether the Angular linker should process all files.",
default = False,
),
"downlevel_async_await": attr.bool(
doc = "Whether to downlevel async/await syntax.",
default = True,
),
"output_name": attr.output(
mandatory = True,
doc = "Name of the file where the config should be written to.",
Expand Down
11 changes: 11 additions & 0 deletions bazel/spec-bundling/esbuild.config-tmpl.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -26,12 +26,23 @@ async function fetchAndCreateLinkerEsbuildPlugin() {
// Based on the Bazel action and its substitutions, we run the linker for all inputs.
const plugins = TMPL_RUN_LINKER ? [await fetchAndCreateLinkerEsbuildPlugin()] : [];

// List of supported features as per ESBuild. See:
// https://esbuild.github.io/api/#supported.
const supported = {};

// Async/Await can be downleveled so that ZoneJS can intercept. See:
// https://github.com/angular/angular-cli/blob/afe9feaa45913/packages/angular_devkit/build_angular/src/builders/browser-esbuild/index.ts#L313-L318.
if (TMPL_DOWNLEVEL_ASYNC_AWAIT) {
supported['async-await'] = false;
}

export default {
// `tslib` sets the `module` condition to resolve to ESM.
conditions: ['es2020', 'es2015', 'module'],
// This ensures that we prioritize ES2020. RxJS would otherwise use the ESM5 output.
mainFields: ['es2020', 'es2015', 'module', 'main'],
// Addition of `.mjs` to the non-jsx defaults. https://esbuild.github.io/api/#resolve-extensions
resolveExtensions: ['.mjs', '.js', '.json'],
supported,
plugins,
};
8 changes: 5 additions & 3 deletions bazel/spec-bundling/spec-bundle.bzl
Original file line number Diff line number Diff line change
Expand Up @@ -10,9 +10,9 @@ def spec_bundle(
bootstrap = [],
run_angular_linker = False,
linker_unknown_declaration_handling = None,
# We cannot use `ES2017` or higher as that would result in `async/await` not being downleveled.
# ZoneJS needs to be able to intercept these as otherwise change detection would not work properly.
target = "es2016",
target = "es2020",
# For ZoneJS compatibility, async/await is downleveled.
downlevel_async_await = True,
external = [],
workspace_name = None):
"""Macro that will bundle all test files with their respective transitive dependencies.
Expand All @@ -30,6 +30,7 @@ def spec_bundle(
ending with `init.js` are picked up.
target: Target ECMAScript to use for the specs bundle.
run_angular_linker: Whether the Angular linker should process the bundled code.
downlevel_async_await: Whether async/await native syntax should be downleveled.
linker_unknown_declaration_handling: Control how unknown partial declarations should be
treated. This passes through to the `unknownDeclarationVersionHandling` linker plugin option.
external: List of modules/packages which should not be bundled.
Expand All @@ -55,6 +56,7 @@ def spec_bundle(
testonly = True,
output_name = "%s_config.mjs" % name,
run_angular_linker = run_angular_linker,
downlevel_async_await = downlevel_async_await,
linker_unknown_declaration_handling = linker_unknown_declaration_handling,
)

Expand Down
31 changes: 31 additions & 0 deletions bazel/spec-bundling/test/BUILD.bazel
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,21 @@ load("@npm//@bazel/jasmine:index.bzl", "jasmine_node_test")
load("//bazel/spec-bundling:index.bzl", "spec_bundle")
load("//tools:defaults.bzl", "ts_library")

ts_library(
name = "test_async_await_lib",
testonly = True,
srcs = ["async-await.spec.ts"],
deps = [
"@npm//@angular/core",
"@npm//@angular/platform-browser-dynamic",
"@npm//@types/jasmine",
"@npm//@types/jsdom",
"@npm//@types/node",
"@npm//jsdom",
"@npm//zone.js",
],
)

ts_library(
name = "test_lib_apf",
testonly = True,
Expand Down Expand Up @@ -46,6 +61,14 @@ spec_bundle(
deps = [":test_lib_invalid_linker_declaration"],
)

spec_bundle(
name = "test_bundle_async_await",
external = ["jsdom"],
platform = "node",
run_angular_linker = True,
deps = [":test_async_await_lib"],
)

jasmine_node_test(
name = "test",
deps = [
Expand All @@ -66,3 +89,11 @@ jasmine_node_test(
":test_bundle_invalid_declaration_linker",
],
)

jasmine_node_test(
name = "test_async_await",
deps = [
":test_bundle_async_await",
"@npm//jsdom",
],
)
58 changes: 58 additions & 0 deletions bazel/spec-bundling/test/async-await.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
import 'zone.js';
import 'zone.js/testing';

import {JSDOM} from 'jsdom';
import {Component} from '@angular/core';
import {fakeAsync, flush, TestBed, waitForAsync} from '@angular/core/testing';
import {
BrowserDynamicTestingModule,
platformBrowserDynamicTesting,
} from '@angular/platform-browser-dynamic/testing';

describe('native async/await downleveled', () => {
beforeAll(() => {
const {window} = new JSDOM();

(global as any).window = window;
(global as any).document = window.document;
(global as any).Node = window.Node;
(global as any).MouseEvent = window.MouseEvent;

TestBed.initTestEnvironment(BrowserDynamicTestingModule, platformBrowserDynamicTesting());
});

it('should properly detect changes', fakeAsync(() => {
TestBed.configureTestingModule({
declarations: [AppComponent],
});
const fixture = TestBed.createComponent(AppComponent);
const el = fixture.nativeElement as HTMLElement;

fixture.detectChanges();
el.dispatchEvent(new MouseEvent('click'));
fixture.detectChanges();

// Flush the timeout macrotask from the click handler.
// Then we attempt detecting changes.
flush();
fixture.detectChanges();

expect(el.textContent).toBe('Yes');
}));
});

@Component({
selector: 'app-component',
template: `<span>{{ triggered ? 'Yes' : 'No' }}</span>`,
host: {
'(click)': 'click()',
},
})
class AppComponent {
triggered = false;

async click() {
await new Promise((resolve) => setTimeout(resolve, 500));
this.triggered = true;
}
}

0 comments on commit 07c1c19

Please sign in to comment.