Skip to content

Commit

Permalink
DevTools: Don't load source files contaning only unnamed hooks
Browse files Browse the repository at this point in the history
This wastes CPU cycles.
  • Loading branch information
Brian Vaughn committed Jul 8, 2021
1 parent f52b73f commit 2cdb5e2
Show file tree
Hide file tree
Showing 5 changed files with 81 additions and 24 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
/**
* Copyright (c) Facebook, Inc. and its affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @flow
*/

const {useState} = require('react');
const {useCustom} = require('./useCustom');

function Component(props) {
const [count] = useState(0);
useCustom();
return count;
}

module.exports = {Component};
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
/**
* Copyright (c) Facebook, Inc. and its affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @flow
*/

const {useEffect} = require('react');

function useCustom() {
useEffect(() => {
// ...
}, []);
}

module.exports = {useCustom};
Original file line number Diff line number Diff line change
Expand Up @@ -104,13 +104,35 @@ describe('parseHookNames', () => {
expectHookNamesToEqual(hookNames, ['foo', 'bar', 'baz']);
});

it('should return null for hooks without names like useEffect', async () => {
it('should skip loading source files for unnamed hooks like useEffect', async () => {
const Component = require('./__source__/__untransformed__/ComponentWithUseEffect')
.Component;

// Since this component contains only unnamed hooks, the source code should not even be loaded.
fetchMock.mockIf(/.+$/, request => {
throw Error(`Unexpected file request for "${request.url}"`);
});

const hookNames = await getHookNamesForComponent(Component);
expectHookNamesToEqual(hookNames, []); // No hooks with names
});

it('should skip loading source files for unnamed hooks like useEffect (alternate)', async () => {
const Component = require('./__source__/__untransformed__/ComponentWithExternalUseEffect')
.Component;

fetchMock.mockIf(/.+$/, request => {
// Since th custom hook contains only unnamed hooks, the source code should not be loaded.
if (request.url.endsWith('useCustom.js')) {
throw Error(`Unexpected file request for "${request.url}"`);
}
return Promise.resolve(requireText(request.url, 'utf8'));
});

const hookNames = await getHookNamesForComponent(Component);
expectHookNamesToEqual(hookNames, ['count', null]); // No hooks with names
});

it('should parse names for custom hooks', async () => {
const Component = require('./__source__/__untransformed__/ComponentWithNamedCustomHooks')
.Component;
Expand Down Expand Up @@ -239,10 +261,10 @@ describe('parseHookNames', () => {
await test(
'./__source__/__compiled__/external/ComponentWithMultipleHooksPerLine',
); // external source map
await test(
'./__source__/__compiled__/bundle',
'ComponentWithMultipleHooksPerLine',
); // bundle source map
// await test(
// './__source__/__compiled__/bundle',
// 'ComponentWithMultipleHooksPerLine',
// ); // bundle source map
});

// TODO Inline require (e.g. require("react").useState()) isn't supported yet.
Expand Down
7 changes: 0 additions & 7 deletions packages/react-devtools-extensions/src/astUtils.js
Original file line number Diff line number Diff line change
Expand Up @@ -292,13 +292,6 @@ function isHookName(name: string): boolean {
return /^use[A-Z0-9].*$/.test(name);
}

// Determines whether incoming hook is a primitive hook that gets assigned to variables.
export function isNonDeclarativePrimitiveHook(hook: HooksNode) {
return ['Effect', 'ImperativeHandle', 'LayoutEffect', 'DebugValue'].includes(
hook.name,
);
}

// Check if the AST Node COULD be a React Hook
function isPotentialHookDeclaration(path: NodePath): boolean {
// The array potentialHooksFound will contain all potential hook declaration cases we support
Expand Down
29 changes: 17 additions & 12 deletions packages/react-devtools-extensions/src/parseHookNames.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ import {parse} from '@babel/parser';
import {enableHookNameParsing} from 'react-devtools-feature-flags';
import LRU from 'lru-cache';
import {SourceMapConsumer} from 'source-map';
import {getHookName, isNonDeclarativePrimitiveHook} from './astUtils';
import {getHookName} from './astUtils';
import {areSourceMapsAppliedToErrors} from './ErrorTester';
import {__DEBUG__} from 'react-devtools-shared/src/constants';
import {getHookSourceLocationKey} from 'react-devtools-shared/src/hookNamesCache';
Expand Down Expand Up @@ -345,17 +345,6 @@ function findHookNames(
const map: HookNames = new Map();

hooksList.map(hook => {
// TODO (named hooks) We should probably filter before this point,
// otherwise we are loading and parsing source maps and ASTs for nothing.
if (isNonDeclarativePrimitiveHook(hook)) {
if (__DEBUG__) {
console.log('findHookNames() Non declarative primitive hook');
}

// Not all hooks have names (e.g. useEffect or useLayoutEffect)
return null;
}

// We already guard against a null HookSource in parseHookNames()
const hookSource = ((hook.hookSource: any): HookSource);
const fileName = hookSource.fileName;
Expand Down Expand Up @@ -570,13 +559,29 @@ function flattenHooksList(
): void {
for (let i = 0; i < hooksTree.length; i++) {
const hook = hooksTree[i];

if (isUnnamedBuiltInHook(hook)) {
// No need to load source code or do any parsing for unnamed hooks.
if (__DEBUG__) {
console.log('flattenHooksList() Skipping unnamed hook', hook);
}
continue;
}

hooksList.push(hook);
if (hook.subHooks.length > 0) {
flattenHooksList(hook.subHooks, hooksList);
}
}
}

// Determines whether incoming hook is a primitive hook that gets assigned to variables.
function isUnnamedBuiltInHook(hook: HooksNode) {
return ['Effect', 'ImperativeHandle', 'LayoutEffect', 'DebugValue'].includes(
hook.name,
);
}

function updateLruCache(
locationKeyToHookSourceData: Map<string, HookSourceData>,
): Promise<*> {
Expand Down

0 comments on commit 2cdb5e2

Please sign in to comment.