Skip to content

Commit

Permalink
cherry-pick(#28986): fix(ct): move import list into the compilation c…
Browse files Browse the repository at this point in the history
…ache data
  • Loading branch information
pavelfeldman committed Jan 18, 2024
1 parent 06518b2 commit cb6c64c
Show file tree
Hide file tree
Showing 9 changed files with 304 additions and 227 deletions.
87 changes: 40 additions & 47 deletions packages/playwright-ct-core/src/tsxTransform.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,10 +18,11 @@ import path from 'path';
import type { T, BabelAPI, PluginObj } from 'playwright/src/transform/babelBundle';
import { types, declare, traverse } from 'playwright/lib/transform/babelBundle';
import { resolveImportSpecifierExtension } from 'playwright/lib/util';
import { setTransformData } from 'playwright/lib/transform/transform';
const t: typeof T = types;

let componentNames: Set<string>;
let componentImports: Map<string, ImportInfo>;
let jsxComponentNames: Set<string>;
let importInfos: Map<string, ImportInfo>;

export default declare((api: BabelAPI) => {
api.assertVersion(7);
Expand All @@ -31,9 +32,8 @@ export default declare((api: BabelAPI) => {
visitor: {
Program: {
enter(path) {
const result = collectComponentUsages(path.node);
componentNames = result.names;
componentImports = new Map();
jsxComponentNames = collectJsxComponentUsages(path.node);
importInfos = new Map();
},
exit(path) {
let firstDeclaration: any;
Expand All @@ -47,13 +47,14 @@ export default declare((api: BabelAPI) => {
const insertionPath = lastImportDeclaration || firstDeclaration;
if (!insertionPath)
return;
for (const componentImport of [...componentImports.values()].reverse()) {

for (const [localName, componentImport] of [...importInfos.entries()].reverse()) {
insertionPath.insertAfter(
t.variableDeclaration(
'const',
[
t.variableDeclarator(
t.identifier(componentImport.localName),
t.identifier(localName),
t.objectExpression([
t.objectProperty(t.identifier('__pw_type'), t.stringLiteral('importRef')),
t.objectProperty(t.identifier('id'), t.stringLiteral(componentImport.id)),
Expand All @@ -63,6 +64,7 @@ export default declare((api: BabelAPI) => {
)
);
}
setTransformData('playwright-ct-core', [...importInfos.values()]);
}
},

Expand All @@ -71,19 +73,35 @@ export default declare((api: BabelAPI) => {
if (!t.isStringLiteral(importNode.source))
return;

let components = 0;
const ext = path.extname(importNode.source.value);

// Convert all non-JS imports into refs.
if (!allJsExtensions.has(ext)) {
for (const specifier of importNode.specifiers) {
if (t.isImportNamespaceSpecifier(specifier))
continue;
const { localName, info } = importInfo(importNode, specifier, this.filename!);
importInfos.set(localName, info);
}
p.skip();
p.remove();
return;
}

// Convert JS imports that are used as components in JSX expressions into refs.
let importCount = 0;
for (const specifier of importNode.specifiers) {
if (t.isImportNamespaceSpecifier(specifier))
continue;
const info = importInfo(importNode, specifier, this.filename!);
if (!componentNames.has(info.localName))
continue;
componentImports.set(info.localName, info);
++components;
const { localName, info } = importInfo(importNode, specifier, this.filename!);
if (jsxComponentNames.has(localName)) {
importInfos.set(localName, info);
++importCount;
}
}

// All the imports were components => delete.
if (components && components === importNode.specifiers.length) {
// All the imports were from JSX => delete.
if (importCount && importCount === importNode.specifiers.length) {
p.skip();
p.remove();
}
Expand All @@ -92,7 +110,7 @@ export default declare((api: BabelAPI) => {
MemberExpression(path) {
if (!t.isIdentifier(path.node.object))
return;
if (!componentImports.has(path.node.object.name))
if (!importInfos.has(path.node.object.name))
return;
if (!t.isIdentifier(path.node.property))
return;
Expand All @@ -108,56 +126,30 @@ export default declare((api: BabelAPI) => {
return result;
});

export function collectComponentUsages(node: T.Node) {
const importedLocalNames = new Set<string>();
function collectJsxComponentUsages(node: T.Node): Set<string> {
const names = new Set<string>();
traverse(node, {
enter: p => {

// First look at all the imports.
if (t.isImportDeclaration(p.node)) {
const importNode = p.node;
if (!t.isStringLiteral(importNode.source))
return;

for (const specifier of importNode.specifiers) {
if (t.isImportNamespaceSpecifier(specifier))
continue;
importedLocalNames.add(specifier.local.name);
}
}

// Treat JSX-everything as component usages.
if (t.isJSXElement(p.node)) {
if (t.isJSXIdentifier(p.node.openingElement.name))
names.add(p.node.openingElement.name.name);
if (t.isJSXMemberExpression(p.node.openingElement.name) && t.isJSXIdentifier(p.node.openingElement.name.object) && t.isJSXIdentifier(p.node.openingElement.name.property))
names.add(p.node.openingElement.name.object.name);
}

// Treat mount(identifier, ...) as component usage if it is in the importedLocalNames list.
if (t.isAwaitExpression(p.node) && t.isCallExpression(p.node.argument) && t.isIdentifier(p.node.argument.callee) && p.node.argument.callee.name === 'mount') {
const callExpression = p.node.argument;
const arg = callExpression.arguments[0];
if (!t.isIdentifier(arg) || !importedLocalNames.has(arg.name))
return;

names.add(arg.name);
}
}
});
return { names };
return names;
}

export type ImportInfo = {
id: string;
isModuleOrAlias: boolean;
importPath: string;
localName: string;
remoteName: string | undefined;
};

export function importInfo(importNode: T.ImportDeclaration, specifier: T.ImportSpecifier | T.ImportDefaultSpecifier, filename: string): ImportInfo {
export function importInfo(importNode: T.ImportDeclaration, specifier: T.ImportSpecifier | T.ImportDefaultSpecifier, filename: string): { localName: string, info: ImportInfo } {
const importSource = importNode.source.value;
const isModuleOrAlias = !importSource.startsWith('.');
const unresolvedImportPath = path.resolve(path.dirname(filename), importSource);
Expand All @@ -171,7 +163,6 @@ export function importInfo(importNode: T.ImportDeclaration, specifier: T.ImportS
id: idPrefix,
importPath,
isModuleOrAlias,
localName: specifier.local.name,
remoteName: undefined,
};

Expand All @@ -184,5 +175,7 @@ export function importInfo(importNode: T.ImportDeclaration, specifier: T.ImportS

if (result.remoteName)
result.id += '_' + result.remoteName;
return result;
return { localName: specifier.local.name, info: result };
}

const allJsExtensions = new Set(['.js', '.jsx', '.cjs', '.mjs', '.ts', '.tsx', '.cts', '.mts', '']);
Loading

0 comments on commit cb6c64c

Please sign in to comment.