UNPKG

@nestjs/common

Version:

Decorators, pipes, exceptions and utilities for NestJS applications: @Controller(), @Injectable(), @Body(), ValidationPipe, HttpException and more.

105 lines (104 loc) • 4.07 kB
import { createRequire } from 'module'; import { Logger } from '../services/logger.service.js'; const MISSING_REQUIRED_DEPENDENCY = (name, reason) => `The "${name}" package is missing. Please, make sure to install it to use ${reason}.`; const logger = new Logger('PackageLoader'); /** * Cache of already-loaded packages keyed by package name. * Allows subsequent calls (including synchronous ones) to * return the module without another async import(). */ const packageCache = new Map(); export async function loadPackage(packageName, context, loaderFn) { const cached = packageCache.get(packageName); if (cached) { return cached; } try { const pkg = loaderFn ? await loaderFn() : await import(packageName); packageCache.set(packageName, pkg); return pkg; } catch (e) { logger.error(MISSING_REQUIRED_DEPENDENCY(packageName, context)); Logger.flush(); process.exit(1); } } /** * Synchronously loads a package using `createRequire` and caches it. * This is meant for optional dependencies that must be loaded in * synchronous contexts (e.g. constructors). * * Prefer {@link loadPackage} with a literal `() => import('pkg')` loader, or a * static import for packages that are regular dependencies: not every bundler * can see a package loaded through `createRequire`. webpack 5 resolves * `createRequire(import.meta.url)('pkg')`, while esbuild and rolldown leave it * out of the bundle without a warning (evanw/esbuild#1828), so applications * bundled with those have to mark the package as external. * * @param loaderFn Optional synchronous loader (e.g. * `() => createRequire(import.meta.url)('pkg')`), resolved from the calling * package rather than from `@nestjs/common`. * Falls back to a `createRequire` call resolved from this file. */ export function loadPackageSync(packageName, context, loaderFn) { const cached = packageCache.get(packageName); if (cached) { return cached; } try { const pkg = loaderFn ? loaderFn() : createRequire(import.meta.url)(packageName); packageCache.set(packageName, pkg); return pkg; } catch (e) { logger.error(MISSING_REQUIRED_DEPENDENCY(packageName, context)); Logger.flush(); process.exit(1); } } /** * Synchronously returns a package that was previously loaded and cached * via {@link loadPackage}. Throws if the package has not been loaded yet. * * Use this in methods that must remain synchronous (e.g. `connectMicroservice`). * Ensure that `loadPackage()` has been `await`ed for the same package name * before calling this function (typically during `init()` or `compile()`). */ export function loadPackageCached(packageName, context) { const cached = packageCache.get(packageName); if (!cached) { if (context) { // The package was not preloaded (typically because it is not // installed). Fall back to a synchronous load so the user gets the // actionable "package is missing" message instead of an internal error. return loadPackageSync(packageName, context); } throw new Error(`Package "${packageName}" has not been loaded yet. ` + `Ensure loadPackage("${packageName}", ...) has been awaited before calling loadPackageCached.`); } return cached; } /** * Attempts to load and cache a package. Returns the loaded module on success * or `null` if the package is not installed. * * Unlike {@link loadPackage}, this function does **not** terminate the process * when the package is missing, making it suitable for optional dependencies. */ export async function tryLoadPackage(packageName, loaderFn) { const cached = packageCache.get(packageName); if (cached) { return cached; } try { const pkg = loaderFn ? await loaderFn() : await import(packageName); packageCache.set(packageName, pkg); return pkg; } catch { return null; } }