mirror of https://github.com/status-im/metro.git
BREAKING: Add regenerator-runtime on demand, based on the files
Summary: Adding a Babel plugin that will analyze the file looking for any potential candidate to use `regenerator-runtime`, and if so, will inject dynamically the module. The module is injected per file, so we avoid polluting the global environment. The plugin is also able to inject the `require` call beforehand, so that the inliner can pick them and inline them. The Babel plugin is part of `react-native-babel-preset`, so as long as you are using this preset you are safe. If not, you should include the specific transformer into your list of plugins, as `react-native-babel-preset/transforms/transform-regenerator-runtime-insertion.js`. Reviewed By: davidaurelio Differential Revision: D5388655 fbshipit-source-id: dc403f3d5e2d807529eb8569a85c45fec36a6a3e
This commit is contained in:
parent
6fd25bb98a
commit
d7521c53ae
|
@ -1,5 +1,5 @@
|
|||
{
|
||||
"version": "0.9.0",
|
||||
"version": "0.10.0",
|
||||
"lerna": "2.0.0-rc.5",
|
||||
"npmClient": "yarn",
|
||||
"packages": [
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
{
|
||||
"version": "0.9.0",
|
||||
"version": "0.10.0",
|
||||
"name": "metro-bundler",
|
||||
"description": "🚇 The JavaScript bundler for React Native.",
|
||||
"main": "src/index.js",
|
||||
|
@ -19,7 +19,7 @@
|
|||
"babel-plugin-external-helpers": "^6.18.0",
|
||||
"babel-preset-es2015-node": "^6.1.1",
|
||||
"babel-preset-fbjs": "^2.1.4",
|
||||
"babel-preset-react-native": "^2.0.0",
|
||||
"babel-preset-react-native": "^2.1.0",
|
||||
"babel-register": "^6.24.1",
|
||||
"babylon": "^6.17.0",
|
||||
"chalk": "^1.1.1",
|
||||
|
|
|
@ -10,26 +10,28 @@
|
|||
*/
|
||||
'use strict';
|
||||
|
||||
const babel = require('babel-core');
|
||||
/* eslint-disable max-len */
|
||||
|
||||
const constantFolding = require('../constant-folding');
|
||||
|
||||
function parse(code) {
|
||||
return babel.transform(code, {code: false, babelrc: false, compact: true});
|
||||
}
|
||||
const {transform, transformFromAst} = require('babel-core');
|
||||
|
||||
const babelOptions = {
|
||||
babelrc: false,
|
||||
compact: true,
|
||||
retainLines: false,
|
||||
};
|
||||
|
||||
function normalize({code}) {
|
||||
return babel.transform(code, babelOptions).code;
|
||||
function toString(ast) {
|
||||
return normalize(transformFromAst(ast, babelOptions).code);
|
||||
}
|
||||
|
||||
function normalize(code) {
|
||||
return transform(code, babelOptions).code;
|
||||
}
|
||||
|
||||
describe('constant expressions', () => {
|
||||
it('can optimize conditional expressions with constant conditions', () => {
|
||||
const code = `
|
||||
const before = `
|
||||
a(
|
||||
'production'=="production",
|
||||
'production'!=='development',
|
||||
|
@ -39,56 +41,97 @@ describe('constant expressions', () => {
|
|||
'android'==='android' ? {a:1} : {a:0},
|
||||
'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());',
|
||||
);
|
||||
`;
|
||||
|
||||
const after = `
|
||||
a(
|
||||
true,
|
||||
true,
|
||||
2,
|
||||
true,
|
||||
{},
|
||||
{a:1},
|
||||
c,
|
||||
f() ? g() : h()
|
||||
);
|
||||
`;
|
||||
|
||||
const {ast} = constantFolding('arbitrary.js', {code: before});
|
||||
expect(toString(ast)).toEqual(normalize(after));
|
||||
});
|
||||
|
||||
it('can optimize ternary expressions with constant conditions', () => {
|
||||
const code = `var a = true ? 1 : 2;
|
||||
const before = `
|
||||
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';",
|
||||
);
|
||||
: 'i';
|
||||
`;
|
||||
|
||||
const after = `
|
||||
var a = 1;
|
||||
var b = 'A';
|
||||
`;
|
||||
|
||||
const {ast} = constantFolding('arbitrary.js', {code: before});
|
||||
expect(toString(ast)).toEqual(normalize(after));
|
||||
});
|
||||
|
||||
it('can optimize logical operator expressions with constant conditions', () => {
|
||||
const code = `
|
||||
const before = `
|
||||
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";',
|
||||
);
|
||||
'production' != 'production' || null || "A";
|
||||
`;
|
||||
|
||||
const after = `
|
||||
var a = true;
|
||||
var b = "A";
|
||||
`;
|
||||
|
||||
const {ast} = constantFolding('arbitrary.js', {code: before});
|
||||
expect(toString(ast)).toEqual(normalize(after));
|
||||
});
|
||||
|
||||
it('can optimize logical operators with partly constant operands', () => {
|
||||
const code = `
|
||||
const before = `
|
||||
var a = "truthy" || z();
|
||||
var b = "truthy" && z();
|
||||
var c = null && z();
|
||||
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;',
|
||||
);
|
||||
|
||||
const after = `
|
||||
var a = "truthy";
|
||||
var b = z();
|
||||
var c = null;
|
||||
var d = z();
|
||||
var e = false;
|
||||
`;
|
||||
|
||||
const {ast} = constantFolding('arbitrary.js', {code: before});
|
||||
expect(toString(ast)).toEqual(normalize(after));
|
||||
});
|
||||
|
||||
it('can remode an if statement with a falsy constant test', () => {
|
||||
const code = `
|
||||
const before = `
|
||||
if ('production' === 'development' || false) {
|
||||
var a = 1;
|
||||
}
|
||||
`;
|
||||
expect(normalize(constantFolding('arbitrary.js', parse(code)))).toEqual('');
|
||||
|
||||
// Intentionally empty: all dead code.
|
||||
const after = `
|
||||
`;
|
||||
|
||||
const {ast} = constantFolding('arbitrary.js', {code: before});
|
||||
expect(toString(ast)).toEqual(normalize(after));
|
||||
});
|
||||
|
||||
it('can optimize if-else-branches with constant conditions', () => {
|
||||
const code = `
|
||||
const before = `
|
||||
if ('production' == 'development') {
|
||||
var a = 1;
|
||||
var b = a + 2;
|
||||
|
@ -99,13 +142,20 @@ describe('constant expressions', () => {
|
|||
var a = 'b';
|
||||
}
|
||||
`;
|
||||
expect(normalize(constantFolding('arbitrary.js', parse(code)))).toEqual(
|
||||
'{var a=3;var b=a+4;}',
|
||||
);
|
||||
|
||||
const after = `
|
||||
{
|
||||
var a = 3;
|
||||
var b = a + 4;
|
||||
}
|
||||
`;
|
||||
|
||||
const {ast} = constantFolding('arbitrary.js', {code: before});
|
||||
expect(toString(ast)).toEqual(normalize(after));
|
||||
});
|
||||
|
||||
it('can optimize nested if-else constructs', () => {
|
||||
const code = `
|
||||
const before = `
|
||||
if ('ios' === "android") {
|
||||
if (true) {
|
||||
require('a');
|
||||
|
@ -120,8 +170,16 @@ describe('constant expressions', () => {
|
|||
}
|
||||
}
|
||||
`;
|
||||
expect(normalize(constantFolding('arbitrary.js', parse(code)))).toEqual(
|
||||
"{{require('c');}}",
|
||||
);
|
||||
|
||||
const after = `
|
||||
{
|
||||
{
|
||||
require('c');
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
const {ast} = constantFolding('arbitrary.js', {code: before});
|
||||
expect(toString(ast)).toEqual(normalize(after));
|
||||
});
|
||||
});
|
||||
|
|
|
@ -275,7 +275,7 @@ describe('code transformation worker:', () => {
|
|||
const inlineResult = {map: {version: 3, sources: []}, ast: {}};
|
||||
inline.mockReturnValue(inlineResult);
|
||||
transformCode(transformer, filename, filename, 'code', options, () => {
|
||||
expect(constantFolding).toBeCalledWith(filename, inlineResult);
|
||||
expect(constantFolding).toBeCalledWith(filename, inlineResult, options);
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
|
|
@ -13,8 +13,9 @@
|
|||
'use strict';
|
||||
|
||||
const babel = require('babel-core');
|
||||
const invariant = require('fbjs/lib/invariant');
|
||||
|
||||
import type {Ast, SourceMap as MappingsMap} from 'babel-core';
|
||||
import type {IntermediateTransformResult} from './types.flow';
|
||||
const t = babel.types;
|
||||
|
||||
const Conditional = {
|
||||
|
@ -31,7 +32,7 @@ const Conditional = {
|
|||
},
|
||||
};
|
||||
|
||||
const plugin = {
|
||||
const constantFoldingPlugin = {
|
||||
visitor: {
|
||||
BinaryExpression: {
|
||||
exit(path) {
|
||||
|
@ -71,25 +72,32 @@ const plugin = {
|
|||
},
|
||||
};
|
||||
|
||||
const plugin = () => constantFoldingPlugin;
|
||||
|
||||
function constantFolding(
|
||||
filename: string,
|
||||
transformResult: {
|
||||
ast: Ast,
|
||||
code?: ?string,
|
||||
map: ?MappingsMap,
|
||||
},
|
||||
) {
|
||||
return babel.transformFromAst(transformResult.ast, transformResult.code, {
|
||||
transformResult: IntermediateTransformResult,
|
||||
options: {+dev: boolean, +platform: ?string},
|
||||
): IntermediateTransformResult {
|
||||
const code = transformResult.code;
|
||||
const babelOptions = {
|
||||
filename,
|
||||
plugins: [plugin],
|
||||
plugins: [[plugin, options]],
|
||||
inputSourceMap: transformResult.map,
|
||||
sourceMaps: true,
|
||||
sourceFileName: filename,
|
||||
code: true,
|
||||
babelrc: false,
|
||||
compact: true,
|
||||
retainLines: true,
|
||||
});
|
||||
};
|
||||
|
||||
const result = transformResult.ast
|
||||
? babel.transformFromAst(transformResult.ast, code, babelOptions)
|
||||
: (code && babel.transform(code, babelOptions)) || {};
|
||||
const {ast} = result;
|
||||
invariant(ast != null, 'Missing AST in babel transform results.');
|
||||
return {ast, code: result.code, map: result.map};
|
||||
}
|
||||
|
||||
constantFolding.plugin = plugin;
|
||||
constantFolding.plugin = constantFoldingPlugin;
|
||||
module.exports = constantFolding;
|
||||
|
|
|
@ -22,7 +22,8 @@ const minify = require('./minify');
|
|||
import type {LogEntry} from '../../Logger/Types';
|
||||
import type {MappingsMap} from '../../lib/SourceMap';
|
||||
import type {LocalPath} from '../../node-haste/lib/toLocalPath';
|
||||
import type {Ast, Plugins as BabelPlugins} from 'babel-core';
|
||||
import type {IntermediateTransformResult} from './types.flow';
|
||||
import type {Plugins as BabelPlugins} from 'babel-core';
|
||||
|
||||
export type TransformedCode = {
|
||||
code: string,
|
||||
|
@ -38,7 +39,7 @@ export type Transformer<ExtraOptions: {} = {}> = {
|
|||
options: ExtraOptions & TransformOptions,
|
||||
plugins?: BabelPlugins,
|
||||
src: string,
|
||||
|}) => {ast: ?Ast, code: string, map: ?MappingsMap},
|
||||
|}) => IntermediateTransformResult,
|
||||
getCacheKey: () => string,
|
||||
};
|
||||
|
||||
|
@ -83,6 +84,8 @@ type TransformCode = (
|
|||
Callback<Data>,
|
||||
) => void;
|
||||
|
||||
const transformers = [inline, constantFolding];
|
||||
|
||||
const transformCode: TransformCode = asyncify(
|
||||
(
|
||||
transformer: Transformer<*>,
|
||||
|
@ -121,17 +124,29 @@ const transformCode: TransformCode = asyncify(
|
|||
'Missing transform results despite having no error.',
|
||||
);
|
||||
|
||||
var code, map;
|
||||
let code;
|
||||
let map;
|
||||
|
||||
if (options.minify) {
|
||||
({code, map} = constantFolding(
|
||||
filename,
|
||||
inline(filename, transformed, options),
|
||||
));
|
||||
invariant(code != null, 'Missing code from constant-folding transform.');
|
||||
let result = transformed;
|
||||
const length = transformers.length;
|
||||
|
||||
for (let i = 0; i < length; i++) {
|
||||
result = transformers[i](filename, result, options);
|
||||
}
|
||||
|
||||
({code, map} = result);
|
||||
} else {
|
||||
({code, map} = transformed);
|
||||
}
|
||||
|
||||
invariant(
|
||||
code != null,
|
||||
'The last transformer on the list (' +
|
||||
transformers[transformers.length - 1].name +
|
||||
') has to output code',
|
||||
);
|
||||
|
||||
if (isJson) {
|
||||
code = code.replace(/^\w+\.exports=/, '');
|
||||
} else {
|
||||
|
|
|
@ -15,7 +15,7 @@
|
|||
const babel = require('babel-core');
|
||||
const invariant = require('fbjs/lib/invariant');
|
||||
|
||||
import type {Ast, SourceMap as MappingsMap} from 'babel-core';
|
||||
import type {IntermediateTransformResult} from './types.flow';
|
||||
const t = babel.types;
|
||||
|
||||
const React = {name: 'React'};
|
||||
|
@ -115,6 +115,16 @@ function findProperty(objectExpression, key, fallback) {
|
|||
return property ? property.value : fallback();
|
||||
}
|
||||
|
||||
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))
|
||||
);
|
||||
}
|
||||
|
||||
const inlinePlugin = {
|
||||
visitor: {
|
||||
Identifier(path, state) {
|
||||
|
@ -162,27 +172,11 @@ const inlinePlugin = {
|
|||
|
||||
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))
|
||||
);
|
||||
}
|
||||
|
||||
type AstResult = {
|
||||
ast: Ast,
|
||||
code: ?string,
|
||||
map: ?MappingsMap,
|
||||
};
|
||||
|
||||
function inline(
|
||||
filename: string,
|
||||
transformResult: {ast?: ?Ast, code: string, map: ?MappingsMap},
|
||||
transformResult: IntermediateTransformResult,
|
||||
options: {+dev: boolean, +platform: ?string},
|
||||
): AstResult {
|
||||
): IntermediateTransformResult {
|
||||
const code = transformResult.code;
|
||||
const babelOptions = {
|
||||
filename,
|
||||
|
@ -197,7 +191,7 @@ function inline(
|
|||
|
||||
const result = transformResult.ast
|
||||
? babel.transformFromAst(transformResult.ast, code, babelOptions)
|
||||
: babel.transform(code, babelOptions);
|
||||
: (code && babel.transform(code, babelOptions)) || {};
|
||||
const {ast} = result;
|
||||
invariant(ast != null, 'Missing AST in babel transform results.');
|
||||
return {ast, code: result.code, map: result.map};
|
||||
|
|
|
@ -0,0 +1,21 @@
|
|||
/**
|
||||
* Copyright (c) 2017-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.
|
||||
*
|
||||
* @flow
|
||||
* @format
|
||||
*/
|
||||
|
||||
'use strict';
|
||||
|
||||
import type {Ast, SourceMap as MappingsMap} from 'babel-core';
|
||||
|
||||
export type IntermediateTransformResult = {
|
||||
ast: ?Ast,
|
||||
code: ?string,
|
||||
map: ?MappingsMap,
|
||||
};
|
|
@ -536,9 +536,9 @@ babel-preset-fbjs@^2.1.4:
|
|||
babel-plugin-transform-react-display-name "^6.8.0"
|
||||
babel-plugin-transform-react-jsx "^6.8.0"
|
||||
|
||||
babel-preset-react-native@^2.0.0:
|
||||
version "2.0.0"
|
||||
resolved "https://registry.yarnpkg.com/babel-preset-react-native/-/babel-preset-react-native-2.0.0.tgz#c26c7066c7399df30926fa03c012ef87f2cce5b7"
|
||||
babel-preset-react-native@^2.1.0:
|
||||
version "2.1.0"
|
||||
resolved "https://registry.yarnpkg.com/babel-preset-react-native/-/babel-preset-react-native-2.1.0.tgz#9013ebd82da1c88102bf588810ff59e209ca2b8a"
|
||||
dependencies:
|
||||
babel-plugin-check-es2015-constants "^6.5.0"
|
||||
babel-plugin-react-transform "2.0.2"
|
||||
|
|
Loading…
Reference in New Issue