-
Notifications
You must be signed in to change notification settings - Fork 50.6k
DevTools: Parse named source AST in a worker #21902
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 5 commits
2a40c4b
11fced8
be66d47
7346b50
1bebee8
485567a
c119085
754f8a3
ea30ba7
d2b962d
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 |
|---|---|---|
|
|
@@ -11,10 +11,15 @@ | |
| // This is done to control if and how the code is transformed at runtime. | ||
| // Do not declare test components within this test file as it is very fragile. | ||
|
|
||
| const {parse} = require('@babel/parser'); | ||
| const babelParserWorker = require('../workerizedBabelParser/babelParser.worker.js'); | ||
|
|
||
| describe('parseHookNames', () => { | ||
| let fetchMock; | ||
| let inspectHooks; | ||
| let parseHookNames; | ||
| let babelParserMock; | ||
| let workerizedParseMock; | ||
|
|
||
| beforeEach(() => { | ||
| jest.resetModules(); | ||
|
|
@@ -23,6 +28,25 @@ describe('parseHookNames', () => { | |
| console.trace('source-map-support'); | ||
| }); | ||
|
|
||
| window.Worker = undefined; | ||
|
|
||
| babelParserMock = jest.fn(parse); | ||
| workerizedParseMock = jest.fn(babelParserWorker.workerizedParse); | ||
|
|
||
| jest.mock('@babel/parser', () => { | ||
| return { | ||
| __esModule: true, | ||
| parse: babelParserMock, | ||
| }; | ||
| }); | ||
|
|
||
| jest.mock('../workerizedBabelParser/babelParser.worker.js', () => { | ||
| return { | ||
| __esModule: true, | ||
| default: () => ({workerizedParse: workerizedParseMock}), | ||
| }; | ||
| }); | ||
|
|
||
| fetchMock = require('jest-fetch-mock'); | ||
| fetchMock.enableMocks(); | ||
|
|
||
|
|
@@ -89,6 +113,30 @@ describe('parseHookNames', () => { | |
| return hookNames; | ||
| } | ||
|
|
||
| it('should use worker when available', async () => { | ||
| const Component = require('./__source__/__untransformed__/ComponentWithUseState') | ||
| .Component; | ||
|
|
||
| window.Worker = true; | ||
| // resets module so mocked worker instance can be updated | ||
| jest.resetModules(); | ||
| parseHookNames = require('../parseHookNames').parseHookNames; | ||
|
|
||
| const hookNames = await getHookNamesForComponent(Component); | ||
| expectHookNamesToEqual(hookNames, ['foo', 'bar', 'baz']); | ||
| expect(workerizedParseMock).toHaveBeenCalledTimes(3); | ||
| }); | ||
|
|
||
| it('should use babel parser when worker is not available', async () => { | ||
| const Component = require('./__source__/__untransformed__/ComponentWithUseState') | ||
| .Component; | ||
|
|
||
| const hookNames = await getHookNamesForComponent(Component); | ||
| expectHookNamesToEqual(hookNames, ['foo', 'bar', 'baz']); | ||
| expect(workerizedParseMock).toHaveBeenCalledTimes(0); | ||
| expect(babelParserMock).toHaveBeenCalledTimes(3); | ||
| }); | ||
|
|
||
|
||
| it('should parse names for useState()', async () => { | ||
| const Component = require('./__source__/__untransformed__/ComponentWithUseState') | ||
| .Component; | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -9,11 +9,11 @@ | |
| * @flow | ||
| */ | ||
|
|
||
| import {parse} from '@babel/parser'; | ||
| import LRU from 'lru-cache'; | ||
| import {SourceMapConsumer} from 'source-map'; | ||
| import {getHookName} from './astUtils'; | ||
| import {areSourceMapsAppliedToErrors} from './ErrorTester'; | ||
| import {workerizedParse} from './workerizedBabelParser'; | ||
| import {__DEBUG__} from 'react-devtools-shared/src/constants'; | ||
| import {getHookSourceLocationKey} from 'react-devtools-shared/src/hookNamesCache'; | ||
|
|
||
|
|
@@ -471,6 +471,7 @@ function loadSourceFiles( | |
| async function parseSourceAST( | ||
| locationKeyToHookSourceData: Map<string, HookSourceData>, | ||
| ): Promise<*> { | ||
| const promises = []; | ||
| locationKeyToHookSourceData.forEach(hookSourceData => { | ||
| if (hookSourceData.originalSourceAST !== null) { | ||
| // Use cached metadata. | ||
|
|
@@ -550,24 +551,26 @@ async function parseSourceAST( | |
| const plugin = | ||
| originalSourceCode.indexOf('@flow') > 0 ? 'flow' : 'typescript'; | ||
|
|
||
| // TODO (named hooks) Parsing should ideally be done off of the main thread. | ||
| const originalSourceAST = parse(originalSourceCode, { | ||
| const parsePromise = workerizedParse(originalSourceCode, { | ||
| sourceType: 'unambiguous', | ||
| plugins: ['jsx', plugin], | ||
| }).then(originalSourceAST => { | ||
| hookSourceData.originalSourceAST = originalSourceAST; | ||
| if (__DEBUG__) { | ||
| console.log( | ||
| `parseSourceAST() Caching source metadata for "${originalSourceURL}"`, | ||
| ); | ||
| } | ||
| originalURLToMetadataCache.set(originalSourceURL, { | ||
| originalSourceAST, | ||
| originalSourceCode, | ||
| }); | ||
| }); | ||
| hookSourceData.originalSourceAST = originalSourceAST; | ||
| if (__DEBUG__) { | ||
| console.log( | ||
| `parseSourceAST() Caching source metadata for "${originalSourceURL}"`, | ||
| ); | ||
| } | ||
| originalURLToMetadataCache.set(originalSourceURL, { | ||
| originalSourceAST, | ||
| originalSourceCode, | ||
| }); | ||
|
|
||
| promises.push(parsePromise); | ||
|
||
| } | ||
| }); | ||
| return Promise.resolve(); | ||
| return Promise.all(promises); | ||
| } | ||
|
|
||
| function flattenHooksList( | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,5 @@ | ||
| import {parse} from '@babel/parser'; | ||
|
|
||
| export function workerizedParse(...params) { | ||
| return parse(...params); | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,33 @@ | ||
| /** | ||
| * 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 | ||
| */ | ||
|
|
||
| // This file uses workerize to load ./babelParse.worker as a webworker | ||
| // and instanciates it, exposing flow typed functions that can be used | ||
| // on other files. | ||
|
|
||
| import {parse} from '@babel/parser'; | ||
| import WorkerizedBabelParser from './babelParser.worker'; | ||
|
|
||
| const workerizedBabelParser = window.Worker && WorkerizedBabelParser(); | ||
|
|
||
| type Parse = typeof parse; | ||
|
|
||
| export const workerizedParse = async ( | ||
| input: string, | ||
| options?: { | ||
| plugins: string[], | ||
| }, | ||
| ) => { | ||
| // Checks if worker is not available runs regular babel parse | ||
| if (workerizedBabelParser) { | ||
| const workerParse: Parse = workerizedBabelParser.workerizedParse; | ||
| return workerParse(input, options); | ||
| } | ||
| return parse(input, options); | ||
| }; |
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.
Also don't forget to run
yarn installin the root directory to update theyarn.lockfile for this change.