Skip to content

Commit 035d4ce

Browse files
devversiondgp1130
authored andcommitted
build: improve ts_project hybrid interop with node modules
Currently the interop resulting target of a `ts_project` ends up not necessarily working at runtime. This may be the case because a consuming Node program may end up with mixes of `node_modules` dependencies from `rules_nodejs` (old) and `rules_js` (new). This sounds fine at first glance, but in practice can break very subtly because: * Rules NodeJS leverages the linker, creating `node_module` directories outside of Bazel, at runtime. These don't depend on symlink resolving. * Rules JS puts real node module folders via Bazel actions. These rely on `pnpm` non-hoisting layout, and symlink resolving. As we can see there is a hard conflict with symlinks. They need to be enabled with the new toolchain, but the other one doesn't enable symlink resolution, and enabling is not possible as we'd otherwise risk escaping the sandbox and cause even more subtle errors. A good compromise solution is to automatically drop the `rules_js` node module files/folder in the interop-`rules_nodejs` target and instead brining in the equivalent `@npm//` dependencies from `rules_nodejs`. This kind of keeps the logic similar to when not using `rules_js` or the interop, and enables the simplest & safest mental model; and it works compared to other solutions I tried with symlinking. Notably, we can't keep both node module variants as the linker doesn't override existing node module files from e.g. rules_js then (and would break then).
1 parent 349b005 commit 035d4ce

File tree

5 files changed

+116
-79
lines changed

5 files changed

+116
-79
lines changed

packages/angular_devkit/build_angular/BUILD.bazel

+77-76
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
load("@npm//@angular/build-tooling/bazel/api-golden:index.bzl", "api_golden_test_npm_package")
77
load("@npm//@bazel/jasmine:index.bzl", "jasmine_node_test")
88
load("//tools:defaults.bzl", "pkg_npm", "ts_library")
9+
load("//tools:interop.bzl", "ts_project")
910
load("//tools:ts_json_schema.bzl", "ts_json_schema")
1011

1112
licenses(["notice"])
@@ -77,9 +78,8 @@ ts_json_schema(
7778
src = "src/builders/web-test-runner/schema.json",
7879
)
7980

