Skip to content

Commit

Permalink
fix(perftool): fix react 16,17 compatibility
Browse files Browse the repository at this point in the history
  • Loading branch information
akhdrv committed Apr 22, 2023
1 parent 2c94f45 commit 196230a
Show file tree
Hide file tree
Showing 8 changed files with 74 additions and 28 deletions.
3 changes: 1 addition & 2 deletions packages/perftool/scripts/start-compare.sh
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
#!/usr/bin/env sh

set -ex
set -e

case "$(uname -s)" in
Darwin*)
Expand All @@ -21,7 +21,6 @@ OPTS="--loader $TS_NODE_ESM_PATH --experimental-specifier-resolution=node"

if [ -z "$PERFTOOL_DEBUG" ]; then
OPTS="$OPTS --no-warnings"
set -x
fi

NODE_OPTIONS=$OPTS TS_NODE_PROJECT="$PROJECT_DIR/tsconfig.json" TS_NODE_FILES=true node "$PROJECT_DIR/src/compare/index.ts" "$@"
3 changes: 1 addition & 2 deletions packages/perftool/scripts/start.sh
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
#!/usr/bin/env sh

set -ex
set -e

case "$(uname -s)" in
Darwin*)
Expand All @@ -21,7 +21,6 @@ OPTS="--loader $TS_NODE_ESM_PATH --experimental-specifier-resolution=node"

if [ -z "$PERFTOOL_DEBUG" ]; then
OPTS="$OPTS --no-warnings"
set -x
fi

NODE_OPTIONS=$OPTS TS_NODE_PROJECT="$PROJECT_DIR/tsconfig.json" TS_NODE_FILES=true node "$PROJECT_DIR/src/index.ts" "$@"
10 changes: 9 additions & 1 deletion packages/perftool/src/client/index.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import type { Config } from '../config/common';
import { debug } from '../utils/logger';

import type { Task } from './measurement/types';
import { runTask, Subject } from './measurement/runner';
Expand All @@ -10,22 +11,29 @@ type CreatePerfToolClientParams<T extends Task<any, any>[]> = {
config: Config;
};

// TODO debug logging
export async function createPerfToolClient<T extends Task<any, any>[]>({
subjects,
tasks,
config,
}: CreatePerfToolClientParams<T>) {
debug('Perftool client created');
debug('Available subjects: ', subjects);
debug('Available tasks: ', tasks);
debug('Config: ', config);

const tests = await resolveTests({ tasks, subjects });
const resultPromises = [];

debug(`Running ${tests.length} tests`);
for (const { task, subject } of tests) {
const resultPromise = runTask({ task, subject, config });

resultPromises.push(resultPromise);
}

debug('Waiting for all tests to complete...');
const results = await Promise.all(resultPromises);

debug('All tests complete, calling window.finish');
await window.finish(results);
}
40 changes: 31 additions & 9 deletions packages/perftool/src/client/measurement/runner.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import type { Config } from '../../config/common';
import { getTaskConfig } from '../../config/task';
import BaseError from '../../utils/baseError';
import { defer } from '../../utils/deferred';
import { debug } from '../../utils/logger';

import { Task } from './types';

