mirror of
https://github.com/angular/angular
synced 2026-05-24 09:28:37 +00:00
build: set up ts_project interop for rules_js migration (#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 #61087
This commit is contained in:
parent
1c7f669f33
commit
72b7de0d73
7 changed files with 305 additions and 36 deletions
45
WORKSPACE
45
WORKSPACE
|
|
@ -2,6 +2,7 @@ workspace(
|
|||
name = "angular",
|
||||
)
|
||||
|
||||
load("@bazel_tools//tools/build_defs/repo:git.bzl", "git_repository")
|
||||
load("@bazel_tools//tools/build_defs/repo:http.bzl", "http_archive")
|
||||
load("//:yarn.bzl", "YARN_LABEL")
|
||||
|
||||
|
|
@ -129,9 +130,18 @@ http_archive(
|
|||
load("@aspect_rules_ts//ts:repositories.bzl", "rules_ts_dependencies")
|
||||
|
||||
rules_ts_dependencies(
|
||||
# Obtained by: curl --silent https://registry.npmjs.org/typescript/5.8.2 | jq -r '.dist.integrity'
|
||||
ts_integrity = "sha512-aJn6wq13/afZp/jT9QZmwEjDqqvSGp1VT5GVg+f/t6/oVyrgXM6BY1h9BRh/O5p3PlUPAe+WuiEZOmb/49RqoQ==",
|
||||
ts_version_from = "//:package.json",
|
||||
)
|
||||
|
||||
http_archive(
|
||||
name = "aspect_rules_rollup",
|
||||
sha256 = "c4062681968f5dcd3ce01e09e4ba73670c064744a7046211763e17c98ab8396e",
|
||||
strip_prefix = "rules_rollup-2.0.0",
|
||||
url = "https://github.com/aspect-build/rules_rollup/releases/download/v2.0.0/rules_rollup-v2.0.0.tar.gz",
|
||||
)
|
||||
|
||||
load("@aspect_bazel_lib//lib:repositories.bzl", "aspect_bazel_lib_dependencies")
|
||||
|
||||
aspect_bazel_lib_dependencies()
|
||||
|
|
@ -231,3 +241,38 @@ yarn_install(
|
|||
yarn = YARN_LABEL,
|
||||
yarn_lock = "//packages/core/schematics/migrations/signal-migration/test/ts-versions:yarn.lock",
|
||||
)
|
||||
|
||||
git_repository(
|
||||
name = "devinfra",
|
||||
commit = "c4f7d3cdec164044284139182b709dfd4be339ed",
|
||||
remote = "https://github.com/angular/dev-infra.git",
|
||||
)
|
||||
|
||||
load("@devinfra//bazel:setup_dependencies_1.bzl", "setup_dependencies_1")
|
||||
|
||||
setup_dependencies_1()
|
||||
|
||||
load("@devinfra//bazel:setup_dependencies_2.bzl", "setup_dependencies_2")
|
||||
|
||||
setup_dependencies_2()
|
||||
|
||||
git_repository(
|
||||
name = "rules_angular",
|
||||
commit = "0a54fca16350cab2b823908f1725aec175fcfeb2",
|
||||
remote = "https://github.com/devversion/rules_angular.git",
|
||||
)
|
||||
|
||||
load("@rules_angular//setup:step_1.bzl", "rules_angular_step1")
|
||||
|
||||
rules_angular_step1()
|
||||
|
||||
load("@rules_angular//setup:step_2.bzl", "rules_angular_step2")
|
||||
|
||||
rules_angular_step2()
|
||||
|
||||
load("@rules_angular//setup:step_3.bzl", "rules_angular_step3")
|
||||
|
||||
rules_angular_step3(
|
||||
angular_compiler_cli = "//:node_modules/@angular/compiler-cli",
|
||||
typescript = "//:node_modules/typescript",
|
||||
)
|
||||
|
|
|
|||
|
|
@ -37,7 +37,18 @@ ts_config(
|
|||
rules_js_tsconfig(
|
||||
name = "build-tsconfig",
|
||||
src = "tsconfig-build.json",
|
||||
deps = [],
|
||||
deps = [
|
||||
"//:node_modules/tslib",
|
||||
],
|
||||
)
|
||||
|
||||
rules_js_tsconfig(
|
||||
name = "test-tsconfig",
|
||||
src = "tsconfig-test.json",
|
||||
deps = [
|
||||
":build-tsconfig",
|
||||
"//:node_modules/@types/jasmine",
|
||||
],
|
||||
)
|
||||
|
||||
exports_files([
|
||||
|
|
|
|||
0
tools/bazel/BUILD.bazel
Normal file
0
tools/bazel/BUILD.bazel
Normal file
27
tools/bazel/module_name.bzl
Normal file
27
tools/bazel/module_name.bzl
Normal file
|
|
@ -0,0 +1,27 @@
|
|||
def compute_module_name(testonly):
|
||||
""" Provide better defaults for package names.
|
||||
|
||||
e.g. rather than angular/packages/core/testing we want @angular/core/testing
|
||||
"""
|
||||
pkg = native.package_name()
|
||||
|
||||
if testonly:
|
||||
# Some tests currently rely on the long-form package names
|
||||
return None
|
||||
|
||||
if pkg.startswith("packages/bazel"):
|
||||
# Avoid infinite recursion in the ViewEngine compiler. Error looks like:
|
||||
# Compiling Angular templates (ngc) //packages/bazel/test/ngc-wrapped/empty:empty failed (Exit 1)
|
||||
# : RangeError: Maximum call stack size exceeded
|
||||
# at normalizeString (path.js:57:25)
|
||||
# at Object.normalize (path.js:1132:12)
|
||||
# at Object.join (path.js:1167:18)
|
||||
# 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)
|
||||
# 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)
|
||||
# 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)
|
||||
return None
|
||||
|
||||
if pkg.startswith("packages/"):
|
||||
return "@angular/" + pkg[len("packages/"):]
|
||||
|
||||
return None
|
||||
173
tools/bazel/ts_project_interop.bzl
Normal file
173
tools/bazel/ts_project_interop.bzl
Normal file
|
|
@ -0,0 +1,173 @@
|
|||
load("@aspect_rules_js//js:providers.bzl", "JsInfo", "js_info")
|
||||
load("@build_bazel_rules_nodejs//:providers.bzl", "DeclarationInfo", "JSEcmaScriptModuleInfo", "JSModuleInfo", "LinkablePackageInfo")
|
||||
load("@devinfra//bazel/ts_project:index.bzl", "strict_deps_test")
|
||||
load("@rules_angular//src/ts_project:index.bzl", _ts_project = "ts_project")
|
||||
|
||||
def _ts_deps_interop_impl(ctx):
|
||||
types = []
|
||||
sources = []
|
||||
runfiles = ctx.runfiles(files = [])
|
||||
for dep in ctx.attr.deps:
|
||||
if not DeclarationInfo in dep:
|
||||
fail("Expected target with DeclarationInfo: %s", dep)
|
||||
types.append(dep[DeclarationInfo].transitive_declarations)
|
||||
if not JSModuleInfo in dep:
|
||||
fail("Expected target with JSModuleInfo: %s", dep)
|
||||
sources.append(dep[JSModuleInfo].sources)
|
||||
if not DefaultInfo in dep:
|
||||
fail("Expected target with DefaultInfo: %s", dep)
|
||||
runfiles = runfiles.merge(dep[DefaultInfo].default_runfiles)
|
||||
|
||||
return [
|
||||
DefaultInfo(runfiles = runfiles),
|
||||
## NOTE: We don't need to propagate module mappings FORTUNATELY!
|
||||
# because rules_nodejs supports tsconfig path mapping, given that
|
||||
# everything is nicely compiled from `bazel-bin/`!
|
||||
js_info(
|
||||
target = ctx.label,
|
||||
transitive_types = depset(transitive = types),
|
||||
transitive_sources = depset(transitive = sources),
|
||||
),
|
||||
]
|
||||
|
||||
ts_deps_interop = rule(
|
||||
implementation = _ts_deps_interop_impl,
|
||||
attrs = {
|
||||
"deps": attr.label_list(providers = [DeclarationInfo], mandatory = True),
|
||||
},
|
||||
)
|
||||
|
||||
def _ts_project_module_impl(ctx):
|
||||
# Forward runfiles. e.g. JSON files on `ts_project#data`. The jasmine
|
||||
# consuming rules may rely on this, or the linker due to its symlinks then.
|
||||
runfiles = ctx.attr.dep[DefaultInfo].default_runfiles
|
||||
info = ctx.attr.dep[JsInfo]
|
||||
|
||||
# Filter runfiles to not include `node_modules` from Aspect as this interop
|
||||
# target is supposed to be used downstream by `rules_nodejs` consumers,
|
||||
# and mixing pnpm-style node modules with linker node modules is incompatible.
|
||||
filtered_runfiles = []
|
||||
for f in runfiles.files.to_list():
|
||||
if f.short_path.startswith("node_modules/"):
|
||||
continue
|
||||
filtered_runfiles.append(f)
|
||||
runfiles = ctx.runfiles(files = filtered_runfiles)
|
||||
|
||||
providers = [
|
||||
DefaultInfo(
|
||||
runfiles = runfiles,
|
||||
),
|
||||
JSModuleInfo(
|
||||
direct_sources = info.sources,
|
||||
sources = depset(transitive = [info.transitive_sources]),
|
||||
),
|
||||
JSEcmaScriptModuleInfo(
|
||||
direct_sources = info.sources,
|
||||
sources = depset(transitive = [info.transitive_sources]),
|
||||
),
|
||||
DeclarationInfo(
|
||||
declarations = _filter_types_depset(info.types),
|
||||
transitive_declarations = _filter_types_depset(info.transitive_types),
|
||||
type_blocklisted_declarations = depset(),
|
||||
),
|
||||
]
|
||||
|
||||
if ctx.attr.module_name:
|
||||
providers.append(
|
||||
LinkablePackageInfo(
|
||||
package_name = ctx.attr.module_name,
|
||||
package_path = "",
|
||||
path = "%s/%s/%s" % (ctx.bin_dir.path, ctx.label.workspace_root, ctx.label.package),
|
||||
files = info.sources,
|
||||
),
|
||||
)
|
||||
|
||||
return providers
|
||||
|
||||
ts_project_module = rule(
|
||||
implementation = _ts_project_module_impl,
|
||||
attrs = {
|
||||
"dep": attr.label(providers = [JsInfo], mandatory = True),
|
||||
# Noop attribute for aspect propagation of the linker interop deps; so
|
||||
# that transitive linker dependencies are discovered.
|
||||
"deps": attr.label_list(),
|
||||
# Note: The module aspect from consuming `ts_library` targets will
|
||||
# consume the module mappings automatically.
|
||||
"module_name": attr.string(),
|
||||
"module_root": attr.string(),
|
||||
},
|
||||
)
|
||||
|
||||
def ts_project(
|
||||
name,
|
||||
module_name = None,
|
||||
deps = [],
|
||||
interop_deps = [],
|
||||
tsconfig = None,
|
||||
testonly = False,
|
||||
visibility = None,
|
||||
ignore_strict_deps = False,
|
||||
enable_runtime_rnjs_interop = True,
|
||||
rule_impl = _ts_project,
|
||||
**kwargs):
|
||||
# Pull in the `rules_nodejs` variants of dependencies we know are "hybrid". This
|
||||
# is necessary as we can't mix `npm/node_modules` from RNJS with the pnpm-style
|
||||
# symlink-dependent node modules. In addition, we need to extract `_rjs` interop
|
||||
# dependencies so that we can forward and capture the module mappings for runtime
|
||||
# execution, with regards to first-party dependency linking.
|
||||
rjs_modules_to_rnjs = []
|
||||
if enable_runtime_rnjs_interop:
|
||||
for d in deps:
|
||||
if d.startswith("//:node_modules/"):
|
||||
rjs_modules_to_rnjs.append(d.replace("//:node_modules/", "@npm//"))
|
||||
if d.endswith("_rjs"):
|
||||
rjs_modules_to_rnjs.append(d.replace("_rjs", ""))
|
||||
|
||||
ts_deps_interop(
|
||||
name = "%s_interop_deps" % name,
|
||||
deps = [] + interop_deps + rjs_modules_to_rnjs,
|
||||
visibility = visibility,
|
||||
testonly = testonly,
|
||||
)
|
||||
|
||||
rule_impl(
|
||||
name = "%s_rjs" % name,
|
||||
testonly = testonly,
|
||||
declaration = True,
|
||||
tsconfig = tsconfig,
|
||||
visibility = visibility,
|
||||
deps = [":%s_interop_deps" % name] + deps,
|
||||
**kwargs
|
||||
)
|
||||
|
||||
if not ignore_strict_deps:
|
||||
strict_deps_test(
|
||||
name = "%s_strict_deps_test" % name,
|
||||
srcs = kwargs.get("srcs", []),
|
||||
deps = deps,
|
||||
)
|
||||
|
||||
ts_project_module(
|
||||
name = name,
|
||||
testonly = testonly,
|
||||
visibility = visibility,
|
||||
dep = "%s_rjs" % name,
|
||||
# Forwarded dependencies for linker module mapping aspect.
|
||||
# RJS deps can also transitively pull in module mappings from their `interop_deps`.
|
||||
deps = [] + ["%s_interop_deps" % name] + deps,
|
||||
module_name = module_name,
|
||||
)
|
||||
|
||||
# Filter type provider to not include `.json` files. `ts_config`
|
||||
# targets are included in `ts_project` and their tsconfig json file
|
||||
# is included as type. See:
|
||||
# https://github.com/aspect-build/rules_ts/blob/main/ts/private/ts_config.bzl#L55C63-L55C68.
|
||||
def _filter_types_depset(types_depset):
|
||||
types = []
|
||||
|
||||
for t in types_depset.to_list():
|
||||
if t.short_path.endswith(".json"):
|
||||
continue
|
||||
types.append(t)
|
||||
|
||||
return depset(types)
|
||||
|
|
@ -20,6 +20,7 @@ load("@npm//typescript:index.bzl", "tsc")
|
|||
load("@rules_pkg//:pkg.bzl", "pkg_tar")
|
||||
load("//adev/shared-docs/pipeline/api-gen:generate_api_docs.bzl", _generate_api_docs = "generate_api_docs")
|
||||
load("//packages/bazel:index.bzl", _ng_module = "ng_module", _ng_package = "ng_package")
|
||||
load("//tools/bazel:module_name.bzl", "compute_module_name")
|
||||
load("//tools/esm-interop:index.bzl", "enable_esm_node_module_loader", _nodejs_binary = "nodejs_binary", _nodejs_test = "nodejs_test")
|
||||
|
||||
_DEFAULT_TSCONFIG_TEST = "//packages:tsconfig-test"
|
||||
|
|
@ -65,37 +66,6 @@ PKG_GROUP_REPLACEMENTS = {
|
|||
]""" % ",\n ".join(["\"%s\"" % s for s in ANGULAR_SCOPED_PACKAGES]),
|
||||
}
|
||||
|
||||
def _default_module_name(testonly):
|
||||
""" Provide better defaults for package names.
|
||||
|
||||
e.g. rather than angular/packages/core/testing we want @angular/core/testing
|
||||
|
||||
TODO(alexeagle): we ought to supply a default module name for every library in the repo.
|
||||
But we short-circuit below in cases that are currently not working.
|
||||
"""
|
||||
pkg = native.package_name()
|
||||
|
||||
if testonly:
|
||||
# Some tests currently rely on the long-form package names
|
||||
return None
|
||||
|
||||
if pkg.startswith("packages/bazel"):
|
||||
# Avoid infinite recursion in the ViewEngine compiler. Error looks like:
|
||||
# Compiling Angular templates (ngc) //packages/bazel/test/ngc-wrapped/empty:empty failed (Exit 1)
|
||||
# : RangeError: Maximum call stack size exceeded
|
||||
# at normalizeString (path.js:57:25)
|
||||
# at Object.normalize (path.js:1132:12)
|
||||
# at Object.join (path.js:1167:18)
|
||||
# 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)
|
||||
# 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)
|
||||
# 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)
|
||||
return None
|
||||
|
||||
if pkg.startswith("packages/"):
|
||||
return "@angular/" + pkg[len("packages/"):]
|
||||
|
||||
return None
|
||||
|
||||
ts_config = _ts_config
|
||||
|
||||
def ts_library(
|
||||
|
|
@ -118,13 +88,13 @@ def ts_library(
|
|||
tsconfig = _DEFAULT_TSCONFIG_TEST
|
||||
|
||||
if not module_name:
|
||||
module_name = _default_module_name(testonly)
|
||||
module_name = compute_module_name(testonly)
|
||||
|
||||
# If no `package_name` is explicitly set, we use the default module name as package
|
||||
# name, so that the target can be resolved within NodeJS executions, by activating
|
||||
# the Bazel NodeJS linker. See: https://github.com/bazelbuild/rules_nodejs/pull/2799.
|
||||
if not package_name:
|
||||
package_name = _default_module_name(testonly)
|
||||
package_name = compute_module_name(testonly)
|
||||
|
||||
default_module = "esnext"
|
||||
|
||||
|
|
@ -160,13 +130,13 @@ def ng_module(name, tsconfig = None, entry_point = None, testonly = False, deps
|
|||
tsconfig = _DEFAULT_TSCONFIG_TEST
|
||||
|
||||
if not module_name:
|
||||
module_name = _default_module_name(testonly)
|
||||
module_name = compute_module_name(testonly)
|
||||
|
||||
# If no `package_name` is explicitly set, we use the default module name as package
|
||||
# name, so that the target can be resolved within NodeJS executions, by activating
|
||||
# the Bazel NodeJS linker. See: https://github.com/bazelbuild/rules_nodejs/pull/2799.
|
||||
if not package_name:
|
||||
package_name = _default_module_name(testonly)
|
||||
package_name = compute_module_name(testonly)
|
||||
|
||||
if not entry_point:
|
||||
entry_point = "public_api.ts"
|
||||
|
|
|
|||
43
tools/defaults2.bzl
Normal file
43
tools/defaults2.bzl
Normal file
|
|
@ -0,0 +1,43 @@
|
|||
load("@rules_angular//src/ng_project:index.bzl", _ng_project = "ng_project")
|
||||
load("//tools/bazel:module_name.bzl", "compute_module_name")
|
||||
load("//tools/bazel:ts_project_interop.bzl", _ts_project = "ts_project")
|
||||
|
||||
def ts_project(
|
||||
name,
|
||||
source_map = True,
|
||||
testonly = False,
|
||||
tsconfig = None,
|
||||
**kwargs):
|
||||
module_name = kwargs.pop("module_name", compute_module_name(testonly))
|
||||
|
||||
if tsconfig == None and native.package_name().startswith("packages"):
|
||||
tsconfig = "//packages:test-tsconfig" if testonly else "//packages:build-tsconfig"
|
||||
|
||||
_ts_project(
|
||||
name,
|
||||
source_map = source_map,
|
||||
module_name = module_name,
|
||||
testonly = testonly,
|
||||
tsconfig = tsconfig,
|
||||
**kwargs
|
||||
)
|
||||
|
||||
def ng_project(
|
||||
name,
|
||||
source_map = True,
|
||||
testonly = False,
|
||||
tsconfig = None,
|
||||
**kwargs):
|
||||
module_name = kwargs.pop("module_name", compute_module_name(testonly))
|
||||
|
||||
if tsconfig == None and native.package_name().startswith("packages"):
|
||||
tsconfig = "//packages:test-tsconfig" if testonly else "//packages:build-tsconfig"
|
||||
_ts_project(
|
||||
name,
|
||||
source_map = source_map,
|
||||
module_name = module_name,
|
||||
rule_impl = _ng_project,
|
||||
testonly = testonly,
|
||||
tsconfig = tsconfig,
|
||||
**kwargs
|
||||
)
|
||||
Loading…
Reference in a new issue