Skip to content

Commit c8e021e

Browse files
committed
build: support esbuild configurations using ESM dependencies (angular#48521)
We use `bazel/esbuild` in various places (e.g. for app bundling tests). These tests rely on the Angular Compiler-CLI itself for e.g. linking or the Terser configuration. Since everything in this repo is now strict ESM, the ESBuild configs (which are already ESM-supported) need to import from `//packages/compiler-cli`. We also need to be able to leverage our existing ESM Bazel loader for this though as otherwise resolution would fail. Long-term we can remove this if everything in the compiler-cli would use `.mjs` extensions and the import paths would also specify an explicit extension. See: https://nodejs.org/api/esm.html#mandatory-file-extensions PR Close angular#48521
1 parent 64f5ed8 commit c8e021e

9 files changed

+247
-128
lines changed

WORKSPACE

+2
Original file line numberDiff line numberDiff line change
@@ -105,7 +105,9 @@ yarn_install(
105105
"//:scripts/puppeteer-chromedriver-versions.js",
106106
"//:scripts/webdriver-manager-update.js",
107107
"//tools:postinstall-patches.js",
108+
"//tools/esm-interop:patches/npm/@angular+build-tooling+0.0.0-96fdaaa056f1cfa7ffbc4c69b7e9007279f76c94.patch",
108109
"//tools/esm-interop:patches/npm/@bazel+concatjs+5.7.1.patch",
110+
"//tools/esm-interop:patches/npm/@bazel+esbuild+5.7.1.patch",
109111
"//tools/esm-interop:patches/npm/rxjs+6.6.7.patch",
110112
],
111113
# Currently disabled due to:

tools/defaults.bzl

+9-33
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
"""Re-export of some bazel rules with repository-wide defaults."""
22

33
load("@rules_pkg//:pkg.bzl", "pkg_tar")
4-
load("@build_bazel_rules_nodejs//:index.bzl", _nodejs_binary = "nodejs_binary", _nodejs_test = "nodejs_test", _npm_package_bin = "npm_package_bin", _pkg_npm = "pkg_npm")
4+
load("@build_bazel_rules_nodejs//:index.bzl", _npm_package_bin = "npm_package_bin", _pkg_npm = "pkg_npm")
55
load("@npm//@bazel/jasmine:index.bzl", _jasmine_node_test = "jasmine_node_test")
66
load("@npm//@bazel/concatjs:index.bzl", _concatjs_devserver = "concatjs_devserver", _ts_config = "ts_config", _ts_library = "ts_library")
77
load("@npm//@bazel/rollup:index.bzl", _rollup_bundle = "rollup_bundle")
@@ -18,7 +18,7 @@ load("@npm//@angular/build-tooling/bazel/esbuild:index.bzl", _esbuild = "esbuild
1818
load("@npm//@angular/build-tooling/bazel/spec-bundling:spec-entrypoint.bzl", "spec_entrypoint")
1919
load("@npm//tsec:index.bzl", _tsec_test = "tsec_test")
2020
load("//packages/bazel:index.bzl", _ng_module = "ng_module", _ng_package = "ng_package")
21-
load("//tools/esm-interop:index.bzl", "enable_esm_node_module_loader", "extract_esm_outputs")
21+
load("//tools/esm-interop:index.bzl", "enable_esm_node_module_loader", "extract_esm_outputs", _nodejs_binary = "nodejs_binary", _nodejs_test = "nodejs_test")
2222

2323
_DEFAULT_TSCONFIG_TEST = "//packages:tsconfig-test"
2424
_INTERNAL_NG_MODULE_COMPILER = "//packages/bazel/src/ngc-wrapped"
@@ -371,11 +371,6 @@ def protractor_web_test_suite(**kwargs):
371371

372372
def nodejs_binary(
373373
name,
374-
entry_point,
375-
testonly = False,
376-
data = [],
377-
env = {},
378-
data_for_args = [],
379374
templated_args = [],
380375
enable_linker = False,
381376
**kwargs):
@@ -388,48 +383,29 @@ def nodejs_binary(
388383
"--nobazel_run_linker",
389384
]
390385

391-
env = enable_esm_node_module_loader(npm_workspace, env)
392-
393-
extract_esm_outputs(
394-
name = "%s_esm_deps" % name,
395-
testonly = testonly,
396-
deps = data,
397-
)
398-
399386
_nodejs_binary(
400387
name = name,
401-
data = [":%s_esm_deps" % name] + data_for_args,
402-
testonly = testonly,
403-
entry_point = entry_point.replace(".js", ".mjs"),
404-
env = env,
388+
npm_workspace = npm_workspace,
389+
linker_enabled = enable_linker,
405390
templated_args = templated_args,
406-
use_esm = True,
407391
**kwargs
408392
)
409393

410-
def nodejs_test(name, data = [], env = {}, data_for_args = [], templated_args = [], enable_linker = False, **kwargs):
394+
def nodejs_test(name, templated_args = [], enable_linker = False, **kwargs):
395+
npm_workspace = _node_modules_workspace_name()
396+
411397
if not enable_linker:
412398
templated_args = templated_args + [
413399
# Disable the linker and rely on patched resolution which works better on Windows
414400
# and is less prone to race conditions when targets build concurrently.
415401
"--nobazel_run_linker",
416402
]
417403

418-
npm_workspace = _node_modules_workspace_name()
419-
env = enable_esm_node_module_loader(npm_workspace, env)
420-
421-
extract_esm_outputs(
422-
name = "%s_esm_deps" % name,
423-
testonly = True,
424-
deps = data,
425-
)
426-
427404
_nodejs_test(
428405
name = name,
429-
data = [":%s_esm_deps" % name] + data_for_args,
430-
env = env,
431406
templated_args = templated_args,
432-
use_esm = True,
407+
linker_enabled = enable_linker,
408+
npm_workspace = npm_workspace,
433409
**kwargs
434410
)
435411

tools/esm-interop/esm-node-module-loader.mjs

+1-1
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ import {resolve as resolveExports} from '../../third_party/github.com/lukeed/res
1818
// patched function as it knows about first party mapped packages.
1919
const requireFn = createRequire(import.meta.url);
2020

21-
const npmDepsWorkspace = process.env.NODE_MODULES_WORKSPACE_NAME;
21+
const npmDepsWorkspace = process.env.NODE_MODULES_WORKSPACE_NAME ?? '';
2222
const runfilesRoot = path.resolve(process.env.RUNFILES);
2323
const nodeModulesPath = path.join(runfilesRoot, npmDepsWorkspace, 'node_modules');
2424

+18
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
"""Helper macro for extracting ESM output from dependencies."""
2+
3+
load("@npm//@angular/build-tooling/bazel:extract_js_module_output.bzl", "extract_js_module_output")
4+
5+
def extract_esm_outputs(name, deps, testonly = False):
6+
""""Extracts the ESM output variants from the given dependency."""
7+
8+
extract_js_module_output(
9+
name = name,
10+
deps = deps,
11+
testonly = testonly,
12+
tags = ["manual"],
13+
provider = "JSEcmaScriptModuleInfo",
14+
forward_linker_mappings = True,
15+
include_external_npm_packages = True,
16+
include_default_files = True,
17+
include_declarations = False,
18+
)

tools/esm-interop/index.bzl

+5-16
Original file line numberDiff line numberDiff line change
@@ -1,21 +1,10 @@
11
"""ESM interop helpers."""
22

3-
load("@npm//@angular/build-tooling/bazel:extract_js_module_output.bzl", "extract_js_module_output")
3+
load("//tools/esm-interop:nodejs-rules.bzl", _nodejs_binary = "nodejs_binary", _nodejs_test = "nodejs_test")
44
load("//tools/esm-interop:esm-node-module-loader.bzl", _enable_esm_node_module_loader = "enable_esm_node_module_loader")
5+
load("//tools/esm-interop:extract-esm-output.bzl", _extract_esm_outputs = "extract_esm_outputs")
56

7+
nodejs_binary = _nodejs_binary
8+
nodejs_test = _nodejs_test
69
enable_esm_node_module_loader = _enable_esm_node_module_loader
7-
8-
def extract_esm_outputs(name, deps, testonly = False):
9-
""""Extracts the ESM output variants from the given dependency."""
10-
11-
extract_js_module_output(
12-
name = name,
13-
deps = deps,
14-
testonly = testonly,
15-
tags = ["manual"],
16-
provider = "JSEcmaScriptModuleInfo",
17-
forward_linker_mappings = True,
18-
include_external_npm_packages = True,
19-
include_default_files = True,
20-
include_declarations = False,
21-
)
10+
extract_esm_outputs = _extract_esm_outputs

tools/esm-interop/nodejs-rules.bzl

+71
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
"""Macros for extending the NodeJS Bazel rules with ESM support."""
2+
3+
load("@build_bazel_rules_nodejs//:index.bzl", _nodejs_binary = "nodejs_binary", _nodejs_test = "nodejs_test")
4+
load("//tools/esm-interop:esm-node-module-loader.bzl", "enable_esm_node_module_loader")
5+
load("//tools/esm-interop:extract-esm-output.bzl", "extract_esm_outputs")
6+
7+
def nodejs_binary(
8+
name,
9+
data_for_args = [],
10+
linker_enabled = False,
11+
npm_workspace = "npm",
12+
**kwargs):
13+
env = kwargs.pop("env", {})
14+
data = kwargs.pop("data", [])
15+
testonly = kwargs.pop("testonly", False)
16+
entry_point = kwargs.pop("entry_point", None)
17+
18+
# Ensure ESM entry-points are not resolved to their link target.
19+
templated_args = kwargs.pop("templated_args", [])
20+
templated_args = templated_args + ["--node_options=--preserve-symlinks-main"]
21+
22+
if not linker_enabled:
23+
env = enable_esm_node_module_loader(npm_workspace, env)
24+
25+
extract_esm_outputs(
26+
name = "%s_esm_deps" % name,
27+
testonly = testonly,
28+
deps = data,
29+
)
30+
31+
_nodejs_binary(
32+
name = name,
33+
data = [":%s_esm_deps" % name] + data_for_args,
34+
testonly = testonly,
35+
entry_point = str(entry_point).replace(".js", ".mjs"),
36+
env = env,
37+
templated_args = templated_args,
38+
use_esm = True,
39+
**kwargs
40+
)
41+
42+
def nodejs_test(
43+
name,
44+
data_for_args = [],
45+
linker_enabled = False,
46+
npm_workspace = "npm",
47+
**kwargs):
48+
env = kwargs.pop("env", {})
49+
data = kwargs.pop("data", [])
50+
51+
# Ensure ESM entry-points are not resolved to their link target.
52+
templated_args = kwargs.pop("templated_args", [])
53+
templated_args = templated_args + ["--node_options=--preserve-symlinks-main"]
54+
55+
if not linker_enabled:
56+
env = enable_esm_node_module_loader(npm_workspace, env)
57+
58+
extract_esm_outputs(
59+
name = "%s_esm_deps" % name,
60+
testonly = True,
61+
deps = data,
62+
)
63+
64+
_nodejs_test(
65+
name = name,
66+
data = [":%s_esm_deps" % name] + data_for_args,
67+
env = env,
68+
templated_args = templated_args,
69+
use_esm = True,
70+
**kwargs
71+
)
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,93 @@
1+
diff --git a/node_modules/@angular/build-tooling/bazel/app-bundling/esbuild.config-tmpl.mjs b/node_modules/@angular/build-tooling/bazel/app-bundling/esbuild.config-tmpl.mjs
2+
index 618bbc5..96c6c92 100755
3+
--- a/node_modules/@angular/build-tooling/bazel/app-bundling/esbuild.config-tmpl.mjs
4+
+++ b/node_modules/@angular/build-tooling/bazel/app-bundling/esbuild.config-tmpl.mjs
5+
@@ -9,9 +9,9 @@
6+
import * as path from 'path';
7+
8+
import {createEsbuildAngularOptimizePlugin} from '@angular/build-tooling/shared-scripts/angular-optimization/esbuild-plugin.mjs';
9+
-import {createEs2015LinkerPlugin} from '@angular/compiler-cli/linker/babel';
10+
-import {ConsoleLogger, NodeJSFileSystem, LogLevel} from '@angular/compiler-cli';
11+
-import {GLOBAL_DEFS_FOR_TERSER_WITH_AOT} from '@angular/compiler-cli/private/tooling';
12+
+import {createEs2015LinkerPlugin} from '@angular/compiler-cli/linker/babel/index.js';
13+
+import {ConsoleLogger, NodeJSFileSystem, LogLevel} from '@angular/compiler-cli/index.mjs';
14+
+import {GLOBAL_DEFS_FOR_TERSER_WITH_AOT} from '@angular/compiler-cli/private/tooling.js';
15+
16+
/** Root path pointing to the app bundle source entry-point file. */
17+
const entryPointSourceRootPath = path.normalize(`TMPL_ENTRY_POINT_ROOTPATH`);
18+
diff --git a/node_modules/@angular/build-tooling/bazel/app-bundling/index.bzl b/node_modules/@angular/build-tooling/bazel/app-bundling/index.bzl
19+
index 1edd96d..b1c2ed1 100755
20+
--- a/node_modules/@angular/build-tooling/bazel/app-bundling/index.bzl
21+
+++ b/node_modules/@angular/build-tooling/bazel/app-bundling/index.bzl
22+
@@ -64,7 +64,8 @@ def app_bundle(
23+
name = "%s_esbuild_config" % name,
24+
config_file = ":%s_config_file" % name,
25+
deps = [
26+
- "@npm//@angular/compiler-cli",
27+
+ "@angular//packages/compiler-cli",
28+
+ "@angular//packages/compiler-cli/linker/babel",
29+
"@npm//@angular/build-tooling/shared-scripts/angular-optimization:js_lib",
30+
],
31+
**common_base_attributes
32+
diff --git a/node_modules/@angular/build-tooling/bazel/benchmark/component_benchmark/component_benchmark.bzl b/node_modules/@angular/build-tooling/bazel/benchmark/component_benchmark/component_benchmark.bzl
33+
index 6213703..7c0abd3 100755
34+
--- a/node_modules/@angular/build-tooling/bazel/benchmark/component_benchmark/component_benchmark.bzl
35+
+++ b/node_modules/@angular/build-tooling/bazel/benchmark/component_benchmark/component_benchmark.bzl
36+
@@ -1,7 +1,7 @@
37+
load("@npm//@angular/build-tooling/bazel/app-bundling:index.bzl", "app_bundle")
38+
load("@npm//@angular/build-tooling/bazel/http-server:index.bzl", "http_server")
39+
load("@npm//@angular/build-tooling/bazel:expand_template.bzl", "expand_template")
40+
-load("@npm//@angular/bazel:index.bzl", "ng_module")
41+
+load("@angular//tools:defaults.bzl", "ng_module")
42+
load("@npm//@bazel/concatjs:index.bzl", "ts_library")
43+
load(":benchmark_test.bzl", "benchmark_test")
44+
45+
@@ -37,8 +37,8 @@ def component_benchmark(
46+
driver_deps,
47+
ng_srcs,
48+
ng_deps = [
49+
- "@npm//@angular/core",
50+
- "@npm//@angular/platform-browser",
51+
+ "@angular//packages/core",
52+
+ "@angular//packages/platform-browser",
53+
],
54+
ng_assets = [],
55+
assets = None,
56+
@@ -108,7 +108,7 @@ def component_benchmark(
57+
# checked by TypeScript. We add the dependency only for bundling to reduce the compilation
58+
# scope and to make it easier to replace this dependency inside the `angular/angular`
59+
# repository with its corresponding source target that does not come with any typings.
60+
- ng_bundle_deps.append("@npm//zone.js")
61+
+ ng_bundle_deps.append("//packages/zone.js/dist:zone")
62+
63+
if not assets:
64+
html = prefix + "index.html"
65+
diff --git a/node_modules/@angular/build-tooling/shared-scripts/angular-linker/BUILD.bazel b/node_modules/@angular/build-tooling/shared-scripts/angular-linker/BUILD.bazel
66+
index b14dbdb..eb21619 100755
67+
--- a/node_modules/@angular/build-tooling/shared-scripts/angular-linker/BUILD.bazel
68+
+++ b/node_modules/@angular/build-tooling/shared-scripts/angular-linker/BUILD.bazel
69+
@@ -19,7 +19,8 @@ js_library(
70+
package_name = "@angular/build-tooling/shared-scripts/angular-linker",
71+
srcs = [":js_lib_files"],
72+
deps = [
73+
- "@npm//@angular/compiler-cli",
74+
+ "@angular//packages/compiler-cli",
75+
+ "@angular//packages/compiler-cli/linker/babel",
76+
"@npm//@babel/core",
77+
],
78+
)
79+
diff --git a/node_modules/@angular/build-tooling/shared-scripts/angular-linker/esbuild-plugin.mjs b/node_modules/@angular/build-tooling/shared-scripts/angular-linker/esbuild-plugin.mjs
80+
index 965a10e..175aa92 100755
81+
--- a/node_modules/@angular/build-tooling/shared-scripts/angular-linker/esbuild-plugin.mjs
82+
+++ b/node_modules/@angular/build-tooling/shared-scripts/angular-linker/esbuild-plugin.mjs
83+
@@ -7,8 +7,8 @@
84+
*/
85+
86+
import fs from 'fs';
87+
-import {NodeJSFileSystem, ConsoleLogger, LogLevel} from '@angular/compiler-cli';
88+
-import {createEs2015LinkerPlugin} from '@angular/compiler-cli/linker/babel';
89+
+import {NodeJSFileSystem, ConsoleLogger, LogLevel} from '@angular/compiler-cli/index.mjs';
90+
+import {createEs2015LinkerPlugin} from '@angular/compiler-cli/linker/babel/index.mjs';
91+
import babel from '@babel/core';
92+
93+
/** Naively checks whether this node path resolves to an Angular declare invocation. */
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
diff --git a/node_modules/@bazel/esbuild/BUILD b/node_modules/@bazel/esbuild/BUILD
2+
index 7d84b1d..d008b4b 100755
3+
--- a/node_modules/@bazel/esbuild/BUILD
4+
+++ b/node_modules/@bazel/esbuild/BUILD
5+
@@ -1 +1 @@
6+
-exports_files(["launcher.js"])
7+
+exports_files(["launcher.mjs"])
8+
diff --git a/node_modules/@bazel/esbuild/esbuild.bzl b/node_modules/@bazel/esbuild/esbuild.bzl
9+
index 9600076..5a69c8d 100755
10+
--- a/node_modules/@bazel/esbuild/esbuild.bzl
11+
+++ b/node_modules/@bazel/esbuild/esbuild.bzl
12+
@@ -4,7 +4,7 @@ esbuild rule
13+
14+
load("@rules_nodejs//nodejs:providers.bzl", "JSModuleInfo", "STAMP_ATTR")
15+
load("@bazel_skylib//rules:common_settings.bzl", "BuildSettingInfo")
16+
-load("@build_bazel_rules_nodejs//:index.bzl", "nodejs_binary")
17+
+load("@angular//tools/esm-interop:index.bzl", "nodejs_binary")
18+
load("@build_bazel_rules_nodejs//:providers.bzl", "ExternalNpmPackageInfo", "JSEcmaScriptModuleInfo", "node_modules_aspect", "run_node")
19+
load("@build_bazel_rules_nodejs//internal/linker:link_node_modules.bzl", "LinkerPackageMappingInfo", "module_mappings_aspect")
20+
load("@build_bazel_rules_nodejs//internal/common:expand_variables.bzl", "expand_variables")
21+
@@ -420,7 +420,7 @@ def esbuild_macro(name, output_dir = False, splitting = False, **kwargs):
22+
_launcher = "_%s_esbuild_launcher" % name
23+
nodejs_binary(
24+
name = _launcher,
25+
- entry_point = Label("//@bazel/esbuild:launcher.js"),
26+
+ entry_point = Label("//@bazel/esbuild:launcher.mjs"),
27+
)
28+
29+
srcs = kwargs.pop("srcs", [])
30+
diff --git a/node_modules/@bazel/esbuild/launcher.js b/node_modules/@bazel/esbuild/launcher.mjs
31+
similarity index 95%
32+
rename from node_modules/@bazel/esbuild/launcher.js
33+
rename to node_modules/@bazel/esbuild/launcher.mjs
34+
index cd0e606..8dd8299 100755
35+
--- a/node_modules/@bazel/esbuild/launcher.js
36+
+++ b/node_modules/@bazel/esbuild/launcher.mjs
37+
@@ -1,7 +1,7 @@
38+
-const {readFileSync, writeFileSync} = require('fs');
39+
-const {pathToFileURL} = require('url');
40+
-const {join} = require('path');
41+
-const esbuild = require('esbuild');
42+
+import {readFileSync, writeFileSync} from 'fs';
43+
+import {pathToFileURL} from 'url';
44+
+import {join} from 'path';
45+
+import esbuild from 'esbuild';
46+
47+
function getFlag(flag, required = true) {
48+
const argvFlag = process.argv.find(arg => arg.startsWith(`${flag}=`));

0 commit comments

Comments
 (0)