mirror of
https://github.com/zhigang1992/react-native.git
synced 2026-04-09 22:43:10 +08:00
Remove react-packager indirection.
Summary: This moves the `src` directory one level up and removes the `react-packager` folder. Personally, I always disliked this indirection. I'm reorganizing some things in RNP, so this seems to make sense. Not sure if I forgot to update any paths. Can anyone advice if there are more places that need change? Reviewed By: jeanlauliac Differential Revision: D4487867 fbshipit-source-id: d63f9c79d6238300df9632d2e6a4e6a4196d5ccb
This commit is contained in:
committed by
Facebook Github Bot
parent
0ecc4047af
commit
a2c84d14ce
@@ -0,0 +1,133 @@
|
||||
/**
|
||||
* 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.
|
||||
*/
|
||||
'use strict';
|
||||
|
||||
jest.disableAutomock();
|
||||
|
||||
const collectDependencies = require('../collect-dependencies');
|
||||
const astFromCode = require('babylon').parse;
|
||||
const {codeFromAst, comparableCode} = require('../../test-helpers');
|
||||
|
||||
const {any} = expect;
|
||||
|
||||
describe('dependency collection from ASTs:', () => {
|
||||
it('collects dependency identifiers from the code', () => {
|
||||
const ast = astFromCode(`
|
||||
const a = require('b/lib/a');
|
||||
exports.do = () => require("do");
|
||||
if (!something) {
|
||||
require("setup/something");
|
||||
}
|
||||
`);
|
||||
|
||||
expect(collectDependencies(ast).dependencies)
|
||||
.toEqual(['b/lib/a', 'do', 'setup/something']);
|
||||
});
|
||||
|
||||
it('supports template literals as arguments', () => {
|
||||
const ast = astFromCode('require(`left-pad`)');
|
||||
|
||||
expect(collectDependencies(ast).dependencies)
|
||||
.toEqual(['left-pad']);
|
||||
});
|
||||
|
||||
it('ignores template literals with interpolations', () => {
|
||||
const ast = astFromCode('require(`left${"-"}pad`)');
|
||||
|
||||
expect(collectDependencies(ast).dependencies)
|
||||
.toEqual([]);
|
||||
});
|
||||
|
||||
it('ignores tagged template literals', () => {
|
||||
const ast = astFromCode('require(tag`left-pad`)');
|
||||
|
||||
expect(collectDependencies(ast).dependencies)
|
||||
.toEqual([]);
|
||||
});
|
||||
|
||||
it('exposes a string as `dependencyMapName`', () => {
|
||||
const ast = astFromCode('require("arbitrary")');
|
||||
expect(collectDependencies(ast).dependencyMapName)
|
||||
.toEqual(any(String));
|
||||
});
|
||||
|
||||
it('exposes a string as `dependencyMapName` even without collecting dependencies', () => {
|
||||
const ast = astFromCode('');
|
||||
expect(collectDependencies(ast).dependencyMapName)
|
||||
.toEqual(any(String));
|
||||
});
|
||||
|
||||
it('replaces all required module ID strings with array lookups and keeps the ID as second argument', () => {
|
||||
const ast = astFromCode(`
|
||||
const a = require('b/lib/a');
|
||||
const b = require(123);
|
||||
exports.do = () => require("do");
|
||||
if (!something) {
|
||||
require("setup/something");
|
||||
}
|
||||
`);
|
||||
|
||||
const {dependencyMapName} = collectDependencies(ast);
|
||||
|
||||
expect(codeFromAst(ast)).toEqual(comparableCode(`
|
||||
const a = require(${dependencyMapName}[0], 'b/lib/a');
|
||||
const b = require(123);
|
||||
exports.do = () => require(${dependencyMapName}[1], "do");
|
||||
if (!something) {
|
||||
require(${dependencyMapName}[2], "setup/something");
|
||||
}
|
||||
`));
|
||||
});
|
||||
});
|
||||
|
||||
describe('Dependency collection from optimized ASTs:', () => {
|
||||
const dependencyMapName = 'arbitrary';
|
||||
const {forOptimization} = collectDependencies;
|
||||
let ast, names;
|
||||
|
||||
beforeEach(() => {
|
||||
ast = astFromCode(`
|
||||
const a = require(${dependencyMapName}[0], 'b/lib/a');
|
||||
const b = require(123);
|
||||
exports.do = () => require(${dependencyMapName}[1], "do");
|
||||
if (!something) {
|
||||
require(${dependencyMapName}[2], "setup/something");
|
||||
}
|
||||
`);
|
||||
names = ['b/lib/a', 'do', 'setup/something'];
|
||||
});
|
||||
|
||||
it('passes the `dependencyMapName` through', () => {
|
||||
const result = forOptimization(ast, names, dependencyMapName);
|
||||
expect(result.dependencyMapName).toEqual(dependencyMapName);
|
||||
});
|
||||
|
||||
it('returns the list of passed in dependencies', () => {
|
||||
const result = forOptimization(ast, names, dependencyMapName);
|
||||
expect(result.dependencies).toEqual(names);
|
||||
});
|
||||
|
||||
it('only returns dependencies that are in the code', () => {
|
||||
ast = astFromCode(`require(${dependencyMapName}[1], 'do')`);
|
||||
const result = forOptimization(ast, names, dependencyMapName);
|
||||
expect(result.dependencies).toEqual(['do']);
|
||||
});
|
||||
|
||||
it('replaces all call signatures inserted by a prior call to `collectDependencies`', () => {
|
||||
forOptimization(ast, names, dependencyMapName);
|
||||
expect(codeFromAst(ast)).toEqual(comparableCode(`
|
||||
const a = require(${dependencyMapName}[0]);
|
||||
const b = require(123);
|
||||
exports.do = () => require(${dependencyMapName}[1]);
|
||||
if (!something) {
|
||||
require(${dependencyMapName}[2]);
|
||||
}
|
||||
`));
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,97 @@
|
||||
/**
|
||||
* 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';
|
||||
|
||||
jest.disableAutomock();
|
||||
|
||||
const optimizeModule = require('../optimize-module');
|
||||
const transformModule = require('../transform-module');
|
||||
const transform = require('../../../../transformer.js');
|
||||
const {SourceMapConsumer} = require('source-map');
|
||||
|
||||
const {objectContaining} = jasmine;
|
||||
|
||||
describe('optimizing JS modules', () => {
|
||||
const filename = 'arbitrary/file.js';
|
||||
const optimizationOptions = {
|
||||
dev: false,
|
||||
platform: 'android',
|
||||
};
|
||||
const originalCode =
|
||||
`if (Platform.OS !== 'android') {
|
||||
require('arbitrary-dev');
|
||||
} else {
|
||||
__DEV__ ? require('arbitrary-android-dev') : require('arbitrary-android-prod');
|
||||
}`;
|
||||
|
||||
let transformResult;
|
||||
beforeAll(done => {
|
||||
transformModule(originalCode, {filename, transform}, (error, result) => {
|
||||
if (error) {
|
||||
throw error;
|
||||
}
|
||||
transformResult = JSON.stringify(result);
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('copies everything from the transformed file, except for transform results', () => {
|
||||
const result = optimizeModule(transformResult, optimizationOptions);
|
||||
const expected = JSON.parse(transformResult);
|
||||
delete expected.transformed;
|
||||
expect(result).toEqual(objectContaining(expected));
|
||||
});
|
||||
|
||||
describe('code optimization', () => {
|
||||
let dependencyMapName, injectedVars, optimized, requireName;
|
||||
beforeAll(() => {
|
||||
const result = optimizeModule(transformResult, optimizationOptions);
|
||||
optimized = result.transformed.default;
|
||||
injectedVars = optimized.code.match(/function\(([^)]*)/)[1].split(',');
|
||||
[,requireName,,, dependencyMapName] = injectedVars;
|
||||
});
|
||||
|
||||
it('optimizes code', () => {
|
||||
expect(optimized.code)
|
||||
.toEqual(`__d(function(${injectedVars}){${requireName}(${dependencyMapName}[0])});`);
|
||||
});
|
||||
|
||||
it('extracts dependencies', () => {
|
||||
expect(optimized.dependencies).toEqual(['arbitrary-android-prod']);
|
||||
});
|
||||
|
||||
it('creates source maps', () => {
|
||||
const consumer = new SourceMapConsumer(optimized.map);
|
||||
const column = optimized.code.lastIndexOf(requireName + '(');
|
||||
const loc = findLast(originalCode, 'require');
|
||||
|
||||
expect(consumer.originalPositionFor({line: 1, column}))
|
||||
.toEqual(objectContaining(loc));
|
||||
});
|
||||
|
||||
it('does not extract dependencies for polyfills', () => {
|
||||
const result = optimizeModule(
|
||||
transformResult,
|
||||
{...optimizationOptions, isPolyfill: true},
|
||||
);
|
||||
expect(result.transformed.default.dependencies).toEqual([]);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
function findLast(code, needle) {
|
||||
const lines = code.split(/(?:(?!.)\s)+/);
|
||||
let line = lines.length;
|
||||
while (line--) {
|
||||
const column = lines[line].lastIndexOf(needle);
|
||||
if (column !== -1) {
|
||||
return {line: line + 1, column};
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,232 @@
|
||||
/**
|
||||
* 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';
|
||||
|
||||
jest.disableAutomock();
|
||||
|
||||
const transformModule = require('../transform-module');
|
||||
|
||||
const t = require('babel-types');
|
||||
const {SourceMapConsumer} = require('source-map');
|
||||
const {fn} = require('../../test-helpers');
|
||||
const {parse} = require('babylon');
|
||||
const generate = require('babel-generator').default;
|
||||
const {traverse} = require('babel-core');
|
||||
|
||||
const {any, objectContaining} = jasmine;
|
||||
|
||||
describe('transforming JS modules:', () => {
|
||||
const filename = 'arbitrary';
|
||||
|
||||
let transform;
|
||||
|
||||
beforeEach(() => {
|
||||
transform = fn();
|
||||
transform.stub.yields(null, transformResult());
|
||||
});
|
||||
|
||||
const {bodyAst, sourceCode, transformedCode} = createTestData();
|
||||
|
||||
const options = variants => ({
|
||||
filename,
|
||||
transform,
|
||||
variants,
|
||||
});
|
||||
|
||||
const transformResult = (body = bodyAst) => ({
|
||||
ast: t.file(t.program(body)),
|
||||
});
|
||||
|
||||
it('passes through file name and code', done => {
|
||||
transformModule(sourceCode, options(), (error, result) => {
|
||||
expect(result).toEqual(objectContaining({
|
||||
code: sourceCode,
|
||||
file: filename,
|
||||
}));
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('exposes a haste ID if present', done => {
|
||||
const hasteID = 'TheModule';
|
||||
const codeWithHasteID = `/** @providesModule ${hasteID} */`;
|
||||
transformModule(codeWithHasteID, options(), (error, result) => {
|
||||
expect(result).toEqual(objectContaining({hasteID}));
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('sets `type` to `"module"` by default', done => {
|
||||
transformModule(sourceCode, options(), (error, result) => {
|
||||
expect(result).toEqual(objectContaining({type: 'module'}));
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('sets `type` to `"script"` if the input is a polyfill', done => {
|
||||
transformModule(sourceCode, {...options(), polyfill: true}, (error, result) => {
|
||||
expect(result).toEqual(objectContaining({type: 'script'}));
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('calls the passed-in transform function with code, file name, and options for all passed in variants', done => {
|
||||
const variants = {dev: {dev: true}, prod: {dev: false}};
|
||||
|
||||
transformModule(sourceCode, options(variants), () => {
|
||||
expect(transform)
|
||||
.toBeCalledWith({filename, sourceCode, options: variants.dev}, any(Function));
|
||||
expect(transform)
|
||||
.toBeCalledWith({filename, sourceCode, options: variants.prod}, any(Function));
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('calls back with any error yielded by the transform function', done => {
|
||||
const error = new Error();
|
||||
transform.stub.yields(error);
|
||||
|
||||
transformModule(sourceCode, options(), e => {
|
||||
expect(e).toBe(error);
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('wraps the code produced by the transform function into a module factory', done => {
|
||||
transformModule(sourceCode, options(), (error, result) => {
|
||||
expect(error).toEqual(null);
|
||||
|
||||
const {code, dependencyMapName} = result.transformed.default;
|
||||
expect(code.replace(/\s+/g, ''))
|
||||
.toEqual(
|
||||
`__d(function(global,require,module,exports,${
|
||||
dependencyMapName}){${transformedCode}});`
|
||||
);
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('wraps the code produced by the transform function into an immediately invoked function expression for polyfills', done => {
|
||||
transformModule(sourceCode, {...options(), polyfill: true}, (error, result) => {
|
||||
expect(error).toEqual(null);
|
||||
|
||||
const {code} = result.transformed.default;
|
||||
expect(code.replace(/\s+/g, ''))
|
||||
.toEqual(`(function(global){${transformedCode}})(this);`);
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('creates source maps', done => {
|
||||
transformModule(sourceCode, options(), (error, result) => {
|
||||
const {code, map} = result.transformed.default;
|
||||
const column = code.indexOf('code');
|
||||
const consumer = new SourceMapConsumer(map);
|
||||
expect(consumer.originalPositionFor({line: 1, column}))
|
||||
.toEqual(objectContaining({line: 1, column: sourceCode.indexOf('code')}));
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('extracts dependencies (require calls)', done => {
|
||||
const dep1 = 'foo', dep2 = 'bar';
|
||||
const code = `require('${dep1}'),require('${dep2}')`;
|
||||
const {body} = parse(code).program;
|
||||
transform.stub.yields(null, transformResult(body));
|
||||
|
||||
transformModule(code, options(), (error, result) => {
|
||||
expect(result.transformed.default)
|
||||
.toEqual(objectContaining({dependencies: [dep1, dep2]}));
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('transforms for all variants', done => {
|
||||
const variants = {dev: {dev: true}, prod: {dev: false}};
|
||||
transform.stub
|
||||
.withArgs(filename, sourceCode, variants.dev)
|
||||
.yields(null, transformResult(bodyAst))
|
||||
.withArgs(filename, sourceCode, variants.prod)
|
||||
.yields(null, transformResult([]));
|
||||
|
||||
transformModule(sourceCode, options(variants), (error, result) => {
|
||||
const {dev, prod} = result.transformed;
|
||||
expect(dev.code.replace(/\s+/g, ''))
|
||||
.toEqual(
|
||||
`__d(function(global,require,module,exports,${
|
||||
dev.dependencyMapName}){arbitrary(code);});`
|
||||
);
|
||||
expect(prod.code.replace(/\s+/g, ''))
|
||||
.toEqual(
|
||||
`__d(function(global,require,module,exports,${
|
||||
prod.dependencyMapName}){arbitrary(code);});`
|
||||
);
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('prefixes JSON files with `module.exports = `', done => {
|
||||
const json = '{"foo":"bar"}';
|
||||
|
||||
transformModule(json, {...options(), filename: 'some.json'}, (error, result) => {
|
||||
const {code} = result.transformed.default;
|
||||
expect(code.replace(/\s+/g, ''))
|
||||
.toEqual(
|
||||
'__d(function(global,require,module,exports){' +
|
||||
`module.exports=${json}});`
|
||||
);
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('does not create source maps for JSON files', done => {
|
||||
transformModule('{}', {...options(), filename: 'some.json'}, (error, result) => {
|
||||
expect(result.transformed.default)
|
||||
.toEqual(objectContaining({map: null}));
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('adds package data for `package.json` files', done => {
|
||||
const pkg = {
|
||||
name: 'package-name',
|
||||
main: 'package/main',
|
||||
browser: {browser: 'defs'},
|
||||
'react-native': {'react-native': 'defs'},
|
||||
};
|
||||
|
||||
transformModule(
|
||||
JSON.stringify(pkg),
|
||||
{...options(), filename: 'arbitrary/package.json'},
|
||||
(error, result) => {
|
||||
expect(result.package).toEqual(pkg);
|
||||
done();
|
||||
},
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
function createTestData() {
|
||||
// creates test data with an transformed AST, so that we can test source
|
||||
// map generation.
|
||||
const sourceCode = 'some(arbitrary(code));';
|
||||
const fileAst = parse(sourceCode);
|
||||
traverse(fileAst, {
|
||||
CallExpression(path) {
|
||||
if (path.node.callee.name === 'some') {
|
||||
path.replaceWith(path.node.arguments[0]);
|
||||
}
|
||||
}
|
||||
});
|
||||
return {
|
||||
bodyAst: fileAst.program.body,
|
||||
sourceCode,
|
||||
transformedCode: generate(fileAst).code,
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,89 @@
|
||||
/**
|
||||
* 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';
|
||||
|
||||
jest
|
||||
.disableAutomock()
|
||||
.setMock('fs', jest.genMockFromModule('fs'))
|
||||
.mock('mkdirp');
|
||||
|
||||
const wrapWorkerFn = require('../wrap-worker-fn');
|
||||
const {dirname} = require('path');
|
||||
const {fn} = require('../../test-helpers');
|
||||
|
||||
const {any} = jasmine;
|
||||
|
||||
describe('wrapWorkerFn:', () => {
|
||||
const infile = '/arbitrary/in/file';
|
||||
const outfile = '/arbitrary/in/file';
|
||||
|
||||
let workerFn, wrapped;
|
||||
beforeEach(() => {
|
||||
workerFn = fn();
|
||||
workerFn.stub.yields();
|
||||
wrapped = wrapWorkerFn(workerFn);
|
||||
});
|
||||
|
||||
const fs = require('fs');
|
||||
const mkdirp = require('mkdirp');
|
||||
|
||||
it('reads the passed-in file synchronously as UTF-8', done => {
|
||||
wrapped(infile, outfile, {}, () => {
|
||||
expect(fs.readFileSync).toBeCalledWith(infile, 'utf8');
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('calls the worker function with file contents and options', done => {
|
||||
const contents = 'arbitrary(contents);';
|
||||
const options = {arbitrary: 'options'};
|
||||
fs.readFileSync.mockReturnValue(contents);
|
||||
wrapped(infile, outfile, options, () => {
|
||||
expect(workerFn).toBeCalledWith(contents, options, any(Function));
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('passes through any error that the worker function calls back with', done => {
|
||||
const error = new Error();
|
||||
workerFn.stub.yields(error);
|
||||
wrapped(infile, outfile, {}, e => {
|
||||
expect(e).toBe(error);
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('writes the result to disk', done => {
|
||||
const result = {arbitrary: 'result'};
|
||||
workerFn.stub.yields(null, result);
|
||||
wrapped(infile, outfile, {}, () => {
|
||||
expect(mkdirp.sync).toBeCalledWith(dirname(outfile));
|
||||
expect(fs.writeFileSync).toBeCalledWith(outfile, JSON.stringify(result), 'utf8');
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('calls back with any error thrown by `mkdirp.sync`', done => {
|
||||
const error = new Error();
|
||||
mkdirp.sync.mockImplementationOnce(() => { throw error; });
|
||||
wrapped(infile, outfile, {}, e => {
|
||||
expect(e).toBe(error);
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('calls back with any error thrown by `fs.writeFileSync`', done => {
|
||||
const error = new Error();
|
||||
fs.writeFileSync.mockImplementationOnce(() => { throw error; });
|
||||
wrapped(infile, outfile, {}, e => {
|
||||
expect(e).toBe(error);
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
||||
150
packager/src/ModuleGraph/worker/collect-dependencies.js
Normal file
150
packager/src/ModuleGraph/worker/collect-dependencies.js
Normal file
@@ -0,0 +1,150 @@
|
||||
/**
|
||||
* 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.
|
||||
*
|
||||
* @flow
|
||||
*/
|
||||
'use strict';
|
||||
|
||||
const nullthrows = require('fbjs/lib/nullthrows');
|
||||
|
||||
const {traverse, types} = require('babel-core');
|
||||
|
||||
type AST = Object;
|
||||
|
||||
class Replacement {
|
||||
nameToIndex: Map<string, number>;
|
||||
nextIndex: number;
|
||||
|
||||
constructor() {
|
||||
this.nameToIndex = new Map();
|
||||
this.nextIndex = 0;
|
||||
}
|
||||
|
||||
isRequireCall(callee, firstArg) {
|
||||
return (
|
||||
callee.type === 'Identifier' && callee.name === 'require' &&
|
||||
firstArg && isLiteralString(firstArg)
|
||||
);
|
||||
}
|
||||
|
||||
getIndex(stringLiteralOrTemplateLiteral) {
|
||||
const name = stringLiteralOrTemplateLiteral.quasis
|
||||
? stringLiteralOrTemplateLiteral.quasis[0].value.cooked
|
||||
: stringLiteralOrTemplateLiteral.value;
|
||||
let index = this.nameToIndex.get(name);
|
||||
if (index !== undefined) {
|
||||
return index;
|
||||
}
|
||||
index = this.nextIndex++;
|
||||
this.nameToIndex.set(name, index);
|
||||
return index;
|
||||
}
|
||||
|
||||
getNames() {
|
||||
return Array.from(this.nameToIndex.keys());
|
||||
}
|
||||
|
||||
makeArgs(newId, oldId, dependencyMapIdentifier) {
|
||||
const mapLookup = createMapLookup(dependencyMapIdentifier, newId);
|
||||
return [mapLookup, oldId];
|
||||
}
|
||||
}
|
||||
|
||||
class ProdReplacement {
|
||||
replacement: Replacement;
|
||||
names: Array<string>;
|
||||
|
||||
constructor(names) {
|
||||
this.replacement = new Replacement();
|
||||
this.names = names;
|
||||
}
|
||||
|
||||
isRequireCall(callee, firstArg) {
|
||||
return (
|
||||
callee.type === 'Identifier' &&
|
||||
callee.name === 'require' &&
|
||||
firstArg &&
|
||||
firstArg.type === 'MemberExpression' &&
|
||||
firstArg.property &&
|
||||
firstArg.property.type === 'NumericLiteral'
|
||||
);
|
||||
}
|
||||
|
||||
getIndex(memberExpression) {
|
||||
const id = memberExpression.property.value;
|
||||
if (id in this.names) {
|
||||
return this.replacement.getIndex({value: this.names[id]});
|
||||
}
|
||||
|
||||
throw new Error(
|
||||
`${id} is not a known module ID. Existing mappings: ${
|
||||
this.names.map((n, i) => `${i} => ${n}`).join(', ')}`
|
||||
);
|
||||
}
|
||||
|
||||
getNames() {
|
||||
return this.replacement.getNames();
|
||||
}
|
||||
|
||||
makeArgs(newId, _, dependencyMapIdentifier) {
|
||||
const mapLookup = createMapLookup(dependencyMapIdentifier, newId);
|
||||
return [mapLookup];
|
||||
}
|
||||
}
|
||||
|
||||
function createMapLookup(dependencyMapIdentifier, propertyIdentifier) {
|
||||
return types.memberExpression(
|
||||
dependencyMapIdentifier,
|
||||
propertyIdentifier,
|
||||
true,
|
||||
);
|
||||
}
|
||||
|
||||
function collectDependencies(ast, replacement, dependencyMapIdentifier) {
|
||||
const traversalState = {dependencyMapIdentifier};
|
||||
traverse(ast, {
|
||||
Program(path, state) {
|
||||
if (!state.dependencyMapIdentifier) {
|
||||
state.dependencyMapIdentifier =
|
||||
path.scope.generateUidIdentifier('dependencyMap');
|
||||
}
|
||||
},
|
||||
CallExpression(path, state) {
|
||||
const node = path.node;
|
||||
const arg = node.arguments[0];
|
||||
if (replacement.isRequireCall(node.callee, arg)) {
|
||||
const index = replacement.getIndex(arg);
|
||||
node.arguments = replacement.makeArgs(
|
||||
types.numericLiteral(index),
|
||||
arg,
|
||||
state.dependencyMapIdentifier,
|
||||
);
|
||||
}
|
||||
},
|
||||
}, null, traversalState);
|
||||
|
||||
return {
|
||||
dependencies: replacement.getNames(),
|
||||
dependencyMapName: nullthrows(traversalState.dependencyMapIdentifier).name,
|
||||
};
|
||||
}
|
||||
|
||||
function isLiteralString(node) {
|
||||
return node.type === 'StringLiteral' ||
|
||||
node.type === 'TemplateLiteral' && node.quasis.length === 1;
|
||||
}
|
||||
|
||||
exports = module.exports =
|
||||
(ast: AST) => collectDependencies(ast, new Replacement());
|
||||
exports.forOptimization =
|
||||
(ast: AST, names: Array<string>, dependencyMapName?: string) =>
|
||||
collectDependencies(
|
||||
ast,
|
||||
new ProdReplacement(names),
|
||||
dependencyMapName ? types.identifier(dependencyMapName) : undefined,
|
||||
);
|
||||
26
packager/src/ModuleGraph/worker/generate.js
Normal file
26
packager/src/ModuleGraph/worker/generate.js
Normal file
@@ -0,0 +1,26 @@
|
||||
/**
|
||||
* 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.
|
||||
*
|
||||
* @flow
|
||||
*/
|
||||
'use strict';
|
||||
|
||||
const babelGenerate = require('babel-generator').default;
|
||||
|
||||
function generate(ast: Object, filename: string, sourceCode: string) {
|
||||
return babelGenerate(ast, {
|
||||
comments: false,
|
||||
compact: true,
|
||||
filename,
|
||||
sourceFileName: filename,
|
||||
sourceMaps: true,
|
||||
sourceMapTarget: filename,
|
||||
}, sourceCode);
|
||||
}
|
||||
|
||||
module.exports = generate;
|
||||
108
packager/src/ModuleGraph/worker/optimize-module.js
Normal file
108
packager/src/ModuleGraph/worker/optimize-module.js
Normal file
@@ -0,0 +1,108 @@
|
||||
/**
|
||||
* 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.
|
||||
*
|
||||
* @flow
|
||||
*/
|
||||
'use strict';
|
||||
|
||||
const babel = require('babel-core');
|
||||
const collectDependencies = require('./collect-dependencies');
|
||||
const constantFolding = require('../../JSTransformer/worker/constant-folding').plugin;
|
||||
const generate = require('./generate');
|
||||
const inline = require('../../JSTransformer/worker/inline').plugin;
|
||||
const minify = require('../../JSTransformer/worker/minify');
|
||||
const sourceMap = require('source-map');
|
||||
|
||||
import type {TransformedFile, TransformResult} from '../types.flow';
|
||||
|
||||
export type OptimizationOptions = {|
|
||||
dev: boolean,
|
||||
isPolyfill?: boolean,
|
||||
platform: string,
|
||||
|};
|
||||
|
||||
function optimizeModule(
|
||||
data: string | TransformedFile,
|
||||
optimizationOptions: OptimizationOptions,
|
||||
): TransformedFile {
|
||||
if (typeof data === 'string') {
|
||||
data = JSON.parse(data);
|
||||
}
|
||||
const {code, file, transformed} = data;
|
||||
const result = {...data, transformed: {}};
|
||||
|
||||
//$FlowIssue #14545724
|
||||
Object.entries(transformed).forEach(([k, t: TransformResult]: [*, TransformResult]) => {
|
||||
result.transformed[k] = optimize(t, file, code, optimizationOptions);
|
||||
});
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
function optimize(transformed, file, originalCode, options): TransformResult {
|
||||
const {code, dependencyMapName, map} = transformed;
|
||||
const optimized = optimizeCode(code, map, file, options);
|
||||
|
||||
let dependencies;
|
||||
if (options.isPolyfill) {
|
||||
dependencies = [];
|
||||
} else {
|
||||
({dependencies} = collectDependencies.forOptimization(
|
||||
optimized.ast,
|
||||
transformed.dependencies,
|
||||
dependencyMapName,
|
||||
));
|
||||
}
|
||||
|
||||
const inputMap = transformed.map;
|
||||
const gen = generate(optimized.ast, file, originalCode);
|
||||
|
||||
const min = minify(
|
||||
file,
|
||||
gen.code,
|
||||
inputMap && mergeSourceMaps(file, inputMap, gen.map),
|
||||
);
|
||||
return {code: min.code, map: inputMap && min.map, dependencies};
|
||||
}
|
||||
|
||||
function optimizeCode(code, map, filename, inliningOptions) {
|
||||
return babel.transform(code, {
|
||||
plugins: [
|
||||
[constantFolding],
|
||||
[inline, {...inliningOptions, isWrapped: true}],
|
||||
],
|
||||
babelrc: false,
|
||||
code: false,
|
||||
filename,
|
||||
});
|
||||
}
|
||||
|
||||
function mergeSourceMaps(file, originalMap, secondMap) {
|
||||
const merged = new sourceMap.SourceMapGenerator();
|
||||
const inputMap = new sourceMap.SourceMapConsumer(originalMap);
|
||||
new sourceMap.SourceMapConsumer(secondMap)
|
||||
.eachMapping(mapping => {
|
||||
const original = inputMap.originalPositionFor({
|
||||
line: mapping.originalLine,
|
||||
column: mapping.originalColumn,
|
||||
});
|
||||
if (original.line == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
merged.addMapping({
|
||||
generated: {line: mapping.generatedLine, column: mapping.generatedColumn},
|
||||
original: {line: original.line, column: original.column || 0},
|
||||
source: file,
|
||||
name: original.name || mapping.name,
|
||||
});
|
||||
});
|
||||
return merged.toJSON();
|
||||
}
|
||||
|
||||
module.exports = optimizeModule;
|
||||
165
packager/src/ModuleGraph/worker/transform-module.js
Normal file
165
packager/src/ModuleGraph/worker/transform-module.js
Normal file
@@ -0,0 +1,165 @@
|
||||
/**
|
||||
* 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.
|
||||
*
|
||||
* @flow
|
||||
*/
|
||||
'use strict';
|
||||
|
||||
const babel = require('babel-core');
|
||||
const collectDependencies = require('./collect-dependencies');
|
||||
const docblock = require('../../node-haste/DependencyGraph/docblock');
|
||||
const generate = require('./generate');
|
||||
const series = require('async/series');
|
||||
|
||||
const {basename} = require('path');
|
||||
|
||||
import type {
|
||||
Callback,
|
||||
TransformedFile,
|
||||
TransformFn,
|
||||
TransformFnResult,
|
||||
TransformResult,
|
||||
TransformVariants,
|
||||
} from '../types.flow';
|
||||
|
||||
export type TransformOptions = {|
|
||||
filename: string,
|
||||
polyfill?: boolean,
|
||||
transform: TransformFn,
|
||||
variants?: TransformVariants,
|
||||
|};
|
||||
|
||||
const defaultVariants = {default: {}};
|
||||
const moduleFactoryParameters = ['global', 'require', 'module', 'exports'];
|
||||
const polyfillFactoryParameters = ['global'];
|
||||
|
||||
function transformModule(
|
||||
code: string,
|
||||
options: TransformOptions,
|
||||
callback: Callback<TransformedFile>,
|
||||
): void {
|
||||
if (options.filename.endsWith('.json')) {
|
||||
return transformJSON(code, options, callback);
|
||||
}
|
||||
|
||||
const {filename, transform, variants = defaultVariants} = options;
|
||||
const tasks = {};
|
||||
Object.keys(variants).forEach(name => {
|
||||
tasks[name] = cb => transform({
|
||||
filename,
|
||||
sourceCode: code,
|
||||
options: variants[name],
|
||||
}, cb);
|
||||
});
|
||||
|
||||
series(tasks, (error, results: {[key: string]: TransformFnResult}) => {
|
||||
if (error) {
|
||||
callback(error);
|
||||
return;
|
||||
}
|
||||
|
||||
const transformed: {[key: string]: TransformResult} = {};
|
||||
|
||||
//$FlowIssue #14545724
|
||||
Object.entries(results).forEach(([key, value]: [*, TransformFnResult]) => {
|
||||
transformed[key] = makeResult(value.ast, filename, code, options.polyfill);
|
||||
});
|
||||
|
||||
const annotations = docblock.parseAsObject(docblock.extract(code));
|
||||
|
||||
callback(null, {
|
||||
code,
|
||||
file: filename,
|
||||
hasteID: annotations.providesModule || annotations.provide || null,
|
||||
transformed,
|
||||
type: options.polyfill ? 'script' : 'module',
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function transformJSON(json, options, callback) {
|
||||
const value = JSON.parse(json);
|
||||
const {filename} = options;
|
||||
const code =
|
||||
`__d(function(${moduleFactoryParameters.join(', ')}) { module.exports = \n${
|
||||
json
|
||||
}\n});`;
|
||||
|
||||
const moduleData = {
|
||||
code,
|
||||
map: null, // no source map for JSON files!
|
||||
dependencies: [],
|
||||
};
|
||||
const transformed = {};
|
||||
|
||||
Object
|
||||
.keys(options.variants || defaultVariants)
|
||||
.forEach(key => (transformed[key] = moduleData));
|
||||
|
||||
const result: TransformedFile = {
|
||||
code: json,
|
||||
file: filename,
|
||||
hasteID: value.name,
|
||||
transformed,
|
||||
type: 'module',
|
||||
};
|
||||
|
||||
if (basename(filename) === 'package.json') {
|
||||
result.package = {
|
||||
name: value.name,
|
||||
main: value.main,
|
||||
browser: value.browser,
|
||||
'react-native': value['react-native'],
|
||||
};
|
||||
}
|
||||
callback(null, result);
|
||||
}
|
||||
|
||||
function makeResult(ast, filename, sourceCode, isPolyfill = false) {
|
||||
let dependencies, dependencyMapName, file;
|
||||
if (isPolyfill) {
|
||||
dependencies = [];
|
||||
file = wrapPolyfill(ast);
|
||||
} else {
|
||||
({dependencies, dependencyMapName} = collectDependencies(ast));
|
||||
file = wrapModule(ast, dependencyMapName);
|
||||
}
|
||||
|
||||
const gen = generate(file, filename, sourceCode);
|
||||
return {code: gen.code, map: gen.map, dependencies, dependencyMapName};
|
||||
}
|
||||
|
||||
function wrapModule(file, dependencyMapName) {
|
||||
const t = babel.types;
|
||||
const params = moduleFactoryParameters.concat(dependencyMapName);
|
||||
const factory = functionFromProgram(file.program, params);
|
||||
const def = t.callExpression(t.identifier('__d'), [factory]);
|
||||
return t.file(t.program([t.expressionStatement(def)]));
|
||||
}
|
||||
|
||||
function wrapPolyfill(file) {
|
||||
const t = babel.types;
|
||||
const factory = functionFromProgram(file.program, polyfillFactoryParameters);
|
||||
const iife = t.callExpression(factory, [t.identifier('this')]);
|
||||
return t.file(t.program([t.expressionStatement(iife)]));
|
||||
}
|
||||
|
||||
function functionFromProgram(program, parameters) {
|
||||
const t = babel.types;
|
||||
return t.functionExpression(
|
||||
t.identifier(''),
|
||||
parameters.map(makeIdentifier),
|
||||
t.blockStatement(program.body, program.directives),
|
||||
);
|
||||
}
|
||||
|
||||
function makeIdentifier(name) {
|
||||
return babel.types.identifier(name);
|
||||
}
|
||||
|
||||
module.exports = transformModule;
|
||||
62
packager/src/ModuleGraph/worker/wrap-worker-fn.js
Normal file
62
packager/src/ModuleGraph/worker/wrap-worker-fn.js
Normal file
@@ -0,0 +1,62 @@
|
||||
/**
|
||||
* 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.
|
||||
*
|
||||
* @flow
|
||||
*/
|
||||
'use strict';
|
||||
|
||||
const fs = require('fs');
|
||||
const mkdirp = require('mkdirp');
|
||||
|
||||
const {dirname} = require('path');
|
||||
|
||||
import type {Callback} from '../types.flow';
|
||||
|
||||
type Path = string;
|
||||
type WorkerFn<Options> = (
|
||||
fileContents: string,
|
||||
options: Options,
|
||||
callback: Callback<Object>,
|
||||
) => void;
|
||||
export type WorkerFnWithIO<Options> = (
|
||||
infile: Path,
|
||||
outfile: Path,
|
||||
options: Options,
|
||||
callback: Callback<>,
|
||||
) => void;
|
||||
|
||||
function wrapWorkerFn<Options>(
|
||||
workerFunction: WorkerFn<Options>,
|
||||
): WorkerFnWithIO<Options> {
|
||||
return (
|
||||
infile: Path,
|
||||
outfile: Path,
|
||||
options: Options,
|
||||
callback: Callback<>,
|
||||
) => {
|
||||
const contents = fs.readFileSync(infile, 'utf8');
|
||||
workerFunction(contents, options, (error, result) => {
|
||||
if (error) {
|
||||
callback(error);
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
mkdirp.sync(dirname(outfile));
|
||||
fs.writeFileSync(outfile, JSON.stringify(result), 'utf8');
|
||||
} catch (writeError) {
|
||||
callback(writeError);
|
||||
return;
|
||||
}
|
||||
|
||||
callback(null);
|
||||
});
|
||||
};
|
||||
}
|
||||
|
||||
module.exports = wrapWorkerFn;
|
||||
Reference in New Issue
Block a user