diff --git a/.aspect/rules/external_repository_action_cache/npm_translate_lock_MzA5NzUwNzMx b/.aspect/rules/external_repository_action_cache/npm_translate_lock_MzA5NzUwNzMx
index 74f3dd181991..6cafeb874954 100755
--- a/.aspect/rules/external_repository_action_cache/npm_translate_lock_MzA5NzUwNzMx
+++ b/.aspect/rules/external_repository_action_cache/npm_translate_lock_MzA5NzUwNzMx
@@ -2,7 +2,7 @@
 # Input hashes for repository rule npm_translate_lock(name = "npm2", pnpm_lock = "@//:pnpm-lock.yaml").
 # This file should be checked into version control along with the pnpm-lock.yaml file.
 .npmrc=-2023857461
-package.json=-1482816862
-pnpm-lock.yaml=-2100360822
+package.json=1689751705
+pnpm-lock.yaml=1731666083
 pnpm-workspace.yaml=1711114604
 yarn.lock=-1734434772
diff --git a/WORKSPACE b/WORKSPACE
index a8354cf49eaf..2796f1fb552d 100644
--- a/WORKSPACE
+++ b/WORKSPACE
@@ -115,6 +115,7 @@ yarn_install(
         "//:tools/postinstall/patches/@angular+bazel+20.0.0-next.1.patch",
         "//:tools/postinstall/patches/@angular+build-tooling+0.0.0-335a273d3eb2a73c51efb97930fc1e0cd72e0d32.patch",
         "//:tools/postinstall/patches/@bazel+concatjs+5.8.1.patch",
+        "//:tools/postinstall/patches/tsec+0.2.2.patch",
     ],
     # Currently disabled due to:
     #  1. Missing Windows support currently.
@@ -163,6 +164,14 @@ load("@aspect_rules_js//npm:repositories.bzl", "npm_translate_lock")
 
 npm_translate_lock(
     name = "npm2",
+    custom_postinstalls = {
+        "@angular/animations": "node ../../@nginfra/angular-linking/index.mjs",
+        "@angular/common": "node ../../@nginfra/angular-linking/index.mjs",
+        "@angular/forms": "node ../../@nginfra/angular-linking/index.mjs",
+        "@angular/platform-browser": "node ../../@nginfra/angular-linking/index.mjs",
+        "@angular/router": "node ../../@nginfra/angular-linking/index.mjs",
+        "@angular/localize": "node ../../@nginfra/angular-linking/index.mjs",
+    },
     data = [
         "//:package.json",
         "//:pnpm-workspace.yaml",
diff --git a/package.json b/package.json
index d7d815479516..e9318c8e9d2a 100644
--- a/package.json
+++ b/package.json
@@ -47,7 +47,7 @@
     "integration-tests:size-test": "bazel test //integration/size-test/...",
     "test-linker-aot": "bazel test --partial_compilation --test_tag_filters=partial-compilation-integration,-firefox --build_tests_only -- //integration/... //src/...",
     "test-linker-jit": "bazel test --partial_compilation --test_tag_filters=partial-compilation-integration,-firefox --build_tests_only --//tools:force_partial_jit_compilation=True  -- //integration/... //src/...",
-    "check-tooling-setup": "yarn tsc --project tools/tsconfig.json && yarn tsc --project scripts/tsconfig.json && yarn tsc --project .ng-dev/tsconfig.json",
+    "check-tooling-setup": "yarn tsc --project tools/tsconfig.json --noEmit && yarn tsc --project scripts/tsconfig.json --noEmit && yarn tsc --project .ng-dev/tsconfig.json --noEmit",
     "tsc": "node ./node_modules/typescript/bin/tsc",
     "ci-push-deploy-docs-app": "node --no-warnings=ExperimentalWarning --loader ts-node/esm/transpile-only scripts/docs-deploy/deploy-ci-push.mts",
     "ci-docs-monitor-test": "node --no-warnings=ExperimentalWarning --loader ts-node/esm/transpile-only scripts/docs-deploy/monitoring/ci-test.mts",
@@ -186,6 +186,38 @@
     "wrap-ansi": "7.0.0"
   },
   "pnpm": {
-    "onlyBuiltDependencies": []
+    "onlyBuiltDependencies": [],
+    "packageExtensions": {
+      "@angular/animations": {
+        "dependencies": {
+          "@nginfra/angular-linking": "1.0.0"
+        }
+      },
+      "@angular/common": {
+        "dependencies": {
+          "@nginfra/angular-linking": "1.0.0"
+        }
+      },
+      "@angular/forms": {
+        "dependencies": {
+          "@nginfra/angular-linking": "1.0.0"
+        }
+      },
+      "@angular/platform-browser": {
+        "dependencies": {
+          "@nginfra/angular-linking": "1.0.0"
+        }
+      },
+      "@angular/router": {
+        "dependencies": {
+          "@nginfra/angular-linking": "1.0.0"
+        }
+      },
+      "@angular/localize": {
+        "dependencies": {
+          "@nginfra/angular-linking": "1.0.0"
+        }
+      }
+    }
   }
 }
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index 766c72334b93..bd3f1c092aca 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -13,16 +13,18 @@ overrides:
   string-width: 4.2.3
   wrap-ansi: 7.0.0
 
+packageExtensionsChecksum: 30b86fa4d9ef090f3f7ab1684b2075bc
+
 importers:
 
   .:
     dependencies:
       '@angular/animations':
         specifier: ^19.2.0
-        version: 19.2.0(@angular/core@19.2.0)
+        version: 19.2.0(@angular/compiler-cli@19.2.0)(@angular/core@19.2.0)
       '@angular/common':
         specifier: ^19.2.0
-        version: 19.2.0(@angular/core@19.2.0)(rxjs@6.6.7)
+        version: 19.2.0(@angular/compiler-cli@19.2.0)(@angular/core@19.2.0)(rxjs@6.6.7)
       '@angular/compiler':
         specifier: ^19.2.0
         version: 19.2.0(@angular/core@19.2.0)
@@ -31,10 +33,10 @@ importers:
         version: 19.2.0(rxjs@6.6.7)(zone.js@0.15.0)
       '@angular/forms':
         specifier: ^19.2.0
-        version: 19.2.0(@angular/common@19.2.0)(@angular/core@19.2.0)(@angular/platform-browser@19.2.0)(rxjs@6.6.7)
+        version: 19.2.0(@angular/common@19.2.0)(@angular/compiler-cli@19.2.0)(@angular/core@19.2.0)(@angular/platform-browser@19.2.0)(rxjs@6.6.7)
       '@angular/platform-browser':
         specifier: ^19.2.0
-        version: 19.2.0(@angular/animations@19.2.0)(@angular/common@19.2.0)(@angular/core@19.2.0)
+        version: 19.2.0(@angular/animations@19.2.0)(@angular/common@19.2.0)(@angular/compiler-cli@19.2.0)(@angular/core@19.2.0)
       '@types/google.maps':
         specifier: ^3.54.10
         version: 3.55.10
@@ -92,7 +94,7 @@ importers:
         version: 19.2.0(@angular/common@19.2.0)(@angular/compiler@19.2.0)(@angular/core@19.2.0)(@angular/platform-browser@19.2.0)
       '@angular/router':
         specifier: ^19.2.0
-        version: 19.2.0(@angular/common@19.2.0)(@angular/core@19.2.0)(@angular/platform-browser@19.2.0)(rxjs@6.6.7)
+        version: 19.2.0(@angular/common@19.2.0)(@angular/compiler-cli@19.2.0)(@angular/core@19.2.0)(@angular/platform-browser@19.2.0)(rxjs@6.6.7)
       '@babel/core':
         specifier: ^7.16.12
         version: 7.26.9
@@ -384,7 +386,6 @@ packages:
     dependencies:
       '@jridgewell/gen-mapping': 0.3.5
       '@jridgewell/trace-mapping': 0.3.25
