Skip to content

Commit

Permalink
Add js_file_prod rule
Browse files Browse the repository at this point in the history
Reviewed By: matryoshcow

Differential Revision: D3913439

fbshipit-source-id: fe3a30fddb4d8fcabfc88caf9b1b032189812b89
  • Loading branch information
davidaurelio authored and Facebook Github Bot committed Oct 5, 2016
1 parent aef3d81 commit 0b2d531
Show file tree
Hide file tree
Showing 5 changed files with 229 additions and 47 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -277,5 +277,30 @@ describe('inline constants', () => {
const {ast} = inline('arbitrary.hs', {ast: toAst(code)}, {dev: false});
expect(toString(ast)).toEqual(code.replace(/__DEV__/, 'false'));
});
});

it('can work with wrapped modules', () => {
const code = `__arbitrary(function() {
var Platform = require('react-native').Platform;
var a = Platform.OS, b = Platform.select({android: 1, ios: 2});
});`;
const {ast} = inline(
'arbitrary', {code}, {dev: true, platform: 'android', isWrapped: true});
expect(toString(ast)).toEqual(
normalize(
code
.replace(/Platform\.OS/, '"android"')
.replace(/Platform\.select[^)]+\)/, 1)
)
);
});

it('can work with transformed require calls', () => {
const code = `__arbitrary(function() {
var a = require(123, 'react-native').Platform.OS;
});`;
const {ast} = inline(
'arbitrary', {code}, {dev: true, platform: 'android', isWrapped: true});
expect(toString(ast)).toEqual(
normalize(code.replace(/require\([^)]+\)\.Platform\.OS/, '"android"')));
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -12,40 +12,87 @@

const {traverse, types} = require('babel-core');

const isRequireCall = (callee, firstArg) =>
callee.type !== 'Identifier' ||
callee.name !== 'require' ||
!firstArg ||
firstArg.type !== 'StringLiteral';

function collectDependencies(ast, code) {
let nextIndex = 0;
const dependencyIndexes = new Map();

function getIndex(depencyId) {
let index = dependencyIndexes.get(depencyId);
class Replacement {
constructor() {
this.nameToIndex = new Map();
this.nextIndex = 0;
}

isRequireCall(callee, firstArg) {
return (
callee.type === 'Identifier' && callee.name === 'require' &&
firstArg && firstArg.type === 'StringLiteral'
);
}

getIndex(name) {
let index = this.nameToIndex.get(name);
if (index !== undefined) {
return index;
}

index = nextIndex++;
dependencyIndexes.set(depencyId, index);
index = this.nextIndex++;
this.nameToIndex.set(name, index);
return index;
}

getNames() {
return Array.from(this.nameToIndex.keys());
}

makeArgs(newId, oldId) {
return [newId, oldId];
}
}

class ProdReplacement {
constructor(names) {
this.replacement = new Replacement();
this.names = names;
}

isRequireCall(callee, firstArg) {
return (
callee.type === 'Identifier' && callee.name === 'require' &&
firstArg && firstArg.type === 'NumericLiteral'
);
}

getIndex(id) {
if (id in this.names) {
return this.replacement.getIndex(this.names[id]);
}

throw new Error(
`${id} is not a known module ID. Existing mappings: ${
this.names.map((n, i) => `${i} => ${n}`).join(', ')}`
);
}

getNames() {
return this.replacement.getNames();
}

makeArgs(newId) {
return [newId];
}
}

function collectDependencies(ast, replacement) {
traverse(ast, {
CallExpression(path) {
const node = path.node;
const arg = node.arguments[0];
if (isRequireCall(node.callee, arg)) {
return;
if (replacement.isRequireCall(node.callee, arg)) {
const index = replacement.getIndex(arg.value);
node.arguments = replacement.makeArgs(types.numericLiteral(index), arg);
}

node.arguments[0] = types.numericLiteral(getIndex(arg.value));
}
});

return Array.from(dependencyIndexes.keys());
return replacement.getNames();
}

module.exports = collectDependencies;
exports = module.exports =
ast => collectDependencies(ast, new Replacement());
exports.forOptimization =
(ast, names) => collectDependencies(ast, new ProdReplacement(names));
Original file line number Diff line number Diff line change
Expand Up @@ -11,9 +11,6 @@
const babel = require('babel-core');
const t = babel.types;

const isLiteral = binaryExpression =>
t.isLiteral(binaryExpression.left) && t.isLiteral(binaryExpression.right);

const Conditional = {
exit(path) {
const node = path.node;
Expand Down Expand Up @@ -81,5 +78,5 @@ function constantFolding(filename, transformResult) {
});
}

constantFolding.plugin = plugin;
module.exports = constantFolding;

59 changes: 41 additions & 18 deletions packager/react-packager/src/JSTransformer/worker/inline.js
Original file line number Diff line number Diff line change
Expand Up @@ -28,34 +28,41 @@ const importMap = new Map([['ReactNative', 'react-native']]);

const isGlobal = (binding) => !binding;

const isToplevelBinding = (binding) => isGlobal(binding) || !binding.scope.parent;
const isToplevelBinding = (binding, isWrappedModule) =>
isGlobal(binding) ||
!binding.scope.parent ||
isWrappedModule && !binding.scope.parent.parent;

const isRequireCall = (node, dependencyId, scope) =>
t.isCallExpression(node) &&
t.isIdentifier(node.callee, requirePattern) &&
t.isStringLiteral(node.arguments[0], t.stringLiteral(dependencyId));
checkRequireArgs(node.arguments, dependencyId);

const isImport = (node, scope, patterns) =>
patterns.some(pattern => {
const importName = importMap.get(pattern.name) || pattern.name;
return isRequireCall(node, importName, scope);
});

function isImportOrGlobal(node, scope, patterns) {
function isImportOrGlobal(node, scope, patterns, isWrappedModule) {
const identifier = patterns.find(pattern => t.isIdentifier(node, pattern));
return identifier && isToplevelBinding(scope.getBinding(identifier.name)) ||
isImport(node, scope, patterns);
return (
identifier &&
isToplevelBinding(scope.getBinding(identifier.name), isWrappedModule) ||
isImport(node, scope, patterns)
);
}

const isPlatformOS = (node, scope) =>
const isPlatformOS = (node, scope, isWrappedModule) =>
t.isIdentifier(node.property, os) &&
isImportOrGlobal(node.object, scope, [platform]);
isImportOrGlobal(node.object, scope, [platform], isWrappedModule);

const isReactPlatformOS = (node, scope) =>
const isReactPlatformOS = (node, scope, isWrappedModule) =>
t.isIdentifier(node.property, os) &&
t.isMemberExpression(node.object) &&
t.isIdentifier(node.object.property, platform) &&
isImportOrGlobal(node.object.object, scope, [React, ReactNative]);
isImportOrGlobal(
node.object.object, scope, [React, ReactNative], isWrappedModule);

const isProcessEnvNodeEnv = (node, scope) =>
t.isIdentifier(node.property, nodeEnv) &&
Expand All @@ -64,18 +71,19 @@ const isProcessEnvNodeEnv = (node, scope) =>
t.isIdentifier(node.object.object, processId) &&
isGlobal(scope.getBinding(processId.name));

const isPlatformSelect = (node, scope) =>
const isPlatformSelect = (node, scope, isWrappedModule) =>
t.isMemberExpression(node.callee) &&
t.isIdentifier(node.callee.object, platform) &&
t.isIdentifier(node.callee.property, select) &&
isImportOrGlobal(node.callee.object, scope, [platform]);
isImportOrGlobal(node.callee.object, scope, [platform], isWrappedModule);

const isReactPlatformSelect = (node, scope) =>
const isReactPlatformSelect = (node, scope, isWrappedModule) =>
t.isMemberExpression(node.callee) &&
t.isIdentifier(node.callee.property, select) &&
t.isMemberExpression(node.callee.object) &&
t.isIdentifier(node.callee.object.property, platform) &&
isImportOrGlobal(node.callee.object.object, scope, [React, ReactNative]);
isImportOrGlobal(
node.callee.object.object, scope, [React, ReactNative], isWrappedModule);

const isDev = (node, parent, scope) =>
t.isIdentifier(node, dev) &&
Expand All @@ -97,22 +105,30 @@ const inlinePlugin = {
MemberExpression(path, state) {
const node = path.node;
const scope = path.scope;
const opts = state.opts;

if (isPlatformOS(node, scope) || isReactPlatformOS(node, scope)) {
path.replaceWith(t.stringLiteral(state.opts.platform));
if (
isPlatformOS(node, scope, opts.isWrapped) ||
isReactPlatformOS(node, scope, opts.isWrapped)
) {
path.replaceWith(t.stringLiteral(opts.platform));
} else if (isProcessEnvNodeEnv(node, scope)) {
path.replaceWith(
t.stringLiteral(state.opts.dev ? 'development' : 'production'));
t.stringLiteral(opts.dev ? 'development' : 'production'));
}
},
CallExpression(path, state) {
const node = path.node;
const scope = path.scope;
const arg = node.arguments[0];
const opts = state.opts;

if (isPlatformSelect(node, scope) || isReactPlatformSelect(node, scope)) {
if (
isPlatformSelect(node, scope, opts.isWrapped) ||
isReactPlatformSelect(node, scope, opts.isWrapped)
) {
const replacement = t.isObjectExpression(arg)
? findProperty(arg, state.opts.platform)
? findProperty(arg, opts.platform)
: node;

path.replaceWith(replacement);
Expand All @@ -123,6 +139,12 @@ const inlinePlugin = {

const plugin = () => inlinePlugin;

function checkRequireArgs(args, dependencyId) {
const pattern = t.stringLiteral(dependencyId);
return t.isStringLiteral(args[0], pattern) ||
t.isNumericLiteral(args[0]) && t.isStringLiteral(args[1], pattern);
}

function inline(filename, transformResult, options) {
const code = transformResult.code;
const babelOptions = {
Expand All @@ -141,4 +163,5 @@ function inline(filename, transformResult, options) {
: babel.transform(code, babelOptions);
}

inline.plugin = inlinePlugin;
module.exports = inline;
Loading

0 comments on commit 0b2d531

Please sign in to comment.