Skip to content

Commit e7deea1

Browse files
Miguel Jimenez Esunfacebook-github-bot
authored andcommitted
Transform _dependencyMap[n] into the corresponding integer to save space
Summary: Uses Babel to transform references to `_dependencyMap[n]` into the corresponding integer, thus saving space when generating the bundles. Reviewed By: davidaurelio Differential Revision: D10488230 fbshipit-source-id: 072a2273e0d67a6051f7a5ab0b2da68c24e59df3
1 parent df212a4 commit e7deea1

File tree

6 files changed

+161
-42
lines changed

6 files changed

+161
-42
lines changed

packages/metro/src/ModuleGraph/output/__tests__/indexed-ram-bundle-test.js

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -89,7 +89,7 @@ it('creates a source map', () => {
8989
return section;
9090
}),
9191
);
92-
expect(map.x_facebook_offsets).toEqual([1, 2, 3, 4, 5, 6]);
92+
expect(map.x_facebook_offsets).toEqual([1, 4, 7, 10, 13, 16]);
9393
});
9494

9595
describe('Startup section optimization', () => {
@@ -136,7 +136,7 @@ describe('Startup section optimization', () => {
136136
countLines(requireCall),
137137
);
138138

139-
expect(map.x_facebook_offsets).toEqual([4, 5, , , 6]); // eslint-disable-line no-sparse-arrays
139+
expect(map.x_facebook_offsets).toEqual([10, 13, undefined, undefined, 16]);
140140

141141
expect(map.sections.slice(1)).toEqual(
142142
modules.filter(not(Set.prototype.has), new Set(preloaded)).map(m => {
@@ -177,9 +177,9 @@ describe('RAM groups / common sections', () => {
177177
});
178178

179179
it('reflects section groups in the source map', () => {
180-
expect(map.x_facebook_offsets).toEqual([1, 2, 2, 5, 5, 2]);
180+
expect(map.x_facebook_offsets).toEqual([1, 4, 4, 13, 13, 4]);
181181
const maps = map.sections.slice(-2);
182-
const toplevelOffsets = [2, 5];
182+
const toplevelOffsets = [4, 13];
183183

184184
maps
185185
.map((groupMap, i) => [groups[i], groupMap])
@@ -240,14 +240,14 @@ function makeModule(
240240
function makeModuleMap(name, path) {
241241
return {
242242
version: 3,
243-
mappings: Array(parseInt(name, 36) + 1).join(','),
244-
names: [name],
243+
mappings: '',
244+
names: [],
245245
sources: [path],
246246
};
247247
}
248248

249249
function makeModuleCode(moduleCode) {
250-
return `__d(() => {${moduleCode}})`;
250+
return `__d(() => {\n${moduleCode}\n})`;
251251
}
252252

253253
function makeModulePath(name) {
@@ -310,7 +310,7 @@ function countLines(module) {
310310
function lineByLineMap(file) {
311311
return {
312312
file,
313-
mappings: 'AAAA;',
313+
mappings: '',
314314
names: [],
315315
sources: [file],
316316
version: 3,

packages/metro/src/ModuleGraph/output/__tests__/multiple-files-ram-bundle-test.js

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -67,7 +67,7 @@ it('creates a source map', () => {
6767
return section;
6868
}),
6969
);
70-
expect(map.x_facebook_offsets).toEqual([1, 2, 3, 4, 5, 6]);
70+
expect(map.x_facebook_offsets).toEqual([1, 4, 7, 10, 13, 16]);
7171
});
7272

7373
it('creates a magic file with the number', () => {
@@ -128,14 +128,14 @@ function makeModule(
128128
function makeModuleMap(name, path) {
129129
return {
130130
version: 3,
131-
mappings: Array(parseInt(name, 36) + 1).join(','),
132-
names: [name],
131+
mappings: '',
132+
names: [],
133133
sources: [path],
134134
};
135135
}
136136

137137
function makeModuleCode(moduleCode) {
138-
return `__d(() => {${moduleCode}})`;
138+
return `__d(() => {\n${moduleCode}\n})`;
139139
}
140140

141141
function makeModulePath(name) {
@@ -170,7 +170,7 @@ function countLines(module) {
170170
function lineByLineMap(file) {
171171
return {
172172
file,
173-
mappings: 'AAAA;',
173+
mappings: '',
174174
names: [],
175175
sources: [file],
176176
version: 3,

packages/metro/src/ModuleGraph/output/__tests__/util-test.js

Lines changed: 45 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -10,22 +10,44 @@
1010

1111
'use strict';
1212

13-
const {addModuleIdsToModuleWrapper, createIdForPathFn} = require('../util');
13+
const {inlineModuleIds, createIdForPathFn} = require('../util');
1414

1515
const {any} = jasmine;
1616

17-
describe('`addModuleIdsToModuleWrapper`:', () => {
17+
describe('`inlineModuleIds`:', () => {
1818
const path = 'path/to/file';
19+
20+
const basicCode = `
21+
__d(function(require, depMap) {
22+
require(depMap[0]);
23+
require(depMap[1]);
24+
});
25+
`;
26+
1927
const createModule = (dependencies = []) => ({
2028
dependencies,
21-
file: {code: '__d(function(){});', isModule: true, path},
29+
file: {code: basicCode, isModule: true, path},
2230
});
2331

24-
it('completes the module wrapped with module ID, and an array of dependency IDs', () => {
32+
const reUsedVariableCode = `
33+
__d(function(require, depMap) {
34+
function anotherScope(depMap) {
35+
return depMap++;
36+
}
37+
});
38+
`;
39+
40+
const createReUsedVariableModule = (dependencies = []) => ({
41+
dependencies,
42+
file: {code: reUsedVariableCode, isModule: true, path},
43+
});
44+
45+
it('inlines module ids', () => {
2546
const dependencies = [
2647
{id: 'a', path: 'path/to/a.js'},
2748
{id: 'b', path: 'location/of/b.js'},
2849
];
50+
2951
const module = createModule(dependencies);
3052

3153
const idForPath = jest.fn().mockImplementation(({path: inputPath}) => {
@@ -41,15 +63,28 @@ describe('`addModuleIdsToModuleWrapper`:', () => {
4163
throw new Error(`Unexpected path: ${inputPath}`);
4264
});
4365

44-
expect(addModuleIdsToModuleWrapper(module, idForPath)).toEqual(
45-
'__d(function(){},12,[345,6]);',
66+
expect(inlineModuleIds(module, idForPath).moduleCode).toEqual(
67+
[
68+
'__d(function (require, depMap) {',
69+
' require(345);',
70+
'',
71+
' require(6);',
72+
'},12);',
73+
].join('\n'),
4674
);
4775
});
4876

49-
it('omits the array of dependency IDs if it is empty', () => {
50-
const module = createModule();
51-
expect(addModuleIdsToModuleWrapper(module, () => 98)).toEqual(
52-
`__d(function(){},${98});`,
77+
it('avoids inlining if the variable is in a different scope', () => {
78+
const module = createReUsedVariableModule();
79+
80+
expect(inlineModuleIds(module, () => 98).moduleCode).toEqual(
81+
[
82+
'__d(function (require, depMap) {',
83+
' function anotherScope(depMap) {',
84+
' return depMap++;',
85+
' }',
86+
'},98);',
87+
].join('\n'),
5388
);
5489
});
5590
});
Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
/**
2+
* Copyright (c) Facebook, Inc. and its affiliates.
3+
*
4+
* This source code is licensed under the MIT license found in the
5+
* LICENSE file in the root directory of this source tree.
6+
*
7+
* @flow strict-local
8+
* @format
9+
*/
10+
11+
'use strict';
12+
13+
import typeof {types as BabelTypes} from '@babel/core';
14+
import type {Path} from '@babel/traverse';
15+
16+
type State = {|
17+
opts: {|
18+
+dependencyIds: $ReadOnlyArray<number>,
19+
|},
20+
|};
21+
22+
function reverseDependencyMapReferences({types: t}: {types: BabelTypes}) {
23+
return {
24+
visitor: {
25+
CallExpression(path: Path, state: State) {
26+
const {node} = path;
27+
28+
if (node.callee.name === '__d') {
29+
const lastArg = node.arguments[0].params.slice(-1)[0];
30+
const depMapName = lastArg && lastArg.name;
31+
32+
if (!depMapName) {
33+
return;
34+
}
35+
36+
const scope = path.get('arguments.0.body').scope;
37+
const binding = scope.getBinding(depMapName);
38+
39+
binding.referencePaths.forEach(({parentPath}) => {
40+
const memberNode = parentPath.node;
41+
42+
if (
43+
memberNode.type === 'MemberExpression' &&
44+
memberNode.property.type === 'NumericLiteral'
45+
) {
46+
parentPath.replaceWith(
47+
t.numericLiteral(
48+
state.opts.dependencyIds[memberNode.property.value],
49+
),
50+
);
51+
}
52+
});
53+
}
54+
},
55+
},
56+
};
57+
}
58+
59+
module.exports = reverseDependencyMapReferences;

packages/metro/src/ModuleGraph/output/util.js

Lines changed: 38 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -11,9 +11,15 @@
1111
'use strict';
1212

1313
const addParamsToDefineCall = require('../../lib/addParamsToDefineCall');
14+
const generate = require('../worker/generate');
15+
const mergeSourceMaps = require('../worker/mergeSourceMaps');
16+
const reverseDependencyMapReferences = require('./reverse-dependency-map-references');
1417
const virtualModule = require('../module').virtual;
1518

19+
const {transformSync} = require('@babel/core');
20+
1621
import type {IdsForPathFn, Module} from '../types.flow';
22+
import type {MetroSourceMap} from 'metro-source-map';
1723

1824
// Transformed modules have the form
1925
// __d(function(require, module, global, exports, dependencyMap) {
@@ -22,28 +28,45 @@ import type {IdsForPathFn, Module} from '../types.flow';
2228
//
2329
// This function adds the numeric module ID, and an array with dependencies of
2430
// the dependencies of the module before the closing parenthesis.
25-
function addModuleIdsToModuleWrapper(
31+
function inlineModuleIds(
2632
module: Module,
2733
idForPath: ({path: string}) => number,
28-
): string {
34+
): {
35+
moduleCode: string,
36+
moduleMap: ?MetroSourceMap,
37+
} {
2938
const {dependencies, file} = module;
30-
const {code} = file;
39+
const {code, map, path} = file;
3140

3241
// calling `idForPath` on the module itself first gives us a lower module id
3342
// for the file itself than for its dependencies. That reflects their order
3443
// in the bundle.
3544
const fileId = idForPath(file);
45+
const dependencyIds = dependencies.map(idForPath);
46+
47+
const {ast} = transformSync(code, {
48+
ast: true,
49+
babelrc: false,
50+
code: false,
51+
configFile: false,
52+
minified: false,
53+
plugins: [[reverseDependencyMapReferences, {dependencyIds}]],
54+
});
55+
56+
const {code: generatedCode, map: generatedMap} = generate(
57+
ast,
58+
path,
59+
'',
60+
false,
61+
);
3662

37-
const paramsToAdd = [fileId];
38-
39-
if (dependencies.length) {
40-
paramsToAdd.push(dependencies.map(idForPath));
41-
}
42-
43-
return addParamsToDefineCall(code, ...paramsToAdd);
63+
return {
64+
moduleCode: addParamsToDefineCall(generatedCode, fileId),
65+
moduleMap: map && generatedMap && mergeSourceMaps(path, map, generatedMap),
66+
};
4467
}
4568

46-
exports.addModuleIdsToModuleWrapper = addModuleIdsToModuleWrapper;
69+
exports.inlineModuleIds = inlineModuleIds;
4770

4871
type IdForPathFn = ({path: string}) => number;
4972

@@ -52,14 +75,11 @@ type IdForPathFn = ({path: string}) => number;
5275
function getModuleCodeAndMap(module: Module, idForPath: IdForPathFn) {
5376
const {file} = module;
5477

55-
const moduleCode =
56-
file.type === 'module'
57-
? addModuleIdsToModuleWrapper(module, idForPath)
58-
: file.code;
59-
60-
const moduleMap = file.map;
78+
if (file.type !== 'module') {
79+
return {moduleCode: file.code, moduleMap: file.map};
80+
}
6181

62-
return {moduleCode, moduleMap};
82+
return inlineModuleIds(module, idForPath);
6383
}
6484

6585
exports.getModuleCodeAndMap = getModuleCodeAndMap;

packages/metro/src/ModuleGraph/worker/mergeSourceMaps.js

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ function mergeSourceMaps(
2525
): BabelSourceMap {
2626
const merged = new sourceMap.SourceMapGenerator();
2727
const inputMap = new sourceMap.SourceMapConsumer(originalMap);
28+
2829
new sourceMap.SourceMapConsumer(secondMap).eachMapping(mapping => {
2930
const original = inputMap.originalPositionFor({
3031
line: mapping.originalLine,
@@ -41,7 +42,11 @@ function mergeSourceMaps(
4142
name: original.name || mapping.name,
4243
});
4344
});
44-
return merged.toJSON();
45+
46+
return {
47+
...merged.toJSON(),
48+
sources: inputMap.sources,
49+
};
4550
}
4651

4752
module.exports = mergeSourceMaps;

0 commit comments

Comments
 (0)