metro-bundler: ModuleGraph worker: @format

Reviewed By: davidaurelio

Differential Revision: D5658384

fbshipit-source-id: 918017a117e5fb574b3f6801104b4db2708a0eff
This commit is contained in:
Jean Lauliac 2017-08-21 04:23:15 -07:00 committed by Facebook Github Bot
parent 72455941ba
commit 6a0efc0853
9 changed files with 182 additions and 109 deletions

View File

@ -6,11 +6,14 @@
* LICENSE file in the root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*
* @format
* @flow
*/
'use strict';
/* eslint-disable no-unclear-flowtypes */
const babel = require('babel-core');
const MODULE_FACTORY_PARAMETERS = ['global', 'require', 'module', 'exports'];
@ -26,12 +29,18 @@ function wrapModule(fileAst: Object, dependencyMapName: string): Object {
function wrapPolyfill(fileAst: Object): Object {
const t = babel.types;
const factory = functionFromProgram(fileAst.program, POLYFILL_FACTORY_PARAMETERS);
const factory = functionFromProgram(
fileAst.program,
POLYFILL_FACTORY_PARAMETERS,
);
const iife = t.callExpression(factory, [t.identifier('this')]);
return t.file(t.program([t.expressionStatement(iife)]));
}
function functionFromProgram(program: Object, parameters: Array<string>): Object {
function functionFromProgram(
program: Object,
parameters: Array<string>,
): Object {
const t = babel.types;
return t.functionExpression(
t.identifier(''),

View File

@ -5,7 +5,11 @@
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*
* @format
* @emails oncall+javascript_tools
*/
'use strict';
const collectDependencies = require('../collect-dependencies');
@ -24,46 +28,43 @@ describe('dependency collection from ASTs:', () => {
}
`);
expect(collectDependencies(ast).dependencies)
.toEqual(['b/lib/a', 'do', 'setup/something']);
expect(collectDependencies(ast).dependencies).toEqual([
'b/lib/a',
'do',
'setup/something',
]);
});
it('supports template literals as arguments', () => {
const ast = astFromCode('require(`left-pad`)');
expect(collectDependencies(ast).dependencies)
.toEqual(['left-pad']);
expect(collectDependencies(ast).dependencies).toEqual(['left-pad']);
});
it('ignores template literals with interpolations', () => {
const ast = astFromCode('require(`left${"-"}pad`)');
expect(collectDependencies(ast).dependencies)
.toEqual([]);
expect(collectDependencies(ast).dependencies).toEqual([]);
});
it('ignores tagged template literals', () => {
const ast = astFromCode('require(tag`left-pad`)');
expect(collectDependencies(ast).dependencies)
.toEqual([]);
expect(collectDependencies(ast).dependencies).toEqual([]);
});
it('exposes a string as `dependencyMapName`', () => {
const ast = astFromCode('require("arbitrary")');
expect(collectDependencies(ast).dependencyMapName)
.toEqual(any(String));
expect(collectDependencies(ast).dependencyMapName).toEqual(any(String));
});
it('exposes a string as `dependencyMapName` even without collecting dependencies', () => {
const ast = astFromCode('');
expect(collectDependencies(ast).dependencyMapName)
.toEqual(any(String));
expect(collectDependencies(ast).dependencyMapName).toEqual(any(String));
});
it('replaces all required module ID strings with array lookups, keeps the ID as second argument',
() => {
const ast = astFromCode(`
it('replaces all required module ID strings with array lookups, keeps the ID as second argument', () => {
const ast = astFromCode(`
const a = require('b/lib/a');
const b = require(123);
exports.do = () => require("do");
@ -72,18 +73,19 @@ describe('dependency collection from ASTs:', () => {
}
`);
const {dependencyMapName} = collectDependencies(ast);
const {dependencyMapName} = collectDependencies(ast);
expect(codeFromAst(ast)).toEqual(comparableCode(`
expect(codeFromAst(ast)).toEqual(
comparableCode(`
const a = require(${dependencyMapName}[0], 'b/lib/a');
const b = require(123);
exports.do = () => require(${dependencyMapName}[1], "do");
if (!something) {
require(${dependencyMapName}[2], "setup/something");
}
`));
},
);
`),
);
});
});
describe('Dependency collection from optimized ASTs:', () => {
@ -121,13 +123,15 @@ describe('Dependency collection from optimized ASTs:', () => {
it('replaces all call signatures inserted by a prior call to `collectDependencies`', () => {
forOptimization(ast, names, dependencyMapName);
expect(codeFromAst(ast)).toEqual(comparableCode(`
expect(codeFromAst(ast)).toEqual(
comparableCode(`
const a = require(${dependencyMapName}[0]);
const b = require(123);
exports.do = () => require(${dependencyMapName}[1]);
if (!something) {
require(${dependencyMapName}[2]);
}
`));
`),
);
});
});

View File

@ -5,7 +5,11 @@
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*
* @format
* @emails oncall+javascript_tools
*/
'use strict';
const optimizeModule = require('../optimize-module');
@ -23,8 +27,7 @@ describe('optimizing JS modules', () => {
platform: 'android',
postMinifyProcess: x => x,
};
const originalCode =
`if (Platform.OS !== 'android') {
const originalCode = `if (Platform.OS !== 'android') {
require('arbitrary-dev');
} else {
__DEV__ ? require('arbitrary-android-dev') : require('arbitrary-android-prod');
@ -55,12 +58,13 @@ describe('optimizing JS modules', () => {
const result = optimizeModule(transformResult, optimizationOptions);
optimized = result.details.transformed.default;
injectedVars = optimized.code.match(/function\(([^)]*)/)[1].split(',');
[, requireName,,, dependencyMapName] = injectedVars;
[, requireName, , , dependencyMapName] = injectedVars;
});
it('optimizes code', () => {
expect(optimized.code)
.toEqual(`__d(function(${injectedVars}){${requireName}(${dependencyMapName}[0])});`);
expect(optimized.code).toEqual(
`__d(function(${injectedVars}){${requireName}(${dependencyMapName}[0])});`,
);
});
it('extracts dependencies', () => {
@ -72,15 +76,16 @@ describe('optimizing JS modules', () => {
const column = optimized.code.lastIndexOf(requireName + '(');
const loc = findLast(originalCode, 'require');
expect(consumer.originalPositionFor({line: 1, column}))
.toEqual(objectContaining(loc));
expect(consumer.originalPositionFor({line: 1, column})).toEqual(
objectContaining(loc),
);
});
it('does not extract dependencies for polyfills', () => {
const result = optimizeModule(
transformResult,
{...optimizationOptions, isPolyfill: true},
).details;
const result = optimizeModule(transformResult, {
...optimizationOptions,
isPolyfill: true,
}).details;
expect(result.transformed.default.dependencies).toEqual([]);
});
});
@ -90,7 +95,10 @@ describe('optimizing JS modules', () => {
beforeEach(() => {
postMinifyProcess = fn();
optimize = () =>
optimizeModule(transformResult, {...optimizationOptions, postMinifyProcess});
optimizeModule(transformResult, {
...optimizationOptions,
postMinifyProcess,
});
});
it('passes the result to the provided postprocessing function', () => {
@ -104,15 +112,17 @@ describe('optimizing JS modules', () => {
const code = 'var postprocessed = "code";';
const map = {version: 3, mappings: 'postprocessed'};
postMinifyProcess.stub.returns({code, map});
expect(optimize().details.transformed.default)
.toEqual(objectContaining({code, map}));
expect(optimize().details.transformed.default).toEqual(
objectContaining({code, map}),
);
});
});
it('passes through non-code data unmodified', () => {
const data = {type: 'asset', details: {arbitrary: 'data'}};
expect(optimizeModule(JSON.stringify(data), {dev: true, platform: ''}))
.toEqual(data);
expect(
optimizeModule(JSON.stringify(data), {dev: true, platform: ''}),
).toEqual(data);
});
});

View File

@ -7,7 +7,9 @@
* of patent rights can be found in the PATENTS file in the same directory.
*
* @format
* @emails oncall+javascript_tools
*/
'use strict';
const transformModule = require('../transform-module');

View File

@ -5,12 +5,14 @@
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*
* @format
* @emails oncall+javascript_tools
*/
'use strict';
jest
.mock('fs', () => jest.genMockFromModule('fs'))
.mock('mkdirp');
jest.mock('fs', () => jest.genMockFromModule('fs')).mock('mkdirp');
const wrapWorkerFn = require('../wrap-worker-fn');
const {dirname} = require('path');
@ -63,14 +65,20 @@ describe('wrapWorkerFn:', () => {
workerFn.stub.yields(null, result);
wrapped(infile, outfile, {}, () => {
expect(mkdirp.sync).toBeCalledWith(dirname(outfile));
expect(fs.writeFileSync).toBeCalledWith(outfile, JSON.stringify(result), 'utf8');
expect(fs.writeFileSync).toBeCalledWith(
outfile,
JSON.stringify(result),
'utf8',
);
done();
});
});
it('calls back with any error thrown by `mkdirp.sync`', done => {
const error = new Error();
mkdirp.sync.mockImplementationOnce(() => { throw error; });
mkdirp.sync.mockImplementationOnce(() => {
throw error;
});
wrapped(infile, outfile, {}, e => {
expect(e).toBe(error);
done();
@ -79,7 +87,9 @@ describe('wrapWorkerFn:', () => {
it('calls back with any error thrown by `fs.writeFileSync`', done => {
const error = new Error();
fs.writeFileSync.mockImplementationOnce(() => { throw error; });
fs.writeFileSync.mockImplementationOnce(() => {
throw error;
});
wrapped(infile, outfile, {}, e => {
expect(e).toBe(error);
done();

View File

@ -6,8 +6,10 @@
* LICENSE file in the root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*
* @format
* @flow
*/
'use strict';
const nullthrows = require('fbjs/lib/nullthrows');
@ -27,8 +29,10 @@ class Replacement {
isRequireCall(callee, firstArg) {
return (
callee.type === 'Identifier' && callee.name === 'require' &&
firstArg && isLiteralString(firstArg)
callee.type === 'Identifier' &&
callee.name === 'require' &&
firstArg &&
isLiteralString(firstArg)
);
}
@ -82,8 +86,9 @@ class ProdReplacement {
}
throw new Error(
`${id} is not a known module ID. Existing mappings: ${
this.names.map((n, i) => `${i} => ${n}`).join(', ')}`
`${id} is not a known module ID. Existing mappings: ${this.names
.map((n, i) => `${i} => ${n}`)
.join(', ')}`,
);
}
@ -107,26 +112,32 @@ function createMapLookup(dependencyMapIdentifier, propertyIdentifier) {
function collectDependencies(ast, replacement, dependencyMapIdentifier) {
const traversalState = {dependencyMapIdentifier};
traverse(ast, {
Program(path, state) {
if (!state.dependencyMapIdentifier) {
state.dependencyMapIdentifier =
path.scope.generateUidIdentifier('dependencyMap');
}
traverse(
ast,
{
Program(path, state) {
if (!state.dependencyMapIdentifier) {
state.dependencyMapIdentifier = path.scope.generateUidIdentifier(
'dependencyMap',
);
}
},
CallExpression(path, state) {
const node = path.node;
const arg = node.arguments[0];
if (replacement.isRequireCall(node.callee, arg)) {
const index = replacement.getIndex(arg);
node.arguments = replacement.makeArgs(
types.numericLiteral(index),
arg,
state.dependencyMapIdentifier,
);
}
},
},
CallExpression(path, state) {
const node = path.node;
const arg = node.arguments[0];
if (replacement.isRequireCall(node.callee, arg)) {
const index = replacement.getIndex(arg);
node.arguments = replacement.makeArgs(
types.numericLiteral(index),
arg,
state.dependencyMapIdentifier,
);
}
},
}, null, traversalState);
null,
traversalState,
);
return {
dependencies: replacement.getNames(),
@ -135,16 +146,22 @@ function collectDependencies(ast, replacement, dependencyMapIdentifier) {
}
function isLiteralString(node) {
return node.type === 'StringLiteral' ||
node.type === 'TemplateLiteral' && node.quasis.length === 1;
return (
node.type === 'StringLiteral' ||
(node.type === 'TemplateLiteral' && node.quasis.length === 1)
);
}
exports = module.exports =
(ast: AST) => collectDependencies(ast, new Replacement());
exports.forOptimization =
(ast: AST, names: Array<string>, dependencyMapName?: string) =>
collectDependencies(
ast,
new ProdReplacement(names),
dependencyMapName ? types.identifier(dependencyMapName) : undefined,
);
const xp = (module.exports = (ast: AST) =>
collectDependencies(ast, new Replacement()));
xp.forOptimization = (
ast: AST,
names: Array<string>,
dependencyMapName?: string,
) =>
collectDependencies(
ast,
new ProdReplacement(names),
dependencyMapName ? types.identifier(dependencyMapName) : undefined,
);

View File

@ -6,21 +6,34 @@
* LICENSE file in the root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*
* @format
* @flow
*/
'use strict';
/* eslint-disable no-unclear-flowtypes */
const babelGenerate = require('babel-generator').default;
function generate(ast: Object, filename: string, sourceCode: string, compact: boolean) {
return babelGenerate(ast, {
comments: false,
compact,
filename,
sourceFileName: filename,
sourceMaps: true,
sourceMapTarget: filename,
}, sourceCode);
function generate(
ast: Object,
filename: string,
sourceCode: string,
compact: boolean,
) {
return babelGenerate(
ast,
{
comments: false,
compact,
filename,
sourceFileName: filename,
sourceMaps: true,
sourceMapTarget: filename,
},
sourceCode,
);
}
module.exports = generate;

View File

@ -6,13 +6,16 @@
* LICENSE file in the root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*
* @format
* @flow
*/
'use strict';
const babel = require('babel-core');
const collectDependencies = require('./collect-dependencies');
const constantFolding = require('../../JSTransformer/worker/constant-folding').plugin;
const constantFolding = require('../../JSTransformer/worker/constant-folding')
.plugin;
const generate = require('./generate');
const inline = require('../../JSTransformer/worker/inline').plugin;
const minify = require('../../JSTransformer/worker/minify');
@ -22,7 +25,6 @@ import type {TransformedSourceFile, TransformResult} from '../types.flow';
import type {MappingsMap, SourceMap} from '../../lib/SourceMap';
import type {PostMinifyProcess} from '../../Bundler/index.js';
export type OptimizationOptions = {|
dev: boolean,
isPolyfill?: boolean,
@ -46,9 +48,14 @@ function optimizeModule(
const {postMinifyProcess} = optimizationOptions;
//$FlowIssue #14545724
Object.entries(transformed).forEach(([k, t: TransformResult]: [*, TransformResult]) => {
Object.entries(
transformed,
).forEach(([k, t: TransformResult]: [*, TransformResult]) => {
const optimized = optimize(t, file, code, optimizationOptions);
const processed = postMinifyProcess({code: optimized.code, map: optimized.map});
const processed = postMinifyProcess({
code: optimized.code,
map: optimized.map,
});
optimized.code = processed.code;
optimized.map = processed.map;
result.transformed[k] = optimized;
@ -102,23 +109,22 @@ function mergeSourceMaps(
): MappingsMap {
const merged = new sourceMap.SourceMapGenerator();
const inputMap = new sourceMap.SourceMapConsumer(originalMap);
new sourceMap.SourceMapConsumer(secondMap)
.eachMapping(mapping => {
const original = inputMap.originalPositionFor({
line: mapping.originalLine,
column: mapping.originalColumn,
});
if (original.line == null) {
return;
}
merged.addMapping({
generated: {line: mapping.generatedLine, column: mapping.generatedColumn},
original: {line: original.line, column: original.column || 0},
source: file,
name: original.name || mapping.name,
});
new sourceMap.SourceMapConsumer(secondMap).eachMapping(mapping => {
const original = inputMap.originalPositionFor({
line: mapping.originalLine,
column: mapping.originalColumn,
});
if (original.line == null) {
return;
}
merged.addMapping({
generated: {line: mapping.generatedLine, column: mapping.generatedColumn},
original: {line: original.line, column: original.column || 0},
source: file,
name: original.name || mapping.name,
});
});
return merged.toJSON();
}

View File

@ -6,8 +6,10 @@
* LICENSE file in the root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*
* @format
* @flow
*/
'use strict';
const fs = require('fs');