Skip to content

Commit

Permalink
Add new worker for code transform, optimization, and dependency extra…
Browse files Browse the repository at this point in the history
…ction

Summary:This adds a new worker implementation that

- uses the existing transforms to transform code
- optionally inline `__DEV__`, `process.env.NODE_ENV`, and `Platform.OS`
- optionally eliminate branches of conditionals with constant conditions
- extracts dependencies
- optionally minifies

This will land as part of a multi-commit stack, not in isolation

Reviewed By: martinbigio

Differential Revision: D2976677

fb-gh-sync-id: 38e317f90b6948b28ef2e3fe8b66fc0b9c75aa38
shipit-source-id: 38e317f90b6948b28ef2e3fe8b66fc0b9c75aa38
  • Loading branch information
davidaurelio authored and Facebook Github Bot 9 committed Mar 1, 2016
1 parent bf0806b commit 5309991
Show file tree
Hide file tree
Showing 10 changed files with 1,173 additions and 0 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,112 @@
/**
* Copyright (c) 2015-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.
*/
'use strict';

jest.autoMockOff();
const babel = require('babel-core');
const constantFolding = require('../constant-folding');

function parse(code) {
return babel.transform(code, {code: false, babelrc: false, compact: true});
}

describe('constant expressions', () => {
it('can optimize conditional expressions with constant conditions', () => {
const code = `
a(
'production'=="production",
'production'!=='development',
false && 1 || 0 || 2,
true || 3,
'android'==='ios' ? null : {},
'android'==='android' ? {a:1} : {a:0},
'foo'==='bar' ? b : c,
f() ? g() : h()
);`;
expect(constantFolding('arbitrary.js', parse(code)).code)
.toEqual(`a(true,true,2,true,{},{a:1},c,f()?g():h());`);
});

it('can optimize ternary expressions with constant conditions', () => {
const code =
`var a = true ? 1 : 2;
var b = 'android' == 'android'
? ('production' != 'production' ? 'a' : 'A')
: 'i';`;
expect(constantFolding('arbitrary.js', parse(code)).code)
.toEqual(`var a=1;var b='A';`);
});

it('can optimize logical operator expressions with constant conditions', () => {
const code = `
var a = true || 1;
var b = 'android' == 'android' &&
'production' != 'production' || null || "A";`;
expect(constantFolding('arbitrary.js', parse(code)).code)
.toEqual(`var a=true;var b="A";`);
});

it('can optimize logical operators with partly constant operands', () => {
const code = `
var a = "truthy" || z();
var b = "truthy" && z();
var c = null && z();
var d = null || z();
var e = !1 && z();
`;
expect(constantFolding('arbitrary.js', parse(code)).code)
.toEqual(`var a="truthy";var b=z();var c=null;var d=z();var e=false;`);
});

it('can remode an if statement with a falsy constant test', () => {
const code = `
if ('production' === 'development' || false) {
var a = 1;
}
`;
expect(constantFolding('arbitrary.js', parse(code)).code)
.toEqual(``);
});

it('can optimize if-else-branches with constant conditions', () => {
const code = `
if ('production' == 'development') {
var a = 1;
var b = a + 2;
} else if ('development' == 'development') {
var a = 3;
var b = a + 4;
} else {
var a = 'b';
}
`;
expect(constantFolding('arbitrary.js', parse(code)).code)
.toEqual(`{var a=3;var b=a+4;}`);
});

it('can optimize nested if-else constructs', () => {
const code = `
if ('ios' === "android") {
if (true) {
require('a');
} else {
require('b');
}
} else if ('android' === 'android') {
if (true) {
require('c');
} else {
require('d');
}
}
`;
expect(constantFolding('arbitrary.js', parse(code)).code)
.toEqual(`{{require('c');}}`);
});
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
/**
* Copyright (c) 2015-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.
*/
'use strict';

jest.autoMockOff();

const extractDependencies = require('../extract-dependencies');

describe('Dependency extraction:', () => {
it('can extract calls to require', () => {
const code = `require('foo/bar');
var React = require("React");
var A = React.createClass({
render: function() {
return require ( "Component" );
}
});
require
('more');`
const {dependencies, dependencyOffsets} = extractDependencies(code);
expect(dependencies)
.toEqual(['foo/bar', 'React', 'Component', 'more']);
expect(dependencyOffsets).toEqual([8, 46, 147, 203]);
});

it('does not extract require method calls', () => {
const code = `
require('a');
foo.require('b');
bar.
require ( 'c').require('d')require('e')`;

const {dependencies, dependencyOffsets} = extractDependencies(code);
expect(dependencies).toEqual(['a', 'e']);
expect(dependencyOffsets).toEqual([15, 97]);
});

it('does not extract require calls from strings', () => {
const code = `require('foo');
var React = '\\'require("React")';
var a = ' // require("yadda")';
var a = ' /* require("yadda") */';
var A = React.createClass({
render: function() {
return require ( "Component" );
}
});
" \\" require('more')";`

const {dependencies, dependencyOffsets} = extractDependencies(code);
expect(dependencies).toEqual(['foo', 'Component']);
expect(dependencyOffsets).toEqual([8, 226]);
});

it('does not extract require calls in comments', () => {
const code = `require('foo')//require("not/this")
/* A comment here with a require('call') that should not be extracted */require('bar')
// ending comment without newline require("baz")`;

const {dependencies, dependencyOffsets} = extractDependencies(code);
expect(dependencies).toEqual(['foo', 'bar']);
expect(dependencyOffsets).toEqual([8, 122]);
});

it('deduplicates dependencies', () => {
const code = `require('foo');require( "foo" );
require("foo");`;

const {dependencies, dependencyOffsets} = extractDependencies(code);
expect(dependencies).toEqual(['foo']);
expect(dependencyOffsets).toEqual([8, 24, 47]);
});

it('does not extract calls to function with names that start with "require"', () => {
const code = `arbitraryrequire('foo');`;

const {dependencies, dependencyOffsets} = extractDependencies(code);
expect(dependencies).toEqual([]);
expect(dependencyOffsets).toEqual([]);
});

it('does not get confused by previous states', () => {
// yes, this was a bug
const code = `require("a");/* a comment */ var a = /[a]/.test('a');`

const {dependencies, dependencyOffsets} = extractDependencies(code);
expect(dependencies).toEqual(['a']);
expect(dependencyOffsets).toEqual([8]);
});
});
133 changes: 133 additions & 0 deletions react-packager/src/JSTransformer/worker/__tests__/inline-test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,133 @@
/**
* Copyright (c) 2015-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.
*/
'use strict';

jest.autoMockOff();
const inline = require('../inline');
const {transform, transformFromAst} = require('babel-core');

const babelOptions = {
babelrc: false,
compact: true,
};

function toString(ast) {
return normalize(transformFromAst(ast, babelOptions).code);
}

function normalize(code) {
return transform(code, babelOptions).code;
}

function toAst(code) {
return transform(code, {...babelOptions, code: false}).ast;
}

describe('inline constants', () => {
it('replaces __DEV__ in the code', () => {
const code = `function a() {
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')));
});

it('replaces Platform.OS in the code if Platform is a global', () => {
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"')));
});

it('replaces Platform.OS in the code if Platform is a top level import', () => {
const code = `
var Platform = require('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"')));
});

it('replaces require("Platform").OS in the code', () => {
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"')));
});

it('replaces React.Platform.OS in the code if React is a global', () => {
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 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"')));
});

it('replaces require("React").Platform.OS in the code', () => {
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 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"')));
});

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: true});
expect(toString(ast)).toEqual(
normalize(code.replace(/process\.env\.NODE_ENV/, '"development"')));
});

it('accepts an AST as input', function() {
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'))
});
});

Loading

0 comments on commit 5309991

Please sign in to comment.