diff --git a/react-packager/src/ModuleGraph/ModuleGraph.js b/react-packager/src/ModuleGraph/ModuleGraph.js new file mode 100644 index 00000000..bd559940 --- /dev/null +++ b/react-packager/src/ModuleGraph/ModuleGraph.js @@ -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, + options: BuildOptions, + callback: Callback<{modules: Iterable, entryModules: Iterable}>, +) => 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, polyfills: Array}, + ) => { + 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(...iterables: Array>): Iterable { + for (const it of iterables) { + yield* it; + } +} + +function prelude(optimize) { + return virtualModule( + `var __DEV__= ${String(!optimize) + }, __BUNDLE_START_TIME__ = Date.now();` + ); +} diff --git a/react-packager/src/ModuleGraph/output/as-plain-bundle.js b/react-packager/src/ModuleGraph/output/as-plain-bundle.js index e0bf8a2c..2cf40dde 100644 --- a/react-packager/src/ModuleGraph/output/as-plain-bundle.js +++ b/react-packager/src/ModuleGraph/output/as-plain-bundle.js @@ -13,36 +13,32 @@ const {createIndexMap} = require('./source-map'); const {addModuleIdsToModuleWrapper} = require('./util'); -import type {Module} from '../types.flow'; -import type {SourceMap} from './source-map'; +import type {OutputFn} from '../types.flow'; module.exports = ( - modules: Iterable, - filename?: string, - idForPath: {path: string} => number, -): {code: string, map: SourceMap} => { - let code = ''; - let line = 0; - const sections = []; + (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; + 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} - }); + code += moduleCode + '\n'; + if (file.map) { + sections.push({ + map: file.map, + 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; function countLines(string: string): number { diff --git a/react-packager/src/ModuleGraph/output/util.js b/react-packager/src/ModuleGraph/output/util.js index 2f3d8fe0..d233817e 100644 --- a/react-packager/src/ModuleGraph/output/util.js +++ b/react-packager/src/ModuleGraph/output/util.js @@ -10,7 +10,7 @@ */ 'use strict'; -import type {Module} from '../types.flow'; +import type {IdForPathFn, Module} from '../types.flow'; // Transformed modules have the form // __d(function(require, module, global, exports, dependencyMap) { @@ -59,11 +59,27 @@ exports.createIdForPathFn = (): ({path: string} => number) => { }; }; -exports.virtualModule = (code: string): Module => ({ - dependencies: [], - file: { - code, - path: '', - type: 'script', +// creates a series of virtual modules with require calls to the passed-in +// modules. +exports.requireCallsTo = function* ( + modules: Iterable, + idForPath: IdForPathFn, +): Iterable { + 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', + } + }; +} diff --git a/react-packager/src/ModuleGraph/package.json b/react-packager/src/ModuleGraph/package.json new file mode 100644 index 00000000..4c0d77b8 --- /dev/null +++ b/react-packager/src/ModuleGraph/package.json @@ -0,0 +1 @@ +{"main": "ModuleGraph.js"} diff --git a/react-packager/src/ModuleGraph/types.flow.js b/react-packager/src/ModuleGraph/types.flow.js index 707bca57..d3910cfd 100644 --- a/react-packager/src/ModuleGraph/types.flow.js +++ b/react-packager/src/ModuleGraph/types.flow.js @@ -10,29 +10,13 @@ */ 'use strict'; +import type {SourceMap} from './output/source-map'; import type {Console} from 'console'; export type Callback = (Error => 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, -|}; - type Dependency = {| id: string, path: string, @@ -47,11 +31,6 @@ export type File = {| type FileTypes = 'module' | 'script'; -export type Module = {| - dependencies: Array, - file: File, -|}; - export type GraphFn = ( entryPoints: Iterable, platform: string, @@ -59,11 +38,55 @@ export type GraphFn = ( callback?: Callback, ) => void; +type GraphOptions = {| + cwd?: string, + log?: Console, + optimize?: boolean, + skip?: Set, +|}; + export type GraphResult = { entryModules: Array, modules: Array, }; +export type IdForPathFn = {path: string} => number; + +export type LoadFn = ( + file: string, + options: LoadOptions, + callback: Callback>, +) => void; + +type LoadOptions = {| + log?: Console, + optimize?: boolean, + platform?: string, +|}; + +export type Module = {| + dependencies: Array, + file: File, +|}; + +export type OutputFn = ( + modules: Iterable, + 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, @@ -72,12 +95,25 @@ export type ResolveFn = ( callback: Callback, ) => void; -export type LoadFn = ( - file: string, - options: LoadOptions, - callback: Callback>, +type ResolveOptions = { + log?: Console, +}; + +export type TransformFn = ( + data: {| + filename: string, + options?: Object, + plugins?: Array, + sourceCode: string, + |}, + callback: Callback ) => void; + +export type TransformFnResult = { + ast: Object, +}; + export type TransformResult = {| code: string, dependencies: Array, @@ -93,24 +129,3 @@ export type TransformedFile = { transformed: {[variant: string]: TransformResult}, 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, - sourceCode: string, - |}, - callback: Callback -) => void;