Skip to content

Commit 72b7de0

Browse files
josephperrottAndrewKushnir
authored andcommitted
build: set up ts_project interop for rules_js migration (angular#61087)
The `ts_project` interop rule that we've built was also used in the Angular CLI migration, and it allows us to mix `ts_project` and `ts_library` targets; enabling an incremental migration. Additionally set up the `ng_project` to replace `ng_module`. PR Close angular#61087
1 parent 1c7f669 commit 72b7de0

7 files changed

Lines changed: 305 additions & 36 deletions

File tree

‎WORKSPACE‎

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ workspace(
22
name = "angular",
33
)
44

5+
load("@bazel_tools//tools/build_defs/repo:git.bzl", "git_repository")
56
load("@bazel_tools//tools/build_defs/repo:http.bzl", "http_archive")
67
load("//:yarn.bzl", "YARN_LABEL")
78

@@ -129,9 +130,18 @@ http_archive(
129130
load("@aspect_rules_ts//ts:repositories.bzl", "rules_ts_dependencies")
130131

131132
rules_ts_dependencies(
133+
# Obtained by: curl --silent https://registry.npmjs.org/typescript/5.8.2 | jq -r '.dist.integrity'
134+
ts_integrity = "sha512-aJn6wq13/afZp/jT9QZmwEjDqqvSGp1VT5GVg+f/t6/oVyrgXM6BY1h9BRh/O5p3PlUPAe+WuiEZOmb/49RqoQ==",
132135
ts_version_from = "//:package.json",
133136
)
134137

138+
http_archive(
139+
name = "aspect_rules_rollup",
140+
sha256 = "c4062681968f5dcd3ce01e09e4ba73670c064744a7046211763e17c98ab8396e",
141+
strip_prefix = "rules_rollup-2.0.0",
142+
url = "https://github.com/aspect-build/rules_rollup/releases/download/v2.0.0/rules_rollup-v2.0.0.tar.gz",
143+
)
144+
135145
load("@aspect_bazel_lib//lib:repositories.bzl", "aspect_bazel_lib_dependencies")
136146

137147
aspect_bazel_lib_dependencies()
@@ -231,3 +241,38 @@ yarn_install(
231241
yarn = YARN_LABEL,
232242
yarn_lock = "//packages/core/schematics/migrations/signal-migration/test/ts-versions:yarn.lock",
233243
)
244+
245+
git_repository(
246+
name = "devinfra",
247+
commit = "c4f7d3cdec164044284139182b709dfd4be339ed",
248+
remote = "https://github.com/angular/dev-infra.git",
249+
)
250+
251+
load("@devinfra//bazel:setup_dependencies_1.bzl", "setup_dependencies_1")
252+
253+
setup_dependencies_1()
254+
255+
load("@devinfra//bazel:setup_dependencies_2.bzl", "setup_dependencies_2")
256+
257+
setup_dependencies_2()
258+
259+
git_repository(
260+
name = "rules_angular",
261+
commit = "0a54fca16350cab2b823908f1725aec175fcfeb2",
262+
remote = "https://github.com/devversion/rules_angular.git",
263+
)
264+
265+
load("@rules_angular//setup:step_1.bzl", "rules_angular_step1")
266+
267+
rules_angular_step1()
268+
269+
load("@rules_angular//setup:step_2.bzl", "rules_angular_step2")
270+
271+
rules_angular_step2()
272+
273+
load("@rules_angular//setup:step_3.bzl", "rules_angular_step3")
274+
275+
rules_angular_step3(
276+
angular_compiler_cli = "//:node_modules/@angular/compiler-cli",
277+
typescript = "//:node_modules/typescript",
278+
)

‎packages/BUILD.bazel‎

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,18 @@ ts_config(
3737
rules_js_tsconfig(
3838
name = "build-tsconfig",
3939
src = "tsconfig-build.json",
40-
deps = [],
40+
deps = [
41+
"//:node_modules/tslib",
42+
],
43+
)
44+
45+
rules_js_tsconfig(
46+
name = "test-tsconfig",
47+
src = "tsconfig-test.json",
48+
deps = [
49+
":build-tsconfig",
50+
"//:node_modules/@types/jasmine",
51+
],
4152
)
4253

4354
exports_files([

‎tools/bazel/BUILD.bazel‎

Whitespace-only changes.

‎tools/bazel/module_name.bzl‎

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
def compute_module_name(testonly):
2+
""" Provide better defaults for package names.
3+
4+
e.g. rather than angular/packages/core/testing we want @angular/core/testing
5+
"""
6+
pkg = native.package_name()
7+
8+
if testonly:
9+
# Some tests currently rely on the long-form package names
10+
return None
11+
12+
if pkg.startswith("packages/bazel"):
13+
# Avoid infinite recursion in the ViewEngine compiler. Error looks like:
14+
# Compiling Angular templates (ngc) //packages/bazel/test/ngc-wrapped/empty:empty failed (Exit 1)
15+
# : RangeError: Maximum call stack size exceeded
16+
# at normalizeString (path.js:57:25)
17+
# at Object.normalize (path.js:1132:12)
18+
# at Object.join (path.js:1167:18)
19+
# at resolveModule (execroot/angular/bazel-out/host/bin/packages/bazel/src/ngc-wrapped/ngc-wrapped.runfiles/angular/packages/compiler-cli/src/metadata/bundler.js:582:50)
20+
# at MetadataBundler.exportAll (execroot/angular/bazel-out/host/bin/packages/bazel/src/ngc-wrapped/ngc-wrapped.runfiles/angular/packages/compiler-cli/src/metadata/bundler.js:119:42)
21+
# at MetadataBundler.exportAll (execroot/angular/bazel-out/host/bin/packages/bazel/src/ngc-wrapped/ngc-wrapped.runfiles/angular/packages/compiler-cli/src/metadata/bundler.js:121:52)
22+
return None
23+
24+
if pkg.startswith("packages/"):
25+
return "@angular/" + pkg[len("packages/"):]
26+
27+
return None

‎tools/bazel/ts_project_interop.bzl‎

Lines changed: 173 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,173 @@
1+
load("@aspect_rules_js//js:providers.bzl", "JsInfo", "js_info")
2+
load("@build_bazel_rules_nodejs//:providers.bzl", "DeclarationInfo", "JSEcmaScriptModuleInfo", "JSModuleInfo", "LinkablePackageInfo")
3+
load("@devinfra//bazel/ts_project:index.bzl", "strict_deps_test")
4+
load("@rules_angular//src/ts_project:index.bzl", _ts_project = "ts_project")
5+
6+
def _ts_deps_interop_impl(ctx):
7+
types = []
8+
sources = []
9+
runfiles = ctx.runfiles(files = [])
10+
for dep in ctx.attr.deps:
11+
if not DeclarationInfo in dep:
12+
fail("Expected target with DeclarationInfo: %s", dep)
13+
types.append(dep[DeclarationInfo].transitive_declarations)
14+
if not JSModuleInfo in dep:
15+
fail("Expected target with JSModuleInfo: %s", dep)
16+
sources.append(dep[JSModuleInfo].sources)
17+
if not DefaultInfo in dep:
18+
fail("Expected target with DefaultInfo: %s", dep)
19+
runfiles = runfiles.merge(dep[DefaultInfo].default_runfiles)
20+
21+
return [
22+
DefaultInfo(runfiles = runfiles),
23+
## NOTE: We don't need to propagate module mappings FORTUNATELY!
24+
# because rules_nodejs supports tsconfig path mapping, given that
25+
# everything is nicely compiled from `bazel-bin/`!
26+
js_info(
27+
target = ctx.label,
28+
transitive_types = depset(transitive = types),
29+
transitive_sources = depset(transitive = sources),
30+
),
31+
]
32+
33+
ts_deps_interop = rule(
34+
implementation = _ts_deps_interop_impl,
35+
attrs = {
36+
"deps": attr.label_list(providers = [DeclarationInfo], mandatory = True),
37+
},
38+
)
39+
40+
def _ts_project_module_impl(ctx):
41+
# Forward runfiles. e.g. JSON files on `ts_project#data`. The jasmine
42+
# consuming rules may rely on this, or the linker due to its symlinks then.
43+
runfiles = ctx.attr.dep[DefaultInfo].default_runfiles
44+
info = ctx.attr.dep[JsInfo]
45+
46+
# Filter runfiles to not include `node_modules` from Aspect as this interop
47+
# target is supposed to be used downstream by `rules_nodejs` consumers,
48+
# and mixing pnpm-style node modules with linker node modules is incompatible.
49+
filtered_runfiles = []
50+
for f in runfiles.files.to_list():
51+
if f.short_path.startswith("node_modules/"):
52+
continue
53+
filtered_runfiles.append(f)
54+
runfiles = ctx.runfiles(files = filtered_runfiles)
55+
56+
providers = [
57+
DefaultInfo(
58+
runfiles = runfiles,
59+
),
60+
JSModuleInfo(
61+
direct_sources = info.sources,
62+
sources = depset(transitive = [info.transitive_sources]),
63+
),
64+
JSEcmaScriptModuleInfo(
65+
direct_sources = info.sources,
66+
sources = depset(transitive = [info.transitive_sources]),
67+
),
68+
DeclarationInfo(
69+
declarations = _filter_types_depset(info.types),
70+
transitive_declarations = _filter_types_depset(info.transitive_types),
71+
type_blocklisted_declarations = depset(),
72+
),
73+
]
74+
75+
if ctx.attr.module_name:
76+
providers.append(
77+
LinkablePackageInfo(
78+
package_name = ctx.attr.module_name,
79+
package_path = "",
80+
path = "%s/%s/%s" % (ctx.bin_dir.path, ctx.label.workspace_root, ctx.label.package),
81+
files = info.sources,
82+
),
83+
)
84+
85+
return providers
86+
87+
ts_project_module = rule(
88+
implementation = _ts_project_module_impl,
89+
attrs = {
90+
"dep": attr.label(providers = [JsInfo], mandatory = True),
91+
# Noop attribute for aspect propagation of the linker interop deps; so
92+
# that transitive linker dependencies are discovered.
93+
"deps": attr.label_list(),
94+
# Note: The module aspect from consuming `ts_library` targets will
95+
# consume the module mappings automatically.
96+
"module_name": attr.string(),
97+
"module_root": attr.string(),
98+
},
99+
)
100+
101+
def ts_project(
102+
name,
103+
module_name = None,
104+
deps = [],
105+
interop_deps = [],
106+
tsconfig = None,
107+
testonly = False,
108+
visibility = None,
109+
ignore_strict_deps = False,
110+
enable_runtime_rnjs_interop = True,
111+
rule_impl = _ts_project,
112+
**kwargs):
113+
# Pull in the `rules_nodejs` variants of dependencies we know are "hybrid". This
114+
# is necessary as we can't mix `npm/node_modules` from RNJS with the pnpm-style
115+
# symlink-dependent node modules. In addition, we need to extract `_rjs` interop
116+
# dependencies so that we can forward and capture the module mappings for runtime
117+
# execution, with regards to first-party dependency linking.
118+
rjs_modules_to_rnjs = []
119+
if enable_runtime_rnjs_interop:
120+
for d in deps:
121+
if d.startswith("//:node_modules/"):
122+
rjs_modules_to_rnjs.append(d.replace("//:node_modules/", "@npm//"))
123+
if d.endswith("_rjs"):
124+
rjs_modules_to_rnjs.append(d.replace("_rjs", ""))
125+
126+
ts_deps_interop(
127+
name = "%s_interop_deps" % name,
128+
deps = [] + interop_deps + rjs_modules_to_rnjs,
129+
visibility = visibility,
130+
testonly = testonly,
131+
)
132+
133+
rule_impl(
134+
name = "%s_rjs" % name,
135+
testonly = testonly,
136+
declaration = True,
137+
tsconfig = tsconfig,
138+
visibility = visibility,
139+
deps = [":%s_interop_deps" % name] + deps,
140+
**kwargs
141+
)
142+
143+
if not ignore_strict_deps:
144+
strict_deps_test(
145+
name = "%s_strict_deps_test" % name,
146+
srcs = kwargs.get("srcs", []),
147+
deps = deps,
148+
)
149+
150+
ts_project_module(
151+
name = name,
152+
testonly = testonly,
153+
visibility = visibility,
154+
dep = "%s_rjs" % name,
155+
# Forwarded dependencies for linker module mapping aspect.
156+
# RJS deps can also transitively pull in module mappings from their `interop_deps`.
157+
deps = [] + ["%s_interop_deps" % name] + deps,
158+
module_name = module_name,
159+
)
160+
161+
# Filter type provider to not include `.json` files. `ts_config`
162+
# targets are included in `ts_project` and their tsconfig json file
163+
# is included as type. See:
164+
# https://github.com/aspect-build/rules_ts/blob/main/ts/private/ts_config.bzl#L55C63-L55C68.
165+
def _filter_types_depset(types_depset):
166+
types = []
167+
168+
for t in types_depset.to_list():
169+
if t.short_path.endswith(".json"):
170+
continue
171+
types.append(t)
172+
173+
return depset(types)

‎tools/defaults.bzl‎

Lines changed: 5 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ load("@npm//typescript:index.bzl", "tsc")
2020
load("@rules_pkg//:pkg.bzl", "pkg_tar")
2121
load("//adev/shared-docs/pipeline/api-gen:generate_api_docs.bzl", _generate_api_docs = "generate_api_docs")
2222
load("//packages/bazel:index.bzl", _ng_module = "ng_module", _ng_package = "ng_package")
23+
load("//tools/bazel:module_name.bzl", "compute_module_name")
2324
load("//tools/esm-interop:index.bzl", "enable_esm_node_module_loader", _nodejs_binary = "nodejs_binary", _nodejs_test = "nodejs_test")
2425

2526
_DEFAULT_TSCONFIG_TEST = "//packages:tsconfig-test"
@@ -65,37 +66,6 @@ PKG_GROUP_REPLACEMENTS = {
6566
]""" % ",\n ".join(["\"%s\"" % s for s in ANGULAR_SCOPED_PACKAGES]),
6667
}
6768

68-
def _default_module_name(testonly):
69-
""" Provide better defaults for package names.
70-
71-
e.g. rather than angular/packages/core/testing we want @angular/core/testing
72-
73-
TODO(alexeagle): we ought to supply a default module name for every library in the repo.
74-
But we short-circuit below in cases that are currently not working.
75-
"""
76-
pkg = native.package_name()
77-
78-
if testonly:
79-
# Some tests currently rely on the long-form package names
80-
return None
81-
82-
if pkg.startswith("packages/bazel"):
83-
# Avoid infinite recursion in the ViewEngine compiler. Error looks like:
84-
# Compiling Angular templates (ngc) //packages/bazel/test/ngc-wrapped/empty:empty failed (Exit 1)
85-
# : RangeError: Maximum call stack size exceeded
86-
# at normalizeString (path.js:57:25)
87-
# at Object.normalize (path.js:1132:12)
88-
# at Object.join (path.js:1167:18)
89-
# at resolveModule (execroot/angular/bazel-out/host/bin/packages/bazel/src/ngc-wrapped/ngc-wrapped.runfiles/angular/packages/compiler-cli/src/metadata/bundler.js:582:50)
90-
# at MetadataBundler.exportAll (execroot/angular/bazel-out/host/bin/packages/bazel/src/ngc-wrapped/ngc-wrapped.runfiles/angular/packages/compiler-cli/src/metadata/bundler.js:119:42)
91-
# at MetadataBundler.exportAll (execroot/angular/bazel-out/host/bin/packages/bazel/src/ngc-wrapped/ngc-wrapped.runfiles/angular/packages/compiler-cli/src/metadata/bundler.js:121:52)
92-
return None
93-
94-
if pkg.startswith("packages/"):
95-
return "@angular/" + pkg[len("packages/"):]
96-
97-
return None
98-
9969
ts_config = _ts_config
10070

10171
def ts_library(
@@ -118,13 +88,13 @@ def ts_library(
11888
tsconfig = _DEFAULT_TSCONFIG_TEST
11989

12090
if not module_name:
121-
module_name = _default_module_name(testonly)
91+
module_name = compute_module_name(testonly)
12292

12393
# If no `package_name` is explicitly set, we use the default module name as package
12494
# name, so that the target can be resolved within NodeJS executions, by activating
12595
# the Bazel NodeJS linker. See: https://github.com/bazelbuild/rules_nodejs/pull/2799.
12696
if not package_name:
127-
package_name = _default_module_name(testonly)
97+
package_name = compute_module_name(testonly)
12898

12999
default_module = "esnext"
130100

@@ -160,13 +130,13 @@ def ng_module(name, tsconfig = None, entry_point = None, testonly = False, deps
160130
tsconfig = _DEFAULT_TSCONFIG_TEST
161131

162132
if not module_name:
163-
module_name = _default_module_name(testonly)
133+
module_name = compute_module_name(testonly)
164134

165135
# If no `package_name` is explicitly set, we use the default module name as package
166136
# name, so that the target can be resolved within NodeJS executions, by activating
167137
# the Bazel NodeJS linker. See: https://github.com/bazelbuild/rules_nodejs/pull/2799.
168138
if not package_name:
169-
package_name = _default_module_name(testonly)
139+
package_name = compute_module_name(testonly)
170140

171141
if not entry_point:
172142
entry_point = "public_api.ts"

0 commit comments

Comments
 (0)