Skip to content

Commit 39e3553

Browse files
committed
cherry-pick(#41399): fix(test): load require-reached files as commonjs in sync ESM loader
1 parent 4328122 commit 39e3553

2 files changed

Lines changed: 44 additions & 3 deletions

File tree

packages/playwright/src/transform/esmLoaderSync.ts

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -56,7 +56,7 @@ const kSupportedFormats = new Map([
5656
[undefined, undefined]
5757
]);
5858

59-
export function load(moduleUrl: string, context: { format?: string }, nextLoad: Function) {
59+
export function load(moduleUrl: string, context: { format?: string, conditions?: string[] | Set<string> }, nextLoad: Function) {
6060
// Bail out for wasm, json, etc.
6161
if (!kSupportedFormats.has(context.format))
6262
return nextLoad(moduleUrl, context);
@@ -71,8 +71,10 @@ export function load(moduleUrl: string, context: { format?: string }, nextLoad:
7171
if (!shouldTransform(filename))
7272
return nextLoad(moduleUrl, context);
7373

74-
// Output format is required, so we determine it manually when unknown.
75-
const format = kSupportedFormats.get(context.format) || (fileIsModule(filename) ? 'module' : 'commonjs');
74+
// A file reached via `require` is loaded as CommonJS. Files reached via `import` keep real ESM semantics.
75+
// This is a "bundler" behavior, where we go against Node.js semantics, to support extension-less esm imports, path mapping, etc.
76+
const isRequire = !new Set(context.conditions).has('import');
77+
const format = isRequire ? 'commonjs' : (kSupportedFormats.get(context.format) || (fileIsModule(filename) ? 'module' : 'commonjs'));
7678

7779
const code = fs.readFileSync(filename, 'utf-8');
7880
// Pass `moduleUrl` only for ESM. For CommonJS we omit it so that babel

tests/playwright-test/loader.spec.ts

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
*/
1616

1717
import { test, expect, playwrightCtConfigText } from './playwright-test-fixtures';
18+
import fs from 'fs';
1819
import path from 'path';
1920
import url from 'url';
2021

@@ -913,6 +914,44 @@ test('should resolve no-extension import of module into .ts file', async ({ runI
913914
expect(result.exitCode).toBe(0);
914915
});
915916

917+
test('should resolve extensionless .ts subpath import across a workspace symlink in ESM', {
918+
annotation: { type: 'issue', description: 'https://github.com/microsoft/playwright/issues/41371' },
919+
}, async ({ runInlineTest }, testInfo) => {
920+
const baseDir = testInfo.outputPath();
921+
const symlinkType = process.platform === 'win32' ? 'junction' : 'dir';
922+
const link = async (target: string, linkPath: string) => {
923+
await fs.promises.mkdir(path.dirname(linkPath), { recursive: true });
924+
await fs.promises.symlink(path.join(baseDir, target), linkPath, symlinkType);
925+
};
926+
// It is important to symlink so that our belongsToNodeModules() check does not trigger.
927+
await link('packages/shared', path.join(baseDir, 'packages/core/node_modules/@repro/shared'));
928+
await link('packages/core', path.join(baseDir, 'apps/e2e/node_modules/@repro/core'));
929+
930+
const result = await runInlineTest({
931+
// Root package.json is required for workspace:* dependencies to work.
932+
'package.json': JSON.stringify({ name: 'repro-root', private: true }),
933+
'packages/shared/package.json': JSON.stringify({ name: '@repro/shared', private: true, type: 'module' }),
934+
'packages/shared/lib/text.utils.ts': `
935+
export function greet(name: string) {
936+
return 'Hello, ' + name;
937+
}
938+
`,
939+
'packages/core/package.json': JSON.stringify({ name: '@repro/core', private: true, type: 'module', dependencies: { '@repro/shared': 'workspace:*' } }),
940+
'packages/core/lib/conversations.ts': `
941+
export { greet } from '@repro/shared/lib/text.utils';
942+
`,
943+
'apps/e2e/tests/basic.spec.ts': `
944+
import { test, expect } from '@playwright/test';
945+
import { greet } from '@repro/core/lib/conversations';
946+
test('greet returns expected string', () => {
947+
expect(greet('world')).toBe('Hello, world');
948+
});
949+
`,
950+
});
951+
expect(result.passed).toBe(1);
952+
expect(result.exitCode).toBe(0);
953+
});
954+
916955
test('should support node imports', async ({ runInlineTest }) => {
917956
const result = await runInlineTest({
918957
'playwright.config.ts': 'export default {}',

0 commit comments

Comments
 (0)