We now hopefully align with `moduleResolution: bundler` tsconfig option, allowing directory imports in every scenario, and allowing proper module imports when not going through the type mapping. This regressed in #32078. Fixes #32480, fixes #31811.
This commit is contained in:
parent
8762407038
commit
13e6e48a2b
|
|
@ -23,7 +23,7 @@ import type { LoadedTsConfig } from '../third_party/tsconfig-loader';
|
||||||
import { tsConfigLoader } from '../third_party/tsconfig-loader';
|
import { tsConfigLoader } from '../third_party/tsconfig-loader';
|
||||||
import Module from 'module';
|
import Module from 'module';
|
||||||
import type { BabelPlugin, BabelTransformFunction } from './babelBundle';
|
import type { BabelPlugin, BabelTransformFunction } from './babelBundle';
|
||||||
import { createFileMatcher, fileIsModule, resolveImportSpecifierExtension } from '../util';
|
import { createFileMatcher, fileIsModule, resolveImportSpecifierAfterMapping } from '../util';
|
||||||
import type { Matcher } from '../util';
|
import type { Matcher } from '../util';
|
||||||
import { getFromCompilationCache, currentFileDepsCollector, belongsToNodeModules, installSourceMapSupport } from './compilationCache';
|
import { getFromCompilationCache, currentFileDepsCollector, belongsToNodeModules, installSourceMapSupport } from './compilationCache';
|
||||||
|
|
||||||
|
|
@ -99,8 +99,13 @@ export function resolveHook(filename: string, specifier: string): string | undef
|
||||||
return;
|
return;
|
||||||
|
|
||||||
if (isRelativeSpecifier(specifier))
|
if (isRelativeSpecifier(specifier))
|
||||||
return resolveImportSpecifierExtension(path.resolve(path.dirname(filename), specifier));
|
return resolveImportSpecifierAfterMapping(path.resolve(path.dirname(filename), specifier), false);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* TypeScript discourages path-mapping into node_modules:
|
||||||
|
* https://www.typescriptlang.org/docs/handbook/modules/reference.html#paths-should-not-point-to-monorepo-packages-or-node_modules-packages
|
||||||
|
* However, if path-mapping doesn't yield a result, TypeScript falls back to the default resolution through node_modules.
|
||||||
|
*/
|
||||||
const isTypeScript = filename.endsWith('.ts') || filename.endsWith('.tsx');
|
const isTypeScript = filename.endsWith('.ts') || filename.endsWith('.tsx');
|
||||||
const tsconfigs = loadAndValidateTsconfigsForFile(filename);
|
const tsconfigs = loadAndValidateTsconfigsForFile(filename);
|
||||||
for (const tsconfig of tsconfigs) {
|
for (const tsconfig of tsconfigs) {
|
||||||
|
|
@ -142,7 +147,7 @@ export function resolveHook(filename: string, specifier: string): string | undef
|
||||||
if (value.includes('*'))
|
if (value.includes('*'))
|
||||||
candidate = candidate.replace('*', matchedPartOfSpecifier);
|
candidate = candidate.replace('*', matchedPartOfSpecifier);
|
||||||
candidate = path.resolve(tsconfig.pathsBase!, candidate);
|
candidate = path.resolve(tsconfig.pathsBase!, candidate);
|
||||||
const existing = resolveImportSpecifierExtension(candidate);
|
const existing = resolveImportSpecifierAfterMapping(candidate, true);
|
||||||
if (existing) {
|
if (existing) {
|
||||||
longestPrefixLength = keyPrefix.length;
|
longestPrefixLength = keyPrefix.length;
|
||||||
pathMatchedByLongestPrefix = existing;
|
pathMatchedByLongestPrefix = existing;
|
||||||
|
|
@ -156,7 +161,7 @@ export function resolveHook(filename: string, specifier: string): string | undef
|
||||||
if (path.isAbsolute(specifier)) {
|
if (path.isAbsolute(specifier)) {
|
||||||
// Handle absolute file paths like `import '/path/to/file'`
|
// Handle absolute file paths like `import '/path/to/file'`
|
||||||
// Do not handle module imports like `import 'fs'`
|
// Do not handle module imports like `import 'fs'`
|
||||||
return resolveImportSpecifierExtension(specifier);
|
return resolveImportSpecifierAfterMapping(specifier, false);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -295,8 +295,23 @@ function folderIsModule(folder: string): boolean {
|
||||||
return require(packageJsonPath).type === 'module';
|
return require(packageJsonPath).type === 'module';
|
||||||
}
|
}
|
||||||
|
|
||||||
// This follows the --moduleResolution=bundler strategy from tsc.
|
const packageJsonMainFieldCache = new Map<string, string | undefined>();
|
||||||
// https://devblogs.microsoft.com/typescript/announcing-typescript-5-0-beta/#moduleresolution-bundler
|
|
||||||
|
function getMainFieldFromPackageJson(packageJsonPath: string) {
|
||||||
|
if (!packageJsonMainFieldCache.has(packageJsonPath)) {
|
||||||
|
let mainField: string | undefined;
|
||||||
|
try {
|
||||||
|
mainField = JSON.parse(fs.readFileSync(packageJsonPath, 'utf8')).main;
|
||||||
|
} catch {
|
||||||
|
}
|
||||||
|
packageJsonMainFieldCache.set(packageJsonPath, mainField);
|
||||||
|
}
|
||||||
|
return packageJsonMainFieldCache.get(packageJsonPath);
|
||||||
|
}
|
||||||
|
|
||||||
|
// This method performs "file extension subsitution" to find the ts, js or similar source file
|
||||||
|
// based on the import specifier, which might or might not have an extension. See TypeScript docs:
|
||||||
|
// https://www.typescriptlang.org/docs/handbook/modules/reference.html#file-extension-substitution.
|
||||||
const kExtLookups = new Map([
|
const kExtLookups = new Map([
|
||||||
['.js', ['.jsx', '.ts', '.tsx']],
|
['.js', ['.jsx', '.ts', '.tsx']],
|
||||||
['.jsx', ['.tsx']],
|
['.jsx', ['.tsx']],
|
||||||
|
|
@ -304,7 +319,7 @@ const kExtLookups = new Map([
|
||||||
['.mjs', ['.mts']],
|
['.mjs', ['.mts']],
|
||||||
['', ['.js', '.ts', '.jsx', '.tsx', '.cjs', '.mjs', '.cts', '.mts']],
|
['', ['.js', '.ts', '.jsx', '.tsx', '.cjs', '.mjs', '.cts', '.mts']],
|
||||||
]);
|
]);
|
||||||
export function resolveImportSpecifierExtension(resolved: string): string | undefined {
|
function resolveImportSpecifierExtension(resolved: string): string | undefined {
|
||||||
if (fileExists(resolved))
|
if (fileExists(resolved))
|
||||||
return resolved;
|
return resolved;
|
||||||
|
|
||||||
|
|
@ -318,13 +333,45 @@ export function resolveImportSpecifierExtension(resolved: string): string | unde
|
||||||
}
|
}
|
||||||
break; // Do not try '' when a more specific extension like '.jsx' matched.
|
break; // Do not try '' when a more specific extension like '.jsx' matched.
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// This method resolves directory imports and performs "file extension subsitution".
|
||||||
|
// It is intended to be called after the path mapping resolution.
|
||||||
|
//
|
||||||
|
// Directory imports follow the --moduleResolution=bundler strategy from tsc.
|
||||||
|
// https://www.typescriptlang.org/docs/handbook/modules/reference.html#directory-modules-index-file-resolution
|
||||||
|
// https://www.typescriptlang.org/docs/handbook/modules/reference.html#bundler
|
||||||
|
//
|
||||||
|
// See also Node.js "folder as module" behavior:
|
||||||
|
// https://nodejs.org/dist/latest-v20.x/docs/api/modules.html#folders-as-modules.
|
||||||
|
export function resolveImportSpecifierAfterMapping(resolved: string, afterPathMapping: boolean): string | undefined {
|
||||||
|
const resolvedFile = resolveImportSpecifierExtension(resolved);
|
||||||
|
if (resolvedFile)
|
||||||
|
return resolvedFile;
|
||||||
|
|
||||||
if (dirExists(resolved)) {
|
if (dirExists(resolved)) {
|
||||||
|
const packageJsonPath = path.join(resolved, 'package.json');
|
||||||
|
|
||||||
|
if (afterPathMapping) {
|
||||||
|
// Most notably, the module resolution algorithm is not performed after the path mapping.
|
||||||
|
// This means no node_modules lookup or package.json#exports.
|
||||||
|
//
|
||||||
|
// Only the "folder as module" Node.js behavior is respected:
|
||||||
|
// - consult `package.json#main`;
|
||||||
|
// - look for `index.js` or similar.
|
||||||
|
const mainField = getMainFieldFromPackageJson(packageJsonPath);
|
||||||
|
const mainFieldResolved = mainField ? resolveImportSpecifierExtension(path.resolve(resolved, mainField)) : undefined;
|
||||||
|
return mainFieldResolved || resolveImportSpecifierExtension(path.join(resolved, 'index'));
|
||||||
|
}
|
||||||
|
|
||||||
// If we import a package, let Node.js figure out the correct import based on package.json.
|
// If we import a package, let Node.js figure out the correct import based on package.json.
|
||||||
if (fileExists(path.join(resolved, 'package.json')))
|
// This also covers the "main" field for "folder as module".
|
||||||
|
if (fileExists(packageJsonPath))
|
||||||
return resolved;
|
return resolved;
|
||||||
|
|
||||||
// Otherwise, try to find a corresponding index file.
|
// Implement the "folder as module" Node.js behavior.
|
||||||
|
// Note that we do not delegate to Node.js, because we support this for ESM as well,
|
||||||
|
// following the TypeScript "bundler" mode.
|
||||||
const dirImport = path.join(resolved, 'index');
|
const dirImport = path.join(resolved, 'index');
|
||||||
return resolveImportSpecifierExtension(dirImport);
|
return resolveImportSpecifierExtension(dirImport);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -569,43 +569,6 @@ test('should resolve paths relative to the originating config when extending and
|
||||||
expect(result.exitCode).toBe(0);
|
expect(result.exitCode).toBe(0);
|
||||||
});
|
});
|
||||||
|
|
||||||
test('should import packages with non-index main script through path resolver', async ({ runInlineTest }) => {
|
|
||||||
const result = await runInlineTest({
|
|
||||||
'app/pkg/main.ts': `
|
|
||||||
export const foo = 42;
|
|
||||||
`,
|
|
||||||
'app/pkg/package.json': `
|
|
||||||
{ "main": "main.ts" }
|
|
||||||
`,
|
|
||||||
'package.json': `
|
|
||||||
{ "name": "example-project" }
|
|
||||||
`,
|
|
||||||
'playwright.config.ts': `
|
|
||||||
export default {};
|
|
||||||
`,
|
|
||||||
'tsconfig.json': `{
|
|
||||||
"compilerOptions": {
|
|
||||||
"baseUrl": ".",
|
|
||||||
"paths": {
|
|
||||||
"app/*": ["app/*"],
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}`,
|
|
||||||
'example.spec.ts': `
|
|
||||||
import { foo } from 'app/pkg';
|
|
||||||
import { test, expect } from '@playwright/test';
|
|
||||||
test('test', ({}) => {
|
|
||||||
console.log('foo=' + foo);
|
|
||||||
});
|
|
||||||
`,
|
|
||||||
});
|
|
||||||
|
|
||||||
expect(result.exitCode).toBe(0);
|
|
||||||
expect(result.passed).toBe(1);
|
|
||||||
expect(result.output).not.toContain(`find module`);
|
|
||||||
expect(result.output).toContain(`foo=42`);
|
|
||||||
});
|
|
||||||
|
|
||||||
test('should respect tsconfig project references', async ({ runInlineTest }) => {
|
test('should respect tsconfig project references', async ({ runInlineTest }) => {
|
||||||
test.info().annotations.push({ type: 'issue', description: 'https://github.com/microsoft/playwright/issues/29256' });
|
test.info().annotations.push({ type: 'issue', description: 'https://github.com/microsoft/playwright/issues/29256' });
|
||||||
|
|
||||||
|
|
@ -693,3 +656,426 @@ test('should respect --tsconfig option', async ({ runInlineTest }) => {
|
||||||
expect(result.exitCode).toBe(0);
|
expect(result.exitCode).toBe(0);
|
||||||
expect(result.output).not.toContain(`Could not`);
|
expect(result.output).not.toContain(`Could not`);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test.describe('directory imports', () => {
|
||||||
|
test('should resolve index.js without path mapping in CJS', async ({ runInlineTest, runTSC }) => {
|
||||||
|
const files = {
|
||||||
|
'foo-pkg/index.js': `
|
||||||
|
exports.foo = 'bar';
|
||||||
|
`,
|
||||||
|
'foo-pkg/index.d.ts': `
|
||||||
|
export const foo: 'bar';
|
||||||
|
`,
|
||||||
|
'a.test.ts': `
|
||||||
|
import { test, expect } from '@playwright/test';
|
||||||
|
import { foo } from './foo-pkg';
|
||||||
|
test('pass', async () => {
|
||||||
|
const bar: 'bar' = foo;
|
||||||
|
expect(bar).toBe('bar');
|
||||||
|
});
|
||||||
|
`,
|
||||||
|
};
|
||||||
|
|
||||||
|
const result = await runInlineTest(files);
|
||||||
|
expect(result.passed).toBe(1);
|
||||||
|
expect(result.exitCode).toBe(0);
|
||||||
|
|
||||||
|
const tscResult = await runTSC(files);
|
||||||
|
expect(tscResult.exitCode).toBe(0);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('should resolve index.js without path mapping in ESM', async ({ runInlineTest, runTSC }) => {
|
||||||
|
const files = {
|
||||||
|
'foo-pkg/index.js': `
|
||||||
|
export const foo = 'bar';
|
||||||
|
`,
|
||||||
|
'foo-pkg/index.d.ts': `
|
||||||
|
export const foo: 'bar';
|
||||||
|
`,
|
||||||
|
'package.json': `
|
||||||
|
{ "type": "module" }
|
||||||
|
`,
|
||||||
|
'a.test.ts': `
|
||||||
|
import { test, expect } from '@playwright/test';
|
||||||
|
import { foo } from './foo-pkg';
|
||||||
|
test('pass', async () => {
|
||||||
|
const bar: 'bar' = foo;
|
||||||
|
expect(bar).toBe('bar');
|
||||||
|
});
|
||||||
|
`,
|
||||||
|
};
|
||||||
|
|
||||||
|
const result = await runInlineTest(files);
|
||||||
|
expect(result.passed).toBe(1);
|
||||||
|
expect(result.exitCode).toBe(0);
|
||||||
|
|
||||||
|
const tscResult = await runTSC(files);
|
||||||
|
expect(tscResult.exitCode).toBe(0);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('should resolve index.js after path mapping in CJS', async ({ runInlineTest, runTSC }) => {
|
||||||
|
test.info().annotations.push({ type: 'issue', description: 'https://github.com/microsoft/playwright/issues/31811' });
|
||||||
|
|
||||||
|
const files = {
|
||||||
|
'@acme/lib/index.js': `
|
||||||
|
exports.greet = () => 2;
|
||||||
|
`,
|
||||||
|
'@acme/lib/index.d.ts': `
|
||||||
|
export const greet: () => number;
|
||||||
|
`,
|
||||||
|
'tests/hello.test.ts': `
|
||||||
|
import { greet } from '@acme/lib';
|
||||||
|
import { test, expect } from '@playwright/test';
|
||||||
|
test('hello', async ({}) => {
|
||||||
|
const foo: number = greet();
|
||||||
|
expect(foo).toBe(2);
|
||||||
|
});
|
||||||
|
`,
|
||||||
|
'tsconfig.json': `
|
||||||
|
{
|
||||||
|
"compilerOptions": {
|
||||||
|
"paths": {
|
||||||
|
"@acme/*": ["./@acme/*"]
|
||||||
|
},
|
||||||
|
"moduleResolution": "bundler",
|
||||||
|
"module": "preserve",
|
||||||
|
"noEmit": true,
|
||||||
|
"noImplicitAny": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`,
|
||||||
|
};
|
||||||
|
|
||||||
|
const result = await runInlineTest(files);
|
||||||
|
expect(result.exitCode).toBe(0);
|
||||||
|
expect(result.passed).toBe(1);
|
||||||
|
|
||||||
|
const tscResult = await runTSC(files);
|
||||||
|
expect(tscResult.exitCode).toBe(0);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('should resolve index.js after path mapping in ESM', async ({ runInlineTest, runTSC }) => {
|
||||||
|
test.info().annotations.push({ type: 'issue', description: 'https://github.com/microsoft/playwright/issues/31811' });
|
||||||
|
|
||||||
|
const files = {
|
||||||
|
'@acme/lib/index.js': `
|
||||||
|
export const greet = () => 2;
|
||||||
|
`,
|
||||||
|
'@acme/lib/index.d.ts': `
|
||||||
|
export const greet: () => number;
|
||||||
|
`,
|
||||||
|
'package.json': `
|
||||||
|
{ "type": "module" }
|
||||||
|
`,
|
||||||
|
'tests/hello.test.ts': `
|
||||||
|
import { greet } from '@acme/lib';
|
||||||
|
import { test, expect } from '@playwright/test';
|
||||||
|
test('hello', async ({}) => {
|
||||||
|
const foo: number = greet();
|
||||||
|
expect(foo).toBe(2);
|
||||||
|
});
|
||||||
|
`,
|
||||||
|
'tsconfig.json': `
|
||||||
|
{
|
||||||
|
"compilerOptions": {
|
||||||
|
"paths": {
|
||||||
|
"@acme/*": ["./@acme/*"]
|
||||||
|
},
|
||||||
|
"moduleResolution": "bundler",
|
||||||
|
"module": "preserve",
|
||||||
|
"noEmit": true,
|
||||||
|
"noImplicitAny": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`,
|
||||||
|
};
|
||||||
|
|
||||||
|
const result = await runInlineTest(files);
|
||||||
|
expect(result.exitCode).toBe(0);
|
||||||
|
expect(result.passed).toBe(1);
|
||||||
|
|
||||||
|
const tscResult = await runTSC(files);
|
||||||
|
expect(tscResult.exitCode).toBe(0);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('should respect package.json#main after path mapping in CJS', async ({ runInlineTest, runTSC }) => {
|
||||||
|
const files = {
|
||||||
|
'app/pkg/main.ts': `
|
||||||
|
export const foo = 42;
|
||||||
|
`,
|
||||||
|
'app/pkg/package.json': `
|
||||||
|
{ "main": "main.ts" }
|
||||||
|
`,
|
||||||
|
'package.json': `
|
||||||
|
{ "name": "example-project" }
|
||||||
|
`,
|
||||||
|
'playwright.config.ts': `
|
||||||
|
export default {};
|
||||||
|
`,
|
||||||
|
'tsconfig.json': `
|
||||||
|
{
|
||||||
|
"compilerOptions": {
|
||||||
|
"baseUrl": ".",
|
||||||
|
"paths": {
|
||||||
|
"app/*": ["app/*"]
|
||||||
|
},
|
||||||
|
"moduleResolution": "bundler",
|
||||||
|
"module": "preserve",
|
||||||
|
"noEmit": true,
|
||||||
|
"noImplicitAny": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`,
|
||||||
|
'example.spec.ts': `
|
||||||
|
import { foo } from 'app/pkg';
|
||||||
|
import { test, expect } from '@playwright/test';
|
||||||
|
test('test', ({}) => {
|
||||||
|
const bar: number = foo;
|
||||||
|
expect(bar).toBe(42);
|
||||||
|
});
|
||||||
|
`,
|
||||||
|
};
|
||||||
|
|
||||||
|
const result = await runInlineTest(files);
|
||||||
|
expect(result.exitCode).toBe(0);
|
||||||
|
expect(result.passed).toBe(1);
|
||||||
|
expect(result.output).not.toContain(`find module`);
|
||||||
|
|
||||||
|
const tscResult = await runTSC(files);
|
||||||
|
expect(tscResult.exitCode).toBe(0);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('should respect package.json#main after path mapping in ESM', async ({ runInlineTest, runTSC }) => {
|
||||||
|
const files = {
|
||||||
|
'app/pkg/main.ts': `
|
||||||
|
export const foo = 42;
|
||||||
|
`,
|
||||||
|
'app/pkg/package.json': `
|
||||||
|
{ "main": "main.ts", "type": "module" }
|
||||||
|
`,
|
||||||
|
'package.json': `
|
||||||
|
{ "name": "example-project", "type": "module" }
|
||||||
|
`,
|
||||||
|
'playwright.config.ts': `
|
||||||
|
export default {};
|
||||||
|
`,
|
||||||
|
'tsconfig.json': `
|
||||||
|
{
|
||||||
|
"compilerOptions": {
|
||||||
|
"baseUrl": ".",
|
||||||
|
"paths": {
|
||||||
|
"app/*": ["app/*"]
|
||||||
|
},
|
||||||
|
"moduleResolution": "bundler",
|
||||||
|
"module": "preserve",
|
||||||
|
"noEmit": true,
|
||||||
|
"noImplicitAny": true
|
||||||
|
},
|
||||||
|
}
|
||||||
|
`,
|
||||||
|
'example.spec.ts': `
|
||||||
|
import { foo } from 'app/pkg';
|
||||||
|
import { test, expect } from '@playwright/test';
|
||||||
|
test('test', ({}) => {
|
||||||
|
const bar: number = foo;
|
||||||
|
expect(bar).toBe(42);
|
||||||
|
});
|
||||||
|
`,
|
||||||
|
};
|
||||||
|
|
||||||
|
const result = await runInlineTest(files);
|
||||||
|
expect(result.exitCode).toBe(0);
|
||||||
|
expect(result.passed).toBe(1);
|
||||||
|
|
||||||
|
const tscResult = await runTSC(files);
|
||||||
|
expect(tscResult.exitCode).toBe(0);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('should respect package.json#exports without path mapping in CJS', async ({ runInlineTest, runTSC }) => {
|
||||||
|
const files = {
|
||||||
|
'node_modules/foo-pkg/package.json': `
|
||||||
|
{ "name": "foo-pkg", "exports": { ".": "./foo.js" } }
|
||||||
|
`,
|
||||||
|
'node_modules/foo-pkg/foo.js': `
|
||||||
|
exports.foo = 'bar';
|
||||||
|
`,
|
||||||
|
'node_modules/foo-pkg/foo.d.ts': `
|
||||||
|
export const foo: 'bar';
|
||||||
|
`,
|
||||||
|
'package.json': `
|
||||||
|
{ "name": "test-project" }
|
||||||
|
`,
|
||||||
|
'a.test.ts': `
|
||||||
|
import { test, expect } from '@playwright/test';
|
||||||
|
import { foo } from 'foo-pkg';
|
||||||
|
test('pass', async () => {
|
||||||
|
const bar: 'bar' = foo;
|
||||||
|
expect(bar).toBe('bar');
|
||||||
|
});
|
||||||
|
`,
|
||||||
|
'tsconfig.json': `
|
||||||
|
{
|
||||||
|
"compilerOptions": {
|
||||||
|
"moduleResolution": "bundler",
|
||||||
|
"module": "preserve",
|
||||||
|
"noEmit": true,
|
||||||
|
"noImplicitAny": true
|
||||||
|
},
|
||||||
|
}
|
||||||
|
`,
|
||||||
|
};
|
||||||
|
|
||||||
|
const result = await runInlineTest(files);
|
||||||
|
expect(result.passed).toBe(1);
|
||||||
|
expect(result.exitCode).toBe(0);
|
||||||
|
|
||||||
|
const tscResult = await runTSC(files);
|
||||||
|
expect(tscResult.exitCode).toBe(0);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('should respect package.json#exports without path mapping in ESM', async ({ runInlineTest, runTSC }) => {
|
||||||
|
const files = {
|
||||||
|
'node_modules/foo-pkg/package.json': `
|
||||||
|
{ "name": "foo-pkg", "type": "module", "exports": { "default": "./foo.js" } }
|
||||||
|
`,
|
||||||
|
'node_modules/foo-pkg/foo.js': `
|
||||||
|
export const foo = 'bar';
|
||||||
|
`,
|
||||||
|
'node_modules/foo-pkg/foo.d.ts': `
|
||||||
|
export const foo: 'bar';
|
||||||
|
`,
|
||||||
|
'package.json': `
|
||||||
|
{ "name": "test-project", "type": "module" }
|
||||||
|
`,
|
||||||
|
'a.test.ts': `
|
||||||
|
import { test, expect } from '@playwright/test';
|
||||||
|
import { foo } from 'foo-pkg';
|
||||||
|
test('pass', async () => {
|
||||||
|
const bar: 'bar' = foo;
|
||||||
|
expect(bar).toBe('bar');
|
||||||
|
});
|
||||||
|
`,
|
||||||
|
'tsconfig.json': `
|
||||||
|
{
|
||||||
|
"compilerOptions": {
|
||||||
|
"moduleResolution": "bundler",
|
||||||
|
"module": "preserve",
|
||||||
|
"noEmit": true,
|
||||||
|
"noImplicitAny": true
|
||||||
|
},
|
||||||
|
}
|
||||||
|
`,
|
||||||
|
};
|
||||||
|
|
||||||
|
const result = await runInlineTest(files);
|
||||||
|
expect(result.passed).toBe(1);
|
||||||
|
expect(result.exitCode).toBe(0);
|
||||||
|
|
||||||
|
const tscResult = await runTSC(files);
|
||||||
|
expect(tscResult.exitCode).toBe(0);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('should not respect package.json#exports after type mapping in CJS', async ({ runInlineTest, runTSC }) => {
|
||||||
|
const files = {
|
||||||
|
'app/pkg/main.ts': `
|
||||||
|
export const filename: 'main.ts' = 'main.ts';
|
||||||
|
`,
|
||||||
|
'app/pkg/index.js': `
|
||||||
|
export const filename = 'index.js';
|
||||||
|
`,
|
||||||
|
'app/pkg/index.d.ts': `
|
||||||
|
export const filename: 'index.js';
|
||||||
|
`,
|
||||||
|
'app/pkg/package.json': `
|
||||||
|
{ "exports": { ".": "./main.ts" } }
|
||||||
|
`,
|
||||||
|
'package.json': `
|
||||||
|
{ "name": "example-project" }
|
||||||
|
`,
|
||||||
|
'playwright.config.ts': `
|
||||||
|
export default {};
|
||||||
|
`,
|
||||||
|
'tsconfig.json': `
|
||||||
|
{
|
||||||
|
"compilerOptions": {
|
||||||
|
"baseUrl": ".",
|
||||||
|
"paths": {
|
||||||
|
"app/*": ["app/*"]
|
||||||
|
},
|
||||||
|
"moduleResolution": "bundler",
|
||||||
|
"module": "preserve",
|
||||||
|
"noEmit": true,
|
||||||
|
"noImplicitAny": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`,
|
||||||
|
'example.spec.ts': `
|
||||||
|
import { filename } from 'app/pkg';
|
||||||
|
import { test, expect } from '@playwright/test';
|
||||||
|
test('test', ({}) => {
|
||||||
|
const foo: 'index.js' = filename;
|
||||||
|
expect(foo).toBe('index.js');
|
||||||
|
});
|
||||||
|
`,
|
||||||
|
};
|
||||||
|
|
||||||
|
const result = await runInlineTest(files);
|
||||||
|
expect(result.exitCode).toBe(0);
|
||||||
|
expect(result.passed).toBe(1);
|
||||||
|
|
||||||
|
const tscResult = await runTSC(files);
|
||||||
|
expect(tscResult.exitCode).toBe(0);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('should not respect package.json#exports after type mapping in ESM', async ({ runInlineTest, runTSC }) => {
|
||||||
|
const files = {
|
||||||
|
'app/pkg/main.ts': `
|
||||||
|
export const filename: 'main.ts' = 'main.ts';
|
||||||
|
`,
|
||||||
|
'app/pkg/index.js': `
|
||||||
|
export const filename = 'index.js';
|
||||||
|
`,
|
||||||
|
'app/pkg/index.d.ts': `
|
||||||
|
export const filename: 'index.js';
|
||||||
|
`,
|
||||||
|
'app/pkg/package.json': `
|
||||||
|
{ "exports": { ".": "./main.ts" }, "type": "module" }
|
||||||
|
`,
|
||||||
|
'package.json': `
|
||||||
|
{ "name": "example-project", "type": "module" }
|
||||||
|
`,
|
||||||
|
'playwright.config.ts': `
|
||||||
|
export default {};
|
||||||
|
`,
|
||||||
|
'tsconfig.json': `
|
||||||
|
{
|
||||||
|
"compilerOptions": {
|
||||||
|
"baseUrl": ".",
|
||||||
|
"paths": {
|
||||||
|
"app/*": ["app/*"]
|
||||||
|
},
|
||||||
|
"moduleResolution": "bundler",
|
||||||
|
"module": "preserve",
|
||||||
|
"noEmit": true,
|
||||||
|
"noImplicitAny": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`,
|
||||||
|
'example.spec.ts': `
|
||||||
|
import { filename } from 'app/pkg';
|
||||||
|
import { test, expect } from '@playwright/test';
|
||||||
|
test('test', ({}) => {
|
||||||
|
const foo: 'index.js' = filename;
|
||||||
|
expect(foo).toBe('index.js');
|
||||||
|
});
|
||||||
|
`,
|
||||||
|
};
|
||||||
|
|
||||||
|
const result = await runInlineTest(files);
|
||||||
|
expect(result.exitCode).toBe(0);
|
||||||
|
expect(result.passed).toBe(1);
|
||||||
|
|
||||||
|
const tscResult = await runTSC(files);
|
||||||
|
expect(tscResult.exitCode).toBe(0);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue