@format JSTransformer

Reviewed By: jeanlauliac

Differential Revision: D5208868

fbshipit-source-id: 3b80197c4e879974f9129ccfc4e4a7ca7d4b4258
This commit is contained in:
Christoph Pojer 2017-06-08 07:32:15 -07:00 committed by Facebook Github Bot
parent 5e6d70d4d6
commit 802094c6f0
15 changed files with 397 additions and 198 deletions

View File

@ -5,6 +5,8 @@
* 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.
*
* @format
*/
'use strict';

View File

@ -5,6 +5,8 @@
* 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.
*
* @format
*/
'use strict';

View File

@ -5,6 +5,8 @@
* 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.
*
* @format
*/
'use strict';
@ -34,48 +36,68 @@ describe('Transformer', function() {
fs.writeFileSync.mockClear();
workerFarm.mockClear();
workerFarm.mockImplementation((opts, path, methods) => {
const api = workers = {};
methods.forEach(method => {api[method] = jest.fn();});
return {methods: api, stdout: new Readable({read() {}}), stderr: new Readable({read() {}})};
const api = (workers = {});
methods.forEach(method => {
api[method] = jest.fn();
});
return {
methods: api,
stdout: new Readable({read() {}}),
stderr: new Readable({read() {}}),
};
});
});
it('passes transform module path, file path, source code' +
' to the worker farm when transforming', () => {
const transformOptions = {arbitrary: 'options'};
const code = 'arbitrary(code)';
new Transformer(transformModulePath).transformFile(fileName, localPath, code, transformOptions);
expect(workers.transformAndExtractDependencies).toBeCalledWith(
transformModulePath,
fileName,
localPath,
code,
transformOptions,
any(Function),
);
});
it(
'passes transform module path, file path, source code' +
' to the worker farm when transforming',
() => {
const transformOptions = {arbitrary: 'options'};
const code = 'arbitrary(code)';
new Transformer(transformModulePath).transformFile(
fileName,
localPath,
code,
transformOptions,
);
expect(workers.transformAndExtractDependencies).toBeCalledWith(
transformModulePath,
fileName,
localPath,
code,
transformOptions,
any(Function),
);
},
);
it('should add file info to parse errors', function() {
const transformer = new Transformer(transformModulePath);
var message = 'message';
var snippet = 'snippet';
workers.transformAndExtractDependencies.mockImplementation(
function(transformPath, filename, localPth, code, opts, callback) {
var babelError = new SyntaxError(message);
babelError.type = 'SyntaxError';
babelError.description = message;
babelError.loc = {
line: 2,
column: 15,
};
babelError.codeFrame = snippet;
callback(babelError);
},
);
workers.transformAndExtractDependencies.mockImplementation(function(
transformPath,
filename,
localPth,
code,
opts,
callback,
) {
var babelError = new SyntaxError(message);
babelError.type = 'SyntaxError';
babelError.description = message;
babelError.loc = {
line: 2,
column: 15,
};
babelError.codeFrame = snippet;
callback(babelError);
});
expect.assertions(7);
return transformer.transformFile(fileName, localPath, '', {})
return transformer
.transformFile(fileName, localPath, '', {})
.catch(function(error) {
expect(error.type).toEqual('TransformError');
expect(error.message).toBe('SyntaxError ' + message);

View File

@ -7,6 +7,7 @@
* of patent rights can be found in the PATENTS file in the same directory.
*
* @flow
* @format
*/
'use strict';
@ -23,12 +24,18 @@ const workerFarm = require('../worker-farm');
import type {Data as TransformData, Options as WorkerOptions} from './worker';
import type {LocalPath} from '../node-haste/lib/toLocalPath';
import type {MappingsMap} from '../lib/SourceMap';
import typeof {minify as Minify, transformAndExtractDependencies as TransformAndExtractDependencies} from './worker';
import typeof {
minify as Minify,
transformAndExtractDependencies as TransformAndExtractDependencies,
} from './worker';
type CB<T> = (?Error, ?T) => mixed;
type Denodeify =
& (<A, B, C, T>((A, B, C, CB<T>) => void) => (A, B, C) => Promise<T>)
& (<A, B, C, D, E, T>((A, B, C, D, E, CB<T>) => void) => (A, B, C, D, E) => Promise<T>);
type Denodeify = (<A, B, C, T>(
(A, B, C, CB<T>) => void,
) => (A, B, C) => Promise<T>) &
(<A, B, C, D, E, T>(
(A, B, C, D, E, CB<T>) => void,
) => (A, B, C, D, E) => Promise<T>);
// Avoid memory leaks caused in workers. This number seems to be a good enough number
// to avoid any memory leak while not slowing down initial builds.
@ -63,7 +70,6 @@ type Reporters = {
};
class Transformer {
_workers: {[name: string]: Function};
_transformModulePath: string;
_transform: (
@ -85,7 +91,10 @@ class Transformer {
reporters: Reporters,
workerPath: ?string,
) {
invariant(path.isAbsolute(transformModulePath), 'transform module path should be absolute');
invariant(
path.isAbsolute(transformModulePath),
'transform module path should be absolute',
);
this._transformModulePath = transformModulePath;
const farm = makeFarm(
@ -102,7 +111,10 @@ class Transformer {
});
this._workers = farm.methods;
this._transform = denodeify((this._workers.transformAndExtractDependencies: TransformAndExtractDependencies));
this._transform = denodeify(
(this._workers
.transformAndExtractDependencies: TransformAndExtractDependencies),
);
this.minify = denodeify((this._workers.minify: Minify));
}
@ -114,19 +126,19 @@ class Transformer {
fileName: string,
localPath: LocalPath,
code: string,
options: WorkerOptions) {
options: WorkerOptions,
) {
if (!this._transform) {
return Promise.reject(new Error('No transform module'));
}
debug('transforming file', fileName);
return this
._transform(
this._transformModulePath,
fileName,
localPath,
code,
options,
)
return this._transform(
this._transformModulePath,
fileName,
localPath,
code,
options,
)
.then(data => {
Logger.log(data.transformFileStartLogEntry);
Logger.log(data.transformFileEndLogEntry);
@ -137,8 +149,8 @@ class Transformer {
if (error.type === 'TimeoutError') {
const timeoutErr = new Error(
`TimeoutError: transforming ${fileName} took longer than ` +
`${TRANSFORM_TIMEOUT_INTERVAL / 1000} seconds.\n` +
'You can adjust timeout via the \'transformTimeoutInterval\' option'
`${TRANSFORM_TIMEOUT_INTERVAL / 1000} seconds.\n` +
"You can adjust timeout via the 'transformTimeoutInterval' option",
);
/* $FlowFixMe: monkey-patch Error */
timeoutErr.type = 'TimeoutError';
@ -146,7 +158,7 @@ class Transformer {
} else if (error.type === 'ProcessTerminatedError') {
const uncaughtError = new Error(
'Uncaught error in the transformer worker: ' +
this._transformModulePath
this._transformModulePath,
);
/* $FlowFixMe: monkey-patch Error */
uncaughtError.type = 'ProcessTerminatedError';

View File

@ -7,6 +7,7 @@
* of patent rights can be found in the PATENTS file in the same directory.
*
* @flow
* @format
*/
'use strict';

View File

@ -5,6 +5,8 @@
* 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.
*
* @format
*/
'use strict';
@ -38,18 +40,19 @@ describe('constant expressions', () => {
'foo'==='bar' ? b : c,
f() ? g() : h()
);`;
expect(normalize(constantFolding('arbitrary.js', parse(code))))
.toEqual('a(true,true,2,true,{},{a:1},c,f()?g():h());');
expect(normalize(constantFolding('arbitrary.js', parse(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;
const code = `var a = true ? 1 : 2;
var b = 'android' == 'android'
? ('production' != 'production' ? 'a' : 'A')
: 'i';`;
expect(normalize(constantFolding('arbitrary.js', parse(code))))
.toEqual('var a=1;var b=\'A\';');
expect(normalize(constantFolding('arbitrary.js', parse(code)))).toEqual(
"var a=1;var b='A';",
);
});
it('can optimize logical operator expressions with constant conditions', () => {
@ -57,8 +60,9 @@ describe('constant expressions', () => {
var a = true || 1;
var b = 'android' == 'android' &&
'production' != 'production' || null || "A";`;
expect(normalize(constantFolding('arbitrary.js', parse(code))))
.toEqual('var a=true;var b="A";');
expect(normalize(constantFolding('arbitrary.js', parse(code)))).toEqual(
'var a=true;var b="A";',
);
});
it('can optimize logical operators with partly constant operands', () => {
@ -69,8 +73,9 @@ describe('constant expressions', () => {
var d = null || z();
var e = !1 && z();
`;
expect(normalize(constantFolding('arbitrary.js', parse(code))))
.toEqual('var a="truthy";var b=z();var c=null;var d=z();var e=false;');
expect(normalize(constantFolding('arbitrary.js', parse(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', () => {
@ -79,8 +84,7 @@ describe('constant expressions', () => {
var a = 1;
}
`;
expect(normalize(constantFolding('arbitrary.js', parse(code))))
.toEqual('');
expect(normalize(constantFolding('arbitrary.js', parse(code)))).toEqual('');
});
it('can optimize if-else-branches with constant conditions', () => {
@ -95,8 +99,9 @@ describe('constant expressions', () => {
var a = 'b';
}
`;
expect(normalize(constantFolding('arbitrary.js', parse(code))))
.toEqual('{var a=3;var b=a+4;}');
expect(normalize(constantFolding('arbitrary.js', parse(code)))).toEqual(
'{var a=3;var b=a+4;}',
);
});
it('can optimize nested if-else constructs', () => {
@ -115,7 +120,8 @@ describe('constant expressions', () => {
}
}
`;
expect(normalize(constantFolding('arbitrary.js', parse(code))))
.toEqual('{{require(\'c\');}}');
expect(normalize(constantFolding('arbitrary.js', parse(code)))).toEqual(
"{{require('c');}}",
);
});
});

View File

@ -5,6 +5,8 @@
* 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.
*
* @format
*/
'use strict';
@ -22,8 +24,7 @@ describe('Dependency extraction:', () => {
require
('more');`;
const {dependencies, dependencyOffsets} = extractDependencies(code);
expect(dependencies)
.toEqual(['foo/bar', 'React', 'Component', 'more']);
expect(dependencies).toEqual(['foo/bar', 'React', 'Component', 'more']);
expect(dependencyOffsets).toEqual([8, 46, 147, 203]);
});
@ -76,7 +77,7 @@ describe('Dependency extraction:', () => {
});
it('does not extract calls to function with names that start with "require"', () => {
const code = 'arbitraryrequire(\'foo\');';
const code = "arbitraryrequire('foo');";
const {dependencies, dependencyOffsets} = extractDependencies(code);
expect(dependencies).toEqual([]);
@ -84,7 +85,7 @@ describe('Dependency extraction:', () => {
});
it('does not extract calls to require with non-static arguments', () => {
const code = 'require(\'foo/\' + bar)';
const code = "require('foo/' + bar)";
const {dependencies, dependencyOffsets} = extractDependencies(code);
expect(dependencies).toEqual([]);
@ -101,7 +102,7 @@ describe('Dependency extraction:', () => {
});
it('can handle regular expressions', () => {
const code = 'require(\'a\'); /["\']/.test(\'foo\'); require("b");';
const code = "require('a'); /[\"']/.test('foo'); require(\"b\");";
const {dependencies, dependencyOffsets} = extractDependencies(code);
expect(dependencies).toEqual(['a', 'b']);

View File

@ -5,6 +5,8 @@
* 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.
*
* @format
*/
'use strict';
@ -47,7 +49,9 @@ describe('inline constants', () => {
var b = a.Platform.OS;
}`;
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"')),
);
});
it('replaces Platform.OS in the code if Platform is a top level import', () => {
@ -58,7 +62,9 @@ describe('inline constants', () => {
var b = a.Platform.OS;
}`;
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"')),
);
});
it('replaces Platform.OS in the code if Platform is a top level import from react-native', () => {
@ -69,7 +75,9 @@ describe('inline constants', () => {
var b = a.Platform.OS;
}`;
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"')),
);
});
it('replaces require("Platform").OS in the code', () => {
@ -79,7 +87,8 @@ describe('inline constants', () => {
}`;
const {ast} = inline('arbitrary.js', {code}, {platform: 'android'});
expect(toString(ast)).toEqual(
normalize(code.replace(/require\('Platform'\)\.OS/, '"android"')));
normalize(code.replace(/require\('Platform'\)\.OS/, '"android"')),
);
});
it('replaces React.Platform.OS in the code if React is a global', () => {
@ -88,7 +97,9 @@ describe('inline constants', () => {
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"')));
expect(toString(ast)).toEqual(
normalize(code.replace(/React\.Platform\.OS/, '"ios"')),
);
});
it('replaces ReactNative.Platform.OS in the code if ReactNative is a global', () => {
@ -97,7 +108,9 @@ describe('inline constants', () => {
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"')));
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', () => {
@ -108,7 +121,9 @@ describe('inline constants', () => {
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"')));
expect(toString(ast)).toEqual(
normalize(code.replace(/React.Platform\.OS/, '"ios"')),
);
});
it('replaces require("React").Platform.OS in the code', () => {
@ -118,7 +133,8 @@ describe('inline constants', () => {
}`;
const {ast} = inline('arbitrary.js', {code}, {platform: 'android'});
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', () => {
@ -129,7 +145,9 @@ describe('inline constants', () => {
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"')));
expect(toString(ast)).toEqual(
normalize(code.replace(/ReactNative.Platform\.OS/, '"android"')),
);
});
it('replaces require("react-native").Platform.OS in the code', () => {
@ -139,7 +157,10 @@ describe('inline constants', () => {
}`;
const {ast} = inline('arbitrary.js', {code}, {platform: 'android'});
expect(toString(ast)).toEqual(
normalize(code.replace(/require\('react-native'\)\.Platform\.OS/, '"android"')));
normalize(
code.replace(/require\('react-native'\)\.Platform\.OS/, '"android"'),
),
);
});
it('inlines Platform.select in the code if Platform is a global and the argument is an object literal', () => {
@ -148,16 +169,20 @@ describe('inline constants', () => {
var b = a.Platform.select({ios: 1, android: 2});
}`;
const {ast} = inline('arbitrary.js', {code}, {platform: 'ios'});
expect(toString(ast)).toEqual(normalize(code.replace(/Platform\.select[^;]+/, '1')));
expect(toString(ast)).toEqual(
normalize(code.replace(/Platform\.select[^;]+/, '1')),
);
});
it('inlines Platform.select in the code if Platform is a global and the argument doesn\'t have target platform in it\'s keys', () => {
it("inlines Platform.select in the code if Platform is a global and the argument doesn't have target platform in it's keys", () => {
const code = `function a() {
var a = Platform.select({ios: 1, default: 2});
var b = a.Platform.select({ios: 1, default: 2});
}`;
const {ast} = inline('arbitrary.js', {code}, {platform: 'android'});
expect(toString(ast)).toEqual(normalize(code.replace(/Platform\.select[^;]+/, '2')));
expect(toString(ast)).toEqual(
normalize(code.replace(/Platform\.select[^;]+/, '2')),
);
});
it('replaces Platform.select in the code if Platform is a top level import', () => {
@ -168,7 +193,9 @@ describe('inline constants', () => {
var b = a.Platform.select({});
}`;
const {ast} = inline('arbitrary.js', {code}, {platform: 'android'});
expect(toString(ast)).toEqual(normalize(code.replace(/Platform\.select[^;]+/, '2')));
expect(toString(ast)).toEqual(
normalize(code.replace(/Platform\.select[^;]+/, '2')),
);
});
it('replaces Platform.select in the code if Platform is a top level import from react-native', () => {
@ -179,7 +206,9 @@ describe('inline constants', () => {
var b = a.Platform.select({});
}`;
const {ast} = inline('arbitrary.js', {code}, {platform: 'ios'});
expect(toString(ast)).toEqual(normalize(code.replace(/Platform\.select[^;]+/, '1')));
expect(toString(ast)).toEqual(
normalize(code.replace(/Platform\.select[^;]+/, '1')),
);
});
it('replaces require("Platform").select in the code', () => {
@ -188,7 +217,9 @@ describe('inline constants', () => {
var b = a.require('Platform').select({});
}`;
const {ast} = inline('arbitrary.js', {code}, {platform: 'android'});
expect(toString(ast)).toEqual(normalize(code.replace(/Platform\.select[^;]+/, '2')));
expect(toString(ast)).toEqual(
normalize(code.replace(/Platform\.select[^;]+/, '2')),
);
});
it('replaces React.Platform.select in the code if React is a global', () => {
@ -197,7 +228,9 @@ describe('inline constants', () => {
var b = a.React.Platform.select({});
}`;
const {ast} = inline('arbitrary.js', {code}, {platform: 'ios'});
expect(toString(ast)).toEqual(normalize(code.replace(/React\.Platform\.select[^;]+/, '1')));
expect(toString(ast)).toEqual(
normalize(code.replace(/React\.Platform\.select[^;]+/, '1')),
);
});
it('replaces ReactNative.Platform.select in the code if ReactNative is a global', () => {
@ -219,7 +252,9 @@ describe('inline constants', () => {
var b = a.React.Platform.select({});
}`;
const {ast} = inline('arbitrary.js', {code}, {platform: 'ios'});
expect(toString(ast)).toEqual(normalize(code.replace(/React\.Platform\.select[^;]+/, '1')));
expect(toString(ast)).toEqual(
normalize(code.replace(/React\.Platform\.select[^;]+/, '1')),
);
});
it('replaces require("React").Platform.select in the code', () => {
@ -229,7 +264,8 @@ describe('inline constants', () => {
}`;
const {ast} = inline('arbitrary.js', {code}, {platform: 'android'});
expect(toString(ast)).toEqual(
normalize(code.replace(/require\('React'\)\.Platform\.select[^;]+/, '2')));
normalize(code.replace(/require\('React'\)\.Platform\.select[^;]+/, '2')),
);
});
it('replaces ReactNative.Platform.select in the code if ReactNative is a top level import', () => {
@ -252,14 +288,18 @@ describe('inline constants', () => {
`;
const {ast} = inline('arbitrary.js', {code}, {platform: 'android'});
expect(toString(ast)).toEqual(
normalize(code.replace(/require\('react-native'\)\.Platform\.select[^;]+/, '2')));
normalize(
code.replace(/require\('react-native'\)\.Platform\.select[^;]+/, '2'),
),
);
});
it('replaces non-existing properties with `undefined`', () => {
const code = 'var a = Platform.select({ios: 1, android: 2})';
const {ast} = inline('arbitrary.js', {code}, {platform: 'doesnotexist'});
expect(toString(ast)).toEqual(
normalize(code.replace(/Platform\.select[^;]+/, 'undefined')));
normalize(code.replace(/Platform\.select[^;]+/, 'undefined')),
);
});
it('replaces process.env.NODE_ENV in the code', () => {
@ -271,7 +311,8 @@ describe('inline constants', () => {
}`;
const {ast} = inline('arbitrary.js', {code}, {dev: false});
expect(toString(ast)).toEqual(
normalize(code.replace(/process\.env\.NODE_ENV/, '"production"')));
normalize(code.replace(/process\.env\.NODE_ENV/, '"production"')),
);
});
it('accepts an AST as input', function() {
@ -286,13 +327,16 @@ describe('inline constants', () => {
var a = Platform.OS, b = Platform.select({android: 1, ios: 2});
});`;
const {ast} = inline(
'arbitrary', {code}, {dev: true, platform: 'android', isWrapped: true});
'arbitrary',
{code},
{dev: true, platform: 'android', isWrapped: true},
);
expect(toString(ast)).toEqual(
normalize(
code
.replace(/Platform\.OS/, '"android"')
.replace(/Platform\.select[^)]+\)/, 1)
)
.replace(/Platform\.select[^)]+\)/, 1),
),
);
});
@ -301,9 +345,13 @@ describe('inline constants', () => {
var a = require(arbitraryMapName[123], 'react-native').Platform.OS;
});`;
const {ast} = inline(
'arbitrary', {code}, {dev: true, platform: 'android', isWrapped: true});
'arbitrary',
{code},
{dev: true, platform: 'android', isWrapped: true},
);
expect(toString(ast)).toEqual(
normalize(code.replace(/require\([^)]+\)\.Platform\.OS/, '"android"')));
normalize(code.replace(/require\([^)]+\)\.Platform\.OS/, '"android"')),
);
});
it('works with flow-declared variables', () => {
@ -311,10 +359,10 @@ describe('inline constants', () => {
const code = `declare var __DEV__;
const a: boolean = __DEV__;`;
const transformed = transform(
code,
{...babelOptions, plugins: [stripFlow, [inline.plugin, {dev: false}]]},
).code;
const transformed = transform(code, {
...babelOptions,
plugins: [stripFlow, [inline.plugin, {dev: false}]],
}).code;
expect(transformed).toEqual('const a=false;');
});
@ -326,10 +374,10 @@ describe('inline constants', () => {
const a: boolean = __DEV__;
});`;
const transformed = transform(
code,
{...babelOptions, plugins: [stripFlow, [inline.plugin, {dev: true}]]},
).code;
const transformed = transform(code, {
...babelOptions,
plugins: [stripFlow, [inline.plugin, {dev: true}]],
}).code;
expect(transformed).toEqual('__d(()=>{const a=true;});');
});

View File

@ -5,6 +5,8 @@
* 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.
*
* @format
*/
'use strict';
@ -35,11 +37,14 @@ describe('Minification:', () => {
it('passes file name, code, and source map to `uglify`', () => {
minify(filename, code, map);
expect(uglify.minify).toBeCalledWith(code, objectContaining({
fromString: true,
inSourceMap: map,
outSourceMap: true,
}));
expect(uglify.minify).toBeCalledWith(
code,
objectContaining({
fromString: true,
inSourceMap: map,
outSourceMap: true,
}),
);
});
it('returns the code provided by uglify', () => {

View File

@ -5,6 +5,8 @@
* 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.
*
* @format
*/
'use strict';
@ -22,8 +24,9 @@ describe('code transformation worker:', () => {
beforeEach(() => {
jest.resetModules();
({transformCode} = require('..'));
extractDependencies =
require('../extract-dependencies').mockReturnValue({});
extractDependencies = require('../extract-dependencies').mockReturnValue(
{},
);
transformer = {
transform: jest.fn(({filename, options, src}) => ({
code: src,
@ -37,7 +40,14 @@ describe('code transformation worker:', () => {
const localPath = `local/${filename}`;
const sourceCode = 'arbitrary(code)';
const transformOptions = {arbitrary: 'options'};
transformCode(transformer, filename, localPath, sourceCode, {transform: transformOptions}, () => {});
transformCode(
transformer,
filename,
localPath,
sourceCode,
{transform: transformOptions},
() => {},
);
expect(transformer.transform).toBeCalledWith({
filename,
localPath,
@ -65,26 +75,39 @@ describe('code transformation worker:', () => {
map: {},
};
transformCode(transformer, 'filename', 'local/filename', result.code, {}, (error, data) => {
expect(error).toBeNull();
expect(data.result).toEqual(objectContaining(result));
done();
});
transformCode(
transformer,
'filename',
'local/filename',
result.code,
{},
(error, data) => {
expect(error).toBeNull();
expect(data.result).toEqual(objectContaining(result));
done();
},
);
});
it(
'removes the leading assignment to `module.exports` before passing ' +
'on the result if the file is a JSON file, even if minified',
'on the result if the file is a JSON file, even if minified',
done => {
const code = '{a:1,b:2}';
const filePath = 'arbitrary/file.json';
transformCode(transformer, filePath, filePath, code, {}, (error, data) => {
expect(error).toBeNull();
expect(data.result.code).toEqual(code);
done();
},
transformCode(
transformer,
filePath,
filePath,
code,
{},
(error, data) => {
expect(error).toBeNull();
expect(data.result.code).toEqual(code);
done();
},
);
}
},
);
it('removes shebang when present', done => {
@ -93,13 +116,20 @@ describe('code transformation worker:', () => {
code: `${shebang} \n arbitrary(code)`,
};
const filePath = 'arbitrary/file.js';
transformCode(transformer, filePath, filePath, result.code, {}, (error, data) => {
expect(error).toBeNull();
const {code} = data.result;
expect(code).not.toContain(shebang);
expect(code.split('\n').length).toEqual(result.code.split('\n').length);
done();
});
transformCode(
transformer,
filePath,
filePath,
result.code,
{},
(error, data) => {
expect(error).toBeNull();
const {code} = data.result;
expect(code).not.toContain(shebang);
expect(code.split('\n').length).toEqual(result.code.split('\n').length);
done();
},
);
});
it('calls back with any error yielded by the transform', done => {
@ -108,51 +138,78 @@ describe('code transformation worker:', () => {
throw new Error(message);
});
transformCode(transformer, 'filename', 'local/filename', 'code', {}, error => {
expect(error.message).toBe(message);
done();
});
transformCode(
transformer,
'filename',
'local/filename',
'code',
{},
error => {
expect(error.message).toBe(message);
done();
},
);
});
describe('dependency extraction', () => {
it('passes the transformed code the `extractDependencies`', done => {
const code = 'arbitrary(code)';
transformCode(transformer, 'filename', 'local/filename', code, {}, error => {
expect(error).toBeNull();
expect(extractDependencies).toBeCalledWith(code);
done();
});
transformCode(
transformer,
'filename',
'local/filename',
code,
{},
error => {
expect(error).toBeNull();
expect(extractDependencies).toBeCalledWith(code);
done();
},
);
});
it(
'uses `dependencies` and `dependencyOffsets` ' +
'provided by `extractDependencies` for the result',
'provided by `extractDependencies` for the result',
done => {
const dependencyData = {
dependencies: ['arbitrary', 'list', 'of', 'dependencies'],
dependencyOffsets: [12, 119, 185, 328, 471],
dependencyOffsets: [12, 119, 185, 328, 471],
};
extractDependencies.mockReturnValue(dependencyData);
transformCode(transformer, 'filename', 'local/filename', 'code', {}, (error, data) => {
expect(error).toBeNull();
expect(data.result).toEqual(objectContaining(dependencyData));
done();
});
}
transformCode(
transformer,
'filename',
'local/filename',
'code',
{},
(error, data) => {
expect(error).toBeNull();
expect(data.result).toEqual(objectContaining(dependencyData));
done();
},
);
},
);
it('does not extract requires of JSON files', done => {
const jsonStr = '{"arbitrary":"json"}';
transformCode(transformer, 'arbitrary.json', 'local/arbitrary.json', jsonStr, {}, (error, data) => {
expect(error).toBeNull();
const {dependencies, dependencyOffsets} = data.result;
expect(extractDependencies).not.toBeCalled();
expect(dependencies).toEqual([]);
expect(dependencyOffsets).toEqual([]);
done();
}
transformCode(
transformer,
'arbitrary.json',
'local/arbitrary.json',
jsonStr,
{},
(error, data) => {
expect(error).toBeNull();
const {dependencies, dependencyOffsets} = data.result;
expect(extractDependencies).not.toBeCalled();
expect(dependencies).toEqual([]);
expect(dependencyOffsets).toEqual([]);
done();
},
);
});
});
@ -165,8 +222,10 @@ describe('code transformation worker:', () => {
const foldedMap = {version: 3, sources: ['fold.js']};
beforeEach(() => {
constantFolding = require('../constant-folding')
.mockReturnValue({code: foldedCode, map: foldedMap});
constantFolding = require('../constant-folding').mockReturnValue({
code: foldedCode,
map: foldedMap,
});
extractDependencies = require('../extract-dependencies');
inline = require('../inline');
@ -177,9 +236,12 @@ describe('code transformation worker:', () => {
};
extractDependencies.mockImplementation(
code => code === foldedCode ? dependencyData : {});
code => (code === foldedCode ? dependencyData : {}),
);
transformer.transform.mockImplementation((src, fileName, _) => transformResult);
transformer.transform.mockImplementation(
(src, fileName, _) => transformResult,
);
});
it('passes the transform result to `inline` for constant inlining', done => {
@ -207,19 +269,34 @@ describe('code transformation worker:', () => {
});
it('uses the dependencies obtained from the optimized result', done => {
transformCode(transformer, filename, filename, 'code', options, (_, data) => {
const result = data.result;
expect(result.dependencies).toEqual(dependencyData.dependencies);
done();
});
transformCode(
transformer,
filename,
filename,
'code',
options,
(_, data) => {
const result = data.result;
expect(result.dependencies).toEqual(dependencyData.dependencies);
done();
},
);
});
it('uses data produced by `constant-folding` for the result', done => {
transformCode(transformer, 'filename', 'local/filename', 'code', options, (_, data) => {
expect(data.result)
.toEqual(objectContaining({code: foldedCode, map: foldedMap}));
done();
});
transformCode(
transformer,
'filename',
'local/filename',
'code',
options,
(_, data) => {
expect(data.result).toEqual(
objectContaining({code: foldedCode, map: foldedMap}),
);
done();
},
);
});
});
});

View File

@ -7,6 +7,7 @@
* of patent rights can be found in the PATENTS file in the same directory.
*
* @flow
* @format
*/
'use strict';
@ -70,11 +71,14 @@ const plugin = {
},
};
function constantFolding(filename: string, transformResult: {
ast: Ast,
code?: ?string,
map: ?MappingsMap,
}) {
function constantFolding(
filename: string,
transformResult: {
ast: Ast,
code?: ?string,
map: ?MappingsMap,
},
) {
return babel.transformFromAst(transformResult.ast, transformResult.code, {
filename,
plugins: [plugin],

View File

@ -7,6 +7,7 @@
* of patent rights can be found in the PATENTS file in the same directory.
*
* @flow
* @format
*/
'use strict';

View File

@ -7,6 +7,7 @@
* of patent rights can be found in the PATENTS file in the same directory.
*
* @flow
* @format
*/
'use strict';
@ -41,7 +42,6 @@ export type Transformer<ExtraOptions: {} = {}> = {
getCacheKey: () => string,
};
export type TransformOptionsStrict = {|
+dev: boolean,
+generateSourceMaps: boolean,
@ -73,10 +73,7 @@ export type Data = {
transformFileEndLogEntry: LogEntry,
};
type Callback<T> = (
error: ?Error,
data: ?T,
) => mixed;
type Callback<T> = (error: ?Error, data: ?T) => mixed;
function transformCode(
transformer: Transformer<*>,
@ -124,8 +121,10 @@ function transformCode(
var code, map;
if (options.minify) {
({code, map} =
constantFolding(filename, inline(filename, transformed, options)));
({code, map} = constantFolding(
filename,
inline(filename, transformed, options),
));
invariant(code != null, 'Missing code from constant-folding transform.');
} else {
({code, map} = transformed);
@ -170,7 +169,14 @@ exports.transformAndExtractDependencies = (
babelRegisterOnly([transform]);
/* $FlowFixMe: impossible to type a dynamic require */
const transformModule: Transformer<*> = require(transform);
transformCode(transformModule, filename, localPath, sourceCode, options, callback);
transformCode(
transformModule,
filename,
localPath,
sourceCode,
options,
callback,
);
};
exports.minify = (

View File

@ -7,6 +7,7 @@
* of patent rights can be found in the PATENTS file in the same directory.
*
* @flow
* @format
*/
'use strict';
@ -34,8 +35,7 @@ const importMap = new Map([['ReactNative', 'react-native']]);
const isGlobal = binding => !binding;
const isFlowDeclared = binding =>
t.isDeclareVariable(binding.path);
const isFlowDeclared = binding => t.isDeclareVariable(binding.path);
const isGlobalOrFlowDeclared = binding =>
isGlobal(binding) || isFlowDeclared(binding);
@ -43,7 +43,7 @@ const isGlobalOrFlowDeclared = binding =>
const isToplevelBinding = (binding, isWrappedModule) =>
isGlobal(binding) ||
!binding.scope.parent ||
isWrappedModule && !binding.scope.parent.parent;
(isWrappedModule && !binding.scope.parent.parent);
const isRequireCall = (node, dependencyId, scope) =>
t.isCallExpression(node) &&
@ -59,8 +59,8 @@ const isImport = (node, scope, patterns) =>
function isImportOrGlobal(node, scope, patterns, isWrappedModule) {
const identifier = patterns.find(pattern => t.isIdentifier(node, pattern));
return (
identifier &&
isToplevelBinding(scope.getBinding(identifier.name), isWrappedModule) ||
(identifier &&
isToplevelBinding(scope.getBinding(identifier.name), isWrappedModule)) ||
isImport(node, scope, patterns)
);
}
@ -74,7 +74,11 @@ const isReactPlatformOS = (node, scope, isWrappedModule) =>
t.isMemberExpression(node.object) &&
t.isIdentifier(node.object.property, platform) &&
isImportOrGlobal(
node.object.object, scope, [React, ReactNative], isWrappedModule);
node.object.object,
scope,
[React, ReactNative],
isWrappedModule,
);
const isProcessEnvNodeEnv = (node, scope) =>
t.isIdentifier(node.property, nodeEnv) &&
@ -95,12 +99,16 @@ const isReactPlatformSelect = (node, scope, isWrappedModule) =>
t.isMemberExpression(node.callee.object) &&
t.isIdentifier(node.callee.object.property, platform) &&
isImportOrGlobal(
node.callee.object.object, scope, [React, ReactNative], isWrappedModule);
node.callee.object.object,
scope,
[React, ReactNative],
isWrappedModule,
);
const isDev = (node, parent, scope) =>
t.isIdentifier(node, dev) &&
isGlobalOrFlowDeclared(scope.getBinding(dev.name)) &&
!(t.isMemberExpression(parent));
!t.isMemberExpression(parent);
function findProperty(objectExpression, key, fallback) {
const property = objectExpression.properties.find(p => p.key.name === key);
@ -126,7 +134,8 @@ const inlinePlugin = {
path.replaceWith(t.stringLiteral(opts.platform));
} else if (isProcessEnvNodeEnv(node, scope)) {
path.replaceWith(
t.stringLiteral(opts.dev ? 'development' : 'production'));
t.stringLiteral(opts.dev ? 'development' : 'production'),
);
}
},
CallExpression(path, state) {
@ -155,10 +164,12 @@ const plugin = () => inlinePlugin;
function checkRequireArgs(args, dependencyId) {
const pattern = t.stringLiteral(dependencyId);
return t.isStringLiteral(args[0], pattern) ||
t.isMemberExpression(args[0]) &&
t.isNumericLiteral(args[0].property) &&
t.isStringLiteral(args[1], pattern);
return (
t.isStringLiteral(args[0], pattern) ||
(t.isMemberExpression(args[0]) &&
t.isNumericLiteral(args[0].property) &&
t.isStringLiteral(args[1], pattern))
);
}
type AstResult = {

View File

@ -7,6 +7,7 @@
* of patent rights can be found in the PATENTS file in the same directory.
*
* @flow
* @format
*/
'use strict';