Rename the require function name inside modules

Reviewed By: davidaurelio

Differential Revision: D6818774

fbshipit-source-id: ae67dccc07fa8c56be28d7b83e2f510d927e2345
This commit is contained in:
Rafael Oleza 2018-02-04 09:14:29 -08:00 committed by Facebook Github Bot
parent 4c4eaeac8a
commit cd23c579b5
11 changed files with 244 additions and 65 deletions

View File

@ -69,7 +69,7 @@ describe('code transformation worker:', () => {
expect(result.code).toBe(
[
'__d(function (global, require, module, exports, _dependencyMap) {',
'__d(function (global, _require, module, exports, _dependencyMap) {',
' arbitrary(code);',
'});',
].join('\n'),
@ -105,16 +105,16 @@ describe('code transformation worker:', () => {
expect(BABEL_VERSION).toBe(7);
expect(result.code).toBe(
[
'__d(function (global, require, module, exports, _dependencyMap) {',
'__d(function (global, _require, module, exports, _dependencyMap) {',
" 'use strict';",
'',
' var _c = babelHelpers.interopRequireDefault(require(_dependencyMap[0], "./c"));',
' var _c = babelHelpers.interopRequireDefault(_require(_dependencyMap[0], "./c"));',
'',
' require(_dependencyMap[1], "./a");',
' _require(_dependencyMap[1], "./a");',
'',
' arbitrary(code);',
'',
' var b = require(_dependencyMap[2], "b");',
' var b = _require(_dependencyMap[2], "b");',
'});',
].join('\n'),
);
@ -147,16 +147,16 @@ describe('code transformation worker:', () => {
expect(BABEL_VERSION).toBe(6);
expect(result.code).toBe(
[
'__d(function (global, require, module, exports, _dependencyMap) {',
' var _c = require(_dependencyMap[0], "./c");',
'__d(function (global, _require, module, exports, _dependencyMap) {',
' var _c = _require(_dependencyMap[0], "./c");',
'',
' var _c2 = babelHelpers.interopRequireDefault(_c);',
'',
' require(_dependencyMap[1], "./a");',
' _require(_dependencyMap[1], "./a");',
'',
' arbitrary(code);',
'',
' var b = require(_dependencyMap[2], "b");',
' var b = _require(_dependencyMap[2], "b");',
'});',
].join('\n'),
);

View File

@ -143,11 +143,21 @@ function postTransform(
}
throw error;
}
const wrapped = JsFileWrapping.wrapModule(ast, dependencyMapName);
wrappedAst = wrapped.ast;
if (!options.dev) {
dependencies = optimizeDependencies(ast, dependencies, dependencyMapName);
dependencies = optimizeDependencies(
wrappedAst,
dependencies,
dependencyMapName,
wrapped.requireName,
);
}
dependencies = dependencies.map(dep => dep.name);
wrappedAst = JsFileWrapping.wrapModule(ast, dependencyMapName);
}
const result = generate(

View File

@ -162,6 +162,7 @@ export type TransformResult = {|
dependencies: $ReadOnlyArray<TransformResultDependency>,
dependencyMapName?: string,
map: ?BabelSourceMap,
requireName: string,
|};
export type TransformResults = {[string]: TransformResult};

View File

@ -13,18 +13,24 @@
'use strict';
/* eslint-disable no-unclear-flowtypes */
const {babelTypes} = require('../../babel-bridge');
const {babelTypes, babelTraverse: traverse} = require('../../babel-bridge');
const MODULE_FACTORY_PARAMETERS = ['global', 'require', 'module', 'exports'];
const POLYFILL_FACTORY_PARAMETERS = ['global'];
function wrapModule(fileAst: Object, dependencyMapName: string): Object {
function wrapModule(
fileAst: Object,
dependencyMapName: string,
): {ast: Object, requireName: string} {
const t = babelTypes;
const params = MODULE_FACTORY_PARAMETERS.concat(dependencyMapName);
const factory = functionFromProgram(fileAst.program, params);
const def = t.callExpression(t.identifier('__d'), [factory]);
return t.file(t.program([t.expressionStatement(def)]));
const ast = t.file(t.program([t.expressionStatement(def)]));
const requireName = renameRequires(ast);
return {ast, requireName};
}
function wrapPolyfill(fileAst: Object): Object {
@ -53,6 +59,21 @@ function makeIdentifier(name: string): Object {
return babelTypes.identifier(name);
}
function renameRequires(ast: Object) {
let requireName = 'require';
traverse(ast, {
Program(path) {
const body = path.get('body.0.expression.arguments.0.body');
requireName = body.scope.generateUid('_require');
body.scope.rename('require', requireName);
},
});
return requireName;
}
module.exports = {
MODULE_FACTORY_PARAMETERS,
POLYFILL_FACTORY_PARAMETERS,

View File

@ -0,0 +1,107 @@
/**
* Copyright (c) 2017-present, Facebook, Inc.
* All rights reserved.
*
* 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.
*
* @emails oncall+javascript_foundation
* @flow
* @format
*/
'use strict';
const JsFileWrapping = require('../JsFileWrapping');
const {babylon} = require('../../../babel-bridge');
const {codeFromAst, comparableCode} = require('../../test-helpers');
it('wraps a module correctly', () => {
const dependencyMapName = '_dependencyMapName';
const originalAst = astFromCode(`
const dynamicRequire = require;
const a = require('b/lib/a');
exports.do = () => require("do");
if (!something) {
require("setup/something");
}
require.blah('do');
`);
const {ast, requireName} = JsFileWrapping.wrapModule(
originalAst,
dependencyMapName,
);
expect(requireName).toBe('_require');
expect(codeFromAst(ast)).toEqual(
comparableCode(`
__d(function (global, _require, module, exports, _dependencyMapName) {
const dynamicRequire = _require;
const a = _require('b/lib/a');
exports.do = () => _require("do");
if (!something) {
_require("setup/something");
}
_require.blah('do');
});`),
);
});
it('replaces the require variable by a unique one', () => {
const dependencyMapName = '_dependencyMapName';
const originalAst = astFromCode(`
const dynamicRequire = require;
const a = require('b/lib/a');
let _require = 'foo';
exports.do = () => require("do");
if (!something) {
require("setup/something");
}
require.blah('do');
`);
const {ast, requireName} = JsFileWrapping.wrapModule(
originalAst,
dependencyMapName,
);
expect(requireName).toBe('_require2');
expect(codeFromAst(ast)).toEqual(
comparableCode(`
__d(function (global, _require2, module, exports, _dependencyMapName) {
const dynamicRequire = _require2;
const a = _require2('b/lib/a');
let _require = 'foo';
exports.do = () => _require2("do");
if (!something) {
_require2("setup/something");
}
_require2.blah('do');
});`),
);
});
it('wraps a polyfill correctly', () => {
const ast = astFromCode(`
if (something) {
console.log('foo');
}
`);
const wrappedAst = JsFileWrapping.wrapPolyfill(ast);
expect(codeFromAst(wrappedAst)).toEqual(
comparableCode(`
(function (global) {
if (something) {
console.log('foo');
}
})(this);`),
);
});
function astFromCode(code) {
return babylon.parse(code, {plugins: ['dynamicImport']});
}

View File

@ -28,6 +28,7 @@ const DEPS = [
{name: 'some/async/module', isAsync: true},
{name: 'setup/something', isAsync: false},
];
const REQUIRE_NAME = 'require';
it('returns dependencies from the transformed AST', () => {
const ast = astFromCode(`
@ -38,7 +39,12 @@ it('returns dependencies from the transformed AST', () => {
require(${DEP_MAP_NAME}[4], "setup/something");
}
`);
const dependencies = optimizeDependencies(ast, DEPS, DEP_MAP_NAME);
const dependencies = optimizeDependencies(
ast,
DEPS,
DEP_MAP_NAME,
REQUIRE_NAME,
);
expect(dependencies).toEqual(DEPS);
expect(codeFromAst(ast)).toEqual(
comparableCode(`
@ -54,7 +60,12 @@ it('returns dependencies from the transformed AST', () => {
it('strips unused dependencies and translates require() calls', () => {
const ast = astFromCode(`require(${DEP_MAP_NAME}[1], 'do');`);
const dependencies = optimizeDependencies(ast, DEPS, DEP_MAP_NAME);
const dependencies = optimizeDependencies(
ast,
DEPS,
DEP_MAP_NAME,
REQUIRE_NAME,
);
expect(dependencies).toEqual([{name: 'do', isAsync: false}]);
expect(codeFromAst(ast)).toEqual(
comparableCode(`require(${DEP_MAP_NAME}[0]);`),
@ -65,7 +76,12 @@ it('strips unused dependencies and translates loadForModule() calls', () => {
const ast = astFromCode(`
require(${DEP_MAP_NAME}[2], "asyncRequire")(${DEP_MAP_NAME}[3]).then(foo => {});
`);
const dependencies = optimizeDependencies(ast, DEPS, DEP_MAP_NAME);
const dependencies = optimizeDependencies(
ast,
DEPS,
DEP_MAP_NAME,
REQUIRE_NAME,
);
expect(dependencies).toEqual([
{name: 'asyncRequire', isAsync: false},
{name: 'some/async/module', isAsync: true},
@ -87,7 +103,12 @@ it('strips unused dependencies and translates loadForModule() calls; different o
{name: 'some/async/module', isAsync: true},
{name: 'asyncRequire', isAsync: false},
];
const dependencies = optimizeDependencies(ast, deps, DEP_MAP_NAME);
const dependencies = optimizeDependencies(
ast,
deps,
DEP_MAP_NAME,
REQUIRE_NAME,
);
expect(dependencies).toEqual([
{name: 'something/else', isAsync: false},
{name: 'asyncRequire', isAsync: false},
@ -104,7 +125,7 @@ it('strips unused dependencies and translates loadForModule() calls; different o
it('throws if an invalid require() call is encountered', () => {
const ast = astFromCode(`require(${DEP_MAP_NAME}[1]);`);
try {
optimizeDependencies(ast, DEPS, DEP_MAP_NAME);
optimizeDependencies(ast, DEPS, DEP_MAP_NAME, REQUIRE_NAME);
throw new Error('should not reach this');
} catch (error) {
expect(error).toBeInstanceOf(InvalidRequireCallError);

View File

@ -137,7 +137,7 @@ describe('transforming JS modules:', () => {
const {code, dependencyMapName} = result.details.transformed.default;
invariant(dependencyMapName != null, 'dependencyMapName cannot be null');
expect(code.replace(/\s+/g, '')).toEqual(
`__d(function(global,require,module,exports,${dependencyMapName}){${transformedCode}});`,
`__d(function(global,_require,module,exports,${dependencyMapName}){${transformedCode}});`,
);
});
@ -194,12 +194,12 @@ describe('transforming JS modules:', () => {
invariant(result.type === 'code', 'result must be code');
const {dev, prod} = result.details.transformed;
expect(dev.code.replace(/\s+/g, '')).toEqual(
`__d(function(global,require,module,exports,${nullthrows(
`__d(function(global,_require,module,exports,${nullthrows(
dev.dependencyMapName,
)}){arbitrary(code);});`,
);
expect(prod.code.replace(/\s+/g, '')).toEqual(
`__d(function(global,require,module,exports,${nullthrows(
`__d(function(global,_require,module,exports,${nullthrows(
prod.dependencyMapName,
)}){arbitrary(code);});`,
);

View File

@ -64,7 +64,11 @@ function optimizeModule(
return {type: 'code', details: result};
}
function optimize(transformed: TransformResult, file, options) {
function optimize(
transformed: TransformResult,
file,
options,
): TransformResult {
const {code, dependencyMapName, map} = transformed;
const optimized = optimizeCode(code, map, file, options);
@ -83,6 +87,7 @@ function optimize(transformed: TransformResult, file, options) {
optimized.ast,
transformed.dependencies,
dependencyMapName,
transformed.requireName,
);
}
}
@ -95,7 +100,12 @@ function optimize(transformed: TransformResult, file, options) {
inputMap && gen.map && mergeSourceMaps(file, inputMap, gen.map),
file,
);
return {code: min.code, map: min.map, dependencies};
return {
code: min.code,
map: min.map,
dependencies,
requireName: transformed.requireName,
};
}
function optimizeCode(code, map, filename, inliningOptions) {

View File

@ -29,16 +29,21 @@ function optimizeDependencies(
ast: Ast,
dependencies: Dependencies,
dependencyMapName: string,
): Array<TransformResultDependency> {
requireName: string,
): $ReadOnlyArray<TransformResultDependency> {
const visited = new WeakSet();
const context = {oldToNewIndex: new Map(), dependencies: []};
const context = {
oldToNewIndex: new Map(),
dependencies: [],
};
const visitor = {
CallExpression(path) {
const {node} = path;
if (visited.has(node)) {
return;
}
if (isRequireCall(node.callee)) {
if (isRequireCall(node.callee, requireName)) {
processRequireCall(node);
visited.add(node);
}
@ -58,8 +63,8 @@ function optimizeDependencies(
return context.dependencies;
}
function isRequireCall(callee) {
return callee.type === 'Identifier' && callee.name === 'require';
function isRequireCall(callee, requireName) {
return callee.type === 'Identifier' && callee.name === requireName;
}
function processRequireCall(node) {

View File

@ -137,6 +137,7 @@ function transformJSON(json, options): TransformedSourceFile {
code,
map: null, // no source map for JSON files!
dependencies: [],
requireName: '_require', // not relevant for JSON files
};
const transformed = {};
@ -202,24 +203,27 @@ function makeResult(options: {|
+isPolyfill: boolean,
+sourceCode: string,
|}) {
let dependencies, dependencyMapName, file;
const {ast} = options;
let dependencies, dependencyMapName;
let requireName = 'require';
let {ast} = options;
if (options.isPolyfill) {
dependencies = [];
file = JsFileWrapping.wrapPolyfill(ast);
ast = JsFileWrapping.wrapPolyfill(ast);
} else {
const {asyncRequireModulePath} = options;
const opts = {asyncRequireModulePath, dynamicRequires: 'reject'};
({dependencies, dependencyMapName} = collectDependencies(ast, opts));
file = JsFileWrapping.wrapModule(ast, dependencyMapName);
({ast, requireName} = JsFileWrapping.wrapModule(ast, dependencyMapName));
}
const {filename, sourceCode} = options;
const gen = generate(file, filename, sourceCode, false);
const gen = generate(ast, filename, sourceCode, false);
return {
code: gen.code,
map: gen.map,
dependencies,
dependencyMapName,
requireName,
};
}

View File

@ -138,40 +138,40 @@ exports[`basic_bundle bundles package with polyfills (babel v6) 1`] = `
String.prototype.repeat = function () {};
}
})(this);
__d(function (global, require, module, exports, _dependencyMap) {
__d(function (global, _require, module, exports, _dependencyMap) {
'use strict';
var Bar = require(_dependencyMap[0]);
var Bar = _require(_dependencyMap[0]);
var Foo = require(_dependencyMap[1]);
var Foo = _require(_dependencyMap[1]);
module.exports = {
Foo: Foo,
Bar: Bar
};
},4,[5,6]);
__d(function (global, require, module, exports, _dependencyMap) {
__d(function (global, _require, module, exports, _dependencyMap) {
'use strict';
var Foo = require(_dependencyMap[0]);
var Foo = _require(_dependencyMap[0]);
module.exports = {
type: 'bar',
foo: Foo.type
};
},5,[6]);
__d(function (global, require, module, exports, _dependencyMap) {
__d(function (global, _require, module, exports, _dependencyMap) {
'use strict';
var asset = require(_dependencyMap[0]);
var asset = _require(_dependencyMap[0]);
module.exports = {
type: 'foo',
asset: asset
};
},6,[7]);
__d(function (global, require, module, exports, _dependencyMap) {
module.exports = require(_dependencyMap[0]).registerAsset({
__d(function (global, _require, module, exports, _dependencyMap) {
module.exports = _require(_dependencyMap[0]).registerAsset({
\\"__packager_asset\\": true,
\\"httpServerLocation\\": \\"/assets\\",
\\"width\\": 8,
@ -182,7 +182,7 @@ __d(function (global, require, module, exports, _dependencyMap) {
\\"type\\": \\"png\\"
});
},7,[8]);
__d(function (global, require, module, exports, _dependencyMap) {
__d(function (global, _require, module, exports, _dependencyMap) {
'use strict';
},8,[]);
require(4);"
@ -326,40 +326,40 @@ exports[`basic_bundle bundles package with polyfills (babel v7) 1`] = `
String.prototype.repeat = function () {};
}
})(this);
__d(function (global, require, module, exports, _dependencyMap) {
__d(function (global, _require, module, exports, _dependencyMap) {
'use strict';
var Bar = require(_dependencyMap[0]);
var Bar = _require(_dependencyMap[0]);
var Foo = require(_dependencyMap[1]);
var Foo = _require(_dependencyMap[1]);
module.exports = {
Foo: Foo,
Bar: Bar
};
},4,[5,6]);
__d(function (global, require, module, exports, _dependencyMap) {
__d(function (global, _require, module, exports, _dependencyMap) {
'use strict';
var Foo = require(_dependencyMap[0]);
var Foo = _require(_dependencyMap[0]);
module.exports = {
type: 'bar',
foo: Foo.type
};
},5,[6]);
__d(function (global, require, module, exports, _dependencyMap) {
__d(function (global, _require, module, exports, _dependencyMap) {
'use strict';
var asset = require(_dependencyMap[0]);
var asset = _require(_dependencyMap[0]);
module.exports = {
type: 'foo',
asset: asset
};
},6,[7]);
__d(function (global, require, module, exports, _dependencyMap) {
module.exports = require(_dependencyMap[0]).registerAsset({
__d(function (global, _require, module, exports, _dependencyMap) {
module.exports = _require(_dependencyMap[0]).registerAsset({
\\"__packager_asset\\": true,
\\"httpServerLocation\\": \\"/assets\\",
\\"width\\": 8,
@ -370,7 +370,7 @@ __d(function (global, require, module, exports, _dependencyMap) {
\\"type\\": \\"png\\"
});
},7,[8]);
__d(function (global, require, module, exports, _dependencyMap) {
__d(function (global, _require, module, exports, _dependencyMap) {
'use strict';
},8,[]);
require(4);"
@ -500,40 +500,40 @@ exports[`basic_bundle bundles package without polyfills 1`] = `
return Error('Requiring module \\"' + displayName + '\\", which threw an exception: ' + error);
}
})(this);
__d(function (global, require, module, exports, _dependencyMap) {
__d(function (global, _require, module, exports, _dependencyMap) {
'use strict';
var Bar = require(_dependencyMap[0]);
var Bar = _require(_dependencyMap[0]);
var Foo = require(_dependencyMap[1]);
var Foo = _require(_dependencyMap[1]);
module.exports = {
Foo: Foo,
Bar: Bar
};
},2,[3,4]);
__d(function (global, require, module, exports, _dependencyMap) {
__d(function (global, _require, module, exports, _dependencyMap) {
'use strict';
var Foo = require(_dependencyMap[0]);
var Foo = _require(_dependencyMap[0]);
module.exports = {
type: 'bar',
foo: Foo.type
};
},3,[4]);
__d(function (global, require, module, exports, _dependencyMap) {
__d(function (global, _require, module, exports, _dependencyMap) {
'use strict';
var asset = require(_dependencyMap[0]);
var asset = _require(_dependencyMap[0]);
module.exports = {
type: 'foo',
asset: asset
};
},4,[5]);
__d(function (global, require, module, exports, _dependencyMap) {
module.exports = require(_dependencyMap[0]).registerAsset({
__d(function (global, _require, module, exports, _dependencyMap) {
module.exports = _require(_dependencyMap[0]).registerAsset({
\\"__packager_asset\\": true,
\\"httpServerLocation\\": \\"/assets\\",
\\"width\\": 8,
@ -544,7 +544,7 @@ __d(function (global, require, module, exports, _dependencyMap) {
\\"type\\": \\"png\\"
});
},5,[6]);
__d(function (global, require, module, exports, _dependencyMap) {
__d(function (global, _require, module, exports, _dependencyMap) {
'use strict';
},6,[]);
require(2);"