feat(ct): code split for better performance and isolation (#22494)

This commit is contained in:
Sander 2023-04-26 03:08:34 +02:00 committed by GitHub
parent d34c4e99f5
commit 44e56d2404
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
8 changed files with 283 additions and 117 deletions

View File

@ -322,9 +322,9 @@ function vitePlugin(registerSource: string, relativeTemplateDir: string, buildIn
for (const [alias, value] of componentRegistry) {
const importPath = value.isModuleOrAlias ? value.importPath : './' + path.relative(folder, value.importPath).replace(/\\/g, '/');
if (value.importedName)
lines.push(`import { ${value.importedName} as ${alias} } from '${importPath}';`);
lines.push(`const ${alias} = () => import('${importPath}').then((mod) => mod.${value.importedName});`);
else
lines.push(`import ${alias} from '${importPath}';`);
lines.push(`const ${alias} = () => import('${importPath}').then((mod) => mod.default);`);
}
lines.push(`pwRegister({ ${[...componentRegistry.keys()].join(',\n ')} });`);

View File

@ -21,48 +21,74 @@ import * as __pwReact from 'react';
import { createRoot as __pwCreateRoot } from 'react-dom/client';
/** @typedef {import('../playwright-ct-core/types/component').Component} Component */
/** @typedef {import('../playwright-ct-core/types/component').JsxComponent} JsxComponent */
/** @typedef {import('../playwright-ct-core/types/component').ObjectComponent} ObjectComponent */
/** @typedef {import('react').FunctionComponent} FrameworkComponent */
/** @type {Map<string, () => Promise<FrameworkComponent>>} */
const __pwLoaderRegistry = new Map();
/** @type {Map<string, FrameworkComponent>} */
const __pwRegistry = new Map();
/** @type {Map<Element, import('react-dom/client').Root>} */
const __pwRootRegistry = new Map();
/**
* @param {{[key: string]: FrameworkComponent}} components
* @param {{[key: string]: () => Promise<FrameworkComponent>}} components
*/
export function pwRegister(components) {
for (const [name, value] of Object.entries(components))
__pwRegistry.set(name, value);
__pwLoaderRegistry.set(name, value);
}
/**
* @param {Component} component
* @returns {component is JsxComponent | ObjectComponent}
*/
function isComponent(component) {
return !(typeof component !== 'object' || Array.isArray(component));
}
/**
* @param {Component} component
*/
async function __pwResolveComponent(component) {
if (!isComponent(component))
return
let componentFactory = __pwLoaderRegistry.get(component.type);
if (!componentFactory) {
// Lookup by shorthand.
for (const [name, value] of __pwLoaderRegistry) {
if (component.type.endsWith(`_${name}`)) {
componentFactory = value;
break;
}
}
}
if (!componentFactory && component.type[0].toUpperCase() === component.type[0])
throw new Error(`Unregistered component: ${component.type}. Following components are registered: ${[...__pwRegistry.keys()]}`);
if(componentFactory)
__pwRegistry.set(component.type, await componentFactory())
if ('children' in component)
await Promise.all(component.children.map(child => __pwResolveComponent(child)))
}
/**
* @param {Component} component
*/
function __pwRender(component) {
if (typeof component !== 'object' || Array.isArray(component))
if (!isComponent(component))
return component;
let componentFunc = __pwRegistry.get(component.type);
if (!componentFunc) {
// Lookup by shorthand.
for (const [name, value] of __pwRegistry) {
if (component.type.endsWith(`_${name}`)) {
componentFunc = value;
break;
}
}
}
if (!componentFunc && component.type[0].toUpperCase() === component.type[0])
throw new Error(`Unregistered component: ${component.type}. Following components are registered: ${[...__pwRegistry.keys()]}`);
const componentFuncOrString = componentFunc || component.type;
const componentFunc = __pwRegistry.get(component.type);
if (component.kind !== 'jsx')
throw new Error('Object mount notation is not supported');
return __pwReact.createElement(componentFuncOrString, component.props, ...component.children.map(child => {
return __pwReact.createElement(componentFunc || component.type, component.props, ...component.children.map(child => {
if (typeof child === 'string')
return child;
return __pwRender(child);
@ -74,6 +100,7 @@ function __pwRender(component) {
}
window.playwrightMount = async (component, rootElement, hooksConfig) => {
await __pwResolveComponent(component);
let App = () => __pwRender(component);
for (const hook of window.__pw_hooks_before_mount || []) {
const wrapper = await hook({ App, hooksConfig });
@ -105,6 +132,7 @@ window.playwrightUnmount = async rootElement => {
};
window.playwrightUpdate = async (rootElement, component) => {
await __pwResolveComponent(component);
const root = __pwRootRegistry.get(rootElement);
if (root === undefined)
throw new Error('Component was not mounted');

View File

@ -22,46 +22,72 @@ import __pwReact from 'react';
import __pwReactDOM from 'react-dom';
/** @typedef {import('../playwright-ct-core/types/component').Component} Component */
/** @typedef {import('../playwright-ct-core/types/component').JsxComponent} JsxComponent */
/** @typedef {import('../playwright-ct-core/types/component').ObjectComponent} ObjectComponent */
/** @typedef {import('react').FunctionComponent} FrameworkComponent */
/** @type {Map<string, FrameworkComponent>} */
/** @type {Map<string, () => Promise<FrameworkComponent>>} */
const __pwLoaderRegistry = new Map();
/** @type {Map<string, FrameworkComponent} */
const __pwRegistry = new Map();
/**
* @param {{[key: string]: FrameworkComponent}} components
* @param {{[key: string]: () => Promise<FrameworkComponent>}} components
*/
export function pwRegister(components) {
for (const [name, value] of Object.entries(components))
__pwRegistry.set(name, value);
__pwLoaderRegistry.set(name, value);
}
/**
* @param {Component} component
* @returns {component is JsxComponent | ObjectComponent}
*/
function isComponent(component) {
return !(typeof component !== 'object' || Array.isArray(component));
}
/**
* @param {Component} component
*/
async function __pwResolveComponent(component) {
if (!isComponent(component))
return
let componentFactory = __pwLoaderRegistry.get(component.type);
if (!componentFactory) {
// Lookup by shorthand.
for (const [name, value] of __pwLoaderRegistry) {
if (component.type.endsWith(`_${name}`)) {
componentFactory = value;
break;
}
}
}
if (!componentFactory && component.type[0].toUpperCase() === component.type[0])
throw new Error(`Unregistered component: ${component.type}. Following components are registered: ${[...__pwRegistry.keys()]}`);
if(componentFactory)
__pwRegistry.set(component.type, await componentFactory())
if ('children' in component)
await Promise.all(component.children.map(child => __pwResolveComponent(child)))
}
/**
* @param {Component} component
*/
function __pwRender(component) {
if (typeof component !== 'object' || Array.isArray(component))
if (!isComponent(component))
return component;
let componentFunc = __pwRegistry.get(component.type);
if (!componentFunc) {
// Lookup by shorthand.
for (const [name, value] of __pwRegistry) {
if (component.type.endsWith(`_${name}`)) {
componentFunc = value;
break;
}
}
}
if (!componentFunc && component.type[0].toUpperCase() === component.type[0])
throw new Error(`Unregistered component: ${component.type}. Following components are registered: ${[...__pwRegistry.keys()]}`);
const componentFuncOrString = componentFunc || component.type;
const componentFunc = __pwRegistry.get(component.type);
if (component.kind !== 'jsx')
throw new Error('Object mount notation is not supported');
return __pwReact.createElement(componentFuncOrString, component.props, ...component.children.map(child => {
return __pwReact.createElement(componentFunc || component.type, component.props, ...component.children.map(child => {
if (typeof child === 'string')
return child;
return __pwRender(child);
@ -73,6 +99,7 @@ function __pwRender(component) {
}
window.playwrightMount = async (component, rootElement, hooksConfig) => {
await __pwResolveComponent(component);
let App = () => __pwRender(component);
for (const hook of window.__pw_hooks_before_mount || []) {
const wrapper = await hook({ App, hooksConfig });
@ -92,5 +119,6 @@ window.playwrightUnmount = async rootElement => {
};
window.playwrightUpdate = async (rootElement, component) => {
await __pwResolveComponent(component);
__pwReactDOM.render(__pwRender(/** @type {Component} */(component)), rootElement);
};

View File

@ -21,17 +21,57 @@ import { render as __pwSolidRender, createComponent as __pwSolidCreateComponent
import __pwH from 'solid-js/h';
/** @typedef {import('../playwright-ct-core/types/component').Component} Component */
/** @typedef {import('../playwright-ct-core/types/component').JsxComponent} JsxComponent */
/** @typedef {import('../playwright-ct-core/types/component').ObjectComponent} ObjectComponent */
/** @typedef {() => import('solid-js').JSX.Element} FrameworkComponent */
/** @type {Map<string, () => Promise<FrameworkComponent>>} */
const __pwLoaderRegistry = new Map();
/** @type {Map<string, FrameworkComponent>} */
const __pwRegistry = new Map();
/**
* @param {{[key: string]: FrameworkComponent}} components
* @param {{[key: string]: () => Promise<FrameworkComponent>}} components
*/
export function pwRegister(components) {
for (const [name, value] of Object.entries(components))
__pwRegistry.set(name, value);
__pwLoaderRegistry.set(name, value);
}
/**
* @param {Component} component
* @returns {component is JsxComponent | ObjectComponent}
*/
function isComponent(component) {
return !(typeof component !== 'object' || Array.isArray(component));
}
/**
* @param {Component} component
*/
async function __pwResolveComponent(component) {
if (!isComponent(component))
return
let componentFactory = __pwLoaderRegistry.get(component.type);
if (!componentFactory) {
// Lookup by shorthand.
for (const [name, value] of __pwLoaderRegistry) {
if (component.type.endsWith(`_${name}`)) {
componentFactory = value;
break;
}
}
}
if (!componentFactory && component.type[0].toUpperCase() === component.type[0])
throw new Error(`Unregistered component: ${component.type}. Following components are registered: ${[...__pwRegistry.keys()]}`);
if(componentFactory)
__pwRegistry.set(component.type, await componentFactory())
if ('children' in component)
await Promise.all(component.children.map(child => __pwResolveComponent(child)))
}
function __pwCreateChild(child) {
@ -45,19 +85,7 @@ function __pwCreateComponent(component) {
if (typeof component !== 'object' || Array.isArray(component))
return component;
let Component = __pwRegistry.get(component.type);
if (!Component) {
// Lookup by shorthand.
for (const [name, value] of __pwRegistry) {
if (component.type.endsWith(`_${name}`)) {
Component = value;
break;
}
}
}
if (!Component && component.type[0].toUpperCase() === component.type[0])
throw new Error(`Unregistered component: ${component.type}. Following components are registered: ${[...__pwRegistry.keys()]}`);
const componentFunc = __pwRegistry.get(component.type);
if (component.kind !== 'jsx')
throw new Error('Object mount notation is not supported');
@ -69,15 +97,16 @@ function __pwCreateComponent(component) {
return children;
}, []);
if (!Component)
if (!componentFunc)
return __pwH(component.type, component.props, children);
return __pwSolidCreateComponent(Component, { ...component.props, children });
return __pwSolidCreateComponent(componentFunc, { ...component.props, children });
}
const __pwUnmountKey = Symbol('unmountKey');
window.playwrightMount = async (component, rootElement, hooksConfig) => {
await __pwResolveComponent(component);
let App = () => __pwCreateComponent(component);
for (const hook of window.__pw_hooks_before_mount || []) {
const wrapper = await hook({ App, hooksConfig });

View File

@ -21,18 +21,58 @@
import { detach as __pwDetach, insert as __pwInsert, noop as __pwNoop } from 'svelte/internal';
/** @typedef {import('../playwright-ct-core/types/component').Component} Component */
/** @typedef {import('../playwright-ct-core/types/component').JsxComponent} JsxComponent */
/** @typedef {import('../playwright-ct-core/types/component').ObjectComponent} ObjectComponent */
/** @typedef {any} FrameworkComponent */
/** @typedef {import('svelte').SvelteComponent} SvelteComponent */
/** @type {Map<string, () => Promise<FrameworkComponent>>} */
const __pwLoaderRegistry = new Map();
/** @type {Map<string, FrameworkComponent>} */
const __pwRegistry = new Map();
/**
* @param {{[key: string]: FrameworkComponent}} components
* @param {{[key: string]: () => Promise<FrameworkComponent>}} components
*/
export function pwRegister(components) {
for (const [name, value] of Object.entries(components))
__pwRegistry.set(name, value);
__pwLoaderRegistry.set(name, value);
}
/**
* @param {Component} component
* @returns {component is JsxComponent | ObjectComponent}
*/
function isComponent(component) {
return !(typeof component !== 'object' || Array.isArray(component));
}
/**
* @param {Component} component
*/
async function __pwResolveComponent(component) {
if (!isComponent(component))
return
let componentFactory = __pwLoaderRegistry.get(component.type);
if (!componentFactory) {
// Lookup by shorthand.
for (const [name, value] of __pwLoaderRegistry) {
if (component.type.endsWith(`_${name}_svelte`)) {
componentFactory = value;
break;
}
}
}
if (!componentFactory)
throw new Error(`Unregistered component: ${component.type}. Following components are registered: ${[...__pwRegistry.keys()]}`);
if(componentFactory)
__pwRegistry.set(component.type, await componentFactory())
if ('children' in component)
await Promise.all(component.children.map(child => __pwResolveComponent(child)))
}
/**
@ -69,20 +109,9 @@ function __pwCreateSlots(slots) {
const __pwSvelteComponentKey = Symbol('svelteComponent');
window.playwrightMount = async (component, rootElement, hooksConfig) => {
let componentCtor = __pwRegistry.get(component.type);
if (!componentCtor) {
// Lookup by shorthand.
for (const [name, value] of __pwRegistry) {
if (component.type.endsWith(`_${name}_svelte`)) {
componentCtor = value;
break;
}
}
}
if (!componentCtor)
throw new Error(`Unregistered component: ${component.type}. Following components are registered: ${[...__pwRegistry.keys()]}`);
await __pwResolveComponent(component);
const componentCtor = __pwRegistry.get(component.type);
if (component.kind !== 'object')
throw new Error('JSX mount notation is not supported');
@ -115,6 +144,7 @@ window.playwrightUnmount = async rootElement => {
};
window.playwrightUpdate = async (rootElement, component) => {
await __pwResolveComponent(component);
const svelteComponent = /** @type {SvelteComponent} */ (rootElement[__pwSvelteComponentKey]);
if (!svelteComponent)
throw new Error('Component was not mounted');

View File

@ -21,18 +21,58 @@ import { createApp as __pwCreateApp, setDevtoolsHook as __pwSetDevtoolsHook, h a
import { compile as __pwCompile } from '@vue/compiler-dom';
import * as __pwVue from 'vue';
/** @typedef {import('@playwright/test/types/experimentalComponent').Component} Component */
/** @typedef {import('../playwright-ct-core/types/component').Component} Component */
/** @typedef {import('../playwright-ct-core/types/component').JsxComponent} JsxComponent */
/** @typedef {import('../playwright-ct-core/types/component').ObjectComponent} ObjectComponent */
/** @typedef {import('vue').Component} FrameworkComponent */
/** @type {Map<string, () => Promise<FrameworkComponent>>} */
const __pwLoaderRegistry = new Map();
/** @type {Map<string, FrameworkComponent>} */
const __pwRegistry = new Map();
/**
* @param {{[key: string]: FrameworkComponent}} components
* @param {{[key: string]: () => Promise<FrameworkComponent>}} components
*/
export function pwRegister(components) {
for (const [name, value] of Object.entries(components))
__pwRegistry.set(name, value);
__pwLoaderRegistry.set(name, value);
}
/**
* @param {Component} component
* @returns {component is JsxComponent | ObjectComponent}
*/
function isComponent(component) {
return !(typeof component !== 'object' || Array.isArray(component));
}
/**
* @param {Component} component
*/
async function __pwResolveComponent(component) {
if (!isComponent(component))
return
let componentFactory = __pwLoaderRegistry.get(component.type);
if (!componentFactory) {
// Lookup by shorthand.
for (const [name, value] of __pwLoaderRegistry) {
if (component.type.endsWith(`_${name}_vue`)) {
componentFactory = value;
break;
}
}
}
if (!componentFactory && component.type[0].toUpperCase() === component.type[0])
throw new Error(`Unregistered component: ${component.type}. Following components are registered: ${[...__pwRegistry.keys()]}`);
if(componentFactory)
__pwRegistry.set(component.type, await componentFactory())
if ('children' in component)
await Promise.all(component.children.map(child => __pwResolveComponent(child)))
}
const __pwAllListeners = new Map();
@ -95,23 +135,7 @@ function __pwCreateComponent(component) {
if (typeof component === 'string')
return component;
/**
* @type {import('vue').Component | string | undefined}
*/
let componentFunc = __pwRegistry.get(component.type);
if (!componentFunc) {
// Lookup by shorthand.
for (const [name, value] of __pwRegistry) {
if (component.type.endsWith(`_${name}_vue`)) {
componentFunc = value;
break;
}
}
}
if (!componentFunc && component.type[0].toUpperCase() === component.type[0])
throw new Error(`Unregistered component: ${component.type}. Following components are registered: ${[...__pwRegistry.keys()]}`);
componentFunc = componentFunc || component.type;
const isVueComponent = componentFunc !== component.type;
@ -223,6 +247,7 @@ const __pwAppKey = Symbol('appKey');
const __pwWrapperKey = Symbol('wrapperKey');
window.playwrightMount = async (component, rootElement, hooksConfig) => {
await __pwResolveComponent(component);
const app = __pwCreateApp({
render: () => {
const wrapper = __pwCreateWrapper(component);
@ -248,7 +273,8 @@ window.playwrightUnmount = async rootElement => {
app.unmount();
};
window.playwrightUpdate = async (rootElement, options) => {
window.playwrightUpdate = async (rootElement, component) => {
await __pwResolveComponent(component);
const wrapper = rootElement[__pwWrapperKey];
if (!wrapper)
throw new Error('Component was not mounted');
@ -256,7 +282,7 @@ window.playwrightUpdate = async (rootElement, options) => {
if (!wrapper.component)
throw new Error('Updating a native HTML element is not supported');
const { slots, listeners, props } = __pwCreateComponent(options);
const { slots, listeners, props } = __pwCreateComponent(component);
wrapper.component.slots = __pwWrapFunctions(slots);
__pwAllListeners.set(wrapper, listeners);

View File

@ -21,17 +21,57 @@
import __pwVue, { h as __pwH } from 'vue';
/** @typedef {import('../playwright-ct-core/types/component').Component} Component */
/** @typedef {import('../playwright-ct-core/types/component').JsxComponent} JsxComponent */
/** @typedef {import('../playwright-ct-core/types/component').ObjectComponent} ObjectComponent */
/** @typedef {import('vue').Component} FrameworkComponent */
/** @type {Map<string, () => Promise<FrameworkComponent>>} */
const __pwLoaderRegistry = new Map();
/** @type {Map<string, FrameworkComponent>} */
const __pwRegistry = new Map();
/**
* @param {{[key: string]: FrameworkComponent}} components
* @param {{[key: string]: () => Promise<FrameworkComponent>}} components
*/
export function pwRegister(components) {
for (const [name, value] of Object.entries(components))
__pwRegistry.set(name, value);
__pwLoaderRegistry.set(name, value);
}
/**
* @param {Component} component
* @returns {component is JsxComponent | ObjectComponent}
*/
function isComponent(component) {
return !(typeof component !== 'object' || Array.isArray(component));
}
/**
* @param {Component} component
*/
async function __pwResolveComponent(component) {
if (!isComponent(component))
return
let componentFactory = __pwLoaderRegistry.get(component.type);
if (!componentFactory) {
// Lookup by shorthand.
for (const [name, value] of __pwLoaderRegistry) {
if (component.type.endsWith(`_${name}_vue`)) {
componentFactory = value;
break;
}
}
}
if (!componentFactory && component.type[0].toUpperCase() === component.type[0])
throw new Error(`Unregistered component: ${component.type}. Following components are registered: ${[...__pwRegistry.keys()]}`);
if(componentFactory)
__pwRegistry.set(component.type, await componentFactory())
if ('children' in component)
await Promise.all(component.children.map(child => __pwResolveComponent(child)))
}
/**
@ -59,25 +99,7 @@ function __pwComponentHasKeyInProps(Component, key) {
* @param {Component} component
*/
function __pwCreateComponent(component) {
/**
* @type {import('vue').Component | string | undefined}
*/
let componentFunc = __pwRegistry.get(component.type);
if (!componentFunc) {
// Lookup by shorthand.
for (const [name, value] of __pwRegistry) {
if (component.type.endsWith(`_${name}_vue`)) {
componentFunc = value;
break;
}
}
}
if (!componentFunc && component.type[0].toUpperCase() === component.type[0])
throw new Error(`Unregistered component: ${component.type}. Following components are registered: ${[...__pwRegistry.keys()]}`);
componentFunc = componentFunc || component.type;
const componentFunc = __pwRegistry.get(component.type) || component.type;
const isVueComponent = componentFunc !== component.type;
/**
@ -157,6 +179,7 @@ const instanceKey = Symbol('instanceKey');
const wrapperKey = Symbol('wrapperKey');
window.playwrightMount = async (component, rootElement, hooksConfig) => {
await __pwResolveComponent(component);
let options = {};
for (const hook of window.__pw_hooks_before_mount || [])
options = await hook({ hooksConfig, Vue: __pwVue });
@ -185,6 +208,7 @@ window.playwrightUnmount = async rootElement => {
};
window.playwrightUpdate = async (element, options) => {
await __pwResolveComponent(options);
const wrapper = /** @type {any} */(element)[wrapperKey];
if (!wrapper)
throw new Error('Component was not mounted');

View File

@ -17,3 +17,4 @@
import '../src/common.css';
import '../src/theme.ts';
import '../src/third_party/vscode/codicon.css';
import '../src/third_party/vscode/colors.css';