chore: extract a dev variation of the ct plugin (#29126)

This commit is contained in:
Pavel Feldman 2024-01-23 12:55:28 -08:00 committed by GitHub
parent 8e607d509f
commit 8898a537e0
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
13 changed files with 329 additions and 185 deletions

View File

@ -7,3 +7,4 @@
!types/**
!index.d.ts
!index.js
!plugin.js

View File

@ -21,7 +21,7 @@
},
"./cli": "./cli.js",
"./lib/mount": "./lib/mount.js",
"./lib/vitePlugin": "./lib/vitePlugin.js",
"./plugin": "./plugin.js",
"./types/component": {
"types": "./types/component.d.ts"
}

View File

@ -0,0 +1,17 @@
#!/usr/bin/env node
/**
* Copyright (c) Microsoft Corporation.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
module.exports = require('./lib/vitePlugin');

View File

@ -1,6 +1,9 @@
[vitePlugin.ts]
generated/indexSource.ts
[viteDevPlugin.ts]
generated/indexSource.ts
[mount.ts]
generated/serializers.ts
injected/**

View File

@ -0,0 +1,64 @@
/**
* Copyright (c) Microsoft Corporation.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import fs from 'fs';
import type { FullConfig } from 'playwright/test';
import type { PluginContext } from 'rollup';
import type { Plugin } from 'vite';
import type { TestRunnerPlugin } from '../../playwright/src/plugins';
import { source as injectedSource } from './generated/indexSource';
import type { ImportInfo } from './tsxTransform';
import type { ComponentRegistry } from './viteUtils';
import { createConfig, hasJSComponents, populateComponentsFromTests, resolveDirs, transformIndexFile } from './viteUtils';
export function createPlugin(
registerSourceFile: string,
frameworkPluginFactory?: () => Promise<Plugin>): TestRunnerPlugin {
let configDir: string;
let config: FullConfig;
return {
name: 'playwright-vite-plugin',
setup: async (configObject: FullConfig, configDirectory: string) => {
config = configObject;
configDir = configDirectory;
},
begin: async () => {
const registerSource = injectedSource + '\n' + await fs.promises.readFile(registerSourceFile, 'utf-8');
const componentRegistry: ComponentRegistry = new Map();
await populateComponentsFromTests(componentRegistry);
const dirs = resolveDirs(configDir, config);
const viteConfig = await createConfig(dirs, config, frameworkPluginFactory, hasJSComponents([...componentRegistry.values()]));
viteConfig.plugins.push(vitePlugin(registerSource, dirs.templateDir, componentRegistry));
const { createServer } = await import('vite');
const devServer = await createServer(viteConfig);
await devServer.listen();
const protocol = viteConfig.server.https ? 'https:' : 'http:';
process.env.PLAYWRIGHT_TEST_BASE_URL = `${protocol}//${viteConfig.server.host || 'localhost'}:${viteConfig.server.port}`;
},
};
}
function vitePlugin(registerSource: string, templateDir: string, importInfos: Map<string, ImportInfo>): Plugin {
return {
name: 'playwright:component-index',
async transform(this: PluginContext, content, id) {
return transformIndexFile(id, content, templateDir, registerSource, importInfos);
},
};
}

View File

@ -14,38 +14,29 @@
* limitations under the License.
*/
import type { Suite } from 'playwright/types/testReporter';
import type { PlaywrightTestConfig as BasePlaywrightTestConfig, FullConfig } from 'playwright/test';
import type http from 'http';
import type { InlineConfig, Plugin, ResolveFn, ResolvedConfig, UserConfig } from 'vite';
import type { TestRunnerPlugin } from '../../playwright/src/plugins';
import type { AddressInfo } from 'net';
import type { PluginContext } from 'rollup';
import { debug } from 'playwright-core/lib/utilsBundle';
import fs from 'fs';
import type http from 'http';
import type { AddressInfo } from 'net';
import path from 'path';
import { assert, calculateSha1, getPlaywrightVersion } from 'playwright-core/lib/utils';
import { debug } from 'playwright-core/lib/utilsBundle';
import { internalDependenciesForTestFile, setExternalDependencies } from 'playwright/lib/transform/compilationCache';
import { stoppable } from 'playwright/lib/utilsBundle';
import { assert, calculateSha1 } from 'playwright-core/lib/utils';
import { getPlaywrightVersion } from 'playwright-core/lib/utils';
import { getUserData, internalDependenciesForTestFile, setExternalDependencies } from 'playwright/lib/transform/compilationCache';
import type { FullConfig } from 'playwright/test';
import type { Suite } from 'playwright/types/testReporter';
import type { PluginContext } from 'rollup';
import type { Plugin, ResolveFn, ResolvedConfig } from 'vite';
import type { TestRunnerPlugin } from '../../playwright/src/plugins';
import { source as injectedSource } from './generated/indexSource';
import type { ImportInfo } from './tsxTransform';
import type { ComponentRegistry } from './viteUtils';
import { createConfig, hasJSComponents, populateComponentsFromTests, resolveDirs, transformIndexFile } from './viteUtils';
const log = debug('pw:vite');
let stoppableServer: any;
const playwrightVersion = getPlaywrightVersion();
type CtConfig = BasePlaywrightTestConfig['use'] & {
ctPort?: number;
ctTemplateDir?: string;
ctCacheDir?: string;
ctViteConfig?: InlineConfig | (() => Promise<InlineConfig>);
};
const importReactRE = /(^|\n|;)import\s+(\*\s+as\s+)?React(,|\s+)/;
const compiledReactRE = /(const|var)\s+React\s*=/;
export function createPlugin(
registerSourceFile: string,
frameworkPluginFactory?: () => Promise<Plugin>): TestRunnerPlugin {
@ -60,50 +51,8 @@ export function createPlugin(
},
begin: async (suite: Suite) => {
// We are going to have 3 config files:
// - the defaults that user config overrides (baseConfig)
// - the user config (userConfig)
// - frameworks overrides (frameworkOverrides);
const use = config.projects[0].use as CtConfig;
const baseURL = new URL(use.baseURL || 'http://localhost');
const relativeTemplateDir = use.ctTemplateDir || 'playwright';
// FIXME: use build plugin to determine html location to resolve this.
// TemplateDir must be relative, otherwise we can't move the final index.html into its target location post-build.
// This regressed in https://github.com/microsoft/playwright/pull/26526
const templateDir = path.join(configDir, relativeTemplateDir);
// Compose base config from the playwright config only.
const baseConfig: InlineConfig = {
root: templateDir,
configFile: false,
publicDir: path.join(configDir, 'public'),
define: {
__VUE_PROD_DEVTOOLS__: true,
},
css: {
devSourcemap: true,
},
build: {
outDir: use.ctCacheDir ? path.resolve(configDir, use.ctCacheDir) : path.resolve(templateDir, '.cache')
},
preview: {
https: baseURL.protocol.startsWith('https:') ? {} : undefined,
host: baseURL.hostname,
port: use.ctPort || Number(baseURL.port) || 3100
},
// Vite preview server will otherwise always return the index.html with 200.
appType: 'mpa',
};
// Vite 5 refuses to support CJS.
const { version: viteVersion, build, preview, mergeConfig } = await import('vite');
// Apply user config on top of the base config. This could have changed root and build.outDir.
const userConfig = typeof use.ctViteConfig === 'function' ? await use.ctViteConfig() : (use.ctViteConfig || {});
const baseAndUserConfig = mergeConfig(baseConfig, userConfig);
const buildInfoFile = path.join(baseAndUserConfig.build.outDir, 'metainfo.json');
const dirs = resolveDirs(configDir, config);
const buildInfoFile = path.join(dirs.outDir, 'metainfo.json');
let buildExists = false;
let buildInfo: BuildInfo;
@ -111,6 +60,8 @@ export function createPlugin(
const registerSource = injectedSource + '\n' + await fs.promises.readFile(registerSourceFile, 'utf-8');
const registerSourceHash = calculateSha1(registerSource);
const { version: viteVersion, build, preview, mergeConfig } = await import('vite');
try {
buildInfo = JSON.parse(await fs.promises.readFile(buildInfoFile, 'utf-8')) as BuildInfo;
assert(buildInfo.version === playwrightVersion);
@ -145,52 +96,38 @@ export function createPlugin(
// 4. Update component info.
buildInfo.components = [...componentRegistry.values()];
const frameworkOverrides: UserConfig = { plugins: [] };
// React heuristic. If we see a component in a file with .js extension,
// consider it a potential JSX-in-JS scenario and enable JSX loader for all
// .js files.
if (hasJSComponents(buildInfo.components)) {
log('jsx-in-js detected');
frameworkOverrides.esbuild = {
loader: 'jsx',
include: /.*\.jsx?$/,
exclude: [],
};
frameworkOverrides.optimizeDeps = {
esbuildOptions: {
loader: { '.js': 'jsx' },
}
};
}
// We assume that any non-empty plugin list includes `vite-react` or similar.
if (frameworkPluginFactory && !baseAndUserConfig.plugins?.length)
frameworkOverrides.plugins = [await frameworkPluginFactory()];
// But only add out own plugin when we actually build / transform.
const depsCollector = new Map<string, string[]>();
if (sourcesDirty)
frameworkOverrides.plugins!.push(vitePlugin(registerSource, templateDir, buildInfo, componentRegistry, depsCollector));
frameworkOverrides.build = {
target: 'esnext',
minify: false,
rollupOptions: {
treeshake: false,
input: {
index: path.join(templateDir, 'index.html')
},
},
sourcemap: true,
};
const finalConfig = mergeConfig(baseAndUserConfig, frameworkOverrides);
const jsxInJS = hasJSComponents(buildInfo.components);
const viteConfig = await createConfig(dirs, config, frameworkPluginFactory, jsxInJS);
if (sourcesDirty) {
// Only add out own plugin when we actually build / transform.
log('build');
await build(finalConfig);
const depsCollector = new Map<string, string[]>();
const buildConfig = mergeConfig(viteConfig, {
plugins: [vitePlugin(registerSource, dirs.templateDir, buildInfo, componentRegistry, depsCollector)]
});
await build(buildConfig);
buildInfo.deps = Object.fromEntries(depsCollector.entries());
// Update dependencies based on the vite build.
for (const projectSuite of suite.suites) {
for (const fileSuite of projectSuite.suites) {
// For every test file...
const testFile = fileSuite.location!.file;
const deps = new Set<string>();
// Collect its JS dependencies (helpers).
for (const file of [testFile, ...(internalDependenciesForTestFile(testFile) || [])]) {
// For each helper, get all the imported components.
for (const componentFile of componentsByImportingFile.get(file) || []) {
// For each component, get all the dependencies.
for (const d of depsCollector.get(componentFile) || [])
deps.add(d);
}
}
// Now we have test file => all components along with dependencies.
setExternalDependencies(testFile, [...deps]);
}
}
}
if (hasNewComponents || sourcesDirty) {
@ -198,32 +135,13 @@ export function createPlugin(
await fs.promises.writeFile(buildInfoFile, JSON.stringify(buildInfo, undefined, 2));
}
for (const projectSuite of suite.suites) {
for (const fileSuite of projectSuite.suites) {
// For every test file...
const testFile = fileSuite.location!.file;
const deps = new Set<string>();
// Collect its JS dependencies (helpers).
for (const file of [testFile, ...(internalDependenciesForTestFile(testFile) || [])]) {
// For each helper, get all the imported components.
for (const componentFile of componentsByImportingFile.get(file) || []) {
// For each component, get all the dependencies.
for (const d of depsCollector.get(componentFile) || [])
deps.add(d);
}
}
// Now we have test file => all components along with dependencies.
setExternalDependencies(testFile, [...deps]);
}
}
const previewServer = await preview(finalConfig);
const previewServer = await preview(viteConfig);
stoppableServer = stoppable(previewServer.httpServer as http.Server, 0);
const isAddressInfo = (x: any): x is AddressInfo => x?.address;
const address = previewServer.httpServer.address();
if (isAddressInfo(address)) {
const protocol = finalConfig.preview.https ? 'https:' : 'http:';
process.env.PLAYWRIGHT_TEST_BASE_URL = `${protocol}//${finalConfig.preview.host}:${address.port}`;
const protocol = viteConfig.preview.https ? 'https:' : 'http:';
process.env.PLAYWRIGHT_TEST_BASE_URL = `${protocol}//${viteConfig.preview.host}:${address.port}`;
}
},
@ -249,8 +167,6 @@ type BuildInfo = {
}
};
type ComponentRegistry = Map<string, ImportInfo>;
async function checkSources(buildInfo: BuildInfo): Promise<boolean> {
for (const [source, sourceInfo] of Object.entries(buildInfo.sources)) {
try {
@ -267,15 +183,6 @@ async function checkSources(buildInfo: BuildInfo): Promise<boolean> {
return false;
}
async function populateComponentsFromTests(componentRegistry: ComponentRegistry, componentsByImportingFile: Map<string, string[]>) {
const importInfos: Map<string, ImportInfo[]> = await getUserData('playwright-ct-core');
for (const [file, importList] of importInfos) {
for (const importInfo of importList)
componentRegistry.set(importInfo.id, importInfo);
componentsByImportingFile.set(file, importList.filter(i => !i.isModuleOrAlias).map(i => i.importPath));
}
}
async function checkNewComponents(buildInfo: BuildInfo, componentRegistry: ComponentRegistry): Promise<boolean> {
const newComponents = [...componentRegistry.keys()];
const oldComponents = new Map(buildInfo.components.map(c => [c.id, c]));
@ -314,35 +221,7 @@ function vitePlugin(registerSource: string, templateDir: string, buildInfo: Buil
// Silent if can't read the file.
}
}
// Vite React plugin will do this for .jsx files, but not .js files.
if (id.endsWith('.js') && content.includes('React.createElement') && !content.match(importReactRE) && !content.match(compiledReactRE)) {
const code = `import React from 'react';\n${content}`;
return { code, map: { mappings: '' } };
}
const indexTs = path.join(templateDir, 'index.ts');
const indexTsx = path.join(templateDir, 'index.tsx');
const indexJs = path.join(templateDir, 'index.js');
const indexJsx = path.join(templateDir, 'index.jsx');
const idResolved = path.resolve(id);
if (!idResolved.endsWith(indexTs) && !idResolved.endsWith(indexTsx) && !idResolved.endsWith(indexJs) && !idResolved.endsWith(indexJsx))
return;
const folder = path.dirname(id);
const lines = [content, ''];
lines.push(registerSource);
for (const value of importInfos.values()) {
const importPath = value.isModuleOrAlias ? value.importPath : './' + path.relative(folder, value.importPath).replace(/\\/g, '/');
lines.push(`const ${value.id} = () => import('${importPath}').then((mod) => mod.${value.remoteName || 'default'});`);
}
lines.push(`__pwRegistry.initialize({ ${[...importInfos.keys()].join(',\n ')} });`);
return {
code: lines.join('\n'),
map: { mappings: '' }
};
return transformIndexFile(id, content, templateDir, registerSource, importInfos);
},
async writeBundle(this: PluginContext) {
@ -371,12 +250,3 @@ function collectViteModuleDependencies(context: PluginContext, id: string, deps:
for (const importedId of module?.dynamicallyImportedIds || [])
collectViteModuleDependencies(context, importedId, deps);
}
function hasJSComponents(components: ImportInfo[]): boolean {
for (const component of components) {
const extname = path.extname(component.importPath);
if (extname === '.js' || !extname && fs.existsSync(component.importPath + '.js'))
return true;
}
return false;
}

View File

@ -0,0 +1,189 @@
/**
* Copyright (c) Microsoft Corporation.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import fs from 'fs';
import path from 'path';
import { debug } from 'playwright-core/lib/utilsBundle';
import { getUserData } from 'playwright/lib/transform/compilationCache';
import type { PlaywrightTestConfig as BasePlaywrightTestConfig, FullConfig } from 'playwright/test';
import type { InlineConfig, Plugin, TransformResult, UserConfig } from 'vite';
import type { ImportInfo } from './tsxTransform';
const log = debug('pw:vite');
export type CtConfig = BasePlaywrightTestConfig['use'] & {
ctPort?: number;
ctTemplateDir?: string;
ctCacheDir?: string;
ctViteConfig?: InlineConfig | (() => Promise<InlineConfig>);
};
export type ComponentRegistry = Map<string, ImportInfo>;
export type ComponentDirs = {
configDir: string;
outDir: string;
templateDir: string;
};
export function resolveDirs(configDir: string, config: FullConfig): ComponentDirs {
const use = config.projects[0].use as CtConfig;
// FIXME: use build plugin to determine html location to resolve this.
// TemplateDir must be relative, otherwise we can't move the final index.html into its target location post-build.
// This regressed in https://github.com/microsoft/playwright/pull/26526
const relativeTemplateDir = use.ctTemplateDir || 'playwright';
const templateDir = path.join(configDir, relativeTemplateDir);
const outDir = use.ctCacheDir ? path.resolve(configDir, use.ctCacheDir) : path.resolve(templateDir, '.cache');
return {
configDir,
outDir,
templateDir
};
}
export async function createConfig(dirs: ComponentDirs, config: FullConfig, frameworkPluginFactory: (() => Promise<Plugin>) | undefined, supportJsxInJs: boolean) {
// We are going to have 3 config files:
// - the defaults that user config overrides (baseConfig)
// - the user config (userConfig)
// - frameworks overrides (frameworkOverrides);
const use = config.projects[0].use as CtConfig;
const baseURL = new URL(use.baseURL || 'http://localhost');
// Compose base config from the playwright config only.
const baseConfig: InlineConfig = {
root: dirs.templateDir,
configFile: false,
publicDir: path.join(dirs.configDir, 'public'),
define: {
__VUE_PROD_DEVTOOLS__: true,
},
css: {
devSourcemap: true,
},
build: {
outDir: dirs.outDir
},
preview: {
https: baseURL.protocol.startsWith('https:') ? {} : undefined,
host: baseURL.hostname,
port: use.ctPort || Number(baseURL.port) || 3100
},
server: {
https: baseURL.protocol.startsWith('https:') ? {} : undefined,
host: baseURL.hostname,
port: use.ctPort || Number(baseURL.port) || 3100
},
// Vite preview server will otherwise always return the index.html with 200.
appType: 'mpa',
};
// Vite 5 refuses to support CJS.
const { mergeConfig } = await import('vite');
// Apply user config on top of the base config. This could have changed root and build.outDir.
const userConfig = typeof use.ctViteConfig === 'function' ? await use.ctViteConfig() : (use.ctViteConfig || {});
const baseAndUserConfig = mergeConfig(baseConfig, userConfig);
const frameworkOverrides: UserConfig = { plugins: [] };
// React heuristic. If we see a component in a file with .js extension,
// consider it a potential JSX-in-JS scenario and enable JSX loader for all
// .js files.
if (supportJsxInJs) {
log('jsx-in-js detected');
frameworkOverrides.esbuild = {
loader: 'jsx',
include: /.*\.jsx?$/,
exclude: [],
};
frameworkOverrides.optimizeDeps = {
esbuildOptions: {
loader: { '.js': 'jsx' },
}
};
}
frameworkOverrides.build = {
target: 'esnext',
minify: false,
rollupOptions: {
treeshake: false,
input: {
index: path.join(dirs.templateDir, 'index.html')
},
},
sourcemap: true,
};
// We assume that any non-empty plugin list includes `vite-react` or similar.
if (frameworkPluginFactory && !baseAndUserConfig.plugins?.length)
frameworkOverrides.plugins = [await frameworkPluginFactory()];
return mergeConfig(baseAndUserConfig, frameworkOverrides);
}
export async function populateComponentsFromTests(componentRegistry: ComponentRegistry, componentsByImportingFile?: Map<string, string[]>) {
const importInfos: Map<string, ImportInfo[]> = await getUserData('playwright-ct-core');
for (const [file, importList] of importInfos) {
for (const importInfo of importList)
componentRegistry.set(importInfo.id, importInfo);
if (componentsByImportingFile)
componentsByImportingFile.set(file, importList.filter(i => !i.isModuleOrAlias).map(i => i.importPath));
}
}
export function hasJSComponents(components: ImportInfo[]): boolean {
for (const component of components) {
const extname = path.extname(component.importPath);
if (extname === '.js' || !extname && fs.existsSync(component.importPath + '.js'))
return true;
}
return false;
}
const importReactRE = /(^|\n|;)import\s+(\*\s+as\s+)?React(,|\s+)/;
const compiledReactRE = /(const|var)\s+React\s*=/;
export function transformIndexFile(id: string, content: string, templateDir: string, registerSource: string, importInfos: Map<string, ImportInfo>): TransformResult | null {
// Vite React plugin will do this for .jsx files, but not .js files.
if (id.endsWith('.js') && content.includes('React.createElement') && !content.match(importReactRE) && !content.match(compiledReactRE)) {
const code = `import React from 'react';\n${content}`;
return { code, map: { mappings: '' } };
}
const indexTs = path.join(templateDir, 'index.ts');
const indexTsx = path.join(templateDir, 'index.tsx');
const indexJs = path.join(templateDir, 'index.js');
const indexJsx = path.join(templateDir, 'index.jsx');
const idResolved = path.resolve(id);
if (!idResolved.endsWith(indexTs) && !idResolved.endsWith(indexTsx) && !idResolved.endsWith(indexJs) && !idResolved.endsWith(indexJsx))
return null;
const folder = path.dirname(id);
const lines = [content, ''];
lines.push(registerSource);
for (const value of importInfos.values()) {
const importPath = value.isModuleOrAlias ? value.importPath : './' + path.relative(folder, value.importPath).replace(/\\/g, '/');
lines.push(`const ${value.id} = () => import('${importPath}').then((mod) => mod.${value.remoteName || 'default'});`);
}
lines.push(`__pwRegistry.initialize({ ${[...importInfos.keys()].join(',\n ')} });`);
return {
code: lines.join('\n'),
map: { mappings: '' }
};
}

View File

@ -19,7 +19,7 @@ const path = require('path');
const plugin = () => {
// Only fetch upon request to avoid resolution in workers.
const { createPlugin } = require('@playwright/experimental-ct-core/lib/vitePlugin');
const { createPlugin } = require('@playwright/experimental-ct-core/plugin');
return createPlugin(
path.join(__dirname, 'registerSource.mjs'),
() => import('@vitejs/plugin-react').then(plugin => plugin.default()));

View File

@ -19,7 +19,7 @@ const path = require('path');
const plugin = () => {
// Only fetch upon request to avoid resolution in workers.
const { createPlugin } = require('@playwright/experimental-ct-core/lib/vitePlugin');
const { createPlugin } = require('@playwright/experimental-ct-core/plugin');
return createPlugin(
path.join(__dirname, 'registerSource.mjs'),
() => import('@vitejs/plugin-react').then(plugin => plugin.default()));

View File

@ -19,7 +19,7 @@ const path = require('path');
const plugin = () => {
// Only fetch upon request to avoid resolution in workers.
const { createPlugin } = require('@playwright/experimental-ct-core/lib/vitePlugin');
const { createPlugin } = require('@playwright/experimental-ct-core/plugin');
return createPlugin(
path.join(__dirname, 'registerSource.mjs'),
() => import('vite-plugin-solid').then(plugin => plugin.default()));

View File

@ -19,7 +19,7 @@ const path = require('path');
const plugin = () => {
// Only fetch upon request to avoid resolution in workers.
const { createPlugin } = require('@playwright/experimental-ct-core/lib/vitePlugin');
const { createPlugin } = require('@playwright/experimental-ct-core/plugin');
return createPlugin(
path.join(__dirname, 'registerSource.mjs'),
() => import('@sveltejs/vite-plugin-svelte').then(plugin => plugin.svelte()));

View File

@ -19,7 +19,7 @@ const path = require('path');
const plugin = () => {
// Only fetch upon request to avoid resolution in workers.
const { createPlugin } = require('@playwright/experimental-ct-core/lib/vitePlugin');
const { createPlugin } = require('@playwright/experimental-ct-core/plugin');
return createPlugin(
path.join(__dirname, 'registerSource.mjs'),
() => import('@vitejs/plugin-vue').then(plugin => plugin.default()));

View File

@ -19,7 +19,7 @@ const path = require('path');
const plugin = () => {
// Only fetch upon request to avoid resolution in workers.
const { createPlugin } = require('@playwright/experimental-ct-core/lib/vitePlugin');
const { createPlugin } = require('@playwright/experimental-ct-core/plugin');
return createPlugin(
path.join(__dirname, 'registerSource.mjs'),
() => import('@vitejs/plugin-vue2').then(plugin => plugin.default()));