From fe6dcd1f5c4303de913f70926893996ba2d74fc7 Mon Sep 17 00:00:00 2001 From: Paul Gschwendtner Date: Tue, 6 Dec 2022 14:22:55 +0000 Subject: [PATCH] build: rename `esm-loader.mjs` to a more specific filename (#48521) This is in prearation for having a proper diff when this loader is adjusted to support more situations than just simple external node modules. See next commit. Also the file is formatted to make the diff less verbose later. The file was never formatted correctly and we don't lint `.mjs` files. PR Close #48521 --- tools/{esm-loader => esm-interop}/BUILD.bazel | 0 tools/esm-interop/esm-node-module-loader.mjs | 76 +++++++++++++++++++ tools/esm-loader/esm-loader.mjs | 64 ---------------- 3 files changed, 76 insertions(+), 64 deletions(-) rename tools/{esm-loader => esm-interop}/BUILD.bazel (100%) create mode 100644 tools/esm-interop/esm-node-module-loader.mjs delete mode 100644 tools/esm-loader/esm-loader.mjs diff --git a/tools/esm-loader/BUILD.bazel b/tools/esm-interop/BUILD.bazel similarity index 100% rename from tools/esm-loader/BUILD.bazel rename to tools/esm-interop/BUILD.bazel diff --git a/tools/esm-interop/esm-node-module-loader.mjs b/tools/esm-interop/esm-node-module-loader.mjs new file mode 100644 index 00000000000..dba5ab49421 --- /dev/null +++ b/tools/esm-interop/esm-node-module-loader.mjs @@ -0,0 +1,76 @@ +/** + * @license + * Copyright Google LLC All Rights Reserved. + * + * Use of this source code is governed by an MIT-style license that can be + * found in the LICENSE file at https://angular.io/license + */ + +import fs from 'fs'; +import path from 'path'; +import {pathToFileURL} from 'url'; +import {resolve as resolveExports} from '../../third_party/github.com/lukeed/resolve.exports/index.mjs'; + +/* + Custom module loader (see https://nodejs.org/api/cli.html#--experimental-loadermodule) to support + loading third-party packages in esm modules when the rules_nodejs linker is disabled. Resolves + third-party imports from the node_modules folder in the bazel workspace defined by + process.env.NODE_MODULES_WORKSPACE_NAME, and uses default resolution for all other imports. + + This is required because rules_nodejs only patches requires in cjs modules when the linker + is disabled, not imports in mjs modules. +*/ +export async function resolve(specifier, context, defaultResolve) { + if (!isNodeOrNpmPackageImport(specifier)) { + return defaultResolve(specifier, context, defaultResolve); + } + + const runfilesRoot = path.resolve(process.env.RUNFILES); + const nodeModules = path.join( + runfilesRoot, + process.env.NODE_MODULES_WORKSPACE_NAME, + 'node_modules' + ); + const packageImport = parsePackageImport(specifier); + const pathToNodeModule = path.join(nodeModules, packageImport.packageName); + + const isInternalNodePackage = !fs.existsSync(pathToNodeModule); + if (isInternalNodePackage) { + return defaultResolve(specifier, context, defaultResolve); + } + + const packageJson = JSON.parse( + fs.readFileSync(path.join(pathToNodeModule, 'package.json'), 'utf-8') + ); + + const localPackagePath = resolvePackageLocalFilepath(packageImport, packageJson); + const resolvedFilePath = path.join(pathToNodeModule, localPackagePath); + + return {url: pathToFileURL(resolvedFilePath).href}; +} + +function isNodeOrNpmPackageImport(specifier) { + return ( + !specifier.startsWith('./') && + !specifier.startsWith('../') && + !specifier.startsWith('node:') && + !specifier.startsWith('file:') + ); +} + +function parsePackageImport(specifier) { + const [, packageName, pathInPackage = ''] = + /^((?:@[^/]+\/)?[^/]+)(?:\/(.+))?$/.exec(specifier) ?? []; + if (!packageName) { + throw new Error(`Could not parse package name import statement '${specifier}'`); + } + return {packageName, pathInPackage, specifier}; +} + +function resolvePackageLocalFilepath(packageImport, packageJson) { + if (packageJson.exports) { + return resolveExports(packageJson, packageImport.specifier); + } + + return packageImport.pathInPackage || packageJson.module || packageJson.main || 'index.js'; +} diff --git a/tools/esm-loader/esm-loader.mjs b/tools/esm-loader/esm-loader.mjs deleted file mode 100644 index ddae64de05a..00000000000 --- a/tools/esm-loader/esm-loader.mjs +++ /dev/null @@ -1,64 +0,0 @@ -/** - * @license - * Copyright Google LLC All Rights Reserved. - * - * Use of this source code is governed by an MIT-style license that can be - * found in the LICENSE file at https://angular.io/license - */ - -import fs from 'fs'; -import path from 'path'; -import {pathToFileURL} from 'url'; -import {resolve as resolveExports} from '../../third_party/github.com/lukeed/resolve.exports/index.mjs'; - -/* - Custom module loader (see https://nodejs.org/api/cli.html#--experimental-loadermodule) to support - loading third-party packages in esm modules when the rules_nodejs linker is disabled. Resolves - third-party imports from the node_modules folder in the bazel workspace defined by - process.env.NODE_MODULES_WORKSPACE_NAME, and uses default resolution for all other imports. - - This is required because rules_nodejs only patches requires in cjs modules when the linker - is disabled, not imports in mjs modules. -*/ -export async function resolve(specifier, context, defaultResolve) { - if (!isNodeOrNpmPackageImport(specifier)) { - return defaultResolve(specifier, context, defaultResolve); - } - - const runfilesRoot = path.resolve(process.env.RUNFILES); - const nodeModules = path.join(runfilesRoot, process.env.NODE_MODULES_WORKSPACE_NAME, 'node_modules'); - const packageImport = parsePackageImport(specifier); - const pathToNodeModule = path.join(nodeModules, packageImport.packageName); - - const isInternalNodePackage = !fs.existsSync(pathToNodeModule); - if (isInternalNodePackage) { - return defaultResolve(specifier, context, defaultResolve); - } - - const packageJson = JSON.parse(fs.readFileSync(path.join(pathToNodeModule, 'package.json'), 'utf-8')); - - const localPackagePath = resolvePackageLocalFilepath(packageImport, packageJson); - const resolvedFilePath = path.join(pathToNodeModule, localPackagePath); - - return {url: pathToFileURL(resolvedFilePath).href}; -} - -function isNodeOrNpmPackageImport(specifier) { - return !specifier.startsWith('./') && !specifier.startsWith('../') && !specifier.startsWith('node:') && !specifier.startsWith('file:'); -} - -function parsePackageImport(specifier) { - const [, packageName, pathInPackage = ''] = /^((?:@[^/]+\/)?[^/]+)(?:\/(.+))?$/.exec(specifier) ?? []; - if (!packageName) { - throw new Error(`Could not parse package name import statement '${specifier}'`); - } - return {packageName, pathInPackage, specifier}; -} - -function resolvePackageLocalFilepath(packageImport, packageJson) { - if (packageJson.exports) { - return resolveExports(packageJson, packageImport.specifier); - } - - return packageImport.pathInPackage || packageJson.module || packageJson.main || 'index.js'; -}