diff --git a/compiler/apps/playground/components/Editor/ConfigEditor.tsx b/compiler/apps/playground/components/Editor/ConfigEditor.tsx index d922f27c97864..18f904d225f0e 100644 --- a/compiler/apps/playground/components/Editor/ConfigEditor.tsx +++ b/compiler/apps/playground/components/Editor/ConfigEditor.tsx @@ -6,7 +6,6 @@ */ import MonacoEditor, {loader, type Monaco} from '@monaco-editor/react'; -import {PluginOptions} from 'babel-plugin-react-compiler'; import type {editor} from 'monaco-editor'; import * as monaco from 'monaco-editor'; import React, { @@ -18,9 +17,8 @@ import React, { } from 'react'; import {Resizable} from 're-resizable'; import {useStore, useStoreDispatch} from '../StoreContext'; -import {monacoOptions} from './monacoOptions'; +import {monacoConfigOptions} from './monacoOptions'; import {IconChevron} from '../Icons/IconChevron'; -import prettyFormat from 'pretty-format'; import {CONFIG_PANEL_TRANSITION} from '../../lib/transitionTypes'; // @ts-expect-error - webpack asset/source loader handles .d.ts files as strings @@ -29,9 +27,9 @@ import compilerTypeDefs from 'babel-plugin-react-compiler/dist/index.d.ts'; loader.config({monaco}); export default function ConfigEditor({ - appliedOptions, + formattedAppliedConfig, }: { - appliedOptions: PluginOptions | null; + formattedAppliedConfig: string; }): React.ReactElement { const [isExpanded, setIsExpanded] = useState(false); @@ -49,7 +47,7 @@ export default function ConfigEditor({ setIsExpanded(false); }); }} - appliedOptions={appliedOptions} + formattedAppliedConfig={formattedAppliedConfig} />
void; - appliedOptions: PluginOptions | null; + onToggle: (expanded: boolean) => void; + formattedAppliedConfig: string; }): React.ReactElement { const store = useStore(); const dispatchStore = useStoreDispatch(); @@ -122,13 +120,6 @@ function ExpandedEditor({ }); }; - const formattedAppliedOptions = appliedOptions - ? prettyFormat(appliedOptions, { - printFunctionName: false, - printBasicPrototype: false, - }) - : 'Invalid configs'; - return ( @@ -158,7 +149,7 @@ function ExpandedEditor({ Config Overrides
-
+
@@ -186,23 +168,16 @@ function ExpandedEditor({ Applied Configs -
+
diff --git a/compiler/apps/playground/components/Editor/EditorImpl.tsx b/compiler/apps/playground/components/Editor/EditorImpl.tsx index 9f000f85564a2..5b39b91654e27 100644 --- a/compiler/apps/playground/components/Editor/EditorImpl.tsx +++ b/compiler/apps/playground/components/Editor/EditorImpl.tsx @@ -5,312 +5,17 @@ * LICENSE file in the root directory of this source tree. */ -import {parse as babelParse, ParseResult} from '@babel/parser'; -import * as HermesParser from 'hermes-parser'; -import * as t from '@babel/types'; -import BabelPluginReactCompiler, { - CompilerError, +import { CompilerErrorDetail, CompilerDiagnostic, - Effect, - ErrorCategory, - parseConfigPragmaForTests, - ValueKind, - type Hook, - PluginOptions, - CompilerPipelineValue, - parsePluginOptions, - printReactiveFunctionWithOutlined, - printFunctionWithOutlined, - type LoggerEvent, } from 'babel-plugin-react-compiler'; -import {useDeferredValue, useMemo} from 'react'; +import {useDeferredValue, useMemo, useState} from 'react'; import {useStore} from '../StoreContext'; import ConfigEditor from './ConfigEditor'; import Input from './Input'; -import { - CompilerOutput, - CompilerTransformOutput, - default as Output, - PrintedCompilerPipelineValue, -} from './Output'; -import {transformFromAstSync} from '@babel/core'; - -function parseInput( - input: string, - language: 'flow' | 'typescript', -): ParseResult { - // Extract the first line to quickly check for custom test directives - if (language === 'flow') { - return HermesParser.parse(input, { - babel: true, - flow: 'all', - sourceType: 'module', - enableExperimentalComponentSyntax: true, - }); - } else { - return babelParse(input, { - plugins: ['typescript', 'jsx'], - sourceType: 'module', - }) as ParseResult; - } -} - -function invokeCompiler( - source: string, - language: 'flow' | 'typescript', - options: PluginOptions, -): CompilerTransformOutput { - const ast = parseInput(source, language); - let result = transformFromAstSync(ast, source, { - filename: '_playgroundFile.js', - highlightCode: false, - retainLines: true, - plugins: [[BabelPluginReactCompiler, options]], - ast: true, - sourceType: 'module', - configFile: false, - sourceMaps: true, - babelrc: false, - }); - if (result?.ast == null || result?.code == null || result?.map == null) { - throw new Error('Expected successful compilation'); - } - return { - code: result.code, - sourceMaps: result.map, - language, - }; -} - -const COMMON_HOOKS: Array<[string, Hook]> = [ - [ - 'useFragment', - { - valueKind: ValueKind.Frozen, - effectKind: Effect.Freeze, - noAlias: true, - transitiveMixedData: true, - }, - ], - [ - 'usePaginationFragment', - { - valueKind: ValueKind.Frozen, - effectKind: Effect.Freeze, - noAlias: true, - transitiveMixedData: true, - }, - ], - [ - 'useRefetchableFragment', - { - valueKind: ValueKind.Frozen, - effectKind: Effect.Freeze, - noAlias: true, - transitiveMixedData: true, - }, - ], - [ - 'useLazyLoadQuery', - { - valueKind: ValueKind.Frozen, - effectKind: Effect.Freeze, - noAlias: true, - transitiveMixedData: true, - }, - ], - [ - 'usePreloadedQuery', - { - valueKind: ValueKind.Frozen, - effectKind: Effect.Freeze, - noAlias: true, - transitiveMixedData: true, - }, - ], -]; - -function parseOptions( - source: string, - mode: 'compiler' | 'linter', - configOverrides: string, -): PluginOptions { - // Extract the first line to quickly check for custom test directives - const pragma = source.substring(0, source.indexOf('\n')); - - const parsedPragmaOptions = parseConfigPragmaForTests(pragma, { - compilationMode: 'infer', - environment: - mode === 'linter' - ? { - // enabled in compiler - validateRefAccessDuringRender: false, - // enabled in linter - validateNoSetStateInRender: true, - validateNoSetStateInEffects: true, - validateNoJSXInTryStatements: true, - validateNoImpureFunctionsInRender: true, - validateStaticComponents: true, - validateNoFreezingKnownMutableFunctions: true, - validateNoVoidUseMemo: true, - } - : { - /* use defaults for compiler mode */ - }, - }); - - // Parse config overrides from config editor - let configOverrideOptions: any = {}; - const configMatch = configOverrides.match(/^\s*import.*?\n\n\((.*)\)/s); - if (configOverrides.trim()) { - if (configMatch && configMatch[1]) { - const configString = configMatch[1].replace(/satisfies.*$/, '').trim(); - configOverrideOptions = new Function(`return (${configString})`)(); - } else { - throw new Error('Invalid override format'); - } - } - - const opts: PluginOptions = parsePluginOptions({ - ...parsedPragmaOptions, - ...configOverrideOptions, - environment: { - ...parsedPragmaOptions.environment, - ...configOverrideOptions.environment, - customHooks: new Map([...COMMON_HOOKS]), - }, - }); - - return opts; -} - -function compile( - source: string, - mode: 'compiler' | 'linter', - configOverrides: string, -): [CompilerOutput, 'flow' | 'typescript', PluginOptions | null] { - const results = new Map>(); - const error = new CompilerError(); - const otherErrors: Array = []; - const upsert: (result: PrintedCompilerPipelineValue) => void = result => { - const entry = results.get(result.name); - if (Array.isArray(entry)) { - entry.push(result); - } else { - results.set(result.name, [result]); - } - }; - let language: 'flow' | 'typescript'; - if (source.match(/\@flow/)) { - language = 'flow'; - } else { - language = 'typescript'; - } - let transformOutput; - - let baseOpts: PluginOptions | null = null; - try { - baseOpts = parseOptions(source, mode, configOverrides); - } catch (err) { - error.details.push( - new CompilerErrorDetail({ - category: ErrorCategory.Config, - reason: `Unexpected failure when transforming configs! \n${err}`, - loc: null, - suggestions: null, - }), - ); - } - if (baseOpts) { - try { - const logIR = (result: CompilerPipelineValue): void => { - switch (result.kind) { - case 'ast': { - break; - } - case 'hir': { - upsert({ - kind: 'hir', - fnName: result.value.id, - name: result.name, - value: printFunctionWithOutlined(result.value), - }); - break; - } - case 'reactive': { - upsert({ - kind: 'reactive', - fnName: result.value.id, - name: result.name, - value: printReactiveFunctionWithOutlined(result.value), - }); - break; - } - case 'debug': { - upsert({ - kind: 'debug', - fnName: null, - name: result.name, - value: result.value, - }); - break; - } - default: { - const _: never = result; - throw new Error(`Unhandled result ${result}`); - } - } - }; - // Add logger options to the parsed options - const opts = { - ...baseOpts, - logger: { - debugLogIRs: logIR, - logEvent: (_filename: string | null, event: LoggerEvent): void => { - if (event.kind === 'CompileError') { - otherErrors.push(event.detail); - } - }, - }, - }; - transformOutput = invokeCompiler(source, language, opts); - } catch (err) { - /** - * error might be an invariant violation or other runtime error - * (i.e. object shape that is not CompilerError) - */ - if (err instanceof CompilerError && err.details.length > 0) { - error.merge(err); - } else { - /** - * Handle unexpected failures by logging (to get a stack trace) - * and reporting - */ - error.details.push( - new CompilerErrorDetail({ - category: ErrorCategory.Invariant, - reason: `Unexpected failure when transforming input! \n${err}`, - loc: null, - suggestions: null, - }), - ); - } - } - } - // Only include logger errors if there weren't other errors - if (!error.hasErrors() && otherErrors.length !== 0) { - otherErrors.forEach(e => error.details.push(e)); - } - if (error.hasErrors()) { - return [{kind: 'err', results, error}, language, baseOpts]; - } - return [ - {kind: 'ok', results, transformOutput, errors: error.details}, - language, - baseOpts, - ]; -} +import {CompilerOutput, default as Output} from './Output'; +import {compile} from '../../lib/compilation'; +import prettyFormat from 'pretty-format'; export default function Editor(): JSX.Element { const store = useStore(); @@ -323,6 +28,7 @@ export default function Editor(): JSX.Element { () => compile(deferredStore.source, 'linter', deferredStore.config), [deferredStore.source, deferredStore.config], ); + const [formattedAppliedConfig, setFormattedAppliedConfig] = useState(''); let mergedOutput: CompilerOutput; let errors: Array; @@ -336,11 +42,22 @@ export default function Editor(): JSX.Element { mergedOutput = compilerOutput; errors = compilerOutput.error.details; } + + if (appliedOptions) { + const formatted = prettyFormat(appliedOptions, { + printFunctionName: false, + printBasicPrototype: false, + }); + if (formatted !== formattedAppliedConfig) { + setFormattedAppliedConfig(formatted); + } + } + return ( <>
- +
diff --git a/compiler/apps/playground/components/Editor/monacoOptions.ts b/compiler/apps/playground/components/Editor/monacoOptions.ts index 7fed1b7875fce..d52c8bbedfa8d 100644 --- a/compiler/apps/playground/components/Editor/monacoOptions.ts +++ b/compiler/apps/playground/components/Editor/monacoOptions.ts @@ -32,3 +32,14 @@ export const monacoOptions: Partial = { tabSize: 2, }; + +export const monacoConfigOptions: Partial = { + ...monacoOptions, + lineNumbers: 'off', + renderLineHighlight: 'none', + overviewRulerBorder: false, + overviewRulerLanes: 0, + fontSize: 12, + scrollBeyondLastLine: false, + glyphMargin: false, +}; diff --git a/compiler/apps/playground/lib/compilation.ts b/compiler/apps/playground/lib/compilation.ts new file mode 100644 index 0000000000000..10bf0164c0e77 --- /dev/null +++ b/compiler/apps/playground/lib/compilation.ts @@ -0,0 +1,308 @@ +/** + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +import {parse as babelParse, ParseResult} from '@babel/parser'; +import * as HermesParser from 'hermes-parser'; +import * as t from '@babel/types'; +import BabelPluginReactCompiler, { + CompilerError, + CompilerErrorDetail, + CompilerDiagnostic, + Effect, + ErrorCategory, + parseConfigPragmaForTests, + ValueKind, + type Hook, + PluginOptions, + CompilerPipelineValue, + parsePluginOptions, + printReactiveFunctionWithOutlined, + printFunctionWithOutlined, + type LoggerEvent, +} from 'babel-plugin-react-compiler'; +import {transformFromAstSync} from '@babel/core'; +import type { + CompilerOutput, + CompilerTransformOutput, + PrintedCompilerPipelineValue, +} from '../components/Editor/Output'; + +function parseInput( + input: string, + language: 'flow' | 'typescript', +): ParseResult { + // Extract the first line to quickly check for custom test directives + if (language === 'flow') { + return HermesParser.parse(input, { + babel: true, + flow: 'all', + sourceType: 'module', + enableExperimentalComponentSyntax: true, + }); + } else { + return babelParse(input, { + plugins: ['typescript', 'jsx'], + sourceType: 'module', + }) as ParseResult; + } +} + +function invokeCompiler( + source: string, + language: 'flow' | 'typescript', + options: PluginOptions, +): CompilerTransformOutput { + const ast = parseInput(source, language); + let result = transformFromAstSync(ast, source, { + filename: '_playgroundFile.js', + highlightCode: false, + retainLines: true, + plugins: [[BabelPluginReactCompiler, options]], + ast: true, + sourceType: 'module', + configFile: false, + sourceMaps: true, + babelrc: false, + }); + if (result?.ast == null || result?.code == null || result?.map == null) { + throw new Error('Expected successful compilation'); + } + return { + code: result.code, + sourceMaps: result.map, + language, + }; +} + +const COMMON_HOOKS: Array<[string, Hook]> = [ + [ + 'useFragment', + { + valueKind: ValueKind.Frozen, + effectKind: Effect.Freeze, + noAlias: true, + transitiveMixedData: true, + }, + ], + [ + 'usePaginationFragment', + { + valueKind: ValueKind.Frozen, + effectKind: Effect.Freeze, + noAlias: true, + transitiveMixedData: true, + }, + ], + [ + 'useRefetchableFragment', + { + valueKind: ValueKind.Frozen, + effectKind: Effect.Freeze, + noAlias: true, + transitiveMixedData: true, + }, + ], + [ + 'useLazyLoadQuery', + { + valueKind: ValueKind.Frozen, + effectKind: Effect.Freeze, + noAlias: true, + transitiveMixedData: true, + }, + ], + [ + 'usePreloadedQuery', + { + valueKind: ValueKind.Frozen, + effectKind: Effect.Freeze, + noAlias: true, + transitiveMixedData: true, + }, + ], +]; + +function parseOptions( + source: string, + mode: 'compiler' | 'linter', + configOverrides: string, +): PluginOptions { + // Extract the first line to quickly check for custom test directives + const pragma = source.substring(0, source.indexOf('\n')); + + const parsedPragmaOptions = parseConfigPragmaForTests(pragma, { + compilationMode: 'infer', + environment: + mode === 'linter' + ? { + // enabled in compiler + validateRefAccessDuringRender: false, + // enabled in linter + validateNoSetStateInRender: true, + validateNoSetStateInEffects: true, + validateNoJSXInTryStatements: true, + validateNoImpureFunctionsInRender: true, + validateStaticComponents: true, + validateNoFreezingKnownMutableFunctions: true, + validateNoVoidUseMemo: true, + } + : { + /* use defaults for compiler mode */ + }, + }); + + // Parse config overrides from config editor + let configOverrideOptions: any = {}; + const configMatch = configOverrides.match(/^\s*import.*?\n\n\((.*)\)/s); + if (configOverrides.trim()) { + if (configMatch && configMatch[1]) { + const configString = configMatch[1].replace(/satisfies.*$/, '').trim(); + configOverrideOptions = new Function(`return (${configString})`)(); + } else { + throw new Error('Invalid override format'); + } + } + + const opts: PluginOptions = parsePluginOptions({ + ...parsedPragmaOptions, + ...configOverrideOptions, + environment: { + ...parsedPragmaOptions.environment, + ...configOverrideOptions.environment, + customHooks: new Map([...COMMON_HOOKS]), + }, + }); + + return opts; +} + +export function compile( + source: string, + mode: 'compiler' | 'linter', + configOverrides: string, +): [CompilerOutput, 'flow' | 'typescript', PluginOptions | null] { + const results = new Map>(); + const error = new CompilerError(); + const otherErrors: Array = []; + const upsert: (result: PrintedCompilerPipelineValue) => void = result => { + const entry = results.get(result.name); + if (Array.isArray(entry)) { + entry.push(result); + } else { + results.set(result.name, [result]); + } + }; + let language: 'flow' | 'typescript'; + if (source.match(/\@flow/)) { + language = 'flow'; + } else { + language = 'typescript'; + } + let transformOutput; + + let baseOpts: PluginOptions | null = null; + try { + baseOpts = parseOptions(source, mode, configOverrides); + } catch (err) { + error.details.push( + new CompilerErrorDetail({ + category: ErrorCategory.Config, + reason: `Unexpected failure when transforming configs! \n${err}`, + loc: null, + suggestions: null, + }), + ); + } + if (baseOpts) { + try { + const logIR = (result: CompilerPipelineValue): void => { + switch (result.kind) { + case 'ast': { + break; + } + case 'hir': { + upsert({ + kind: 'hir', + fnName: result.value.id, + name: result.name, + value: printFunctionWithOutlined(result.value), + }); + break; + } + case 'reactive': { + upsert({ + kind: 'reactive', + fnName: result.value.id, + name: result.name, + value: printReactiveFunctionWithOutlined(result.value), + }); + break; + } + case 'debug': { + upsert({ + kind: 'debug', + fnName: null, + name: result.name, + value: result.value, + }); + break; + } + default: { + const _: never = result; + throw new Error(`Unhandled result ${result}`); + } + } + }; + // Add logger options to the parsed options + const opts = { + ...baseOpts, + logger: { + debugLogIRs: logIR, + logEvent: (_filename: string | null, event: LoggerEvent): void => { + if (event.kind === 'CompileError') { + otherErrors.push(event.detail); + } + }, + }, + }; + transformOutput = invokeCompiler(source, language, opts); + } catch (err) { + /** + * error might be an invariant violation or other runtime error + * (i.e. object shape that is not CompilerError) + */ + if (err instanceof CompilerError && err.details.length > 0) { + error.merge(err); + } else { + /** + * Handle unexpected failures by logging (to get a stack trace) + * and reporting + */ + error.details.push( + new CompilerErrorDetail({ + category: ErrorCategory.Invariant, + reason: `Unexpected failure when transforming input! \n${err}`, + loc: null, + suggestions: null, + }), + ); + } + } + } + // Only include logger errors if there weren't other errors + if (!error.hasErrors() && otherErrors.length !== 0) { + otherErrors.forEach(e => error.details.push(e)); + } + if (error.hasErrors()) { + return [{kind: 'err', results, error}, language, baseOpts]; + } + return [ + {kind: 'ok', results, transformOutput, errors: error.details}, + language, + baseOpts, + ]; +} diff --git a/compiler/packages/babel-plugin-react-compiler/package.json b/compiler/packages/babel-plugin-react-compiler/package.json index 75cf3ba53cef3..8d3f1c8ae68e2 100644 --- a/compiler/packages/babel-plugin-react-compiler/package.json +++ b/compiler/packages/babel-plugin-react-compiler/package.json @@ -52,8 +52,8 @@ "react-dom": "0.0.0-experimental-4beb1fd8-20241118", "ts-jest": "^29.1.1", "ts-node": "^10.9.2", - "zod": "^3.22.4", - "zod-validation-error": "^2.1.0" + "zod": "^3.22.4 || ^4.0.0", + "zod-validation-error": "^3.0.3 || ^4.0.0" }, "resolutions": { "./**/@babel/parser": "7.7.4", diff --git a/compiler/packages/babel-plugin-react-compiler/src/HIR/Environment.ts b/compiler/packages/babel-plugin-react-compiler/src/HIR/Environment.ts index 9fa88680ca2eb..7889e13c2f864 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/HIR/Environment.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/HIR/Environment.ts @@ -159,7 +159,7 @@ export const EnvironmentConfigSchema = z.object({ * A function that, given the name of a module, can optionally return a description * of that module's type signature. */ - moduleTypeProvider: z.nullable(z.function().args(z.string())).default(null), + moduleTypeProvider: z.nullable(z.any()).default(null), /** * A list of functions which the application compiles as macros, where @@ -249,7 +249,7 @@ export const EnvironmentConfigSchema = z.object({ * Allows specifying a function that can populate HIR with type information from * Flow */ - flowTypeProvider: z.nullable(z.function().args(z.string())).default(null), + flowTypeProvider: z.nullable(z.any()).default(null), /** * Enables inference of optional dependency chains. Without this flag @@ -906,6 +906,12 @@ export class Environment { if (moduleTypeProvider == null) { return null; } + if (typeof moduleTypeProvider !== 'function') { + CompilerError.throwInvalidConfig({ + reason: `Expected a function for \`moduleTypeProvider\``, + loc, + }); + } const unparsedModuleConfig = moduleTypeProvider(moduleName); if (unparsedModuleConfig != null) { const parsedModuleConfig = TypeSchema.safeParse(unparsedModuleConfig); diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/envConfig-test.ts b/compiler/packages/babel-plugin-react-compiler/src/__tests__/envConfig-test.ts index f8a6330977fe8..933990b6bdd9c 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/envConfig-test.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/envConfig-test.ts @@ -20,7 +20,7 @@ describe('parseConfigPragma()', () => { validateHooksUsage: 1, } as any); }).toThrowErrorMatchingInlineSnapshot( - `"Error: Could not validate environment config. Update React Compiler config to fix the error. Validation error: Expected boolean, received number at "validateHooksUsage"."`, + `"Error: Could not validate environment config. Update React Compiler config to fix the error. Validation error: Invalid input: expected boolean, received number at "validateHooksUsage"."`, ); }); @@ -38,7 +38,7 @@ describe('parseConfigPragma()', () => { ], } as any); }).toThrowErrorMatchingInlineSnapshot( - `"Error: Could not validate environment config. Update React Compiler config to fix the error. Validation error: autodepsIndex must be > 0 at "inferEffectDependencies[0].autodepsIndex"."`, + `"Error: Could not validate environment config. Update React Compiler config to fix the error. Validation error: AutodepsIndex must be > 0 at "inferEffectDependencies[0].autodepsIndex"."`, ); }); diff --git a/compiler/packages/eslint-plugin-react-compiler/babel.config.js b/compiler/packages/eslint-plugin-react-compiler/babel.config.js index 8fc8dfbc24cee..9be68a85b7fe3 100644 --- a/compiler/packages/eslint-plugin-react-compiler/babel.config.js +++ b/compiler/packages/eslint-plugin-react-compiler/babel.config.js @@ -10,6 +10,5 @@ module.exports = { plugins: [ ['@babel/plugin-transform-private-property-in-object', {loose: true}], ['@babel/plugin-transform-class-properties', {loose: true}], - ['@babel/plugin-transform-private-methods', {loose: true}], ], }; diff --git a/compiler/packages/eslint-plugin-react-compiler/package.json b/compiler/packages/eslint-plugin-react-compiler/package.json index e5402611e2bbb..6c95bf495c66d 100644 --- a/compiler/packages/eslint-plugin-react-compiler/package.json +++ b/compiler/packages/eslint-plugin-react-compiler/package.json @@ -14,10 +14,9 @@ "dependencies": { "@babel/core": "^7.24.4", "@babel/parser": "^7.24.4", - "@babel/plugin-proposal-private-methods": "^7.18.6", "hermes-parser": "^0.25.1", - "zod": "^3.22.4", - "zod-validation-error": "^3.0.3" + "zod": "^3.22.4 || ^4.0.0", + "zod-validation-error": "^3.0.3 || ^4.0.0" }, "devDependencies": { "@babel/preset-env": "^7.22.4", diff --git a/compiler/packages/eslint-plugin-react-compiler/src/shared/RunReactCompiler.ts b/compiler/packages/eslint-plugin-react-compiler/src/shared/RunReactCompiler.ts index 53d2f31e0f418..419dc3841c7b1 100644 --- a/compiler/packages/eslint-plugin-react-compiler/src/shared/RunReactCompiler.ts +++ b/compiler/packages/eslint-plugin-react-compiler/src/shared/RunReactCompiler.ts @@ -8,8 +8,6 @@ import {transformFromAstSync} from '@babel/core'; import {parse as babelParse} from '@babel/parser'; import {File} from '@babel/types'; -// @ts-expect-error: no types available -import PluginProposalPrivateMethods from '@babel/plugin-proposal-private-methods'; import BabelPluginReactCompiler, { parsePluginOptions, validateEnvironmentConfig, @@ -145,10 +143,7 @@ function runReactCompilerImpl({ filename, highlightCode: false, retainLines: true, - plugins: [ - [PluginProposalPrivateMethods, {loose: true}], - [BabelPluginReactCompiler, options], - ], + plugins: [[BabelPluginReactCompiler, options]], sourceType: 'module', configFile: false, babelrc: false, diff --git a/compiler/packages/eslint-plugin-react-compiler/tsup.config.ts b/compiler/packages/eslint-plugin-react-compiler/tsup.config.ts index 3e3b1b13131a5..4b4f526439461 100644 --- a/compiler/packages/eslint-plugin-react-compiler/tsup.config.ts +++ b/compiler/packages/eslint-plugin-react-compiler/tsup.config.ts @@ -10,13 +10,7 @@ import {defineConfig} from 'tsup'; export default defineConfig({ entry: ['./src/index.ts'], outDir: './dist', - external: [ - '@babel/core', - '@babel/plugin-proposal-private-methods', - 'hermes-parser', - 'zod', - 'zod-validation-error', - ], + external: ['@babel/core', 'hermes-parser', 'zod', 'zod-validation-error'], splitting: false, sourcemap: false, dts: false, diff --git a/compiler/packages/react-compiler-healthcheck/package.json b/compiler/packages/react-compiler-healthcheck/package.json index 2f6d625eb837d..61825b73d8320 100644 --- a/compiler/packages/react-compiler-healthcheck/package.json +++ b/compiler/packages/react-compiler-healthcheck/package.json @@ -17,8 +17,8 @@ "fast-glob": "^3.3.2", "ora": "5.4.1", "yargs": "^17.7.2", - "zod": "^3.22.4", - "zod-validation-error": "^3.0.3" + "zod": "^3.22.4 || ^4.0.0", + "zod-validation-error": "^3.0.3 || ^4.0.0" }, "devDependencies": {}, "engines": { diff --git a/compiler/packages/react-mcp-server/package.json b/compiler/packages/react-mcp-server/package.json index 191ff4f9e199c..4d744c1d667fb 100644 --- a/compiler/packages/react-mcp-server/package.json +++ b/compiler/packages/react-mcp-server/package.json @@ -24,7 +24,7 @@ "html-to-text": "^9.0.5", "prettier": "^3.3.3", "puppeteer": "^24.7.2", - "zod": "^3.23.8" + "zod": "^3.22.4 || ^4.0.0" }, "devDependencies": { "@types/html-to-text": "^9.0.4", diff --git a/compiler/yarn.lock b/compiler/yarn.lock index 696261cbf53af..daafc705fdcda 100644 --- a/compiler/yarn.lock +++ b/compiler/yarn.lock @@ -326,7 +326,7 @@ lru-cache "^5.1.1" semver "^6.3.1" -"@babel/helper-create-class-features-plugin@^7.18.6", "@babel/helper-create-class-features-plugin@^7.25.9", "@babel/helper-create-class-features-plugin@^7.27.0": +"@babel/helper-create-class-features-plugin@^7.25.9", "@babel/helper-create-class-features-plugin@^7.27.0": version "7.27.0" resolved "https://registry.npmjs.org/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.27.0.tgz" integrity sha512-vSGCvMecvFCd/BdpGlhpXYNhhC4ccxyvQWpbGL4CWbvfEoLFWUZuSuf7s9Aw70flgQF+6vptvgK2IfOnKlRmBg== @@ -706,14 +706,6 @@ "@babel/helper-plugin-utils" "^7.25.9" "@babel/traverse" "^7.25.9" -"@babel/plugin-proposal-private-methods@^7.18.6": - version "7.18.6" - resolved "https://registry.npmjs.org/@babel/plugin-proposal-private-methods/-/plugin-proposal-private-methods-7.18.6.tgz" - integrity sha512-nutsvktDItsNn4rpGItSNV2sz1XwS+nfU0Rg8aCx3W3NOKVzdMjJRu0O5OkgDp3ZGICSTbgRpxZoWsxoKRvbeA== - dependencies: - "@babel/helper-create-class-features-plugin" "^7.18.6" - "@babel/helper-plugin-utils" "^7.18.6" - "@babel/plugin-proposal-private-property-in-object@7.21.0-placeholder-for-preset-env.2": version "7.21.0-placeholder-for-preset-env.2" resolved "https://registry.npmjs.org/@babel/plugin-proposal-private-property-in-object/-/plugin-proposal-private-property-in-object-7.21.0-placeholder-for-preset-env.2.tgz" @@ -10494,16 +10486,7 @@ string-length@^4.0.1: char-regex "^1.0.2" strip-ansi "^6.0.0" -"string-width-cjs@npm:string-width@^4.2.0": - version "4.2.3" - resolved "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz" - integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g== - dependencies: - emoji-regex "^8.0.0" - is-fullwidth-code-point "^3.0.0" - strip-ansi "^6.0.1" - -string-width@^4.1.0, string-width@^4.2.0, string-width@^4.2.3: +"string-width-cjs@npm:string-width@^4.2.0", string-width@^4.1.0, string-width@^4.2.0, string-width@^4.2.3: version "4.2.3" resolved "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz" integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g== @@ -10576,14 +10559,7 @@ string_decoder@~1.1.1: dependencies: safe-buffer "~5.1.0" -"strip-ansi-cjs@npm:strip-ansi@^6.0.1": - version "6.0.1" - resolved "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz" - integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A== - dependencies: - ansi-regex "^5.0.1" - -strip-ansi@^6.0.0, strip-ansi@^6.0.1: +"strip-ansi-cjs@npm:strip-ansi@^6.0.1", strip-ansi@^6.0.0, strip-ansi@^6.0.1: version "6.0.1" resolved "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz" integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A== @@ -11360,7 +11336,7 @@ workerpool@^6.5.1: resolved "https://registry.npmjs.org/workerpool/-/workerpool-6.5.1.tgz" integrity sha512-Fs4dNYcsdpYSAfVxhnl1L5zTksjvOJxtC5hzMNl+1t9B8hTJTdKDyZ5ju7ztgPy+ft9tBFXoOlDNiOT9WUXZlA== -"wrap-ansi-cjs@npm:wrap-ansi@^7.0.0": +"wrap-ansi-cjs@npm:wrap-ansi@^7.0.0", wrap-ansi@^7.0.0: version "7.0.0" resolved "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz" integrity sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q== @@ -11378,15 +11354,6 @@ wrap-ansi@^6.2.0: string-width "^4.1.0" strip-ansi "^6.0.0" -wrap-ansi@^7.0.0: - version "7.0.0" - resolved "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz" - integrity sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q== - dependencies: - ansi-styles "^4.0.0" - string-width "^4.1.0" - strip-ansi "^6.0.0" - wrap-ansi@^8.1.0: version "8.1.0" resolved "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-8.1.0.tgz" @@ -11538,17 +11505,17 @@ zod-to-json-schema@^3.24.1: resolved "https://registry.npmjs.org/zod-to-json-schema/-/zod-to-json-schema-3.24.5.tgz" integrity sha512-/AuWwMP+YqiPbsJx5D6TfgRTc4kTLjsh5SOcd4bLsfUg2RcEXrFMJl1DGgdHy2aCfsIA/cr/1JM0xcB2GZji8g== -zod-validation-error@^2.1.0: - version "2.1.0" - resolved "https://registry.npmjs.org/zod-validation-error/-/zod-validation-error-2.1.0.tgz" - integrity sha512-VJh93e2wb4c3tWtGgTa0OF/dTt/zoPCPzXq4V11ZjxmEAFaPi/Zss1xIZdEB5RD8GD00U0/iVXgqkF77RV7pdQ== +"zod-validation-error@^3.0.3 || ^4.0.0": + version "4.0.2" + resolved "https://registry.yarnpkg.com/zod-validation-error/-/zod-validation-error-4.0.2.tgz#bc605eba49ce0fcd598c127fee1c236be3f22918" + integrity sha512-Q6/nZLe6jxuU80qb/4uJ4t5v2VEZ44lzQjPDhYJNztRQ4wyWc6VF3D3Kb/fAuPetZQnhS3hnajCf9CsWesghLQ== -zod-validation-error@^3.0.3: - version "3.0.3" - resolved "https://registry.npmjs.org/zod-validation-error/-/zod-validation-error-3.0.3.tgz" - integrity sha512-cETTrcMq3Ze58vhdR0zD37uJm/694I6mAxcf/ei5bl89cC++fBNxrC2z8lkFze/8hVMPwrbtrwXHR2LB50fpHw== +"zod@^3.22.4 || ^4.0.0": + version "4.1.11" + resolved "https://registry.yarnpkg.com/zod/-/zod-4.1.11.tgz#4aab62f76cfd45e6c6166519ba31b2ea019f75f5" + integrity sha512-WPsqwxITS2tzx1bzhIKsEs19ABD5vmCVa4xBo2tq/SrV4RNZtfws1EnCWQXM6yh8bD08a1idvkB5MZSBiZsjwg== -zod@^3.22.4, zod@^3.23.8, zod@^3.24.1: +zod@^3.23.8, zod@^3.24.1: version "3.24.3" resolved "https://registry.npmjs.org/zod/-/zod-3.24.3.tgz" integrity sha512-HhY1oqzWCQWuUqvBFnsyrtZRhyPeR7SUGv+C4+MsisMuVfSPx8HpwWqH8tRahSlt6M3PiFAcoeFhZAqIXTxoSg== diff --git a/packages/eslint-plugin-react-hooks/README.md b/packages/eslint-plugin-react-hooks/README.md index 20d32fe9fd1bc..afd89ab2263ac 100644 --- a/packages/eslint-plugin-react-hooks/README.md +++ b/packages/eslint-plugin-react-hooks/README.md @@ -1,8 +1,6 @@ # `eslint-plugin-react-hooks` -This ESLint plugin enforces the [Rules of Hooks](https://react.dev/reference/rules/rules-of-hooks). - -It is a part of the [Hooks API](https://react.dev/reference/react/hooks) for React. +The official ESLint plugin for [React](https://react.dev) which enforces the [Rules of React](https://react.dev/reference/eslint-plugin-react-hooks) and other best practices. ## Installation @@ -89,7 +87,7 @@ If you're using a version earlier than 5.2.0, the legacy config was simply `reco ### Custom Configuration -If you want more fine-grained configuration, you can instead add a snippet like this to your ESLint configuration file: +If you want more fine-grained configuration, you can instead choose to enable specific rules. However, we strongly encourage using the recommended presets — see above — so that you will automatically receive new recommended rules as we add them in future versions of the plugin. #### Flat Config (eslint.config.js|ts) diff --git a/packages/eslint-plugin-react-hooks/package.json b/packages/eslint-plugin-react-hooks/package.json index 8f7cfc361d1ff..557e48af65fcd 100644 --- a/packages/eslint-plugin-react-hooks/package.json +++ b/packages/eslint-plugin-react-hooks/package.json @@ -41,24 +41,22 @@ "dependencies": { "@babel/core": "^7.24.4", "@babel/parser": "^7.24.4", - "@babel/plugin-proposal-private-methods": "^7.18.6", - "hermes-parser": "^0.25.1", - "zod": "^3.22.4", - "zod-validation-error": "^3.0.3" + "zod": "^3.22.4 || ^4.0.0", + "zod-validation-error": "^3.0.3 || ^4.0.0" }, "devDependencies": { "@babel/eslint-parser": "^7.11.4", "@babel/preset-typescript": "^7.26.0", "@babel/types": "^7.19.0", "@tsconfig/strictest": "^2.0.5", - "@typescript-eslint/parser-v2": "npm:@typescript-eslint/parser@^2.26.0", - "@typescript-eslint/parser-v3": "npm:@typescript-eslint/parser@^3.10.0", - "@typescript-eslint/parser-v4": "npm:@typescript-eslint/parser@^4.1.0", - "@typescript-eslint/parser-v5": "npm:@typescript-eslint/parser@^5.62.0", "@types/eslint": "^8.56.12", "@types/estree": "^1.0.6", "@types/estree-jsx": "^1.0.5", "@types/node": "^20.2.5", + "@typescript-eslint/parser-v2": "npm:@typescript-eslint/parser@^2.26.0", + "@typescript-eslint/parser-v3": "npm:@typescript-eslint/parser@^3.10.0", + "@typescript-eslint/parser-v4": "npm:@typescript-eslint/parser@^4.1.0", + "@typescript-eslint/parser-v5": "npm:@typescript-eslint/parser@^5.62.0", "babel-eslint": "^10.0.3", "eslint-v7": "npm:eslint@^7.7.0", "eslint-v8": "npm:eslint@^8.57.1", diff --git a/packages/eslint-plugin-react-hooks/src/shared/RunReactCompiler.ts b/packages/eslint-plugin-react-hooks/src/shared/RunReactCompiler.ts index fc9067d27403a..7755dbe947f4a 100644 --- a/packages/eslint-plugin-react-hooks/src/shared/RunReactCompiler.ts +++ b/packages/eslint-plugin-react-hooks/src/shared/RunReactCompiler.ts @@ -9,8 +9,6 @@ import {transformFromAstSync} from '@babel/core'; import {parse as babelParse} from '@babel/parser'; import {File} from '@babel/types'; -// @ts-expect-error: no types available -import PluginProposalPrivateMethods from '@babel/plugin-proposal-private-methods'; import BabelPluginReactCompiler, { parsePluginOptions, validateEnvironmentConfig, @@ -19,7 +17,6 @@ import BabelPluginReactCompiler, { LoggerEvent, } from 'babel-plugin-react-compiler'; import type {SourceCode} from 'eslint'; -import * as HermesParser from 'hermes-parser'; import {isDeepStrictEqual} from 'util'; import type {ParseResult} from '@babel/parser'; @@ -80,7 +77,6 @@ function getFlowSuppressions( return results; } - function runReactCompilerImpl({ sourceCode, filename, @@ -117,27 +113,14 @@ function runReactCompilerImpl({ } let babelAST: ParseResult | null = null; - if (filename.endsWith('.tsx') || filename.endsWith('.ts')) { - try { - babelAST = babelParse(sourceCode.text, { - sourceFilename: filename, - sourceType: 'unambiguous', - plugins: ['typescript', 'jsx'], - }); - } catch { - /* empty */ - } - } else { - try { - babelAST = HermesParser.parse(sourceCode.text, { - babel: true, - enableExperimentalComponentSyntax: true, - sourceFilename: filename, - sourceType: 'module', - }); - } catch { - /* empty */ - } + try { + babelAST = babelParse(sourceCode.text, { + sourceFilename: filename, + sourceType: 'unambiguous', + plugins: ['typescript', 'jsx'], + }); + } catch (err: unknown) { + /* empty */ } if (babelAST != null) { @@ -147,10 +130,7 @@ function runReactCompilerImpl({ filename, highlightCode: false, retainLines: true, - plugins: [ - [PluginProposalPrivateMethods, {loose: true}], - [BabelPluginReactCompiler, options], - ], + plugins: [[BabelPluginReactCompiler, options]], sourceType: 'module', configFile: false, babelrc: false, diff --git a/packages/react-devtools-core/package.json b/packages/react-devtools-core/package.json index 8ce8f436e815d..c8facd1a24d46 100644 --- a/packages/react-devtools-core/package.json +++ b/packages/react-devtools-core/package.json @@ -1,6 +1,6 @@ { "name": "react-devtools-core", - "version": "6.1.5", + "version": "7.0.0", "description": "Use react-devtools outside of the browser", "license": "MIT", "main": "./dist/backend.js", diff --git a/packages/react-devtools-inline/package.json b/packages/react-devtools-inline/package.json index 9d207527b813d..0363530cd1025 100644 --- a/packages/react-devtools-inline/package.json +++ b/packages/react-devtools-inline/package.json @@ -1,6 +1,6 @@ { "name": "react-devtools-inline", - "version": "6.1.5", + "version": "7.0.0", "description": "Embed react-devtools within a website", "license": "MIT", "main": "./dist/backend.js", diff --git a/packages/react-devtools-shared/src/devtools/views/SuspenseTab/SuspenseRects.js b/packages/react-devtools-shared/src/devtools/views/SuspenseTab/SuspenseRects.js index 8e43944e7a79d..f949631be8427 100644 --- a/packages/react-devtools-shared/src/devtools/views/SuspenseTab/SuspenseRects.js +++ b/packages/react-devtools-shared/src/devtools/views/SuspenseTab/SuspenseRects.js @@ -330,12 +330,25 @@ function SuspenseRectsContainer(): React$Node { }); } + function handleDoubleClick(event: SyntheticMouseEvent) { + if (event.defaultPrevented) { + // Already clicked on an inner rect + return; + } + event.preventDefault(); + suspenseTreeDispatch({ + type: 'SUSPENSE_SET_TIMELINE_INDEX', + payload: 0, + }); + } + const isRootSelected = roots.includes(inspectedElementID); return (
void, unobserveUsing: (observer: IntersectionObserver) => void, compareDocumentPosition: (otherNode: PublicInstance) => number, + getRootNode(getRootNodeOptions?: { + composed: boolean, + }): Node | FragmentInstanceType, + getClientRects: () => Array, }; function FragmentInstance(this: FragmentInstanceType, fragmentFiber: Fiber) { @@ -754,6 +758,42 @@ function collectChildren(child: Fiber, collection: Array): boolean { return false; } +// $FlowFixMe[prop-missing] +FragmentInstance.prototype.getRootNode = function ( + this: FragmentInstanceType, + getRootNodeOptions?: {composed: boolean}, +): Node | FragmentInstanceType { + const parentHostFiber = getFragmentParentHostFiber(this._fragmentFiber); + if (parentHostFiber === null) { + return this; + } + const parentHostInstance = getPublicInstanceFromHostFiber(parentHostFiber); + // $FlowFixMe[incompatible-use] Fabric PublicInstance is opaque + const rootNode = (parentHostInstance.getRootNode(getRootNodeOptions): Node); + return rootNode; +}; + +// $FlowFixMe[prop-missing] +FragmentInstance.prototype.getClientRects = function ( + this: FragmentInstanceType, +): Array { + const rects: Array = []; + traverseFragmentInstance(this._fragmentFiber, collectClientRects, rects); + return rects; +}; +function collectClientRects(child: Fiber, rects: Array): boolean { + const instance = getPublicInstanceFromHostFiber(child); + + // getBoundingClientRect is available on Fabric instances while getClientRects is not. + // This should work as a substitute in this case because the only equivalent of a multi-rect + // element in RN would be a nested Text component. + // Since we only use top-level nodes here, we can assume that getBoundingClientRect is sufficient. + // $FlowFixMe[method-unbinding] + // $FlowFixMe[incompatible-use] Fabric PublicInstance is opaque + rects.push(instance.getBoundingClientRect()); + return false; +} + export function createFragmentInstance( fragmentFiber: Fiber, ): FragmentInstanceType { diff --git a/scripts/rollup/bundles.js b/scripts/rollup/bundles.js index 33bce79e4c781..c66b43797f180 100644 --- a/scripts/rollup/bundles.js +++ b/scripts/rollup/bundles.js @@ -1253,7 +1253,6 @@ const bundles = [ preferBuiltins: true, externals: [ '@babel/core', - '@babel/plugin-proposal-private-methods', 'hermes-parser', 'zod', 'zod-validation-error', diff --git a/yarn.lock b/yarn.lock index d2f2e8d017c61..f5ac1f649dd2d 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1108,14 +1108,6 @@ "@babel/helper-create-class-features-plugin" "^7.10.4" "@babel/helper-plugin-utils" "^7.10.4" -"@babel/plugin-proposal-private-methods@^7.18.6": - version "7.18.6" - resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-private-methods/-/plugin-proposal-private-methods-7.18.6.tgz#5209de7d213457548a98436fa2882f52f4be6bea" - integrity sha512-nutsvktDItsNn4rpGItSNV2sz1XwS+nfU0Rg8aCx3W3NOKVzdMjJRu0O5OkgDp3ZGICSTbgRpxZoWsxoKRvbeA== - dependencies: - "@babel/helper-create-class-features-plugin" "^7.18.6" - "@babel/helper-plugin-utils" "^7.18.6" - "@babel/plugin-proposal-private-property-in-object@7.21.0-placeholder-for-preset-env.2": version "7.21.0-placeholder-for-preset-env.2" resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-private-property-in-object/-/plugin-proposal-private-property-in-object-7.21.0-placeholder-for-preset-env.2.tgz#7844f9289546efa9febac2de4cfe358a050bd703" @@ -10198,11 +10190,6 @@ hermes-eslint@^0.32.0: hermes-estree "0.32.0" hermes-parser "0.32.0" -hermes-estree@0.25.1: - version "0.25.1" - resolved "https://registry.yarnpkg.com/hermes-estree/-/hermes-estree-0.25.1.tgz#6aeec17d1983b4eabf69721f3aa3eb705b17f480" - integrity sha512-0wUoCcLp+5Ev5pDW2OriHC2MJCbwLwuRx+gAqMTOkGKJJiBCLjtrvy4PWUGn6MIVefecRpzoOZ/UV6iGdOr+Cw== - hermes-estree@0.29.1: version "0.29.1" resolved "https://registry.yarnpkg.com/hermes-estree/-/hermes-estree-0.29.1.tgz#043c7db076e0e8ef8c5f6ed23828d1ba463ebcc5" @@ -10227,13 +10214,6 @@ hermes-parser@0.32.0, hermes-parser@^0.32.0: dependencies: hermes-estree "0.32.0" -hermes-parser@^0.25.1: - version "0.25.1" - resolved "https://registry.yarnpkg.com/hermes-parser/-/hermes-parser-0.25.1.tgz#5be0e487b2090886c62bd8a11724cd766d5f54d1" - integrity sha512-6pEjquH3rqaI6cYAXYPcz9MS4rY6R4ngRgrgfDshRptUZIc3lw0MCIJIGDj9++mfySOuPTHB4nrSW99BCvOPIA== - dependencies: - hermes-estree "0.25.1" - homedir-polyfill@^1.0.0, homedir-polyfill@^1.0.1: version "1.0.3" resolved "https://registry.yarnpkg.com/homedir-polyfill/-/homedir-polyfill-1.0.3.tgz#743298cef4e5af3e194161fbadcc2151d3a058e8"