node-cjs-loader-utils.js 5.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122
  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. const path = require('path');
  5. const packageJsonReader = require('./node-package-json-reader');
  6. const {JSONParse} = require('./node-primordials');
  7. module.exports.assertScriptCanLoadAsCJSImpl = assertScriptCanLoadAsCJSImpl;
  8. // copied from Module._extensions['.js']
  9. // https://github.com/nodejs/node/blob/v15.3.0/lib/internal/modules/cjs/loader.js#L1113-L1120
  10. function assertScriptCanLoadAsCJSImpl(filename) {
  11. const pkg = readPackageScope(filename);
  12. // Function require shouldn't be used in ES modules.
  13. if (pkg && pkg.data && pkg.data.type === 'module') {
  14. const parentPath = module.parent && module.parent.filename;
  15. const packageJsonPath = path.resolve(pkg.path, 'package.json');
  16. throw createErrRequireEsm(filename, parentPath, packageJsonPath);
  17. }
  18. }
  19. // Copied from https://github.com/nodejs/node/blob/2d5d77306f6dff9110c1f77fefab25f973415770/lib/internal/modules/cjs/loader.js#L285-L301
  20. function readPackageScope(checkPath) {
  21. const rootSeparatorIndex = checkPath.indexOf(path.sep);
  22. let separatorIndex;
  23. while (
  24. (separatorIndex = checkPath.lastIndexOf(path.sep)) > rootSeparatorIndex
  25. ) {
  26. checkPath = checkPath.slice(0, separatorIndex);
  27. if (checkPath.endsWith(path.sep + 'node_modules'))
  28. return false;
  29. const pjson = readPackage(checkPath);
  30. if (pjson) return {
  31. path: checkPath,
  32. data: pjson
  33. };
  34. }
  35. return false;
  36. }
  37. // Copied from https://github.com/nodejs/node/blob/2d5d77306f6dff9110c1f77fefab25f973415770/lib/internal/modules/cjs/loader.js#L249
  38. const packageJsonCache = new Map();
  39. // Copied from https://github.com/nodejs/node/blob/v15.3.0/lib/internal/modules/cjs/loader.js#L275-L304
  40. function readPackage(requestPath) {
  41. const jsonPath = path.resolve(requestPath, 'package.json');
  42. const existing = packageJsonCache.get(jsonPath);
  43. if (existing !== undefined) return existing;
  44. const result = packageJsonReader.read(jsonPath);
  45. const json = result.containsKeys === false ? '{}' : result.string;
  46. if (json === undefined) {
  47. packageJsonCache.set(jsonPath, false);
  48. return false;
  49. }
  50. try {
  51. const parsed = JSONParse(json);
  52. const filtered = {
  53. name: parsed.name,
  54. main: parsed.main,
  55. exports: parsed.exports,
  56. imports: parsed.imports,
  57. type: parsed.type
  58. };
  59. packageJsonCache.set(jsonPath, filtered);
  60. return filtered;
  61. } catch (e) {
  62. e.path = jsonPath;
  63. e.message = 'Error parsing ' + jsonPath + ': ' + e.message;
  64. throw e;
  65. }
  66. }
  67. // Native ERR_REQUIRE_ESM Error is declared here:
  68. // https://github.com/nodejs/node/blob/2d5d77306f6dff9110c1f77fefab25f973415770/lib/internal/errors.js#L1294-L1313
  69. // Error class factory is implemented here:
  70. // function E: https://github.com/nodejs/node/blob/2d5d77306f6dff9110c1f77fefab25f973415770/lib/internal/errors.js#L323-L341
  71. // function makeNodeErrorWithCode: https://github.com/nodejs/node/blob/2d5d77306f6dff9110c1f77fefab25f973415770/lib/internal/errors.js#L251-L278
  72. // The code below should create an error that matches the native error as closely as possible.
  73. // Third-party libraries which attempt to catch the native ERR_REQUIRE_ESM should recognize our imitation error.
  74. function createErrRequireEsm(filename, parentPath, packageJsonPath) {
  75. const code = 'ERR_REQUIRE_ESM'
  76. const err = new Error(getMessage(filename, parentPath, packageJsonPath))
  77. // Set `name` to be used in stack trace, generate stack trace with that name baked in, then re-declare the `name` field.
  78. // This trick is copied from node's source.
  79. err.name = `Error [${ code }]`
  80. err.stack
  81. Object.defineProperty(err, 'name', {
  82. value: 'Error',
  83. enumerable: false,
  84. writable: true,
  85. configurable: true
  86. })
  87. err.code = code
  88. return err
  89. // Copy-pasted from https://github.com/nodejs/node/blob/b533fb3508009e5f567cc776daba8fbf665386a6/lib/internal/errors.js#L1293-L1311
  90. // so that our error message is identical to the native message.
  91. function getMessage(filename, parentPath = null, packageJsonPath = null) {
  92. const ext = path.extname(filename)
  93. let msg = `Must use import to load ES Module: ${filename}`;
  94. if (parentPath && packageJsonPath) {
  95. const path = require('path');
  96. const basename = path.basename(filename) === path.basename(parentPath) ?
  97. filename : path.basename(filename);
  98. msg +=
  99. '\nrequire() of ES modules is not supported.\nrequire() of ' +
  100. `${filename} ${parentPath ? `from ${parentPath} ` : ''}` +
  101. `is an ES module file as it is a ${ext} file whose nearest parent ` +
  102. `package.json contains "type": "module" which defines all ${ext} ` +
  103. 'files in that package scope as ES modules.\nInstead ' +
  104. 'change the requiring code to use ' +
  105. 'import(), or remove "type": "module" from ' +
  106. `${packageJsonPath}.\n`;
  107. return msg;
  108. }
  109. return msg;
  110. }
  111. }