chore: extract a dev variation of the ct plugin (#29126)
This commit is contained in:
parent
8e607d509f
commit
8898a537e0
|
|
@ -7,3 +7,4 @@
|
|||
!types/**
|
||||
!index.d.ts
|
||||
!index.js
|
||||
!plugin.js
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
}
|
||||
|
|
|
|||
17
packages/playwright-ct-core/plugin.js
Executable file
17
packages/playwright-ct-core/plugin.js
Executable 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');
|
||||
|
|
@ -1,6 +1,9 @@
|
|||
[vitePlugin.ts]
|
||||
generated/indexSource.ts
|
||||
|
||||
[viteDevPlugin.ts]
|
||||
generated/indexSource.ts
|
||||
|
||||
[mount.ts]
|
||||
generated/serializers.ts
|
||||
injected/**
|
||||
|
|
|
|||
64
packages/playwright-ct-core/src/viteDevPlugin.ts
Normal file
64
packages/playwright-ct-core/src/viteDevPlugin.ts
Normal 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);
|
||||
},
|
||||
};
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
|
|
|
|||
189
packages/playwright-ct-core/src/viteUtils.ts
Normal file
189
packages/playwright-ct-core/src/viteUtils.ts
Normal 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: '' }
|
||||
};
|
||||
}
|
||||
|
|
@ -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()));
|
||||
|
|
|
|||
|
|
@ -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()));
|
||||
|
|
|
|||
|
|
@ -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()));
|
||||
|
|
|
|||
|
|
@ -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()));
|
||||
|
|
|
|||
|
|
@ -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()));
|
||||
|
|
|
|||
|
|
@ -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()));
|
||||
|
|
|
|||
Loading…
Reference in a new issue