mirror of
https://github.com/zhigang1992/react-native.git
synced 2026-04-06 09:31:11 +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
173
packager/src/ModuleGraph/Graph.js
Normal file
173
packager/src/ModuleGraph/Graph.js
Normal file
@@ -0,0 +1,173 @@
|
||||
/**
|
||||
* 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 invariant = require('fbjs/lib/invariant');
|
||||
const memoize = require('async/memoize');
|
||||
const nullthrows = require('fbjs/lib/nullthrows');
|
||||
const queue = require('async/queue');
|
||||
const seq = require('async/seq');
|
||||
|
||||
import type {
|
||||
Callback,
|
||||
File,
|
||||
GraphFn,
|
||||
LoadFn,
|
||||
ResolveFn,
|
||||
} from './types.flow';
|
||||
|
||||
type Async$Queue<T, C> = {
|
||||
buffer: number,
|
||||
concurrency: number,
|
||||
drain: () => mixed,
|
||||
empty: () => mixed,
|
||||
error: (Error, T) => mixed,
|
||||
idle(): boolean,
|
||||
kill(): void,
|
||||
length(): number,
|
||||
pause(): void,
|
||||
paused: boolean,
|
||||
push(T | Array<T>, void | C): void,
|
||||
resume(): void,
|
||||
running(): number,
|
||||
saturated: () => mixed,
|
||||
started: boolean,
|
||||
unsaturated: () => mixed,
|
||||
unshift(T, void | C): void,
|
||||
workersList(): Array<T>,
|
||||
};
|
||||
|
||||
type LoadQueue =
|
||||
Async$Queue<{id: string, parent: string}, Callback<File, Array<string>>>;
|
||||
|
||||
const createParentModule =
|
||||
() => ({file: {code: '', type: 'script', path: ''}, dependencies: []});
|
||||
|
||||
const noop = () => {};
|
||||
const NO_OPTIONS = {};
|
||||
|
||||
exports.create = function create(resolve: ResolveFn, load: LoadFn): GraphFn {
|
||||
function Graph(entryPoints, platform, options, callback = noop) {
|
||||
const {
|
||||
cwd = '',
|
||||
log = (console: any),
|
||||
optimize = false,
|
||||
skip,
|
||||
} = options || NO_OPTIONS;
|
||||
|
||||
if (typeof platform !== 'string') {
|
||||
log.error('`Graph`, called without a platform');
|
||||
callback(Error('The target platform has to be passed'));
|
||||
return;
|
||||
}
|
||||
|
||||
const loadQueue: LoadQueue = queue(seq(
|
||||
({id, parent}, cb) => resolve(id, parent, platform, options || NO_OPTIONS, cb),
|
||||
memoize((file, cb) => load(file, {log, optimize}, cb)),
|
||||
), Number.MAX_SAFE_INTEGER);
|
||||
|
||||
const {collect, loadModule} = createGraphHelpers(loadQueue, cwd, skip);
|
||||
|
||||
loadQueue.drain = () => {
|
||||
loadQueue.kill();
|
||||
callback(null, collect());
|
||||
};
|
||||
loadQueue.error = error => {
|
||||
loadQueue.error = noop;
|
||||
loadQueue.kill();
|
||||
callback(error);
|
||||
};
|
||||
|
||||
let i = 0;
|
||||
for (const entryPoint of entryPoints) {
|
||||
loadModule(entryPoint, null, i++);
|
||||
}
|
||||
|
||||
if (i === 0) {
|
||||
log.error('`Graph` called without any entry points');
|
||||
loadQueue.kill();
|
||||
callback(Error('At least one entry point has to be passed.'));
|
||||
}
|
||||
}
|
||||
|
||||
return Graph;
|
||||
};
|
||||
|
||||
function createGraphHelpers(loadQueue, cwd, skip) {
|
||||
const modules = new Map([[null, createParentModule()]]);
|
||||
|
||||
function collect(
|
||||
path = null,
|
||||
serialized = {entryModules: [], modules: []},
|
||||
seen = new Set(),
|
||||
) {
|
||||
const module = modules.get(path);
|
||||
if (module == null || seen.has(path)) {
|
||||
return serialized;
|
||||
}
|
||||
|
||||
const {dependencies} = module;
|
||||
if (path === null) {
|
||||
serialized.entryModules =
|
||||
dependencies.map(dep => nullthrows(modules.get(dep.path)));
|
||||
} else {
|
||||
serialized.modules.push(module);
|
||||
seen.add(path);
|
||||
}
|
||||
|
||||
for (const dependency of dependencies) {
|
||||
collect(dependency.path, serialized, seen);
|
||||
}
|
||||
|
||||
return serialized;
|
||||
}
|
||||
|
||||
function loadModule(id, parent, parentDepIndex) {
|
||||
loadQueue.push(
|
||||
{id, parent: parent != null ? parent : cwd},
|
||||
(error, file, dependencyIDs) =>
|
||||
onFileLoaded(error, file, dependencyIDs, id, parent, parentDepIndex),
|
||||
);
|
||||
}
|
||||
|
||||
function onFileLoaded(
|
||||
error,
|
||||
file,
|
||||
dependencyIDs,
|
||||
id,
|
||||
parent,
|
||||
parentDependencyIndex,
|
||||
) {
|
||||
if (error) {
|
||||
return;
|
||||
}
|
||||
|
||||
const {path} = nullthrows(file);
|
||||
dependencyIDs = nullthrows(dependencyIDs);
|
||||
|
||||
const parentModule = modules.get(parent);
|
||||
invariant(parentModule, 'Invalid parent module: ' + String(parent));
|
||||
parentModule.dependencies[parentDependencyIndex] = {id, path};
|
||||
|
||||
if ((!skip || !skip.has(path)) && !modules.has(path)) {
|
||||
const module = {
|
||||
dependencies: Array(dependencyIDs.length),
|
||||
file: nullthrows(file),
|
||||
};
|
||||
modules.set(path, module);
|
||||
for (let i = 0; i < dependencyIDs.length; ++i) {
|
||||
loadModule(dependencyIDs[i], path, i);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return {collect, loadModule};
|
||||
}
|
||||
101
packager/src/ModuleGraph/ModuleGraph.js
Normal file
101
packager/src/ModuleGraph/ModuleGraph.js
Normal file
@@ -0,0 +1,101 @@
|
||||
/**
|
||||
* 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 defaults = require('../../defaults');
|
||||
const nullthrows = require('fbjs/lib/nullthrows');
|
||||
const parallel = require('async/parallel');
|
||||
const seq = require('async/seq');
|
||||
|
||||
const {virtualModule} = require('./output/util');
|
||||
|
||||
import type {
|
||||
Callback,
|
||||
GraphFn,
|
||||
GraphResult,
|
||||
Module,
|
||||
} from './types.flow';
|
||||
|
||||
type BuildFn = (
|
||||
entryPoints: Iterable<string>,
|
||||
options: BuildOptions,
|
||||
callback: Callback<{modules: Iterable<Module>, entryModules: Iterable<Module>}>,
|
||||
) => void;
|
||||
|
||||
type BuildOptions = {|
|
||||
optimize?: boolean,
|
||||
platform?: string,
|
||||
|};
|
||||
|
||||
exports.createBuildSetup = (
|
||||
graph: GraphFn,
|
||||
translateDefaultsPath: string => string = x => x,
|
||||
): BuildFn =>
|
||||
(entryPoints, options, callback) => {
|
||||
const {
|
||||
optimize = false,
|
||||
platform = defaults.platforms[0],
|
||||
} = options;
|
||||
const graphOptions = {optimize};
|
||||
|
||||
const graphWithOptions =
|
||||
(entry, cb) => graph(entry, platform, graphOptions, cb);
|
||||
const graphOnlyModules = seq(graphWithOptions, getModules);
|
||||
|
||||
parallel({
|
||||
graph: cb => graphWithOptions(
|
||||
concat(defaults.runBeforeMainModule, entryPoints),
|
||||
cb,
|
||||
),
|
||||
moduleSystem: cb => graphOnlyModules(
|
||||
[translateDefaultsPath(defaults.moduleSystem)],
|
||||
cb,
|
||||
),
|
||||
polyfills: cb => graphOnlyModules(
|
||||
defaults.polyfills.map(translateDefaultsPath),
|
||||
cb,
|
||||
),
|
||||
}, (
|
||||
error: ?Error,
|
||||
result?: {graph: GraphResult, moduleSystem: Array<Module>, polyfills: Array<Module>},
|
||||
) => {
|
||||
if (error) {
|
||||
callback(error);
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
const {
|
||||
graph: {modules, entryModules},
|
||||
moduleSystem,
|
||||
polyfills,
|
||||
} = nullthrows(result);
|
||||
|
||||
callback(null, {
|
||||
entryModules,
|
||||
modules: concat([prelude(optimize)], moduleSystem, polyfills, modules),
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
const getModules = (x, cb) => cb(null, x.modules);
|
||||
|
||||
function* concat<T>(...iterables: Array<Iterable<T>>): Iterable<T> {
|
||||
for (const it of iterables) {
|
||||
yield* it;
|
||||
}
|
||||
}
|
||||
|
||||
function prelude(optimize) {
|
||||
return virtualModule(
|
||||
`var __DEV__=${String(!optimize)},__BUNDLE_START_TIME__=Date.now();`
|
||||
);
|
||||
}
|
||||
376
packager/src/ModuleGraph/__tests__/Graph-test.js
Normal file
376
packager/src/ModuleGraph/__tests__/Graph-test.js
Normal file
@@ -0,0 +1,376 @@
|
||||
/**
|
||||
* 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()
|
||||
.useRealTimers()
|
||||
.mock('console');
|
||||
|
||||
const {Console} = require('console');
|
||||
const Graph = require('../Graph');
|
||||
const {fn} = require('../test-helpers');
|
||||
|
||||
const {any, objectContaining} = jasmine;
|
||||
const quiet = new Console();
|
||||
|
||||
describe('Graph:', () => {
|
||||
const anyEntry = ['arbitrary/entry/point'];
|
||||
const anyPlatform = 'arbitrary platform';
|
||||
const noOpts = undefined;
|
||||
|
||||
let graph, load, resolve;
|
||||
beforeEach(() => {
|
||||
load = fn();
|
||||
resolve = fn();
|
||||
resolve.stub.yields(null, 'arbitrary file');
|
||||
load.stub.yields(null, createFile('arbitrary file'), []);
|
||||
|
||||
graph = Graph.create(resolve, load);
|
||||
});
|
||||
|
||||
it('calls back an error when called without any entry point', done => {
|
||||
graph([], anyPlatform, {log: quiet}, (error) => {
|
||||
expect(error).toEqual(any(Error));
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('resolves the entry point with the passed-in `resolve` function', done => {
|
||||
const entryPoint = '/arbitrary/path';
|
||||
graph([entryPoint], anyPlatform, noOpts, () => {
|
||||
expect(resolve).toBeCalledWith(
|
||||
entryPoint, '', any(String), any(Object), any(Function));
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('allows to specify multiple entry points', done => {
|
||||
const entryPoints = ['Arbitrary', '../entry.js'];
|
||||
graph(entryPoints, anyPlatform, noOpts, () => {
|
||||
expect(resolve).toBeCalledWith(
|
||||
entryPoints[0], '', any(String), any(Object), any(Function));
|
||||
expect(resolve).toBeCalledWith(
|
||||
entryPoints[1], '', any(String), any(Object), any(Function));
|
||||
done();
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
it('calls back with an error when called without `platform` option', done => {
|
||||
graph(anyEntry, undefined, {log: quiet}, error => {
|
||||
expect(error).toEqual(any(Error));
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('forwards a passed-in `platform` to `resolve`', done => {
|
||||
const platform = 'any';
|
||||
graph(anyEntry, platform, noOpts, () => {
|
||||
expect(resolve).toBeCalledWith(
|
||||
any(String), '', platform, any(Object), any(Function));
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('forwards a passed-in `log` option to `resolve`', done => {
|
||||
const log = new Console();
|
||||
graph(anyEntry, anyPlatform, {log}, () => {
|
||||
expect(resolve).toBeCalledWith(
|
||||
any(String), '', any(String), objectContaining({log}), any(Function));
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('calls back with every error produced by `resolve`', done => {
|
||||
const error = Error();
|
||||
resolve.stub.yields(error);
|
||||
graph(anyEntry, anyPlatform, noOpts, e => {
|
||||
expect(e).toBe(error);
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('only calls back once if two parallel invocations of `resolve` fail', done => {
|
||||
load.stub.yields(null, createFile('with two deps'), ['depA', 'depB']);
|
||||
resolve.stub
|
||||
.withArgs('depA').yieldsAsync(new Error())
|
||||
.withArgs('depB').yieldsAsync(new Error());
|
||||
|
||||
let calls = 0;
|
||||
function callback() {
|
||||
if (calls === 0) {
|
||||
process.nextTick(() => {
|
||||
expect(calls).toEqual(1);
|
||||
done();
|
||||
});
|
||||
}
|
||||
++calls;
|
||||
}
|
||||
|
||||
graph(['entryA', 'entryB'], anyPlatform, noOpts, callback);
|
||||
});
|
||||
|
||||
it('passes the files returned by `resolve` on to the `load` function', done => {
|
||||
const modules = new Map([
|
||||
['Arbitrary', '/absolute/path/to/Arbitrary.js'],
|
||||
['../entry.js', '/whereever/is/entry.js'],
|
||||
]);
|
||||
for (const [id, file] of modules) {
|
||||
resolve.stub.withArgs(id).yields(null, file);
|
||||
}
|
||||
const [file1, file2] = modules.values();
|
||||
|
||||
graph(modules.keys(), anyPlatform, noOpts, () => {
|
||||
expect(load).toBeCalledWith(file1, any(Object), any(Function));
|
||||
expect(load).toBeCalledWith(file2, any(Object), any(Function));
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('passes the `optimize` flag on to `load`', done => {
|
||||
graph(anyEntry, anyPlatform, {optimize: true}, () => {
|
||||
expect(load).toBeCalledWith(
|
||||
any(String), objectContaining({optimize: true}), any(Function));
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('uses `false` as the default for the `optimize` flag', done => {
|
||||
graph(anyEntry, anyPlatform, noOpts, () => {
|
||||
expect(load).toBeCalledWith(
|
||||
any(String), objectContaining({optimize: false}), any(Function));
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('forwards a passed-in `log` to `load`', done => {
|
||||
const log = new Console();
|
||||
graph(anyEntry, anyPlatform, {log}, () => {
|
||||
expect(load)
|
||||
.toBeCalledWith(any(String), objectContaining({log}), any(Function));
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('calls back with every error produced by `load`', done => {
|
||||
const error = Error();
|
||||
load.stub.yields(error);
|
||||
graph(anyEntry, anyPlatform, noOpts, e => {
|
||||
expect(e).toBe(error);
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('resolves any dependencies provided by `load`', done => {
|
||||
const entryPath = '/path/to/entry.js';
|
||||
const id1 = 'required/id';
|
||||
const id2 = './relative/import';
|
||||
resolve.stub.withArgs('entry').yields(null, entryPath);
|
||||
load.stub.withArgs(entryPath)
|
||||
.yields(null, {path: entryPath}, [id1, id2]);
|
||||
|
||||
graph(['entry'], anyPlatform, noOpts, () => {
|
||||
expect(resolve).toBeCalledWith(
|
||||
id1, entryPath, any(String), any(Object), any(Function));
|
||||
expect(resolve).toBeCalledWith(
|
||||
id2, entryPath, any(String), any(Object), any(Function));
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('loads transitive dependencies', done => {
|
||||
const entryPath = '/path/to/entry.js';
|
||||
const id1 = 'required/id';
|
||||
const id2 = './relative/import';
|
||||
const path1 = '/path/to/dep/1';
|
||||
const path2 = '/path/to/dep/2';
|
||||
|
||||
resolve.stub
|
||||
.withArgs(id1).yields(null, path1)
|
||||
.withArgs(id2).yields(null, path2)
|
||||
.withArgs('entry').yields(null, entryPath);
|
||||
load.stub
|
||||
.withArgs(entryPath).yields(null, {path: entryPath}, [id1])
|
||||
.withArgs(path1).yields(null, {path: path1}, [id2]);
|
||||
|
||||
graph(['entry'], anyPlatform, noOpts, () => {
|
||||
expect(resolve).toBeCalledWith(id2, path1, any(String), any(Object), any(Function));
|
||||
expect(load).toBeCalledWith(path1, any(Object), any(Function));
|
||||
expect(load).toBeCalledWith(path2, any(Object), any(Function));
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('calls back with an array of modules in depth-first traversal order, regardless of the order of resolution', done => {
|
||||
load.stub.reset();
|
||||
resolve.stub.reset();
|
||||
|
||||
const ids = [
|
||||
'a',
|
||||
'b',
|
||||
'c', 'd',
|
||||
'e',
|
||||
'f', 'g',
|
||||
'h',
|
||||
];
|
||||
ids.forEach(id => {
|
||||
const path = idToPath(id);
|
||||
resolve.stub.withArgs(id).yields(null, path);
|
||||
load.stub.withArgs(path).yields(null, createFile(id), []);
|
||||
});
|
||||
load.stub.withArgs(idToPath('a')).yields(null, createFile('a'), ['b', 'e', 'h']);
|
||||
load.stub.withArgs(idToPath('b')).yields(null, createFile('b'), ['c', 'd']);
|
||||
load.stub.withArgs(idToPath('e')).yields(null, createFile('e'), ['f', 'g']);
|
||||
|
||||
// load certain ids later
|
||||
['b', 'e', 'h'].forEach(id => resolve.stub.withArgs(id).resetBehavior());
|
||||
resolve.stub.withArgs('h').func = (a, b, c, d, callback) => {
|
||||
callback(null, idToPath('h'));
|
||||
['e', 'b'].forEach(
|
||||
id => resolve.stub.withArgs(id).yield(null, idToPath(id)));
|
||||
};
|
||||
|
||||
graph(['a'], anyPlatform, noOpts, (error, result) => {
|
||||
expect(error).toEqual(null);
|
||||
expect(result.modules).toEqual([
|
||||
createModule('a', ['b', 'e', 'h']),
|
||||
createModule('b', ['c', 'd']),
|
||||
createModule('c'),
|
||||
createModule('d'),
|
||||
createModule('e', ['f', 'g']),
|
||||
createModule('f'),
|
||||
createModule('g'),
|
||||
createModule('h'),
|
||||
]);
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('calls back with the resolved modules of the entry points', done => {
|
||||
load.stub.reset();
|
||||
resolve.stub.reset();
|
||||
|
||||
load.stub.withArgs(idToPath('a')).yields(null, createFile('a'), ['b']);
|
||||
load.stub.withArgs(idToPath('b')).yields(null, createFile('b'), []);
|
||||
load.stub.withArgs(idToPath('c')).yields(null, createFile('c'), ['d']);
|
||||
load.stub.withArgs(idToPath('d')).yields(null, createFile('d'), []);
|
||||
|
||||
'abcd'.split('')
|
||||
.forEach(id => resolve.stub.withArgs(id).yields(null, idToPath(id)));
|
||||
|
||||
graph(['a', 'c'], anyPlatform, noOpts, (error, result) => {
|
||||
expect(result.entryModules).toEqual([
|
||||
createModule('a', ['b']),
|
||||
createModule('c', ['d']),
|
||||
]);
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('calls back with the resolved modules of the entry points if one entry point is a dependency of another', done => {
|
||||
load.stub.reset();
|
||||
resolve.stub.reset();
|
||||
|
||||
load.stub.withArgs(idToPath('a')).yields(null, createFile('a'), ['b']);
|
||||
load.stub.withArgs(idToPath('b')).yields(null, createFile('b'), []);
|
||||
|
||||
'ab'.split('')
|
||||
.forEach(id => resolve.stub.withArgs(id).yields(null, idToPath(id)));
|
||||
|
||||
graph(['a', 'b'], anyPlatform, noOpts, (error, result) => {
|
||||
expect(result.entryModules).toEqual([
|
||||
createModule('a', ['b']),
|
||||
createModule('b', []),
|
||||
]);
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('does not include dependencies more than once', done => {
|
||||
const ids = ['a', 'b', 'c', 'd'];
|
||||
ids.forEach(id => {
|
||||
const path = idToPath(id);
|
||||
resolve.stub.withArgs(id).yields(null, path);
|
||||
load.stub.withArgs(path).yields(null, createFile(id), []);
|
||||
});
|
||||
['a', 'd'].forEach(id =>
|
||||
load.stub
|
||||
.withArgs(idToPath(id)).yields(null, createFile(id), ['b', 'c']));
|
||||
|
||||
graph(['a', 'd', 'b'], anyPlatform, noOpts, (error, result) => {
|
||||
expect(error).toEqual(null);
|
||||
expect(result.modules).toEqual([
|
||||
createModule('a', ['b', 'c']),
|
||||
createModule('b'),
|
||||
createModule('c'),
|
||||
createModule('d', ['b', 'c']),
|
||||
]);
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('handles dependency cycles', done => {
|
||||
resolve.stub
|
||||
.withArgs('a').yields(null, idToPath('a'))
|
||||
.withArgs('b').yields(null, idToPath('b'))
|
||||
.withArgs('c').yields(null, idToPath('c'));
|
||||
load.stub
|
||||
.withArgs(idToPath('a')).yields(null, createFile('a'), ['b'])
|
||||
.withArgs(idToPath('b')).yields(null, createFile('b'), ['c'])
|
||||
.withArgs(idToPath('c')).yields(null, createFile('c'), ['a']);
|
||||
|
||||
graph(['a'], anyPlatform, noOpts, (error, result) => {
|
||||
expect(result.modules).toEqual([
|
||||
createModule('a', ['b']),
|
||||
createModule('b', ['c']),
|
||||
createModule('c', ['a']),
|
||||
]);
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('can skip files', done => {
|
||||
['a', 'b', 'c', 'd', 'e'].forEach(
|
||||
id => resolve.stub.withArgs(id).yields(null, idToPath(id)));
|
||||
load.stub
|
||||
.withArgs(idToPath('a')).yields(null, createFile('a'), ['b', 'c', 'd'])
|
||||
.withArgs(idToPath('b')).yields(null, createFile('b'), ['e']);
|
||||
['c', 'd', 'e'].forEach(id =>
|
||||
load.stub.withArgs(idToPath(id)).yields(null, createFile(id), []));
|
||||
const skip = new Set([idToPath('b'), idToPath('c')]);
|
||||
|
||||
graph(['a'], anyPlatform, {skip}, (error, result) => {
|
||||
expect(result.modules).toEqual([
|
||||
createModule('a', ['b', 'c', 'd']),
|
||||
createModule('d', []),
|
||||
]);
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
function createDependency(id) {
|
||||
return {id, path: idToPath(id)};
|
||||
}
|
||||
|
||||
function createFile(id) {
|
||||
return {ast: {}, path: idToPath(id)};
|
||||
}
|
||||
|
||||
function createModule(id, dependencies = []): Module {
|
||||
return {
|
||||
file: createFile(id),
|
||||
dependencies: dependencies.map(createDependency)
|
||||
};
|
||||
}
|
||||
|
||||
function idToPath(id) {
|
||||
return '/path/to/' + id;
|
||||
}
|
||||
119
packager/src/ModuleGraph/__tests__/ModuleGraph-test.js
Normal file
119
packager/src/ModuleGraph/__tests__/ModuleGraph-test.js
Normal file
@@ -0,0 +1,119 @@
|
||||
/**
|
||||
* 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 ModuleGraph = require('../ModuleGraph');
|
||||
const defaults = require('../../../defaults');
|
||||
|
||||
const FILE_TYPE = 'module';
|
||||
|
||||
describe('build setup', () => {
|
||||
const buildSetup = ModuleGraph.createBuildSetup(graph);
|
||||
const noOptions = {};
|
||||
const noEntryPoints = [];
|
||||
|
||||
it('adds a prelude containing start time and `__DEV__` to the build', done => {
|
||||
buildSetup(noEntryPoints, noOptions, (error, result) => {
|
||||
expect(error).toEqual(null);
|
||||
|
||||
const [prelude] = result.modules;
|
||||
expect(prelude).toEqual({
|
||||
dependencies: [],
|
||||
file: {
|
||||
code: 'var __DEV__=true,__BUNDLE_START_TIME__=Date.now();',
|
||||
path: '',
|
||||
type: 'script',
|
||||
},
|
||||
});
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('sets `__DEV__` to false in the prelude if optimization is enabled', done => {
|
||||
buildSetup(noEntryPoints, {optimize: true}, (error, result) => {
|
||||
const [prelude] = result.modules;
|
||||
expect(prelude.file.code)
|
||||
.toEqual('var __DEV__=false,__BUNDLE_START_TIME__=Date.now();');
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('places the module system implementation directly after the prelude', done => {
|
||||
buildSetup(noEntryPoints, noOptions, (error, result) => {
|
||||
const [, moduleSystem] = result.modules;
|
||||
expect(moduleSystem).toEqual({
|
||||
dependencies: [],
|
||||
file: {
|
||||
code: '',
|
||||
path: defaults.moduleSystem,
|
||||
type: FILE_TYPE,
|
||||
},
|
||||
});
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('places polyfills after the module system', done => {
|
||||
buildSetup(noEntryPoints, noOptions, (error, result) => {
|
||||
const polyfills =
|
||||
Array.from(result.modules).slice(2, 2 + defaults.polyfills.length);
|
||||
expect(polyfills).toEqual(defaults.polyfills.map(moduleFromPath));
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('places all modules from `defaults.runBeforeMainModule` after the polyfills', done => {
|
||||
buildSetup(noEntryPoints, noOptions, (error, result) => {
|
||||
const additionalModules =
|
||||
Array.from(result.modules).slice(-defaults.runBeforeMainModule.length);
|
||||
expect(additionalModules)
|
||||
.toEqual(defaults.runBeforeMainModule.map(moduleFromPath));
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('places all entry points at the end', done => {
|
||||
const entryPoints = ['a', 'b', 'c'];
|
||||
buildSetup(entryPoints, noOptions, (error, result) => {
|
||||
expect(Array.from(result.modules).slice(-3))
|
||||
.toEqual(entryPoints.map(moduleFromPath));
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('concatenates `runBeforeMainModule` and entry points as `entryModules`', done => {
|
||||
const entryPoints = ['a', 'b', 'c'];
|
||||
buildSetup(entryPoints, noOptions, (error, result) => {
|
||||
expect(Array.from(result.entryModules)).toEqual(
|
||||
defaults.runBeforeMainModule.concat(entryPoints).map(moduleFromPath));
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
function moduleFromPath(path) {
|
||||
return {
|
||||
dependencies: [],
|
||||
file: {
|
||||
code: '',
|
||||
path,
|
||||
type: FILE_TYPE,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
function graph(entryPoints, platform, options, callback) {
|
||||
const modules = Array.from(entryPoints, moduleFromPath);
|
||||
callback(null, {
|
||||
entryModules: modules,
|
||||
modules,
|
||||
});
|
||||
}
|
||||
80
packager/src/ModuleGraph/node-haste/HasteFS.js
Normal file
80
packager/src/ModuleGraph/node-haste/HasteFS.js
Normal file
@@ -0,0 +1,80 @@
|
||||
/**
|
||||
* 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.
|
||||
*
|
||||
* @flow
|
||||
*/
|
||||
|
||||
'use strict';
|
||||
|
||||
const {dirname, join, parse} = require('path');
|
||||
|
||||
module.exports = class HasteFS {
|
||||
directories: Set<string>;
|
||||
directoryEntries: Map<string, Array<string>>;
|
||||
files: Set<string>;
|
||||
|
||||
constructor(files: Array<string>) {
|
||||
this.directories = buildDirectorySet(files);
|
||||
this.directoryEntries = buildDirectoryEntries(files.map(parse));
|
||||
this.files = new Set(files);
|
||||
}
|
||||
|
||||
closest(path: string, fileName: string): ?string {
|
||||
let {dir, root} = parse(path);
|
||||
do {
|
||||
const candidate = join(dir, fileName);
|
||||
if (this.files.has(candidate)) {
|
||||
return candidate;
|
||||
}
|
||||
dir = dirname(dir);
|
||||
} while (dir !== '.' && dir !== root);
|
||||
return null;
|
||||
}
|
||||
|
||||
dirExists(path: string) {
|
||||
return this.directories.has(path);
|
||||
}
|
||||
|
||||
exists(path: string) {
|
||||
return this.files.has(path);
|
||||
}
|
||||
|
||||
getAllFiles() {
|
||||
return Array.from(this.files.keys());
|
||||
}
|
||||
|
||||
matches(directory: string, pattern: RegExp) {
|
||||
const entries = this.directoryEntries.get(directory);
|
||||
return entries ? entries.filter(pattern.test, pattern) : [];
|
||||
}
|
||||
};
|
||||
|
||||
function buildDirectorySet(files) {
|
||||
const directories = new Set();
|
||||
files.forEach(path => {
|
||||
let {dir, root} = parse(path);
|
||||
while (dir !== '.' && dir !== root && !directories.has(dir)) {
|
||||
directories.add(dir);
|
||||
dir = dirname(dir);
|
||||
}
|
||||
});
|
||||
return directories;
|
||||
}
|
||||
|
||||
function buildDirectoryEntries(files) {
|
||||
const directoryEntries = new Map();
|
||||
files.forEach(({base, dir}) => {
|
||||
const entries = directoryEntries.get(dir);
|
||||
if (entries) {
|
||||
entries.push(base);
|
||||
} else {
|
||||
directoryEntries.set(dir, [base]);
|
||||
}
|
||||
});
|
||||
return directoryEntries;
|
||||
}
|
||||
51
packager/src/ModuleGraph/node-haste/Module.js
Normal file
51
packager/src/ModuleGraph/node-haste/Module.js
Normal file
@@ -0,0 +1,51 @@
|
||||
/**
|
||||
* 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.
|
||||
*
|
||||
* @flow
|
||||
*/
|
||||
|
||||
'use strict';
|
||||
|
||||
import type {TransformedFile} from '../types.flow';
|
||||
import type {ModuleCache} from './node-haste.flow';
|
||||
|
||||
module.exports = class Module {
|
||||
hasteID: Promise<?string>;
|
||||
moduleCache: ModuleCache;
|
||||
name: Promise<string>;
|
||||
path: string;
|
||||
type: 'Module';
|
||||
|
||||
constructor(
|
||||
path: string,
|
||||
moduleCache: ModuleCache,
|
||||
info: Promise<TransformedFile>,
|
||||
) {
|
||||
this.hasteID = info.then(({hasteID}) => hasteID);
|
||||
this.moduleCache = moduleCache;
|
||||
this.name = this.hasteID.then(name => name || getName(path));
|
||||
this.path = path;
|
||||
this.type = 'Module';
|
||||
}
|
||||
|
||||
getName() {
|
||||
return this.name;
|
||||
}
|
||||
|
||||
getPackage() {
|
||||
return this.moduleCache.getPackageOf(this.path);
|
||||
}
|
||||
|
||||
isHaste() {
|
||||
return this.hasteID.then(Boolean);
|
||||
}
|
||||
};
|
||||
|
||||
function getName(path) {
|
||||
return path.replace(/^.*[\/\\]node_modules[\///]/, '');
|
||||
}
|
||||
65
packager/src/ModuleGraph/node-haste/ModuleCache.js
Normal file
65
packager/src/ModuleGraph/node-haste/ModuleCache.js
Normal file
@@ -0,0 +1,65 @@
|
||||
/**
|
||||
* 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.
|
||||
*
|
||||
* @flow
|
||||
*/
|
||||
|
||||
'use strict';
|
||||
|
||||
const Module = require('./Module');
|
||||
const Package = require('./Package');
|
||||
|
||||
import type {PackageData, TransformedFile} from '../types.flow';
|
||||
|
||||
type GetFn<T> = (path: string) => Promise<T>;
|
||||
type GetClosestPackageFn = (filePath: string) => ?string;
|
||||
|
||||
module.exports = class ModuleCache {
|
||||
_getClosestPackage: GetClosestPackageFn;
|
||||
getPackageData: GetFn<PackageData>;
|
||||
getTransformedFile: GetFn<TransformedFile>;
|
||||
modules: Map<string, Module>;
|
||||
packages: Map<string, Package>;
|
||||
|
||||
constructor(getClosestPackage: GetClosestPackageFn, getTransformedFile: GetFn<TransformedFile>) {
|
||||
this._getClosestPackage = getClosestPackage;
|
||||
this.getTransformedFile = getTransformedFile;
|
||||
this.getPackageData = path => getTransformedFile(path).then(
|
||||
f => f.package || Promise.reject(new Error(`"${path}" does not exist`))
|
||||
);
|
||||
this.modules = new Map();
|
||||
this.packages = new Map();
|
||||
}
|
||||
|
||||
getAssetModule(path: string) {
|
||||
return this.getModule(path);
|
||||
}
|
||||
|
||||
getModule(path: string) {
|
||||
let m = this.modules.get(path);
|
||||
if (!m) {
|
||||
m = new Module(path, this, this.getTransformedFile(path));
|
||||
this.modules.set(path, m);
|
||||
}
|
||||
return m;
|
||||
}
|
||||
|
||||
getPackage(path: string) {
|
||||
let p = this.packages.get(path);
|
||||
if (!p) {
|
||||
p = new Package(path, this.getPackageData(path));
|
||||
this.packages.set(path, p);
|
||||
}
|
||||
return p;
|
||||
}
|
||||
|
||||
getPackageOf(filePath: string) {
|
||||
const candidate = this._getClosestPackage(filePath);
|
||||
return candidate != null ? this.getPackage(candidate) : null;
|
||||
}
|
||||
};
|
||||
138
packager/src/ModuleGraph/node-haste/Package.js
Normal file
138
packager/src/ModuleGraph/node-haste/Package.js
Normal file
@@ -0,0 +1,138 @@
|
||||
/**
|
||||
* 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.
|
||||
*
|
||||
* @flow
|
||||
*/
|
||||
|
||||
'use strict';
|
||||
|
||||
const path = require('path');
|
||||
|
||||
import type {PackageData} from '../types.flow';
|
||||
|
||||
module.exports = class Package {
|
||||
data: Promise<PackageData>;
|
||||
path: string;
|
||||
root: string;
|
||||
type: 'Package';
|
||||
|
||||
constructor(packagePath: string, data: Promise<PackageData>) {
|
||||
this.data = data;
|
||||
this.path = packagePath;
|
||||
this.root = path.dirname(packagePath);
|
||||
this.type = 'Package';
|
||||
}
|
||||
|
||||
getMain() {
|
||||
// Copied from node-haste/Package.js
|
||||
return this.data.then(data => {
|
||||
const replacements = getReplacements(data);
|
||||
if (typeof replacements === 'string') {
|
||||
return path.join(this.root, replacements);
|
||||
}
|
||||
|
||||
let main = getMain(data);
|
||||
|
||||
if (replacements && typeof replacements === 'object') {
|
||||
main = replacements[main] ||
|
||||
replacements[main + '.js'] ||
|
||||
replacements[main + '.json'] ||
|
||||
replacements[main.replace(/(\.js|\.json)$/, '')] ||
|
||||
main;
|
||||
}
|
||||
|
||||
return path.join(this.root, main);
|
||||
});
|
||||
}
|
||||
|
||||
getName() {
|
||||
return this.data.then(p => p.name);
|
||||
}
|
||||
|
||||
isHaste() {
|
||||
return this.data.then(p => !!p.name);
|
||||
}
|
||||
|
||||
redirectRequire(name: string) {
|
||||
// Copied from node-haste/Package.js
|
||||
return this.data.then(data => {
|
||||
const replacements = getReplacements(data);
|
||||
|
||||
if (!replacements || typeof replacements !== 'object') {
|
||||
return name;
|
||||
}
|
||||
|
||||
if (!path.isAbsolute(name)) {
|
||||
const replacement = replacements[name];
|
||||
// support exclude with "someDependency": false
|
||||
return replacement === false
|
||||
? false
|
||||
: replacement || name;
|
||||
}
|
||||
|
||||
let relPath = './' + path.relative(this.root, name);
|
||||
if (path.sep !== '/') {
|
||||
relPath = relPath.replace(new RegExp('\\' + path.sep, 'g'), '/');
|
||||
}
|
||||
|
||||
let redirect = replacements[relPath];
|
||||
|
||||
// false is a valid value
|
||||
if (redirect == null) {
|
||||
redirect = replacements[relPath + '.js'];
|
||||
if (redirect == null) {
|
||||
redirect = replacements[relPath + '.json'];
|
||||
}
|
||||
}
|
||||
|
||||
// support exclude with "./someFile": false
|
||||
if (redirect === false) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (redirect) {
|
||||
return path.join(
|
||||
this.root,
|
||||
redirect
|
||||
);
|
||||
}
|
||||
|
||||
return name;
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
function getMain(pkg) {
|
||||
return pkg.main || 'index';
|
||||
}
|
||||
|
||||
// Copied from node-haste/Package.js
|
||||
function getReplacements(pkg) {
|
||||
let rn = pkg['react-native'];
|
||||
let browser = pkg.browser;
|
||||
if (rn == null) {
|
||||
return browser;
|
||||
}
|
||||
|
||||
if (browser == null) {
|
||||
return rn;
|
||||
}
|
||||
|
||||
const main = getMain(pkg);
|
||||
if (typeof rn !== 'object') {
|
||||
rn = { [main]: rn };
|
||||
}
|
||||
|
||||
if (typeof browser !== 'object') {
|
||||
browser = { [main]: browser };
|
||||
}
|
||||
|
||||
// merge with "browser" as default,
|
||||
// "react-native" as override
|
||||
return { ...browser, ...rn };
|
||||
}
|
||||
71
packager/src/ModuleGraph/node-haste/node-haste.flow.js
Normal file
71
packager/src/ModuleGraph/node-haste/node-haste.flow.js
Normal file
@@ -0,0 +1,71 @@
|
||||
/**
|
||||
* 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.
|
||||
*
|
||||
* @flow
|
||||
*/
|
||||
'use strict';
|
||||
|
||||
'use strict';
|
||||
|
||||
import DependencyGraphHelpers from '../../node-haste/DependencyGraph/DependencyGraphHelpers';
|
||||
|
||||
type ModuleID = string;
|
||||
export type Path = string;
|
||||
type Platform = string;
|
||||
type Platforms = Set<Platform>;
|
||||
|
||||
export type Extensions = Array<string>;
|
||||
|
||||
export type Module = {
|
||||
path: Path,
|
||||
type: 'Module',
|
||||
getName(): Promise<ModuleID>,
|
||||
getPackage(): ?Package,
|
||||
isHaste(): Promise<boolean>,
|
||||
};
|
||||
|
||||
export type Package = {
|
||||
path: Path,
|
||||
root: Path,
|
||||
type: 'Package',
|
||||
getMain(): Promise<Path>,
|
||||
getName(): Promise<ModuleID>,
|
||||
isHaste(): Promise<boolean>,
|
||||
redirectRequire(id: ModuleID): Promise<Path | false>,
|
||||
};
|
||||
|
||||
// when changing this to `type`, the code does not typecheck any more
|
||||
export interface ModuleCache {
|
||||
getAssetModule(path: Path): Module,
|
||||
getModule(path: Path): Module,
|
||||
getPackage(path: Path): Package,
|
||||
getPackageOf(path: Path): ?Package,
|
||||
}
|
||||
|
||||
export type FastFS = {
|
||||
dirExists(path: Path): boolean,
|
||||
closest(path: string, fileName: string): ?string,
|
||||
fileExists(path: Path): boolean,
|
||||
getAllFiles(): Array<Path>,
|
||||
matches(directory: Path, pattern: RegExp): Array<Path>,
|
||||
};
|
||||
|
||||
type HasteMapOptions = {|
|
||||
extensions: Extensions,
|
||||
files: Array<string>,
|
||||
helpers: DependencyGraphHelpers,
|
||||
moduleCache: ModuleCache,
|
||||
platforms: Platforms,
|
||||
preferNativePlatform: true,
|
||||
|};
|
||||
|
||||
declare class HasteMap {
|
||||
// node-haste/DependencyGraph/HasteMap.js
|
||||
build(): Promise<Object>,
|
||||
constructor(options: HasteMapOptions): void,
|
||||
}
|
||||
101
packager/src/ModuleGraph/node-haste/node-haste.js
Normal file
101
packager/src/ModuleGraph/node-haste/node-haste.js
Normal file
@@ -0,0 +1,101 @@
|
||||
/**
|
||||
* 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.
|
||||
*
|
||||
* @flow
|
||||
*/
|
||||
|
||||
'use strict';
|
||||
|
||||
import type { // eslint-disable-line sort-requires
|
||||
Extensions,
|
||||
Path,
|
||||
} from './node-haste.flow';
|
||||
|
||||
import type {
|
||||
ResolveFn,
|
||||
TransformedFile,
|
||||
} from '../types.flow';
|
||||
|
||||
const DependencyGraphHelpers = require('../../node-haste/DependencyGraph/DependencyGraphHelpers');
|
||||
const HasteFS = require('./HasteFS');
|
||||
const HasteMap = require('../../node-haste/DependencyGraph/HasteMap');
|
||||
const Module = require('./Module');
|
||||
const ModuleCache = require('./ModuleCache');
|
||||
const ResolutionRequest = require('../../node-haste/DependencyGraph/ResolutionRequest');
|
||||
|
||||
const defaults = require('../../../defaults');
|
||||
|
||||
type ResolveOptions = {|
|
||||
assetExts: Extensions,
|
||||
extraNodeModules: {[id: string]: string},
|
||||
transformedFiles: {[path: Path]: TransformedFile},
|
||||
|};
|
||||
|
||||
const platforms = new Set(defaults.platforms);
|
||||
|
||||
exports.createResolveFn = function(options: ResolveOptions): ResolveFn {
|
||||
const {
|
||||
assetExts,
|
||||
extraNodeModules,
|
||||
transformedFiles,
|
||||
} = options;
|
||||
const files = Object.keys(transformedFiles);
|
||||
const getTransformedFile =
|
||||
path => Promise.resolve(
|
||||
transformedFiles[path] || Promise.reject(new Error(`"${path} does not exist`))
|
||||
);
|
||||
|
||||
const helpers = new DependencyGraphHelpers({
|
||||
assetExts,
|
||||
providesModuleNodeModules: defaults.providesModuleNodeModules,
|
||||
});
|
||||
|
||||
const hasteFS = new HasteFS(files);
|
||||
const moduleCache = new ModuleCache(
|
||||
filePath => hasteFS.closest(filePath, 'package.json'),
|
||||
getTransformedFile,
|
||||
);
|
||||
const hasteMap = new HasteMap({
|
||||
extensions: ['js', 'json'],
|
||||
files,
|
||||
helpers,
|
||||
moduleCache,
|
||||
platforms,
|
||||
preferNativePlatform: true,
|
||||
});
|
||||
|
||||
const hasteMapBuilt = hasteMap.build();
|
||||
const resolutionRequests = {};
|
||||
return (id, source, platform, _, callback) => {
|
||||
let resolutionRequest = resolutionRequests[platform];
|
||||
if (!resolutionRequest) {
|
||||
resolutionRequest = resolutionRequests[platform] = new ResolutionRequest({
|
||||
dirExists: filePath => hasteFS.dirExists(filePath),
|
||||
entryPath: '',
|
||||
extraNodeModules,
|
||||
/* $FlowFixMe: object is missing matchFiles method */
|
||||
hasteFS,
|
||||
hasteMap,
|
||||
helpers,
|
||||
moduleCache,
|
||||
platform,
|
||||
platforms,
|
||||
preferNativePlatform: true,
|
||||
});
|
||||
}
|
||||
|
||||
const from = new Module(source, moduleCache, getTransformedFile(source));
|
||||
hasteMapBuilt
|
||||
.then(() => resolutionRequest.resolveDependency(from, id))
|
||||
.then(
|
||||
// nextTick to escape promise error handling
|
||||
module => process.nextTick(callback, null, module.path),
|
||||
error => process.nextTick(callback, error),
|
||||
);
|
||||
};
|
||||
};
|
||||
1
packager/src/ModuleGraph/node-haste/package.json
Normal file
1
packager/src/ModuleGraph/node-haste/package.json
Normal file
@@ -0,0 +1 @@
|
||||
{"main":"node-haste.js"}
|
||||
83
packager/src/ModuleGraph/output/__tests__/util-test.js
Normal file
83
packager/src/ModuleGraph/output/__tests__/util-test.js
Normal file
@@ -0,0 +1,83 @@
|
||||
/**
|
||||
* 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 {match} = require('sinon');
|
||||
const {fn} = require('../../test-helpers');
|
||||
const {
|
||||
addModuleIdsToModuleWrapper,
|
||||
createIdForPathFn,
|
||||
} = require('../util');
|
||||
|
||||
const {any} = jasmine;
|
||||
|
||||
describe('`addModuleIdsToModuleWrapper`:', () => {
|
||||
const path = 'path/to/file';
|
||||
const createModule = (dependencies = []) => ({
|
||||
dependencies,
|
||||
file: {code: '__d(function(){});', isModule: true, path},
|
||||
});
|
||||
|
||||
it('completes the module wrapped with module ID, and an array of dependency IDs', () => {
|
||||
const dependencies = [
|
||||
{id: 'a', path: 'path/to/a.js'},
|
||||
{id: 'b', path: 'location/of/b.js'},
|
||||
];
|
||||
const module = createModule(dependencies);
|
||||
|
||||
const idForPath = fn();
|
||||
idForPath.stub
|
||||
.withArgs(match({path})).returns(12)
|
||||
.withArgs(match({path: dependencies[0].path})).returns(345)
|
||||
.withArgs(match({path: dependencies[1].path})).returns(6);
|
||||
|
||||
expect(addModuleIdsToModuleWrapper(module, idForPath))
|
||||
.toEqual('__d(function(){},12,[345,6]);');
|
||||
});
|
||||
|
||||
it('omits the array of dependency IDs if it is empty', () => {
|
||||
const module = createModule();
|
||||
expect(addModuleIdsToModuleWrapper(module, () => 98))
|
||||
.toEqual(`__d(function(){},${98});`);
|
||||
});
|
||||
});
|
||||
|
||||
describe('`createIdForPathFn`', () => {
|
||||
let idForPath;
|
||||
beforeEach(() => {
|
||||
idForPath = createIdForPathFn();
|
||||
});
|
||||
|
||||
it('returns a number for a string', () => {
|
||||
expect(idForPath({path: 'arbitrary'})).toEqual(any(Number));
|
||||
});
|
||||
|
||||
it('returns consecutive numbers', () => {
|
||||
const strings = [
|
||||
'arbitrary string',
|
||||
'looking/like/a/path',
|
||||
'/absolute/path/to/file.js',
|
||||
'/more files/are here',
|
||||
];
|
||||
|
||||
strings.forEach((string, i) => {
|
||||
expect(idForPath({path: string})).toEqual(i);
|
||||
});
|
||||
});
|
||||
|
||||
it('returns the same id if the same string is passed in again', () => {
|
||||
const path = 'this/is/an/arbitrary/path.js';
|
||||
const id = idForPath({path});
|
||||
idForPath({path: '/other/file'});
|
||||
idForPath({path: 'and/another/file'});
|
||||
expect(idForPath({path})).toEqual(id);
|
||||
});
|
||||
});
|
||||
47
packager/src/ModuleGraph/output/as-plain-bundle.js
Normal file
47
packager/src/ModuleGraph/output/as-plain-bundle.js
Normal file
@@ -0,0 +1,47 @@
|
||||
/**
|
||||
* 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 {createIndexMap} = require('./source-map');
|
||||
const {addModuleIdsToModuleWrapper} = require('./util');
|
||||
|
||||
import type {OutputFn} from '../types.flow';
|
||||
|
||||
module.exports = (
|
||||
(modules, filename, idForPath) => {
|
||||
let code = '';
|
||||
let line = 0;
|
||||
const sections = [];
|
||||
|
||||
for (const module of modules) {
|
||||
const {file} = module;
|
||||
const moduleCode = file.type === 'module'
|
||||
? addModuleIdsToModuleWrapper(module, idForPath)
|
||||
: file.code;
|
||||
|
||||
code += moduleCode + '\n';
|
||||
if (file.map) {
|
||||
sections.push({
|
||||
map: file.map,
|
||||
offset: {column: 0, line}
|
||||
});
|
||||
}
|
||||
line += countLines(moduleCode);
|
||||
}
|
||||
|
||||
return {code, map: createIndexMap({file: filename, sections})};
|
||||
}: OutputFn);
|
||||
|
||||
const reLine = /^/gm;
|
||||
function countLines(string: string): number {
|
||||
//$FlowFixMe This regular expression always matches
|
||||
return string.match(reLine).length;
|
||||
}
|
||||
48
packager/src/ModuleGraph/output/source-map.js
Normal file
48
packager/src/ModuleGraph/output/source-map.js
Normal file
@@ -0,0 +1,48 @@
|
||||
/**
|
||||
* 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';
|
||||
|
||||
type CreateIndexMapOptions = {|
|
||||
file?: string,
|
||||
sections?: Array<IndexMapSection>
|
||||
|};
|
||||
|
||||
type IndexMap = MapBase & {
|
||||
sections: Array<IndexMapSection>,
|
||||
};
|
||||
|
||||
type IndexMapSection = {
|
||||
map: IndexMap | MappingsMap,
|
||||
offset: {line: number, column: number},
|
||||
};
|
||||
|
||||
type MapBase = {
|
||||
// always the first entry in the source map entry object per
|
||||
// https://fburl.com/source-map-spec#heading=h.qz3o9nc69um5
|
||||
version: 3,
|
||||
file?: string,
|
||||
};
|
||||
|
||||
type MappingsMap = MapBase & {
|
||||
mappings: string,
|
||||
names: Array<string>,
|
||||
sourceRoot?: string,
|
||||
sources: Array<string>,
|
||||
sourcesContent?: Array<?string>,
|
||||
};
|
||||
|
||||
export type SourceMap = IndexMap | MappingsMap;
|
||||
|
||||
exports.createIndexMap = (opts?: CreateIndexMapOptions): IndexMap => ({
|
||||
version: 3,
|
||||
file: opts && opts.file,
|
||||
sections: opts && opts.sections || [],
|
||||
});
|
||||
85
packager/src/ModuleGraph/output/util.js
Normal file
85
packager/src/ModuleGraph/output/util.js
Normal file
@@ -0,0 +1,85 @@
|
||||
/**
|
||||
* 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';
|
||||
|
||||
import type {IdForPathFn, Module} from '../types.flow';
|
||||
|
||||
// Transformed modules have the form
|
||||
// __d(function(require, module, global, exports, dependencyMap) {
|
||||
// /* code */
|
||||
// });
|
||||
//
|
||||
// This function adds the numeric module ID, and an array with dependencies of
|
||||
// the dependencies of the module before the closing parenthesis.
|
||||
exports.addModuleIdsToModuleWrapper = (
|
||||
module: Module,
|
||||
idForPath: {path: string} => number,
|
||||
): string => {
|
||||
const {dependencies, file} = module;
|
||||
const {code} = file;
|
||||
const index = code.lastIndexOf(')');
|
||||
|
||||
// calling `idForPath` on the module itself first gives us a lower module id
|
||||
// for the file itself than for its dependencies. That reflects their order
|
||||
// in the bundle.
|
||||
const fileId = idForPath(file);
|
||||
|
||||
// This code runs for both development and production builds, after
|
||||
// minification. That's why we leave out all spaces.
|
||||
const depencyIds =
|
||||
dependencies.length ? `,[${dependencies.map(idForPath).join(',')}]` : '';
|
||||
return (
|
||||
code.slice(0, index) +
|
||||
`,${fileId}` +
|
||||
depencyIds +
|
||||
code.slice(index)
|
||||
);
|
||||
};
|
||||
|
||||
// Creates an idempotent function that returns numeric IDs for objects based
|
||||
// on their `path` property.
|
||||
exports.createIdForPathFn = (): ({path: string} => number) => {
|
||||
const seen = new Map();
|
||||
let next = 0;
|
||||
return ({path}) => {
|
||||
let id = seen.get(path);
|
||||
if (id == null) {
|
||||
id = next++;
|
||||
seen.set(path, id);
|
||||
}
|
||||
return id;
|
||||
};
|
||||
};
|
||||
|
||||
// creates a series of virtual modules with require calls to the passed-in
|
||||
// modules.
|
||||
exports.requireCallsTo = function* (
|
||||
modules: Iterable<Module>,
|
||||
idForPath: IdForPathFn,
|
||||
): Iterable<Module> {
|
||||
for (const module of modules) {
|
||||
yield virtualModule(`require(${idForPath(module.file)});`);
|
||||
}
|
||||
};
|
||||
|
||||
// creates a virtual module (i.e. not corresponding to a file on disk)
|
||||
// with the given source code.
|
||||
exports.virtualModule = virtualModule;
|
||||
function virtualModule(code: string) {
|
||||
return {
|
||||
dependencies: [],
|
||||
file: {
|
||||
code,
|
||||
path: '',
|
||||
type: 'script',
|
||||
}
|
||||
};
|
||||
}
|
||||
1
packager/src/ModuleGraph/package.json
Normal file
1
packager/src/ModuleGraph/package.json
Normal file
@@ -0,0 +1 @@
|
||||
{"main": "ModuleGraph.js"}
|
||||
15
packager/src/ModuleGraph/silent-console.js
Normal file
15
packager/src/ModuleGraph/silent-console.js
Normal file
@@ -0,0 +1,15 @@
|
||||
/**
|
||||
* 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 {Console} = require('console');
|
||||
const {Writable} = require('stream');
|
||||
|
||||
const write = (_, __, callback) => callback();
|
||||
module.exports = new Console(new Writable({write, writev: write}));
|
||||
23
packager/src/ModuleGraph/test-helpers.js
Normal file
23
packager/src/ModuleGraph/test-helpers.js
Normal file
@@ -0,0 +1,23 @@
|
||||
/**
|
||||
* 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 generate = require('babel-generator').default;
|
||||
const stub = require('sinon/lib/sinon/stub');
|
||||
|
||||
exports.fn = () => {
|
||||
const s = stub();
|
||||
const f = jest.fn(s);
|
||||
f.stub = s;
|
||||
return f;
|
||||
};
|
||||
|
||||
const generateOptions = {concise: true};
|
||||
exports.codeFromAst = ast => generate(ast, generateOptions).code;
|
||||
exports.comparableCode = code => code.trim().replace(/\s\s+/g, ' ');
|
||||
134
packager/src/ModuleGraph/types.flow.js
Normal file
134
packager/src/ModuleGraph/types.flow.js
Normal file
@@ -0,0 +1,134 @@
|
||||
/**
|
||||
* 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';
|
||||
|
||||
import type {SourceMap} from './output/source-map';
|
||||
import type {Console} from 'console';
|
||||
|
||||
export type Callback<A = void, B = void>
|
||||
= (Error => void)
|
||||
& ((null | void, A, B) => void);
|
||||
|
||||
type Dependency = {|
|
||||
id: string,
|
||||
path: string,
|
||||
|};
|
||||
|
||||
export type File = {|
|
||||
code: string,
|
||||
map?: ?Object,
|
||||
path: string,
|
||||
type: FileTypes,
|
||||
|};
|
||||
|
||||
type FileTypes = 'module' | 'script';
|
||||
|
||||
export type GraphFn = (
|
||||
entryPoints: Iterable<string>,
|
||||
platform: string,
|
||||
options?: ?GraphOptions,
|
||||
callback?: Callback<GraphResult>,
|
||||
) => void;
|
||||
|
||||
type GraphOptions = {|
|
||||
cwd?: string,
|
||||
log?: Console,
|
||||
optimize?: boolean,
|
||||
skip?: Set<string>,
|
||||
|};
|
||||
|
||||
export type GraphResult = {
|
||||
entryModules: Array<Module>,
|
||||
modules: Array<Module>,
|
||||
};
|
||||
|
||||
export type IdForPathFn = {path: string} => number;
|
||||
|
||||
export type LoadFn = (
|
||||
file: string,
|
||||
options: LoadOptions,
|
||||
callback: Callback<File, Array<string>>,
|
||||
) => void;
|
||||
|
||||
type LoadOptions = {|
|
||||
log?: Console,
|
||||
optimize?: boolean,
|
||||
platform?: string,
|
||||
|};
|
||||
|
||||
export type Module = {|
|
||||
dependencies: Array<Dependency>,
|
||||
file: File,
|
||||
|};
|
||||
|
||||
export type OutputFn = (
|
||||
modules: Iterable<Module>,
|
||||
filename?: string,
|
||||
idForPath: IdForPathFn,
|
||||
) => OutputResult;
|
||||
|
||||
type OutputResult = {
|
||||
code: string,
|
||||
map: SourceMap,
|
||||
};
|
||||
|
||||
export type PackageData = {|
|
||||
browser?: Object | string,
|
||||
main?: string,
|
||||
name?: string,
|
||||
'react-native'?: Object | string,
|
||||
|};
|
||||
|
||||
export type ResolveFn = (
|
||||
id: string,
|
||||
source: string,
|
||||
platform: string,
|
||||
options?: ResolveOptions,
|
||||
callback: Callback<string>,
|
||||
) => void;
|
||||
|
||||
type ResolveOptions = {
|
||||
log?: Console,
|
||||
};
|
||||
|
||||
export type TransformFn = (
|
||||
data: {|
|
||||
filename: string,
|
||||
options?: Object,
|
||||
plugins?: Array<string | Object | [string | Object, any]>,
|
||||
sourceCode: string,
|
||||
|},
|
||||
callback: Callback<TransformFnResult>
|
||||
) => void;
|
||||
|
||||
export type TransformFnResult = {
|
||||
ast: Object,
|
||||
};
|
||||
|
||||
export type TransformResult = {|
|
||||
code: string,
|
||||
dependencies: Array<string>,
|
||||
dependencyMapName?: string,
|
||||
map: ?Object,
|
||||
|};
|
||||
|
||||
export type TransformResults = {[string]: TransformResult};
|
||||
|
||||
export type TransformVariants = {[key: string]: Object};
|
||||
|
||||
export type TransformedFile = {
|
||||
code: string,
|
||||
file: string,
|
||||
hasteID: ?string,
|
||||
package?: PackageData,
|
||||
transformed: TransformResults,
|
||||
type: FileTypes,
|
||||
};
|
||||
25
packager/src/ModuleGraph/worker.js
Normal file
25
packager/src/ModuleGraph/worker.js
Normal file
@@ -0,0 +1,25 @@
|
||||
/**
|
||||
* 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 asyncify = require('async/asyncify');
|
||||
const optimizeModule = require('./worker/optimize-module');
|
||||
const transformModule = require('./worker/transform-module');
|
||||
const wrapWorkerFn = require('./worker/wrap-worker-fn');
|
||||
|
||||
import type {OptimizationOptions} from './worker/optimize-module';
|
||||
import type {TransformOptions} from './worker/transform-module';
|
||||
import type {WorkerFnWithIO} from './worker/wrap-worker-fn';
|
||||
|
||||
exports.optimizeModule =
|
||||
(wrapWorkerFn(asyncify(optimizeModule)): WorkerFnWithIO<OptimizationOptions>);
|
||||
exports.transformModule =
|
||||
(wrapWorkerFn(transformModule): WorkerFnWithIO<TransformOptions>);
|
||||
@@ -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