Add `bundle` command

Summary: This adds the machinery necessary to create a bundling function, and adds different reusable parts around generating code and maps for bundles. Used for the new integration with Buck

Reviewed By: cpojer

Differential Revision: D4299272

fbshipit-source-id: 59ebe39a454ebf56c2159717c2881088d6d3308a
This commit is contained in:
David Aurelio 2016-12-13 07:20:46 -08:00 committed by Facebook Github Bot
parent d556e5824a
commit 4843500e9e
5 changed files with 209 additions and 79 deletions

View File

@ -0,0 +1,102 @@
/**
* Copyright (c) 2016-present, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*
* @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();`
);
}

View File

@ -13,36 +13,32 @@
const {createIndexMap} = require('./source-map'); const {createIndexMap} = require('./source-map');
const {addModuleIdsToModuleWrapper} = require('./util'); const {addModuleIdsToModuleWrapper} = require('./util');
import type {Module} from '../types.flow'; import type {OutputFn} from '../types.flow';
import type {SourceMap} from './source-map';
module.exports = ( module.exports = (
modules: Iterable<Module>, (modules, filename, idForPath) => {
filename?: string, let code = '';
idForPath: {path: string} => number, let line = 0;
): {code: string, map: SourceMap} => { const sections = [];
let code = '';
let line = 0;
const sections = [];
for (const module of modules) { for (const module of modules) {
const {file} = module; const {file} = module;
const moduleCode = file.type === 'module' const moduleCode = file.type === 'module'
? addModuleIdsToModuleWrapper(module, idForPath) ? addModuleIdsToModuleWrapper(module, idForPath)
: file.code; : file.code;
code += moduleCode + '\n'; code += moduleCode + '\n';
if (file.map) { if (file.map) {
sections.push({ sections.push({
map: file.map, map: file.map,
offset: {column: 0, line} offset: {column: 0, line}
}); });
}
line += countLines(moduleCode);
} }
line += countLines(moduleCode);
}
return {code, map: createIndexMap({file: filename, sections})}; return {code, map: createIndexMap({file: filename, sections})};
}; }: OutputFn);
const reLine = /^/gm; const reLine = /^/gm;
function countLines(string: string): number { function countLines(string: string): number {

View File

@ -10,7 +10,7 @@
*/ */
'use strict'; 'use strict';
import type {Module} from '../types.flow'; import type {IdForPathFn, Module} from '../types.flow';
// Transformed modules have the form // Transformed modules have the form
// __d(function(require, module, global, exports, dependencyMap) { // __d(function(require, module, global, exports, dependencyMap) {
@ -59,11 +59,27 @@ exports.createIdForPathFn = (): ({path: string} => number) => {
}; };
}; };
exports.virtualModule = (code: string): Module => ({ // creates a series of virtual modules with require calls to the passed-in
dependencies: [], // modules.
file: { exports.requireCallsTo = function* (
code, modules: Iterable<Module>,
path: '', idForPath: IdForPathFn,
type: 'script', ): 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',
}
};
}

View File

@ -0,0 +1 @@
{"main": "ModuleGraph.js"}

View File

@ -10,29 +10,13 @@
*/ */
'use strict'; 'use strict';
import type {SourceMap} from './output/source-map';
import type {Console} from 'console'; import type {Console} from 'console';
export type Callback<A = void, B = void> export type Callback<A = void, B = void>
= (Error => mixed) = (Error => mixed)
& ((null | void, A, B) => mixed); & ((null | void, A, B) => mixed);
type ResolveOptions = {
log?: Console,
};
type LoadOptions = {|
log?: Console,
optimize?: boolean,
platform?: string,
|};
type GraphOptions = {|
cwd?: string,
log?: Console,
optimize?: boolean,
skip?: Set<string>,
|};
type Dependency = {| type Dependency = {|
id: string, id: string,
path: string, path: string,
@ -47,11 +31,6 @@ export type File = {|
type FileTypes = 'module' | 'script'; type FileTypes = 'module' | 'script';
export type Module = {|
dependencies: Array<Dependency>,
file: File,
|};
export type GraphFn = ( export type GraphFn = (
entryPoints: Iterable<string>, entryPoints: Iterable<string>,
platform: string, platform: string,
@ -59,11 +38,55 @@ export type GraphFn = (
callback?: Callback<GraphResult>, callback?: Callback<GraphResult>,
) => void; ) => void;
type GraphOptions = {|
cwd?: string,
log?: Console,
optimize?: boolean,
skip?: Set<string>,
|};
export type GraphResult = { export type GraphResult = {
entryModules: Array<Module>, entryModules: Array<Module>,
modules: 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 = ( export type ResolveFn = (
id: string, id: string,
source: string, source: string,
@ -72,12 +95,25 @@ export type ResolveFn = (
callback: Callback<string>, callback: Callback<string>,
) => void; ) => void;
export type LoadFn = ( type ResolveOptions = {
file: string, log?: Console,
options: LoadOptions, };
callback: Callback<File, Array<string>>,
export type TransformFn = (
data: {|
filename: string,
options?: Object,
plugins?: Array<string | Object | [string | Object, any]>,
sourceCode: string,
|},
callback: Callback<TransformFnResult>
) => void; ) => void;
export type TransformFnResult = {
ast: Object,
};
export type TransformResult = {| export type TransformResult = {|
code: string, code: string,
dependencies: Array<string>, dependencies: Array<string>,
@ -93,24 +129,3 @@ export type TransformedFile = {
transformed: {[variant: string]: TransformResult}, transformed: {[variant: string]: TransformResult},
type: FileTypes, type: FileTypes,
}; };
export type PackageData = {|
browser?: Object | string,
main?: string,
name?: string,
'react-native'?: Object | string,
|};
export type TransformFnResult = {
ast: Object,
};
export type TransformFn = (
data: {|
filename: string,
options?: Object,
plugins?: Array<string | Object | [string | Object, any]>,
sourceCode: string,
|},
callback: Callback<TransformFnResult>
) => void;