diff --git a/dist-raw/node-errors.js b/dist-raw/node-errors.js new file mode 100644 index 000000000..2446e2a68 --- /dev/null +++ b/dist-raw/node-errors.js @@ -0,0 +1,29 @@ +exports.codes = {}; + +function defineError(code, buildMessage) { + if (!buildMessage) { + buildMessage = (...args) => args.join(' '); + } + + exports.codes[code] = class CustomError extends Error { + constructor(...args) { + super(`${code}: ${buildMessage(...args)}`); + this.code = code; + } + } +} + +defineError("ERR_INPUT_TYPE_NOT_ALLOWED"); +defineError("ERR_INVALID_ARG_VALUE"); +defineError("ERR_INVALID_MODULE_SPECIFIER"); +defineError("ERR_INVALID_PACKAGE_CONFIG"); +defineError("ERR_INVALID_PACKAGE_TARGET"); +defineError("ERR_MANIFEST_DEPENDENCY_MISSING"); +defineError("ERR_MODULE_NOT_FOUND", (path, base, type = 'package') => { + return `Cannot find ${type} '${path}' imported from ${base}`; +}); +defineError("ERR_PACKAGE_IMPORT_NOT_DEFINED"); +defineError("ERR_PACKAGE_PATH_NOT_EXPORTED"); +defineError("ERR_UNSUPPORTED_DIR_IMPORT"); +defineError("ERR_UNSUPPORTED_ESM_URL_SCHEME"); +defineError("ERR_UNKNOWN_FILE_EXTENSION"); diff --git a/dist-raw/node-internal-errors.js b/dist-raw/node-internal-errors.js index ddcd66178..ed83c310a 100644 --- a/dist-raw/node-internal-errors.js +++ b/dist-raw/node-internal-errors.js @@ -2,36 +2,46 @@ const path = require('path'); -exports.codes = { - ERR_INPUT_TYPE_NOT_ALLOWED: createErrorCtor(joinArgs('ERR_INPUT_TYPE_NOT_ALLOWED')), - ERR_INVALID_ARG_VALUE: createErrorCtor(joinArgs('ERR_INVALID_ARG_VALUE')), - ERR_INVALID_MODULE_SPECIFIER: createErrorCtor(joinArgs('ERR_INVALID_MODULE_SPECIFIER')), - ERR_INVALID_PACKAGE_CONFIG: createErrorCtor(joinArgs('ERR_INVALID_PACKAGE_CONFIG')), - ERR_INVALID_PACKAGE_TARGET: createErrorCtor(joinArgs('ERR_INVALID_PACKAGE_TARGET')), - ERR_MANIFEST_DEPENDENCY_MISSING: createErrorCtor(joinArgs('ERR_MANIFEST_DEPENDENCY_MISSING')), - ERR_MODULE_NOT_FOUND: createErrorCtor((path, base, type = 'package') => { - return `Cannot find ${type} '${path}' imported from ${base}` - }), - ERR_PACKAGE_IMPORT_NOT_DEFINED: createErrorCtor(joinArgs('ERR_PACKAGE_IMPORT_NOT_DEFINED')), - ERR_PACKAGE_PATH_NOT_EXPORTED: createErrorCtor(joinArgs('ERR_PACKAGE_PATH_NOT_EXPORTED')), - ERR_UNSUPPORTED_DIR_IMPORT: createErrorCtor(joinArgs('ERR_UNSUPPORTED_DIR_IMPORT')), - ERR_UNSUPPORTED_ESM_URL_SCHEME: createErrorCtor(joinArgs('ERR_UNSUPPORTED_ESM_URL_SCHEME')), - ERR_UNKNOWN_FILE_EXTENSION: createErrorCtor(joinArgs('ERR_UNKNOWN_FILE_EXTENSION')), +// TODO will be useful when implementing https://github.com/TypeStrong/ts-node/issues/1754 +// Try to pluck this symbol off of an intentionally-created node error +let kIsNodeError; +try { + path.join(0); +} catch(nodeError) { + const symbols = Object.getOwnPropertySymbols(nodeError); + kIsNodeError = symbols.find(s => s.toString().includes('kIsNodeError')); } -function joinArgs(name) { - return (...args) => { - return [name, ...args].join(' ') +exports.codes = {}; + +function defineError(code, buildMessage) { + if (!buildMessage) { + buildMessage = (...args) => args.join(' '); } -} -function createErrorCtor(errorMessageCreator) { - return class CustomError extends Error { + exports.codes[code] = class CustomError extends Error { constructor(...args) { - super(errorMessageCreator(...args)) + super(`${code}: ${buildMessage(...args)}`); + this.code = code; } } } + +defineError("ERR_INPUT_TYPE_NOT_ALLOWED"); +defineError("ERR_INVALID_ARG_VALUE"); +defineError("ERR_INVALID_MODULE_SPECIFIER"); +defineError("ERR_INVALID_PACKAGE_CONFIG"); +defineError("ERR_INVALID_PACKAGE_TARGET"); +defineError("ERR_MANIFEST_DEPENDENCY_MISSING"); +defineError("ERR_MODULE_NOT_FOUND", (path, base, type = 'package') => { + return `Cannot find ${type} '${path}' imported from ${base}`; +}); +defineError("ERR_PACKAGE_IMPORT_NOT_DEFINED"); +defineError("ERR_PACKAGE_PATH_NOT_EXPORTED"); +defineError("ERR_UNSUPPORTED_DIR_IMPORT"); +defineError("ERR_UNSUPPORTED_ESM_URL_SCHEME"); +defineError("ERR_UNKNOWN_FILE_EXTENSION"); + exports.createErrRequireEsm = createErrRequireEsm; // Native ERR_REQUIRE_ESM Error is declared here: diff --git a/src/cjs-resolve-hooks.ts b/src/cjs-resolve-hooks.ts index 9300d8f77..be2303422 100644 --- a/src/cjs-resolve-hooks.ts +++ b/src/cjs-resolve-hooks.ts @@ -49,15 +49,55 @@ export function installCommonjsResolveHooksIfNecessary(tsNodeService: Service) { ...rest ); - return Module_resolveFilename.call( - this, - request, - parent, - isMain, - options, - ...rest - ); + // #region path-mapping + // Note: [SYNC-PATH-MAPPING] keep this logic synced with the corresponding ESM implementation. + let candidateSpecifiers: string[] = [request]; + const attemptPathMapping = + tsNodeService.commonjsPathMapping && + parent?.filename && + !tsNodeService.ignored(parent.filename); + if (attemptPathMapping) { + const mappedSpecifiers = tsNodeService.mapPath(request); + if (mappedSpecifiers) { + candidateSpecifiers = [...mappedSpecifiers, request]; + } + } + // Attempt all resolutions. Collect resolution failures and throw an + // aggregated error if they all fail. + const moduleNotFoundErrors = []; + for (let i = 0; i < candidateSpecifiers.length; i++) { + try { + // TODO does this break if `options.paths` is passed? Should we bail if + // we receive `options.paths`? + return Module_resolveFilename.call( + this, + candidateSpecifiers[i], + parent, + isMain, + options + ); + } catch (err: any) { + const isNotFoundError = err.code === 'MODULE_NOT_FOUND'; + if (!isNotFoundError) { + throw err; + } + moduleNotFoundErrors.push(err); + } + } + // If only one candidate, no need to wrap it. + if (candidateSpecifiers.length === 1) { + throw moduleNotFoundErrors[0]; + } else { + throw new MappedCommonJSModuleNotFoundError( + request, + parent!.filename, + candidateSpecifiers, + moduleNotFoundErrors + ); + } + // #endregion } + function _findPath(this: any): string { if (!tsNodeService.enabled()) return originalFindPath.apply(this, arguments as any); @@ -65,3 +105,42 @@ export function installCommonjsResolveHooksIfNecessary(tsNodeService: Service) { } } } + +interface NodeCommonJSModuleNotFoundError extends Error { + requireStack?: string[]; +} + +class MappedCommonJSModuleNotFoundError extends Error { + // Same code as other module not found errors. + readonly code = 'MODULE_NOT_FOUND' as const; + readonly errors!: ReadonlyArray; + readonly requireStack?: string[]; + + constructor( + specifier: string, + parentFilename: string, + candidates: string[], + moduleNotFoundErrors: Error[] + ) { + super( + [ + `Cannot find '${specifier}' imported from ${parentFilename} using TypeScript path mapping`, + 'Candidates attempted:', + ...candidates.map((candidate) => `- ${candidate}`), + ].join('\n') + ); + // TODO this differs slightly from nodejs errors; see if we can match them + this.name = `Error [${this.code}]`; + // Match shape of `AggregateError` + Object.defineProperty(this, 'errors', { + value: moduleNotFoundErrors, + configurable: true, + writable: true, + }); + // Assume every `requireStack` is identical, and maybe downstream code is doing + // something with it + this.requireStack = ( + moduleNotFoundErrors[0] as NodeCommonJSModuleNotFoundError | undefined + )?.requireStack; + } +} diff --git a/src/configuration.ts b/src/configuration.ts index 5142a3584..36d9f441e 100644 --- a/src/configuration.ts +++ b/src/configuration.ts @@ -381,6 +381,7 @@ function filterRecognizedTsConfigTsNodeOptions(jsonObject: any): { experimentalReplAwait, swc, experimentalResolver, + experimentalPathMapping, esm, experimentalSpecifierResolution, ...unrecognized @@ -407,6 +408,7 @@ function filterRecognizedTsConfigTsNodeOptions(jsonObject: any): { moduleTypes, swc, experimentalResolver, + experimentalPathMapping, esm, experimentalSpecifierResolution, }; diff --git a/src/esm.ts b/src/esm.ts index 102d32d9b..a7521c0f8 100644 --- a/src/esm.ts +++ b/src/esm.ts @@ -5,6 +5,7 @@ import { UrlWithStringQuery, fileURLToPath, pathToFileURL, + URL, } from 'url'; import { extname } from 'path'; import * as assert from 'assert'; @@ -212,15 +213,55 @@ export function createEsmHooks(tsNodeService: Service) { return entrypointFallback(defer); } - // pathname is the path to be resolved + // Note: [SYNC-PATH-MAPPING] keep this logic synced with the corresponding CJS implementation. + let candidateSpecifiers: string[] = [specifier]; + + if (tsNodeService.esmPathMapping && context.parentURL) { + const parentUrl = new URL(context.parentURL); + const parentPath = + parentUrl.protocol === 'file:' && fileURLToPath(parentUrl); + if (parentPath && !tsNodeService.ignored(parentPath)) { + const mappedSpecifiers = tsNodeService.mapPath(specifier); + if (mappedSpecifiers) { + candidateSpecifiers = [ + ...mappedSpecifiers.map((path) => pathToFileURL(path).toString()), + specifier, + ]; + } + } + } - return entrypointFallback(() => - nodeResolveImplementation.defaultResolve( - specifier, - context, - defaultResolve - ) - ); + return entrypointFallback(async () => { + // Attempt all resolutions. Collect resolution failures and throw an + // aggregated error if they all fail. + const moduleNotFoundErrors = []; + for (let i = 0; i < candidateSpecifiers.length; i++) { + try { + return await nodeResolveImplementation.defaultResolve( + candidateSpecifiers[i], + context, + defaultResolve + ); + } catch (err: any) { + const isNotFoundError = err.code === 'ERR_MODULE_NOT_FOUND'; + if (!isNotFoundError) { + throw err; + } + moduleNotFoundErrors.push(err); + } + } + // If only one candidate, no need to wrap it. + if (candidateSpecifiers.length === 1) { + throw moduleNotFoundErrors[0]; + } else { + throw new MappedModuleNotFoundError( + specifier, + context.parentURL, + candidateSpecifiers, + moduleNotFoundErrors + ); + } + }); }); } @@ -405,6 +446,35 @@ export function createEsmHooks(tsNodeService: Service) { return hooksAPI; } +class MappedModuleNotFoundError extends Error { + // Same code as other module not found errors. + readonly code = 'ERR_MODULE_NOT_FOUND' as const; + readonly errors!: ReadonlyArray; + + constructor( + specifier: string, + base: string, + candidates: string[], + moduleNotFoundErrors: Error[] + ) { + super( + [ + `Cannot find '${specifier}' imported from ${base} using TypeScript path mapping`, + 'Candidates attempted:', + ...candidates.map((candidate) => `- ${candidate}`), + ].join('\n') + ); + // TODO this differs slightly from nodejs errors; see if we can match them + this.name = `Error [${this.code}]`; + // Match shape of `AggregateError` + Object.defineProperty(this, 'errors', { + value: moduleNotFoundErrors, + configurable: true, + writable: true, + }); + } +} + async function addShortCircuitFlag(fn: () => Promise) { const ret = await fn(); // Not sure if this is necessary; being lazy. Can revisit in the future. diff --git a/src/index.ts b/src/index.ts index a0079bd24..d1bf67641 100644 --- a/src/index.ts +++ b/src/index.ts @@ -27,6 +27,7 @@ import { } from './module-type-classifier'; import { createResolverFunctions } from './resolver-functions'; import type { createEsmHooks as createEsmHooksFn } from './esm'; +import { createPathMapper } from './path-mapping'; import { installCommonjsResolveHooksIfNecessary, ModuleConstructorWithInternals, @@ -379,6 +380,17 @@ export interface CreateOptions { * @default console.log */ tsTrace?: (str: string) => void; + /** + * Enable TypeScript path mapping in the ESM loader, CommonJS loader, or both. + * Today, the default is 'esm' to map paths in the experimental ESM loader but not + * CommonJS. In the next major release, the default will become 'both'. + * + * Note: If you use tsconfig-paths, be sure to disable it before enabling ts-node's CommonJS path mapper. + * tsconfig-paths already maps paths in CommonJS but not ESM, and it may conflict with ts-node's mapper. + * + * See: https://www.typescriptlang.org/docs/handbook/module-resolution.html#path-mapping + */ + experimentalPathMapping?: 'both' | 'esm' | 'cjs' | 'none'; /** * Enable native ESM support. * @@ -546,6 +558,18 @@ export interface Service { enableExperimentalEsmLoaderInterop(): void; /** @internal */ transpileOnly: boolean; + /** + * @internal + * + * Map import paths to candidates according to the `paths` compiler + * option. Returns `null` if the specifier did not match and was not + * mapped. + */ + mapPath(specifier: string): string[] | null; + /** @internal */ + commonjsPathMapping: boolean; + /** @internal */ + esmPathMapping: boolean; /** @internal */ projectLocalResolveHelper: ProjectLocalResolveHelper; /** @internal */ @@ -805,6 +829,21 @@ export function createFromPreloadedConfig( } } + if ( + ![undefined, null, 'both', 'esm', 'cjs', 'none'].includes( + options.experimentalPathMapping + ) + ) { + throw new Error( + `experimentalPathMapping must be one of: "both", "esm", "cjs", "none"` + ); + } + const experimentalPathMapping = options.experimentalPathMapping ?? 'esm'; + const commonjsPathMapping = + experimentalPathMapping === 'both' || experimentalPathMapping === 'cjs'; + const esmPathMapping = + experimentalPathMapping === 'both' || experimentalPathMapping === 'esm'; + /** * True if require() hooks should interop with experimental ESM loader. * Enabled explicitly via a flag since it is a breaking change. @@ -1485,6 +1524,8 @@ export function createFromPreloadedConfig( }); } + const mapPath = createPathMapper(config.options); + const getNodeEsmResolver = once(() => ( require('../dist-raw/node-internal-modules-esm-resolve') as typeof _nodeInternalModulesEsmResolve @@ -1530,6 +1571,9 @@ export function createFromPreloadedConfig( installSourceMapSupport, enableExperimentalEsmLoaderInterop, transpileOnly, + mapPath, + commonjsPathMapping, + esmPathMapping, projectLocalResolveHelper, getNodeEsmResolver, getNodeEsmGetFormat, diff --git a/src/path-mapping.ts b/src/path-mapping.ts new file mode 100644 index 000000000..c15d06396 --- /dev/null +++ b/src/path-mapping.ts @@ -0,0 +1,132 @@ +import type * as ts from 'typescript'; +import { join as joinPath } from 'path'; +import { normalizeSlashes } from './util'; + +// Path mapper returns a list of mapped specifiers or `null` if the +// given `specifier` was not mapped. +type PathMapper = (specifier: string) => string[] | null; + +export function createPathMapper( + compilerOptions: ts.CompilerOptions +): PathMapper { + if (compilerOptions.baseUrl) { + // TODO should only attempt baseUrl / path mapping for non-relative, non-absolute specifiers. + // TODO double-check: should `*` default apply if `paths` is specified but it does not specify a `*` mapping? + const mappings = Object.entries( + compilerOptions.paths ?? { '*': ['*'] } + ).map(([patternString, outputs]) => ({ + pattern: parsePattern(patternString), + outputs, + })); + const mappingConfig = { mappings, baseUrl: compilerOptions.baseUrl }; + + return function map(specifier: string): string[] | null { + return mapPath(mappingConfig, specifier); + }; + } else { + return () => null; + } +} + +interface MappingConfig { + mappings: Mapping[]; + baseUrl: string; +} + +interface Mapping { + pattern: Pattern; + outputs: string[]; +} + +type Pattern = + | { + type: 'wildcard'; + prefix: string; + suffix: string; + } + | { type: 'static'; value: string }; + +function mapPath(mappingConfig: MappingConfig, path: string): string[] | null { + let bestMatchWeight = -Infinity; + let bestMatch: [Mapping, string] | null = null; + + for (const mapping of mappingConfig.mappings) { + if (patternWeight(mapping.pattern) > bestMatchWeight) { + const match = matchPattern(mapping.pattern, path); + if (match !== null) { + bestMatch = [mapping, match]; + bestMatchWeight = patternWeight(mapping.pattern); + } + } + } + + if (bestMatch) { + const [mapping, match] = bestMatch; + return mapping.outputs.map((output) => + normalizeSlashes( + joinPath(mappingConfig.baseUrl, output.replace('*', match)) + ) + ); + } else { + return null; + } +} + +// Return the submatch when the pattern matches. +// +// For the wildcard pattern string `a*z` and candidate `afooz` this +// returns `foo`. For the static pattern `bar` and the candidate `bar` +// this returns `bar`. +function matchPattern(pattern: Pattern, candidate: string): string | null { + switch (pattern.type) { + case 'wildcard': + if ( + candidate.length >= pattern.prefix.length + pattern.suffix.length && + candidate.startsWith(pattern.prefix) && + candidate.endsWith(pattern.suffix) + ) { + return candidate.substring( + pattern.prefix.length, + candidate.length - pattern.suffix.length + ); + } else { + return null; + } + case 'static': + if (pattern.value === candidate) { + return candidate; + } else { + return null; + } + } +} + +// Pattern weight to sort best matches. +// +// Static patterns have the highest weight. For wildcard patterns the +// weight is determined by the length of the prefix before the glob +// `*`. +function patternWeight(pattern: Pattern): number { + if (pattern.type === 'wildcard') { + return pattern.prefix.length; + } else { + return Infinity; + } +} + +function parsePattern(patternString: string): Pattern { + const indexOfStar = patternString.indexOf('*'); + if (indexOfStar === -1) { + return { type: 'static', value: patternString }; + } + + if (patternString.indexOf('*', indexOfStar + 1) !== -1) { + throw new Error(`Path pattern ${patternString} contains two wildcards '*'`); + } + + return { + type: 'wildcard', + prefix: patternString.substring(0, indexOfStar), + suffix: patternString.substring(indexOfStar + 1), + }; +} diff --git a/src/test/helpers.ts b/src/test/helpers.ts index 0a58c5a6b..80c4897a9 100644 --- a/src/test/helpers.ts +++ b/src/test/helpers.ts @@ -49,6 +49,7 @@ export const CMD_TS_NODE_WITHOUT_PROJECT_FLAG = `"${BIN_PATH}"`; export const EXPERIMENTAL_MODULES_FLAG = semver.gte(process.version, '12.17.0') ? '' : '--experimental-modules'; +/** Default `node --loader` invocation (pass `TS_NODE_PROJECT` as env) */ export const CMD_ESM_LOADER_WITHOUT_PROJECT = `node ${EXPERIMENTAL_MODULES_FLAG} --loader ts-node/esm`; //#endregion diff --git a/src/test/path-mapping.spec.ts b/src/test/path-mapping.spec.ts new file mode 100644 index 000000000..ecf3ec513 --- /dev/null +++ b/src/test/path-mapping.spec.ts @@ -0,0 +1,173 @@ +import { join } from 'path'; + +import { createExec } from './exec-helpers'; +import { + CMD_ESM_LOADER_WITHOUT_PROJECT, + CMD_TS_NODE_WITHOUT_PROJECT_FLAG, + nodeSupportsEsmHooks, + TEST_DIR, + installTsNode, +} from './helpers'; +import { test, expect } from './testlib'; + +test.beforeAll(installTsNode); + +function execBuilder( + command: string, + moduleDir: string, + tsConfig: string = 'tsconfig.json' +) { + const partialExec = createExec({ + cwd: join(TEST_DIR, moduleDir), + env: { ...process.env, TS_NODE_PROJECT: tsConfig }, + }); + + return (file = 'index.ts') => partialExec(`${command} ${file}`); +} + +const MODULE_TYPES = { + CJS: { + name: 'cjs', + baseDir: 'cjs-path-mapping', + command: CMD_TS_NODE_WITHOUT_PROJECT_FLAG, + }, + ESM: { + name: 'esm', + baseDir: 'esm-path-mapping', + command: CMD_ESM_LOADER_WITHOUT_PROJECT, + }, +} as const; + +const PROJECT_CONFIGS = { + BASE_URL_NO_PATHS: 'tsconfig-baseurl-no-paths.json', + BASE_URL_SOME_PATHS: 'tsconfig-baseurl-some-paths.json', + BASE_URL_STAR_PATH: 'tsconfig-baseurl-star-path.json', +} as const; + +for (const moduleType of Object.values(MODULE_TYPES)) { + test.suite(`path mapping ${moduleType.name}`, (test) => { + test.runIf( + nodeSupportsEsmHooks || moduleType.name !== MODULE_TYPES.ESM.name + ); + + for (const project of Object.values(PROJECT_CONFIGS)) { + // Create ts-node runner for this config + const exec = execBuilder(moduleType.command, moduleType.baseDir, project); + + test.suite(`${project}`, (test) => { + test('ignore type definitions', async (t) => { + const { err } = await exec('ignore-type-definitions.ts'); + expect(err).toBeNull(); + }); + + test(`fallback to node built-in`, async (t) => { + const { err } = await exec('import-node-built-in.ts'); + expect(err).toBe(null); + }); + + test(`import at baseUrl`, async () => { + const { err } = await exec('import-at-base.ts'); + expect(err).toBeNull(); + }); + + test(`import at baseUrl without file extensions`, async () => { + const { err } = await exec('import-at-base-no-extensions.ts'); + expect(err).toBeNull(); + }); + + test(`import below baseUrl`, async () => { + const { err } = await exec('import-below-base.ts'); + expect(err).toBeNull(); + }); + + test(`import from js, jsx, tsx`, async () => { + const { err } = await exec('import-from-js-jsx-tsx.ts'); + expect(err).toBeNull(); + }); + + test(`import node built-in`, async (t) => { + const { err } = await exec('import-node-built-in.ts'); + expect(err).toBeNull(); + }); + + test(`import node_modules`, async (t) => { + const { err } = await exec('import-node-modules.ts'); + expect(err).toBeNull(); + }); + + test(`import within node_modules ignores paths`, async (t) => { + const { err } = await exec('import-within-node-modules.ts'); + expect(err).toBeNull(); + }); + + test('import relative', async () => { + const { err } = await exec('import-relative.ts'); + expect(err).toBeNull(); + }); + + test(`import invalid path should error & list candidates`, async () => { + const { err, stderr } = await exec('import-non-existing.ts'); + expect(err).toBeTruthy(); + expect(stderr).toMatch( + `[ERR_MODULE_NOT_FOUND]: Cannot find 'non-existing.js'` + ); + expect(stderr).toMatch(/- file:\/\/.*non-existing.js/); + }); + }); + } + + test.suite(`${PROJECT_CONFIGS.BASE_URL_STAR_PATH} only`, (test) => { + const exec = execBuilder( + moduleType.command, + moduleType.baseDir, + PROJECT_CONFIGS.BASE_URL_STAR_PATH + ); + + test('import relative should not succeed using star-path', async (t) => { + const { err, stderr } = await exec('import-relative-ignores-star.ts'); + expect(err).toBeTruthy(); + expect(stderr).toMatch( + `[ERR_MODULE_NOT_FOUND]: Cannot find './should-not-resolve'` + ); + }); + }); + + test.suite(`${PROJECT_CONFIGS.BASE_URL_SOME_PATHS} only`, (test) => { + const exec = execBuilder( + moduleType.command, + moduleType.baseDir, + PROJECT_CONFIGS.BASE_URL_SOME_PATHS + ); + + test('map using a prefix', async (t) => { + const { err } = await exec('map-using-prefix.ts'); + expect(err).toBeNull(); + }); + + test('map to js, jsx, tsx', async (t) => { + const { err } = await exec('map-to-js-jsx-tsx.ts'); + expect(err).toBeNull(); + }); + + test('map to first available candidate', async (t) => { + const { err } = await exec('map-to-first-available-candidate.ts'); + expect(err).toBeNull(); + }); + + test('map using more specific candidate', async (t) => { + const { err } = await exec('map-using-more-specific-path.ts'); + expect(err).toBeNull(); + }); + + test('map to static (no wildcard)', async (t) => { + const { err } = await exec('map-using-static-path.ts'); + expect(err).toBeNull(); + }); + + test('map from js, jsx, tsx', async (t) => { + const { err } = await exec('map-from-js-jsx-tsx.ts'); + expect(err).toBeNull(); + }); + }); + }); +} diff --git a/tests/cjs-path-mapping/ignore-type-definitions.ts b/tests/cjs-path-mapping/ignore-type-definitions.ts new file mode 100644 index 000000000..f920e1f94 --- /dev/null +++ b/tests/cjs-path-mapping/ignore-type-definitions.ts @@ -0,0 +1,11 @@ +// Should ignore local ambient.d.ts +const ambient = require('ambient'); + +// Pre-conditions +const assert = require('assert'); + +// Assertions +assert.strictEqual(ambient, 'ambient'); // not our 'local-ambient' + +// Force this to be a module +export {}; diff --git a/tests/cjs-path-mapping/import-at-base-no-extension.ts b/tests/cjs-path-mapping/import-at-base-no-extension.ts new file mode 100644 index 000000000..4a94463ac --- /dev/null +++ b/tests/cjs-path-mapping/import-at-base-no-extension.ts @@ -0,0 +1,15 @@ +// Should import from base directory, assuming file extension +const atBaseJs = require('at-base-js'); +const atBaseJsx = require('at-base-jsx'); +const atBaseTs = require('at-base-ts'); + +// Pre-conditions +const assert = require('assert'); + +// Assertions +assert.strictEqual(atBaseJs, 'at-base-js'); +assert.strictEqual(atBaseJsx, 'at-base-jsx'); +assert.strictEqual(atBaseTs, 'at-base-ts'); + +// Force this to be a module +export {}; diff --git a/tests/cjs-path-mapping/import-at-base.ts b/tests/cjs-path-mapping/import-at-base.ts new file mode 100644 index 000000000..9bd6bde0d --- /dev/null +++ b/tests/cjs-path-mapping/import-at-base.ts @@ -0,0 +1,15 @@ +// Should import from base directory +const atBaseJs = require('at-base-js.js'); +const atBaseJsx = require('at-base-jsx.js'); +const atBaseTs = require('at-base-ts.js'); + +// Pre-conditions +const assert = require('assert'); + +// Assertions +assert.strictEqual(atBaseJs, 'at-base-js'); +assert.strictEqual(atBaseJsx, 'at-base-jsx'); +assert.strictEqual(atBaseTs, 'at-base-ts'); + +// Force this to be a module +export {}; diff --git a/tests/cjs-path-mapping/import-below-base.ts b/tests/cjs-path-mapping/import-below-base.ts new file mode 100644 index 000000000..08087f29d --- /dev/null +++ b/tests/cjs-path-mapping/import-below-base.ts @@ -0,0 +1,15 @@ +// Should import below base directory +const belowBaseJs = require('level-3/below-base-js.js'); +const belowBaseJsx = require('level-3/below-base-jsx.js'); +const belowBaseTs = require('level-3/below-base-ts.js'); + +// Pre-conditions +const assert = require('assert'); + +// Assertions +assert.strictEqual(belowBaseJs, 'below-base-js'); +assert.strictEqual(belowBaseJsx, 'below-base-jsx'); +assert.strictEqual(belowBaseTs, 'below-base-ts'); + +// Force this to be a module +export {}; diff --git a/tests/cjs-path-mapping/import-from-js-jsx-tsx.ts b/tests/cjs-path-mapping/import-from-js-jsx-tsx.ts new file mode 100644 index 000000000..825ca6ac7 --- /dev/null +++ b/tests/cjs-path-mapping/import-from-js-jsx-tsx.ts @@ -0,0 +1,3 @@ +require('./import-from-js.js'); +require('./import-from-jsx.js'); +require('./import-from-tsx.js'); diff --git a/tests/cjs-path-mapping/import-from-js.js b/tests/cjs-path-mapping/import-from-js.js new file mode 100644 index 000000000..9bd6bde0d --- /dev/null +++ b/tests/cjs-path-mapping/import-from-js.js @@ -0,0 +1,15 @@ +// Should import from base directory +const atBaseJs = require('at-base-js.js'); +const atBaseJsx = require('at-base-jsx.js'); +const atBaseTs = require('at-base-ts.js'); + +// Pre-conditions +const assert = require('assert'); + +// Assertions +assert.strictEqual(atBaseJs, 'at-base-js'); +assert.strictEqual(atBaseJsx, 'at-base-jsx'); +assert.strictEqual(atBaseTs, 'at-base-ts'); + +// Force this to be a module +export {}; diff --git a/tests/cjs-path-mapping/import-from-jsx.jsx b/tests/cjs-path-mapping/import-from-jsx.jsx new file mode 100644 index 000000000..9bd6bde0d --- /dev/null +++ b/tests/cjs-path-mapping/import-from-jsx.jsx @@ -0,0 +1,15 @@ +// Should import from base directory +const atBaseJs = require('at-base-js.js'); +const atBaseJsx = require('at-base-jsx.js'); +const atBaseTs = require('at-base-ts.js'); + +// Pre-conditions +const assert = require('assert'); + +// Assertions +assert.strictEqual(atBaseJs, 'at-base-js'); +assert.strictEqual(atBaseJsx, 'at-base-jsx'); +assert.strictEqual(atBaseTs, 'at-base-ts'); + +// Force this to be a module +export {}; diff --git a/tests/cjs-path-mapping/import-from-tsx.tsx b/tests/cjs-path-mapping/import-from-tsx.tsx new file mode 100644 index 000000000..9bd6bde0d --- /dev/null +++ b/tests/cjs-path-mapping/import-from-tsx.tsx @@ -0,0 +1,15 @@ +// Should import from base directory +const atBaseJs = require('at-base-js.js'); +const atBaseJsx = require('at-base-jsx.js'); +const atBaseTs = require('at-base-ts.js'); + +// Pre-conditions +const assert = require('assert'); + +// Assertions +assert.strictEqual(atBaseJs, 'at-base-js'); +assert.strictEqual(atBaseJsx, 'at-base-jsx'); +assert.strictEqual(atBaseTs, 'at-base-ts'); + +// Force this to be a module +export {}; diff --git a/tests/cjs-path-mapping/import-node-built-in.ts b/tests/cjs-path-mapping/import-node-built-in.ts new file mode 100644 index 000000000..c83abd406 --- /dev/null +++ b/tests/cjs-path-mapping/import-node-built-in.ts @@ -0,0 +1,9 @@ +// Should be able to import node built-ins +const assert = require('assert'); +const { stat } = require('fs'); + +// Assertions +assert.strictEqual(typeof stat, 'function'); + +// Force this to be a module +export {}; diff --git a/tests/cjs-path-mapping/import-node-modules.ts b/tests/cjs-path-mapping/import-node-modules.ts new file mode 100644 index 000000000..fa5602c30 --- /dev/null +++ b/tests/cjs-path-mapping/import-node-modules.ts @@ -0,0 +1,23 @@ +// Do not extract this helper - it will change the meaning of relative imports +const importDefaultHelper = new Function( + 'specifier', + 'return import(specifier).then(mod => mod.default)' +); + +const main = async (): Promise => { + // Should be able to import from node_modules + const someCjsDependency = require('some-cjs-dependency'); + const someEsmDependency = await importDefaultHelper('some-esm-dependency'); + + // Pre-conditions + const assert = require('assert'); + + // Assertions + assert.strictEqual(someCjsDependency, 'export-from-some-cjs-dependency'); + assert.strictEqual(someEsmDependency, 'export-from-some-esm-dependency'); +}; + +main(); + +// Force this to be a module +export {}; diff --git a/tests/cjs-path-mapping/import-non-existing.ts b/tests/cjs-path-mapping/import-non-existing.ts new file mode 100644 index 000000000..b834dd231 --- /dev/null +++ b/tests/cjs-path-mapping/import-non-existing.ts @@ -0,0 +1,8 @@ +// Should fail to import non-existing file +const nonExisting = require('non-existing.js'); + +// Pretend we want to use it +console.log(nonExisting); + +// Force this to be a module +export {}; diff --git a/tests/cjs-path-mapping/import-relative-ignores-star.ts b/tests/cjs-path-mapping/import-relative-ignores-star.ts new file mode 100644 index 000000000..1da0468e5 --- /dev/null +++ b/tests/cjs-path-mapping/import-relative-ignores-star.ts @@ -0,0 +1,8 @@ +// Should fail to import relative import that would need to apply a path +const shouldNotResolve = require('./should-not-resolve'); + +// Pretend we want to use it +console.log(shouldNotResolve); + +// Force this to be a module +export {}; diff --git a/tests/cjs-path-mapping/import-relative.ts b/tests/cjs-path-mapping/import-relative.ts new file mode 100644 index 000000000..36780f91e --- /dev/null +++ b/tests/cjs-path-mapping/import-relative.ts @@ -0,0 +1,15 @@ +// Should be able to use relative imports +const aboveBaseJs = require('./level-1/above-base-js'); +const aboveBaseJsx = require('../cjs-path-mapping/level-1/above-base-jsx'); +// const aboveBaseTs = require('/level-1/above-base-ts'); + +// Pre-conditions +const assert = require('assert'); + +// Assertions +assert.strictEqual(aboveBaseJs, 'above-base-js'); +assert.strictEqual(aboveBaseJsx, 'above-base-jsx'); +// assert.strictEqual(aboveBaseTs, 'above-base-ts'); + +// Force this to be a module +export {}; diff --git a/tests/cjs-path-mapping/import-within-node-modules.ts b/tests/cjs-path-mapping/import-within-node-modules.ts new file mode 100644 index 000000000..1a9328528 --- /dev/null +++ b/tests/cjs-path-mapping/import-within-node-modules.ts @@ -0,0 +1,11 @@ +// Should ignore paths when importing inside node_modules +const { proxyLodash } = require('depends-on-lodash'); + +// Pre-conditions +const assert = require('assert'); + +// Assertions +assert.strictEqual(proxyLodash, 'lodash'); // not our 'lodash-local' + +// Force this to be a module +export {}; diff --git a/tests/cjs-path-mapping/level-1/above-base-js.js b/tests/cjs-path-mapping/level-1/above-base-js.js new file mode 100644 index 000000000..14787d065 --- /dev/null +++ b/tests/cjs-path-mapping/level-1/above-base-js.js @@ -0,0 +1 @@ +export default 'above-base-js'; diff --git a/tests/cjs-path-mapping/level-1/above-base-jsx.jsx b/tests/cjs-path-mapping/level-1/above-base-jsx.jsx new file mode 100644 index 000000000..bf2de07c1 --- /dev/null +++ b/tests/cjs-path-mapping/level-1/above-base-jsx.jsx @@ -0,0 +1 @@ +export default 'above-base-jsx'; diff --git a/tests/cjs-path-mapping/level-1/above-base-ts.ts b/tests/cjs-path-mapping/level-1/above-base-ts.ts new file mode 100644 index 000000000..0e6b7f531 --- /dev/null +++ b/tests/cjs-path-mapping/level-1/above-base-ts.ts @@ -0,0 +1 @@ +export default 'above-base-ts'; diff --git a/tests/cjs-path-mapping/level-1/level-2/ambient.d.ts b/tests/cjs-path-mapping/level-1/level-2/ambient.d.ts new file mode 100644 index 000000000..71fdb63fc --- /dev/null +++ b/tests/cjs-path-mapping/level-1/level-2/ambient.d.ts @@ -0,0 +1,4 @@ +declare module 'ambient' { + const defaultExport = 'ambient-local'; // not the same node_modules/ambient + export default defaultExport; +} diff --git a/tests/cjs-path-mapping/level-1/level-2/at-base-js.js b/tests/cjs-path-mapping/level-1/level-2/at-base-js.js new file mode 100644 index 000000000..90e543097 --- /dev/null +++ b/tests/cjs-path-mapping/level-1/level-2/at-base-js.js @@ -0,0 +1 @@ +export default 'at-base-js'; diff --git a/tests/cjs-path-mapping/level-1/level-2/at-base-jsx.jsx b/tests/cjs-path-mapping/level-1/level-2/at-base-jsx.jsx new file mode 100644 index 000000000..4efaeebfe --- /dev/null +++ b/tests/cjs-path-mapping/level-1/level-2/at-base-jsx.jsx @@ -0,0 +1,6 @@ +export default 'at-base-jsx'; + +const React = { + createElement() {}, +}; +const div =
; diff --git a/tests/cjs-path-mapping/level-1/level-2/at-base-ts.ts b/tests/cjs-path-mapping/level-1/level-2/at-base-ts.ts new file mode 100644 index 000000000..d1695f553 --- /dev/null +++ b/tests/cjs-path-mapping/level-1/level-2/at-base-ts.ts @@ -0,0 +1 @@ +export default 'at-base-ts'; diff --git a/tests/cjs-path-mapping/level-1/level-2/candidate-1-foo.ts b/tests/cjs-path-mapping/level-1/level-2/candidate-1-foo.ts new file mode 100644 index 000000000..9fa19cea4 --- /dev/null +++ b/tests/cjs-path-mapping/level-1/level-2/candidate-1-foo.ts @@ -0,0 +1 @@ +export default 'candidate-1-foo'; diff --git a/tests/cjs-path-mapping/level-1/level-2/candidate-1-foo/bar.ts b/tests/cjs-path-mapping/level-1/level-2/candidate-1-foo/bar.ts new file mode 100644 index 000000000..67f84813a --- /dev/null +++ b/tests/cjs-path-mapping/level-1/level-2/candidate-1-foo/bar.ts @@ -0,0 +1 @@ +export default 'nested-bar'; // not the same as ../candidate-foo-bar diff --git a/tests/cjs-path-mapping/level-1/level-2/candidate-2-bar.ts b/tests/cjs-path-mapping/level-1/level-2/candidate-2-bar.ts new file mode 100644 index 000000000..6580a49f4 --- /dev/null +++ b/tests/cjs-path-mapping/level-1/level-2/candidate-2-bar.ts @@ -0,0 +1 @@ +export default 'candidate-2-bar'; diff --git a/tests/cjs-path-mapping/level-1/level-2/candidate-2-foo.ts b/tests/cjs-path-mapping/level-1/level-2/candidate-2-foo.ts new file mode 100644 index 000000000..d8728d603 --- /dev/null +++ b/tests/cjs-path-mapping/level-1/level-2/candidate-2-foo.ts @@ -0,0 +1 @@ +export default 'candidate-2-foo'; diff --git a/tests/cjs-path-mapping/level-1/level-2/candidate-foo-bar.ts b/tests/cjs-path-mapping/level-1/level-2/candidate-foo-bar.ts new file mode 100644 index 000000000..fcc393269 --- /dev/null +++ b/tests/cjs-path-mapping/level-1/level-2/candidate-foo-bar.ts @@ -0,0 +1 @@ +export default 'candidate-foo-bar'; diff --git a/tests/cjs-path-mapping/level-1/level-2/immobile.ts b/tests/cjs-path-mapping/level-1/level-2/immobile.ts new file mode 100644 index 000000000..d54b5651d --- /dev/null +++ b/tests/cjs-path-mapping/level-1/level-2/immobile.ts @@ -0,0 +1 @@ +export default 'immobile'; diff --git a/tests/cjs-path-mapping/level-1/level-2/level-3/below-base-js.js b/tests/cjs-path-mapping/level-1/level-2/level-3/below-base-js.js new file mode 100644 index 000000000..72bebdf46 --- /dev/null +++ b/tests/cjs-path-mapping/level-1/level-2/level-3/below-base-js.js @@ -0,0 +1 @@ +export default 'below-base-js'; diff --git a/tests/cjs-path-mapping/level-1/level-2/level-3/below-base-jsx.jsx b/tests/cjs-path-mapping/level-1/level-2/level-3/below-base-jsx.jsx new file mode 100644 index 000000000..b5c8c33c2 --- /dev/null +++ b/tests/cjs-path-mapping/level-1/level-2/level-3/below-base-jsx.jsx @@ -0,0 +1,6 @@ +export default 'below-base-jsx'; + +const React = { + createElement() {}, +}; +const div =
; diff --git a/tests/cjs-path-mapping/level-1/level-2/level-3/below-base-ts.ts b/tests/cjs-path-mapping/level-1/level-2/level-3/below-base-ts.ts new file mode 100644 index 000000000..e270d7ff1 --- /dev/null +++ b/tests/cjs-path-mapping/level-1/level-2/level-3/below-base-ts.ts @@ -0,0 +1 @@ +export default 'below-base-ts'; diff --git a/tests/cjs-path-mapping/level-1/level-2/lodash.ts b/tests/cjs-path-mapping/level-1/level-2/lodash.ts new file mode 100644 index 000000000..aa747374a --- /dev/null +++ b/tests/cjs-path-mapping/level-1/level-2/lodash.ts @@ -0,0 +1 @@ +export default 'lodash-local'; // not the same as node_modules/lodash diff --git a/tests/cjs-path-mapping/level-1/level-2/mapped-js.js b/tests/cjs-path-mapping/level-1/level-2/mapped-js.js new file mode 100644 index 000000000..b68675ef9 --- /dev/null +++ b/tests/cjs-path-mapping/level-1/level-2/mapped-js.js @@ -0,0 +1 @@ +export default 'mapped-js'; diff --git a/tests/cjs-path-mapping/level-1/level-2/mapped-jsx.jsx b/tests/cjs-path-mapping/level-1/level-2/mapped-jsx.jsx new file mode 100644 index 000000000..c83d6fee6 --- /dev/null +++ b/tests/cjs-path-mapping/level-1/level-2/mapped-jsx.jsx @@ -0,0 +1,6 @@ +export default 'mapped-jsx'; + +const React = { + createElement() {}, +}; +const div =
; diff --git a/tests/cjs-path-mapping/level-1/level-2/mapped-ts.ts b/tests/cjs-path-mapping/level-1/level-2/mapped-ts.ts new file mode 100644 index 000000000..2859038cc --- /dev/null +++ b/tests/cjs-path-mapping/level-1/level-2/mapped-ts.ts @@ -0,0 +1 @@ +export default 'mapped-ts'; diff --git a/tests/cjs-path-mapping/level-1/level-2/mapped-tsx.tsx b/tests/cjs-path-mapping/level-1/level-2/mapped-tsx.tsx new file mode 100644 index 000000000..3ccd0d524 --- /dev/null +++ b/tests/cjs-path-mapping/level-1/level-2/mapped-tsx.tsx @@ -0,0 +1,6 @@ +export default 'mapped-tsx'; + +const React = { + createElement() {}, +}; +const div =
; diff --git a/tests/cjs-path-mapping/level-1/level-2/should-not-resolve.ts b/tests/cjs-path-mapping/level-1/level-2/should-not-resolve.ts new file mode 100644 index 000000000..07f3178c6 --- /dev/null +++ b/tests/cjs-path-mapping/level-1/level-2/should-not-resolve.ts @@ -0,0 +1 @@ +export default 'should-not-resolve-at-base'; diff --git a/tests/cjs-path-mapping/level-2/should-not-resolve.ts b/tests/cjs-path-mapping/level-2/should-not-resolve.ts new file mode 100644 index 000000000..938efdb04 --- /dev/null +++ b/tests/cjs-path-mapping/level-2/should-not-resolve.ts @@ -0,0 +1 @@ +export default 'should-not-resolve'; diff --git a/tests/cjs-path-mapping/map-from-js-jsx-tsx.ts b/tests/cjs-path-mapping/map-from-js-jsx-tsx.ts new file mode 100644 index 000000000..8d46cd4c5 --- /dev/null +++ b/tests/cjs-path-mapping/map-from-js-jsx-tsx.ts @@ -0,0 +1,3 @@ +require('./map-from-js'); +require('./map-from-jsx'); +require('./map-from-tsx'); diff --git a/tests/cjs-path-mapping/map-from-js.js b/tests/cjs-path-mapping/map-from-js.js new file mode 100644 index 000000000..3f39a8ae1 --- /dev/null +++ b/tests/cjs-path-mapping/map-from-js.js @@ -0,0 +1,25 @@ +// All mapped imports +const mappedJs = require('mapped/js'); +const mappedJsx = require('mapped/jsx'); +const mappedTs = require('mapped/ts'); +const mappedTsx = require('mapped/tsx'); +const foo = require('candidate/foo'); +const bar = require('candidate/bar'); +const fooBar = require('candidate/foo/bar'); +const immobile = require('static'); + +// Pre-conditions +const assert = require('assert'); + +// Assertions +assert.strictEqual(mappedTs, 'mapped-ts'); +assert.strictEqual(mappedTsx, 'mapped-tsx'); +assert.strictEqual(mappedJs, 'mapped-js'); +assert.strictEqual(mappedJsx, 'mapped-jsx'); +assert.strictEqual(foo, 'candidate-1-foo'); +assert.strictEqual(bar, 'candidate-2-bar'); +assert.strictEqual(fooBar, 'candidate-foo-bar'); +assert.strictEqual(immobile, 'immobile'); + +// Force this to be a module +export {}; diff --git a/tests/cjs-path-mapping/map-from-jsx.jsx b/tests/cjs-path-mapping/map-from-jsx.jsx new file mode 100644 index 000000000..3f39a8ae1 --- /dev/null +++ b/tests/cjs-path-mapping/map-from-jsx.jsx @@ -0,0 +1,25 @@ +// All mapped imports +const mappedJs = require('mapped/js'); +const mappedJsx = require('mapped/jsx'); +const mappedTs = require('mapped/ts'); +const mappedTsx = require('mapped/tsx'); +const foo = require('candidate/foo'); +const bar = require('candidate/bar'); +const fooBar = require('candidate/foo/bar'); +const immobile = require('static'); + +// Pre-conditions +const assert = require('assert'); + +// Assertions +assert.strictEqual(mappedTs, 'mapped-ts'); +assert.strictEqual(mappedTsx, 'mapped-tsx'); +assert.strictEqual(mappedJs, 'mapped-js'); +assert.strictEqual(mappedJsx, 'mapped-jsx'); +assert.strictEqual(foo, 'candidate-1-foo'); +assert.strictEqual(bar, 'candidate-2-bar'); +assert.strictEqual(fooBar, 'candidate-foo-bar'); +assert.strictEqual(immobile, 'immobile'); + +// Force this to be a module +export {}; diff --git a/tests/cjs-path-mapping/map-from-tsx.tsx b/tests/cjs-path-mapping/map-from-tsx.tsx new file mode 100644 index 000000000..3f39a8ae1 --- /dev/null +++ b/tests/cjs-path-mapping/map-from-tsx.tsx @@ -0,0 +1,25 @@ +// All mapped imports +const mappedJs = require('mapped/js'); +const mappedJsx = require('mapped/jsx'); +const mappedTs = require('mapped/ts'); +const mappedTsx = require('mapped/tsx'); +const foo = require('candidate/foo'); +const bar = require('candidate/bar'); +const fooBar = require('candidate/foo/bar'); +const immobile = require('static'); + +// Pre-conditions +const assert = require('assert'); + +// Assertions +assert.strictEqual(mappedTs, 'mapped-ts'); +assert.strictEqual(mappedTsx, 'mapped-tsx'); +assert.strictEqual(mappedJs, 'mapped-js'); +assert.strictEqual(mappedJsx, 'mapped-jsx'); +assert.strictEqual(foo, 'candidate-1-foo'); +assert.strictEqual(bar, 'candidate-2-bar'); +assert.strictEqual(fooBar, 'candidate-foo-bar'); +assert.strictEqual(immobile, 'immobile'); + +// Force this to be a module +export {}; diff --git a/tests/cjs-path-mapping/map-to-first-available-candidate.ts b/tests/cjs-path-mapping/map-to-first-available-candidate.ts new file mode 100644 index 000000000..9a2f7baba --- /dev/null +++ b/tests/cjs-path-mapping/map-to-first-available-candidate.ts @@ -0,0 +1,13 @@ +// Should map to the first path pattern that exists +const foo = require('candidate/foo'); +const bar = require('candidate/bar'); + +// Pre-conditions +const assert = require('assert'); + +// Assertions +assert.strictEqual(foo, 'candidate-1-foo'); +assert.strictEqual(bar, 'candidate-2-bar'); + +// Force this to be a module +export {}; diff --git a/tests/cjs-path-mapping/map-to-js-jsx-tsx.ts b/tests/cjs-path-mapping/map-to-js-jsx-tsx.ts new file mode 100644 index 000000000..16304de0b --- /dev/null +++ b/tests/cjs-path-mapping/map-to-js-jsx-tsx.ts @@ -0,0 +1,15 @@ +// Should be able to use path to map to js, jsx, tsx +const mappedJs = require('mapped/js'); +const mappedJsx = require('mapped/jsx'); +const mappedTsx = require('mapped/tsx'); + +// Pre-conditions +const assert = require('assert'); + +// Assertions +assert.strictEqual(mappedJs, 'mapped-js'); +assert.strictEqual(mappedJsx, 'mapped-jsx'); +assert.strictEqual(mappedTsx, 'mapped-tsx'); + +// Force this to be a module +export {}; diff --git a/tests/cjs-path-mapping/map-using-more-specific-path.ts b/tests/cjs-path-mapping/map-using-more-specific-path.ts new file mode 100644 index 000000000..23aa65809 --- /dev/null +++ b/tests/cjs-path-mapping/map-using-more-specific-path.ts @@ -0,0 +1,13 @@ +// Should map using the more specific path +// ❌ "candidate/*": ["./candidate-1-*", "./candidate-2-*"] => ./candidate-1-foo/bar +// ✅ "candidate/foo/*": ["./candidate-foo-*"] => ./candidate-foo-bar +const fooBar = require('candidate/foo/bar'); + +// Pre-conditions +const assert = require('assert'); + +// Assertions +assert.strictEqual(fooBar, 'candidate-foo-bar'); + +// Force this to be a module +export {}; diff --git a/tests/cjs-path-mapping/map-using-prefix.ts b/tests/cjs-path-mapping/map-using-prefix.ts new file mode 100644 index 000000000..6f720e26b --- /dev/null +++ b/tests/cjs-path-mapping/map-using-prefix.ts @@ -0,0 +1,11 @@ +// Should be able to use path to map import +const mappedTs = require('mapped/ts'); + +// Pre-conditions +const assert = require('assert'); + +// Assertions +assert.strictEqual(mappedTs, 'mapped-ts'); + +// Force this to be a module +export {}; diff --git a/tests/cjs-path-mapping/map-using-static-path.ts b/tests/cjs-path-mapping/map-using-static-path.ts new file mode 100644 index 000000000..6358d1e9d --- /dev/null +++ b/tests/cjs-path-mapping/map-using-static-path.ts @@ -0,0 +1,11 @@ +// Should map to static path (no wildcard) +const immobile = require('static'); + +// Pre-conditions +const assert = require('assert'); + +// Assertions +assert.strictEqual(immobile, 'immobile'); + +// Force this to be a module +export {}; diff --git a/tests/cjs-path-mapping/node_modules/ambient/index.d.ts b/tests/cjs-path-mapping/node_modules/ambient/index.d.ts new file mode 100644 index 000000000..0d8c7c450 --- /dev/null +++ b/tests/cjs-path-mapping/node_modules/ambient/index.d.ts @@ -0,0 +1,4 @@ +declare module 'ambient' { + const defaultExport = 'ambient'; + export default defaultExport; +} diff --git a/tests/cjs-path-mapping/node_modules/ambient/index.js b/tests/cjs-path-mapping/node_modules/ambient/index.js new file mode 100644 index 000000000..4b25619bb --- /dev/null +++ b/tests/cjs-path-mapping/node_modules/ambient/index.js @@ -0,0 +1 @@ +module.exports = 'ambient'; diff --git a/tests/cjs-path-mapping/node_modules/ambient/package.json b/tests/cjs-path-mapping/node_modules/ambient/package.json new file mode 100644 index 000000000..0967ef424 --- /dev/null +++ b/tests/cjs-path-mapping/node_modules/ambient/package.json @@ -0,0 +1 @@ +{} diff --git a/tests/cjs-path-mapping/node_modules/depends-on-lodash/index.d.ts b/tests/cjs-path-mapping/node_modules/depends-on-lodash/index.d.ts new file mode 100644 index 000000000..7c7e06362 --- /dev/null +++ b/tests/cjs-path-mapping/node_modules/depends-on-lodash/index.d.ts @@ -0,0 +1,3 @@ +declare module 'depends-on-lodash' { + export const proxyLodash: any; +} diff --git a/tests/cjs-path-mapping/node_modules/depends-on-lodash/index.js b/tests/cjs-path-mapping/node_modules/depends-on-lodash/index.js new file mode 100644 index 000000000..80a74e960 --- /dev/null +++ b/tests/cjs-path-mapping/node_modules/depends-on-lodash/index.js @@ -0,0 +1,3 @@ +const lodash = require('lodash'); + +exports.proxyLodash = lodash diff --git a/tests/cjs-path-mapping/node_modules/depends-on-lodash/package.json b/tests/cjs-path-mapping/node_modules/depends-on-lodash/package.json new file mode 100644 index 000000000..0967ef424 --- /dev/null +++ b/tests/cjs-path-mapping/node_modules/depends-on-lodash/package.json @@ -0,0 +1 @@ +{} diff --git a/tests/cjs-path-mapping/node_modules/lodash/index.d.ts b/tests/cjs-path-mapping/node_modules/lodash/index.d.ts new file mode 100644 index 000000000..563853d68 --- /dev/null +++ b/tests/cjs-path-mapping/node_modules/lodash/index.d.ts @@ -0,0 +1,4 @@ +declare module 'lodash' { + const defaultExport = 'lodash' + export default defaultExport +} diff --git a/tests/cjs-path-mapping/node_modules/lodash/index.js b/tests/cjs-path-mapping/node_modules/lodash/index.js new file mode 100644 index 000000000..8cae1154e --- /dev/null +++ b/tests/cjs-path-mapping/node_modules/lodash/index.js @@ -0,0 +1 @@ +module.exports = 'lodash'; diff --git a/tests/cjs-path-mapping/node_modules/lodash/package.json b/tests/cjs-path-mapping/node_modules/lodash/package.json new file mode 100644 index 000000000..0967ef424 --- /dev/null +++ b/tests/cjs-path-mapping/node_modules/lodash/package.json @@ -0,0 +1 @@ +{} diff --git a/tests/cjs-path-mapping/node_modules/some-cjs-dependency/index.d.ts b/tests/cjs-path-mapping/node_modules/some-cjs-dependency/index.d.ts new file mode 100644 index 000000000..f8bd3426e --- /dev/null +++ b/tests/cjs-path-mapping/node_modules/some-cjs-dependency/index.d.ts @@ -0,0 +1,4 @@ +declare module 'some-cjs-dependency' { + const defaultExport = 'export-from-some-cjs-dependency' + export = defaultExport +} diff --git a/tests/cjs-path-mapping/node_modules/some-cjs-dependency/index.js b/tests/cjs-path-mapping/node_modules/some-cjs-dependency/index.js new file mode 100644 index 000000000..fcbabb7d6 --- /dev/null +++ b/tests/cjs-path-mapping/node_modules/some-cjs-dependency/index.js @@ -0,0 +1 @@ +module.exports = 'export-from-some-cjs-dependency'; diff --git a/tests/cjs-path-mapping/node_modules/some-cjs-dependency/package.json b/tests/cjs-path-mapping/node_modules/some-cjs-dependency/package.json new file mode 100644 index 000000000..0967ef424 --- /dev/null +++ b/tests/cjs-path-mapping/node_modules/some-cjs-dependency/package.json @@ -0,0 +1 @@ +{} diff --git a/tests/cjs-path-mapping/node_modules/some-esm-dependency/index.d.ts b/tests/cjs-path-mapping/node_modules/some-esm-dependency/index.d.ts new file mode 100644 index 000000000..fc659026d --- /dev/null +++ b/tests/cjs-path-mapping/node_modules/some-esm-dependency/index.d.ts @@ -0,0 +1,4 @@ +declare module 'some-esm-dependency' { + const defaultExport = 'export-from-some-esm-dependency' + export default defaultExport +} diff --git a/tests/cjs-path-mapping/node_modules/some-esm-dependency/index.js b/tests/cjs-path-mapping/node_modules/some-esm-dependency/index.js new file mode 100644 index 000000000..44fe556cc --- /dev/null +++ b/tests/cjs-path-mapping/node_modules/some-esm-dependency/index.js @@ -0,0 +1 @@ +export default 'export-from-some-esm-dependency'; diff --git a/tests/cjs-path-mapping/node_modules/some-esm-dependency/package.json b/tests/cjs-path-mapping/node_modules/some-esm-dependency/package.json new file mode 100644 index 000000000..32458cb54 --- /dev/null +++ b/tests/cjs-path-mapping/node_modules/some-esm-dependency/package.json @@ -0,0 +1,9 @@ +{ + "type": "module", + "main": "./index.js", + "exports": { + "import": "./index.js", + "require": "./index.js", + "default": "./index.js" + } +} diff --git a/tests/cjs-path-mapping/package.json b/tests/cjs-path-mapping/package.json new file mode 100644 index 000000000..0967ef424 --- /dev/null +++ b/tests/cjs-path-mapping/package.json @@ -0,0 +1 @@ +{} diff --git a/tests/cjs-path-mapping/tsconfig-baseurl-no-paths.json b/tests/cjs-path-mapping/tsconfig-baseurl-no-paths.json new file mode 100644 index 000000000..e33017d5d --- /dev/null +++ b/tests/cjs-path-mapping/tsconfig-baseurl-no-paths.json @@ -0,0 +1,7 @@ +{ + "extends": "./tsconfig.base.json", + "include": ["**/*.ts", "**/*.mts", "**/*.tsx", "**/*.js", "**/*.jsx"], + "compilerOptions": { + "baseUrl": "./level-1/level-2" + } +} diff --git a/tests/cjs-path-mapping/tsconfig-baseurl-some-paths.json b/tests/cjs-path-mapping/tsconfig-baseurl-some-paths.json new file mode 100644 index 000000000..5f7bf5e58 --- /dev/null +++ b/tests/cjs-path-mapping/tsconfig-baseurl-some-paths.json @@ -0,0 +1,13 @@ +{ + "extends": "./tsconfig.base.json", + "include": ["**/*.ts", "**/*.mts", "**/*.tsx", "**/*.js", "**/*.jsx"], + "compilerOptions": { + "baseUrl": "./level-1/level-2", + "paths": { + "mapped/*": ["./mapped-*"], + "candidate/*": ["./candidate-1-*", "./candidate-2-*"] + // "map2/specific/*": ["./2-specific-*"], + // "static": ["./static.js"] + } + } +} diff --git a/tests/cjs-path-mapping/tsconfig-baseurl-star-path.json b/tests/cjs-path-mapping/tsconfig-baseurl-star-path.json new file mode 100644 index 000000000..ebadfcedd --- /dev/null +++ b/tests/cjs-path-mapping/tsconfig-baseurl-star-path.json @@ -0,0 +1,10 @@ +{ + "extends": "./tsconfig.base.json", + "include": ["**/*.ts", "**/*.mts", "**/*.tsx", "**/*.js", "**/*.jsx"], + "compilerOptions": { + "baseUrl": "./level-1", + "paths": { + "*": ["./level-2/*"] + } + } +} diff --git a/tests/cjs-path-mapping/tsconfig.base.json b/tests/cjs-path-mapping/tsconfig.base.json new file mode 100644 index 000000000..b143eec9b --- /dev/null +++ b/tests/cjs-path-mapping/tsconfig.base.json @@ -0,0 +1,14 @@ +{ + "compilerOptions": { + "allowJs": true, + "jsx": "react", + "module": "CommonJS", + "moduleResolution": "node", + "outDir": "./lib" + }, + "ts-node": { + "experimentalPathMapping": "both", + "experimentalResolver": true, + "transpileOnly": true + } +} diff --git a/tests/cjs-path-mapping/tsconfig.json b/tests/cjs-path-mapping/tsconfig.json new file mode 100644 index 000000000..524bb2d38 --- /dev/null +++ b/tests/cjs-path-mapping/tsconfig.json @@ -0,0 +1,4 @@ +{ + "extends": "./tsconfig-baseurl-some-paths.json", + "include": ["**/*.ts", "**/*.mts", "**/*.tsx", "**/*.js", "**/*.jsx"] +} diff --git a/tests/esm-path-mapping/ignore-type-definitions.ts b/tests/esm-path-mapping/ignore-type-definitions.ts new file mode 100644 index 000000000..612cc6f3f --- /dev/null +++ b/tests/esm-path-mapping/ignore-type-definitions.ts @@ -0,0 +1,8 @@ +// Should ignore local ambient.d.ts +import ambient from 'ambient'; + +// Pre-conditions +import * as assert from 'assert'; + +// Assertions +assert.strictEqual(ambient, 'ambient'); // not our 'local-ambient' diff --git a/tests/esm-path-mapping/import-at-base-no-extension.ts b/tests/esm-path-mapping/import-at-base-no-extension.ts new file mode 100644 index 000000000..fad33cf3c --- /dev/null +++ b/tests/esm-path-mapping/import-at-base-no-extension.ts @@ -0,0 +1,12 @@ +// Should import from base directory, assuming file extension +import atBaseJs from 'at-base-js'; +import atBaseJsx from 'at-base-jsx'; +import atBaseTs from 'at-base-ts'; + +// Pre-conditions +import * as assert from 'assert'; + +// Assertions +assert.strictEqual(atBaseJs, 'at-base-js'); +assert.strictEqual(atBaseJsx, 'at-base-jsx'); +assert.strictEqual(atBaseTs, 'at-base-ts'); diff --git a/tests/esm-path-mapping/import-at-base.ts b/tests/esm-path-mapping/import-at-base.ts new file mode 100644 index 000000000..bce786af4 --- /dev/null +++ b/tests/esm-path-mapping/import-at-base.ts @@ -0,0 +1,12 @@ +// Should import from base directory +import atBaseJs from 'at-base-js.js'; +import atBaseJsx from 'at-base-jsx.js'; +import atBaseTs from 'at-base-ts.js'; + +// Pre-conditions +import * as assert from 'assert'; + +// Assertions +assert.strictEqual(atBaseJs, 'at-base-js'); +assert.strictEqual(atBaseJsx, 'at-base-jsx'); +assert.strictEqual(atBaseTs, 'at-base-ts'); diff --git a/tests/esm-path-mapping/import-below-base.ts b/tests/esm-path-mapping/import-below-base.ts new file mode 100644 index 000000000..f318ff24e --- /dev/null +++ b/tests/esm-path-mapping/import-below-base.ts @@ -0,0 +1,12 @@ +// Should import below base directory +import belowBaseJs from 'level-3/below-base-js.js'; +import belowBaseJsx from 'level-3/below-base-jsx.js'; +import belowBaseTs from 'level-3/below-base-ts.js'; + +// Pre-conditions +import * as assert from 'assert'; + +// Assertions +assert.strictEqual(belowBaseJs, 'below-base-js'); +assert.strictEqual(belowBaseJsx, 'below-base-jsx'); +assert.strictEqual(belowBaseTs, 'below-base-ts'); diff --git a/tests/esm-path-mapping/import-from-js-jsx-tsx.ts b/tests/esm-path-mapping/import-from-js-jsx-tsx.ts new file mode 100644 index 000000000..84f44fcca --- /dev/null +++ b/tests/esm-path-mapping/import-from-js-jsx-tsx.ts @@ -0,0 +1,3 @@ +import './import-from-js.js'; +import './import-from-jsx.js'; +import './import-from-tsx.js'; diff --git a/tests/esm-path-mapping/import-from-js.js b/tests/esm-path-mapping/import-from-js.js new file mode 100644 index 000000000..bce786af4 --- /dev/null +++ b/tests/esm-path-mapping/import-from-js.js @@ -0,0 +1,12 @@ +// Should import from base directory +import atBaseJs from 'at-base-js.js'; +import atBaseJsx from 'at-base-jsx.js'; +import atBaseTs from 'at-base-ts.js'; + +// Pre-conditions +import * as assert from 'assert'; + +// Assertions +assert.strictEqual(atBaseJs, 'at-base-js'); +assert.strictEqual(atBaseJsx, 'at-base-jsx'); +assert.strictEqual(atBaseTs, 'at-base-ts'); diff --git a/tests/esm-path-mapping/import-from-jsx.jsx b/tests/esm-path-mapping/import-from-jsx.jsx new file mode 100644 index 000000000..bce786af4 --- /dev/null +++ b/tests/esm-path-mapping/import-from-jsx.jsx @@ -0,0 +1,12 @@ +// Should import from base directory +import atBaseJs from 'at-base-js.js'; +import atBaseJsx from 'at-base-jsx.js'; +import atBaseTs from 'at-base-ts.js'; + +// Pre-conditions +import * as assert from 'assert'; + +// Assertions +assert.strictEqual(atBaseJs, 'at-base-js'); +assert.strictEqual(atBaseJsx, 'at-base-jsx'); +assert.strictEqual(atBaseTs, 'at-base-ts'); diff --git a/tests/esm-path-mapping/import-from-tsx.tsx b/tests/esm-path-mapping/import-from-tsx.tsx new file mode 100644 index 000000000..bce786af4 --- /dev/null +++ b/tests/esm-path-mapping/import-from-tsx.tsx @@ -0,0 +1,12 @@ +// Should import from base directory +import atBaseJs from 'at-base-js.js'; +import atBaseJsx from 'at-base-jsx.js'; +import atBaseTs from 'at-base-ts.js'; + +// Pre-conditions +import * as assert from 'assert'; + +// Assertions +assert.strictEqual(atBaseJs, 'at-base-js'); +assert.strictEqual(atBaseJsx, 'at-base-jsx'); +assert.strictEqual(atBaseTs, 'at-base-ts'); diff --git a/tests/esm-path-mapping/import-node-built-in.ts b/tests/esm-path-mapping/import-node-built-in.ts new file mode 100644 index 000000000..08878447a --- /dev/null +++ b/tests/esm-path-mapping/import-node-built-in.ts @@ -0,0 +1,6 @@ +// Should be able to import node built-ins +import * as assert from 'assert'; +import { stat } from 'fs'; + +// Assertions +assert.strictEqual(typeof stat, 'function'); diff --git a/tests/esm-path-mapping/import-node-modules.ts b/tests/esm-path-mapping/import-node-modules.ts new file mode 100644 index 000000000..f63c0def1 --- /dev/null +++ b/tests/esm-path-mapping/import-node-modules.ts @@ -0,0 +1,18 @@ +// Using helper to maintain similarity with CJS test +const importDefaultHelper = async (specifier: string) => + await import(specifier).then((mod) => mod.default); + +const main = async (): Promise => { + // Should be able to import from node_modules + const someCjsDependency = await importDefaultHelper('some-cjs-dependency'); + const someEsmDependency = await importDefaultHelper('some-esm-dependency'); + + // Pre-conditions + const assert: any = await import('assert'); + + // Assertions + assert.strictEqual(someCjsDependency, 'export-from-some-cjs-dependency'); + assert.strictEqual(someEsmDependency, 'export-from-some-esm-dependency'); +}; + +main(); diff --git a/tests/esm-path-mapping/import-non-existing.ts b/tests/esm-path-mapping/import-non-existing.ts new file mode 100644 index 000000000..b1432d821 --- /dev/null +++ b/tests/esm-path-mapping/import-non-existing.ts @@ -0,0 +1,5 @@ +// Should fail to import non-existing file +import nonExisting from 'non-existing.js'; + +// Pretend we want to use it +console.log(nonExisting); diff --git a/tests/esm-path-mapping/import-relative-ignores-star.ts b/tests/esm-path-mapping/import-relative-ignores-star.ts new file mode 100644 index 000000000..fdb3def8f --- /dev/null +++ b/tests/esm-path-mapping/import-relative-ignores-star.ts @@ -0,0 +1,5 @@ +// Should fail to import relative import that would need to apply a path +import shouldNotResolve from './should-not-resolve.js'; + +// Pretend we want to use it +console.log(shouldNotResolve); diff --git a/tests/esm-path-mapping/import-relative.ts b/tests/esm-path-mapping/import-relative.ts new file mode 100644 index 000000000..a4e5db46f --- /dev/null +++ b/tests/esm-path-mapping/import-relative.ts @@ -0,0 +1,12 @@ +// Should be able to use relative imports +import aboveBaseJs from './level-1/above-base-js.js'; +import aboveBaseJsx from '../cjs-path-mapping/level-1/above-base-jsx.js'; +// import aboveBaseTs from '/level-1/above-base-ts'; + +// Pre-conditions +import * as assert from 'assert'; + +// Assertions +assert.strictEqual(aboveBaseJs, 'above-base-js'); +assert.strictEqual(aboveBaseJsx, 'above-base-jsx'); +// assert.strictEqual(aboveBaseTs, 'above-base-ts'); diff --git a/tests/esm-path-mapping/import-within-node-modules.ts b/tests/esm-path-mapping/import-within-node-modules.ts new file mode 100644 index 000000000..80e949884 --- /dev/null +++ b/tests/esm-path-mapping/import-within-node-modules.ts @@ -0,0 +1,8 @@ +// Should ignore paths when importing inside node_modules +import { proxyLodash } from 'depends-on-lodash'; + +// Pre-conditions +import * as assert from 'assert'; + +// Assertions +assert.strictEqual(proxyLodash, 'lodash'); // not our 'lodash-local' diff --git a/tests/esm-path-mapping/level-1/above-base-js.js b/tests/esm-path-mapping/level-1/above-base-js.js new file mode 100644 index 000000000..14787d065 --- /dev/null +++ b/tests/esm-path-mapping/level-1/above-base-js.js @@ -0,0 +1 @@ +export default 'above-base-js'; diff --git a/tests/esm-path-mapping/level-1/above-base-jsx.jsx b/tests/esm-path-mapping/level-1/above-base-jsx.jsx new file mode 100644 index 000000000..bf2de07c1 --- /dev/null +++ b/tests/esm-path-mapping/level-1/above-base-jsx.jsx @@ -0,0 +1 @@ +export default 'above-base-jsx'; diff --git a/tests/esm-path-mapping/level-1/above-base-ts.ts b/tests/esm-path-mapping/level-1/above-base-ts.ts new file mode 100644 index 000000000..0e6b7f531 --- /dev/null +++ b/tests/esm-path-mapping/level-1/above-base-ts.ts @@ -0,0 +1 @@ +export default 'above-base-ts'; diff --git a/tests/esm-path-mapping/level-1/level-2/ambient.d.ts b/tests/esm-path-mapping/level-1/level-2/ambient.d.ts new file mode 100644 index 000000000..71fdb63fc --- /dev/null +++ b/tests/esm-path-mapping/level-1/level-2/ambient.d.ts @@ -0,0 +1,4 @@ +declare module 'ambient' { + const defaultExport = 'ambient-local'; // not the same node_modules/ambient + export default defaultExport; +} diff --git a/tests/esm-path-mapping/level-1/level-2/at-base-js.js b/tests/esm-path-mapping/level-1/level-2/at-base-js.js new file mode 100644 index 000000000..90e543097 --- /dev/null +++ b/tests/esm-path-mapping/level-1/level-2/at-base-js.js @@ -0,0 +1 @@ +export default 'at-base-js'; diff --git a/tests/esm-path-mapping/level-1/level-2/at-base-jsx.jsx b/tests/esm-path-mapping/level-1/level-2/at-base-jsx.jsx new file mode 100644 index 000000000..4efaeebfe --- /dev/null +++ b/tests/esm-path-mapping/level-1/level-2/at-base-jsx.jsx @@ -0,0 +1,6 @@ +export default 'at-base-jsx'; + +const React = { + createElement() {}, +}; +const div =
; diff --git a/tests/esm-path-mapping/level-1/level-2/at-base-ts.ts b/tests/esm-path-mapping/level-1/level-2/at-base-ts.ts new file mode 100644 index 000000000..d1695f553 --- /dev/null +++ b/tests/esm-path-mapping/level-1/level-2/at-base-ts.ts @@ -0,0 +1 @@ +export default 'at-base-ts'; diff --git a/tests/esm-path-mapping/level-1/level-2/candidate-1-foo.ts b/tests/esm-path-mapping/level-1/level-2/candidate-1-foo.ts new file mode 100644 index 000000000..9fa19cea4 --- /dev/null +++ b/tests/esm-path-mapping/level-1/level-2/candidate-1-foo.ts @@ -0,0 +1 @@ +export default 'candidate-1-foo'; diff --git a/tests/esm-path-mapping/level-1/level-2/candidate-1-foo/bar.ts b/tests/esm-path-mapping/level-1/level-2/candidate-1-foo/bar.ts new file mode 100644 index 000000000..67f84813a --- /dev/null +++ b/tests/esm-path-mapping/level-1/level-2/candidate-1-foo/bar.ts @@ -0,0 +1 @@ +export default 'nested-bar'; // not the same as ../candidate-foo-bar diff --git a/tests/esm-path-mapping/level-1/level-2/candidate-2-bar.ts b/tests/esm-path-mapping/level-1/level-2/candidate-2-bar.ts new file mode 100644 index 000000000..6580a49f4 --- /dev/null +++ b/tests/esm-path-mapping/level-1/level-2/candidate-2-bar.ts @@ -0,0 +1 @@ +export default 'candidate-2-bar'; diff --git a/tests/esm-path-mapping/level-1/level-2/candidate-2-foo.ts b/tests/esm-path-mapping/level-1/level-2/candidate-2-foo.ts new file mode 100644 index 000000000..d8728d603 --- /dev/null +++ b/tests/esm-path-mapping/level-1/level-2/candidate-2-foo.ts @@ -0,0 +1 @@ +export default 'candidate-2-foo'; diff --git a/tests/esm-path-mapping/level-1/level-2/candidate-foo-bar.ts b/tests/esm-path-mapping/level-1/level-2/candidate-foo-bar.ts new file mode 100644 index 000000000..fcc393269 --- /dev/null +++ b/tests/esm-path-mapping/level-1/level-2/candidate-foo-bar.ts @@ -0,0 +1 @@ +export default 'candidate-foo-bar'; diff --git a/tests/esm-path-mapping/level-1/level-2/immobile.ts b/tests/esm-path-mapping/level-1/level-2/immobile.ts new file mode 100644 index 000000000..d54b5651d --- /dev/null +++ b/tests/esm-path-mapping/level-1/level-2/immobile.ts @@ -0,0 +1 @@ +export default 'immobile'; diff --git a/tests/esm-path-mapping/level-1/level-2/level-3/below-base-js.js b/tests/esm-path-mapping/level-1/level-2/level-3/below-base-js.js new file mode 100644 index 000000000..72bebdf46 --- /dev/null +++ b/tests/esm-path-mapping/level-1/level-2/level-3/below-base-js.js @@ -0,0 +1 @@ +export default 'below-base-js'; diff --git a/tests/esm-path-mapping/level-1/level-2/level-3/below-base-jsx.jsx b/tests/esm-path-mapping/level-1/level-2/level-3/below-base-jsx.jsx new file mode 100644 index 000000000..b5c8c33c2 --- /dev/null +++ b/tests/esm-path-mapping/level-1/level-2/level-3/below-base-jsx.jsx @@ -0,0 +1,6 @@ +export default 'below-base-jsx'; + +const React = { + createElement() {}, +}; +const div =
; diff --git a/tests/esm-path-mapping/level-1/level-2/level-3/below-base-ts.ts b/tests/esm-path-mapping/level-1/level-2/level-3/below-base-ts.ts new file mode 100644 index 000000000..e270d7ff1 --- /dev/null +++ b/tests/esm-path-mapping/level-1/level-2/level-3/below-base-ts.ts @@ -0,0 +1 @@ +export default 'below-base-ts'; diff --git a/tests/esm-path-mapping/level-1/level-2/lodash.ts b/tests/esm-path-mapping/level-1/level-2/lodash.ts new file mode 100644 index 000000000..aa747374a --- /dev/null +++ b/tests/esm-path-mapping/level-1/level-2/lodash.ts @@ -0,0 +1 @@ +export default 'lodash-local'; // not the same as node_modules/lodash diff --git a/tests/esm-path-mapping/level-1/level-2/mapped-js.js b/tests/esm-path-mapping/level-1/level-2/mapped-js.js new file mode 100644 index 000000000..b68675ef9 --- /dev/null +++ b/tests/esm-path-mapping/level-1/level-2/mapped-js.js @@ -0,0 +1 @@ +export default 'mapped-js'; diff --git a/tests/esm-path-mapping/level-1/level-2/mapped-jsx.jsx b/tests/esm-path-mapping/level-1/level-2/mapped-jsx.jsx new file mode 100644 index 000000000..c83d6fee6 --- /dev/null +++ b/tests/esm-path-mapping/level-1/level-2/mapped-jsx.jsx @@ -0,0 +1,6 @@ +export default 'mapped-jsx'; + +const React = { + createElement() {}, +}; +const div =
; diff --git a/tests/esm-path-mapping/level-1/level-2/mapped-ts.ts b/tests/esm-path-mapping/level-1/level-2/mapped-ts.ts new file mode 100644 index 000000000..2859038cc --- /dev/null +++ b/tests/esm-path-mapping/level-1/level-2/mapped-ts.ts @@ -0,0 +1 @@ +export default 'mapped-ts'; diff --git a/tests/esm-path-mapping/level-1/level-2/mapped-tsx.tsx b/tests/esm-path-mapping/level-1/level-2/mapped-tsx.tsx new file mode 100644 index 000000000..3ccd0d524 --- /dev/null +++ b/tests/esm-path-mapping/level-1/level-2/mapped-tsx.tsx @@ -0,0 +1,6 @@ +export default 'mapped-tsx'; + +const React = { + createElement() {}, +}; +const div =
; diff --git a/tests/esm-path-mapping/level-1/level-2/should-not-resolve.ts b/tests/esm-path-mapping/level-1/level-2/should-not-resolve.ts new file mode 100644 index 000000000..07f3178c6 --- /dev/null +++ b/tests/esm-path-mapping/level-1/level-2/should-not-resolve.ts @@ -0,0 +1 @@ +export default 'should-not-resolve-at-base'; diff --git a/tests/esm-path-mapping/level-2/should-not-resolve.ts b/tests/esm-path-mapping/level-2/should-not-resolve.ts new file mode 100644 index 000000000..938efdb04 --- /dev/null +++ b/tests/esm-path-mapping/level-2/should-not-resolve.ts @@ -0,0 +1 @@ +export default 'should-not-resolve'; diff --git a/tests/esm-path-mapping/map-from-js-jsx-tsx.ts b/tests/esm-path-mapping/map-from-js-jsx-tsx.ts new file mode 100644 index 000000000..187069410 --- /dev/null +++ b/tests/esm-path-mapping/map-from-js-jsx-tsx.ts @@ -0,0 +1,3 @@ +import './map-from-js.js'; +import './map-from-jsx.jsx'; +import './map-from-tsx.tsx'; diff --git a/tests/esm-path-mapping/map-from-js.js b/tests/esm-path-mapping/map-from-js.js new file mode 100644 index 000000000..1d007d0b3 --- /dev/null +++ b/tests/esm-path-mapping/map-from-js.js @@ -0,0 +1,23 @@ +// All mapped imports +import mappedJs from 'mapped/js.js'; +import mappedJsx from 'mapped/jsx.js'; +import mappedTs from 'mapped/ts.js'; +import mappedTsx from './level-1/level-2/mapped-tsx.js'; +import mappedTsx from 'mapped/tsx.js'; +import foo from 'candidate/foo.js'; +import bar from 'candidate/bar.js'; +import fooBar from 'candidate/foo/bar.js'; +import immobile from 'static'; + +// Pre-conditions +import * as assert from 'assert'; + +// Assertions +assert.strictEqual(mappedTs, 'mapped-ts'); +assert.strictEqual(mappedTsx, 'mapped-tsx'); +assert.strictEqual(mappedJs, 'mapped-js'); +assert.strictEqual(mappedJsx, 'mapped-jsx'); +assert.strictEqual(foo, 'candidate-1-foo'); +assert.strictEqual(bar, 'candidate-2-bar'); +assert.strictEqual(fooBar, 'candidate-foo-bar'); +assert.strictEqual(immobile, 'immobile'); diff --git a/tests/esm-path-mapping/map-from-jsx.jsx b/tests/esm-path-mapping/map-from-jsx.jsx new file mode 100644 index 000000000..bb0e6a113 --- /dev/null +++ b/tests/esm-path-mapping/map-from-jsx.jsx @@ -0,0 +1,22 @@ +// All mapped imports +import mappedJs from 'mapped/js.js'; +import mappedJsx from 'mapped/jsx.js'; +import mappedTs from 'mapped/ts.js'; +import mappedTsx from 'mapped/tsx.js'; +import foo from 'candidate/foo.js'; +import bar from 'candidate/bar.js'; +import fooBar from 'candidate/foo/bar.js'; +import immobile from 'static'; + +// Pre-conditions +import * as assert from 'assert'; + +// Assertions +assert.strictEqual(mappedTs, 'mapped-ts'); +assert.strictEqual(mappedTsx, 'mapped-tsx'); +assert.strictEqual(mappedJs, 'mapped-js'); +assert.strictEqual(mappedJsx, 'mapped-jsx'); +assert.strictEqual(foo, 'candidate-1-foo'); +assert.strictEqual(bar, 'candidate-2-bar'); +assert.strictEqual(fooBar, 'candidate-foo-bar'); +assert.strictEqual(immobile, 'immobile'); diff --git a/tests/esm-path-mapping/map-from-tsx.tsx b/tests/esm-path-mapping/map-from-tsx.tsx new file mode 100644 index 000000000..bb0e6a113 --- /dev/null +++ b/tests/esm-path-mapping/map-from-tsx.tsx @@ -0,0 +1,22 @@ +// All mapped imports +import mappedJs from 'mapped/js.js'; +import mappedJsx from 'mapped/jsx.js'; +import mappedTs from 'mapped/ts.js'; +import mappedTsx from 'mapped/tsx.js'; +import foo from 'candidate/foo.js'; +import bar from 'candidate/bar.js'; +import fooBar from 'candidate/foo/bar.js'; +import immobile from 'static'; + +// Pre-conditions +import * as assert from 'assert'; + +// Assertions +assert.strictEqual(mappedTs, 'mapped-ts'); +assert.strictEqual(mappedTsx, 'mapped-tsx'); +assert.strictEqual(mappedJs, 'mapped-js'); +assert.strictEqual(mappedJsx, 'mapped-jsx'); +assert.strictEqual(foo, 'candidate-1-foo'); +assert.strictEqual(bar, 'candidate-2-bar'); +assert.strictEqual(fooBar, 'candidate-foo-bar'); +assert.strictEqual(immobile, 'immobile'); diff --git a/tests/esm-path-mapping/map-to-first-available-candidate.ts b/tests/esm-path-mapping/map-to-first-available-candidate.ts new file mode 100644 index 000000000..7be0150d5 --- /dev/null +++ b/tests/esm-path-mapping/map-to-first-available-candidate.ts @@ -0,0 +1,10 @@ +// Should map to the first path pattern that exists +import foo from 'candidate/foo.js'; +import bar from 'candidate/bar.js'; + +// Pre-conditions +import * as assert from 'assert'; + +// Assertions +assert.strictEqual(foo, 'candidate-1-foo'); +assert.strictEqual(bar, 'candidate-2-bar'); diff --git a/tests/esm-path-mapping/map-to-js-jsx-tsx.ts b/tests/esm-path-mapping/map-to-js-jsx-tsx.ts new file mode 100644 index 000000000..286f20314 --- /dev/null +++ b/tests/esm-path-mapping/map-to-js-jsx-tsx.ts @@ -0,0 +1,12 @@ +// Should be able to use path to map to js, jsx, tsx +import mappedJs from 'mapped/js.js'; +import mappedJsx from 'mapped/jsx.js'; +import mappedTsx from 'mapped/tsx.js'; + +// Pre-conditions +import * as assert from 'assert'; + +// Assertions +assert.strictEqual(mappedJs, 'mapped-js'); +assert.strictEqual(mappedJsx, 'mapped-jsx'); +assert.strictEqual(mappedTsx, 'mapped-tsx'); diff --git a/tests/esm-path-mapping/map-using-more-specific-path.ts b/tests/esm-path-mapping/map-using-more-specific-path.ts new file mode 100644 index 000000000..da9cbb912 --- /dev/null +++ b/tests/esm-path-mapping/map-using-more-specific-path.ts @@ -0,0 +1,10 @@ +// Should map using the more specific path +// ❌ "candidate/*": ["./candidate-1-*", "./candidate-2-*"] => ./candidate-1-foo/bar +// ✅ "candidate/foo/*": ["./candidate-foo-*"] => ./candidate-foo-bar +import fooBar from 'candidate/foo/bar.js'; + +// Pre-conditions +import * as assert from 'assert'; + +// Assertions +assert.strictEqual(fooBar, 'candidate-foo-bar'); diff --git a/tests/esm-path-mapping/map-using-prefix.ts b/tests/esm-path-mapping/map-using-prefix.ts new file mode 100644 index 000000000..c9e89c331 --- /dev/null +++ b/tests/esm-path-mapping/map-using-prefix.ts @@ -0,0 +1,8 @@ +// Should be able to use path to map import +import mappedTs from 'mapped/ts.js'; + +// Pre-conditions +import * as assert from 'assert'; + +// Assertions +assert.strictEqual(mappedTs, 'mapped-ts'); diff --git a/tests/esm-path-mapping/map-using-static-path.ts b/tests/esm-path-mapping/map-using-static-path.ts new file mode 100644 index 000000000..47241c528 --- /dev/null +++ b/tests/esm-path-mapping/map-using-static-path.ts @@ -0,0 +1,8 @@ +// Should map to static path (no wildcard) +import immobile from 'static'; + +// Pre-conditions +import * as assert from 'assert'; + +// Assertions +assert.strictEqual(immobile, 'immobile'); diff --git a/tests/esm-path-mapping/node_modules/ambient/index.d.ts b/tests/esm-path-mapping/node_modules/ambient/index.d.ts new file mode 100644 index 000000000..0d8c7c450 --- /dev/null +++ b/tests/esm-path-mapping/node_modules/ambient/index.d.ts @@ -0,0 +1,4 @@ +declare module 'ambient' { + const defaultExport = 'ambient'; + export default defaultExport; +} diff --git a/tests/esm-path-mapping/node_modules/ambient/index.js b/tests/esm-path-mapping/node_modules/ambient/index.js new file mode 100644 index 000000000..4b25619bb --- /dev/null +++ b/tests/esm-path-mapping/node_modules/ambient/index.js @@ -0,0 +1 @@ +module.exports = 'ambient'; diff --git a/tests/esm-path-mapping/node_modules/ambient/package.json b/tests/esm-path-mapping/node_modules/ambient/package.json new file mode 100644 index 000000000..0967ef424 --- /dev/null +++ b/tests/esm-path-mapping/node_modules/ambient/package.json @@ -0,0 +1 @@ +{} diff --git a/tests/esm-path-mapping/node_modules/depends-on-lodash/index.d.ts b/tests/esm-path-mapping/node_modules/depends-on-lodash/index.d.ts new file mode 100644 index 000000000..7c7e06362 --- /dev/null +++ b/tests/esm-path-mapping/node_modules/depends-on-lodash/index.d.ts @@ -0,0 +1,3 @@ +declare module 'depends-on-lodash' { + export const proxyLodash: any; +} diff --git a/tests/esm-path-mapping/node_modules/depends-on-lodash/index.js b/tests/esm-path-mapping/node_modules/depends-on-lodash/index.js new file mode 100644 index 000000000..80a74e960 --- /dev/null +++ b/tests/esm-path-mapping/node_modules/depends-on-lodash/index.js @@ -0,0 +1,3 @@ +const lodash = require('lodash'); + +exports.proxyLodash = lodash diff --git a/tests/esm-path-mapping/node_modules/depends-on-lodash/package.json b/tests/esm-path-mapping/node_modules/depends-on-lodash/package.json new file mode 100644 index 000000000..0967ef424 --- /dev/null +++ b/tests/esm-path-mapping/node_modules/depends-on-lodash/package.json @@ -0,0 +1 @@ +{} diff --git a/tests/esm-path-mapping/node_modules/lodash/index.d.ts b/tests/esm-path-mapping/node_modules/lodash/index.d.ts new file mode 100644 index 000000000..563853d68 --- /dev/null +++ b/tests/esm-path-mapping/node_modules/lodash/index.d.ts @@ -0,0 +1,4 @@ +declare module 'lodash' { + const defaultExport = 'lodash' + export default defaultExport +} diff --git a/tests/esm-path-mapping/node_modules/lodash/index.js b/tests/esm-path-mapping/node_modules/lodash/index.js new file mode 100644 index 000000000..8cae1154e --- /dev/null +++ b/tests/esm-path-mapping/node_modules/lodash/index.js @@ -0,0 +1 @@ +module.exports = 'lodash'; diff --git a/tests/esm-path-mapping/node_modules/lodash/package.json b/tests/esm-path-mapping/node_modules/lodash/package.json new file mode 100644 index 000000000..0967ef424 --- /dev/null +++ b/tests/esm-path-mapping/node_modules/lodash/package.json @@ -0,0 +1 @@ +{} diff --git a/tests/esm-path-mapping/node_modules/some-cjs-dependency/index.d.ts b/tests/esm-path-mapping/node_modules/some-cjs-dependency/index.d.ts new file mode 100644 index 000000000..d4efcfa8b --- /dev/null +++ b/tests/esm-path-mapping/node_modules/some-cjs-dependency/index.d.ts @@ -0,0 +1,4 @@ +declare module 'some-cjs-dependency' { + const defaultExport = 'export-from-some-cjs-dependency' + export default defaultExport +} diff --git a/tests/esm-path-mapping/node_modules/some-cjs-dependency/index.js b/tests/esm-path-mapping/node_modules/some-cjs-dependency/index.js new file mode 100644 index 000000000..fcbabb7d6 --- /dev/null +++ b/tests/esm-path-mapping/node_modules/some-cjs-dependency/index.js @@ -0,0 +1 @@ +module.exports = 'export-from-some-cjs-dependency'; diff --git a/tests/esm-path-mapping/node_modules/some-cjs-dependency/package.json b/tests/esm-path-mapping/node_modules/some-cjs-dependency/package.json new file mode 100644 index 000000000..0967ef424 --- /dev/null +++ b/tests/esm-path-mapping/node_modules/some-cjs-dependency/package.json @@ -0,0 +1 @@ +{} diff --git a/tests/esm-path-mapping/node_modules/some-esm-dependency/index.d.ts b/tests/esm-path-mapping/node_modules/some-esm-dependency/index.d.ts new file mode 100644 index 000000000..fc659026d --- /dev/null +++ b/tests/esm-path-mapping/node_modules/some-esm-dependency/index.d.ts @@ -0,0 +1,4 @@ +declare module 'some-esm-dependency' { + const defaultExport = 'export-from-some-esm-dependency' + export default defaultExport +} diff --git a/tests/esm-path-mapping/node_modules/some-esm-dependency/index.js b/tests/esm-path-mapping/node_modules/some-esm-dependency/index.js new file mode 100644 index 000000000..44fe556cc --- /dev/null +++ b/tests/esm-path-mapping/node_modules/some-esm-dependency/index.js @@ -0,0 +1 @@ +export default 'export-from-some-esm-dependency'; diff --git a/tests/esm-path-mapping/node_modules/some-esm-dependency/package.json b/tests/esm-path-mapping/node_modules/some-esm-dependency/package.json new file mode 100644 index 000000000..32458cb54 --- /dev/null +++ b/tests/esm-path-mapping/node_modules/some-esm-dependency/package.json @@ -0,0 +1,9 @@ +{ + "type": "module", + "main": "./index.js", + "exports": { + "import": "./index.js", + "require": "./index.js", + "default": "./index.js" + } +} diff --git a/tests/esm-path-mapping/package.json b/tests/esm-path-mapping/package.json new file mode 100644 index 000000000..3dbc1ca59 --- /dev/null +++ b/tests/esm-path-mapping/package.json @@ -0,0 +1,3 @@ +{ + "type": "module" +} diff --git a/tests/esm-path-mapping/tsconfig-baseurl-no-paths.json b/tests/esm-path-mapping/tsconfig-baseurl-no-paths.json new file mode 100644 index 000000000..e33017d5d --- /dev/null +++ b/tests/esm-path-mapping/tsconfig-baseurl-no-paths.json @@ -0,0 +1,7 @@ +{ + "extends": "./tsconfig.base.json", + "include": ["**/*.ts", "**/*.mts", "**/*.tsx", "**/*.js", "**/*.jsx"], + "compilerOptions": { + "baseUrl": "./level-1/level-2" + } +} diff --git a/tests/esm-path-mapping/tsconfig-baseurl-some-paths.json b/tests/esm-path-mapping/tsconfig-baseurl-some-paths.json new file mode 100644 index 000000000..e8f8e4a40 --- /dev/null +++ b/tests/esm-path-mapping/tsconfig-baseurl-some-paths.json @@ -0,0 +1,13 @@ +{ + "extends": "./tsconfig.base.json", + "include": ["**/*.ts", "**/*.mts", "**/*.tsx", "**/*.js", "**/*.jsx"], + "compilerOptions": { + "baseUrl": "./level-1/level-2", + "paths": { + "mapped/*": ["./mapped-*"], + "candidate/*": ["./candidate-1-*", "./candidate-2-*"], + "candidate/foo/*": ["./candidate-foo-*"], + "static": ["./immobile.js"] + } + } +} diff --git a/tests/esm-path-mapping/tsconfig-baseurl-star-path.json b/tests/esm-path-mapping/tsconfig-baseurl-star-path.json new file mode 100644 index 000000000..ebadfcedd --- /dev/null +++ b/tests/esm-path-mapping/tsconfig-baseurl-star-path.json @@ -0,0 +1,10 @@ +{ + "extends": "./tsconfig.base.json", + "include": ["**/*.ts", "**/*.mts", "**/*.tsx", "**/*.js", "**/*.jsx"], + "compilerOptions": { + "baseUrl": "./level-1", + "paths": { + "*": ["./level-2/*"] + } + } +} diff --git a/tests/esm-path-mapping/tsconfig.base.json b/tests/esm-path-mapping/tsconfig.base.json new file mode 100644 index 000000000..547e461fc --- /dev/null +++ b/tests/esm-path-mapping/tsconfig.base.json @@ -0,0 +1,14 @@ +{ + "compilerOptions": { + "allowJs": true, + "jsx": "react", + "module": "ESNext", + "moduleResolution": "node", + "outDir": "./lib" + }, + "ts-node": { + "experimentalPathMapping": "both", + "experimentalResolver": true, + "transpileOnly": true + } +} diff --git a/tests/esm-path-mapping/tsconfig.json b/tests/esm-path-mapping/tsconfig.json new file mode 100644 index 000000000..524bb2d38 --- /dev/null +++ b/tests/esm-path-mapping/tsconfig.json @@ -0,0 +1,4 @@ +{ + "extends": "./tsconfig-baseurl-some-paths.json", + "include": ["**/*.ts", "**/*.mts", "**/*.tsx", "**/*.js", "**/*.jsx"] +} diff --git a/website/docs/options.md b/website/docs/options.md index bcfce5563..1bee83b91 100644 --- a/website/docs/options.md +++ b/website/docs/options.md @@ -374,6 +374,13 @@ Enable experimental features that re-map imports and require calls to support: ` *Default:* `false`
*Can only be specified via `tsconfig.json` or API.* +### experimentalPathMapping + +Enable TypeScript path mapping in the ESM loader, CommonJS loader, or both. Today, the default is `'esm'` to map paths in the experimental ESM loader but not CommonJS. In the next major release, the default will become `'both'`. + +*Default:* `'esm'`
+*Can only be specified via `tsconfig.json` or API.* + ## API Options The API includes [additional options](https://typestrong.org/ts-node/api/interfaces/RegisterOptions.html) not shown here. diff --git a/website/docs/overview.md b/website/docs/overview.md index 4afde82d2..738f8e017 100644 --- a/website/docs/overview.md +++ b/website/docs/overview.md @@ -19,6 +19,7 @@ tools and libraries. * Write standalone scripts * Native ESM loader * Use third-party transpilers +* Automatic [TypeScript path mapping](https://www.typescriptlang.org/docs/handbook/module-resolution.html#path-mapping) * Use custom transformers * Integrate with test runners, debuggers, and CLI tools * Compatible with pre-compilation for production