UNPKG

@rushstack/node-core-library

Version:

Core libraries that every NodeJS toolchain project should use

390 lines 17.6 kB
"use strict"; // Copyright (c) Microsoft Corporation. All rights reserved. Licensed under the MIT license. // See LICENSE in the project root for license information. var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { if (k2 === undefined) k2 = k; var desc = Object.getOwnPropertyDescriptor(m, k); if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) { desc = { enumerable: true, get: function() { return m[k]; } }; } Object.defineProperty(o, k2, desc); }) : (function(o, m, k, k2) { if (k2 === undefined) k2 = k; o[k2] = m[k]; })); var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) { Object.defineProperty(o, "default", { enumerable: true, value: v }); }) : function(o, v) { o["default"] = v; }); var __importStar = (this && this.__importStar) || (function () { var ownKeys = function(o) { ownKeys = Object.getOwnPropertyNames || function (o) { var ar = []; for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k; return ar; }; return ownKeys(o); }; return function (mod) { if (mod && mod.__esModule) return mod; var result = {}; if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]); __setModuleDefault(result, mod); return result; }; })(); Object.defineProperty(exports, "__esModule", { value: true }); exports.Import = void 0; const path = __importStar(require("node:path")); const nodeModule = require("module"); const importLazy = require("import-lazy"); const Resolve = __importStar(require("resolve")); const PackageJsonLookup_1 = require("./PackageJsonLookup"); const FileSystem_1 = require("./FileSystem"); const PackageName_1 = require("./PackageName"); /** * Helpers for resolving and importing Node.js modules. * @public */ class Import { static get _builtInModules() { if (!Import.__builtInModules) { Import.__builtInModules = new Set(nodeModule.builtinModules); } return Import.__builtInModules; } /** * Provides a way to improve process startup times by lazy-loading imported modules. * * @remarks * This is a more structured wrapper for the {@link https://www.npmjs.com/package/import-lazy|import-lazy} * package. It enables you to replace an import like this: * * ```ts * import * as example from 'example'; // <-- 100ms load time * * if (condition) { * example.doSomething(); * } * ``` * * ...with a pattern like this: * * ```ts * const example: typeof import('example') = Import.lazy('example', require); * * if (condition) { * example.doSomething(); // <-- 100ms load time occurs here, only if needed * } * ``` * * The implementation relies on JavaScript's `Proxy` feature to intercept access to object members. Thus * it will only work correctly with certain types of module exports. If a particular export isn't well behaved, * you may need to find (or introduce) some other module in your dependency graph to apply the optimization to. * * Usage guidelines: * * - Always specify types using `typeof` as shown above. * * - Never apply lazy-loading in a way that would convert the module's type to `any`. Losing type safety * seriously impacts the maintainability of the code base. * * - In cases where the non-runtime types are needed, import them separately using the `Types` suffix: * * ```ts * const example: typeof import('example') = Import.lazy('example', require); * import type * as exampleTypes from 'example'; * ``` * * - If the imported module confusingly has the same name as its export, then use the Module suffix: * * ```ts * const exampleModule: typeof import('../../logic/Example') = Import.lazy( * '../../logic/Example', require); * import type * as exampleTypes from '../../logic/Example'; * ``` * * - If the exports cause a lot of awkwardness (e.g. too many expressions need to have `exampleModule.` inserted * into them), or if some exports cannot be proxied (e.g. `Import.lazy('example', require)` returns a function * signature), then do not lazy-load that module. Instead, apply lazy-loading to some other module which is * better behaved. * * - It's recommended to sort imports in a standard ordering: * * ```ts * // 1. external imports * import * as path from 'path'; * import { Import, JsonFile, JsonObject } from '@rushstack/node-core-library'; * * // 2. local imports * import { LocalFile } from './path/LocalFile'; * * // 3. lazy-imports (which are technically variables, not imports) * const semver: typeof import('semver') = Import.lazy('semver', require); * ``` */ // eslint-disable-next-line @typescript-eslint/no-explicit-any static lazy(moduleName, require) { const importLazyLocal = importLazy(require); return importLazyLocal(moduleName); } /** * This resolves a module path using similar logic as the Node.js `require.resolve()` API, * but supporting extra features such as specifying the base folder. * * @remarks * A module path is a text string that might appear in a statement such as * `import { X } from "____";` or `const x = require("___");`. The implementation is based * on the popular `resolve` NPM package. * * Suppose `example` is an NPM package whose entry point is `lib/index.js`: * ```ts * // Returns "/path/to/project/node_modules/example/lib/index.js" * Import.resolveModule({ modulePath: 'example' }); * * // Returns "/path/to/project/node_modules/example/lib/other.js" * Import.resolveModule({ modulePath: 'example/lib/other' }); * ``` * If you need to determine the containing package folder * (`/path/to/project/node_modules/example`), use {@link Import.resolvePackage} instead. * * @returns the absolute path of the resolved module. * If {@link IImportResolveOptions.includeSystemModules} is specified * and a system module is found, then its name is returned without any file path. */ static resolveModule(options) { const { modulePath, baseFolderPath, includeSystemModules, allowSelfReference, getRealPath } = options; if (path.isAbsolute(modulePath)) { return modulePath; } const normalizedRootPath = (getRealPath || FileSystem_1.FileSystem.getRealPath)(baseFolderPath); if (modulePath.startsWith('.')) { // This looks like a conventional relative path return path.resolve(normalizedRootPath, modulePath); } // Built-in modules do not have a scope, so if there is a slash, then we need to check // against the first path segment const slashIndex = modulePath.indexOf('/'); const moduleName = slashIndex === -1 ? modulePath : modulePath.slice(0, slashIndex); if (!includeSystemModules && Import._builtInModules.has(moduleName)) { throw new Error(`Cannot find module "${modulePath}" from "${options.baseFolderPath}".`); } if (allowSelfReference === true) { const ownPackage = Import._getPackageName(normalizedRootPath); if (ownPackage && (modulePath === ownPackage.packageName || modulePath.startsWith(`${ownPackage.packageName}/`))) { const packagePath = modulePath.slice(ownPackage.packageName.length + 1); return path.resolve(ownPackage.packageRootPath, packagePath); } } try { return Resolve.sync(modulePath, { basedir: normalizedRootPath, preserveSymlinks: false, realpathSync: getRealPath }); } catch (e) { throw new Error(`Cannot find module "${modulePath}" from "${options.baseFolderPath}": ${e}`); } } /** * Async version of {@link Import.resolveModule}. */ static async resolveModuleAsync(options) { const { modulePath, baseFolderPath, includeSystemModules, allowSelfReference, getRealPath, getRealPathAsync } = options; if (path.isAbsolute(modulePath)) { return modulePath; } const normalizedRootPath = await (getRealPathAsync || getRealPath || FileSystem_1.FileSystem.getRealPathAsync)(baseFolderPath); if (modulePath.startsWith('.')) { // This looks like a conventional relative path return path.resolve(normalizedRootPath, modulePath); } // Built-in modules do not have a scope, so if there is a slash, then we need to check // against the first path segment const slashIndex = modulePath.indexOf('/'); const moduleName = slashIndex === -1 ? modulePath : modulePath.slice(0, slashIndex); if (!includeSystemModules && Import._builtInModules.has(moduleName)) { throw new Error(`Cannot find module "${modulePath}" from "${options.baseFolderPath}".`); } if (allowSelfReference === true) { const ownPackage = Import._getPackageName(normalizedRootPath); if (ownPackage && (modulePath === ownPackage.packageName || modulePath.startsWith(`${ownPackage.packageName}/`))) { const packagePath = modulePath.slice(ownPackage.packageName.length + 1); return path.resolve(ownPackage.packageRootPath, packagePath); } } try { const resolvePromise = new Promise((resolve, reject) => { const realPathFn = getRealPathAsync || getRealPath ? (filePath, callback) => { if (getRealPathAsync) { getRealPathAsync(filePath) .then((resolvedPath) => callback(null, resolvedPath)) .catch((error) => callback(error)); } else { try { const resolvedPath = getRealPath(filePath); callback(null, resolvedPath); } catch (error) { callback(error); } } } : undefined; Resolve.default(modulePath, { basedir: normalizedRootPath, preserveSymlinks: false, realpath: realPathFn }, (error, resolvedPath) => { if (error) { reject(error); } else { // Resolve docs state that either an error will be returned, or the resolved path. // In this case, the resolved path should always be populated. resolve(resolvedPath); } }); }); return await resolvePromise; } catch (e) { throw new Error(`Cannot find module "${modulePath}" from "${options.baseFolderPath}": ${e}`); } } /** * Performs module resolution to determine the folder where a package is installed. * * @remarks * Suppose `example` is an NPM package whose entry point is `lib/index.js`: * ```ts * // Returns "/path/to/project/node_modules/example" * Import.resolvePackage({ packageName: 'example' }); * ``` * * If you need to resolve a module path, use {@link Import.resolveModule} instead: * ```ts * // Returns "/path/to/project/node_modules/example/lib/index.js" * Import.resolveModule({ modulePath: 'example' }); * ``` * * @returns the absolute path of the package folder. * If {@link IImportResolveOptions.includeSystemModules} is specified * and a system module is found, then its name is returned without any file path. */ static resolvePackage(options) { const { packageName, includeSystemModules, baseFolderPath, allowSelfReference, getRealPath, useNodeJSResolver } = options; if (includeSystemModules && Import._builtInModules.has(packageName)) { return packageName; } const normalizedRootPath = (getRealPath || FileSystem_1.FileSystem.getRealPath)(baseFolderPath); if (allowSelfReference) { const ownPackage = Import._getPackageName(normalizedRootPath); if (ownPackage && ownPackage.packageName === packageName) { return ownPackage.packageRootPath; } } PackageName_1.PackageName.parse(packageName); // Ensure the package name is valid and doesn't contain a path try { const resolvedPath = useNodeJSResolver ? require.resolve(`${packageName}/package.json`, { paths: [normalizedRootPath] }) : // Append `/package.json` to ensure `resolve.sync` doesn't attempt to return a system package, and to avoid // having to mess with the `packageFilter` option. Resolve.sync(`${packageName}/package.json`, { basedir: normalizedRootPath, preserveSymlinks: false, realpathSync: getRealPath }); const packagePath = path.dirname(resolvedPath); return packagePath; } catch (e) { throw new Error(`Cannot find package "${packageName}" from "${baseFolderPath}": ${e}.`); } } /** * Async version of {@link Import.resolvePackage}. */ static async resolvePackageAsync(options) { const { packageName, includeSystemModules, baseFolderPath, allowSelfReference, getRealPath, getRealPathAsync } = options; if (includeSystemModules && Import._builtInModules.has(packageName)) { return packageName; } const normalizedRootPath = await (getRealPathAsync || getRealPath || FileSystem_1.FileSystem.getRealPathAsync)(baseFolderPath); if (allowSelfReference) { const ownPackage = Import._getPackageName(normalizedRootPath); if (ownPackage && ownPackage.packageName === packageName) { return ownPackage.packageRootPath; } } PackageName_1.PackageName.parse(packageName); // Ensure the package name is valid and doesn't contain a path try { const resolvePromise = new Promise((resolve, reject) => { const realPathFn = getRealPathAsync || getRealPath ? (filePath, callback) => { if (getRealPathAsync) { getRealPathAsync(filePath) .then((resolvedPath) => callback(null, resolvedPath)) .catch((error) => callback(error)); } else { try { const resolvedPath = getRealPath(filePath); callback(null, resolvedPath); } catch (error) { callback(error); } } } : undefined; Resolve.default( // Append `/package.json` to ensure `resolve` doesn't attempt to return a system package, and to avoid // having to mess with the `packageFilter` option. `${packageName}/package.json`, { basedir: normalizedRootPath, preserveSymlinks: false, realpath: realPathFn }, (error, resolvedPath) => { if (error) { reject(error); } else { // Resolve docs state that either an error will be returned, or the resolved path. // In this case, the resolved path should always be populated. resolve(resolvedPath); } }); }); const resolvedPath = await resolvePromise; const packagePath = path.dirname(resolvedPath); return packagePath; } catch (e) { throw new Error(`Cannot find package "${packageName}" from "${baseFolderPath}": ${e}`); } } static _getPackageName(rootPath) { const packageJsonPath = PackageJsonLookup_1.PackageJsonLookup.instance.tryGetPackageJsonFilePathFor(rootPath); if (packageJsonPath) { const packageJson = PackageJsonLookup_1.PackageJsonLookup.instance.loadPackageJson(packageJsonPath); return { packageRootPath: path.dirname(packageJsonPath), packageName: packageJson.name }; } else { return undefined; } } } exports.Import = Import; //# sourceMappingURL=Import.js.map