mirror of
https://github.com/facebook/react-native.git
synced 2025-11-01 09:14:26 +00:00
Add new worker for code transform, optimization, and dependency extraction
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
This commit is contained in:
committed by
Facebook Github Bot 9
parent
58f86b2d91
commit
d94a56747d
+112
@@ -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
@@ -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]);
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -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'))
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
@@ -0,0 +1,111 @@
|
|||||||
|
/**
|
||||||
|
* 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 uglify = {
|
||||||
|
minify: jest.genMockFunction().mockImplementation(code => {
|
||||||
|
return {
|
||||||
|
code: code.replace(/(^|\W)\s+/g, '$1'),
|
||||||
|
map: {},
|
||||||
|
};
|
||||||
|
}),
|
||||||
|
};
|
||||||
|
jest.setMock('uglify-js', uglify);
|
||||||
|
|
||||||
|
const minify = require('../minify');
|
||||||
|
const {any} = jasmine;
|
||||||
|
|
||||||
|
describe('Minification:', () => {
|
||||||
|
const fileName = '/arbitrary/file.js';
|
||||||
|
const DEPENDENCY_MARKER = '\u0002\ueffe\ue277\uead5';
|
||||||
|
let map;
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
uglify.minify.mockClear();
|
||||||
|
map = {version: 3, sources: [fileName], mappings: ''};
|
||||||
|
});
|
||||||
|
|
||||||
|
it('passes the transformed code to `uglify.minify`, wrapped in an immediately invoked function expression', () => {
|
||||||
|
const code = 'arbitrary(code)';
|
||||||
|
minify('', code, {}, [], []);
|
||||||
|
expect(uglify.minify).toBeCalledWith(
|
||||||
|
`(function(){${code}}());`, any(Object));
|
||||||
|
});
|
||||||
|
|
||||||
|
it('uses the passed module locals as parameters of the IIFE', () => {
|
||||||
|
const moduleLocals = ['arbitrary', 'parameters'];
|
||||||
|
minify('', '', {}, [], moduleLocals);
|
||||||
|
expect(uglify.minify).toBeCalledWith(
|
||||||
|
`(function(${moduleLocals}){}());`, any(Object));
|
||||||
|
});
|
||||||
|
|
||||||
|
it('passes the transformed source map to `uglify.minify`', () => {
|
||||||
|
minify('', '', map, [], []);
|
||||||
|
const [, options] = uglify.minify.mock.calls[0];
|
||||||
|
expect(options.inSourceMap).toEqual(map);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('passes the file name as `outSourceMap` to `uglify.minify` (uglify uses it for the `file` field on the source map)', () => {
|
||||||
|
minify(fileName, '', {}, [], []);
|
||||||
|
const [, options] = uglify.minify.mock.calls[0];
|
||||||
|
expect(options.outSourceMap).toEqual(fileName);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('inserts a marker for every dependency offset before minifing', () => {
|
||||||
|
const code = `
|
||||||
|
var React = require('React');
|
||||||
|
var Immutable = require('Immutable');`;
|
||||||
|
const dependencyOffsets = [27, 67];
|
||||||
|
const expectedCode =
|
||||||
|
code.replace(/require\('/g, '$&' + DEPENDENCY_MARKER);
|
||||||
|
|
||||||
|
minify('', code, {}, dependencyOffsets, []);
|
||||||
|
expect(uglify.minify).toBeCalledWith(
|
||||||
|
`(function(){${expectedCode}}());`, any(Object));
|
||||||
|
});
|
||||||
|
|
||||||
|
it('returns the code provided by uglify', () => {
|
||||||
|
const code = 'some(source) + code';
|
||||||
|
uglify.minify.mockReturnValue({code: `!function(a,b,c){${code}}()`});
|
||||||
|
|
||||||
|
const result = minify('', '', {}, [], []);
|
||||||
|
expect(result.code).toBe(code);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('extracts dependency offsets from the code provided by uglify', () => {
|
||||||
|
const code = `
|
||||||
|
var a=r("${DEPENDENCY_MARKER}a-dependency");
|
||||||
|
var b=r("\\x02\\ueffe\\ue277\\uead5b-dependency");
|
||||||
|
var e=r(a()?'\\u0002\\ueffe\\ue277\\uead5c-dependency'
|
||||||
|
:'\x02\ueffe\ue277\uead5d-dependency');`;
|
||||||
|
uglify.minify.mockReturnValue({code: `!function(){${code}}());`});
|
||||||
|
|
||||||
|
const result = minify('', '', {}, [], []);
|
||||||
|
expect(result.dependencyOffsets).toEqual([15, 46, 81, 114]);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('returns the source map object provided by uglify', () => {
|
||||||
|
uglify.minify.mockReturnValue({map, code: ''});
|
||||||
|
const result = minify('', '', {}, [], []);
|
||||||
|
expect(result.map).toBe(map);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('adds a `moduleLocals` object to the result that reflects the names of the minified module locals', () => {
|
||||||
|
const moduleLocals = ['arbitrary', 'parameters', 'here'];
|
||||||
|
uglify.minify.mockReturnValue({code: '(function(a,ll,d){}());'});
|
||||||
|
const result = minify('', '', {}, [], moduleLocals);
|
||||||
|
expect(result.moduleLocals).toEqual({
|
||||||
|
arbitrary: 'a',
|
||||||
|
parameters: 'll',
|
||||||
|
here: 'd',
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -0,0 +1,244 @@
|
|||||||
|
/**
|
||||||
|
* 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();
|
||||||
|
jest.mock('../constant-folding');
|
||||||
|
jest.mock('../extract-dependencies');
|
||||||
|
jest.mock('../inline');
|
||||||
|
jest.mock('../minify');
|
||||||
|
|
||||||
|
const {transformCode} = require('..');
|
||||||
|
const {any, objectContaining} = jasmine;
|
||||||
|
|
||||||
|
describe('code transformation worker:', () => {
|
||||||
|
let extractDependencies, transform;
|
||||||
|
beforeEach(() => {
|
||||||
|
extractDependencies =
|
||||||
|
require('../extract-dependencies').mockReturnValue({});
|
||||||
|
transform = jest.genMockFunction();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('calls the transform with file name, source code, and transform options', function() {
|
||||||
|
const filename = 'arbitrary/file.js';
|
||||||
|
const sourceCode = 'arbitrary(code)';
|
||||||
|
const transformOptions = {arbitrary: 'options'};
|
||||||
|
transformCode(transform, filename, sourceCode, {transform: transformOptions});
|
||||||
|
expect(transform).toBeCalledWith(
|
||||||
|
{filename, sourceCode, options: transformOptions}, any(Function));
|
||||||
|
});
|
||||||
|
|
||||||
|
it('prefixes JSON files with an assignment to module.exports to make the code valid', function() {
|
||||||
|
const filename = 'arbitrary/file.json';
|
||||||
|
const sourceCode = '{"arbitrary":"property"}';
|
||||||
|
transformCode(transform, filename, sourceCode, {});
|
||||||
|
expect(transform).toBeCalledWith(
|
||||||
|
{filename, sourceCode: `module.exports=${sourceCode}`}, any(Function));
|
||||||
|
});
|
||||||
|
|
||||||
|
it('calls back with the result of the transform', done => {
|
||||||
|
const result = {
|
||||||
|
code: 'some.other(code)',
|
||||||
|
map: {}
|
||||||
|
};
|
||||||
|
transform.mockImplementation((_, callback) =>
|
||||||
|
callback(null, result));
|
||||||
|
|
||||||
|
transformCode(transform, 'filename', 'code', {}, (_, data) => {
|
||||||
|
expect(data).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', done => {
|
||||||
|
const result = {
|
||||||
|
code: 'p.exports={a:1,b:2}',
|
||||||
|
};
|
||||||
|
transform.mockImplementation((_, callback) =>
|
||||||
|
callback(null, result));
|
||||||
|
|
||||||
|
transformCode(transform, 'aribtrary/file.json', 'b', {}, (_, data) => {
|
||||||
|
expect(data.code).toBe('{a:1,b:2}');
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('calls back with any error yielded by the transform', done => {
|
||||||
|
const error = Error('arbitrary error');
|
||||||
|
transform.mockImplementation((_, callback) => callback(error));
|
||||||
|
transformCode(transform, 'filename', 'code', {}, e => {
|
||||||
|
expect(e).toBe(error);
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('puts an empty `moduleLocals` object on the result', done => {
|
||||||
|
transform.mockImplementation(
|
||||||
|
(_, callback) => callback(null, {code: 'arbitrary'}));
|
||||||
|
transformCode(transform, 'filename', 'code', {}, (_, data) => {
|
||||||
|
expect(data.moduleLocals).toEqual({});
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('if a `moduleLocals` array is passed, the `moduleLocals` object is a key mirror of its items', done => {
|
||||||
|
transform.mockImplementation(
|
||||||
|
(_, callback) => callback(null, {code: 'arbitrary'}));
|
||||||
|
const moduleLocals =
|
||||||
|
['arbitrary', 'list', 'containing', 'variable', 'names'];
|
||||||
|
|
||||||
|
transformCode(transform, 'filename', 'code', {moduleLocals}, (_, data) => {
|
||||||
|
expect(data.moduleLocals).toEqual({
|
||||||
|
arbitrary: 'arbitrary',
|
||||||
|
list: 'list',
|
||||||
|
containing: 'containing',
|
||||||
|
variable: 'variable',
|
||||||
|
names: 'names',
|
||||||
|
});
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('dependency extraction:', () => {
|
||||||
|
let code;
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
transform.mockImplementation(
|
||||||
|
(_, callback) => callback(null, {code}));
|
||||||
|
});
|
||||||
|
|
||||||
|
it('passes the transformed code the `extractDependencies`', done => {
|
||||||
|
code = 'arbitrary(code)';
|
||||||
|
|
||||||
|
transformCode(transform, 'filename', 'code', {}, (_, data) => {
|
||||||
|
expect(extractDependencies).toBeCalledWith(code);
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('uses `dependencies` and `dependencyOffsets` provided by `extractDependencies` for the result', done => {
|
||||||
|
const dependencyData = {
|
||||||
|
dependencies: ['arbitrary', 'list', 'of', 'dependencies'],
|
||||||
|
dependencyOffsets: [12, 119, 185, 328, 471],
|
||||||
|
};
|
||||||
|
extractDependencies.mockReturnValue(dependencyData);
|
||||||
|
|
||||||
|
transformCode(transform, 'filename', 'code', {}, (_, data) => {
|
||||||
|
expect(data).toEqual(objectContaining(dependencyData));
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('does not extract requires if files are marked as "extern"', done => {
|
||||||
|
transformCode(transform, 'filename', 'code', {extern: true}, (_, {dependencies, dependencyOffsets}) => {
|
||||||
|
expect(extractDependencies).not.toBeCalled();
|
||||||
|
expect(dependencies).toEqual([]);
|
||||||
|
expect(dependencyOffsets).toEqual([]);
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('does not extract requires of JSON files', done => {
|
||||||
|
transformCode(transform, 'arbitrary.json', '{"arbitrary":"json"}', {}, (_, {dependencies, dependencyOffsets}) => {
|
||||||
|
expect(extractDependencies).not.toBeCalled();
|
||||||
|
expect(dependencies).toEqual([]);
|
||||||
|
expect(dependencyOffsets).toEqual([]);
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('Minifications:', () => {
|
||||||
|
let constantFolding, extractDependencies, inline, minify, options;
|
||||||
|
let transformResult, dependencyData, moduleLocals;
|
||||||
|
const filename = 'arbitrary/file.js';
|
||||||
|
const foldedCode = 'arbitrary(folded(code));'
|
||||||
|
const foldedMap = {version: 3, sources: ['fold.js']}
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
constantFolding = require('../constant-folding')
|
||||||
|
.mockReturnValue({code: foldedCode, map: foldedMap});
|
||||||
|
extractDependencies = require('../extract-dependencies');
|
||||||
|
inline = require('../inline');
|
||||||
|
minify = require('../minify').mockReturnValue({});
|
||||||
|
|
||||||
|
moduleLocals = ['module', 'require', 'exports'];
|
||||||
|
options = {moduleLocals, minify: true};
|
||||||
|
dependencyData = {
|
||||||
|
dependencies: ['a', 'b', 'c'],
|
||||||
|
dependencyOffsets: [100, 120, 140]
|
||||||
|
};
|
||||||
|
|
||||||
|
extractDependencies.mockImplementation(
|
||||||
|
code => code === foldedCode ? dependencyData : {});
|
||||||
|
|
||||||
|
transform.mockImplementation(
|
||||||
|
(_, callback) => callback(null, transformResult));
|
||||||
|
});
|
||||||
|
|
||||||
|
it('passes the transform result to `inline` for constant inlining', done => {
|
||||||
|
transformResult = {map: {version: 3}, code: 'arbitrary(code)'};
|
||||||
|
transformCode(transform, filename, 'code', options, () => {
|
||||||
|
expect(inline).toBeCalledWith(filename, transformResult, options);
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('passes the result obtained from `inline` on to `constant-folding`', done => {
|
||||||
|
const inlineResult = {map: {version: 3, sources: []}, ast: {}};
|
||||||
|
inline.mockReturnValue(inlineResult);
|
||||||
|
transformCode(transform, filename, 'code', options, () => {
|
||||||
|
expect(constantFolding).toBeCalledWith(filename, inlineResult);
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('Uses the code obtained from `constant-folding` to extract dependencies', done => {
|
||||||
|
transformCode(transform, filename, 'code', options, () => {
|
||||||
|
expect(extractDependencies).toBeCalledWith(foldedCode);
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('passes the code obtained from `constant-folding` to `minify`', done => {
|
||||||
|
transformCode(transform, filename, 'code', options, () => {
|
||||||
|
expect(minify).toBeCalledWith(
|
||||||
|
filename,
|
||||||
|
foldedCode,
|
||||||
|
foldedMap,
|
||||||
|
dependencyData.dependencyOffsets,
|
||||||
|
moduleLocals
|
||||||
|
);
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('uses the dependencies obtained from the optimized result', done => {
|
||||||
|
transformCode(transform, filename, 'code', options, (_, result) => {
|
||||||
|
expect(result.dependencies).toEqual(dependencyData.dependencies);
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('uses data produced by `minify` for the result', done => {
|
||||||
|
const minifyResult = {
|
||||||
|
code: 'minified(code)',
|
||||||
|
dependencyOffsets: [10, 30, 60],
|
||||||
|
map: {version: 3, sources: ['minified.js']},
|
||||||
|
moduleLocals: {module: 'x', require: 'y', exports: 'z'},
|
||||||
|
};
|
||||||
|
minify.mockReturnValue(minifyResult);
|
||||||
|
|
||||||
|
transformCode(transform, 'filename', 'code', options, (_, result) => {
|
||||||
|
expect(result).toEqual(objectContaining(minifyResult))
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -0,0 +1,82 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) 2016-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';
|
||||||
|
|
||||||
|
const babel = require('babel-core');
|
||||||
|
const t = babel.types;
|
||||||
|
|
||||||
|
const isLiteral = binaryExpression =>
|
||||||
|
t.isLiteral(binaryExpression.left) && t.isLiteral(binaryExpression.right);
|
||||||
|
|
||||||
|
const Conditional = {
|
||||||
|
exit(path) {
|
||||||
|
const node = path.node;
|
||||||
|
const test = node.test;
|
||||||
|
if (t.isLiteral(test)) {
|
||||||
|
if (test.value || node.alternate) {
|
||||||
|
path.replaceWith(test.value ? node.consequent : node.alternate);
|
||||||
|
} else if (!test.value) {
|
||||||
|
path.remove();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
const plugin = {
|
||||||
|
visitor: {
|
||||||
|
BinaryExpression: {
|
||||||
|
exit(path) {
|
||||||
|
const node = path.node;
|
||||||
|
if (t.isLiteral(node.left) && t.isLiteral(node.right)) {
|
||||||
|
const result = path.evaluate();
|
||||||
|
if (result.confident) {
|
||||||
|
path.replaceWith(t.valueToNode(result.value));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
ConditionalExpression: Conditional,
|
||||||
|
IfStatement: Conditional,
|
||||||
|
LogicalExpression: {
|
||||||
|
exit(path) {
|
||||||
|
const node = path.node;
|
||||||
|
const left = node.left;
|
||||||
|
if (t.isLiteral(left)) {
|
||||||
|
const value = t.isNullLiteral(left) ? null : left.value;
|
||||||
|
if (node.operator === '||') {
|
||||||
|
path.replaceWith(value ? left : node.right);
|
||||||
|
} else {
|
||||||
|
path.replaceWith(value ? node.right : left);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
UnaryExpression: {
|
||||||
|
exit(path) {
|
||||||
|
const node = path.node;
|
||||||
|
if (node.operator === '!' && t.isLiteral(node.argument)) {
|
||||||
|
path.replaceWith(t.valueToNode(!node.argument.value));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
function constantFolding(filename, transformResult) {
|
||||||
|
return babel.transformFromAst(transformResult.ast, transformResult.code, {
|
||||||
|
filename,
|
||||||
|
plugins: [plugin],
|
||||||
|
inputSourceMap: transformResult.map,
|
||||||
|
babelrc: false,
|
||||||
|
compact: true,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = constantFolding;
|
||||||
|
|
||||||
@@ -0,0 +1,126 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) 2016-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';
|
||||||
|
|
||||||
|
const SINGLE_QUOTE = "'".charCodeAt(0);
|
||||||
|
const DOUBLE_QUOTE = '"'.charCodeAt(0);
|
||||||
|
const BACKSLASH = '\\'.charCodeAt(0);
|
||||||
|
const SLASH = '/'.charCodeAt(0);
|
||||||
|
const NEWLINE = '\n'.charCodeAt(0);
|
||||||
|
const ASTERISK = '*'.charCodeAt(0);
|
||||||
|
|
||||||
|
// dollar is the only regex special character valid in identifiers
|
||||||
|
const escapeRegExp = identifier => identifier.replace(/[$]/g, '\\$');
|
||||||
|
|
||||||
|
function binarySearch(indexes, index) {
|
||||||
|
var low = 0;
|
||||||
|
var high = indexes.length - 1;
|
||||||
|
var i = 0;
|
||||||
|
|
||||||
|
if (indexes[low] === index) {
|
||||||
|
return low;
|
||||||
|
}
|
||||||
|
while (high - low > 1) {
|
||||||
|
var current = low + ((high - low) >>> 1); // right shift divides by 2 and floors
|
||||||
|
if (index === indexes[current]) {
|
||||||
|
return current;
|
||||||
|
}
|
||||||
|
if (index > indexes[current]) {
|
||||||
|
low = current;
|
||||||
|
} else {
|
||||||
|
high = current;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return low;
|
||||||
|
}
|
||||||
|
|
||||||
|
function indexOfCharCode(string, needle, i) {
|
||||||
|
for (var charCode; (charCode = string.charCodeAt(i)); i++) {
|
||||||
|
if (charCode === needle) {
|
||||||
|
return i;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
const reRequire = /(?:^|[^.\s])\s*\brequire\s*\(\s*(['"])(.*?)\1/g;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Extracts dependencies (module IDs imported with the `require` function) from
|
||||||
|
* a string containing code.
|
||||||
|
* The function is regular expression based for speed reasons.
|
||||||
|
*
|
||||||
|
* The code is traversed twice:
|
||||||
|
* 1. An array of ranges is built, where indexes 0-1, 2-3, 4-5, etc. are code,
|
||||||
|
* and indexes 1-2, 3-4, 5-6, etc. are string literals and comments.
|
||||||
|
* 2. require calls are extracted with a regular expression.
|
||||||
|
*
|
||||||
|
* The result of the dependency extraction is an de-duplicated array of
|
||||||
|
* dependencies, and an array of offsets to the string literals with module IDs.
|
||||||
|
* The index points to the opening quote.
|
||||||
|
*/
|
||||||
|
function extractDependencies(code) {
|
||||||
|
const ranges = [0];
|
||||||
|
// are we currently in a quoted string? -> SINGLE_QUOTE or DOUBLE_QUOTE, else undefined
|
||||||
|
var currentQuote;
|
||||||
|
// scan the code for string literals and comments.
|
||||||
|
for (var i = 0, charCode; (charCode = code.charCodeAt(i)); i++) {
|
||||||
|
if (charCode === BACKSLASH) {
|
||||||
|
i += 1;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (charCode === SLASH && currentQuote === undefined) {
|
||||||
|
var next = code.charCodeAt(i + 1);
|
||||||
|
var end = undefined;
|
||||||
|
if (next === SLASH) {
|
||||||
|
end = indexOfCharCode(code, NEWLINE, i + 2);
|
||||||
|
} else if (next === ASTERISK) {
|
||||||
|
end = code.indexOf('*/', i + 2) + 1; // assume valid JS input here
|
||||||
|
}
|
||||||
|
if (end === -1) {
|
||||||
|
// if the comment does not end, it goes to the end of the file
|
||||||
|
end += code.length;
|
||||||
|
}
|
||||||
|
if (end !== undefined) {
|
||||||
|
ranges.push(i, end);
|
||||||
|
i = end;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var isQuoteStart = currentQuote === undefined &&
|
||||||
|
(charCode === SINGLE_QUOTE || charCode === DOUBLE_QUOTE);
|
||||||
|
if (isQuoteStart || currentQuote === charCode) {
|
||||||
|
ranges.push(i);
|
||||||
|
currentQuote = currentQuote === charCode ? undefined : charCode;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ranges.push(i);
|
||||||
|
|
||||||
|
// extract dependencies
|
||||||
|
const dependencies = new Set();
|
||||||
|
const dependencyOffsets = [];
|
||||||
|
for (var match; (match = reRequire.exec(code)); ) {
|
||||||
|
// check whether the match is in a code range, and not inside of a string
|
||||||
|
// literal or a comment
|
||||||
|
if (binarySearch(ranges, match.index) % 2 === 0) {
|
||||||
|
dependencies.add(match[2]);
|
||||||
|
dependencyOffsets.push(
|
||||||
|
match[0].length - match[2].length - 2 + match.index);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
dependencyOffsets,
|
||||||
|
dependencies: Array.from(dependencies.values()),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = extractDependencies;
|
||||||
@@ -0,0 +1,79 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) 2016-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';
|
||||||
|
|
||||||
|
const constantFolding = require('./constant-folding');
|
||||||
|
const extractDependencies = require('./extract-dependencies');
|
||||||
|
const inline = require('./inline');
|
||||||
|
const minify = require('./minify');
|
||||||
|
|
||||||
|
function keyMirrorFromArray(array) {
|
||||||
|
var keyMirror = {};
|
||||||
|
array.forEach(key => keyMirror[key] = key);
|
||||||
|
return keyMirror;
|
||||||
|
}
|
||||||
|
|
||||||
|
function makeTransformParams(filename, sourceCode, options) {
|
||||||
|
if (filename.endsWith('.json')) {
|
||||||
|
sourceCode = 'module.exports=' + sourceCode;
|
||||||
|
}
|
||||||
|
return {filename, sourceCode, options};
|
||||||
|
}
|
||||||
|
|
||||||
|
function transformCode(transform, filename, sourceCode, options, callback) {
|
||||||
|
const params = makeTransformParams(filename, sourceCode, options.transform);
|
||||||
|
const moduleLocals = options.moduleLocals || [];
|
||||||
|
const isJson = filename.endsWith('.json');
|
||||||
|
|
||||||
|
transform(params, (error, transformed) => {
|
||||||
|
if (error) {
|
||||||
|
callback(error);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var code, map;
|
||||||
|
if (options.minify) {
|
||||||
|
const optimized =
|
||||||
|
constantFolding(filename, inline(filename, transformed, options));
|
||||||
|
code = optimized.code;
|
||||||
|
map = optimized.map;
|
||||||
|
} else {
|
||||||
|
code = transformed.code;
|
||||||
|
map = transformed.map;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isJson) {
|
||||||
|
code = code.replace(/^\w+\.exports=/, '');
|
||||||
|
}
|
||||||
|
|
||||||
|
const moduleLocals = options.moduleLocals || [];
|
||||||
|
const dependencyData = isJson || options.extern
|
||||||
|
? {dependencies: [], dependencyOffsets: []}
|
||||||
|
: extractDependencies(code);
|
||||||
|
|
||||||
|
var result;
|
||||||
|
if (options.minify) {
|
||||||
|
result = minify(
|
||||||
|
filename, code, map, dependencyData.dependencyOffsets, moduleLocals);
|
||||||
|
result.dependencies = dependencyData.dependencies;
|
||||||
|
} else {
|
||||||
|
result = dependencyData;
|
||||||
|
result.code = code;
|
||||||
|
result.map = map;
|
||||||
|
result.moduleLocals = keyMirrorFromArray(moduleLocals);
|
||||||
|
}
|
||||||
|
|
||||||
|
callback(null, result);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = function(transform, filename, sourceCode, options, callback) {
|
||||||
|
transformCode(require(transform), filename, sourceCode, options || {}, callback);
|
||||||
|
};
|
||||||
|
module.exports.transformCode = transformCode; // for easier testing
|
||||||
@@ -0,0 +1,102 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) 2016-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';
|
||||||
|
|
||||||
|
const babel = require('babel-core');
|
||||||
|
const t = babel.types;
|
||||||
|
|
||||||
|
const react = {name: 'React'};
|
||||||
|
const platform = {name: 'Platform'};
|
||||||
|
const os = {name: 'OS'};
|
||||||
|
const requirePattern = {name: 'require'};
|
||||||
|
|
||||||
|
const env = {name: 'env'};
|
||||||
|
const nodeEnv = {name: 'NODE_ENV'};
|
||||||
|
const processId = {name: 'process'};
|
||||||
|
|
||||||
|
const dev = {name: '__DEV__'};
|
||||||
|
|
||||||
|
const isGlobal = (binding) => !binding;
|
||||||
|
|
||||||
|
const isToplevelBinding = (binding) => isGlobal(binding) || !binding.scope.parent;
|
||||||
|
|
||||||
|
const isRequireCall = (node, dependencyId, scope) =>
|
||||||
|
t.isCallExpression(node) &&
|
||||||
|
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 isPlatformOS = (node, scope) =>
|
||||||
|
t.isIdentifier(node.property, os) &&
|
||||||
|
isImport(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);
|
||||||
|
|
||||||
|
const isProcessEnvNodeEnv = (node, scope) =>
|
||||||
|
t.isIdentifier(node.property, nodeEnv) &&
|
||||||
|
t.isMemberExpression(node.object) &&
|
||||||
|
t.isIdentifier(node.object.property, env) &&
|
||||||
|
t.isIdentifier(node.object.object, processId) &&
|
||||||
|
isGlobal(scope.getBinding(processId.name));
|
||||||
|
|
||||||
|
const isDev = (node, parent, scope) =>
|
||||||
|
t.isIdentifier(node, dev) &&
|
||||||
|
isGlobal(scope.getBinding(dev.name)) &&
|
||||||
|
!(t.isMemberExpression(parent));
|
||||||
|
|
||||||
|
const inlinePlugin = {
|
||||||
|
visitor: {
|
||||||
|
Identifier(path, state) {
|
||||||
|
if (isDev(path.node, path.parent, path.scope)) {
|
||||||
|
path.replaceWith(t.booleanLiteral(state.opts.dev));
|
||||||
|
}
|
||||||
|
},
|
||||||
|
MemberExpression(path, state) {
|
||||||
|
const node = path.node;
|
||||||
|
const scope = path.scope;
|
||||||
|
|
||||||
|
if (isPlatformOS(node, scope) || isReactPlatformOS(node, scope)) {
|
||||||
|
path.replaceWith(t.stringLiteral(state.opts.platform));
|
||||||
|
}
|
||||||
|
|
||||||
|
if(isProcessEnvNodeEnv(node, scope)) {
|
||||||
|
path.replaceWith(
|
||||||
|
t.stringLiteral(state.opts.dev ? 'development' : 'production'));
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
const plugin = () => inlinePlugin;
|
||||||
|
|
||||||
|
function inline(filename, transformResult, options) {
|
||||||
|
const code = transformResult.code;
|
||||||
|
const babelOptions = {
|
||||||
|
filename,
|
||||||
|
plugins: [[plugin, options]],
|
||||||
|
inputSourceMap: transformResult.map,
|
||||||
|
code: false,
|
||||||
|
babelrc: false,
|
||||||
|
compact: true,
|
||||||
|
};
|
||||||
|
|
||||||
|
return transformResult.ast
|
||||||
|
? babel.transformFromAst(transformResult.ast, code, babelOptions)
|
||||||
|
: babel.transform(code, babelOptions);
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = inline;
|
||||||
@@ -0,0 +1,88 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) 2016-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';
|
||||||
|
|
||||||
|
const uglify = require('uglify-js');
|
||||||
|
|
||||||
|
const MAGIC_MARKER = '\u0002\ueffe\ue277\uead5';
|
||||||
|
const MAGIC_MARKER_SPLITTER =
|
||||||
|
/(?:\x02|\\u0002|\\x02)(?:\ueffe|\\ueffe)(?:\ue277|\\ue277)(?:\uead5|\\uead5)/;
|
||||||
|
|
||||||
|
// IIFE = "immediately invoked function expression"
|
||||||
|
// we wrap modules in functions to allow the minifier to mangle local variables
|
||||||
|
function wrapCodeInIIFE(code, moduleLocals) {
|
||||||
|
return `(function(${moduleLocals.join(',')}){${code}}());`;
|
||||||
|
}
|
||||||
|
|
||||||
|
function extractCodeFromIIFE(code) {
|
||||||
|
return code.substring(code.indexOf('{') + 1, code.lastIndexOf('}'));
|
||||||
|
}
|
||||||
|
|
||||||
|
function extractModuleLocalsFromIIFE(code) {
|
||||||
|
return code.substring(code.indexOf('(', 1) + 1, code.indexOf(')')).split(',');
|
||||||
|
}
|
||||||
|
|
||||||
|
function splitFirstElementAt(array, offset) {
|
||||||
|
const first = array.shift();
|
||||||
|
array.unshift(first.slice(0, offset + 1), first.slice(offset + 1));
|
||||||
|
return array;
|
||||||
|
}
|
||||||
|
|
||||||
|
function insertMarkers(code, dependencyOffsets) {
|
||||||
|
return dependencyOffsets
|
||||||
|
.reduceRight(splitFirstElementAt, [code])
|
||||||
|
.join(MAGIC_MARKER);
|
||||||
|
}
|
||||||
|
|
||||||
|
function extractMarkers(codeWithMarkers) {
|
||||||
|
const dependencyOffsets = [];
|
||||||
|
const codeBits = codeWithMarkers.split(MAGIC_MARKER_SPLITTER);
|
||||||
|
var offset = 0;
|
||||||
|
for (var i = 0, max = codeBits.length - 1; i < max; i++) {
|
||||||
|
offset += codeBits[i].length;
|
||||||
|
dependencyOffsets.push(offset - 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
return {code: codeBits.join(''), dependencyOffsets};
|
||||||
|
}
|
||||||
|
|
||||||
|
function minify(filename, code, map, dependencyOffsets, moduleLocals) {
|
||||||
|
// before minifying, code is wrapped in an immediately invoked function
|
||||||
|
// expression, so that top level variables can be shortened safely
|
||||||
|
code = wrapCodeInIIFE(
|
||||||
|
// since we don't know where the strings specifying dependencies will be
|
||||||
|
// located in the minified code, we mark them with a special marker string
|
||||||
|
// and extract them afterwards.
|
||||||
|
// That way, post-processing code can use these positions
|
||||||
|
insertMarkers(code, dependencyOffsets),
|
||||||
|
moduleLocals
|
||||||
|
);
|
||||||
|
|
||||||
|
const minifyResult = uglify.minify(code, {
|
||||||
|
fromString: true,
|
||||||
|
inSourceMap: map,
|
||||||
|
outSourceMap: filename,
|
||||||
|
output: {
|
||||||
|
ascii_only: true,
|
||||||
|
screw_ie8: true,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const minifiedModuleLocals = extractModuleLocalsFromIIFE(minifyResult.code);
|
||||||
|
const codeWithMarkers = extractCodeFromIIFE(minifyResult.code);
|
||||||
|
const result = extractMarkers(codeWithMarkers);
|
||||||
|
result.map = minifyResult.map;
|
||||||
|
result.moduleLocals = {};
|
||||||
|
moduleLocals.forEach(
|
||||||
|
(key, i) => result.moduleLocals[key] = minifiedModuleLocals[i]);
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = minify;
|
||||||
Reference in New Issue
Block a user