80-
ts_library(
81+
ts_project(
8182
name = "build_angular",
82-
package_name = "@angular-devkit/build-angular",
8383
srcs = glob(
8484
include = [
8585
"src/**/*.ts",
@@ -118,87 +118,88 @@ ts_library(
118118
"builders.json",
119119
"package.json",
120120
],
121-
module_name = "@angular-devkit/build-angular",
122-
module_root = "src/index.d.ts",
123-
deps = [
121+
interop_deps = [
124122
"//packages/angular/build",
125123
"//packages/angular/build:private",
126124
"//packages/angular/ssr",
127-
"//packages/angular_devkit/architect",
128125
"//packages/angular_devkit/build_webpack",
129126
"//packages/angular_devkit/core",
130127
"//packages/angular_devkit/core/node",
131128
"//packages/ngtools/webpack",
132-
"@npm//@ampproject/remapping",
133-
"@npm//@angular/common",
134-
"@npm//@angular/compiler-cli",
135-
"@npm//@angular/core",
136-
"@npm//@angular/localize",
137-
"@npm//@angular/platform-server",
138-
"@npm//@angular/service-worker",
139-
"@npm//@babel/core",
140-
"@npm//@babel/generator",
141-
"@npm//@babel/helper-annotate-as-pure",
142-
"@npm//@babel/helper-split-export-declaration",
143-
"@npm//@babel/plugin-transform-async-generator-functions",
144-
"@npm//@babel/plugin-transform-async-to-generator",
145-
"@npm//@babel/plugin-transform-runtime",
146-
"@npm//@babel/preset-env",
147-
"@npm//@babel/runtime",
148-
"@npm//@discoveryjs/json-ext",
149-
"@npm//@types/babel__core",
150-
"@npm//@types/browser-sync",
151-
"@npm//@types/karma",
152-
"@npm//@types/less",
153-
"@npm//@types/loader-utils",
154-
"@npm//@types/node",
155-
"@npm//@types/picomatch",
156-
"@npm//@types/semver",
157-
"@npm//@types/watchpack",
158-
"@npm//@vitejs/plugin-basic-ssl",
159-
"@npm//@web/test-runner",
160-
"@npm//ajv",
161-
"@npm//ansi-colors",
162-
"@npm//autoprefixer",
163-
"@npm//babel-loader",
164-
"@npm//browserslist",
165-
"@npm//copy-webpack-plugin",
166-
"@npm//css-loader",
167-
"@npm//esbuild",
168-
"@npm//esbuild-wasm",
169-
"@npm//fast-glob",
170-
"@npm//http-proxy-middleware",
171-
"@npm//istanbul-lib-instrument",
172-
"@npm//jsonc-parser",
173-
"@npm//karma",
174-
"@npm//karma-source-map-support",
175-
"@npm//less",
176-
"@npm//less-loader",
177-
"@npm//license-webpack-plugin",
178-
"@npm//loader-utils",
179-
"@npm//mini-css-extract-plugin",
180-
"@npm//ng-packagr",
181-
"@npm//open",
182-
"@npm//ora",
183-
"@npm//piscina",
184-
"@npm//postcss",
185-
"@npm//postcss-loader",
186-
"@npm//resolve-url-loader",
187-
"@npm//rxjs",
188-
"@npm//sass",
189-
"@npm//sass-loader",
190-
"@npm//semver",
191-
"@npm//source-map-loader",
192-
"@npm//source-map-support",
193-
"@npm//terser",
194-
"@npm//tree-kill",
195-
"@npm//tslib",
196-
"@npm//typescript",
197-
"@npm//webpack",
198-
"@npm//webpack-dev-middleware",
199-
"@npm//webpack-dev-server",
200-
"@npm//webpack-merge",
201-
"@npm//webpack-subresource-integrity",
129+
],
130+
module_name = "@angular-devkit/build-angular",
131+
deps = [
132+
"//:root_modules/@ampproject/remapping",
133+
"//:root_modules/@angular/common",
134+
"//:root_modules/@angular/compiler-cli",
135+
"//:root_modules/@angular/core",
136+
"//:root_modules/@angular/localize",
137+
"//:root_modules/@angular/platform-server",
138+
"//:root_modules/@angular/service-worker",
139+
"//:root_modules/@babel/core",
140+
"//:root_modules/@babel/generator",
141+
"//:root_modules/@babel/helper-annotate-as-pure",
142+
"//:root_modules/@babel/helper-split-export-declaration",
143+
"//:root_modules/@babel/plugin-transform-async-generator-functions",
144+
"//:root_modules/@babel/plugin-transform-async-to-generator",
145+
"//:root_modules/@babel/plugin-transform-runtime",
146+
"//:root_modules/@babel/preset-env",
147+
"//:root_modules/@babel/runtime",
148+
"//:root_modules/@discoveryjs/json-ext",
149+
"//:root_modules/@types/babel__core",
150+
"//:root_modules/@types/browser-sync",
151+
"//:root_modules/@types/karma",
152+
"//:root_modules/@types/less",
153+
"//:root_modules/@types/loader-utils",
154+
"//:root_modules/@types/node",
155+
"//:root_modules/@types/picomatch",
156+
"//:root_modules/@types/semver",
157+
"//:root_modules/@types/watchpack",
158+
"//:root_modules/@vitejs/plugin-basic-ssl",
159+
"//:root_modules/@web/test-runner",
160+
"//:root_modules/ajv",
161+
"//:root_modules/ansi-colors",
162+
"//:root_modules/autoprefixer",
163+
"//:root_modules/babel-loader",
164+
"//:root_modules/browserslist",
165+
"//:root_modules/copy-webpack-plugin",
166+
"//:root_modules/css-loader",
167+
"//:root_modules/esbuild",
168+
"//:root_modules/esbuild-wasm",
169+
"//:root_modules/fast-glob",
170+
"//:root_modules/http-proxy-middleware",
171+
"//:root_modules/istanbul-lib-instrument",
172+
"//:root_modules/jsonc-parser",
173+
"//:root_modules/karma",
174+
"//:root_modules/karma-source-map-support",
175+
"//:root_modules/less",
176+
"//:root_modules/less-loader",
177+
"//:root_modules/license-webpack-plugin",
178+
"//:root_modules/loader-utils",
179+
"//:root_modules/mini-css-extract-plugin",
180+
"//:root_modules/ng-packagr",
181+
"//:root_modules/open",
182+
"//:root_modules/ora",
183+
"//:root_modules/piscina",
184+
"//:root_modules/postcss",
185+
"//:root_modules/postcss-loader",
186+
"//:root_modules/resolve-url-loader",
187+
"//:root_modules/rxjs",
188+
"//:root_modules/sass",
189+
"//:root_modules/sass-loader",
190+
"//:root_modules/semver",
191+
"//:root_modules/source-map-loader",
192+
"//:root_modules/source-map-support",
193+
"//:root_modules/terser",
194+
"//:root_modules/tree-kill",
195+
"//:root_modules/tslib",
196+
"//:root_modules/typescript",
197+
"//:root_modules/webpack",
198+
"//:root_modules/webpack-dev-middleware",
199+
"//:root_modules/webpack-dev-server",
200+
"//:root_modules/webpack-merge",
201+
"//:root_modules/webpack-subresource-integrity",
202+
"//packages/angular_devkit/architect",
202203
],
203204
)
204205

packages/angular_devkit/build_webpack/BUILD.bazel

+1
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@ ts_library(
3232
"src/**/*_spec.ts",
3333
],
3434
) + [
35+
"index.ts",
3536
"//packages/angular_devkit/build_webpack:src/builders/webpack-dev-server/schema.ts",
3637
"//packages/angular_devkit/build_webpack:src/builders/webpack/schema.ts",
3738
],
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
/**
2+
* @license
3+
* Copyright Google LLC All Rights Reserved.
4+
*
5+
* Use of this source code is governed by an MIT-style license that can be
6+
* found in the LICENSE file at https://angular.dev/license
7+
*/
8+
9+
export * from './src/index';

tools/interop.bzl

+28-3
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,17 @@ def _ts_project_module_impl(ctx):
4242
runfiles = ctx.attr.dep[DefaultInfo].default_runfiles
4343
info = ctx.attr.dep[JsInfo]
4444

45+
# Filter runfiles to not `node_modules` from Aspect as this interop
46+
# target is supposed to be used downstream by `rules_nodejs` consumers,
47+
# and mixing pnpm-style node modules with linker node modules is incompatible.
48+
filtered = []
49+
for f in runfiles.files.to_list():
50+
if f.short_path.startswith("node_modules/"):
51+
continue
52+
filtered.append(f)
53+
54+
runfiles = ctx.runfiles(files = filtered)
55+
4556
providers = [
4657
DefaultInfo(
4758
runfiles = runfiles,
@@ -83,9 +94,21 @@ ts_project_module = rule(
8394
)
8495

8596
def ts_project(name, module_name = None, interop_deps = [], deps = [], testonly = False, **kwargs):
97+
# Pull in the `rules_nodejs` variants of dependencies we know are "hybrid". This
98+
# is necessary as we can't mix `npm/node_modules` from RNJS with the pnpm-style
99+
# symlink-dependent node modules. In addition, we need to extract `_rjs` interop
100+
# dependencies so that we can forward and capture the module mappings for runtime
101+
# execution, with regards to first-party dependency linking.
102+
rjs_modules_to_rnjs = []
103+
for d in deps:
104+
if d.startswith("//:root_modules/"):
105+
rjs_modules_to_rnjs.append(d.replace("//:root_modules/", "@npm//"))
106+
if d.endswith("_rjs"):
107+
rjs_modules_to_rnjs.append(d.replace("_rjs", ""))
108+
86109
ts_deps_interop(
87110
name = "%s_interop_deps" % name,
88-
deps = interop_deps,
111+
deps = [] + interop_deps + rjs_modules_to_rnjs,
89112
testonly = testonly,
90113
)
91114

@@ -99,14 +122,16 @@ def ts_project(name, module_name = None, interop_deps = [], deps = [], testonly
99122
# worker for efficient, fast DX and avoiding Windows no-sandbox issues.
100123
supports_workers = 1,
101124
tsc_worker = "//tools:vanilla_ts_worker",
102-
deps = ["%s_interop_deps" % name] + deps,
125+
deps = [":%s_interop_deps" % name] + deps,
103126
**kwargs
104127
)
105128

106129
ts_project_module(
107130
name = name,
108131
testonly = testonly,
109132
dep = "%s_rjs" % name,
110-
deps = interop_deps,
133+
# Forwarded dependencies for linker module mapping aspect.
134+
# RJS deps can also transitively pull in module mappings from their `interop_deps`.
135+
deps = [] + interop_deps + deps,
111136
module_name = module_name,
112137
)

tsconfig.json

+1
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@
2727
"@angular-devkit/architect/*": ["./packages/angular_devkit/architect/*/index"],
2828
"@angular-devkit/build-webpack": ["./packages/angular_devkit/build_webpack"],
2929
"@angular-devkit/*": ["./packages/angular_devkit/*/src"],
30+
"@angular/ssr": ["./packages/angular/ssr"],
3031
"@angular/*": ["./packages/angular/*/src"],
3132
"@angular/build/private": ["./packages/angular/build/src/private"],
3233
"@ngtools/webpack": ["./packages/ngtools/webpack/src"],

0 commit comments

Comments
 (0)