From fdcb3f5e454c9bb9ff0ec29fd183a3e1271ee5b3 Mon Sep 17 00:00:00 2001 From: Rob Hogan Date: Wed, 12 Aug 2026 08:32:33 -0700 Subject: [PATCH] Don't emit redundant empty arrays for modules with no dependencies (#1858) Summary: ## Why? This is a small optimisation for runtime speed and bundle size that avoids allocating an empty array for an argument that is not referenced by the function (module) body. Metro-Buck already does this. This diff aligns Metro core and dev, which unblocks unforking the serialisers without introducing a regression. ## Why this is non-breaking By construction, an empty dependency map means the module has no collected dependencies, so the module body has no references to the dependency map *injected by Metro*. We must still ensure there are no references not injected by Metro. Two cases: - `transformer.unstable_dependencyMapReservedName` is set - in this case we enforce that there are no occurrences of the name (as a string) in the module body at all. - Above is not set (default). In that case we use `generateUid` when wrapping the module to ensure we pick a name that is not already bound (and note, this is after any user/framework-supplied transforms) In either case, the module body has no references to the argument. One caveat to mention is that the argument is technically accessible using the `arguments` array-like. This is definitely not considered part of the stable API of Metro. ## Note: Bundle analysers A category of potentially-affected consumers are those which statically analyse the bundle and might expect an array at the dependency map position. Even here, we should be safe because we already have certain module types with a simpler module wrapper - e.g. `wrapJson` never emits a dependency map. That said, heads up byCedric / Atlas. Changelog: Internal Reviewed By: huntie Differential Revision: D113660291 --- .../getRamBundleInfo-test.js.snap | 16 +++++----- .../__tests__/baseJSBundle-test.js | 8 ++--- .../Serializers/__tests__/hmrJSBundle-test.js | 4 +-- .../DeltaBundler/Serializers/helpers/js.js | 29 ++++++++++++------- .../metro/src/Server/__tests__/Server-test.js | 10 +++---- .../metro/src/__tests__/HmrServer-test.js | 6 ++-- scripts/__tests__/api-snapshots-sync-test.js | 2 +- 7 files changed, 42 insertions(+), 33 deletions(-) diff --git a/packages/metro/src/DeltaBundler/Serializers/__tests__/__snapshots__/getRamBundleInfo-test.js.snap b/packages/metro/src/DeltaBundler/Serializers/__tests__/__snapshots__/getRamBundleInfo-test.js.snap index 03fdfc2d87..8b64b26da5 100644 --- a/packages/metro/src/DeltaBundler/Serializers/__tests__/__snapshots__/getRamBundleInfo-test.js.snap +++ b/packages/metro/src/DeltaBundler/Serializers/__tests__/__snapshots__/getRamBundleInfo-test.js.snap @@ -74,7 +74,7 @@ Object { "type": "js/module", }, Object { - "code": "__d(function() {foo2()},\\"/root/foo2.js\\",[],\\"foo2.js\\");", + "code": "__d(function() {foo2()},\\"/root/foo2.js\\",null,\\"foo2.js\\");", "id": "/root/foo2.js", "map": Object { "sections": Array [ @@ -142,7 +142,7 @@ Object { "type": "js/module", }, Object { - "code": "__d(function() {baz()},\\"/root/baz.js\\",[],\\"baz.js\\");", + "code": "__d(function() {baz()},\\"/root/baz.js\\",null,\\"baz.js\\");", "id": "/root/baz.js", "map": Object { "sections": Array [ @@ -176,7 +176,7 @@ Object { "type": "js/module/asset", }, Object { - "code": "__d(function() {bar()},\\"/root/bar.js\\",[],\\"bar.js\\");", + "code": "__d(function() {bar()},\\"/root/bar.js\\",null,\\"bar.js\\");", "id": "/root/bar.js", "map": Object { "sections": Array [ @@ -210,7 +210,7 @@ Object { "type": "js/module", }, Object { - "code": "__d(function() {qux()},\\"/root/qux.js\\",[],\\"qux.js\\");", + "code": "__d(function() {qux()},\\"/root/qux.js\\",null,\\"qux.js\\");", "id": "/root/qux.js", "map": Object { "sections": Array [ @@ -419,7 +419,7 @@ Object { "type": "js/module", }, Object { - "code": "__d(function() {foo2()},\\"/root/foo2.js\\",[],\\"foo2.js\\");", + "code": "__d(function() {foo2()},\\"/root/foo2.js\\",null,\\"foo2.js\\");", "id": "/root/foo2.js", "map": Object { "sections": Array [ @@ -481,7 +481,7 @@ Object { "type": "js/module", }, Object { - "code": "__d(function() {baz()},\\"/root/baz.js\\",[],\\"baz.js\\");", + "code": "__d(function() {baz()},\\"/root/baz.js\\",null,\\"baz.js\\");", "id": "/root/baz.js", "map": Object { "sections": Array [ @@ -512,7 +512,7 @@ Object { "type": "js/module/asset", }, Object { - "code": "__d(function() {bar()},\\"/root/bar.js\\",[],\\"bar.js\\");", + "code": "__d(function() {bar()},\\"/root/bar.js\\",null,\\"bar.js\\");", "id": "/root/bar.js", "map": Object { "sections": Array [ @@ -543,7 +543,7 @@ Object { "type": "js/module", }, Object { - "code": "__d(function() {qux()},\\"/root/qux.js\\",[],\\"qux.js\\");", + "code": "__d(function() {qux()},\\"/root/qux.js\\",null,\\"qux.js\\");", "id": "/root/qux.js", "map": Object { "sections": Array [ diff --git a/packages/metro/src/DeltaBundler/Serializers/__tests__/baseJSBundle-test.js b/packages/metro/src/DeltaBundler/Serializers/__tests__/baseJSBundle-test.js index 4a7d9a7ed4..44362481b1 100644 --- a/packages/metro/src/DeltaBundler/Serializers/__tests__/baseJSBundle-test.js +++ b/packages/metro/src/DeltaBundler/Serializers/__tests__/baseJSBundle-test.js @@ -155,7 +155,7 @@ test('should generate a very simple bundle', () => { ], Array [ "bar", - "__d(function() {/* code for bar */},\\"bar\\",[],\\"bar\\");", + "__d(function() {/* code for bar */},\\"bar\\",null,\\"bar\\");", ], ], "post": "require(\\"foo\\"); @@ -215,7 +215,7 @@ Object { "modules": Array [ Array [ "#.js", - "__d(function() {/* code for ascii file with non ascii characters: %30.бундл.Øಚ😁AA */},\\"#.js\\",[],\\"%30.бундл.Øಚ😁AA/src/?/foo=bar/#.js\\");", + "__d(function() {/* code for ascii file with non ascii characters: %30.бундл.Øಚ😁AA */},\\"#.js\\",null,\\"%30.бундл.Øಚ😁AA/src/?/foo=bar/#.js\\");", ], ], "post": "//# sourceMappingURL=http://localhost/root/%2530.%D0%B1%D1%83%D0%BD%D0%B4%D0%BB.%C3%98%E0%B2%9A%F0%9F%98%81AA/src/%3F/foo%3Dbar/%23.map @@ -307,7 +307,7 @@ test('should handle numeric module ids', () => { ], Array [ 1, - "__d(function() {/* code for bar */},1,[],\\"bar\\");", + "__d(function() {/* code for bar */},1,null,\\"bar\\");", ], ] `); @@ -540,7 +540,7 @@ test('does not add polyfills when `modulesOnly` is used', () => { ], Array [ "bar", - "__d(function() {/* code for bar */},\\"bar\\",[],\\"bar\\");", + "__d(function() {/* code for bar */},\\"bar\\",null,\\"bar\\");", ], ], "post": "require(\\"foo\\"); diff --git a/packages/metro/src/DeltaBundler/Serializers/__tests__/hmrJSBundle-test.js b/packages/metro/src/DeltaBundler/Serializers/__tests__/hmrJSBundle-test.js index 3e34298baa..17f9f52b8f 100644 --- a/packages/metro/src/DeltaBundler/Serializers/__tests__/hmrJSBundle-test.js +++ b/packages/metro/src/DeltaBundler/Serializers/__tests__/hmrJSBundle-test.js @@ -137,7 +137,7 @@ Object { Object { "module": Array [ 0, - "__d(function() {/* code for bar */},0,[],\\"bar\\",{}); + "__d(function() {/* code for bar */},0,null,\\"bar\\",{}); //# sourceMappingURL=http://localhost/bar.map //# sourceURL=http://localhost/bar.bundle ", @@ -172,7 +172,7 @@ Object { Object { "module": Array [ 2, - "__d(function() {/* code for ascii file with non ascii characters: %30.бундл.Øಚ😁AA */},2,[],\\"%30.бундл.Øಚ😁AA/src/?/foo=bar/#.js\\",{}); + "__d(function() {/* code for ascii file with non ascii characters: %30.бундл.Øಚ😁AA */},2,null,\\"%30.бундл.Øಚ😁AA/src/?/foo=bar/#.js\\",{}); //# sourceMappingURL=http://localhost/%2530.%D0%B1%D1%83%D0%BD%D0%B4%D0%BB.%C3%98%E0%B2%9A%F0%9F%98%81AA/src/%3F/foo%3Dbar/%23.map //# sourceURL=http://localhost/%2530.%D0%B1%D1%83%D0%BD%D0%B4%D0%BB.%C3%98%E0%B2%9A%F0%9F%98%81AA/src/%3F/foo%3Dbar/%23.bundle ", diff --git a/packages/metro/src/DeltaBundler/Serializers/helpers/js.js b/packages/metro/src/DeltaBundler/Serializers/helpers/js.js index e2a0a5db2d..4e9ae64e60 100644 --- a/packages/metro/src/DeltaBundler/Serializers/helpers/js.js +++ b/packages/metro/src/DeltaBundler/Serializers/helpers/js.js @@ -165,16 +165,25 @@ export function getModuleParams( options, ); - const params = [ - moduleId, - hasPaths - ? { - // $FlowFixMe[not-an-object] Intentionally spreading an array into an object - ...dependencyMapArray, - paths, - } - : dependencyMapArray, - ]; + const params: Array = [moduleId]; + + // As an optimisation: we only emit an argument for the dependency map if + // it is non-empty. This is safe because either `dependencyMapReservedName` + // is set and we have enforced there are no string occurences of that name, + // or we have used `generateUid` to generate an unbound name for it. + if (hasPaths) { + params.push({ + // $FlowFixMe[not-an-object] Intentionally spreading an array into an object + ...dependencyMapArray, + paths, + }); + } else if (dependencyMapArray.length > 0) { + params.push(dependencyMapArray); + } else if (options.dev) { + // In dev we have one more argument (below), so emit a null to preserve its + // position. + params.push(null); + } if (options.dev) { params.push(getModuleVerboseName(module, options)); diff --git a/packages/metro/src/Server/__tests__/Server-test.js b/packages/metro/src/Server/__tests__/Server-test.js index b92318109e..838170548f 100644 --- a/packages/metro/src/Server/__tests__/Server-test.js +++ b/packages/metro/src/Server/__tests__/Server-test.js @@ -346,7 +346,7 @@ describe('processRequest', () => { [ 'function () {require();}', '__d(function() {entry();},0,[1],"mybundle.js");', - '__d(function() {foo();},1,[],"foo.js");', + '__d(function() {foo();},1,null,"foo.js");', 'require(0);', '//# sourceMappingURL=http://localhost:8081/mybundle.map?runModule=true', '//# sourceURL=http://localhost:8081/mybundle.bundle//&runModule=true', @@ -362,7 +362,7 @@ describe('processRequest', () => { [ 'function () {require();}', '__d(function() {entry();},0,[1],"mybundle.js");', - '__d(function() {foo();},1,[],"foo.js");', + '__d(function() {foo();},1,null,"foo.js");', '//# sourceMappingURL=http://localhost:8081/mybundle.map?runModule=false', '//# sourceURL=http://localhost:8081/mybundle.bundle//&runModule=false', ].join('\n'), @@ -472,7 +472,7 @@ describe('processRequest', () => { expect(response._getString()).toEqual( [ '__d(function() {entry();},0,[1],"mybundle.js");', - '__d(function() {foo();},1,[],"foo.js");', + '__d(function() {foo();},1,null,"foo.js");', '//# sourceMappingURL=http://localhost:8081/mybundle.map?modulesOnly=true&runModule=false', '//# sourceURL=http://localhost:8081/mybundle.bundle//&modulesOnly=true&runModule=false', ].join('\n'), @@ -493,7 +493,7 @@ describe('processRequest', () => { expect(response._getString()).toEqual( [ '__d(function() {entry();},0,[1],"mybundle.js");', - '__d(function() {foo();},1,[],"foo.js");', + '__d(function() {foo();},1,null,"foo.js");', '//# sourceMappingURL=https://forwardedhost.com/mybundle.map?modulesOnly=true&runModule=false&platform=vr', '//# sourceURL=https://forwardedhost.com/mybundle.bundle//&modulesOnly=true&runModule=false&platform=vr', ].join('\n'), @@ -842,7 +842,7 @@ describe('processRequest', () => { [ 'function () {require();}', '__d(function() {entry();},0,[1],"mybundle.js");', - '__d(function() {foo();},1,[],"foo.js");', + '__d(function() {foo();},1,null,"foo.js");', 'require(0);', '//# sourceMappingURL=http://localhost:8081/mybundle.map?runModule=true&TEST_URL_WAS_REWRITTEN=true', '//# sourceURL=http://localhost:8081/mybundle.bundle//&runModule=true&TEST_URL_WAS_REWRITTEN=true', diff --git a/packages/metro/src/__tests__/HmrServer-test.js b/packages/metro/src/__tests__/HmrServer-test.js index 7d0018a003..a6448084e2 100644 --- a/packages/metro/src/__tests__/HmrServer-test.js +++ b/packages/metro/src/__tests__/HmrServer-test.js @@ -388,7 +388,7 @@ describe('HmrServer', () => { id('/root/hi'), '__d(function() { alert("hi"); },' + id('/root/hi') + - ',[],"hi",{});\n' + + ',null,"hi",{});\n' + '//# sourceMappingURL=http://localhost/hi.map?platform=ios&dev=true&minify=false&modulesOnly=true&runModule=false&shallow=true\n' + '//# sourceURL=http://localhost/hi.bundle//&platform=ios&dev=true&minify=false&modulesOnly=true&runModule=false&shallow=true\n', ], @@ -472,7 +472,7 @@ describe('HmrServer', () => { id('/root/hi'), '__d(function() { alert("hi"); },' + id('/root/hi') + - ',[],"hi",{});\n' + + ',null,"hi",{});\n' + '//# sourceMappingURL=http://localhost/hi.map?platform=ios&dev=true&minify=false&modulesOnly=true&runModule=false&shallow=true\n' + '//# sourceURL=http://localhost/hi.bundle//&platform=ios&dev=true&minify=false&modulesOnly=true&runModule=false&shallow=true\n', ], @@ -531,7 +531,7 @@ describe('HmrServer', () => { id('/root/hi'), '__d(function() { alert("hi"); },' + id('/root/hi') + - ',[],"hi",{});\n' + + ',null,"hi",{});\n' + '//# sourceMappingURL=http://localhost/hi.map?platform=ios&dev=true&minify=false&modulesOnly=true&runModule=false&shallow=true\n' + '//# sourceURL=http://localhost/hi.bundle//&platform=ios&dev=true&minify=false&modulesOnly=true&runModule=false&shallow=true\n', ], diff --git a/scripts/__tests__/api-snapshots-sync-test.js b/scripts/__tests__/api-snapshots-sync-test.js index 84fcc484d3..c32c11905e 100644 --- a/scripts/__tests__/api-snapshots-sync-test.js +++ b/scripts/__tests__/api-snapshots-sync-test.js @@ -25,4 +25,4 @@ test('Public API snapshots are in sync (yarn run build-api-snapshots produces no expect(error.errors).toEqual([]); } expect(error).toBeUndefined(); -}, 120000); +}, 180000);