Expand Down Expand Up @@ -43,18 +44,39 @@ export function runTask<T extends Task<any, any>>({
const meta = { taskId: task.id, subjectId: subject.id };
const config = getTaskConfig(task, globalConfig);
const container = createContainer();
let isComplete = false;

debug('Running test\n', `TaskId: ${meta.taskId}\n`, `SubjectId: ${meta.subjectId}`);
debug('Task config: ', config);

return Promise.race([
task
.run({ Subject: subject.Component, config, container })
.then((result) => ({
...meta,
result,
}))
.catch((error: Error) => ({
...meta,
error: error.toString(),
})),
defer(globalConfig.taskWaitTimeout, () => ({ ...meta, error: new TimeoutError().toString() })),
.then((result) => {
debug(`Test ${meta.taskId}, ${meta.subjectId} complete. Result:`, result);

return {
...meta,
result,
};
})
.catch((error: Error) => {
debug(`Test ${meta.taskId}, ${meta.subjectId} failed. Error:`, error);

return {
...meta,
error: error.toString(),
};
})
.finally(() => {
isComplete = true;
}),
defer(globalConfig.taskWaitTimeout, () => {
if (!isComplete) {
debug(`Test ${meta.taskId}, ${meta.subjectId} timed out`);
}

return { ...meta, error: new TimeoutError().toString() };
}),
]);
}
6 changes: 5 additions & 1 deletion packages/perftool/src/clientEntry.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,21 +5,25 @@ import { getAllTasks } from './config/task';
import { createPerfToolClient } from './client';
import { Subject } from './client/measurement/runner';
import { subject as staticTaskSubject } from './stabilizers/staticTask';
import { setLogLevel } from './utils/logger';

// <IMPORT_MARK>

const config = ((v) => v)(
// <CONFIG_ARGS_MARK>
) as unknown as Config;

setLogLevel(config.logLevel);

const allTestSubjects: Subject[] = [
staticTaskSubject,
// <TEST_SUBJECT_MARK>
];

// TODO tasks in client config are serialized,
const allTasks = getAllTasks(config);

createPerfToolClient({
await createPerfToolClient({
config,
tasks: allTasks,
subjects: allTestSubjects,
Expand Down
9 changes: 8 additions & 1 deletion packages/perftool/src/config/webpack.ts
Original file line number Diff line number Diff line change
@@ -1,19 +1,26 @@
import HtmlWebpackPlugin from 'html-webpack-plugin';
import webpack, { Configuration as WebpackConfig } from 'webpack';
import { createRequire } from 'node:module';
import path from 'path';
import { fileURLToPath } from 'url';

import { debug } from '../utils/logger';

import { Config } from './common';

const require = createRequire(import.meta.url);
const dirname = path.dirname(fileURLToPath(import.meta.url));
const ownNodeModules = path.relative(process.cwd(), path.resolve(dirname, '../../node_modules'));

const defaultConfig: WebpackConfig = {
mode: 'production',
externals: ['fsevents'],
resolve: {
extensions: ['.ts', '.tsx', '.js', '.jsx', '.mjs'],
modules: ['node_modules', 'node_modules/@salutejs/perftool/node_modules'],
modules: ['node_modules', ownNodeModules],
fallback: {
'react-dom/client': false,
},
},
experiments: {
topLevelAwait: true,
Expand Down
18 changes: 10 additions & 8 deletions packages/perftool/src/utils/__spec__/react.test.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,6 @@
import { jest } from '@jest/globals';
import type React from 'react';

const originalRequire = global.require;

describe('utils/react/isReact18AndNewer', () => {
afterEach(() => {
jest.restoreAllMocks();
Expand All @@ -25,11 +23,14 @@ describe('utils/react/isReact18AndNewer', () => {
});

describe('utils/render', () => {
beforeEach(() => {
process.env.PERFTOOL_CLIENT_RUNTIME = '1';
});

afterEach(() => {
jest.restoreAllMocks();
jest.resetModules();
process.env.PERFTOOL_CLIENT_RUNTIME = undefined;
global.require = originalRequire;
});

it('should call ReactDOM.render if react version is under 18', async () => {
Expand All @@ -38,7 +39,7 @@ describe('utils/render', () => {

jest.unstable_mockModule('react', () => ({ version: '17.3.0' }));
jest.unstable_mockModule('react-dom', () => ({
default: { render: (renderMock = jest.fn((_, __, resolve: any) => (res = resolve)())) },
render: (renderMock = jest.fn((_, __, resolve: any) => (res = resolve)())),
}));

const { render } = await import('../react');
Expand All @@ -58,7 +59,6 @@ describe('utils/render', () => {
jest.unstable_mockModule('react-dom/client', () => ({
createRoot: (createRootMock = jest.fn(() => ({ render: (rootRenderMock = jest.fn()) }))),
}));
process.env.PERFTOOL_CLIENT_RUNTIME = '1';

const { render } = await import('../react');
const fakeElement = {} as React.ReactElement;
Expand All @@ -74,19 +74,22 @@ describe('utils/render', () => {
});

describe('utils/hydrate', () => {
beforeEach(() => {
process.env.PERFTOOL_CLIENT_RUNTIME = '1';
});

afterEach(() => {
jest.restoreAllMocks();
jest.resetModules();
process.env.PERFTOOL_CLIENT_RUNTIME = undefined;
global.require = originalRequire;
});

it('should call ReactDOM.hydrate if react version is under 18', async () => {
let hydrateMock = {} as jest.Mock;
let res = () => undefined;
jest.unstable_mockModule('react', () => ({ version: '17.3.0' }));
jest.unstable_mockModule('react-dom', () => ({
default: { hydrate: (hydrateMock = jest.fn((_, __, resolve: any) => (res = resolve)())) },
hydrate: (hydrateMock = jest.fn((_, __, resolve: any) => (res = resolve)())),
}));

const { hydrate } = await import('../react');
Expand All @@ -105,7 +108,6 @@ describe('utils/hydrate', () => {
jest.unstable_mockModule('react-dom/client', () => ({
hydrateRoot: (hydrateRootMock = jest.fn()),
}));
process.env.PERFTOOL_CLIENT_RUNTIME = '1';

const { hydrate } = await import('../react');
const fakeElement = {} as React.ReactElement;
Expand Down
13 changes: 9 additions & 4 deletions packages/perftool/src/utils/react.ts
Original file line number Diff line number Diff line change
@@ -1,15 +1,20 @@
import { version } from 'react';
import ReactDOM from 'react-dom';
import type * as ReactDOMType from 'react-dom';
import type * as ReactDOMClientType from 'react-dom/client';

export function isReact18AndNewer() {
return Number(version.split('.')[0]) >= 18;
}

// Import (React 18 specific) react-dom/client conditionally to avoid build-time errors when using older versions
// Import version specific react-dom modules conditionally to avoid build-time errors
let ReactDOMClient: typeof ReactDOMClientType = {} as any;
if (isReact18AndNewer() && process.env.PERFTOOL_CLIENT_RUNTIME) {
ReactDOMClient = await import('react-dom/client');
let ReactDOM: typeof ReactDOMType = {} as any;
if (process.env.PERFTOOL_CLIENT_RUNTIME) {
if (isReact18AndNewer()) {
ReactDOMClient = await import(`react-dom/client`);
} else {
ReactDOM = await import(`react-dom`);
}
}

export async function render(element: React.ReactElement, container: HTMLElement): Promise<void> {
Expand Down

0 comments on commit 196230a

Please sign in to comment.