Provide external modules to plugins
Summary: esbuild references external modules via `require`. We wrap `require` to point the references to built-in modules to global variables Reviewed By: lblasa Differential Revision: D39311893 fbshipit-source-id: a99480161c082f4095d78c22271f114532f32c16
This commit is contained in:
committed by
Facebook GitHub Bot
parent
650ff4bcfb
commit
094c5bdfdd
@@ -23,6 +23,7 @@ export {
|
|||||||
export * from './server-types';
|
export * from './server-types';
|
||||||
export * from './companion-types';
|
export * from './companion-types';
|
||||||
export * from './ServerAddOn';
|
export * from './ServerAddOn';
|
||||||
|
export * from './plugin-external-modules';
|
||||||
export {sleep} from './utils/sleep';
|
export {sleep} from './utils/sleep';
|
||||||
export {timeout} from './utils/timeout';
|
export {timeout} from './utils/timeout';
|
||||||
export {isTest} from './utils/isTest';
|
export {isTest} from './utils/isTest';
|
||||||
|
|||||||
38
desktop/flipper-common/src/plugin-external-modules.tsx
Normal file
38
desktop/flipper-common/src/plugin-external-modules.tsx
Normal file
@@ -0,0 +1,38 @@
|
|||||||
|
/**
|
||||||
|
* 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
|
||||||
|
*/
|
||||||
|
|
||||||
|
// This list should match `dispatcher/plugins.tsx` and `builtInModules` in `desktop/.eslintrc.js`
|
||||||
|
export const pluginExternalModules = {
|
||||||
|
flipper: 'Flipper',
|
||||||
|
'flipper-plugin': 'FlipperPlugin',
|
||||||
|
react: 'React',
|
||||||
|
'react-dom': 'ReactDOM',
|
||||||
|
'react-dom/client': 'ReactDOMClient',
|
||||||
|
'react-is': 'ReactIs',
|
||||||
|
antd: 'antd',
|
||||||
|
immer: 'Immer',
|
||||||
|
'@emotion/styled': 'emotion_styled',
|
||||||
|
'@ant-design/icons': 'antdesign_icons',
|
||||||
|
};
|
||||||
|
|
||||||
|
export const wrapRequire = <T extends (path: string) => any>(require: T): T =>
|
||||||
|
new Proxy(require, {
|
||||||
|
apply(target, thisArg, argumentsList) {
|
||||||
|
const moduleName = argumentsList[0];
|
||||||
|
const replacementName = (
|
||||||
|
pluginExternalModules as Record<string, string | undefined>
|
||||||
|
)[moduleName];
|
||||||
|
|
||||||
|
if (replacementName && replacementName in globalThis) {
|
||||||
|
return (globalThis as any)[replacementName];
|
||||||
|
}
|
||||||
|
|
||||||
|
return Reflect.apply(target, thisArg, argumentsList);
|
||||||
|
},
|
||||||
|
});
|
||||||
@@ -7,7 +7,7 @@
|
|||||||
* @format
|
* @format
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import {FlipperServer, getLogger} from 'flipper-common';
|
import {FlipperServer, getLogger, wrapRequire} from 'flipper-common';
|
||||||
import {getRenderHostInstance, setGlobalObject} from 'flipper-frontend-core';
|
import {getRenderHostInstance, setGlobalObject} from 'flipper-frontend-core';
|
||||||
import * as FlipperPluginSDK from 'flipper-plugin-core';
|
import * as FlipperPluginSDK from 'flipper-plugin-core';
|
||||||
import * as Immer from 'immer';
|
import * as Immer from 'immer';
|
||||||
@@ -18,6 +18,7 @@ import * as React from './globalsReplacements/fakeReact';
|
|||||||
import * as ReactDOM from './globalsReplacements/fakeReactDOM';
|
import * as ReactDOM from './globalsReplacements/fakeReactDOM';
|
||||||
import {styled} from './globalsReplacements/fakeEmotionStyled';
|
import {styled} from './globalsReplacements/fakeEmotionStyled';
|
||||||
import * as legacyExports from './globalsReplacements/fakeLegacyExports';
|
import * as legacyExports from './globalsReplacements/fakeLegacyExports';
|
||||||
|
import Module from 'module';
|
||||||
|
|
||||||
export interface FlipperServerCompanionEnv {
|
export interface FlipperServerCompanionEnv {
|
||||||
pluginInitializer: HeadlessPluginInitializer;
|
pluginInitializer: HeadlessPluginInitializer;
|
||||||
@@ -39,6 +40,7 @@ export const initCompanionEnv = async (
|
|||||||
emotion_styled: {default: styled},
|
emotion_styled: {default: styled},
|
||||||
antdesign_icons: {},
|
antdesign_icons: {},
|
||||||
});
|
});
|
||||||
|
Module.prototype.require = wrapRequire(Module.prototype.require);
|
||||||
|
|
||||||
const flipperServerConfig = await flipperServer.exec('get-config');
|
const flipperServerConfig = await flipperServer.exec('get-config');
|
||||||
initializeRenderHost(flipperServer, flipperServerConfig);
|
initializeRenderHost(flipperServer, flipperServerConfig);
|
||||||
|
|||||||
@@ -12,6 +12,7 @@ import {
|
|||||||
InstalledPluginDetails,
|
InstalledPluginDetails,
|
||||||
Logger,
|
Logger,
|
||||||
MarketplacePluginDetails,
|
MarketplacePluginDetails,
|
||||||
|
wrapRequire,
|
||||||
} from 'flipper-common';
|
} from 'flipper-common';
|
||||||
import {PluginDefinition} from '../plugin';
|
import {PluginDefinition} from '../plugin';
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
@@ -100,6 +101,10 @@ class UIPluginInitializer extends AbstractPluginInitializer {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
declare module globalThis {
|
||||||
|
let require: any;
|
||||||
|
}
|
||||||
|
|
||||||
let uiPluginInitializer: UIPluginInitializer;
|
let uiPluginInitializer: UIPluginInitializer;
|
||||||
export default async (store: Store, _logger: Logger) => {
|
export default async (store: Store, _logger: Logger) => {
|
||||||
setGlobalObject({
|
setGlobalObject({
|
||||||
@@ -114,6 +119,20 @@ export default async (store: Store, _logger: Logger) => {
|
|||||||
emotion_styled,
|
emotion_styled,
|
||||||
antdesign_icons,
|
antdesign_icons,
|
||||||
});
|
});
|
||||||
|
// Whenever we bundle plugins, we assume that they are going to share some modules - React, React-DOM, ant design and etc.
|
||||||
|
// It allows us to decrease the bundle size and not to create separate React roots for every plugin
|
||||||
|
// To tell a plugin that a module is going to be provided externally, we add the module to the list of externals (see https://esbuild.github.io/api/#external).
|
||||||
|
// As a result, esbuild does not bundle hte contents of the module. Instead, it wraps the module name with `require(...)`.
|
||||||
|
// `require` does not exist ion the browser environment, so we substitute it here to feed the plugin our global module.
|
||||||
|
globalThis.require = wrapRequire(
|
||||||
|
// globalThis.require might exist in the electron build
|
||||||
|
globalThis.require ??
|
||||||
|
((module: string) => {
|
||||||
|
throw new Error(
|
||||||
|
`Dynamic require is not supported in browser envs. Tried to require: ${module}`,
|
||||||
|
);
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
|
||||||
uiPluginInitializer = new UIPluginInitializer(store);
|
uiPluginInitializer = new UIPluginInitializer(store);
|
||||||
await uiPluginInitializer.init();
|
await uiPluginInitializer.init();
|
||||||
|
|||||||
@@ -29,8 +29,8 @@ async function runBuild({pluginDir, entry, out, dev, node}: RunBuildConfig) {
|
|||||||
format: 'cjs',
|
format: 'cjs',
|
||||||
// This list should match `dispatcher/plugins.tsx` and `builtInModules` in `desktop/.eslintrc.js`
|
// This list should match `dispatcher/plugins.tsx` and `builtInModules` in `desktop/.eslintrc.js`
|
||||||
external: [
|
external: [
|
||||||
'flipper-plugin',
|
|
||||||
'flipper',
|
'flipper',
|
||||||
|
'flipper-plugin',
|
||||||
'react',
|
'react',
|
||||||
'react-dom',
|
'react-dom',
|
||||||
'react-dom/client',
|
'react-dom/client',
|
||||||
|
|||||||
Reference in New Issue
Block a user