Skip to content

Commit

Permalink
fix: Avoid unnecessary memory leaks due to prevExports (#766)
Browse files Browse the repository at this point in the history
  • Loading branch information
naruaway committed Aug 14, 2023
1 parent c8382ad commit 0ea5af1
Showing 1 changed file with 28 additions and 13 deletions.
41 changes: 28 additions & 13 deletions lib/runtime/RefreshUtils.js
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,21 @@ function getReactRefreshBoundarySignature(moduleExports) {
return signature;
}

/**
* Creates a data object to be retained across refreshes.
* This object should not transtively reference previous exports,
* which can form infinite chain of objects across refreshes, which can pressure RAM.
*
* @param {*} moduleExports A Webpack module exports object.
* @returns {*} A React refresh boundary signature array.
*/
function getWebpackHotData(moduleExports) {
return {
signature: getReactRefreshBoundarySignature(moduleExports),
isReactRefreshBoundary: isReactRefreshBoundary(moduleExports),
};
}

/**
* Creates a helper that performs a delayed React refresh.
* @returns {function(function(): void): void} A debounced React refresh function.
Expand Down Expand Up @@ -167,14 +182,11 @@ function registerExportsForReactRefresh(moduleExports, moduleId) {
* Compares previous and next module objects to check for mutated boundaries.
*
* This implementation is based on the one in [Metro](https://github.com/facebook/metro/blob/907d6af22ac6ebe58572be418e9253a90665ecbd/packages/metro/src/lib/polyfills/require.js#L776-L792).
* @param {*} prevExports The current Webpack module exports object.
* @param {*} nextExports The next Webpack module exports object.
* @param {*} prevSignature The signature of the current Webpack module exports object.
* @param {*} nextSignature The signature of the next Webpack module exports object.
* @returns {boolean} Whether the React refresh boundary should be invalidated.
*/
function shouldInvalidateReactRefreshBoundary(prevExports, nextExports) {
var prevSignature = getReactRefreshBoundarySignature(prevExports);
var nextSignature = getReactRefreshBoundarySignature(nextExports);

function shouldInvalidateReactRefreshBoundary(prevSignature, nextSignature) {
if (prevSignature.length !== nextSignature.length) {
return true;
}
Expand All @@ -194,9 +206,9 @@ function executeRuntime(moduleExports, moduleId, webpackHot, refreshOverlay, isT

if (webpackHot) {
var isHotUpdate = !!webpackHot.data;
var prevExports;
var prevData;
if (isHotUpdate) {
prevExports = webpackHot.data.prevExports;
prevData = webpackHot.data.prevData;
}

if (isReactRefreshBoundary(moduleExports)) {
Expand All @@ -209,7 +221,7 @@ function executeRuntime(moduleExports, moduleId, webpackHot, refreshOverlay, isT
*/
function hotDisposeCallback(data) {
// We have to mutate the data object to get data registered and cached
data.prevExports = moduleExports;
data.prevData = getWebpackHotData(moduleExports);
}
);
webpackHot.accept(
Expand All @@ -235,8 +247,12 @@ function executeRuntime(moduleExports, moduleId, webpackHot, refreshOverlay, isT

if (isHotUpdate) {
if (
isReactRefreshBoundary(prevExports) &&
shouldInvalidateReactRefreshBoundary(prevExports, moduleExports)
prevData &&
prevData.isReactRefreshBoundary &&
shouldInvalidateReactRefreshBoundary(
prevData.signature,
getReactRefreshBoundarySignature(moduleExports)
)
) {
webpackHot.invalidate();
} else {
Expand All @@ -254,7 +270,7 @@ function executeRuntime(moduleExports, moduleId, webpackHot, refreshOverlay, isT
}
}
} else {
if (isHotUpdate && typeof prevExports !== 'undefined') {
if (isHotUpdate && typeof prevData !== 'undefined') {
webpackHot.invalidate();
}
}
Expand All @@ -266,6 +282,5 @@ module.exports = Object.freeze({
executeRuntime: executeRuntime,
getModuleExports: getModuleExports,
isReactRefreshBoundary: isReactRefreshBoundary,
shouldInvalidateReactRefreshBoundary: shouldInvalidateReactRefreshBoundary,
registerExportsForReactRefresh: registerExportsForReactRefresh,
});

0 comments on commit 0ea5af1

Please sign in to comment.