node-internal-modules-cjs-loader.js 19 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593
  1. // Copied from several files in node's source code.
  2. // https://github.com/nodejs/node/blob/2d5d77306f6dff9110c1f77fefab25f973415770/lib/internal/modules/cjs/loader.js
  3. // Each function and variable below must have a comment linking to the source in node's github repo.
  4. 'use strict';
  5. const {
  6. ArrayIsArray,
  7. ArrayPrototypeIncludes,
  8. ArrayPrototypeJoin,
  9. ArrayPrototypePush,
  10. JSONParse,
  11. ObjectKeys,
  12. RegExpPrototypeTest,
  13. SafeMap,
  14. SafeWeakMap,
  15. StringPrototypeCharCodeAt,
  16. StringPrototypeEndsWith,
  17. StringPrototypeLastIndexOf,
  18. StringPrototypeIndexOf,
  19. StringPrototypeMatch,
  20. StringPrototypeSlice,
  21. StringPrototypeStartsWith,
  22. } = require('./node-primordials');
  23. const { NativeModule } = require('./node-nativemodule');
  24. const { pathToFileURL, fileURLToPath } = require('url');
  25. const fs = require('fs');
  26. const path = require('path');
  27. const { sep } = path;
  28. const { internalModuleStat } = require('./node-internalBinding-fs');
  29. const packageJsonReader = require('./node-internal-modules-package_json_reader');
  30. const {
  31. cjsConditions,
  32. } = require('./node-internal-modules-cjs-helpers');
  33. const { getOptionValue } = require('./node-options');
  34. const preserveSymlinks = getOptionValue('--preserve-symlinks');
  35. const preserveSymlinksMain = getOptionValue('--preserve-symlinks-main');
  36. const {normalizeSlashes} = require('../dist/util');
  37. const {createErrRequireEsm} = require('./node-internal-errors');
  38. const {
  39. codes: {
  40. ERR_INVALID_MODULE_SPECIFIER,
  41. },
  42. } = require('./node-internal-errors');
  43. const {
  44. CHAR_FORWARD_SLASH,
  45. } = require('./node-internal-constants');
  46. const Module = require('module');
  47. const isWindows = process.platform === 'win32';
  48. let statCache = null;
  49. function stat(filename) {
  50. filename = path.toNamespacedPath(filename);
  51. if (statCache !== null) {
  52. const result = statCache.get(filename);
  53. if (result !== undefined) return result;
  54. }
  55. const result = internalModuleStat(filename);
  56. if (statCache !== null && result >= 0) {
  57. // Only set cache when `internalModuleStat(filename)` succeeds.
  58. statCache.set(filename, result);
  59. }
  60. return result;
  61. }
  62. // Note:
  63. // we cannot get access to node's internal cache, which is populated from
  64. // within node's Module constructor. So the cache here will always be empty.
  65. // It's possible we could approximate our own cache by building it up with
  66. // hacky workarounds, but it's not worth the complexity and flakiness.
  67. const moduleParentCache = new SafeWeakMap();
  68. // Given a module name, and a list of paths to test, returns the first
  69. // matching file in the following precedence.
  70. //
  71. // require("a.<ext>")
  72. // -> a.<ext>
  73. //
  74. // require("a")
  75. // -> a
  76. // -> a.<ext>
  77. // -> a/index.<ext>
  78. const packageJsonCache = new SafeMap();
  79. function readPackage(requestPath) {
  80. const jsonPath = path.resolve(requestPath, 'package.json');
  81. const existing = packageJsonCache.get(jsonPath);
  82. if (existing !== undefined) return existing;
  83. const result = packageJsonReader.read(jsonPath);
  84. const json = result.containsKeys === false ? '{}' : result.string;
  85. if (json === undefined) {
  86. packageJsonCache.set(jsonPath, false);
  87. return false;
  88. }
  89. try {
  90. const parsed = JSONParse(json);
  91. const filtered = {
  92. name: parsed.name,
  93. main: parsed.main,
  94. exports: parsed.exports,
  95. imports: parsed.imports,
  96. type: parsed.type
  97. };
  98. packageJsonCache.set(jsonPath, filtered);
  99. return filtered;
  100. } catch (e) {
  101. e.path = jsonPath;
  102. e.message = 'Error parsing ' + jsonPath + ': ' + e.message;
  103. throw e;
  104. }
  105. }
  106. function readPackageScope(checkPath) {
  107. const rootSeparatorIndex = StringPrototypeIndexOf(checkPath, sep);
  108. let separatorIndex;
  109. do {
  110. separatorIndex = StringPrototypeLastIndexOf(checkPath, sep);
  111. checkPath = StringPrototypeSlice(checkPath, 0, separatorIndex);
  112. if (StringPrototypeEndsWith(checkPath, sep + 'node_modules'))
  113. return false;
  114. const pjson = readPackage(checkPath + sep);
  115. if (pjson) return {
  116. data: pjson,
  117. path: checkPath,
  118. };
  119. } while (separatorIndex > rootSeparatorIndex);
  120. return false;
  121. }
  122. /**
  123. * @param {{
  124. * nodeEsmResolver: ReturnType<typeof import('./node-internal-modules-esm-resolve').createResolve>,
  125. * extensions: import('../src/file-extensions').Extensions,
  126. * preferTsExts
  127. * }} opts
  128. */
  129. function createCjsLoader(opts) {
  130. const {nodeEsmResolver, preferTsExts} = opts;
  131. const {replacementsForCjs, replacementsForJs, replacementsForMjs, replacementsForJsx} = opts.extensions;
  132. const {
  133. encodedSepRegEx,
  134. packageExportsResolve,
  135. packageImportsResolve
  136. } = nodeEsmResolver;
  137. function tryPackage(requestPath, exts, isMain, originalPath) {
  138. // const pkg = readPackage(requestPath)?.main;
  139. const tmp = readPackage(requestPath)
  140. const pkg = tmp != null ? tmp.main : undefined;
  141. if (!pkg) {
  142. return tryExtensions(path.resolve(requestPath, 'index'), exts, isMain);
  143. }
  144. const filename = path.resolve(requestPath, pkg);
  145. let actual = tryReplacementExtensions(filename, isMain) ||
  146. tryFile(filename, isMain) ||
  147. tryExtensions(filename, exts, isMain) ||
  148. tryExtensions(path.resolve(filename, 'index'), exts, isMain);
  149. if (actual === false) {
  150. actual = tryExtensions(path.resolve(requestPath, 'index'), exts, isMain);
  151. if (!actual) {
  152. // eslint-disable-next-line no-restricted-syntax
  153. const err = new Error(
  154. `Cannot find module '${filename}'. ` +
  155. 'Please verify that the package.json has a valid "main" entry'
  156. );
  157. err.code = 'MODULE_NOT_FOUND';
  158. err.path = path.resolve(requestPath, 'package.json');
  159. err.requestPath = originalPath;
  160. // TODO(BridgeAR): Add the requireStack as well.
  161. throw err;
  162. } else {
  163. const jsonPath = path.resolve(requestPath, 'package.json');
  164. process.emitWarning(
  165. `Invalid 'main' field in '${jsonPath}' of '${pkg}'. ` +
  166. 'Please either fix that or report it to the module author',
  167. 'DeprecationWarning',
  168. 'DEP0128'
  169. );
  170. }
  171. }
  172. return actual;
  173. }
  174. // In order to minimize unnecessary lstat() calls,
  175. // this cache is a list of known-real paths.
  176. // Set to an empty Map to reset.
  177. const realpathCache = new SafeMap();
  178. // Check if the file exists and is not a directory
  179. // if using --preserve-symlinks and isMain is false,
  180. // keep symlinks intact, otherwise resolve to the
  181. // absolute realpath.
  182. function tryFile(requestPath, isMain) {
  183. const rc = stat(requestPath);
  184. if (rc !== 0) return;
  185. if (preserveSymlinks && !isMain) {
  186. return path.resolve(requestPath);
  187. }
  188. return toRealPath(requestPath);
  189. }
  190. function toRealPath(requestPath) {
  191. return fs.realpathSync(requestPath, {
  192. // [internalFS.realpathCacheKey]: realpathCache
  193. });
  194. }
  195. function statReplacementExtensions(p) {
  196. const lastDotIndex = p.lastIndexOf('.');
  197. if(lastDotIndex >= 0) {
  198. const ext = p.slice(lastDotIndex);
  199. if (ext === '.js' || ext === '.jsx' || ext === '.mjs' || ext === '.cjs') {
  200. const pathnameWithoutExtension = p.slice(0, lastDotIndex);
  201. const replacementExts =
  202. ext === '.js' ? replacementsForJs
  203. : ext === '.jsx' ? replacementsForJsx
  204. : ext === '.mjs' ? replacementsForMjs
  205. : replacementsForCjs;
  206. for (let i = 0; i < replacementExts.length; i++) {
  207. const filename = pathnameWithoutExtension + replacementExts[i];
  208. const rc = stat(filename);
  209. if (rc === 0) {
  210. return [rc, filename];
  211. }
  212. }
  213. }
  214. }
  215. return [stat(p), p];
  216. }
  217. function tryReplacementExtensions(p, isMain) {
  218. const lastDotIndex = p.lastIndexOf('.');
  219. if(lastDotIndex >= 0) {
  220. const ext = p.slice(lastDotIndex);
  221. if (ext === '.js' || ext === '.jsx' || ext === '.mjs' || ext === '.cjs') {
  222. const pathnameWithoutExtension = p.slice(0, lastDotIndex);
  223. const replacementExts =
  224. ext === '.js' ? replacementsForJs
  225. : ext === '.jsx' ? replacementsForJsx
  226. : ext === '.mjs' ? replacementsForMjs
  227. : replacementsForCjs;
  228. for (let i = 0; i < replacementExts.length; i++) {
  229. const filename = tryFile(pathnameWithoutExtension + replacementExts[i], isMain);
  230. if (filename) {
  231. return filename;
  232. }
  233. }
  234. }
  235. }
  236. return false;
  237. }
  238. // Given a path, check if the file exists with any of the set extensions
  239. function tryExtensions(p, exts, isMain) {
  240. for (let i = 0; i < exts.length; i++) {
  241. const filename = tryFile(p + exts[i], isMain);
  242. if (filename) {
  243. return filename;
  244. }
  245. }
  246. return false;
  247. }
  248. function trySelfParentPath(parent) {
  249. if (!parent) return false;
  250. if (parent.filename) {
  251. return parent.filename;
  252. } else if (parent.id === '<repl>' || parent.id === 'internal/preload') {
  253. try {
  254. return process.cwd() + path.sep;
  255. } catch {
  256. return false;
  257. }
  258. }
  259. }
  260. function trySelf(parentPath, request) {
  261. if (!parentPath) return false;
  262. const { data: pkg, path: pkgPath } = readPackageScope(parentPath) || {};
  263. if (!pkg || pkg.exports === undefined) return false;
  264. if (typeof pkg.name !== 'string') return false;
  265. let expansion;
  266. if (request === pkg.name) {
  267. expansion = '.';
  268. } else if (StringPrototypeStartsWith(request, `${pkg.name}/`)) {
  269. expansion = '.' + StringPrototypeSlice(request, pkg.name.length);
  270. } else {
  271. return false;
  272. }
  273. try {
  274. return finalizeEsmResolution(packageExportsResolve(
  275. pathToFileURL(pkgPath + '/package.json'), expansion, pkg,
  276. pathToFileURL(parentPath), cjsConditions).resolved, parentPath, pkgPath);
  277. } catch (e) {
  278. if (e.code === 'ERR_MODULE_NOT_FOUND')
  279. throw createEsmNotFoundErr(request, pkgPath + '/package.json');
  280. throw e;
  281. }
  282. }
  283. // This only applies to requests of a specific form:
  284. // 1. name/.*
  285. // 2. @scope/name/.*
  286. const EXPORTS_PATTERN = /^((?:@[^/\\%]+\/)?[^./\\%][^/\\%]*)(\/.*)?$/;
  287. function resolveExports(nmPath, request) {
  288. // The implementation's behavior is meant to mirror resolution in ESM.
  289. const { 1: name, 2: expansion = '' } =
  290. StringPrototypeMatch(request, EXPORTS_PATTERN) || [];
  291. if (!name)
  292. return;
  293. const pkgPath = path.resolve(nmPath, name);
  294. const pkg = readPackage(pkgPath);
  295. // if (pkg?.exports != null) {
  296. if (pkg != null && pkg.exports != null) {
  297. try {
  298. return finalizeEsmResolution(packageExportsResolve(
  299. pathToFileURL(pkgPath + '/package.json'), '.' + expansion, pkg, null,
  300. cjsConditions).resolved, null, pkgPath);
  301. } catch (e) {
  302. if (e.code === 'ERR_MODULE_NOT_FOUND')
  303. throw createEsmNotFoundErr(request, pkgPath + '/package.json');
  304. throw e;
  305. }
  306. }
  307. }
  308. // Backwards compat for old node versions
  309. const hasModulePathCache = !!require('module')._pathCache;
  310. const Module_pathCache = Object.create(null);
  311. const Module_pathCache_get = hasModulePathCache ? (cacheKey) => Module._pathCache[cacheKey] : (cacheKey) => Module_pathCache[cacheKey];
  312. const Module_pathCache_set = hasModulePathCache ? (cacheKey, value) => (Module._pathCache[cacheKey] = value) : (cacheKey) => (Module_pathCache[cacheKey] = value);
  313. const trailingSlashRegex = /(?:^|\/)\.?\.$/;
  314. const Module_findPath = function _findPath(request, paths, isMain) {
  315. const absoluteRequest = path.isAbsolute(request);
  316. if (absoluteRequest) {
  317. paths = [''];
  318. } else if (!paths || paths.length === 0) {
  319. return false;
  320. }
  321. const cacheKey = request + '\x00' + ArrayPrototypeJoin(paths, '\x00');
  322. const entry = Module_pathCache_get(cacheKey);
  323. if (entry)
  324. return entry;
  325. let exts;
  326. let trailingSlash = request.length > 0 &&
  327. StringPrototypeCharCodeAt(request, request.length - 1) ===
  328. CHAR_FORWARD_SLASH;
  329. if (!trailingSlash) {
  330. trailingSlash = RegExpPrototypeTest(trailingSlashRegex, request);
  331. }
  332. // For each path
  333. for (let i = 0; i < paths.length; i++) {
  334. // Don't search further if path doesn't exist
  335. const curPath = paths[i];
  336. if (curPath && stat(curPath) < 1) continue;
  337. if (!absoluteRequest) {
  338. const exportsResolved = resolveExports(curPath, request);
  339. if (exportsResolved)
  340. return exportsResolved;
  341. }
  342. const _basePath = path.resolve(curPath, request);
  343. let filename;
  344. const [rc, basePath] = statReplacementExtensions(_basePath);
  345. if (!trailingSlash) {
  346. if (rc === 0) { // File.
  347. if (!isMain) {
  348. if (preserveSymlinks) {
  349. filename = path.resolve(basePath);
  350. } else {
  351. filename = toRealPath(basePath);
  352. }
  353. } else if (preserveSymlinksMain) {
  354. // For the main module, we use the preserveSymlinksMain flag instead
  355. // mainly for backward compatibility, as the preserveSymlinks flag
  356. // historically has not applied to the main module. Most likely this
  357. // was intended to keep .bin/ binaries working, as following those
  358. // symlinks is usually required for the imports in the corresponding
  359. // files to resolve; that said, in some use cases following symlinks
  360. // causes bigger problems which is why the preserveSymlinksMain option
  361. // is needed.
  362. filename = path.resolve(basePath);
  363. } else {
  364. filename = toRealPath(basePath);
  365. }
  366. }
  367. if (!filename) {
  368. // Try it with each of the extensions
  369. if (exts === undefined)
  370. exts = ObjectKeys(Module._extensions);
  371. filename = tryExtensions(basePath, exts, isMain);
  372. }
  373. }
  374. if (!filename && rc === 1) { // Directory.
  375. // try it with each of the extensions at "index"
  376. if (exts === undefined)
  377. exts = ObjectKeys(Module._extensions);
  378. filename = tryPackage(basePath, exts, isMain, request);
  379. }
  380. if (filename) {
  381. Module_pathCache_set(cacheKey, filename);
  382. return filename;
  383. }
  384. }
  385. return false;
  386. };
  387. const Module_resolveFilename = function _resolveFilename(request, parent, isMain, options) {
  388. if (StringPrototypeStartsWith(request, 'node:') ||
  389. NativeModule.canBeRequiredByUsers(request)) {
  390. return request;
  391. }
  392. let paths;
  393. if (typeof options === 'object' && options !== null) {
  394. if (ArrayIsArray(options.paths)) {
  395. const isRelative = StringPrototypeStartsWith(request, './') ||
  396. StringPrototypeStartsWith(request, '../') ||
  397. ((isWindows && StringPrototypeStartsWith(request, '.\\')) ||
  398. StringPrototypeStartsWith(request, '..\\'));
  399. if (isRelative) {
  400. paths = options.paths;
  401. } else {
  402. const fakeParent = new Module('', null);
  403. paths = [];
  404. for (let i = 0; i < options.paths.length; i++) {
  405. const path = options.paths[i];
  406. fakeParent.paths = Module._nodeModulePaths(path);
  407. const lookupPaths = Module._resolveLookupPaths(request, fakeParent);
  408. for (let j = 0; j < lookupPaths.length; j++) {
  409. if (!ArrayPrototypeIncludes(paths, lookupPaths[j]))
  410. ArrayPrototypePush(paths, lookupPaths[j]);
  411. }
  412. }
  413. }
  414. } else if (options.paths === undefined) {
  415. paths = Module._resolveLookupPaths(request, parent);
  416. } else {
  417. throw new ERR_INVALID_ARG_VALUE('options.paths', options.paths);
  418. }
  419. } else {
  420. paths = Module._resolveLookupPaths(request, parent);
  421. }
  422. // if (parent?.filename) {
  423. // node 12 hack
  424. if (parent != null && parent.filename) {
  425. if (request[0] === '#') {
  426. const pkg = readPackageScope(parent.filename) || {};
  427. // if (pkg.data?.imports != null) {
  428. // node 12 hack
  429. if (pkg.data != null && pkg.data.imports != null) {
  430. try {
  431. return finalizeEsmResolution(
  432. packageImportsResolve(request, pathToFileURL(parent.filename),
  433. cjsConditions), parent.filename,
  434. pkg.path);
  435. } catch (e) {
  436. if (e.code === 'ERR_MODULE_NOT_FOUND')
  437. throw createEsmNotFoundErr(request);
  438. throw e;
  439. }
  440. }
  441. }
  442. }
  443. // Try module self resolution first
  444. const parentPath = trySelfParentPath(parent);
  445. const selfResolved = trySelf(parentPath, request);
  446. if (selfResolved) {
  447. const cacheKey = request + '\x00' +
  448. (paths.length === 1 ? paths[0] : ArrayPrototypeJoin(paths, '\x00'));
  449. Module._pathCache[cacheKey] = selfResolved;
  450. return selfResolved;
  451. }
  452. // Look up the filename first, since that's the cache key.
  453. const filename = Module._findPath(request, paths, isMain, false);
  454. if (filename) return filename;
  455. const requireStack = [];
  456. for (let cursor = parent;
  457. cursor;
  458. cursor = moduleParentCache.get(cursor)) {
  459. ArrayPrototypePush(requireStack, cursor.filename || cursor.id);
  460. }
  461. let message = `Cannot find module '${request}'`;
  462. if (requireStack.length > 0) {
  463. message = message + '\nRequire stack:\n- ' +
  464. ArrayPrototypeJoin(requireStack, '\n- ');
  465. }
  466. // eslint-disable-next-line no-restricted-syntax
  467. const err = new Error(message);
  468. err.code = 'MODULE_NOT_FOUND';
  469. err.requireStack = requireStack;
  470. throw err;
  471. };
  472. function finalizeEsmResolution(resolved, parentPath, pkgPath) {
  473. if (RegExpPrototypeTest(encodedSepRegEx, resolved))
  474. throw new ERR_INVALID_MODULE_SPECIFIER(
  475. resolved, 'must not include encoded "/" or "\\" characters', parentPath);
  476. const filename = fileURLToPath(resolved);
  477. const actual = tryReplacementExtensions(filename) || tryFile(filename);
  478. if (actual)
  479. return actual;
  480. const err = createEsmNotFoundErr(filename,
  481. path.resolve(pkgPath, 'package.json'));
  482. throw err;
  483. }
  484. function createEsmNotFoundErr(request, path) {
  485. // eslint-disable-next-line no-restricted-syntax
  486. const err = new Error(`Cannot find module '${request}'`);
  487. err.code = 'MODULE_NOT_FOUND';
  488. if (path)
  489. err.path = path;
  490. // TODO(BridgeAR): Add the requireStack as well.
  491. return err;
  492. }
  493. return {
  494. Module_findPath,
  495. Module_resolveFilename
  496. }
  497. }
  498. /**
  499. * copied from Module._extensions['.js']
  500. * https://github.com/nodejs/node/blob/v15.3.0/lib/internal/modules/cjs/loader.js#L1113-L1120
  501. * @param {import('../src/index').Service} service
  502. * @param {NodeJS.Module} module
  503. * @param {string} filename
  504. */
  505. function assertScriptCanLoadAsCJSImpl(service, module, filename) {
  506. const pkg = readPackageScope(filename);
  507. // ts-node modification: allow our configuration to override
  508. const tsNodeClassification = service.moduleTypeClassifier.classifyModuleByModuleTypeOverrides(normalizeSlashes(filename));
  509. if(tsNodeClassification.moduleType === 'cjs') return;
  510. // ignore package.json when file extension is ESM-only or CJS-only
  511. // [MUST_UPDATE_FOR_NEW_FILE_EXTENSIONS]
  512. const lastDotIndex = filename.lastIndexOf('.');
  513. const ext = lastDotIndex >= 0 ? filename.slice(lastDotIndex) : '';
  514. if((ext === '.cts' || ext === '.cjs') && tsNodeClassification.moduleType === 'auto') return;
  515. // Function require shouldn't be used in ES modules.
  516. if (ext === '.mts' || ext === '.mjs' || tsNodeClassification.moduleType === 'esm' || (pkg && pkg.data && pkg.data.type === 'module')) {
  517. const parentPath = module.parent && module.parent.filename;
  518. const packageJsonPath = pkg ? path.resolve(pkg.path, 'package.json') : null;
  519. throw createErrRequireEsm(filename, parentPath, packageJsonPath);
  520. }
  521. }
  522. module.exports = {
  523. createCjsLoader,
  524. assertScriptCanLoadAsCJSImpl,
  525. readPackageScope
  526. };