diff --git a/desktop/app/src/store.tsx b/desktop/app/src/store.tsx index 5b2792e79..b37ac8118 100644 --- a/desktop/app/src/store.tsx +++ b/desktop/app/src/store.tsx @@ -28,6 +28,7 @@ import {_SandyPluginDefinition} from 'flipper-plugin'; import BaseDevice from './devices/BaseDevice'; import {State as PluginStates} from './reducers/pluginStates'; import {ActivatablePluginDetails} from 'flipper-plugin-lib'; +import {unloadModule} from './utils/electronModuleCache'; export const store: Store = createStore( rootReducer, @@ -168,6 +169,11 @@ function updateClientPlugin( ]; }); cleanupPluginStates(draft.pluginStates, plugin.id); + const previousVersion = draft.plugins.clientPlugins.get(plugin.id); + if (previousVersion) { + // unload previous version from Electron cache + unloadPluginModule(previousVersion.details); + } // update plugin definition draft.plugins.clientPlugins.set(plugin.id, plugin); // start plugin for each client @@ -191,6 +197,7 @@ function uninstallPlugin(state: StoreState, plugin: PluginDefinition) { delete draft.pluginMessageQueue[pluginKey]; }); cleanupPluginStates(draft.pluginStates, plugin.id); + unloadPluginModule(plugin.details); draft.plugins.clientPlugins.delete(plugin.id); draft.plugins.devicePlugins.delete(plugin.id); draft.pluginManager.uninstalledPlugins.add(plugin.details.name); @@ -207,6 +214,11 @@ function updateDevicePlugin(state: StoreState, plugin: DevicePluginDefinition) { d.unloadDevicePlugin(plugin.id); }); cleanupPluginStates(draft.pluginStates, plugin.id); + const previousVersion = draft.plugins.devicePlugins.get(plugin.id); + if (previousVersion) { + // unload previous version from Electron cache + unloadPluginModule(previousVersion.details); + } draft.plugins.devicePlugins.set(plugin.id, plugin); devicesWithEnabledPlugin.forEach((d) => { d.loadDevicePlugin(plugin); @@ -245,3 +257,11 @@ function cleanupPluginStates(pluginStates: PluginStates, pluginId: string) { } }); } + +function unloadPluginModule(plugin: ActivatablePluginDetails) { + if (plugin.isBundled) { + // We cannot unload bundled plugin. + return; + } + unloadModule(plugin.entry); +} diff --git a/desktop/app/src/utils/electronModuleCache.tsx b/desktop/app/src/utils/electronModuleCache.tsx new file mode 100644 index 000000000..06a1c300f --- /dev/null +++ b/desktop/app/src/utils/electronModuleCache.tsx @@ -0,0 +1,16 @@ +/** + * 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. + * + * @format + */ + +export function unloadModule(path: string) { + const resolvedPath = global.electronRequire.resolve(path); + if (!resolvedPath || !global.electronRequire.cache[resolvedPath]) { + return; + } + delete global.electronRequire.cache[resolvedPath]; +} diff --git a/desktop/types/nodejs.d.ts b/desktop/types/nodejs.d.ts index 3a9f5bd41..80251e075 100644 --- a/desktop/types/nodejs.d.ts +++ b/desktop/types/nodejs.d.ts @@ -11,7 +11,11 @@ declare module NodeJS { interface Global { __REVISION__: string | undefined; __VERSION__: string; - electronRequire: (name: string) => any; + electronRequire: { + (name: string): any; + resolve: (module: string) => string; + cache: {[module: string]: any}; + }; window: Window | undefined; WebSocket: any; fetch: any;