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 * 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. * of patent rights can be found in the PATENTS file in the same directory.
* *
* @format
* @flow * @flow
*/ */
'use strict'; 'use strict';
/* eslint-disable no-unclear-flowtypes */
const babel = require('babel-core'); const babel = require('babel-core');
const MODULE_FACTORY_PARAMETERS = ['global', 'require', 'module', 'exports']; const MODULE_FACTORY_PARAMETERS = ['global', 'require', 'module', 'exports'];
@ -26,12 +29,18 @@ function wrapModule(fileAst: Object, dependencyMapName: string): Object {
function wrapPolyfill(fileAst: Object): Object { function wrapPolyfill(fileAst: Object): Object {
const t = babel.types; 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')]); const iife = t.callExpression(factory, [t.identifier('this')]);
return t.file(t.program([t.expressionStatement(iife)])); 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; const t = babel.types;
return t.functionExpression( return t.functionExpression(
t.identifier(''), t.identifier(''),

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -6,13 +6,16 @@
* LICENSE file in the root directory of this source tree. An additional grant * 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. * of patent rights can be found in the PATENTS file in the same directory.
* *
* @format
* @flow * @flow
*/ */
'use strict'; 'use strict';
const babel = require('babel-core'); const babel = require('babel-core');
const collectDependencies = require('./collect-dependencies'); 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 generate = require('./generate');
const inline = require('../../JSTransformer/worker/inline').plugin; const inline = require('../../JSTransformer/worker/inline').plugin;
const minify = require('../../JSTransformer/worker/minify'); 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 {MappingsMap, SourceMap} from '../../lib/SourceMap';
import type {PostMinifyProcess} from '../../Bundler/index.js'; import type {PostMinifyProcess} from '../../Bundler/index.js';
export type OptimizationOptions = {| export type OptimizationOptions = {|
dev: boolean, dev: boolean,
isPolyfill?: boolean, isPolyfill?: boolean,
@ -46,9 +48,14 @@ function optimizeModule(
const {postMinifyProcess} = optimizationOptions; const {postMinifyProcess} = optimizationOptions;
//$FlowIssue #14545724 //$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 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.code = processed.code;
optimized.map = processed.map; optimized.map = processed.map;
result.transformed[k] = optimized; result.transformed[k] = optimized;
@ -102,8 +109,7 @@ function mergeSourceMaps(
): MappingsMap { ): MappingsMap {
const merged = new sourceMap.SourceMapGenerator(); const merged = new sourceMap.SourceMapGenerator();
const inputMap = new sourceMap.SourceMapConsumer(originalMap); const inputMap = new sourceMap.SourceMapConsumer(originalMap);
new sourceMap.SourceMapConsumer(secondMap) new sourceMap.SourceMapConsumer(secondMap).eachMapping(mapping => {
.eachMapping(mapping => {
const original = inputMap.originalPositionFor({ const original = inputMap.originalPositionFor({
line: mapping.originalLine, line: mapping.originalLine,
column: mapping.originalColumn, column: mapping.originalColumn,

View File

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