Improve constant inlining, add `process.platform`

Reviewed By: bestander

Differential Revision: D3235716

fb-gh-sync-id: f9019ec0042827e409fa84ba74f4c426ccad1519
fbshipit-source-id: f9019ec0042827e409fa84ba74f4c426ccad1519
This commit is contained in:
David Aurelio 2016-04-28 11:22:37 -07:00 committed by Facebook Github Bot 6
parent cc538b9b35
commit 17726e1ae6
2 changed files with 91 additions and 21 deletions

View File

@ -35,7 +35,7 @@ describe('inline constants', () => {
var a = __DEV__ ? 1 : 2; var a = __DEV__ ? 1 : 2;
var b = a.__DEV__; var b = a.__DEV__;
var c = function __DEV__(__DEV__) {}; var c = function __DEV__(__DEV__) {};
}` }`;
const {ast} = inline('arbitrary.js', {code}, {dev: true}); const {ast} = inline('arbitrary.js', {code}, {dev: true});
expect(toString(ast)).toEqual(normalize(code.replace(/__DEV__/, 'true'))); expect(toString(ast)).toEqual(normalize(code.replace(/__DEV__/, 'true')));
}); });
@ -44,7 +44,7 @@ describe('inline constants', () => {
const code = `function a() { const code = `function a() {
var a = Platform.OS; var a = Platform.OS;
var b = a.Platform.OS; var b = a.Platform.OS;
}` }`;
const {ast} = inline('arbitrary.js', {code}, {platform: 'ios'}); const {ast} = inline('arbitrary.js', {code}, {platform: 'ios'});
expect(toString(ast)).toEqual(normalize(code.replace(/Platform\.OS/, '"ios"'))); expect(toString(ast)).toEqual(normalize(code.replace(/Platform\.OS/, '"ios"')));
}); });
@ -55,7 +55,18 @@ describe('inline constants', () => {
function a() { function a() {
if (Platform.OS === 'android') a = function() {}; if (Platform.OS === 'android') a = function() {};
var b = a.Platform.OS; var b = a.Platform.OS;
}` }`;
const {ast} = inline('arbitrary.js', {code}, {platform: 'ios'});
expect(toString(ast)).toEqual(normalize(code.replace(/Platform\.OS/, '"ios"')));
});
it('replaces Platform.OS in the code if Platform is a top level import from react-native', () => {
const code = `
var Platform = require('react-native').Platform;
function a() {
if (Platform.OS === 'android') a = function() {};
var b = a.Platform.OS;
}`;
const {ast} = inline('arbitrary.js', {code}, {platform: 'ios'}); const {ast} = inline('arbitrary.js', {code}, {platform: 'ios'});
expect(toString(ast)).toEqual(normalize(code.replace(/Platform\.OS/, '"ios"'))); expect(toString(ast)).toEqual(normalize(code.replace(/Platform\.OS/, '"ios"')));
}); });
@ -64,7 +75,7 @@ describe('inline constants', () => {
const code = `function a() { const code = `function a() {
var a = require('Platform').OS; var a = require('Platform').OS;
var b = a.require('Platform').OS; var b = a.require('Platform').OS;
}` }`;
const {ast} = inline('arbitrary.js', {code}, {platform: 'android'}); const {ast} = inline('arbitrary.js', {code}, {platform: 'android'});
expect(toString(ast)).toEqual( expect(toString(ast)).toEqual(
normalize(code.replace(/require\('Platform'\)\.OS/, '"android"'))); normalize(code.replace(/require\('Platform'\)\.OS/, '"android"')));
@ -74,18 +85,27 @@ describe('inline constants', () => {
const code = `function a() { const code = `function a() {
var a = React.Platform.OS; var a = React.Platform.OS;
var b = a.React.Platform.OS; var b = a.React.Platform.OS;
}` }`;
const {ast} = inline('arbitrary.js', {code}, {platform: 'ios'}); const {ast} = inline('arbitrary.js', {code}, {platform: 'ios'});
expect(toString(ast)).toEqual(normalize(code.replace(/React\.Platform\.OS/, '"ios"'))); expect(toString(ast)).toEqual(normalize(code.replace(/React\.Platform\.OS/, '"ios"')));
}); });
it('replaces ReactNative.Platform.OS in the code if ReactNative is a global', () => {
const code = `function a() {
var a = ReactNative.Platform.OS;
var b = a.ReactNative.Platform.OS;
}`;
const {ast} = inline('arbitrary.js', {code}, {platform: 'ios'});
expect(toString(ast)).toEqual(normalize(code.replace(/ReactNative\.Platform\.OS/, '"ios"')));
});
it('replaces React.Platform.OS in the code if React is a top level import', () => { it('replaces React.Platform.OS in the code if React is a top level import', () => {
const code = ` const code = `
var React = require('React'); var React = require('React');
function a() { function a() {
if (React.Platform.OS === 'android') a = function() {}; if (React.Platform.OS === 'android') a = function() {};
var b = a.React.Platform.OS; var b = a.React.Platform.OS;
}` }`;
const {ast} = inline('arbitrary.js', {code}, {platform: 'ios'}); const {ast} = inline('arbitrary.js', {code}, {platform: 'ios'});
expect(toString(ast)).toEqual(normalize(code.replace(/React.Platform\.OS/, '"ios"'))); expect(toString(ast)).toEqual(normalize(code.replace(/React.Platform\.OS/, '"ios"')));
}); });
@ -94,19 +114,40 @@ describe('inline constants', () => {
const code = `function a() { const code = `function a() {
var a = require('React').Platform.OS; var a = require('React').Platform.OS;
var b = a.require('React').Platform.OS; var b = a.require('React').Platform.OS;
}` }`;
const {ast} = inline('arbitrary.js', {code}, {platform: 'android'}); const {ast} = inline('arbitrary.js', {code}, {platform: 'android'});
expect(toString(ast)).toEqual( expect(toString(ast)).toEqual(
normalize(code.replace(/require\('React'\)\.Platform\.OS/, '"android"'))); normalize(code.replace(/require\('React'\)\.Platform\.OS/, '"android"')));
}); });
it('replaces ReactNative.Platform.OS in the code if ReactNative is a top level import', () => {
const code = `
var ReactNative = require('react-native');
function a() {
if (ReactNative.Platform.OS === 'android') a = function() {};
var b = a.ReactNative.Platform.OS;
}`;
const {ast} = inline('arbitrary.js', {code}, {platform: 'android'});
expect(toString(ast)).toEqual(normalize(code.replace(/ReactNative.Platform\.OS/, '"android"')));
});
it('replaces require("react-native").Platform.OS in the code', () => {
const code = `function a() {
var a = require('react-native').Platform.OS;
var b = a.require('react-native').Platform.OS;
}`;
const {ast} = inline('arbitrary.js', {code}, {platform: 'android'});
expect(toString(ast)).toEqual(
normalize(code.replace(/require\('react-native'\)\.Platform\.OS/, '"android"')));
});
it('replaces process.env.NODE_ENV in the code', () => { it('replaces process.env.NODE_ENV in the code', () => {
const code = `function a() { const code = `function a() {
if (process.env.NODE_ENV === 'production') { if (process.env.NODE_ENV === 'production') {
return require('Prod'); return require('Prod');
} }
return require('Dev'); return require('Dev');
}` }`;
const {ast} = inline('arbitrary.js', {code}, {dev: false}); const {ast} = inline('arbitrary.js', {code}, {dev: false});
expect(toString(ast)).toEqual( expect(toString(ast)).toEqual(
normalize(code.replace(/process\.env\.NODE_ENV/, '"production"'))); normalize(code.replace(/process\.env\.NODE_ENV/, '"production"')));
@ -118,16 +159,28 @@ describe('inline constants', () => {
return require('Prod'); return require('Prod');
} }
return require('Dev'); return require('Dev');
}` }`;
const {ast} = inline('arbitrary.js', {code}, {dev: true}); const {ast} = inline('arbitrary.js', {code}, {dev: true});
expect(toString(ast)).toEqual( expect(toString(ast)).toEqual(
normalize(code.replace(/process\.env\.NODE_ENV/, '"development"'))); normalize(code.replace(/process\.env\.NODE_ENV/, '"development"')));
}); });
it('replaces process.platform in the code', () => {
const code = `function a() {
if (process.platform === 'android') {
return require('./android');
}
return require('./ios');
}`;
const {ast} = inline('arbitrary.js', {code}, {platform: 'ios'});
expect(toString(ast)).toEqual(
normalize(code.replace(/process\.platform\b/, '"ios"')));
});
it('accepts an AST as input', function() { it('accepts an AST as input', function() {
const code = `function ifDev(a,b){return __DEV__?a:b;}`; const code = 'function ifDev(a,b){return __DEV__?a:b;}';
const {ast} = inline('arbitrary.hs', {ast: toAst(code)}, {dev: false}); const {ast} = inline('arbitrary.hs', {ast: toAst(code)}, {dev: false});
expect(toString(ast)).toEqual(code.replace(/__DEV__/, 'false')) expect(toString(ast)).toEqual(code.replace(/__DEV__/, 'false'));
}); });
}); });