-    dev: true
 
   /@angular-devkit/architect@0.1902.0:
     resolution: {integrity: sha512-F/3O38QOYCwNqECNQauKb56GYdST9SrRSiqTNc5xpnUL//A09kaucmKSZ2VJAVY7K/rktSQn5viiQ3rTJLiZgA==}
@@ -600,14 +601,18 @@ packages:
       - chokidar
     dev: true
 
-  /@angular/animations@19.2.0(@angular/core@19.2.0):
+  /@angular/animations@19.2.0(@angular/compiler-cli@19.2.0)(@angular/core@19.2.0):
     resolution: {integrity: sha512-GJDwtZ+7XmAAbzCbPSJrR1iMs2l16VoA7myeVl6n5k/KsZywqb4KhPmjzLKpQlAFP0NRjg1LbHc2Fsus7/Ydag==}
     engines: {node: ^18.19.1 || ^20.11.1 || >=22.0.0}
     peerDependencies:
       '@angular/core': 19.2.0
     dependencies:
       '@angular/core': 19.2.0(rxjs@6.6.7)(zone.js@0.15.0)
+      '@nginfra/angular-linking': 1.0.0(@angular/compiler-cli@19.2.0)
       tslib: 2.8.1
+    transitivePeerDependencies:
+      - '@angular/compiler-cli'
+      - supports-color
 
   /@angular/benchpress@0.3.0(rxjs@6.6.7)(zone.js@0.15.0):
     resolution: {integrity: sha512-ApxoY5lTj1S0QFLdq5ZdTfdkIds1m3tma9EJOZpNVHRU9eCj2D/5+VFb5tlWsv9NHQ2S0XXkJjauFOAdfzT8uw==}
@@ -906,7 +911,7 @@ packages:
       - supports-color
     dev: true
 
-  /@angular/common@19.2.0(@angular/core@19.2.0)(rxjs@6.6.7):
+  /@angular/common@19.2.0(@angular/compiler-cli@19.2.0)(@angular/core@19.2.0)(rxjs@6.6.7):
     resolution: {integrity: sha512-dm8PR94QY3DucXxltdV5p2Yxyr5bfPlmjOElwLhiTvxWbwCZJTVhPc8dw0TCKzCEu+tKafT48u4BLIB34a0A/g==}
     engines: {node: ^18.19.1 || ^20.11.1 || >=22.0.0}
     peerDependencies:
@@ -914,8 +919,12 @@ packages:
       rxjs: ^6.5.3 || ^7.4.0
     dependencies:
       '@angular/core': 19.2.0(rxjs@6.6.7)(zone.js@0.15.0)
+      '@nginfra/angular-linking': 1.0.0(@angular/compiler-cli@19.2.0)
       rxjs: 6.6.7
       tslib: 2.8.1
+    transitivePeerDependencies:
+      - '@angular/compiler-cli'
+      - supports-color
 
   /@angular/compiler-cli@19.2.0(@angular/compiler@19.2.0)(typescript@5.8.2):
     resolution: {integrity: sha512-IFl3LNfFanspS4gHjn207TPuoJGGieuC9r+j3nDitUcFH49fbShYLGCB6xczvK+j68ZWCqv4voxAOmLyfA/Opw==}
@@ -937,7 +946,6 @@ packages:
       yargs: 17.7.2
     transitivePeerDependencies:
       - supports-color
-    dev: true
 
   /@angular/compiler@19.2.0(@angular/core@19.2.0):
     resolution: {integrity: sha512-xGBD0C9ikH4jVDuQU3XzGqbh9Wovl8UR0wNzNd9rm4fltfC9ipz9NbfetsLPKWpPbfnUqmqMe4/pYjGEgWMonw==}
@@ -974,7 +982,7 @@ packages:
       tslib: 2.8.1
       zone.js: 0.15.0
 
-  /@angular/forms@19.2.0(@angular/common@19.2.0)(@angular/core@19.2.0)(@angular/platform-browser@19.2.0)(rxjs@6.6.7):
+  /@angular/forms@19.2.0(@angular/common@19.2.0)(@angular/compiler-cli@19.2.0)(@angular/core@19.2.0)(@angular/platform-browser@19.2.0)(rxjs@6.6.7):
     resolution: {integrity: sha512-/GHQgiDPUr1vMXCB1O8c+O70DcoZykDBzOICCaz3kTu46rp48g6E6iaZVJoozI0iBwB8+rnuTPQnLWJ46w+wVg==}
     engines: {node: ^18.19.1 || ^20.11.1 || >=22.0.0}
     peerDependencies:
@@ -983,11 +991,15 @@ packages:
       '@angular/platform-browser': 19.2.0
       rxjs: ^6.5.3 || ^7.4.0
     dependencies:
-      '@angular/common': 19.2.0(@angular/core@19.2.0)(rxjs@6.6.7)
+      '@angular/common': 19.2.0(@angular/compiler-cli@19.2.0)(@angular/core@19.2.0)(rxjs@6.6.7)
       '@angular/core': 19.2.0(rxjs@6.6.7)(zone.js@0.15.0)
-      '@angular/platform-browser': 19.2.0(@angular/animations@19.2.0)(@angular/common@19.2.0)(@angular/core@19.2.0)
+      '@angular/platform-browser': 19.2.0(@angular/animations@19.2.0)(@angular/common@19.2.0)(@angular/compiler-cli@19.2.0)(@angular/core@19.2.0)
+      '@nginfra/angular-linking': 1.0.0(@angular/compiler-cli@19.2.0)
       rxjs: 6.6.7
       tslib: 2.8.1
+    transitivePeerDependencies:
+      - '@angular/compiler-cli'
+      - supports-color
     dev: false
 
   /@angular/localize@19.2.0(@angular/compiler-cli@19.2.0)(@angular/compiler@19.2.0):
@@ -1001,6 +1013,7 @@ packages:
       '@angular/compiler': 19.2.0(@angular/core@19.2.0)
       '@angular/compiler-cli': 19.2.0(@angular/compiler@19.2.0)(typescript@5.8.2)
       '@babel/core': 7.26.9
+      '@nginfra/angular-linking': 1.0.0(@angular/compiler-cli@19.2.0)
       '@types/babel__core': 7.20.5
       fast-glob: 3.3.3
       yargs: 17.7.2
@@ -1017,14 +1030,14 @@ packages:
       '@angular/core': 19.2.0
       '@angular/platform-browser': 19.2.0
     dependencies:
-      '@angular/common': 19.2.0(@angular/core@19.2.0)(rxjs@6.6.7)
+      '@angular/common': 19.2.0(@angular/compiler-cli@19.2.0)(@angular/core@19.2.0)(rxjs@6.6.7)
       '@angular/compiler': 19.2.0(@angular/core@19.2.0)
       '@angular/core': 19.2.0(rxjs@6.6.7)(zone.js@0.15.0)
-      '@angular/platform-browser': 19.2.0(@angular/animations@19.2.0)(@angular/common@19.2.0)(@angular/core@19.2.0)
+      '@angular/platform-browser': 19.2.0(@angular/animations@19.2.0)(@angular/common@19.2.0)(@angular/compiler-cli@19.2.0)(@angular/core@19.2.0)
       tslib: 2.8.1
     dev: true
 
-  /@angular/platform-browser@19.2.0(@angular/animations@19.2.0)(@angular/common@19.2.0)(@angular/core@19.2.0):
+  /@angular/platform-browser@19.2.0(@angular/animations@19.2.0)(@angular/common@19.2.0)(@angular/compiler-cli@19.2.0)(@angular/core@19.2.0):
     resolution: {integrity: sha512-rt3byGZWU0jF6QCLxjP+LH94uL0VM5LgtJ+tYclJqCNB1C3fZrpa86GVd9onVbZmDk0ETUOwm7dQHYdef8oiqw==}
     engines: {node: ^18.19.1 || ^20.11.1 || >=22.0.0}
     peerDependencies:
@@ -1035,10 +1048,14 @@ packages:
       '@angular/animations':
         optional: true
     dependencies:
-      '@angular/animations': 19.2.0(@angular/core@19.2.0)
-      '@angular/common': 19.2.0(@angular/core@19.2.0)(rxjs@6.6.7)
+      '@angular/animations': 19.2.0(@angular/compiler-cli@19.2.0)(@angular/core@19.2.0)
+      '@angular/common': 19.2.0(@angular/compiler-cli@19.2.0)(@angular/core@19.2.0)(rxjs@6.6.7)
       '@angular/core': 19.2.0(rxjs@6.6.7)(zone.js@0.15.0)
+      '@nginfra/angular-linking': 1.0.0(@angular/compiler-cli@19.2.0)
       tslib: 2.8.1
+    transitivePeerDependencies:
+      - '@angular/compiler-cli'
+      - supports-color
 
   /@angular/platform-server@19.2.0(@angular/common@19.2.0)(@angular/compiler@19.2.0)(@angular/core@19.2.0)(@angular/platform-browser@19.2.0):
     resolution: {integrity: sha512-640hy3aWduYYdxoII71SDQtN5omvZIiWB7K+e0LbhQyQ9WWKCWEnWOneyRPZnFle8j8hoEnxKgbJLVZxBbnXoA==}
@@ -1049,15 +1066,15 @@ packages:
       '@angular/core': 19.2.0
       '@angular/platform-browser': 19.2.0
     dependencies:
-      '@angular/common': 19.2.0(@angular/core@19.2.0)(rxjs@6.6.7)
+      '@angular/common': 19.2.0(@angular/compiler-cli@19.2.0)(@angular/core@19.2.0)(rxjs@6.6.7)
       '@angular/compiler': 19.2.0(@angular/core@19.2.0)
       '@angular/core': 19.2.0(rxjs@6.6.7)(zone.js@0.15.0)
-      '@angular/platform-browser': 19.2.0(@angular/animations@19.2.0)(@angular/common@19.2.0)(@angular/core@19.2.0)
+      '@angular/platform-browser': 19.2.0(@angular/animations@19.2.0)(@angular/common@19.2.0)(@angular/compiler-cli@19.2.0)(@angular/core@19.2.0)
       tslib: 2.8.1
       xhr2: 0.2.1
     dev: true
 
-  /@angular/router@19.2.0(@angular/common@19.2.0)(@angular/core@19.2.0)(@angular/platform-browser@19.2.0)(rxjs@6.6.7):
+  /@angular/router@19.2.0(@angular/common@19.2.0)(@angular/compiler-cli@19.2.0)(@angular/core@19.2.0)(@angular/platform-browser@19.2.0)(rxjs@6.6.7):
     resolution: {integrity: sha512-Md/zleBpWMi5H6KPMREM0M2EUAkoqe01zkXla0Z0hHoTn7Ty0fv0Te9bGDioVOG7JgHh6wYCrPJ/uJsjKObyvw==}
     engines: {node: ^18.19.1 || ^20.11.1 || >=22.0.0}
     peerDependencies:
@@ -1066,11 +1083,15 @@ packages:
       '@angular/platform-browser': 19.2.0
       rxjs: ^6.5.3 || ^7.4.0
     dependencies:
-      '@angular/common': 19.2.0(@angular/core@19.2.0)(rxjs@6.6.7)
+      '@angular/common': 19.2.0(@angular/compiler-cli@19.2.0)(@angular/core@19.2.0)(rxjs@6.6.7)
       '@angular/core': 19.2.0(rxjs@6.6.7)(zone.js@0.15.0)
-      '@angular/platform-browser': 19.2.0(@angular/animations@19.2.0)(@angular/common@19.2.0)(@angular/core@19.2.0)
+      '@angular/platform-browser': 19.2.0(@angular/animations@19.2.0)(@angular/common@19.2.0)(@angular/compiler-cli@19.2.0)(@angular/core@19.2.0)
+      '@nginfra/angular-linking': 1.0.0(@angular/compiler-cli@19.2.0)
       rxjs: 6.6.7
       tslib: 2.8.1
+    transitivePeerDependencies:
+      - '@angular/compiler-cli'
+      - supports-color
     dev: true
 
   /@apidevtools/json-schema-ref-parser@9.1.2:
@@ -1093,7 +1114,28 @@ packages:
   /@babel/compat-data@7.26.8:
     resolution: {integrity: sha512-oH5UPLMWR3L2wEFLnFJ1TZXqHufiTKAiLfqw5zkhS4dKXLJ10yVztfil/twG8EDTA4F/tvVNw9nOl4ZMslB8rQ==}
     engines: {node: '>=6.9.0'}
-    dev: true
+
+  /@babel/core@7.26.10:
+    resolution: {integrity: sha512-vMqyb7XCDMPvJFFOaT9kxtiRh42GwlZEg1/uIgtZshS5a/8OaduUfCi7kynKgc3Tw/6Uo2D+db9qBttghhmxwQ==}
+    engines: {node: '>=6.9.0'}
+    dependencies:
+      '@ampproject/remapping': 2.3.0
+      '@babel/code-frame': 7.26.2
+      '@babel/generator': 7.26.10
+      '@babel/helper-compilation-targets': 7.26.5
+      '@babel/helper-module-transforms': 7.26.0(@babel/core@7.26.10)
+      '@babel/helpers': 7.26.10
+      '@babel/parser': 7.26.10
+      '@babel/template': 7.26.9
+      '@babel/traverse': 7.26.10
+      '@babel/types': 7.26.10
+      convert-source-map: 2.0.0
+      debug: 4.4.0(supports-color@10.0.0)
+      gensync: 1.0.0-beta.2
+      json5: 2.2.3
+      semver: 6.3.1
+    transitivePeerDependencies:
+      - supports-color
 
   /@babel/core@7.26.9:
     resolution: {integrity: sha512-lWBYIrF7qK5+GjY5Uy+/hEgp8OJWOD/rpy74GplYRhEauvbHDeFB8t5hPOZxCZ0Oxf4Cc36tK51/l3ymJysrKw==}
@@ -1116,7 +1158,16 @@ packages:
       semver: 6.3.1
     transitivePeerDependencies:
       - supports-color
-    dev: true
+
+  /@babel/generator@7.26.10:
+    resolution: {integrity: sha512-rRHT8siFIXQrAYOYqZQVsAr8vJ+cBNqcVAY6m5V8/4QqzaPl+zDBe6cLEPRDuNOUf3ww8RfJVlOyQMoSI+5Ang==}
+    engines: {node: '>=6.9.0'}
+    dependencies:
+      '@babel/parser': 7.26.10
+      '@babel/types': 7.26.10
+      '@jridgewell/gen-mapping': 0.3.5
+      '@jridgewell/trace-mapping': 0.3.25
+      jsesc: 3.0.2
 
   /@babel/generator@7.26.9:
     resolution: {integrity: sha512-kEWdzjOAUMW4hAyrzJ0ZaTOu9OmpyDIQicIh0zg0EEcEkYXZb2TjtBhnHi2ViX7PKwZqF4xwqfAm299/QMP3lg==}
@@ -1144,7 +1195,6 @@ packages:
       browserslist: 4.24.4
       lru-cache: 5.1.1
       semver: 6.3.1
-    dev: true
 
   /@babel/helper-create-class-features-plugin@7.25.9(@babel/core@7.26.9):
     resolution: {integrity: sha512-UTZQMvt0d/rSz6KI+qdu7GQze5TIajwTS++GUozlw8VBJDEOAqSXwm1WvmYEZwqdqSGQshRocPDqrt4HBZB3fQ==}
@@ -1223,7 +1273,19 @@ packages:
       '@babel/types': 7.26.9
     transitivePeerDependencies:
       - supports-color
-    dev: true
+
+  /@babel/helper-module-transforms@7.26.0(@babel/core@7.26.10):
+    resolution: {integrity: sha512-xO+xu6B5K2czEnQye6BHA7DolFFmS3LB7stHZFaOLb1pAwO1HWLS8fXA+eh0A2yIvltPVmx3eNNDBJA2SLHXFw==}
+    engines: {node: '>=6.9.0'}
+    peerDependencies:
+      '@babel/core': ^7.0.0
+    dependencies:
+      '@babel/core': 7.26.10
+      '@babel/helper-module-imports': 7.25.9
+      '@babel/helper-validator-identifier': 7.25.9
+      '@babel/traverse': 7.26.9
+    transitivePeerDependencies:
+      - supports-color
 
   /@babel/helper-module-transforms@7.26.0(@babel/core@7.26.9):
     resolution: {integrity: sha512-xO+xu6B5K2czEnQye6BHA7DolFFmS3LB7stHZFaOLb1pAwO1HWLS8fXA+eh0A2yIvltPVmx3eNNDBJA2SLHXFw==}
@@ -1237,7 +1299,6 @@ packages:
       '@babel/traverse': 7.26.9
     transitivePeerDependencies:
       - supports-color
-    dev: true
 
   /@babel/helper-optimise-call-expression@7.25.9:
     resolution: {integrity: sha512-FIpuNaz5ow8VyrYcnXQTDRGvV6tTjkNtCK/RYNDXGSLlUD6cBuQTSw43CShGxjvfBTfcUA/r6UhUCbtYqkhcuQ==}
@@ -1307,7 +1368,6 @@ packages:
   /@babel/helper-validator-option@7.25.9:
     resolution: {integrity: sha512-e/zv1co8pp55dNdEcCynfj9X7nyUKUXoUEwfXqaZt0omVOmDe9oOTdKStH4GmAw6zxMFs50ZayuMfHDKlO7Tfw==}
     engines: {node: '>=6.9.0'}
-    dev: true
 
   /@babel/helper-wrap-function@7.25.9:
     resolution: {integrity: sha512-ETzz9UTjQSTmw39GboatdymDq4XIQbR8ySgVrylRhPOFpsd+JrKHIuF0de7GCWmem+T4uC5z7EZguod7Wj4A4g==}
@@ -1320,13 +1380,26 @@ packages:
       - supports-color
     dev: true
 
+  /@babel/helpers@7.26.10:
+    resolution: {integrity: sha512-UPYc3SauzZ3JGgj87GgZ89JVdC5dj0AoetR5Bw6wj4niittNyFh6+eOGonYvJ1ao6B8lEa3Q3klS7ADZ53bc5g==}
+    engines: {node: '>=6.9.0'}
+    dependencies:
+      '@babel/template': 7.26.9
+      '@babel/types': 7.26.10
+
   /@babel/helpers@7.26.9:
     resolution: {integrity: sha512-Mz/4+y8udxBKdmzt/UjPACs4G3j5SshJJEFFKxlCGPydG4JAHXxjWjAwjd09tf6oINvl1VfMJo+nB7H2YKQ0dA==}
     engines: {node: '>=6.9.0'}
     dependencies:
       '@babel/template': 7.26.9
       '@babel/types': 7.26.9
-    dev: true
+
+  /@babel/parser@7.26.10:
+    resolution: {integrity: sha512-6aQR2zGE/QFi8JpDLjUZEPYOs7+mhKXm86VaKFiLP35JQwQb6bwUE+XbvkH0EptsYhbNBSUGaUBLKqxH1xSgsA==}
+    engines: {node: '>=6.0.0'}
+    hasBin: true
+    dependencies:
+      '@babel/types': 7.26.10
 
   /@babel/parser@7.26.9:
     resolution: {integrity: sha512-81NWa1njQblgZbQHxWHpxxCzNsa3ZwvFqpUg7P+NNUU6f3UU2jBEg4OlF/J6rl8+PQGh1q6/zWScd001YwcA5A==}
@@ -2152,6 +2225,20 @@ packages:
       '@babel/parser': 7.26.9
       '@babel/types': 7.26.9
 
+  /@babel/traverse@7.26.10:
+    resolution: {integrity: sha512-k8NuDrxr0WrPH5Aupqb2LCVURP/S0vBEn5mK6iH+GIYob66U5EtoZvcdudR2jQ4cmTwhEwW1DLB+Yyas9zjF6A==}
+    engines: {node: '>=6.9.0'}
+    dependencies:
+      '@babel/code-frame': 7.26.2
+      '@babel/generator': 7.26.10
+      '@babel/parser': 7.26.10
+      '@babel/template': 7.26.9
+      '@babel/types': 7.26.10
+      debug: 4.4.0(supports-color@10.0.0)
+      globals: 11.12.0
+    transitivePeerDependencies:
+      - supports-color
+
   /@babel/traverse@7.26.9:
     resolution: {integrity: sha512-ZYW7L+pL8ahU5fXmNbPF+iZFHCv5scFak7MZ9bwaRPLUhHh7QQEMjZUg0HevihoqCM5iSYHN61EyCoZvqC+bxg==}
     engines: {node: '>=6.9.0'}
@@ -2166,6 +2253,13 @@ packages:
     transitivePeerDependencies:
       - supports-color
 
+  /@babel/types@7.26.10:
+    resolution: {integrity: sha512-emqcG3vHrpxUKTrxcblR36dcrcoRDvKmnL/dCL6ZsHaShW80qxCAcNhzQZrpeM765VzEos+xOi4s+r4IXzTwdQ==}
+    engines: {node: '>=6.9.0'}
+    dependencies:
+      '@babel/helper-string-parser': 7.25.9
+      '@babel/helper-validator-identifier': 7.25.9
+
   /@babel/types@7.26.9:
     resolution: {integrity: sha512-Y3IR1cRnOxOCDvMmNiym7XpXQ93iGDDPHx+Zj+NM+rg0fBaShfQLkg+hKPaZCEvg5N/LeCo4+Rj/i3FuJsIQaw==}
     engines: {node: '>=6.9.0'}
@@ -3962,6 +4056,17 @@ packages:
     dev: true
     optional: true
 
+  /@nginfra/angular-linking@1.0.0(@angular/compiler-cli@19.2.0):
+    resolution: {integrity: sha512-i4Wveg0UrCkdE9OPbfa8N7lsfzzF2hfaxlg8gTTIotOOek52lFDsSiLpj6wmQF0+dUmAtr+INqAaH2gzdnJZKg==}
+    peerDependencies:
+      '@angular/compiler-cli': '*'
+    dependencies:
+      '@angular/compiler-cli': 19.2.0(@angular/compiler@19.2.0)(typescript@5.8.2)
+      '@babel/core': 7.26.10
+      tinyglobby: 0.2.12
+    transitivePeerDependencies:
+      - supports-color
+
   /@ngtools/webpack@19.2.0(@angular/compiler-cli@19.2.0)(typescript@5.8.2)(webpack@5.98.0):
     resolution: {integrity: sha512-63/8ys3bNK2h7Py/dKHZ4ZClxQz6xuz3skUgLZIMs9O076KPsHTKDKEDG2oicmwe/nOXjVt6n9Z4wprFaRLbvw==}
     engines: {node: ^18.19.1 || ^20.11.1 || >=22.0.0, npm: ^6.11.0 || ^7.5.6 || >=8.0.0, yarn: '>= 1.13.0'}
@@ -6620,7 +6725,6 @@ packages:
       electron-to-chromium: 1.5.102
       node-releases: 2.0.19
       update-browserslist-db: 1.1.2(browserslist@4.24.4)
-    dev: true
 
   /browserstack-local@1.5.5:
     resolution: {integrity: sha512-jKne7yosrMcptj3hqxp36TP9k0ZW2sCqhyurX24rUL4G3eT7OLgv+CSQN8iq5dtkv5IK+g+v8fWvsiC/S9KxMg==}
@@ -6838,7 +6942,6 @@ packages:
 
   /caniuse-lite@1.0.30001700:
     resolution: {integrity: sha512-2S6XIXwaE7K7erT8dY+kLQcpa5ms63XlRkMkReXjle+kf6c5g38vyMl+Z5y8dSxOFDhcFe+nxnn261PLxBSQsQ==}
-    dev: true
 
   /canonical-path@0.0.2:
     resolution: {integrity: sha512-y8EIEvL+IW81S4hRQWCRFtly+g1cc1G+wxHpjhYR9jI2+JJjWiaKnkH8mmvNHOMOAd9fzgARDO3AEzjuR51qaA==}
@@ -6980,7 +7083,6 @@ packages:
     engines: {node: '>= 14.16.0'}
     dependencies:
       readdirp: 4.0.2
-    dev: true
 
   /chownr@1.1.4:
     resolution: {integrity: sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg==}
@@ -7344,11 +7446,9 @@ packages:
 
   /convert-source-map@1.9.0:
     resolution: {integrity: sha512-ASFBup0Mz1uyiIjANan1jzLQami9z1PoYSZCiiYW2FczPbenXc45FZdBZLzOT+r6+iciuEModtmCti+hjaAk0A==}
-    dev: true
 
   /convert-source-map@2.0.0:
     resolution: {integrity: sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==}
-    dev: true
 
   /cookie-signature@1.0.6:
     resolution: {integrity: sha512-QADzlaHc8icV8I7vbaJXJwod9HWYp8uCqf1xa4OfNu1T7JVxQIrUgOWtHdNDtPiywmFbiS12VjotIXLrKM3orQ==}
@@ -8170,7 +8270,6 @@ packages:
 
   /electron-to-chromium@1.5.102:
     resolution: {integrity: sha512-eHhqaja8tE/FNpIiBrvBjFV/SSKpyWHLvxuR9dPTdo+3V9ppdLmFB7ZZQ98qNovcngPLYIz0oOBF9P0FfZef5Q==}
-    dev: true
 
   /emoji-regex@8.0.0:
     resolution: {integrity: sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==}
@@ -8910,7 +9009,6 @@ packages:
         optional: true
     dependencies:
       picomatch: 4.0.2
-    dev: true
 
   /fecha@4.2.3:
     resolution: {integrity: sha512-OP2IUU6HeYKJi3i0z4A19kHMQoLVs4Hc+DPqqxI2h/DPZHTm/vjsfC6P0b4jCMy14XizLBqvndQ+UilD7707Jw==}
@@ -9439,7 +9537,6 @@ packages:
   /gensync@1.0.0-beta.2:
     resolution: {integrity: sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==}
     engines: {node: '>=6.9.0'}
-    dev: true
 
   /get-amd-module-type@3.0.2:
     resolution: {integrity: sha512-PcuKwB8ouJnKuAPn6Hk3UtdfKoUV3zXRqVEvj8XGIXqjWfgd1j7QGdXy5Z9OdQfzVt1Sk29HVe/P+X74ccOuqw==}
@@ -11012,7 +11109,6 @@ packages:
     resolution: {integrity: sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==}
     engines: {node: '>=6'}
     hasBin: true
-    dev: true
 
   /jsonc-parser@3.3.1:
     resolution: {integrity: sha512-HUgH65KyejrUFPvHFPbqOY0rsFip3Bo5wb4ngvdi1EpCYWUQDC5V+Y7mZws+DLkr4M//zQJoanu1SP+87Dv1oQ==}
@@ -11667,7 +11763,6 @@ packages:
     resolution: {integrity: sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==}
     dependencies:
       yallist: 3.1.1
-    dev: true
 
   /lru-cache@6.0.0:
     resolution: {integrity: sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==}
@@ -12375,7 +12470,6 @@ packages:
 
   /node-releases@2.0.19:
     resolution: {integrity: sha512-xxOWJsBKtzAq7DY0J+DTzuz58K8e7sJbdgwkbMWQe8UYB6ekmsQ45q0M/tJDsGaZmbC+l7n57UV8Hl5tHxO9uw==}
-    dev: true
 
   /node-source-walk@4.3.0:
     resolution: {integrity: sha512-8Q1hXew6ETzqKRAs3jjLioSxNfT1cx74ooiF8RlAONwVMcfq+UdzLC2eB5qcPldUxaE5w3ytLkrmV1TGddhZTA==}
@@ -13059,7 +13153,6 @@ packages:
   /picomatch@4.0.2:
     resolution: {integrity: sha512-M7BAV6Rlcy5u+m6oPhAPFgJTzAioX/6B0DxyvDlo9l8+T3nLKbrczg2WLUyzd45L8RqfUMyGPzekbMvX2Ldkwg==}
     engines: {node: '>=12'}
-    dev: true
 
   /pify@2.3.0:
     resolution: {integrity: sha512-udgsAY+fTnvv7kI7aaxbqwWNb0AHiB0qBO89PZKPkoTmGOgdbrHDKD+0B2X4uTfJ/FT1R09r9gTsjUjNJotuog==}
@@ -13750,7 +13843,6 @@ packages:
   /readdirp@4.0.2:
     resolution: {integrity: sha512-yDMz9g+VaZkqBYS/ozoBJwaBhTbZo3UNYQHNRw1D3UFQB8oHB4uS/tAODO+ZLjGWmUbKnIlOWO+aaIiAxrUWHA==}
     engines: {node: '>= 14.16.0'}
-    dev: true
 
   /rechoir@0.6.2:
     resolution: {integrity: sha512-HFM8rkZ+i3zrV+4LQjwQ0W+ez98pApMGM3HUrN04j3CqzPOzl9nmP15Y8YXNm8QHGv/eacOVEjqhmWpkRV0NAw==}
@@ -13779,7 +13871,6 @@ packages:
 
   /reflect-metadata@0.2.2:
     resolution: {integrity: sha512-urBwgfrvVP/eAyXx4hluJivBKzuEbSQs9rKWCrCkbSxNv8mxPcUZKeuoF3Uy4mJl3Lwprp6yy5/39VWigZ4K6Q==}
-    dev: true
 
   /regenerate-unicode-properties@10.2.0:
     resolution: {integrity: sha512-DqHn3DwbmmPVzeKj9woBadqmXxLvQoQIwu7nopMc72ztvxVmVk2SBhSnx67zuye5TP+lJsb/TBQsjLKhnDf3MA==}
@@ -14426,7 +14517,6 @@ packages:
   /semver@6.3.1:
     resolution: {integrity: sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==}
     hasBin: true
-    dev: true
 
   /semver@7.5.4:
     resolution: {integrity: sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==}
@@ -15631,7 +15721,6 @@ packages:
     dependencies:
       fdir: 6.4.3(picomatch@4.0.2)
       picomatch: 4.0.2
-    dev: true
 
   /title-case@2.1.1:
     resolution: {integrity: sha512-EkJoZ2O3zdCz3zJsYCsxyq2OC5hrxR9mfdd5I+w8h/tmFfeOxJ+vvkxsKxdmN0WtS9zLdHEgfgVOiMVgv+Po4Q==}
@@ -16126,7 +16215,6 @@ packages:
       browserslist: 4.24.4
       escalade: 3.2.0
       picocolors: 1.1.1
-    dev: true
 
   /update-notifier@4.1.3:
     resolution: {integrity: sha512-Yld6Z0RyCYGB6ckIjffGOSOmHXj1gMeE7aROz4MG+XMkmixBX4jUngrGXNYz7wPKBmtoD4MnBa2Anu7RSKht/A==}
@@ -16952,7 +17040,6 @@ packages:
 
   /yallist@3.1.1:
     resolution: {integrity: sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==}
-    dev: true
 
   /yallist@4.0.0:
     resolution: {integrity: sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==}
diff --git a/src/cdk/testing/BUILD.bazel b/src/cdk/testing/BUILD.bazel
index 5399dd4d2bcd..63312ed538aa 100644
--- a/src/cdk/testing/BUILD.bazel
+++ b/src/cdk/testing/BUILD.bazel
@@ -1,10 +1,11 @@
 load("//src/e2e-app:test_suite.bzl", "e2e_test_suite")
-load("//tools:defaults.bzl", "markdown_to_html", "ng_web_test_suite", "ts_library")
+load("//tools:defaults.bzl", "markdown_to_html", "ng_web_test_suite")
 load("//src/cdk/testing/tests:webdriver-test.bzl", "webdriver_test")
+load("//tools:defaults2.bzl", "ts_project")
 
 package(default_visibility = ["//visibility:public"])
 
-ts_library(
+ts_project(
     name = "testing",
     srcs = glob(
         ["**/*.ts"],
@@ -13,7 +14,7 @@ ts_library(
         ],
     ),
     deps = [
-        "@npm//rxjs",
+        "//:node_modules/rxjs",
     ],
 )
 
diff --git a/src/material-experimental/BUILD.bazel b/src/material-experimental/BUILD.bazel
index b960b450fd85..70a726729d7f 100644
--- a/src/material-experimental/BUILD.bazel
+++ b/src/material-experimental/BUILD.bazel
@@ -4,17 +4,18 @@ load(
     "MATERIAL_EXPERIMENTAL_TARGETS",
     "MATERIAL_EXPERIMENTAL_TESTING_TARGETS",
 )
-load("//tools:defaults.bzl", "ng_package", "sass_library", "ts_library")
+load("//tools:defaults.bzl", "ng_package", "sass_library")
+load("//tools:defaults2.bzl", "ts_project")
 
 package(default_visibility = ["//visibility:public"])
 
-ts_library(
+ts_project(
     name = "material-experimental",
     srcs = glob(
         ["*.ts"],
         exclude = ["**/*.spec.ts"],
     ),
-    deps = ["@npm//@angular/core"],
+    deps = ["//:node_modules/@angular/core"],
 )
 
 sass_library(
diff --git a/src/material/bottom-sheet/testing/BUILD.bazel b/src/material/bottom-sheet/testing/BUILD.bazel
index d733cf41d9f6..01f422e44ac6 100644
--- a/src/material/bottom-sheet/testing/BUILD.bazel
+++ b/src/material/bottom-sheet/testing/BUILD.bazel
@@ -1,14 +1,15 @@
-load("//tools:defaults.bzl", "ng_test_library", "ng_web_test_suite", "ts_library")
+load("//tools:defaults.bzl", "ng_test_library", "ng_web_test_suite")
+load("//tools:defaults2.bzl", "ts_project")
 
 package(default_visibility = ["//visibility:public"])
 
-ts_library(
+ts_project(
     name = "testing",
     srcs = glob(
         ["**/*.ts"],
         exclude = ["**/*.spec.ts"],
     ),
-    deps = [
+    interop_deps = [
         "//src/cdk/testing",
         "//src/material/bottom-sheet",
     ],
diff --git a/src/material/button-toggle/testing/BUILD.bazel b/src/material/button-toggle/testing/BUILD.bazel
index 1417959d0433..1473c3997105 100644
--- a/src/material/button-toggle/testing/BUILD.bazel
+++ b/src/material/button-toggle/testing/BUILD.bazel
@@ -1,14 +1,15 @@
-load("//tools:defaults.bzl", "ng_test_library", "ng_web_test_suite", "ts_library")
+load("//tools:defaults.bzl", "ng_test_library", "ng_web_test_suite")
+load("//tools:defaults2.bzl", "ts_project")
 
 package(default_visibility = ["//visibility:public"])
 
-ts_library(
+ts_project(
     name = "testing",
     srcs = glob(
         ["**/*.ts"],
         exclude = ["**/*.spec.ts"],
     ),
-    deps = [
+    interop_deps = [
         "//src/cdk/coercion",
         "//src/cdk/testing",
         "//src/material/button-toggle",
diff --git a/src/material/button/testing/BUILD.bazel b/src/material/button/testing/BUILD.bazel
index 6fc418595b11..6fc3605f8f4e 100644
--- a/src/material/button/testing/BUILD.bazel
+++ b/src/material/button/testing/BUILD.bazel
@@ -1,16 +1,17 @@
-load("//tools:defaults.bzl", "ng_test_library", "ng_web_test_suite", "ts_library")
+load("//tools:defaults.bzl", "ng_test_library", "ng_web_test_suite")
+load("//tools:defaults2.bzl", "ts_project")
 
 package(default_visibility = ["//visibility:public"])
 
-ts_library(
+ts_project(
     name = "testing",
     srcs = glob(
         ["**/*.ts"],
         exclude = ["**/*.spec.ts"],
     ),
     deps = [
-        "//src/cdk/testing",
-        "@npm//@angular/core",
+        "//:node_modules/@angular/core",
+        "//src/cdk/testing:testing_rjs",
     ],
 )
 
diff --git a/src/material/card/testing/BUILD.bazel b/src/material/card/testing/BUILD.bazel
index 3b402109b2f0..a08afd1538cc 100644
--- a/src/material/card/testing/BUILD.bazel
+++ b/src/material/card/testing/BUILD.bazel
@@ -1,15 +1,16 @@
-load("//tools:defaults.bzl", "ng_test_library", "ng_web_test_suite", "ts_library")
+load("//tools:defaults.bzl", "ng_test_library", "ng_web_test_suite")
+load("//tools:defaults2.bzl", "ts_project")
 
 package(default_visibility = ["//visibility:public"])
 
-ts_library(
+ts_project(
     name = "testing",
     srcs = glob(
         ["**/*.ts"],
         exclude = ["**/*.spec.ts"],
     ),
     deps = [
-        "//src/cdk/testing",
+        "//src/cdk/testing:testing_rjs",
     ],
 )
 
diff --git a/src/material/checkbox/testing/BUILD.bazel b/src/material/checkbox/testing/BUILD.bazel
index c0319d997f4e..a9ce7fdc0811 100644
--- a/src/material/checkbox/testing/BUILD.bazel
+++ b/src/material/checkbox/testing/BUILD.bazel
@@ -1,14 +1,15 @@
-load("//tools:defaults.bzl", "ng_test_library", "ng_web_test_suite", "ts_library")
+load("//tools:defaults.bzl", "ng_test_library", "ng_web_test_suite")
+load("//tools:defaults2.bzl", "ts_project")
 
 package(default_visibility = ["//visibility:public"])
 
-ts_library(
+ts_project(
     name = "testing",
     srcs = glob(
         ["**/*.ts"],
         exclude = ["**/*.spec.ts"],
     ),
-    deps = [
+    interop_deps = [
         "//src/cdk/coercion",
         "//src/cdk/testing",
     ],
diff --git a/src/material/chips/testing/BUILD.bazel b/src/material/chips/testing/BUILD.bazel
index e6540ffcbbb3..4f2af8c82f58 100644
--- a/src/material/chips/testing/BUILD.bazel
+++ b/src/material/chips/testing/BUILD.bazel
@@ -1,15 +1,16 @@
-load("//tools:defaults.bzl", "ng_test_library", "ng_web_test_suite", "ts_library")
+load("//tools:defaults.bzl", "ng_test_library", "ng_web_test_suite")
+load("//tools:defaults2.bzl", "ts_project")
 
 package(default_visibility = ["//visibility:public"])
 
-ts_library(
+ts_project(
     name = "testing",
     srcs = glob(
         ["**/*.ts"],
         exclude = ["**/*.spec.ts"],
     ),
     deps = [
-        "//src/cdk/testing",
+        "//src/cdk/testing:testing_rjs",
     ],
 )
 
diff --git a/src/material/core/testing/BUILD.bazel b/src/material/core/testing/BUILD.bazel
index a4fadc6aa3a8..620b7401ab4d 100644
--- a/src/material/core/testing/BUILD.bazel
+++ b/src/material/core/testing/BUILD.bazel
@@ -1,15 +1,16 @@
-load("//tools:defaults.bzl", "ng_test_library", "ng_web_test_suite", "ts_library")
+load("//tools:defaults.bzl", "ng_test_library", "ng_web_test_suite")
+load("//tools:defaults2.bzl", "ts_project")
 
 package(default_visibility = ["//visibility:public"])
 
-ts_library(
+ts_project(
     name = "testing",
     srcs = glob(
         ["**/*.ts"],
         exclude = ["**/*.spec.ts"],
     ),
     deps = [
-        "//src/cdk/testing",
+        "//src/cdk/testing:testing_rjs",
     ],
 )
 
diff --git a/src/material/datepicker/testing/BUILD.bazel b/src/material/datepicker/testing/BUILD.bazel
index 9b3b5fd0f903..cb6e2924af53 100644
--- a/src/material/datepicker/testing/BUILD.bazel
+++ b/src/material/datepicker/testing/BUILD.bazel
@@ -1,14 +1,15 @@
-load("//tools:defaults.bzl", "ng_test_library", "ng_web_test_suite", "ts_library")
+load("//tools:defaults.bzl", "ng_test_library", "ng_web_test_suite")
+load("//tools:defaults2.bzl", "ts_project")
 
 package(default_visibility = ["//visibility:public"])
 
-ts_library(
+ts_project(
     name = "testing",
     srcs = glob(
         ["**/*.ts"],
         exclude = ["**/*.spec.ts"],
     ),
-    deps = [
+    interop_deps = [
         "//src/cdk/coercion",
         "//src/cdk/testing",
         "//src/material/form-field/testing/control",
diff --git a/src/material/menu/testing/BUILD.bazel b/src/material/menu/testing/BUILD.bazel
index 9e14545e5dcd..931be02f4ce0 100644
--- a/src/material/menu/testing/BUILD.bazel
+++ b/src/material/menu/testing/BUILD.bazel
@@ -1,14 +1,15 @@
-load("//tools:defaults.bzl", "ng_test_library", "ng_web_test_suite", "ts_library")
+load("//tools:defaults.bzl", "ng_test_library", "ng_web_test_suite")
+load("//tools:defaults2.bzl", "ts_project")
 
 package(default_visibility = ["//visibility:public"])
 
-ts_library(
+ts_project(
     name = "testing",
     srcs = glob(
         ["**/*.ts"],
         exclude = ["**/*.spec.ts"],
     ),
-    deps = [
+    interop_deps = [
         "//src/cdk/coercion",
         "//src/cdk/testing",
     ],
diff --git a/src/material/paginator/testing/BUILD.bazel b/src/material/paginator/testing/BUILD.bazel
index 02cf24b62d46..a09338d9689f 100644
--- a/src/material/paginator/testing/BUILD.bazel
+++ b/src/material/paginator/testing/BUILD.bazel
@@ -1,14 +1,15 @@
-load("//tools:defaults.bzl", "ng_test_library", "ng_web_test_suite", "ts_library")
+load("//tools:defaults.bzl", "ng_test_library", "ng_web_test_suite")
+load("//tools:defaults2.bzl", "ts_project")
 
 package(default_visibility = ["//visibility:public"])
 
-ts_library(
+ts_project(
     name = "testing",
     srcs = glob(
         ["**/*.ts"],
         exclude = ["**/*.spec.ts"],
     ),
-    deps = [
+    interop_deps = [
         "//src/cdk/coercion",
         "//src/cdk/testing",
         "//src/material/select/testing",
diff --git a/src/material/radio/testing/BUILD.bazel b/src/material/radio/testing/BUILD.bazel
index 858d2a668970..491e599078eb 100644
--- a/src/material/radio/testing/BUILD.bazel
+++ b/src/material/radio/testing/BUILD.bazel
@@ -1,14 +1,15 @@
-load("//tools:defaults.bzl", "ng_test_library", "ng_web_test_suite", "ts_library")
+load("//tools:defaults.bzl", "ng_test_library", "ng_web_test_suite")
+load("//tools:defaults2.bzl", "ts_project")
 
 package(default_visibility = ["//visibility:public"])
 
-ts_library(
+ts_project(
     name = "testing",
     srcs = glob(
         ["**/*.ts"],
         exclude = ["**/*.spec.ts"],
     ),
-    deps = [
+    interop_deps = [
         "//src/cdk/coercion",
         "//src/cdk/testing",
     ],
diff --git a/src/material/select/testing/BUILD.bazel b/src/material/select/testing/BUILD.bazel
index 9d64a0aa81c4..30c5ce2ec513 100644
--- a/src/material/select/testing/BUILD.bazel
+++ b/src/material/select/testing/BUILD.bazel
@@ -1,14 +1,15 @@
-load("//tools:defaults.bzl", "ng_test_library", "ng_web_test_suite", "ts_library")
+load("//tools:defaults.bzl", "ng_test_library", "ng_web_test_suite")
+load("//tools:defaults2.bzl", "ts_project")
 
 package(default_visibility = ["//visibility:public"])
 
-ts_library(
+ts_project(
     name = "testing",
     srcs = glob(
         ["**/*.ts"],
         exclude = ["**/*.spec.ts"],
     ),
-    deps = [
+    interop_deps = [
         "//src/cdk/testing",
         "//src/material/core/testing",
         "//src/material/form-field/testing/control",
diff --git a/src/material/sidenav/testing/BUILD.bazel b/src/material/sidenav/testing/BUILD.bazel
index 21b9f71126ff..a8ca460c2061 100644
--- a/src/material/sidenav/testing/BUILD.bazel
+++ b/src/material/sidenav/testing/BUILD.bazel
@@ -1,14 +1,15 @@
-load("//tools:defaults.bzl", "ng_test_library", "ng_web_test_suite", "ts_library")
+load("//tools:defaults.bzl", "ng_test_library", "ng_web_test_suite")
+load("//tools:defaults2.bzl", "ts_project")
 
 package(default_visibility = ["//visibility:public"])
 
-ts_library(
+ts_project(
     name = "testing",
     srcs = glob(
         ["**/*.ts"],
         exclude = ["**/*.spec.ts"],
     ),
-    deps = [
+    interop_deps = [
         "//src/cdk/coercion",
         "//src/cdk/testing",
     ],
diff --git a/src/material/slide-toggle/testing/BUILD.bazel b/src/material/slide-toggle/testing/BUILD.bazel
index 845d6bb060ab..bcd4b02c4454 100644
--- a/src/material/slide-toggle/testing/BUILD.bazel
+++ b/src/material/slide-toggle/testing/BUILD.bazel
@@ -1,4 +1,5 @@
-load("//tools:defaults.bzl", "ng_test_library", "ng_web_test_suite", "ts_library")
+load("//tools:defaults.bzl", "ng_test_library", "ng_web_test_suite")
+load("//tools:defaults2.bzl", "ts_project")
 
 package(default_visibility = ["//visibility:public"])
 
@@ -7,15 +8,17 @@ filegroup(
     srcs = glob(["**/*.ts"]),
 )
 
-ts_library(
+ts_project(
     name = "testing",
     srcs = glob(
         ["**/*.ts"],
         exclude = ["**/*.spec.ts"],
     ),
-    deps = [
+    interop_deps = [
         "//src/cdk/coercion",
-        "//src/cdk/testing",
+    ],
+    deps = [
+        "//src/cdk/testing:testing_rjs",
     ],
 )
 
diff --git a/src/material/slider/testing/BUILD.bazel b/src/material/slider/testing/BUILD.bazel
index 358bf5846b6e..2eeddfde10d7 100644
--- a/src/material/slider/testing/BUILD.bazel
+++ b/src/material/slider/testing/BUILD.bazel
@@ -1,18 +1,21 @@
-load("//tools:defaults.bzl", "ng_test_library", "ng_web_test_suite", "ts_library")
+load("//tools:defaults.bzl", "ng_test_library", "ng_web_test_suite")
+load("//tools:defaults2.bzl", "ts_project")
 
 package(default_visibility = ["//visibility:public"])
 
-ts_library(
+ts_project(
     name = "testing",
     srcs = glob(
         ["**/*.ts"],
         exclude = ["**/*.spec.ts"],
     ),
-    deps = [
+    interop_deps = [
         "//src/cdk/coercion",
-        "//src/cdk/testing",
         "//src/material/slider",
     ],
+    deps = [
+        "//src/cdk/testing:testing_rjs",
+    ],
 )
 
 ng_test_library(
diff --git a/src/material/snack-bar/testing/BUILD.bazel b/src/material/snack-bar/testing/BUILD.bazel
index 85aeb269b87b..3462d049f2e0 100644
--- a/src/material/snack-bar/testing/BUILD.bazel
+++ b/src/material/snack-bar/testing/BUILD.bazel
@@ -1,16 +1,19 @@
-load("//tools:defaults.bzl", "ng_test_library", "ng_web_test_suite", "ts_library")
+load("//tools:defaults.bzl", "ng_test_library", "ng_web_test_suite")
+load("//tools:defaults2.bzl", "ts_project")
 
 package(default_visibility = ["//visibility:public"])
 
-ts_library(
+ts_project(
     name = "testing",
     srcs = glob(
         ["**/*.ts"],
         exclude = ["**/*.spec.ts"],
     ),
-    deps = [
+    interop_deps = [
         "//src/cdk/a11y",
-        "//src/cdk/testing",
+    ],
+    deps = [
+        "//src/cdk/testing:testing_rjs",
     ],
 )
 
diff --git a/src/material/sort/testing/BUILD.bazel b/src/material/sort/testing/BUILD.bazel
index f49a71a7072d..167b4eb69956 100644
--- a/src/material/sort/testing/BUILD.bazel
+++ b/src/material/sort/testing/BUILD.bazel
@@ -1,17 +1,20 @@
-load("//tools:defaults.bzl", "ng_test_library", "ng_web_test_suite", "ts_library")
+load("//tools:defaults.bzl", "ng_test_library", "ng_web_test_suite")
+load("//tools:defaults2.bzl", "ts_project")
 
 package(default_visibility = ["//visibility:public"])
 
-ts_library(
+ts_project(
     name = "testing",
     srcs = glob(
         ["**/*.ts"],
         exclude = ["**/*.spec.ts"],
     ),
-    deps = [
-        "//src/cdk/testing",
+    interop_deps = [
         "//src/material/sort",
     ],
+    deps = [
+        "//src/cdk/testing:testing_rjs",
+    ],
 )
 
 filegroup(
diff --git a/src/material/stepper/testing/BUILD.bazel b/src/material/stepper/testing/BUILD.bazel
index cfbec52b8fb9..28172633b4d0 100644
--- a/src/material/stepper/testing/BUILD.bazel
+++ b/src/material/stepper/testing/BUILD.bazel
@@ -1,15 +1,16 @@
-load("//tools:defaults.bzl", "ng_test_library", "ng_web_test_suite", "ts_library")
+load("//tools:defaults.bzl", "ng_test_library", "ng_web_test_suite")
+load("//tools:defaults2.bzl", "ts_project")
 
 package(default_visibility = ["//visibility:public"])
 
-ts_library(
+ts_project(
     name = "testing",
     srcs = glob(
         ["**/*.ts"],
         exclude = ["**/*.spec.ts"],
     ),
     deps = [
-        "//src/cdk/testing",
+        "//src/cdk/testing:testing_rjs",
     ],
 )
 
diff --git a/src/material/table/testing/BUILD.bazel b/src/material/table/testing/BUILD.bazel
index 6f074e5aa7d9..3ff3a06c0a10 100644
--- a/src/material/table/testing/BUILD.bazel
+++ b/src/material/table/testing/BUILD.bazel
@@ -1,15 +1,16 @@
-load("//tools:defaults.bzl", "ng_test_library", "ng_web_test_suite", "ts_library")
+load("//tools:defaults.bzl", "ng_test_library", "ng_web_test_suite")
+load("//tools:defaults2.bzl", "ts_project")
 
 package(default_visibility = ["//visibility:public"])
 
-ts_library(
+ts_project(
     name = "testing",
     srcs = glob(
         ["**/*.ts"],
         exclude = ["**/*.spec.ts"],
     ),
     deps = [
-        "//src/cdk/testing",
+        "//src/cdk/testing:testing_rjs",
     ],
 )
 
diff --git a/src/material/tabs/testing/BUILD.bazel b/src/material/tabs/testing/BUILD.bazel
index ce2555520988..0dbf07a93ce1 100644
--- a/src/material/tabs/testing/BUILD.bazel
+++ b/src/material/tabs/testing/BUILD.bazel
@@ -1,14 +1,15 @@
-load("//tools:defaults.bzl", "ng_test_library", "ng_web_test_suite", "ts_library")
+load("//tools:defaults.bzl", "ng_test_library", "ng_web_test_suite")
+load("//tools:defaults2.bzl", "ts_project")
 
 package(default_visibility = ["//visibility:public"])
 
-ts_library(
+ts_project(
     name = "testing",
     srcs = glob(
         ["**/*.ts"],
         exclude = ["**/*.spec.ts"],
     ),
-    deps = [
+    interop_deps = [
         "//src/cdk/testing",
     ],
 )
diff --git a/src/material/timepicker/testing/BUILD.bazel b/src/material/timepicker/testing/BUILD.bazel
index 200894a54557..a7ecb90a6f33 100644
--- a/src/material/timepicker/testing/BUILD.bazel
+++ b/src/material/timepicker/testing/BUILD.bazel
@@ -1,14 +1,15 @@
-load("//tools:defaults.bzl", "ng_test_library", "ng_web_test_suite", "ts_library")
+load("//tools:defaults.bzl", "ng_test_library", "ng_web_test_suite")
+load("//tools:defaults2.bzl", "ts_project")
 
 package(default_visibility = ["//visibility:public"])
 
-ts_library(
+ts_project(
     name = "testing",
     srcs = glob(
         ["**/*.ts"],
         exclude = ["**/*.spec.ts"],
     ),
-    deps = [
+    interop_deps = [
         "//src/cdk/coercion",
         "//src/cdk/testing",
         "//src/material/core/testing",
diff --git a/src/material/toolbar/testing/BUILD.bazel b/src/material/toolbar/testing/BUILD.bazel
index a09f4eda55fa..2ea7f58fe430 100644
--- a/src/material/toolbar/testing/BUILD.bazel
+++ b/src/material/toolbar/testing/BUILD.bazel
@@ -1,14 +1,15 @@
-load("//tools:defaults.bzl", "ng_test_library", "ng_web_test_suite", "ts_library")
+load("//tools:defaults.bzl", "ng_test_library", "ng_web_test_suite")
+load("//tools:defaults2.bzl", "ts_project")
 
 package(default_visibility = ["//visibility:public"])
 
-ts_library(
+ts_project(
     name = "testing",
     srcs = glob(
         ["**/*.ts"],
         exclude = ["**/*.spec.ts"],
     ),
-    deps = [
+    interop_deps = [
         "//src/cdk/testing",
     ],
 )
diff --git a/src/material/tooltip/testing/BUILD.bazel b/src/material/tooltip/testing/BUILD.bazel
index ffd6ace2b9b0..d64338e6ec9b 100644
--- a/src/material/tooltip/testing/BUILD.bazel
+++ b/src/material/tooltip/testing/BUILD.bazel
@@ -1,14 +1,15 @@
-load("//tools:defaults.bzl", "ng_test_library", "ng_web_test_suite", "ts_library")
+load("//tools:defaults.bzl", "ng_test_library", "ng_web_test_suite")
+load("//tools:defaults2.bzl", "ts_project")
 
 package(default_visibility = ["//visibility:public"])
 
-ts_library(
+ts_project(
     name = "testing",
     srcs = glob(
         ["**/*.ts"],
         exclude = ["**/*.spec.ts"],
     ),
-    deps = [
+    interop_deps = [
         "//src/cdk/testing",
     ],
 )
diff --git a/src/material/tree/testing/BUILD.bazel b/src/material/tree/testing/BUILD.bazel
index 5b3f26e183cd..9cfa9ade3dff 100644
--- a/src/material/tree/testing/BUILD.bazel
+++ b/src/material/tree/testing/BUILD.bazel
@@ -1,14 +1,15 @@
-load("//tools:defaults.bzl", "ng_test_library", "ng_web_test_suite", "ts_library")
+load("//tools:defaults.bzl", "ng_test_library", "ng_web_test_suite")
+load("//tools:defaults2.bzl", "ts_project")
 
 package(default_visibility = ["//visibility:public"])
 
-ts_library(
+ts_project(
     name = "testing",
     srcs = glob(
         ["**/*.ts"],
         exclude = ["**/*.spec.ts"],
     ),
-    deps = [
+    interop_deps = [
         "//src/cdk/coercion",
         "//src/cdk/testing",
     ],
diff --git a/src/universal-app/BUILD.bazel b/src/universal-app/BUILD.bazel
index fb5a549a7f1e..d0f060e3eb4e 100644
--- a/src/universal-app/BUILD.bazel
+++ b/src/universal-app/BUILD.bazel
@@ -5,11 +5,22 @@ load("//src/cdk-experimental:config.bzl", "CDK_EXPERIMENTAL_TARGETS")
 load("//src/components-examples:config.bzl", "ALL_EXAMPLES")
 load("//src/material:config.bzl", "MATERIAL_TARGETS")
 load("//src/material-experimental:config.bzl", "MATERIAL_EXPERIMENTAL_TARGETS")
-load("//tools:defaults.bzl", "devmode_esbuild", "http_server", "ng_e2e_test_library", "ng_module", "protractor_web_test_suite", "sass_binary", "ts_library")
+load("//tools:defaults.bzl", "devmode_esbuild", "http_server", "ng_e2e_test_library", "ng_module", "protractor_web_test_suite", "sass_binary")
 load("//tools/angular:index.bzl", "LINKER_PROCESSED_FW_PACKAGES")
+load("//tools:defaults2.bzl", "ts_project")
+load("@aspect_rules_ts//ts:defs.bzl", rules_js_tsconfig = "ts_config")
 
 package(default_visibility = ["//visibility:public"])
 
+rules_js_tsconfig(
+    name = "tsconfig",
+    src = "tsconfig.json",
+    deps = [
+        "//:node_modules/@types/node",
+        "//src:build-tsconfig",
+    ],
+)
+
 ng_module(
     name = "kitchen-sink",
     srcs = [
@@ -25,31 +36,37 @@ ng_module(
     ] + CDK_TARGETS + CDK_EXPERIMENTAL_TARGETS + MATERIAL_TARGETS + MATERIAL_EXPERIMENTAL_TARGETS + ALL_EXAMPLES,
 )
 
-ts_library(
+ts_project(
     name = "client_lib",
     srcs = [
         "main.ts",
     ],
-    deps = [
+    interop_deps = [
         ":kitchen-sink",
-        "@npm//@angular/platform-browser",
+    ],
+    tsconfig = ":tsconfig",
+    deps = [
+        "//:node_modules/@angular/platform-browser",
     ],
 )
 
-ts_library(
+ts_project(
     name = "prerender_lib",
     srcs = [
         "prerender.ts",
     ],
-    deps = [
+    interop_deps = [
         ":kitchen-sink",
-        "@npm//@angular/core",
-        "@npm//@angular/platform-browser",
-        "@npm//@angular/platform-server",
-        "@npm//@bazel/runfiles",
-        "@npm//@types/node",
-        "@npm//reflect-metadata",
-        "@npm//zone.js",
+    ],
+    tsconfig = ":tsconfig",
+    deps = [
+        "//:node_modules/@angular/core",
+        "//:node_modules/@angular/platform-browser",
+        "//:node_modules/@angular/platform-server",
+        "//:node_modules/@bazel/runfiles",
+        "//:node_modules/@types/node",
+        "//:node_modules/reflect-metadata",
+        "//:node_modules/zone.js",
     ],
 )
 
diff --git a/src/universal-app/tsconfig.json b/src/universal-app/tsconfig.json
new file mode 100644
index 000000000000..4bad659bee35
--- /dev/null
+++ b/src/universal-app/tsconfig.json
@@ -0,0 +1,6 @@
+{
+  "extends": "../bazel-tsconfig-build.json",
+  "compilerOptions": {
+    "types": ["node"]
+  }
+}
diff --git a/test/BUILD.bazel b/test/BUILD.bazel
index ebce3a500805..bb78349d4697 100644
--- a/test/BUILD.bazel
+++ b/test/BUILD.bazel
@@ -1,18 +1,25 @@
-load("//tools:defaults.bzl", "ts_library")
+load("//tools:defaults2.bzl", "ts_project")
+load("@aspect_rules_ts//ts:defs.bzl", rules_js_tsconfig = "ts_config")
 
 package(default_visibility = ["//visibility:public"])
 
 exports_files(["bazel-karma-local-config.js"])
 
+rules_js_tsconfig(
+    name = "tsconfig",
+    src = "tsconfig.json",
+    deps = ["//src:build-tsconfig"],
+)
+
 # Common set-up for all Angular Material and CDK tests.
-ts_library(
+ts_project(
     name = "angular_test_init",
     testonly = True,
     # This file *must* end with "spec" in order for "karma_web_test_suite" to load it.
     srcs = ["angular-test-init-spec.ts"],
+    tsconfig = ":tsconfig",
     deps = [
-        "@npm//@angular/core",
-        "@npm//@angular/platform-browser-dynamic",
-        "@npm//@types/jasmine",
+        "//:node_modules/@angular/core",
+        "//:node_modules/@angular/platform-browser-dynamic",
     ],
 )
diff --git a/test/tsconfig.json b/test/tsconfig.json
new file mode 100644
index 000000000000..b28601f5bb9e
--- /dev/null
+++ b/test/tsconfig.json
@@ -0,0 +1,4 @@
+{
+  // TODO(devversion): move bazel tsconfig up, or this folder into `src/`.
+  "extends": "../src/bazel-tsconfig-build.json"
+}
diff --git a/tools/BUILD.bazel b/tools/BUILD.bazel
index 15af1f7e4224..9025c1840b37 100644
--- a/tools/BUILD.bazel
+++ b/tools/BUILD.bazel
@@ -1,7 +1,23 @@
 load("@bazel_skylib//rules:common_settings.bzl", "bool_flag")
+load("@aspect_rules_ts//ts:defs.bzl", rules_js_tsconfig = "ts_config")
 
 package(default_visibility = ["//visibility:public"])
 
+rules_js_tsconfig(
+    name = "tsconfig",
+    src = "tsconfig.json",
+    deps = ["//:node_modules/@types/node"],
+)
+
+rules_js_tsconfig(
+    name = "tsconfig-test",
+    src = "tsconfig-test.json",
+    deps = [
+        ":tsconfig",
+        "//:node_modules/@types/jasmine",
+    ],
+)
+
 # Bazel config setting that matches if the partial compilation flag
 # for `@angular/bazel` is set to `True`.
 config_setting(
diff --git a/tools/bazel/ts_project_interop.bzl b/tools/bazel/ts_project_interop.bzl
index 72e4f28e64cb..0827437a1841 100644
--- a/tools/bazel/ts_project_interop.bzl
+++ b/tools/bazel/ts_project_interop.bzl
@@ -2,6 +2,7 @@ load("@aspect_rules_js//js:providers.bzl", "JsInfo", "js_info")
 load("@aspect_rules_ts//ts:defs.bzl", _ts_project = "ts_project")
 load("@build_bazel_rules_nodejs//:providers.bzl", "DeclarationInfo", "JSEcmaScriptModuleInfo", "JSModuleInfo", "LinkablePackageInfo")
 load("@devinfra//bazel/ts_project:index.bzl", "strict_deps_test")
+load("@build_bazel_rules_nodejs//internal/linker:link_node_modules.bzl", "LinkerPackageMappingInfo")
 
 def _ts_deps_interop_impl(ctx):
     types = []
@@ -81,6 +82,18 @@ def _ts_project_module_impl(ctx):
                 files = info.sources,
             ),
         )
+        providers.append(
+            LinkerPackageMappingInfo(
+                mappings = depset([
+                    struct(
+                        package_name = ctx.attr.module_name,
+                        package_path = "",
+                        link_path = ctx.label.package,
+                    ),
+                ]),
+                node_modules_roots = depset([]),
+            ),
+        )
 
     return providers
 
diff --git a/tools/dgeni/BUILD.bazel b/tools/dgeni/BUILD.bazel
index e7722cc021c8..73ae9ff7a4f9 100644
--- a/tools/dgeni/BUILD.bazel
+++ b/tools/dgeni/BUILD.bazel
@@ -1,5 +1,5 @@
 load("@build_bazel_rules_nodejs//:index.bzl", "nodejs_binary")
-load("//tools:defaults.bzl", "ts_library")
+load("//tools:defaults2.bzl", "ts_project")
 
 package(default_visibility = ["//visibility:public"])
 
@@ -16,18 +16,15 @@ nodejs_binary(
     templated_args = ["--bazel_patch_module_resolver"],
 )
 
-ts_library(
+ts_project(
     name = "sources",
     srcs = glob(["**/*.ts"]),
-    # TODO(ESM): remove this once the Bazel NodeJS rules can handle ESM with `nodejs_binary`.
-    devmode_module = "commonjs",
-    tsconfig = ":tsconfig.json",
+    tsconfig = "//tools:tsconfig",
     deps = [
-        "//tools/highlight-files:sources",
-        "@npm//@types/node",
-        "@npm//dgeni",
-        "@npm//dgeni-packages",
-        "@npm//highlight.js",
-        "@npm//typescript",
+        "//:node_modules/dgeni",
+        "//:node_modules/dgeni-packages",
+        "//:node_modules/highlight.js",
+        "//:node_modules/typescript",
+        "//tools/highlight-files:sources_rjs",
     ],
 )
diff --git a/tools/example-module/BUILD.bazel b/tools/example-module/BUILD.bazel
index 3feffafadc40..6a3c662d739b 100644
--- a/tools/example-module/BUILD.bazel
+++ b/tools/example-module/BUILD.bazel
@@ -1,17 +1,14 @@
 load("@build_bazel_rules_nodejs//:index.bzl", "nodejs_binary")
-load("//tools:defaults.bzl", "ts_library")
+load("//tools:defaults2.bzl", "ts_project")
 
 package(default_visibility = ["//visibility:public"])
 
-ts_library(
+ts_project(
     name = "example-module-lib",
     srcs = glob(["**/*.ts"]),
-    # TODO(ESM): remove this once the Bazel NodeJS rules can handle ESM with `nodejs_binary`.
-    devmode_module = "commonjs",
-    tsconfig = ":tsconfig.json",
+    tsconfig = "//tools:tsconfig",
     deps = [
-        "@npm//@types/node",
-        "@npm//typescript",
+        "//:node_modules/typescript",
     ],
 )
 
diff --git a/tools/extract-tokens/BUILD.bazel b/tools/extract-tokens/BUILD.bazel
index 1877ba74e2b3..b34e1c21b0ad 100644
--- a/tools/extract-tokens/BUILD.bazel
+++ b/tools/extract-tokens/BUILD.bazel
@@ -1,18 +1,15 @@
 load("@build_bazel_rules_nodejs//:index.bzl", "nodejs_binary")
-load("//tools:defaults.bzl", "ts_library")
+load("//tools:defaults2.bzl", "ts_project")
 
 package(default_visibility = ["//visibility:public"])
 
-ts_library(
+ts_project(
     name = "extract_tokens_lib",
     srcs = glob(["**/*.ts"]),
-    # TODO(ESM): remove this once the Bazel NodeJS rules can handle ESM with `nodejs_binary`.
-    devmode_module = "commonjs",
-    tsconfig = ":tsconfig.json",
+    tsconfig = "//tools:tsconfig",
     deps = [
-        "//tools/highlight-files:sources",
-        "@npm//@types/node",
-        "@npm//sass",
+        "//:node_modules/sass",
+        "//tools/highlight-files:sources_rjs",
     ],
 )
 
diff --git a/tools/highlight-files/BUILD.bazel b/tools/highlight-files/BUILD.bazel
index 8200dbbcf5d9..338d81b9d8fb 100644
--- a/tools/highlight-files/BUILD.bazel
+++ b/tools/highlight-files/BUILD.bazel
@@ -1,19 +1,16 @@
 load("@build_bazel_rules_nodejs//:index.bzl", "nodejs_binary")
-load("//tools:defaults.bzl", "ts_library")
+load("//tools:defaults2.bzl", "ts_project")
 
 package(default_visibility = ["//visibility:public"])
 
-ts_library(
+ts_project(
     name = "sources",
     srcs = glob(["**/*.ts"]),
-    # TODO(ESM): remove this once the Bazel NodeJS rules can handle ESM with `nodejs_binary`.
-    devmode_module = "commonjs",
-    tsconfig = ":tsconfig.json",
+    tsconfig = "//tools:tsconfig",
     deps = [
-        "//tools/region-parser",
-        "@npm//@types/fs-extra",
-        "@npm//@types/node",
-        "@npm//fs-extra",
+        "//:node_modules/@types/fs-extra",
+        "//:node_modules/fs-extra",
+        "//tools/region-parser:region-parser_rjs",
     ],
 )
 
diff --git a/tools/markdown-to-html/BUILD.bazel b/tools/markdown-to-html/BUILD.bazel
index 177002d0e6e2..1e8bb496f617 100644
--- a/tools/markdown-to-html/BUILD.bazel
+++ b/tools/markdown-to-html/BUILD.bazel
@@ -1,9 +1,10 @@
 load("@build_bazel_rules_nodejs//:index.bzl", "nodejs_binary")
-load("//tools:defaults.bzl", "jasmine_node_test", "ts_library")
+load("//tools:defaults.bzl", "jasmine_node_test")
+load("//tools:defaults2.bzl", "ts_project")
 
 package(default_visibility = ["//visibility:public"])
 
-ts_library(
+ts_project(
     name = "transform-markdown",
     srcs = glob(
         ["**/*.ts"],
@@ -11,14 +12,11 @@ ts_library(
             "*.spec.ts",
         ],
     ),
-    # TODO(ESM): remove this once the Bazel NodeJS rules can handle ESM with `nodejs_binary`.
-    devmode_module = "commonjs",
-    tsconfig = ":tsconfig.json",
+    tsconfig = "//tools:tsconfig",
     deps = [
-        "//tools/highlight-files:sources",
-        "@npm//@types/marked",
-        "@npm//@types/node",
-        "@npm//marked",
+        "//:node_modules/@types/marked",
+        "//:node_modules/marked",
+        "//tools/highlight-files:sources_rjs",
     ],
 )
 
@@ -33,16 +31,16 @@ nodejs_binary(
     templated_args = ["--bazel_patch_module_resolver"],
 )
 
-ts_library(
+ts_project(
     name = "unit_test_lib",
     testonly = True,
     srcs = glob(
         ["*.spec.ts"],
     ),
-    tsconfig = ":tsconfig.json",
+    tsconfig = "//tools:tsconfig-test",
     visibility = ["//visibility:private"],
     deps = [
-        ":transform-markdown",
+        ":transform-markdown_rjs",
         "@npm//@types/jasmine",
         "@npm//@types/node",
         "@npm//marked",
diff --git a/tools/markdown-to-html/docs-marked-renderer.spec.ts b/tools/markdown-to-html/docs-marked-renderer.spec.ts
index 2ed0b60a32bb..69b6f68fcfe9 100644
--- a/tools/markdown-to-html/docs-marked-renderer.spec.ts
+++ b/tools/markdown-to-html/docs-marked-renderer.spec.ts
@@ -7,8 +7,8 @@ describe('DocsMarkdownRenderer', () => {
   });
 
   it('generates regular headings for h1 and h2', () => {
-    expect(renderer.heading('a', 1, 'ignored')).toEqual('<h1>a</h1>');
-    expect(renderer.heading('b', 2, 'ignored')).toEqual('<h2>b</h2>');
+    expect(renderer.heading('a', 1, 'ignored')).toBe('<h1>a</h1>');
+    expect(renderer.heading('b', 2, 'ignored')).toContain('<h2 ');
   });
 
   it('creates header link for h3 and h4 headings', () => {
@@ -74,23 +74,28 @@ describe('DocsMarkdownRenderer', () => {
     const result = renderer.html(`<!-- example(
          {
           "example": "exampleName",
-          "file": "example-html.html",
+          "file": "example-html.html"
          }
         ) -->`);
     expectEqualIgnoreLeadingWhitespace(
       result,
       `<div material-docs-example="exampleName"
-          file="example-html.html"></div>`,
+          file="example-html.html"
+       ></div>`,
     );
   });
 
   it('generates html using new API with no file and no region', () => {
     const result = renderer.html(`<!-- example(
          {
-          "example": "exampleName",
+          "example": "exampleName"
          }
         ) -->`);
-    expectEqualIgnoreLeadingWhitespace(result, `<div material-docs-example="exampleName"></div>`);
+    expectEqualIgnoreLeadingWhitespace(
+      result,
+      `<div material-docs-example="exampleName"
+       ></div>`,
+    );
   });
 
   it('generates html using old API', () => {
diff --git a/tools/markdown-to-html/docs-marked-renderer.ts b/tools/markdown-to-html/docs-marked-renderer.ts
index e77c851b6d5a..1ffe0ef69a84 100644
--- a/tools/markdown-to-html/docs-marked-renderer.ts
+++ b/tools/markdown-to-html/docs-marked-renderer.ts
@@ -2,7 +2,7 @@ import {Renderer, Slugger} from 'marked';
 import {basename, extname} from 'path';
 
 /** Regular expression that matches example comments. */
-const exampleCommentRegex = /<!--\s*example\(([^)]+)\)\s*-->/g;
+const exampleCommentRegex = /<!--\s*example\(\s*([^)]+)\)\s*-->/g;
 
 /**
  * Custom renderer for marked that will be used to transform markdown files to HTML
@@ -80,7 +80,7 @@ export class DocsMarkdownRenderer extends Renderer {
     html = html.replace(exampleCommentRegex, (_match: string, content: string) => {
       // using [\s\S]* because .* does not match line breaks
       if (content.match(/\{[\s\S]*\}/g)) {
-        const {example, file, region} = JSON.parse(content) as {
+        const {example, file, region} = JSON.parse(content.trim()) as {
           example: string;
           file: string;
           region: string;
diff --git a/tools/markdown-to-html/tsconfig.json b/tools/markdown-to-html/tsconfig.json
deleted file mode 100644
index 42b8317aeb81..000000000000
--- a/tools/markdown-to-html/tsconfig.json
+++ /dev/null
@@ -1,14 +0,0 @@
-{
-  "compilerOptions": {
-    "lib": ["es2020"],
-    "module": "commonjs",
-    "target": "es2020",
-    "esModuleInterop": true,
-    "sourceMap": true,
-    "types": ["jasmine", "node"]
-  },
-  "exclude": ["*.spec.ts"],
-  "bazelOptions": {
-    "suppressTsconfigOverrideWarnings": true
-  }
-}
diff --git a/tools/package-docs-content/BUILD.bazel b/tools/package-docs-content/BUILD.bazel
index 334ef9d6697e..07abf9533195 100644
--- a/tools/package-docs-content/BUILD.bazel
+++ b/tools/package-docs-content/BUILD.bazel
@@ -1,5 +1,5 @@
 load("@build_bazel_rules_nodejs//:index.bzl", "nodejs_binary")
-load("//tools:defaults.bzl", "ts_library")
+load("//tools:defaults2.bzl", "ts_project")
 
 package(default_visibility = ["//visibility:public"])
 
@@ -15,14 +15,11 @@ nodejs_binary(
     templated_args = ["--bazel_patch_module_resolver"],
 )
 
-ts_library(
+ts_project(
     name = "sources",
     srcs = glob(["**/*.ts"]),
-    # TODO(ESM): remove this once the Bazel NodeJS rules can handle ESM with `nodejs_binary`.
-    devmode_module = "commonjs",
-    tsconfig = ":tsconfig.json",
+    tsconfig = "//tools:tsconfig",
     deps = [
-        "@npm//@types/fs-extra",
-        "@npm//@types/node",
+        "//:node_modules/@types/fs-extra",
     ],
 )
diff --git a/tools/package-docs-content/tsconfig.json b/tools/package-docs-content/tsconfig.json
deleted file mode 100644
index 8149f331fae2..000000000000
--- a/tools/package-docs-content/tsconfig.json
+++ /dev/null
@@ -1,13 +0,0 @@
-{
-  "compilerOptions": {
-    "lib": ["es2020"],
-    "module": "commonjs",
-    "target": "es2020",
-    "esModuleInterop": true,
-    "sourceMap": true,
-    "types": ["node"]
-  },
-  "bazelOptions": {
-    "suppressTsconfigOverrideWarnings": true
-  }
-}
diff --git a/tools/postcss/BUILD.bazel b/tools/postcss/BUILD.bazel
index 70bf51398e9f..6e679581ed43 100644
--- a/tools/postcss/BUILD.bazel
+++ b/tools/postcss/BUILD.bazel
@@ -1,15 +1,14 @@
-load("//tools:defaults.bzl", "ts_library")
+load("//tools:defaults2.bzl", "ts_project")
 
 package(default_visibility = ["//visibility:public"])
 
-ts_library(
+ts_project(
     name = "postcss",
     srcs = [
         "compare-nodes.ts",
     ],
-    # TODO(ESM): remove this once the Bazel NodeJS rules can handle ESM with `nodejs_binary`.
-    devmode_module = "commonjs",
+    tsconfig = "//tools:tsconfig",
     deps = [
-        "@npm//postcss",
+        "//:node_modules/postcss",
     ],
 )
diff --git a/tools/postinstall/patches/tsec+0.2.2.patch b/tools/postinstall/patches/tsec+0.2.2.patch
new file mode 100644
index 000000000000..b569c84319b7
--- /dev/null
+++ b/tools/postinstall/patches/tsec+0.2.2.patch
@@ -0,0 +1,22 @@
+diff --git a/node_modules/tsec/index.bzl b/node_modules/tsec/index.bzl
+index d2c2df7..f5bde10 100644
+--- a/node_modules/tsec/index.bzl
++++ b/node_modules/tsec/index.bzl
+@@ -16,7 +16,7 @@ def _capture_tsec_attrs_aspect_impl(target, ctx):
+     node_modules_root = None
+     if module_name:
+         paths[module_name] = target.label.package
+-    for d in ctx.rule.attr.deps:
++    for d in getattr(ctx.rule.attr, "deps", []):
+         if TsecTargetInfo in d:
+             paths.update(d[TsecTargetInfo].paths)
+         if DeclarationInfo in d:
+@@ -25,7 +25,7 @@ def _capture_tsec_attrs_aspect_impl(target, ctx):
+             node_modules_root = "/".join(["external", d[NpmPackageInfo].workspace, "node_modules"])
+     return [
+         TsecTargetInfo(
+-            srcs = ctx.rule.attr.srcs,
++            srcs = getattr(ctx.rule.attr, "srcs", []),
+             deps = depset(transitive = deps),
+             module_name = module_name,
+             paths = paths,
diff --git a/tools/region-parser/BUILD.bazel b/tools/region-parser/BUILD.bazel
index 8228e29ae3c0..75db002c01f2 100644
--- a/tools/region-parser/BUILD.bazel
+++ b/tools/region-parser/BUILD.bazel
@@ -1,14 +1,9 @@
-load("//tools:defaults.bzl", "ts_library")
+load("//tools:defaults2.bzl", "ts_project")
 
 package(default_visibility = ["//visibility:public"])
 
-ts_library(
+ts_project(
     name = "region-parser",
     srcs = glob(["**/*.ts"]),
-    # TODO(ESM): remove this once the Bazel NodeJS rules can handle ESM with `nodejs_binary`.
-    devmode_module = "commonjs",
-    tsconfig = ":tsconfig.json",
-    deps = [
-        "@npm//@types/node",
-    ],
+    tsconfig = "//tools:tsconfig",
 )
diff --git a/tools/region-parser/tsconfig.json b/tools/region-parser/tsconfig.json
deleted file mode 100644
index f72495569730..000000000000
--- a/tools/region-parser/tsconfig.json
+++ /dev/null
@@ -1,13 +0,0 @@
-{
-  "compilerOptions": {
-    "lib": ["es2020"],
-    "esModuleInterop": true,
-    "module": "commonjs",
-    "target": "es2020",
-    "sourceMap": true,
-    "types": ["node"]
-  },
-  "bazelOptions": {
-    "suppressTsconfigOverrideWarnings": true
-  }
-}
diff --git a/tools/sass/BUILD.bazel b/tools/sass/BUILD.bazel
index bb27ec9137e9..8da3b2d199f1 100644
--- a/tools/sass/BUILD.bazel
+++ b/tools/sass/BUILD.bazel
@@ -1,22 +1,21 @@
 load("@build_bazel_rules_nodejs//:index.bzl", "nodejs_binary")
-load("//tools:defaults.bzl", "ts_library")
+load("//tools:defaults2.bzl", "ts_project")
 
 package(default_visibility = ["//visibility:public"])
 
-ts_library(
+ts_project(
     name = "sass_lib",
     srcs = [
         "compiler-main.ts",
         "local-sass-importer.ts",
     ],
-    # TODO(ESM): remove this once the Bazel NodeJS rules can handle ESM with `nodejs_binary`.
-    devmode_module = "commonjs",
+    tsconfig = "//tools:tsconfig",
     deps = [
-        "@npm//@bazel/worker",
-        "@npm//@types/node",
-        "@npm//@types/yargs",
-        "@npm//sass",
-        "@npm//yargs",
+        "//:node_modules/@bazel/worker",
+        "//:node_modules/@types/node",
+        "//:node_modules/@types/yargs",
+        "//:node_modules/sass",
+        "//:node_modules/yargs",
     ],
 )
 
diff --git a/tools/server-test/BUILD.bazel b/tools/server-test/BUILD.bazel
index 57a1a4f5c3d8..09316b3e22ff 100644
--- a/tools/server-test/BUILD.bazel
+++ b/tools/server-test/BUILD.bazel
@@ -1,14 +1,12 @@
-load("//tools:defaults.bzl", "ts_library")
+load("//tools:defaults2.bzl", "ts_project")
 
 package(default_visibility = ["//visibility:public"])
 
-ts_library(
+ts_project(
     name = "test_runner_lib",
     srcs = ["test-runner.ts"],
-    # TODO(ESM): remove this once the Bazel NodeJS rules can handle ESM with `nodejs_binary`.
-    devmode_module = "commonjs",
+    tsconfig = "//tools:tsconfig",
     deps = [
-        "@npm//@bazel/runfiles",
-        "@npm//@types/node",
+        "//:node_modules/@bazel/runfiles",
     ],
 )
diff --git a/tools/tsconfig-test.json b/tools/tsconfig-test.json
new file mode 100644
index 000000000000..0179bc105ba4
--- /dev/null
+++ b/tools/tsconfig-test.json
@@ -0,0 +1,7 @@
+{
+  "extends": "./tsconfig.json",
+  "compilerOptions": {
+    "types": ["node", "jasmine"]
+  },
+  "exclude": []
+}
diff --git a/tools/tsconfig.json b/tools/tsconfig.json
index 35b878af8c31..dd0c432367cb 100644
--- a/tools/tsconfig.json
+++ b/tools/tsconfig.json
@@ -1,19 +1,16 @@
-// A lot of the scripts under `tools` are run manually, rather than on the CI. As such, it's easy
-// to miss compilation errors in them. This config is used to verify the files on the CI. Note that
-// the compiler options are somewhat loose, which is intentional in order to mimic the default
-// options used by `ts-node` when running the scripts.
 {
   "include": ["./**/*.ts"],
   "compilerOptions": {
-    "outDir": "../dist/tools",
     "target": "es2020",
     "module": "Node16",
     "strict": true,
+    "sourceMap": true,
+    "declaration": true,
     "esModuleInterop": true,
     "lib": ["es2020"],
     "skipLibCheck": true,
-    // Don't emit to the file system, because we only want to check for compilation errors.
-    "noEmit": true,
-    "downlevelIteration": true
-  }
+    "downlevelIteration": true,
+    "types": ["node"]
+  },
+  "exclude": ["**/*.spec.ts"]
 }