diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index cc3cb913..e4aa9fec 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -10,7 +10,7 @@ jobs: strategy: matrix: os: [ubuntu-latest, windows-latest] - timeout-minutes: 5 + timeout-minutes: 10 steps: - name: Checkout diff --git a/src/@types/module.d.ts b/src/@types/module.d.ts index 75fadc3b..724e8678 100644 --- a/src/@types/module.d.ts +++ b/src/@types/module.d.ts @@ -20,6 +20,7 @@ declare module 'module' { export const _cache: NodeJS.Require['cache']; export type Parent = { + id: string; /** * Can be null if the parent id is 'internal/preload' (e.g. via --require) diff --git a/src/cjs/api/module-extensions.ts b/src/cjs/api/module-extensions.ts index a8c5e69c..08b3618e 100644 --- a/src/cjs/api/module-extensions.ts +++ b/src/cjs/api/module-extensions.ts @@ -1,4 +1,5 @@ import fs from 'node:fs'; +import path from 'node:path'; import Module from 'node:module'; import type { TransformOptions } from 'esbuild'; import { transformSync } from '../../utils/transform/index.js'; @@ -96,6 +97,17 @@ export const createExtensions = ( return defaultLoader(module, filePath); } + /** + * In new Module(), m.path = path.dirname(module.id) but module.id coming from + * ESM resolver may be a data: path + * + * In these cases, we fix m.path to be the actual directory of the file + */ + // https://github.com/nodejs/node/blob/v22.8.0/lib/internal/modules/cjs/loader.js#L298 + if (module.id.startsWith('data:text/javascript,')) { + module.path = path.dirname(cleanFilePath); + } + // For tracking dependencies in watch mode if (parent?.send) { parent.send({ diff --git a/tests/specs/smoke.ts b/tests/specs/smoke.ts index 24606f95..8843e0f3 100644 --- a/tests/specs/smoke.ts +++ b/tests/specs/smoke.ts @@ -13,7 +13,7 @@ const wasmPath = path.resolve('tests/fixtures/test.wasm'); const wasmPathUrl = pathToFileURL(wasmPath).toString(); export default testSuite(async ({ describe }, { tsx, supports, version }: NodeApis) => { - describe('Smoke', ({ describe }) => { + describe('Smoke', ({ describe, test }) => { for (const packageType of packageTypes) { const isCommonJs = packageType === 'commonjs'; @@ -481,5 +481,44 @@ export default testSuite(async ({ describe }, { tsx, supports, version }: NodeAp } }); } + + // https://github.com/privatenumber/tsx/issues/651 + test('resolves same relative path from CJS loaded by ESM', async ({ onTestFail }) => { + await using fixture = await createFixture({ + 'package.json': createPackageJson({ type: 'commonjs' }), + a: { + 'index.ts': ` + import { value } from './value.js'; + + if (value !== 1) { + throw new Error('Unexpected value'); + } + `, + 'value.js': 'export const value = 1;', + }, + b: { + 'index.ts': ` + import { value } from './value.js'; + + if (value !== 2) { + throw new Error('Unexpected value'); + } + `, + 'value.js': 'export const value = 2;', + }, + 'index.mjs': ` + import './a/index.js'; + import './b/index.js'; + `, + }); + + const p = await tsx(['index.mjs'], { + cwd: fixture.path, + }); + onTestFail(() => { + console.log(p); + }); + expect(p.failed).toBe(false); + }); }); });