View File

@ -11,7 +11,8 @@
const babel = require('babel-core'); const babel = require('babel-core');
const t = babel.types; const t = babel.types;
const react = {name: 'React'}; const React = {name: 'React'};
const ReactNative = {name: 'ReactNative'};
const platform = {name: 'Platform'}; const platform = {name: 'Platform'};
const os = {name: 'OS'}; const os = {name: 'OS'};
const requirePattern = {name: 'require'}; const requirePattern = {name: 'require'};
@ -19,9 +20,12 @@ const requirePattern = {name: 'require'};
const env = {name: 'env'}; const env = {name: 'env'};
const nodeEnv = {name: 'NODE_ENV'}; const nodeEnv = {name: 'NODE_ENV'};
const processId = {name: 'process'}; const processId = {name: 'process'};
const platformId = {name: 'platform'};
const dev = {name: '__DEV__'}; const dev = {name: '__DEV__'};
const importMap = new Map([['ReactNative', 'react-native']]);
const isGlobal = (binding) => !binding; const isGlobal = (binding) => !binding;
const isToplevelBinding = (binding) => isGlobal(binding) || !binding.scope.parent; const isToplevelBinding = (binding) => isGlobal(binding) || !binding.scope.parent;
@ -31,20 +35,27 @@ const isRequireCall = (node, dependencyId, scope) =>
t.isIdentifier(node.callee, requirePattern) && t.isIdentifier(node.callee, requirePattern) &&
t.isStringLiteral(node.arguments[0], t.stringLiteral(dependencyId)); t.isStringLiteral(node.arguments[0], t.stringLiteral(dependencyId));
const isImport = (node, scope, pattern) => const isImport = (node, scope, patterns) =>
t.isIdentifier(node, pattern) && patterns.some(pattern => {
isToplevelBinding(scope.getBinding(pattern.name)) || const importName = importMap.get(pattern.name) || pattern.name;
isRequireCall(node, pattern.name, scope); return isRequireCall(node, importName, scope);
});
function isImportOrGlobal(node, scope, patterns) {
const identifier = patterns.find(pattern => t.isIdentifier(node, pattern));
return identifier && isToplevelBinding(scope.getBinding(identifier.name)) ||
isImport(node, scope, patterns);
}
const isPlatformOS = (node, scope) => const isPlatformOS = (node, scope) =>
t.isIdentifier(node.property, os) && t.isIdentifier(node.property, os) &&
isImport(node.object, scope, platform); isImportOrGlobal(node.object, scope, [platform]);
const isReactPlatformOS = (node, scope) => const isReactPlatformOS = (node, scope) =>
t.isIdentifier(node.property, os) && t.isIdentifier(node.property, os) &&
t.isMemberExpression(node.object) && t.isMemberExpression(node.object) &&
t.isIdentifier(node.object.property, platform) && t.isIdentifier(node.object.property, platform) &&
isImport(node.object.object, scope, react); isImportOrGlobal(node.object.object, scope, [React, ReactNative]);
const isProcessEnvNodeEnv = (node, scope) => const isProcessEnvNodeEnv = (node, scope) =>
t.isIdentifier(node.property, nodeEnv) && t.isIdentifier(node.property, nodeEnv) &&
@ -53,6 +64,11 @@ const isProcessEnvNodeEnv = (node, scope) =>
t.isIdentifier(node.object.object, processId) && t.isIdentifier(node.object.object, processId) &&
isGlobal(scope.getBinding(processId.name)); isGlobal(scope.getBinding(processId.name));
const isProcessPlatform = (node, scope) =>
t.isIdentifier(node.property, platformId) &&
t.isIdentifier(node.object, processId) &&
isGlobal(scope.getBinding(processId.name));
const isDev = (node, parent, scope) => const isDev = (node, parent, scope) =>
t.isIdentifier(node, dev) && t.isIdentifier(node, dev) &&
isGlobal(scope.getBinding(dev.name)) && isGlobal(scope.getBinding(dev.name)) &&
@ -71,11 +87,12 @@ const inlinePlugin = {
if (isPlatformOS(node, scope) || isReactPlatformOS(node, scope)) { if (isPlatformOS(node, scope) || isReactPlatformOS(node, scope)) {
path.replaceWith(t.stringLiteral(state.opts.platform)); path.replaceWith(t.stringLiteral(state.opts.platform));
} } else if (isProcessEnvNodeEnv(node, scope)) {
if(isProcessEnvNodeEnv(node, scope)) {
path.replaceWith( path.replaceWith(
t.stringLiteral(state.opts.dev ? 'development' : 'production')); t.stringLiteral(state.opts.dev ? 'development' : 'production'));
} else if (isProcessPlatform(node, scope)) {
path.replaceWith(
t.stringLiteral(state.opts.platform));
} }
}, },
}, },