@harves/nx-node-esm-plugin
Version:
Node executor including ESM module resolution for buildable libraries within Nx workspaces.
222 lines • 9.86 kB
JavaScript
var _a, _b, _c;
import { __awaiter } from "tslib";
import { readFileSync, statSync } from 'fs';
import { isBuiltin } from 'module';
import { join } from 'path';
import { resolveExports } from 'resolve-pkg-maps';
const VERBOSE = (_a = process.env['NX_VERBOSE_LOGGING']) !== null && _a !== void 0 ? _a : false;
/**
* These mappings map from the Nx library names (as per `package.json`
* and as mapped in `{workspaceRoot}/tsconfig.base.json`) to the
* built output folder for the library.
*
* @example
* ```
* {
* '@scope/pkg': '<absolute-path>/dist/libs/my-lib'
* }
* ```
*/
const mappings = JSON.parse((_b = process.env['NX_MAPPINGS']) !== null && _b !== void 0 ? _b : '{}');
if (VERBOSE) {
console.log(`node-loader: library mappings:`, mappings);
}
/**
* The mappings are provided in configuration and are evaluated preferentially to
* `mappings`.
*
* They map from module specifier to a target file.
* @example
* ```
* {
* '@scope/pkg': '<absolute-path>/dist/libs/my-lib/some-other-file-to-load.js'
* }
* ```
*/
const moduleResolutionOverrides = JSON.parse((_c = process.env['NX_MODULE_RESOLUTION_OVERRIDES']) !== null && _c !== void 0 ? _c : '{}');
if (VERBOSE) {
console.log(`node-loader: moduleResolutionOverrides:`, moduleResolutionOverrides);
}
const legacyMainResolveExtensions = [
'',
'.js',
'.json',
'.node',
'/index.js',
'/index.json',
'/index.node',
];
const legacyMainResolveFallbacks = [
'./index.js',
'./index.json',
'./index.node',
];
function fileExists(pth) {
var _a, _b;
return (_b = (_a = statSync(pth, { throwIfNoEntry: false })) === null || _a === void 0 ? void 0 : _a.isFile()) !== null && _b !== void 0 ? _b : false;
}
class ModuleNotFoundError extends Error {
constructor(specifier, resolvedPath, extraMsg) {
super(`node-runner: Failed to resolve mapped specifier '${specifier}': resolved path '${resolvedPath}' not found${extraMsg ? ' (' + extraMsg + ')' : ''}`);
this.code = 'MODULE_NOT_FOUND';
}
}
/**
* This method resolves Node ESM import specifiers for buildable
* Nx libraries by mapping the library specifiers to the library's
* built output paths.
*
* It also includes support for configurable overrides.
*
* All other specifiers are deferred to the parent resolver.
*
* The resolution algorithm:
*
* 1. Assumes that the mapped Nx library output paths contain a valid
* `package.json`
* 2. Attempts to resolve the library imports for mapped libraries
* using the Node's ESM Module resolution algorithm, with the
* assumption that all Nx library specifiers are "bare specifiers".
* 3. Attempts to resolve using the `package.json` `exports`
* property using the `resolve-pkg-maps` package.
* 4. Falls back to attempting resolution using the `package.json` `main`
* property using Node's legacy CommonJS resolution algorithm.
*
* Legacy CommonJS main resolution is as follows:
* 1. let M = pkg_url + (json main field)
* 2. TRY(M, M.js, M.json, M.node)
* 3. TRY(M/index.js, M/index.json, M/index.node)
* 4. TRY(pkg_url/index.js, pkg_url/index.json, pkg_url/index.node)
* 5. NOT_FOUND
*
* See Node's ESM Module Resolution algorithm:
* https://nodejs.org/api/esm.html
*
* See Node's legacy CommonJS main resolution code:
* https://github.com/nodejs/node/blob/d57af10d1be390f4e33e6efe596133de9da36c86/lib/internal/modules/esm/resolve.js#L189-L200
* and https://github.com/nodejs/node/blob/v16.14.0/lib/internal/modules/esm/resolve.js#L299.
*
* See the resolve-pkg-maps package:
* https://www.npmjs.com/package/resolve-pkg-maps
*
* @see https://github.com/nrwl/nx/issues/11335#issuecomment-2136663019
* @see https://github.com/nrwl/nx/issues/21928
*
* @param specifier
* @param context
* @param nextResolve
* @returns
*/
export function resolve(specifier, context, nextResolve) {
return __awaiter(this, void 0, void 0, function* () {
if (VERBOSE) {
console.log(`node-loader: resolving ${specifier}`);
}
if (typeof moduleResolutionOverrides === 'object') {
if (specifier in moduleResolutionOverrides) {
const resolvedPath = moduleResolutionOverrides[specifier];
if (fileExists(resolvedPath)) {
if (VERBOSE) {
console.log(`node-loader: found override ${specifier} --> ${resolvedPath}`);
}
return nextResolve(resolvedPath, context);
}
else {
throw new ModuleNotFoundError(specifier, resolvedPath, `Bad module resolution override`);
}
}
}
// not essential, but explicitly skip built-ins
if (isBuiltin(specifier)) {
return nextResolve(specifier, context);
}
if (typeof mappings === 'object') {
for (const [libSpecifier, libPath] of Object.entries(mappings)) {
const subpath = specifier === libSpecifier
? ''
: specifier.startsWith(libSpecifier + '/')
? specifier.substring(libSpecifier.length + 1)
: undefined;
if (subpath !== undefined) {
if (typeof libPath === 'string') {
// console.log('GOT THECONTEXT', specifier, libPath, `-${subpath}-`, context);
/*
* read package.json
*/
const pkgJsonPath = join(libPath, 'package.json');
if (!fileExists(pkgJsonPath)) {
throw new ModuleNotFoundError(specifier, pkgJsonPath, `Missing package.json`);
}
let pkgJson;
try {
pkgJson = JSON.parse(readFileSync(pkgJsonPath, { encoding: 'utf-8' }));
}
catch (error) {
throw new ModuleNotFoundError(specifier, pkgJsonPath, '' + error);
}
/*
* attempt resolution via exports
*/
if (pkgJson.exports) {
const resolvedPaths = resolveExports(pkgJson.exports, subpath, context.conditions);
// console.log('RESOLVED PATHS', resolvedPaths);
if (resolvedPaths === null || resolvedPaths === void 0 ? void 0 : resolvedPaths[0]) {
const resolvedPath = join(libPath, resolvedPaths[0]);
if (fileExists(resolvedPath)) {
if (VERBOSE) {
console.log(`node-loader: found mapping ${specifier} --> ${resolvedPath} (export match)`);
}
return nextResolve(resolvedPath, context);
}
else {
throw new ModuleNotFoundError(specifier, resolvedPath);
}
}
}
/*
* Legacy CommonJS main resolution
*/
if (subpath === '') {
if (pkgJson.main) {
for (const ext of legacyMainResolveExtensions) {
const resolvedPath = join(libPath, `${pkgJson.main}${ext}`);
if (fileExists(resolvedPath)) {
// DEP0151
if (VERBOSE) {
console.log(`node-loader: found mapping ${specifier} --> ${resolvedPath} (legacy DEP0151)`);
}
return nextResolve(resolvedPath, context);
}
}
throw new ModuleNotFoundError(specifier, pkgJsonPath, `Cannot resolve package main entry '${pkgJson.main}'`);
}
else {
for (const index of legacyMainResolveFallbacks) {
const resolvedPath = join(libPath, index);
if (fileExists(resolvedPath)) {
// DEP0151
if (VERBOSE) {
console.log(`node-loader: found mapping ${specifier} --> ${resolvedPath} (legacy DEP0151)`);
}
return nextResolve(resolvedPath, context);
}
}
throw new ModuleNotFoundError(specifier, pkgJsonPath, `Cannot resolve package fallback main entry`);
}
}
else {
throw new ModuleNotFoundError(specifier, pkgJsonPath, `Cannot resolve subpath '${subpath}'`);
}
}
else {
// libPath not string (INTERNAL ERROR?)
throw new ModuleNotFoundError(specifier, JSON.stringify(libPath), `Bad mapping`);
}
}
}
}
// fall through to default resolver
return nextResolve(specifier, context);
});
}
//# sourceMappingURL=node-loader.mjs.map