-
Notifications
You must be signed in to change notification settings - Fork 4k
[No QA] Use OXC to transform Reassure perf tests #100429
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,93 @@ | ||
| const crypto = require('crypto'); | ||
| const fs = require('fs'); | ||
| const path = require('path'); | ||
| const esbuild = require('esbuild'); | ||
| const {transformSync} = require('oxc-transform-react'); | ||
|
|
||
| const babelJest = require('babel-jest'); | ||
| const OXC_TRANSFORM_REACT_VERSION = require('oxc-transform-react/package.json').version; | ||
| const BaseReactCompilerConfig = require('./reactCompilerConfig'); | ||
|
|
||
| const babelTransformer = babelJest.createTransformer(); | ||
|
|
||
| const NODE_MODULES_RE = /[/\\]node_modules[/\\]/; | ||
| const TESTS_RE = /[/\\]tests[/\\]/; | ||
| const JEST_SETUP_RE = /[/\\]jest[/\\]/; | ||
| const MOCKS_RE = /[/\\]__mocks__[/\\]/; | ||
|
|
||
| const TRANSFORMER_SOURCE = fs.readFileSync(__filename); | ||
| const REACT_COMPILER_CONFIG_KEY = JSON.stringify(BaseReactCompilerConfig); | ||
|
|
||
| const REACT_COMPILER_OPTIONS = { | ||
| ...BaseReactCompilerConfig, | ||
| panicThreshold: 'none', | ||
| eslintSuppressionRules: [], | ||
| }; | ||
|
|
||
| function getLang(filename) { | ||
| const ext = path.extname(filename).slice(1); | ||
| if (ext === 'tsx') { | ||
| return 'tsx'; | ||
| } | ||
| if (ext === 'ts') { | ||
| return 'ts'; | ||
| } | ||
| return 'jsx'; | ||
| } | ||
|
|
||
| function shouldUseOxc(filename) { | ||
| return !NODE_MODULES_RE.test(filename) && !TESTS_RE.test(filename) && !JEST_SETUP_RE.test(filename) && !MOCKS_RE.test(filename); | ||
| } | ||
|
|
||
| function processWithOxc(sourceText, sourcePath) { | ||
| const oxcResult = transformSync(sourcePath, sourceText, { | ||
| lang: getLang(sourcePath), | ||
| sourcemap: true, | ||
| jsx: {runtime: 'automatic', development: true}, | ||
| reactCompiler: REACT_COMPILER_OPTIONS, | ||
| }); | ||
|
|
||
| if (oxcResult.fatal || !oxcResult.code) { | ||
| return null; | ||
| } | ||
|
|
||
| const cjs = esbuild.transformSync(oxcResult.code, { | ||
| loader: 'js', | ||
| format: 'cjs', | ||
| supported: {'dynamic-import': false}, | ||
| sourcefile: sourcePath, | ||
| sourcemap: true, | ||
|
Comment on lines
+58
to
+59
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
When transformed application code throws during a performance test, this source map treats Useful? React with 👍 / 👎.
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Duplicate from a previous review, chose not to address |
||
| }); | ||
|
|
||
| return {code: cjs.code, map: cjs.map}; | ||
| } | ||
|
|
||
| module.exports = { | ||
| canInstrument: false, | ||
| getCacheKey(sourceText, sourcePath, transformOptions) { | ||
| if (!shouldUseOxc(sourcePath)) { | ||
| return babelTransformer.getCacheKey(sourceText, sourcePath, transformOptions); | ||
| } | ||
|
|
||
| return crypto | ||
| .createHash('sha1') | ||
| .update(sourceText) | ||
| .update('\0', 'utf8') | ||
| .update(sourcePath) | ||
| .update(TRANSFORMER_SOURCE) | ||
| .update(REACT_COMPILER_CONFIG_KEY) | ||
| .update(esbuild.version) | ||
| .update(OXC_TRANSFORM_REACT_VERSION) | ||
| .digest('hex'); | ||
| }, | ||
| process(sourceText, sourcePath, transformOptions) { | ||
| if (shouldUseOxc(sourcePath)) { | ||
| const result = processWithOxc(sourceText, sourcePath); | ||
| if (result) { | ||
| return result; | ||
| } | ||
| } | ||
|
|
||
| return babelTransformer.process(sourceText, sourcePath, transformOptions); | ||
| }, | ||
| }; | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -18,7 +18,10 @@ module.exports = { | |
| `<rootDir>/?(*.)+(spec|test).${testFileExtension}`, | ||
| ], | ||
| transform: { | ||
| '^.+\\.[jt]sx?$': 'babel-jest', | ||
| // Reassure re-transforms ~7k files under `--max-opt=1` (V8 sparkplug only), which | ||
| // makes Babel ~half of each measure job. OXC + esbuild is native and stays fast | ||
| // without TurboFan. Test files stay on babel-jest so `jest.mock` is still hoisted. | ||
| '^.+\\.[jt]sx?$': isPerfTestRun ? '<rootDir>/config/babel/oxcJestTransformer.js' : 'babel-jest', | ||
|
roryabraham marked this conversation as resolved.
Comment on lines
+21
to
+24
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I'm new to this area of our codebase so just about anything you write here will be a bit hard for me to understand, but there's a lot in here which is specifically hard to understand and goes beyond the scope of what this line does. Maybe this version would explain the why behind this line without adding more questions?
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. going to merge and then follow-up with a better comment |
||
| '^.+\\.svg?$': 'jest-transformer-svg', | ||
| }, | ||
| transformIgnorePatterns: [ | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,84 @@ | ||
| import {describe, expect, it} from 'bun:test'; | ||
|
|
||
| import {createRequire} from 'node:module'; | ||
| import path from 'node:path'; | ||
|
|
||
| type TransformResult = {code: string}; | ||
|
|
||
| type OxcTransformer = { | ||
| process: (sourceText: string, sourcePath: string, transformOptions: unknown) => TransformResult; | ||
| }; | ||
|
|
||
| // eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion -- Jest transformers are CJS and tsconfig.bun.json does not type-check config/babel/ | ||
| const oxcTransformer = createRequire(import.meta.url)('../../config/babel/oxcJestTransformer') as OxcTransformer; | ||
|
|
||
| const transformOptions = { | ||
| config: {cwd: process.cwd(), rootDir: process.cwd(), cache: false}, | ||
| cacheFS: new Map(), | ||
| configString: '', | ||
| instrument: false, | ||
| supportsDynamicImport: false, | ||
| supportsExportNamespaceFrom: false, | ||
| supportsStaticESM: false, | ||
| supportsTopLevelAwait: false, | ||
| }; | ||
|
|
||
| describe('oxcTransformer', () => { | ||
| it('emits CommonJS for app TypeScript', () => { | ||
| const source = ` | ||
| export function add(a: number, b: number): number { | ||
| return a + b; | ||
| } | ||
| `; | ||
| const result = oxcTransformer.process(source, path.resolve('src/libs/math.ts'), transformOptions); | ||
| expect(result.code).toContain('module.exports'); | ||
| expect(result.code).toContain('add: () => add'); | ||
| expect(result.code).not.toMatch(/^export /m); | ||
| expect(result.code).not.toContain(': number'); | ||
| }); | ||
|
|
||
| it('runs React Compiler on app components', () => { | ||
| const source = ` | ||
| export function Hello({name}: {name: string}) { | ||
| return <div>{name.toUpperCase()}</div>; | ||
| } | ||
| `; | ||
| const result = oxcTransformer.process(source, path.resolve('src/components/Hello.tsx'), transformOptions); | ||
| expect(result.code).toMatch(/compiler-runtime|_c\(/); | ||
| expect(result.code).toContain('jsxDEV'); | ||
| }); | ||
|
|
||
| it('leaves test files on babel-jest so jest.mock is hoisted', () => { | ||
| const source = ` | ||
| import foo from './foo'; | ||
| jest.mock('./foo'); | ||
| export const x = 1; | ||
| `; | ||
| const result = oxcTransformer.process(source, path.resolve('tests/perf-test/Hello.perf-test.tsx'), transformOptions); | ||
| expect(result.code).toContain('_getJestObj().mock("./foo")'); | ||
| expect(result.code.indexOf('_getJestObj().mock')).toBeLessThan(result.code.indexOf('exports.x')); | ||
| }); | ||
|
|
||
| it('lowers dynamic import() so Jest still owns the module graph', () => { | ||
| const source = ` | ||
| export function loadLazy() { | ||
| return import('./LazyScreen'); | ||
| } | ||
| `; | ||
| const result = oxcTransformer.process(source, path.resolve('src/libs/loadLazy.ts'), transformOptions); | ||
| expect(result.code).not.toMatch(/\bimport\s*\(/); | ||
| expect(result.code).toMatch(/require\(['"]\.\/LazyScreen['"]\)/); | ||
| }); | ||
|
|
||
| it('falls back to babel-jest for Flow in node_modules', () => { | ||
| const source = ` | ||
| // @flow | ||
| export function add(a: number, b: number): number { | ||
| return a + b; | ||
| } | ||
| `; | ||
| const result = oxcTransformer.process(source, path.resolve('node_modules/react-native/Libraries/foo.js'), transformOptions); | ||
| expect(result.code).toBeTruthy(); | ||
| expect(result.code).not.toContain(': number'); | ||
| }); | ||
| }); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
For every
measureRenderssuite, this sends application components through OXC while the React Native production/Jest path still uses Babel. These compilers are documented as behaviorally different, and this route also skips Babel-only transforms such as the FullStory JSX annotations inbabel.config.js, so measured render counts and durations can diverge from the native code the benchmark is intended to protect; a regression caused by Babel's compiler output or annotation overhead can consequently pass this suite. Preserve the production transforms for measured React modules, or reproduce them before using OXC for the remaining lowering. CLAUDE.mdL57-L59Useful? React with 👍 / 👎.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
web uses oxc transformer and the Rust React Compiler. iOS/Android will soon: https://expensify.slack.com/archives/C05LX9D6E07/p1787741223437369