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 4e05d00f0d
commit e6bafca39e
3 changed files with 96 additions and 24 deletions

View File

@ -81,7 +81,7 @@ function polyfillLazyGlobal(name, valueFn, scope = GLOBAL) {
configurable: true,
enumerable: true,
get() {
return this[name] = valueFn();
return (this[name] = valueFn());
},
set(value) {
Object.defineProperty(this, name, {
@ -199,12 +199,14 @@ function setUpProfile() {
}
}
function setUpProcessEnv() {
function setUpProcess() {
GLOBAL.process = GLOBAL.process || {};
GLOBAL.process.env = GLOBAL.process.env || {};
if (!GLOBAL.process.env.NODE_ENV) {
GLOBAL.process.env.NODE_ENV = __DEV__ ? 'development' : 'production';
}
polyfillLazyGlobal('platform', () => require('Platform').OS, GLOBAL.process);
}
function setUpDevTools() {
@ -217,7 +219,7 @@ function setUpDevTools() {
}
}
setUpProcessEnv();
setUpProcess();
setUpConsole();
setUpTimers();
setUpAlert();

View File

@ -35,7 +35,7 @@ describe('inline constants', () => {
var a = __DEV__ ? 1 : 2;
var b = a.__DEV__;
var c = function __DEV__(__DEV__) {};
}`
}`;
const {ast} = inline('arbitrary.js', {code}, {dev: true});
expect(toString(ast)).toEqual(normalize(code.replace(/__DEV__/, 'true')));
});
@ -44,7 +44,7 @@ describe('inline constants', () => {
const code = `function a() {
var 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"')));
});
@ -55,7 +55,18 @@ describe('inline constants', () => {
function a() {
if (Platform.OS === 'android') a = function() {};
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'});
expect(toString(ast)).toEqual(normalize(code.replace(/Platform\.OS/, '"ios"')));
});
@ -64,7 +75,7 @@ describe('inline constants', () => {
const code = `function a() {
var a = require('Platform').OS;
var b = a.require('Platform').OS;
}`
}`;
const {ast} = inline('arbitrary.js', {code}, {platform: 'android'});
expect(toString(ast)).toEqual(
normalize(code.replace(/require\('Platform'\)\.OS/, '"android"')));
@ -74,18 +85,27 @@ describe('inline constants', () => {
const code = `function a() {
var a = React.Platform.OS;
var b = a.React.Platform.OS;
}`
}`;
const {ast} = inline('arbitrary.js', {code}, {platform: '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', () => {
const code = `
var React = require('React');
function a() {
if (React.Platform.OS === 'android') a = function() {};
var b = a.React.Platform.OS;
}`
}`;
const {ast} = inline('arbitrary.js', {code}, {platform: 'ios'});
expect(toString(ast)).toEqual(normalize(code.replace(/React.Platform\.OS/, '"ios"')));
});
@ -94,19 +114,40 @@ describe('inline constants', () => {
const code = `function a() {
var a = require('React').Platform.OS;
var b = a.require('React').Platform.OS;
}`
}`;
const {ast} = inline('arbitrary.js', {code}, {platform: 'android'});
expect(toString(ast)).toEqual(
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', () => {
const code = `function a() {
if (process.env.NODE_ENV === 'production') {
return require('Prod');
}
return require('Dev');
}`
}`;
const {ast} = inline('arbitrary.js', {code}, {dev: false});
expect(toString(ast)).toEqual(
normalize(code.replace(/process\.env\.NODE_ENV/, '"production"')));
@ -118,16 +159,28 @@ describe('inline constants', () => {
return require('Prod');
}
return require('Dev');
}`
}`;
const {ast} = inline('arbitrary.js', {code}, {dev: true});
expect(toString(ast)).toEqual(
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() {
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});
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 t = babel.types;
const react = {name: 'React'};
const React = {name: 'React'};
const ReactNative = {name: 'ReactNative'};
const platform = {name: 'Platform'};
const os = {name: 'OS'};
const requirePattern = {name: 'require'};
@ -19,9 +20,12 @@ const requirePattern = {name: 'require'};
const env = {name: 'env'};
const nodeEnv = {name: 'NODE_ENV'};
const processId = {name: 'process'};
const platformId = {name: 'platform'};
const dev = {name: '__DEV__'};
const importMap = new Map([['ReactNative', 'react-native']]);
const isGlobal = (binding) => !binding;
const isToplevelBinding = (binding) => isGlobal(binding) || !binding.scope.parent;
@ -31,20 +35,27 @@ const isRequireCall = (node, dependencyId, scope) =>
t.isIdentifier(node.callee, requirePattern) &&
t.isStringLiteral(node.arguments[0], t.stringLiteral(dependencyId));
const isImport = (node, scope, pattern) =>
t.isIdentifier(node, pattern) &&
isToplevelBinding(scope.getBinding(pattern.name)) ||
isRequireCall(node, pattern.name, scope);
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) {
const identifier = patterns.find(pattern => t.isIdentifier(node, pattern));
return identifier && isToplevelBinding(scope.getBinding(identifier.name)) ||
isImport(node, scope, patterns);
}
const isPlatformOS = (node, scope) =>
t.isIdentifier(node.property, os) &&
isImport(node.object, scope, platform);
isImportOrGlobal(node.object, scope, [platform]);
const isReactPlatformOS = (node, scope) =>
t.isIdentifier(node.property, os) &&
t.isMemberExpression(node.object) &&
t.isIdentifier(node.object.property, platform) &&
isImport(node.object.object, scope, react);
isImportOrGlobal(node.object.object, scope, [React, ReactNative]);
const isProcessEnvNodeEnv = (node, scope) =>
t.isIdentifier(node.property, nodeEnv) &&
@ -53,6 +64,11 @@ const isProcessEnvNodeEnv = (node, scope) =>
t.isIdentifier(node.object.object, processId) &&
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) =>
t.isIdentifier(node, dev) &&
isGlobal(scope.getBinding(dev.name)) &&
@ -71,11 +87,12 @@ const inlinePlugin = {
if (isPlatformOS(node, scope) || isReactPlatformOS(node, scope)) {
path.replaceWith(t.stringLiteral(state.opts.platform));
}
if(isProcessEnvNodeEnv(node, scope)) {
} else if (isProcessEnvNodeEnv(node, scope)) {
path.replaceWith(
t.stringLiteral(state.opts.dev ? 'development' : 'production'));
} else if (isProcessPlatform(node, scope)) {
path.replaceWith(
t.stringLiteral(state.opts.platform));
}
},
},