Remove remaining Electron imports from product code: paths & env

Summary:
This diff removes most remaining Electron imports, by storing env and path constants on the RenderHost. As nice side effect those paths are all cached now as well.

To make sure RenderHost is initialised before Flipper itself, forced loading Flipper through a require. Otherwise the setup is super sensitive to circular import statements, since a lot of module initialisation code depends on those paths / env vars.

Reviewed By: nikoant

Differential Revision: D31992230

fbshipit-source-id: 91beb430902272aaf4b051b35cdf12d2fc993347
This commit is contained in:
Michel Weststrate
2021-11-03 07:00:03 -07:00
committed by Facebook GitHub Bot
parent dba09542f9
commit 2e7015388c
20 changed files with 413 additions and 375 deletions

View File

@@ -7,55 +7,13 @@
* @format
*/
import {Provider} from 'react-redux';
import ReactDOM from 'react-dom';
import {initializeElectron} from './electron/initializeElectron';
import GK from './fb-stubs/GK';
import {init as initLogger} from './fb-stubs/Logger';
import {SandyApp} from './sandy-chrome/SandyApp';
import setupPrefetcher from './fb-stubs/Prefetcher';
import {Persistor, persistStore} from 'redux-persist';
import {Store} from './reducers/index';
import dispatcher from './dispatcher/index';
import TooltipProvider from './ui/components/TooltipProvider';
import config from './utils/processConfig';
import {initLauncherHooks} from './utils/launcher';
import {setPersistor} from './utils/persistor';
import React from 'react';
import path from 'path';
import {getStore} from './store';
import {cache} from '@emotion/css';
import {CacheProvider} from '@emotion/react';
import {enableMapSet} from 'immer';
import os from 'os';
import {initializeFlipperLibImplementation} from './utils/flipperLibImplementation';
import {enableConsoleHook} from './chrome/ConsoleLogs';
import {sideEffect} from './utils/sideEffect';
import {
_NuxManagerContext,
_createNuxManager,
_setGlobalInteractionReporter,
Logger,
_LoggerContext,
Layout,
theme,
getFlipperLib,
} from 'flipper-plugin';
import isProduction from './utils/isProduction';
import {Button, Input, Result, Typography} from 'antd';
import constants from './fb-stubs/constants';
import styled from '@emotion/styled';
import {CopyOutlined} from '@ant-design/icons';
import {getVersionString} from './utils/versionString';
import {PersistGate} from 'redux-persist/integration/react';
import {
setLoggerInstance,
setUserSessionManagerInstance,
GK as flipperCommonGK,
} from 'flipper-common';
import {internGraphPOSTAPIRequest} from './fb-stubs/user';
import {getRenderHostInstance} from './RenderHost';
import {initializeElectron} from './electron/initializeElectron';
initializeElectron();
if (process.env.NODE_ENV === 'development' && os.platform() === 'darwin') {
// By default Node.JS has its internal certificate storage and doesn't use
@@ -70,187 +28,9 @@ enableMapSet();
GK.init();
class AppFrame extends React.Component<
{logger: Logger; persistor: Persistor},
{error: any; errorInfo: any}
> {
state = {error: undefined as any, errorInfo: undefined as any};
getError() {
return this.state.error
? `${
this.state.error
}\n\nFlipper version: ${getVersionString()}\n\nComponent stack:\n${
this.state.errorInfo?.componentStack
}\n\nError stacktrace:\n${this.state.error?.stack}`
: '';
}
render() {
const {logger, persistor} = this.props;
return this.state.error ? (
<Layout.Container grow center pad={80} style={{height: '100%'}}>
<Layout.Top style={{maxWidth: 800, height: '100%'}}>
<Result
status="error"
title="Detected a Flipper crash"
subTitle={
<p>
A crash was detected in the Flipper chrome. Filing a{' '}
<Typography.Link
href={
constants.IS_PUBLIC_BUILD
? 'https://github.com/facebook/flipper/issues/new/choose'
: constants.FEEDBACK_GROUP_LINK
}>
bug report
</Typography.Link>{' '}
would be appreciated! Please include the details below.
</p>
}
extra={[
<Button
key="copy_error"
icon={<CopyOutlined />}
onClick={() => {
getFlipperLib().writeTextToClipboard(this.getError());
}}>
Copy error
</Button>,
<Button
key="retry_error"
type="primary"
onClick={() => {
this.setState({error: undefined, errorInfo: undefined});
}}>
Retry
</Button>,
]}
/>
<CodeBlock value={this.getError()} readOnly />
</Layout.Top>
</Layout.Container>
) : (
<_LoggerContext.Provider value={logger}>
<Provider store={getStore()}>
<PersistGate persistor={persistor}>
<CacheProvider value={cache}>
<TooltipProvider>
<_NuxManagerContext.Provider value={_createNuxManager()}>
<SandyApp />
</_NuxManagerContext.Provider>
</TooltipProvider>
</CacheProvider>
</PersistGate>
</Provider>
</_LoggerContext.Provider>
);
}
componentDidCatch(error: any, errorInfo: any) {
console.error(
`Flipper chrome crash: ${error}`,
error,
'\nComponents: ' + errorInfo?.componentStack,
);
this.setState({
error,
errorInfo,
});
}
}
function setProcessState(store: Store) {
const settings = store.getState().settingsState;
const androidHome = settings.androidHome;
const idbPath = settings.idbPath;
if (!process.env.ANDROID_HOME && !process.env.ANDROID_SDK_ROOT) {
process.env.ANDROID_HOME = androidHome;
}
// emulator/emulator is more reliable than tools/emulator, so prefer it if
// it exists
process.env.PATH =
['emulator', 'tools', 'platform-tools']
.map((directory) => path.resolve(androidHome, directory))
.join(':') +
`:${idbPath}` +
`:${process.env.PATH}`;
window.requestIdleCallback(() => {
setupPrefetcher(settings);
});
}
function init() {
initializeElectron();
// TODO: centralise all those initialisations in a single configuration call
flipperCommonGK.get = (name) => GK.get(name);
const store = getStore();
const logger = initLogger(store);
setLoggerInstance(logger);
// rehydrate app state before exposing init
const persistor = persistStore(store, undefined, () => {
// Make sure process state is set before dispatchers run
setProcessState(store);
dispatcher(store, logger);
});
setPersistor(persistor);
initializeFlipperLibImplementation(getRenderHostInstance(), store, logger);
_setGlobalInteractionReporter((r) => {
logger.track('usage', 'interaction', r);
if (!isProduction()) {
const msg = `[interaction] ${r.scope}:${r.action} in ${r.duration}ms`;
if (r.success) console.debug(msg);
else console.warn(msg, r.error);
}
});
setUserSessionManagerInstance({
internGraphPOSTAPIRequest,
});
ReactDOM.render(
<AppFrame logger={logger} persistor={persistor} />,
document.getElementById('root'),
);
initLauncherHooks(config(), store);
enableConsoleHook();
window.flipperGlobalStoreDispatch = store.dispatch;
// listen to settings and load the right theme
sideEffect(
store,
{name: 'loadTheme', fireImmediately: false, throttleMs: 500},
(state) => state.settingsState.darkMode,
(theme) => {
let shouldUseDarkMode = false;
if (theme === 'dark') {
shouldUseDarkMode = true;
} else if (theme === 'light') {
shouldUseDarkMode = false;
} else if (theme === 'system') {
shouldUseDarkMode = getRenderHostInstance().shouldUseDarkColors();
}
(
document.getElementById('flipper-theme-import') as HTMLLinkElement
).href = `themes/${shouldUseDarkMode ? 'dark' : 'light'}.css`;
getRenderHostInstance().sendIpcEvent('setTheme', theme);
},
);
}
setImmediate(() => {
// make sure all modules are loaded
// @ts-ignore
window.flipperInit = init;
window.dispatchEvent(new Event('flipper-store-ready'));
});
const CodeBlock = styled(Input.TextArea)({
...theme.monospace,
color: theme.textColorSecondary,
});
// By turning this in a require, we force the JS that the body of this module (init) has completed (initializeElectron),
// before starting the rest of the Flipper process.
// This prevent issues where the render host is referred at module initialisation level,
// but not set yet, which might happen when using normal imports.
// eslint-disable-next-line import/no-commonjs
require('./startFlipperDesktop');