Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 11 additions & 5 deletions packages/playwright/src/transform/compilationCache.ts
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,10 @@ const externalDependencies = new Map<string, Set<string>>();

const devSourceInfix = path.sep + 'playwright' + path.sep + 'packages' + path.sep;

function getCompilationCacheKey(filename: string, moduleUrl?: string) {
return `${moduleUrl ? 'esm' : 'cjs'}\0${filename}`;
}

export function installSourceMapSupport() {
Error.stackTraceLimit = 200;

Expand Down Expand Up @@ -106,10 +110,11 @@ function identitySourceMap(source: string) {

function _innerAddToCompilationCacheAndSerialize(filename: string, entry: MemoryCache) {
sourceMaps.set(entry.moduleUrl || filename, entry.sourceMapPath);
memoryCache.set(filename, entry);
const cacheKey = getCompilationCacheKey(filename, entry.moduleUrl);
memoryCache.set(cacheKey, entry);
return {
sourceMaps: [[entry.moduleUrl || filename, entry.sourceMapPath]],
memoryCache: [[filename, entry]],
memoryCache: [[cacheKey, entry]],
fileDependencies: [],
externalDependencies: [],
};
Expand All @@ -122,9 +127,9 @@ type CompilationCacheLookupResult = {
};

export function getFromCompilationCache(filename: string, contentHash: string, moduleUrl?: string): CompilationCacheLookupResult {
// First check the memory cache by filename, this cache will always work in the worker,
// First check the memory cache by filename+mode, this cache will always work in the worker,
// because we just compiled this file in the loader.
const cache = memoryCache.get(filename);
const cache = memoryCache.get(getCompilationCacheKey(filename, moduleUrl));
if (cache?.codePath) {
try {
return { cachedCode: fs.readFileSync(cache.codePath, 'utf-8') };
Expand Down Expand Up @@ -308,13 +313,14 @@ export function belongsToNodeModules(file: string) {
export async function getUserData(pluginName: string): Promise<Map<string, any>> {
const result = new Map<string, any>();
for (const [fileName, cache] of memoryCache) {
const normalizedFileName = fileName.split('\0', 2)[1] || fileName;
if (!cache.dataPath)
continue;
if (!fs.existsSync(cache.dataPath))
continue;
const data = JSON.parse(await fs.promises.readFile(cache.dataPath, 'utf8'));
if (data[pluginName])
result.set(fileName, data[pluginName]);
result.set(normalizedFileName, data[pluginName]);
}
return result;
}
60 changes: 60 additions & 0 deletions tests/playwright-test/loader.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -952,6 +952,66 @@ test('should resolve extensionless .ts subpath import across a workspace symlink
expect(result.exitCode).toBe(0);
});

test('should keep ESM and CJS loader compilation cache separate for workspace modules', {
annotation: { type: 'issue', description: 'https://github.com/microsoft/playwright/issues/41662' },
}, async ({ runInlineTest }, testInfo) => {
const baseDir = testInfo.outputPath();
const symlinkType = process.platform === 'win32' ? 'junction' : 'dir';
const link = async (target: string, linkPath: string) => {
await fs.promises.mkdir(path.dirname(linkPath), { recursive: true });
await fs.promises.symlink(path.join(baseDir, target), linkPath, symlinkType);
};

// Keep these packages out of a node_modules segment so the loader transform cache path is shared.
await link('packages/shared', path.join(baseDir, 'packages/core/node_modules/@repro/shared'));
await link('packages/core', path.join(baseDir, 'apps/e2e/node_modules/@repro/core'));

const result = await runInlineTest({
'package.json': JSON.stringify({ name: 'repro-root', private: true }),
'packages/shared/package.json': JSON.stringify({ name: '@repro/shared', private: true, type: 'module' }),
'packages/shared/lib/text.utils.ts': `
export function greet(name: string) {
return 'Hello, ' + name;
}
`,
'packages/core/package.json': JSON.stringify({
name: '@repro/core',
private: true,
type: 'module',
dependencies: { '@repro/shared': 'workspace:*' },
}),
'packages/core/lib/conversations.ts': `
export { greet } from '@repro/shared/lib/text.utils';
`,
'apps/e2e/global-setup.js': `
const { greet } = require('@repro/core/lib/conversations');
console.log('setup:' + greet('setup'));
`,
'apps/e2e/playwright.config.mts': `
import { defineConfig } from '@playwright/test';
import { greet } from '@repro/core/lib/conversations';

if (greet('config') !== 'Hello, config')
throw new Error('invalid shared module');

export default defineConfig({
testDir: './tests',
globalSetup: './global-setup.js',
});
`,
'apps/e2e/tests/basic.spec.ts': `
import { test, expect } from '@playwright/test';
import { greet } from '@repro/core/lib/conversations';
test('greet returns expected string', () => {
expect(greet('world')).toBe('Hello, world');
});
`,
}, { config: 'apps/e2e/playwright.config.mts' });
expect(result.exitCode).toBe(0);
expect(result.passed).toBe(1);
expect(result.output).toContain('setup:Hello, setup');
});

test('should support node imports', async ({ runInlineTest }) => {
const result = await runInlineTest({
'playwright.config.ts': 'export default {}',
Expand Down