-
Notifications
You must be signed in to change notification settings - Fork 634
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Add new worker for code transform, optimization, and dependency extra…
…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
1 parent
bf0806b
commit 5309991
Showing
10 changed files
with
1,173 additions
and
0 deletions.
There are no files selected for viewing
112 changes: 112 additions & 0 deletions
112
react-packager/src/JSTransformer/worker/__tests__/constant-folding-test.js
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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');}}`); | ||
}); | ||
}); |
96 changes: 96 additions & 0 deletions
96
react-packager/src/JSTransformer/worker/__tests__/extract-dependencies-test.js
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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
133
react-packager/src/JSTransformer/worker/__tests__/inline-test.js
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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')) | ||
}); | ||
}); | ||
|
Oops, something went wrong.