Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix: Avoid unnecessary memory leaks due to prevExports #766

Merged
merged 1 commit into from
Aug 14, 2023
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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,
});