Skip to content

Commit d86d11d

Browse files
devversionpkozlowski-opensource
authored andcommitted
build: introduce NodeJS loader for rules_js Node execution (angular#61865)
For the `rules_js` migration, we are facing the problem where our current Angular code is shipped as ESM, but we aren't fully there yet with fully compliant strict ESM during development. That is because we lack explicit import extensions, and it's also a different story how this would work in Google3, if we were to add them. In addition, we cross-import from our packages using npm module names. This works well for TS, for ESBuild because those can respect path mappings— but at runtime, when executing native `jasmine_test`'s— such mappings aren't respected. The options here are: - avoid module imports in the repo (impossible; undesired) - use pre-bundling of all NodeJS execution involving npm package code (slower, extra build action cost) - wire up a simple NodeJS loader (supported via official APIs) to simply account for our cases (preferred and similar to what we experimented with for the last year(s); and worked well) This commit implements the last option and allows for an easy migration to `rules_js`, and also is pretty reasonable. Long-term we can resolve the extension problem if we e.g. migrate to real explicit extensions + a proper TS module resolution like e.g. `nodenext`. PR Close angular#61865
1 parent e81ea0c commit d86d11d

File tree

8 files changed

+2763
-2650
lines changed

8 files changed

+2763
-2650
lines changed

.aspect/rules/external_repository_action_cache/npm_translate_lock_MzA5NzUwNzMx

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
adev/shared-docs/pipeline/api-gen/package.json=939673974
77
integration/package.json=-239561259
88
modules/package.json=-1315257891
9-
package.json=1012566638
9+
package.json=-1124738082
1010
packages/animations/package.json=-678724831
1111
packages/common/package.json=1729763064
1212
packages/compiler-cli/linker/babel/test/package.json=939673974
@@ -21,7 +21,7 @@ packages/platform-browser/package.json=-1163479450
2121
packages/router/package.json=860819913
2222
packages/upgrade/package.json=16347051
2323
packages/zone.js/package.json=-1577512523
24-
pnpm-lock.yaml=-1427965318
24+
pnpm-lock.yaml=1622469731
2525
pnpm-workspace.yaml=1973735808
2626
tools/bazel/rules_angular_store/package.json=-239561259
27-
yarn.lock=-1301634010
27+
yarn.lock=916728416

package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -207,6 +207,7 @@
207207
"emoji-regex": "^10.3.0",
208208
"fflate": "^0.8.2",
209209
"firebase-tools": "^14.0.0",
210+
"get-tsconfig": "^4.10.1",
210211
"gulp": "^5.0.0",
211212
"gulp-conventional-changelog": "^5.0.0",
212213
"html-entities": "^2.5.2",

pnpm-lock.yaml

Lines changed: 2648 additions & 2646 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

tools/bazel/js_binary.bzl

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
"""
2+
Extension of native `js_binary` to install a NodeJS resolution hook
3+
that will help with automatically adding missing ESM extension and
4+
mapping `@angular/<..>` to the local first-party package directory.
5+
"""
6+
7+
load("@aspect_rules_js//js:defs.bzl", _js_binary = "js_binary")
8+
9+
def js_binary(name, chdir = None, data = [], **kwargs):
10+
if chdir != None:
11+
to_root = ["/".join([".."] * len(chdir.split("/")))]
12+
else:
13+
to_root = "./"
14+
15+
_js_binary(
16+
name = name,
17+
data = data + ["//tools/bazel/node_loader", "//packages:tsconfig_build"],
18+
node_options = ["--import", "%s/tools/bazel/node_loader/index.mjs" % to_root],
19+
**kwargs
20+
)

tools/bazel/node_loader/BUILD.bazel

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
load("@aspect_rules_js//js:defs.bzl", "js_library")
2+
3+
js_library(
4+
name = "node_loader",
5+
srcs = glob(["*.mjs"]),
6+
visibility = ["//visibility:public"],
7+
deps = ["//:node_modules/get-tsconfig"],
8+
)

tools/bazel/node_loader/hooks.mjs

Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
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+
/**
10+
* @fileoverview
11+
*
12+
* Module loader that augments NodeJS's execution to:
13+
*
14+
* - support native execution of Angular JavaScript output
15+
* that isn't strict ESM at this point (lack of explicit extensions).
16+
* - support path mappings at runtime. This allows us to natively execute ESM
17+
* without having to pre-bundle for testing, or use the slow full npm linked packages
18+
*/
19+
20+
import {parseTsconfig, createPathsMatcher} from 'get-tsconfig';
21+
22+
import path from 'node:path';
23+
24+
const explicitExtensionRe = /\.[mc]?js$/;
25+
const nonModuleImportRe = /^[.\/]/;
26+
27+
const runfilesRoot = process.env.JS_BINARY__RUNFILES;
28+
29+
const tsconfigPath = path.join(runfilesRoot, 'angular/packages/tsconfig-build.json');
30+
const tsconfig = parseTsconfig(tsconfigPath);
31+
const pathMappingMatcher = createPathsMatcher({config: tsconfig, path: tsconfigPath});
32+
33+
/** @type {import('module').ResolveHook} */
34+
export const resolve = async (specifier, context, nextResolve) => {
35+
// True when it's a non-module import without explicit extensions.
36+
const isNonModuleExtensionlessImport =
37+
nonModuleImportRe.test(specifier) && !explicitExtensionRe.test(specifier);
38+
const pathMappings = !nonModuleImportRe.test(specifier) ? pathMappingMatcher(specifier) : [];
39+
40+
// If it's neither path mapped, nor an extension-less import that may be fixed up, exit early.
41+
if (!isNonModuleExtensionlessImport && pathMappings.length === 0) {
42+
return nextResolve(specifier, context);
43+
}
44+
45+
if (pathMappings.length > 0) {
46+
for (const mapping of pathMappings) {
47+
const res = await catchError(() => resolve(mapping, context, nextResolve));
48+
if (res !== null) {
49+
return res;
50+
}
51+
}
52+
} else {
53+
const fixedResult =
54+
(await catchError(() => nextResolve(`${specifier}.js`, context))) ||
55+
(await catchError(() => nextResolve(`${specifier}/index.js`, context)));
56+
57+
if (fixedResult !== null) {
58+
return fixedResult;
59+
}
60+
}
61+
62+
return await nextResolve(specifier, context);
63+
};
64+
65+
async function catchError(fn) {
66+
try {
67+
return await fn();
68+
} catch {
69+
return null;
70+
}
71+
}

tools/bazel/node_loader/index.mjs

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
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+
import {register} from 'node:module';
10+
11+
register('./hooks.mjs', {parentURL: import.meta.url});

yarn.lock

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9571,7 +9571,7 @@ get-symbol-description@^1.1.0:
95719571
es-errors "^1.3.0"
95729572
get-intrinsic "^1.2.6"
95739573

9574-
get-tsconfig@^4.7.5:
9574+
get-tsconfig@^4.10.1, get-tsconfig@^4.7.5:
95759575
version "4.10.1"
95769576
resolved "https://registry.yarnpkg.com/get-tsconfig/-/get-tsconfig-4.10.1.tgz#d34c1c01f47d65a606c37aa7a177bc3e56ab4b2e"
95779577
integrity sha512-auHyJ4AgMz7vgS8Hp3N6HXSmlMdUyhSUrfBF16w153rxtLIEOE+HGqaBppczZvnHLqQJfiHotCYpNhl0lUROFQ==

0 commit comments

Comments
 (0)
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy