Support globally installed React DevTools

Summary:
- Support loading globally installed DevTools

Background:
1. Initially, I wanted to use react-devtools-core as before. react-devtools-core standalone contains quite a few imports of node.js APIs. After [a conversation with Brian](https://fb.workplace.com/groups/react.devtools/permalink/3131548550392044), I pivoted to react-devtools-inline
2. Technical design doc of react-devtools-inline integration: https://docs.google.com/document/d/1STUSUhXzrW_KkvqSu7Ge-rxjVFF7oU3_NbwzimkO_Z4
3. We support usage of globally installed devtools. Code of react-devtools-inline is not ready to be used by the browser as is. We need to bundle it and substitute React and ReactDOM imports with the globals.
4. As we can't pre-compile what users install globally, we need to bundle global devtools on demand,
5. I tried re-using our Metro bundling pipeline initially, but gave up after fighting it for 2 days. Included, `rollup` instead.
6. Size of a `tgz` archive with a plugin is 2.1MB

allow-large-files

Reviewed By: mweststrate

Differential Revision: D34968770

fbshipit-source-id: 352299964ccc195b8677dbda47db84ffaf38737b
This commit is contained in:
Andrey Goncharov
2022-03-31 04:01:33 -07:00
committed by Facebook GitHub Bot
parent 68aec1df60
commit ba9a80545d
21 changed files with 285 additions and 28 deletions

View File

@@ -34,6 +34,14 @@ test('transform react-dom requires to global object', () => {
expect(code).toBe('global.ReactDOM;');
});
test('transform react-dom/client requires to global object', () => {
const src = 'require("react-dom/client")';
const ast = parse(src);
const transformed = transformFromAstSync(ast, src, babelOptions)!.ast;
const {code} = generate(transformed!);
expect(code).toBe('global.ReactDOMClient;');
});
test('transform flipper requires to global object', () => {
const src = 'require("flipper")';
const ast = parse(src);

View File

@@ -45,6 +45,9 @@ export const BUILTINS = [
'v8',
'repl',
'timers',
'perf_hooks',
'fsevents',
'./fsevents.node',
// MWE node-fetch looks strange here, not sure what the effect of changing that would be
'node-fetch',
// jest is referred to in source code, like in TestUtils, but we don't want to ever bundle it up!

View File

@@ -0,0 +1,34 @@
/**
* 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.
*
* @format
*/
import {CallExpression, identifier} from '@babel/types';
import {NodePath} from '@babel/traverse';
module.exports = () => ({
name: 'skip-fsevents-dynamic-imports-on-win-and-linux',
visitor: {
CallExpression(path: NodePath<CallExpression>) {
if (process.platform === 'darwin') {
return;
}
const node = path.node;
if (
node.type === 'CallExpression' &&
node.callee.type === 'Import' &&
node.arguments.length === 1 &&
node.arguments[0].type === 'StringLiteral'
) {
const source = node.arguments[0].value;
if (source === 'fsevents') {
path.replaceWith(identifier('triggerReferenceError'));
}
}
},
},
});

View File

@@ -17,10 +17,7 @@ import {
} from './replace-flipper-requires';
// do not apply this transform for these paths
const EXCLUDE_PATHS = [
'/node_modules/react-devtools-core/',
'relay-devtools/DevtoolsUI',
];
const EXCLUDE_PATHS = ['relay-devtools/DevtoolsUI'];
function isExcludedPath(path: string) {
for (const epath of EXCLUDE_PATHS) {
if (path.indexOf(epath) > -1) {

View File

@@ -21,6 +21,8 @@ const requireReplacements: any = {
'flipper-plugin': 'global.FlipperPlugin',
react: 'global.React',
'react-dom': 'global.ReactDOM',
'react-dom/client': 'global.ReactDOMClient',
'react-is': 'global.ReactIs',
antd: 'global.antd',
immer: 'global.Immer',
'@emotion/styled': 'global.emotion_styled',

View File

@@ -12,6 +12,7 @@ import {default as getCacheKey} from './get-cache-key';
const presets = [require('@babel/preset-react')];
const plugins = [
require('./fsevents-dynamic-imports'),
require('./electron-requires'),
require('./import-react'),
require('./app-flipper-requires'),

View File

@@ -17,7 +17,11 @@ const presets = [
{targets: {electron: flipperEnv.FLIPPER_ELECTRON_VERSION}},
],
];
const plugins = [require('./electron-requires-main'), require('./fb-stubs')];
const plugins = [
require('./fsevents-dynamic-imports'),
require('./electron-requires-main'),
require('./fb-stubs'),
];
module.exports = {
transform,

View File

@@ -11,6 +11,7 @@ import {default as doTransform} from './transform';
const presets = [require('@babel/preset-react')];
const plugins = [
require('./fsevents-dynamic-imports'),
require('./electron-requires'),
require('./plugin-flipper-requires'),
require('./fb-stubs'),

View File

@@ -24,6 +24,7 @@ const presets = [
];
const plugins = [
require('./fsevents-dynamic-imports'),
require('./electron-requires'),
require('./plugin-flipper-requires'),
require('./fb-stubs'),

View File

@@ -23,6 +23,7 @@ const presets = [
// In DEV builds, we keep node_modules as is, as to not waste resources on trying to bundle them
const plugins = [
require('./fsevents-dynamic-imports'),
require('./electron-requires-server'),
require('./plugin-flipper-requires'),
require('./fb-stubs'),

View File

@@ -27,6 +27,7 @@ const presets = [
// electron-requires makes sure that *only* requires of built in node_modules are using "electronRequire"
// (which effectively makes them external, as electronRequire === require, but not rolled up with Metro)
const plugins = [
require('./fsevents-dynamic-imports'),
require('./electron-requires'),
require('./plugin-flipper-requires'),
require('./fb-stubs'),