ts-node
Version:
TypeScript execution environment and REPL for node.js, with source map support
162 lines • 9.13 kB
JavaScript
;
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
return new (P || (P = Promise))(function (resolve, reject) {
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
step((generator = generator.apply(thisArg, _arguments || [])).next());
});
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.createEsmHooks = exports.registerAndCreateEsmHooks = void 0;
const index_1 = require("./index");
const url_1 = require("url");
const path_1 = require("path");
const assert = require("assert");
const util_1 = require("./util");
const { createResolve, } = require('../dist-raw/node-esm-resolve-implementation');
const { defaultGetFormat } = require('../dist-raw/node-esm-default-get-format');
// Note: On Windows, URLs look like this: file:///D:/dev/@TypeStrong/ts-node-examples/foo.ts
// NOTE ABOUT MULTIPLE EXPERIMENTAL LOADER APIS
//
// At the time of writing, this file implements 2x different loader APIs.
// Node made a breaking change to the loader API in https://github.com/nodejs/node/pull/37468
//
// We check the node version number and export either the *old* or the *new* API.
//
// Today, we are implementing the *new* API on top of our implementation of the *old* API,
// which relies on copy-pasted code from the *old* hooks implementation in node.
//
// In the future, we will likely invert this: we will copy-paste the *new* API implementation
// from node, build our implementation of the *new* API on top of it, and implement the *old*
// hooks API as a shim to the *new* API.
/** @internal */
function registerAndCreateEsmHooks(opts) {
// Automatically performs registration just like `-r ts-node/register`
const tsNodeInstance = (0, index_1.register)(opts);
return createEsmHooks(tsNodeInstance);
}
exports.registerAndCreateEsmHooks = registerAndCreateEsmHooks;
function createEsmHooks(tsNodeService) {
tsNodeService.enableExperimentalEsmLoaderInterop();
// Custom implementation that considers additional file extensions and automatically adds file extensions
const nodeResolveImplementation = createResolve(Object.assign(Object.assign({}, (0, index_1.getExtensions)(tsNodeService.config)), { preferTsExts: tsNodeService.options.preferTsExts }));
// The hooks API changed in node version X so we need to check for backwards compatibility.
// TODO: When the new API is backported to v12, v14, v16, update these version checks accordingly.
const newHooksAPI = (0, index_1.versionGteLt)(process.versions.node, '17.0.0') ||
(0, index_1.versionGteLt)(process.versions.node, '16.999.999', '17.0.0') ||
(0, index_1.versionGteLt)(process.versions.node, '14.999.999', '15.0.0') ||
(0, index_1.versionGteLt)(process.versions.node, '12.999.999', '13.0.0');
// Explicit return type to avoid TS's non-ideal inferred type
const hooksAPI = newHooksAPI
? { resolve, load, getFormat: undefined, transformSource: undefined }
: { resolve, getFormat, transformSource, load: undefined };
return hooksAPI;
function isFileUrlOrNodeStyleSpecifier(parsed) {
// We only understand file:// URLs, but in node, the specifier can be a node-style `./foo` or `foo`
const { protocol } = parsed;
return protocol === null || protocol === 'file:';
}
function resolve(specifier, context, defaultResolve) {
return __awaiter(this, void 0, void 0, function* () {
const defer = () => __awaiter(this, void 0, void 0, function* () {
const r = yield defaultResolve(specifier, context, defaultResolve);
return r;
});
const parsed = (0, url_1.parse)(specifier);
const { pathname, protocol, hostname } = parsed;
if (!isFileUrlOrNodeStyleSpecifier(parsed)) {
return defer();
}
if (protocol !== null && protocol !== 'file:') {
return defer();
}
// Malformed file:// URL? We should always see `null` or `''`
if (hostname) {
// TODO file://./foo sets `hostname` to `'.'`. Perhaps we should special-case this.
return defer();
}
// pathname is the path to be resolved
return nodeResolveImplementation.defaultResolve(specifier, context, defaultResolve);
});
}
// `load` from new loader hook API (See description at the top of this file)
function load(url, context, defaultLoad) {
var _a;
return __awaiter(this, void 0, void 0, function* () {
// If we get a format hint from resolve() on the context then use it
// otherwise call the old getFormat() hook using node's old built-in defaultGetFormat() that ships with ts-node
const format = (_a = context.format) !== null && _a !== void 0 ? _a : (yield getFormat(url, context, defaultGetFormat)).format;
let source = undefined;
if (format !== 'builtin' && format !== 'commonjs') {
// Call the new defaultLoad() to get the source
const { source: rawSource } = yield defaultLoad(url, { format }, defaultLoad);
if (rawSource === undefined || rawSource === null) {
throw new Error(`Failed to load raw source: Format was '${format}' and url was '${url}''.`);
}
// Emulate node's built-in old defaultTransformSource() so we can re-use the old transformSource() hook
const defaultTransformSource = (source, _context, _defaultTransformSource) => __awaiter(this, void 0, void 0, function* () { return ({ source }); });
// Call the old hook
const { source: transformedSource } = yield transformSource(rawSource, { url, format }, defaultTransformSource);
source = transformedSource;
}
return { format, source };
});
}
function getFormat(url, context, defaultGetFormat) {
return __awaiter(this, void 0, void 0, function* () {
const defer = (overrideUrl = url) => defaultGetFormat(overrideUrl, context, defaultGetFormat);
const parsed = (0, url_1.parse)(url);
if (!isFileUrlOrNodeStyleSpecifier(parsed)) {
return defer();
}
const { pathname } = parsed;
assert(pathname !== null, 'ESM getFormat() hook: URL should never have null pathname');
const nativePath = (0, url_1.fileURLToPath)(url);
// If file has .ts, .tsx, or .jsx extension, then ask node how it would treat this file if it were .js
const ext = (0, path_1.extname)(nativePath);
let nodeSays;
if (ext !== '.js' && !tsNodeService.ignored(nativePath)) {
nodeSays = yield defer((0, url_1.format)((0, url_1.pathToFileURL)(nativePath + '.js')));
}
else {
nodeSays = yield defer();
}
// For files compiled by ts-node that node believes are either CJS or ESM, check if we should override that classification
if (!tsNodeService.ignored(nativePath) &&
(nodeSays.format === 'commonjs' || nodeSays.format === 'module')) {
const { moduleType } = tsNodeService.moduleTypeClassifier.classifyModule((0, util_1.normalizeSlashes)(nativePath));
if (moduleType === 'cjs') {
return { format: 'commonjs' };
}
else if (moduleType === 'esm') {
return { format: 'module' };
}
}
return nodeSays;
});
}
function transformSource(source, context, defaultTransformSource) {
return __awaiter(this, void 0, void 0, function* () {
if (source === null || source === undefined) {
throw new Error('No source');
}
const defer = () => defaultTransformSource(source, context, defaultTransformSource);
const sourceAsString = typeof source === 'string' ? source : source.toString('utf8');
const { url } = context;
const parsed = (0, url_1.parse)(url);
if (!isFileUrlOrNodeStyleSpecifier(parsed)) {
return defer();
}
const nativePath = (0, url_1.fileURLToPath)(url);
if (tsNodeService.ignored(nativePath)) {
return defer();
}
const emittedJs = tsNodeService.compile(sourceAsString, nativePath);
return { source: emittedJs };
});
}
}
exports.createEsmHooks = createEsmHooks;
//# sourceMappingURL=esm.js.map