Skip to content

Commit

Permalink
Transform _dependencyMap[n] into the corresponding integer to save space
Browse files Browse the repository at this point in the history
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
  • Loading branch information
Miguel Jimenez Esun authored and facebook-github-bot committed Oct 22, 2018
1 parent df212a4 commit e7deea1
Show file tree
Hide file tree
Showing 6 changed files with 161 additions and 42 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -89,7 +89,7 @@ it('creates a source map', () => {
return section;
}),
);
expect(map.x_facebook_offsets).toEqual([1, 2, 3, 4, 5, 6]);
expect(map.x_facebook_offsets).toEqual([1, 4, 7, 10, 13, 16]);
});

describe('Startup section optimization', () => {
Expand Down Expand Up @@ -136,7 +136,7 @@ describe('Startup section optimization', () => {
countLines(requireCall),
);

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

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

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

maps
.map((groupMap, i) => [groups[i], groupMap])
Expand Down Expand Up @@ -240,14 +240,14 @@ function makeModule(
function makeModuleMap(name, path) {
return {
version: 3,
mappings: Array(parseInt(name, 36) + 1).join(','),
names: [name],
mappings: '',
names: [],
sources: [path],
};
}

function makeModuleCode(moduleCode) {
return `__d(() => {${moduleCode}})`;
return `__d(() => {\n${moduleCode}\n})`;
}

function makeModulePath(name) {
Expand Down Expand Up @@ -310,7 +310,7 @@ function countLines(module) {
function lineByLineMap(file) {
return {
file,
mappings: 'AAAA;',
mappings: '',
names: [],
sources: [file],
version: 3,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,7 @@ it('creates a source map', () => {
return section;
}),
);
expect(map.x_facebook_offsets).toEqual([1, 2, 3, 4, 5, 6]);
expect(map.x_facebook_offsets).toEqual([1, 4, 7, 10, 13, 16]);
});

it('creates a magic file with the number', () => {
Expand Down Expand Up @@ -128,14 +128,14 @@ function makeModule(
function makeModuleMap(name, path) {
return {
version: 3,
mappings: Array(parseInt(name, 36) + 1).join(','),
names: [name],
mappings: '',
names: [],
sources: [path],
};
}

function makeModuleCode(moduleCode) {
return `__d(() => {${moduleCode}})`;
return `__d(() => {\n${moduleCode}\n})`;
}

function makeModulePath(name) {
Expand Down Expand Up @@ -170,7 +170,7 @@ function countLines(module) {
function lineByLineMap(file) {
return {
file,
mappings: 'AAAA;',
mappings: '',
names: [],
sources: [file],
version: 3,
Expand Down
55 changes: 45 additions & 10 deletions packages/metro/src/ModuleGraph/output/__tests__/util-test.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,22 +10,44 @@

'use strict';

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

const {any} = jasmine;

describe('`addModuleIdsToModuleWrapper`:', () => {
describe('`inlineModuleIds`:', () => {
const path = 'path/to/file';

const basicCode = `
__d(function(require, depMap) {
require(depMap[0]);
require(depMap[1]);
});
`;

const createModule = (dependencies = []) => ({
dependencies,
file: {code: '__d(function(){});', isModule: true, path},
file: {code: basicCode, isModule: true, path},
});

it('completes the module wrapped with module ID, and an array of dependency IDs', () => {
const reUsedVariableCode = `
__d(function(require, depMap) {
function anotherScope(depMap) {
return depMap++;
}
});
`;

const createReUsedVariableModule = (dependencies = []) => ({
dependencies,
file: {code: reUsedVariableCode, isModule: true, path},
});

it('inlines module ids', () => {
const dependencies = [
{id: 'a', path: 'path/to/a.js'},
{id: 'b', path: 'location/of/b.js'},
];

const module = createModule(dependencies);

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

expect(addModuleIdsToModuleWrapper(module, idForPath)).toEqual(
'__d(function(){},12,[345,6]);',
expect(inlineModuleIds(module, idForPath).moduleCode).toEqual(
[
'__d(function (require, depMap) {',
' require(345);',
'',
' require(6);',
'},12);',
].join('\n'),
);
});

it('omits the array of dependency IDs if it is empty', () => {
const module = createModule();
expect(addModuleIdsToModuleWrapper(module, () => 98)).toEqual(
`__d(function(){},${98});`,
it('avoids inlining if the variable is in a different scope', () => {
const module = createReUsedVariableModule();

expect(inlineModuleIds(module, () => 98).moduleCode).toEqual(
[
'__d(function (require, depMap) {',
' function anotherScope(depMap) {',
' return depMap++;',
' }',
'},98);',
].join('\n'),
);
});
});
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
/**
* 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.
*
* @flow strict-local
* @format
*/

'use strict';

import typeof {types as BabelTypes} from '@babel/core';
import type {Path} from '@babel/traverse';

type State = {|
opts: {|
+dependencyIds: $ReadOnlyArray<number>,
|},
|};

function reverseDependencyMapReferences({types: t}: {types: BabelTypes}) {
return {
visitor: {
CallExpression(path: Path, state: State) {
const {node} = path;

if (node.callee.name === '__d') {
const lastArg = node.arguments[0].params.slice(-1)[0];
const depMapName = lastArg && lastArg.name;

if (!depMapName) {
return;
}

const scope = path.get('arguments.0.body').scope;
const binding = scope.getBinding(depMapName);

binding.referencePaths.forEach(({parentPath}) => {
const memberNode = parentPath.node;

if (
memberNode.type === 'MemberExpression' &&
memberNode.property.type === 'NumericLiteral'
) {
parentPath.replaceWith(
t.numericLiteral(
state.opts.dependencyIds[memberNode.property.value],
),
);
}
});
}
},
},
};
}

module.exports = reverseDependencyMapReferences;
56 changes: 38 additions & 18 deletions packages/metro/src/ModuleGraph/output/util.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,9 +11,15 @@
'use strict';

const addParamsToDefineCall = require('../../lib/addParamsToDefineCall');
const generate = require('../worker/generate');
const mergeSourceMaps = require('../worker/mergeSourceMaps');
const reverseDependencyMapReferences = require('./reverse-dependency-map-references');
const virtualModule = require('../module').virtual;

const {transformSync} = require('@babel/core');

import type {IdsForPathFn, Module} from '../types.flow';
import type {MetroSourceMap} from 'metro-source-map';

// Transformed modules have the form
// __d(function(require, module, global, exports, dependencyMap) {
Expand All @@ -22,28 +28,45 @@ import type {IdsForPathFn, Module} from '../types.flow';
//
// This function adds the numeric module ID, and an array with dependencies of
// the dependencies of the module before the closing parenthesis.
function addModuleIdsToModuleWrapper(
function inlineModuleIds(
module: Module,
idForPath: ({path: string}) => number,
): string {
): {
moduleCode: string,
moduleMap: ?MetroSourceMap,
} {
const {dependencies, file} = module;
const {code} = file;
const {code, map, path} = file;

// calling `idForPath` on the module itself first gives us a lower module id
// for the file itself than for its dependencies. That reflects their order
// in the bundle.
const fileId = idForPath(file);
const dependencyIds = dependencies.map(idForPath);

const {ast} = transformSync(code, {
ast: true,
babelrc: false,
code: false,
configFile: false,
minified: false,
plugins: [[reverseDependencyMapReferences, {dependencyIds}]],
});

const {code: generatedCode, map: generatedMap} = generate(
ast,
path,
'',
false,
);

const paramsToAdd = [fileId];

if (dependencies.length) {
paramsToAdd.push(dependencies.map(idForPath));
}

return addParamsToDefineCall(code, ...paramsToAdd);
return {
moduleCode: addParamsToDefineCall(generatedCode, fileId),
moduleMap: map && generatedMap && mergeSourceMaps(path, map, generatedMap),
};
}

exports.addModuleIdsToModuleWrapper = addModuleIdsToModuleWrapper;
exports.inlineModuleIds = inlineModuleIds;

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

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

const moduleCode =
file.type === 'module'
? addModuleIdsToModuleWrapper(module, idForPath)
: file.code;

const moduleMap = file.map;
if (file.type !== 'module') {
return {moduleCode: file.code, moduleMap: file.map};
}

return {moduleCode, moduleMap};
return inlineModuleIds(module, idForPath);
}

exports.getModuleCodeAndMap = getModuleCodeAndMap;
Expand Down
7 changes: 6 additions & 1 deletion packages/metro/src/ModuleGraph/worker/mergeSourceMaps.js
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ function mergeSourceMaps(
): BabelSourceMap {
const merged = new sourceMap.SourceMapGenerator();
const inputMap = new sourceMap.SourceMapConsumer(originalMap);

new sourceMap.SourceMapConsumer(secondMap).eachMapping(mapping => {
const original = inputMap.originalPositionFor({
line: mapping.originalLine,
Expand All @@ -41,7 +42,11 @@ function mergeSourceMaps(
name: original.name || mapping.name,
});
});
return merged.toJSON();

return {
...merged.toJSON(),
sources: inputMap.sources,
};
}

module.exports = mergeSourceMaps;

0 comments on commit e7deea1

Please sign in to comment.