123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154 |
- const path = require('path');
- class Loader {
- constructor(options) {
- options = options || {};
- this.require_ = options.requireShim || requireShim;
- this.import_ = options.importShim || importShim;
- this.resolvePath_ = options.resolvePath || path.resolve.bind(path);
- this.alwaysImport = true;
- }
- load(modulePath) {
- if ((this.alwaysImport && !modulePath.endsWith('.json')) || modulePath.endsWith('.mjs')) {
- let importSpecifier;
- if (modulePath.indexOf(path.sep) === -1 && modulePath.indexOf('/') === -1) {
- importSpecifier = modulePath;
- } else {
- // The ES module spec requires import paths to be valid URLs. As of v14,
- // Node enforces this on Windows but not on other OSes. On OS X, import
- // paths that are URLs must not contain parent directory references.
- importSpecifier = `file://${this.resolvePath_(modulePath)}`;
- }
- return this.import_(importSpecifier)
- .then(
- mod => mod.default,
- e => {
- if (e.code === 'ERR_UNKNOWN_FILE_EXTENSION') {
- // Extension isn't supported by import, e.g. .jsx. Fall back to
- // require(). This could lead to confusing error messages if someone
- // tries to use ES module syntax without transpiling in a file with
- // an unsupported extension, but it shouldn't break anything and it
- // should work well in the normal case where the file is loadable
- // as a CommonJS module, either directly or with the help of a
- // loader like `@babel/register`.
- return this.require_(modulePath);
- } else {
- return Promise.reject(fixupImportException(e, modulePath));
- }
- }
- );
- } else {
- return new Promise(resolve => {
- const result = this.require_(modulePath);
- resolve(result);
- });
- }
- }
- }
- function requireShim(modulePath) {
- return require(modulePath);
- }
- function importShim(modulePath) {
- return import(modulePath);
- }
- function fixupImportException(e, importedPath) {
- // When an ES module has a syntax error, the resulting exception does not
- // include the filename, which the user will need to debug the problem. We
- // need to fix those up to include the filename. However, other kinds of load-
- // time errors *do* include the filename and usually the line number. We need
- // to leave those alone.
- //
- // Some examples of load-time errors that we need to deal with:
- // 1. Syntax error in an ESM spec:
- // SyntaxError: missing ) after argument list
- // at Loader.moduleStrategy (node:internal/modules/esm/translators:147:18)
- // at async link (node:internal/modules/esm/module_job:64:21)
- //
- // 2. Syntax error in an ES module imported from an ESM spec. This is exactly
- // the same as #1: there is no way to tell which file actually has the syntax
- // error.
- //
- // 3. Syntax error in a CommonJS module imported by an ES module:
- // /path/to/commonjs_with_syntax_error.js:2
- //
- //
- //
- // SyntaxError: Unexpected end of input
- // at Object.compileFunction (node:vm:355:18)
- // at wrapSafe (node:internal/modules/cjs/loader:1038:15)
- // at Module._compile (node:internal/modules/cjs/loader:1072:27)
- // at Object.Module._extensions..js (node:internal/modules/cjs/loader:1137:10)
- // at Module.load (node:internal/modules/cjs/loader:988:32)
- // at Function.Module._load (node:internal/modules/cjs/loader:828:14)
- // at ModuleWrap.<anonymous> (node:internal/modules/esm/translators:201:29)
- // at ModuleJob.run (node:internal/modules/esm/module_job:175:25)
- // at async Loader.import (node:internal/modules/esm/loader:178:24)
- // at async file:///path/to/esm_that_imported_cjs.mjs:2:11
- //
- // Note: For Jasmine's purposes, case 3 only occurs in Node >= 14.8. Older
- // versions don't support top-level await, without which it's not possible to
- // load a CommonJS module from an ES module at load-time. The entire content
- // above, including the file path and the three blank lines, is part of the
- // error's `stack` property. There may or may not be any stack trace after the
- // SyntaxError line, and if there's a stack trace it may or may not contain
- // any useful information.
- //
- // 4. Any other kind of exception thrown at load time
- //
- // Error: nope
- // at Object.<anonymous> (/path/to/file_throwing_error.js:1:7)
- // at Module._compile (node:internal/modules/cjs/loader:1108:14)
- // at Object.Module._extensions..js (node:internal/modules/cjs/loader:1137:10)
- // at Module.load (node:internal/modules/cjs/loader:988:32)
- // at Function.Module._load (node:internal/modules/cjs/loader:828:14)
- // at ModuleWrap.<anonymous> (node:internal/modules/esm/translators:201:29)
- // at ModuleJob.run (node:internal/modules/esm/module_job:175:25)
- // at async Loader.import (node:internal/modules/esm/loader:178:24)
- // at async file:///path_to_file_importing_broken_file.mjs:1:1
- //
- // We need to replace the error with a useful one in cases 1 and 2, but not in
- // cases 3 and 4. Distinguishing among them can be tricky. Simple heuristics
- // like checking the stack trace for the name of the file we imported fail
- // because it often shows up even when the error was elsewhere, e.g. at the
- // bottom of the stack traces in the examples for cases 3 and 4 above. To add
- // to the fun, file paths in errors on Windows can be either Windows style
- // paths (c:\path\to\file.js) or URLs (file:///c:/path/to/file.js).
- if (!(e instanceof SyntaxError)) {
- return e;
- }
- const escapedWin = escapeStringForRegexp(importedPath.replace(/\//g, '\\'));
- const windowsPathRegex = new RegExp('[a-zA-z]:\\\\([^\\s]+\\\\|)' + escapedWin);
- const windowsUrlRegex = new RegExp('file:///[a-zA-z]:\\\\([^\\s]+\\\\|)' + escapedWin);
- const anyUnixPathFirstLineRegex = /^\/[^\s:]+:\d/;
- const anyWindowsPathFirstLineRegex = /^[a-zA-Z]:(\\[^\s\\:]+)+:/;
- if (e.message.indexOf(importedPath) !== -1
- || e.stack.indexOf(importedPath) !== -1
- || e.stack.match(windowsPathRegex) || e.stack.match(windowsUrlRegex)
- || e.stack.match(anyUnixPathFirstLineRegex)
- || e.stack.match(anyWindowsPathFirstLineRegex)) {
- return e;
- } else {
- return new Error(`While loading ${importedPath}: ${e.constructor.name}: ${e.message}`);
- }
- }
- // Adapted from Sindre Sorhus's escape-string-regexp (MIT license)
- function escapeStringForRegexp(string) {
- // Escape characters with special meaning either inside or outside character sets.
- // Use a simple backslash escape when it’s always valid, and a `\xnn` escape when the simpler form would be disallowed by Unicode patterns’ stricter grammar.
- return string
- .replace(/[|\\{}()[\]^$+*?.]/g, '\\$&')
- .replace(/-/g, '\\x2d');
- }
- module.exports = Loader;
|