From 17726e1ae6304a8275f0f8e00bc446147175e266 Mon Sep 17 00:00:00 2001 From: David Aurelio Date: Thu, 28 Apr 2016 11:22:37 -0700 Subject: [PATCH] Improve constant inlining, add `process.platform` Reviewed By: bestander Differential Revision: D3235716 fb-gh-sync-id: f9019ec0042827e409fa84ba74f4c426ccad1519 fbshipit-source-id: f9019ec0042827e409fa84ba74f4c426ccad1519 --- .../worker/__tests__/inline-test.js | 75 ++++++++++++++++--- .../src/JSTransformer/worker/inline.js | 37 ++++++--- 2 files changed, 91 insertions(+), 21 deletions(-) diff --git a/react-packager/src/JSTransformer/worker/__tests__/inline-test.js b/react-packager/src/JSTransformer/worker/__tests__/inline-test.js index f4d11b53..ab598388 100644 --- a/react-packager/src/JSTransformer/worker/__tests__/inline-test.js +++ b/react-packager/src/JSTransformer/worker/__tests__/inline-test.js @@ -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')); }); }); diff --git a/react-packager/src/JSTransformer/worker/inline.js b/react-packager/src/JSTransformer/worker/inline.js index a294bcef..996ff51f 100644 --- a/react-packager/src/JSTransformer/worker/inline.js +++ b/react-packager/src/JSTransformer/worker/inline.js @@ -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)); } }, },