Add flow types for output modules

Summary: Adds flow types for output functionality for easier maintenance and interop with new code

Reviewed By: matryoshcow

Differential Revision: D4211863

fbshipit-source-id: 591407d3a6d49536054ae94ba31125c18a1e1fa1
This commit is contained in:
David Aurelio 2016-11-21 13:19:13 -08:00 committed by Facebook Github Bot
parent 9712d335e2
commit f3779502d3
16 changed files with 226 additions and 71 deletions

View File

@ -9,11 +9,11 @@
type _SourceMap = {
version: number,
file: string,
file?: string,
sources: Array<string>,
names: Array<string>,
mappings: string,
sourcesContent: Array<string>,
sourcesContent?: Array<string>,
};
// based on babylon v6.13.1

View File

@ -16,6 +16,8 @@ const path = require('path');
const saveAssets = require('./saveAssets');
const defaultAssetExts = require('../../packager/defaults').assetExts;
import type RequestOptions from './types.flow';
function saveBundle(output, bundle, args) {
return Promise.resolve(
output.save(bundle, args, log)
@ -27,7 +29,7 @@ function buildBundle(args, config, output = outputBundle, packagerInstance) {
// have other choice than defining it as an env variable here.
process.env.NODE_ENV = args.dev ? 'development' : 'production';
const requestOpts = {
const requestOpts: RequestOptions = {
entryFile: args.entryFile,
sourceMapUrl: args.sourcemapOutput && path.basename(args.sourcemapOutput),
dev: args.dev,

View File

@ -5,27 +5,37 @@
* 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 meta = require('./meta');
const writeFile = require('./writeFile');
function buildBundle(packagerClient, requestOptions) {
import type Bundle from '../../../packager/react-packager/src/Bundler/Bundle';
import type Server from '../../../packager/react-packager/src/Server';
import type {OutputOptions, RequestOptions} from '../types.flow';
function buildBundle(packagerClient: Server, requestOptions: RequestOptions) {
return packagerClient.buildBundle({
...requestOptions,
isolateModuleIDs: true,
});
}
function createCodeWithMap(bundle, dev) {
function createCodeWithMap(bundle: Bundle, dev: boolean): * {
return {
code: bundle.getSource({dev}),
map: JSON.stringify(bundle.getSourceMap({dev})),
};
}
function saveBundleAndMap(bundle, options, log) {
function saveBundleAndMap(
bundle: Bundle,
options: OutputOptions,
log: (x: string) => {},
): Promise<> {
const {
bundleOutput,
bundleEncoding: encoding,
@ -34,7 +44,7 @@ function saveBundleAndMap(bundle, options, log) {
} = options;
log('start');
const codeWithMap = createCodeWithMap(bundle, dev);
const codeWithMap = createCodeWithMap(bundle, !!dev);
log('finish');
log('Writing bundle output to:', bundleOutput);

View File

@ -5,6 +5,8 @@
* 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';
@ -18,6 +20,9 @@ const writeSourceMap = require('./write-sourcemap');
const {joinModules} = require('./util');
import type Bundle from '../../../../packager/react-packager/src/Bundler/Bundle';
import type {OutputOptions} from '../../types.flow';
// must not start with a dot, as that won't go into the apk
const MAGIC_UNBUNDLE_FILENAME = 'UNBUNDLE';
const MODULES_DIR = 'js-modules';
@ -29,7 +34,11 @@ const MODULES_DIR = 'js-modules';
* All other modules go into a 'js-modules' folder that in the same parent
* directory as the startup file.
*/
function saveAsAssets(bundle, options, log) {
function saveAsAssets(
bundle: Bundle,
options: OutputOptions,
log: (x: string) => void,
): Promise<mixed> {
const {
bundleOutput,
bundleEncoding: encoding,
@ -54,11 +63,14 @@ function saveAsAssets(bundle, options, log) {
writeUnbundle.then(() => log('Done writing unbundle output'));
const sourceMap =
buildSourceMapWithMetaData({startupModules, lazyModules});
buildSourceMapWithMetaData({
startupModules: startupModules.concat(),
lazyModules: lazyModules.concat(),
});
return Promise.all([
writeUnbundle,
writeSourceMap(sourcemapOutput, JSON.stringify(sourceMap), log)
sourcemapOutput && writeSourceMap(sourcemapOutput, JSON.stringify(sourceMap), log)
]);
}
@ -80,8 +92,8 @@ function writeModules(modules, modulesDir, encoding) {
function writeMagicFlagFile(outputDir) {
/* global Buffer: true */
const buffer = Buffer(4);
buffer.writeUInt32LE(MAGIC_UNBUNDLE_NUMBER);
const buffer = new Buffer(4);
buffer.writeUInt32LE(MAGIC_UNBUNDLE_NUMBER, 0);
return writeFile(path.join(outputDir, MAGIC_UNBUNDLE_FILENAME), buffer);
}

View File

@ -5,6 +5,8 @@
* 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';
@ -15,6 +17,10 @@ const fs = require('fs');
const writeSourceMap = require('./write-sourcemap');
const {joinModules} = require('./util');
import type ModuleTransport from '../../../../packager/react-packager/src/lib/ModuleTransport';
import type {Bundle, ModuleGroups, OutputOptions} from '../../types.flow';
const SIZEOF_UINT32 = 4;
/**
@ -24,18 +30,22 @@ const SIZEOF_UINT32 = 4;
* The module id for the startup code (prelude, polyfills etc.) is the
* empty string.
*/
function saveAsIndexedFile(bundle, options, log) {
function saveAsIndexedFile(
bundle: Bundle,
options: OutputOptions,
log: (x: string) => void,
): Promise<> {
const {
bundleOutput,
bundleEncoding: encoding,
sourcemapOutput
sourcemapOutput,
} = options;
log('start');
const {startupModules, lazyModules, groups} = bundle.getUnbundle();
log('finish');
const moduleGroups = ModuleGroups(groups, lazyModules);
const moduleGroups = createModuleGroups(groups, lazyModules);
const startupCode = joinModules(startupModules);
log('Writing unbundle output to:', bundleOutput);
@ -45,21 +55,26 @@ function saveAsIndexedFile(bundle, options, log) {
).then(() => log('Done writing unbundle output'));
const sourceMap =
buildSourceMapWithMetaData({startupModules, lazyModules, moduleGroups});
buildSourceMapWithMetaData({
startupModules: startupModules.concat(),
lazyModules: lazyModules.concat(),
moduleGroups,
});
return Promise.all([
writeUnbundle,
writeSourceMap(sourcemapOutput, JSON.stringify(sourceMap), log),
sourcemapOutput && writeSourceMap(sourcemapOutput, JSON.stringify(sourceMap), log),
]);
}
/* global Buffer: true */
const fileHeader = Buffer(4);
fileHeader.writeUInt32LE(MAGIC_UNBUNDLE_FILE_HEADER);
const nullByteBuffer = Buffer(1).fill(0);
const fileHeader = new Buffer(4);
fileHeader.writeUInt32LE(MAGIC_UNBUNDLE_FILE_HEADER, 0);
//$FlowIssue #14640206
const nullByteBuffer: Buffer = new Buffer(1).fill(0);
function writeBuffers(stream, buffers) {
function writeBuffers(stream, buffers: Array<Buffer>) {
buffers.forEach(buffer => stream.write(buffer));
return new Promise((resolve, reject) => {
stream.on('error', reject);
@ -69,7 +84,7 @@ function writeBuffers(stream, buffers) {
}
function nullTerminatedBuffer(contents, encoding) {
return Buffer.concat([Buffer(contents, encoding), nullByteBuffer]);
return Buffer.concat([new Buffer(contents, encoding), nullByteBuffer]);
}
function moduleToBuffer(id, code, encoding) {
@ -98,7 +113,8 @@ function buildModuleTable(startupCode, buffers, moduleGroups) {
const moduleIds = Array.from(moduleGroups.modulesById.keys());
const maxId = moduleIds.reduce((max, id) => Math.max(max, id));
const numEntries = maxId + 1;
const table = new Buffer(entryOffset(numEntries)).fill(0);
//$FlowIssue #14640206
const table: Buffer = new Buffer(entryOffset(numEntries)).fill(0);
// num_entries
table.writeUInt32LE(numEntries, 0);
@ -109,9 +125,8 @@ function buildModuleTable(startupCode, buffers, moduleGroups) {
// entries
let codeOffset = startupCode.length;
buffers.forEach(({id, buffer}) => {
const idsInGroup = moduleGroups.groups.has(id)
? [id].concat(Array.from(moduleGroups.groups.get(id)))
: [id];
const group = moduleGroups.groups.get(id);
const idsInGroup = group ? [id].concat(Array.from(group)) : [id];
idsInGroup.forEach(moduleId => {
const offset = entryOffset(moduleId);
@ -132,7 +147,7 @@ function groupCode(rootCode, moduleGroup, modulesById) {
}
const code = [rootCode];
for (const id of moduleGroup) {
code.push(modulesById.get(id).code);
code.push((modulesById.get(id) || {}).code);
}
return code.join('\n');
@ -170,7 +185,10 @@ function buildTableAndContents(startupCode, modules, moduleGroups, encoding) {
].concat(moduleBuffers.map(({buffer}) => buffer));
}
function ModuleGroups(groups, modules) {
function createModuleGroups(
groups: Map<number, Set<number>>,
modules: Array<ModuleTransport>,
): ModuleGroups {
return {
groups,
modulesById: new Map(modules.map(m => [m.id, m])),

View File

@ -5,14 +5,25 @@
* 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 {combineSourceMaps, joinModules} = require('./util');
module.exports = ({startupModules, lazyModules, moduleGroups}) => {
const startupModule = {
import type {ModuleGroups, ModuleTransportLike} from '../../types.flow';
type Params = {
lazyModules: Array<ModuleTransportLike>,
moduleGroups?: ModuleGroups,
startupModules: Array<ModuleTransportLike>,
};
module.exports = ({startupModules, lazyModules, moduleGroups}: Params) => {
const startupModule: ModuleTransportLike = {
code: joinModules(startupModules),
id: Number.MIN_SAFE_INTEGER,
map: combineSourceMaps({modules: startupModules}),
};
return combineSourceMaps({

View File

@ -5,13 +5,19 @@
* 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 asAssets = require('./as-assets');
const asIndexedFile = require('./as-indexed-file');
function buildBundle(packagerClient, requestOptions) {
import type Bundle from '../../../../packager/react-packager/src/Bundler/Bundle';
import type Server from '../../../../packager/react-packager/src/Server';
import type {OutputOptions, RequestOptions} from '../../types.flow';
function buildBundle(packagerClient: Server, requestOptions: RequestOptions) {
return packagerClient.buildBundle({
...requestOptions,
unbundle: true,
@ -19,7 +25,11 @@ function buildBundle(packagerClient, requestOptions) {
});
}
function saveUnbundle(bundle, options, log) {
function saveUnbundle(
bundle: Bundle,
options: OutputOptions,
log: (x: string) => void,
): Promise<mixed> {
// we fork here depending on the platform:
// while android is pretty good at loading individual assets, ios has a large
// overhead when reading hundreds pf assets from disk

View File

@ -5,6 +5,8 @@
* 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';

View File

@ -5,14 +5,19 @@
* 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 newline = /\r\n?|\n|\u2028|\u2029/g;
const countLines =
string => (string.match(newline) || []).length + 1; // fastest implementation
import type {ModuleGroups, ModuleTransportLike, SourceMap} from '../../types.flow';
function lineToLineSourceMap(source, filename) {
const newline = /\r\n?|\n|\u2028|\u2029/g;
// fastest implementation
const countLines = (string: string) => (string.match(newline) || []).length + 1;
function lineToLineSourceMap(source: string, filename: string = ''): SourceMap {
// The first line mapping in our package is the base64vlq code for zeros (A).
const firstLine = 'AAAA;';
@ -21,34 +26,52 @@ function lineToLineSourceMap(source, filename) {
const line = 'AACA;';
return {
version: 3,
sources: [filename],
file: filename,
mappings: firstLine + Array(countLines(source)).join(line),
sources: [filename],
names: [],
version: 3,
};
}
const wrapperEnd = wrappedCode => wrappedCode.indexOf('{') + 1;
const Section = (line, column, map) => ({map, offset: {line, column}});
const Section =
(line: number, column: number, map: SourceMap) =>
({map, offset: {line, column}});
function combineSourceMaps({modules, withCustomOffsets, moduleGroups}) {
type CombineSourceMapsOptions = {
moduleGroups?: ModuleGroups,
modules: Array<ModuleTransportLike>,
withCustomOffsets?: boolean,
};
function combineSourceMaps({
moduleGroups,
modules,
withCustomOffsets,
}: CombineSourceMapsOptions): SourceMap {
let offsets;
const sections = [];
const sourceMap = {
version: 3,
const sourceMap: Object = {
file: '',
sections,
version: 3,
};
if (withCustomOffsets) {
offsets = sourceMap.x_facebook_offsets = [];
}
let line = 0;
modules.forEach(({code, id, map, name}) => {
modules.forEach(moduleTransport => {
const {code, id, name} = moduleTransport;
let column = 0;
let hasOffset = false;
let group;
let groupLines = 0;
let {map} = moduleTransport;
if (withCustomOffsets) {
if (moduleGroups && moduleGroups.modulesInGroups.has(id)) {
@ -56,15 +79,19 @@ function combineSourceMaps({modules, withCustomOffsets, moduleGroups}) {
return;
}
if (moduleGroups && moduleGroups.groups.has(id)) {
group = moduleGroups.groups.get(id);
const otherModules = Array.from(group).map(
moduleId => moduleGroups.modulesById.get(moduleId));
group = moduleGroups && moduleGroups.groups.get(id);
if (group && moduleGroups) {
const {modulesById} = moduleGroups;
const otherModules: Array<ModuleTransportLike> =
Array.from(group || [])
.map(moduleId => modulesById.get(moduleId))
.filter(Boolean); // needed to appease flow
otherModules.forEach(m => {
groupLines += countLines(m.code);
});
map = combineSourceMaps({
modules: [{code, id, map, name}].concat(otherModules),
modules: [moduleTransport].concat(otherModules),
});
}
@ -85,7 +112,9 @@ function combineSourceMaps({modules, withCustomOffsets, moduleGroups}) {
return sourceMap;
}
const joinModules = modules => modules.map(m => m.code).join('\n');
const joinModules =
(modules: Array<*>): string =>
modules.map(m => m.code).join('\n');
module.exports = {
countLines,

View File

@ -5,12 +5,18 @@
* 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 writeFile = require('../writeFile');
function writeSourcemap(fileName, contents, log) {
function writeSourcemap(
fileName: string,
contents: string,
log: (x: string) => void,
): Promise<> {
if (!fileName) {
return Promise.resolve();
}

View File

@ -5,20 +5,16 @@
* 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 denodeify = require('denodeify');
const fs = require('fs');
function writeFile(file, data, encoding) {
return new Promise((resolve, reject) => {
fs.writeFile(
file,
data,
encoding,
error => error ? reject(error) : resolve()
);
});
}
type WriteFn =
(file: string, data: string | Buffer, encoding?: ?string) => Promise<mixed>;
const writeFile: WriteFn = denodeify(fs.writeFile);
module.exports = writeFile;

View File

@ -0,0 +1,47 @@
/**
* 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 Bundle from '../../packager/react-packager/src/Bundler/Bundle';
import type {Unbundle} from '../../packager/react-packager/src/Bundler/Bundle';
import type ModuleTransport from '../../packager/react-packager/src/lib/ModuleTransport';
import type {MixedSourceMap} from '../../packager/react-packager/src/lib/SourceMap';
export type {Bundle, ModuleTransport, MixedSourceMap as SourceMap, Unbundle};
export type ModuleGroups = {|
groups: Map<number, Set<number>>,
modulesById: Map<number, ModuleTransport>,
modulesInGroups: Set<number>,
|};
export type ModuleTransportLike = {
code: string,
id: number,
map?: ?MixedSourceMap,
+name?: string,
};
export type OutputOptions = {
bundleOutput: string,
bundleEncoding?: 'utf8' | 'utf16le' | 'ascii',
dev?: boolean,
platform: string,
sourcemapOutput?: string,
};
export type RequestOptions = {|
entryFile: string,
sourceMapUrl?: string,
dev?: boolean,
minify: boolean,
platform: string,
|};

View File

@ -21,6 +21,12 @@ const crypto = require('crypto');
import type {SourceMap, CombinedSourceMap, MixedSourceMap} from '../lib/SourceMap';
import type {GetSourceOptions, FinalizeOptions} from './BundleBase';
export type Unbundle = {
startupModules: Array<*>,
lazyModules: Array<*>,
groups: Map<number, Set<number>>,
};
const SOURCEMAPPING_URL = '\n\/\/# sourceMappingURL=';
class Bundle extends BundleBase {
@ -29,7 +35,7 @@ class Bundle extends BundleBase {
_inlineSourceMap: string | void;
_minify: boolean | void;
_numRequireCalls: number;
_ramBundle: mixed | void;
_ramBundle: Unbundle | null;
_ramGroups: Array<string> | void;
_shouldCombineSourceMaps: boolean;
_sourceMap: boolean;
@ -144,7 +150,7 @@ class Bundle extends BundleBase {
return source;
}
getUnbundle() {
getUnbundle(): Unbundle {
this.assertFinalized();
if (!this._ramBundle) {
const modules = this.getModules().slice();
@ -419,7 +425,7 @@ function createGroups(ramGroups: Array<string>, lazyModules) {
});
// build a map of group root IDs to an array of module IDs in the group
const result = new Map(
const result: Map<number, Set<number>> = new Map(
ramGroups
.map(modulePath => {
const root = byPath.get(modulePath);

View File

@ -19,7 +19,7 @@ export type FinalizeOptions = {
};
export type GetSourceOptions = {
inlineSourceMap: boolean,
inlineSourceMap?: boolean,
dev: boolean,
};

View File

@ -11,7 +11,7 @@
'use strict';
import type {SourceMap} from './SourceMap';
import type {MixedSourceMap} from './SourceMap';
type Metadata = {
dependencyPairs?: Array<[mixed, {path: string}]>,
@ -21,25 +21,25 @@ type Metadata = {
class ModuleTransport {
name: string;
id: string | number;
id: number;
code: string;
sourceCode: string;
sourcePath: string;
virtual: ?boolean;
meta: ?Metadata;
polyfill: ?boolean;
map: ?SourceMap;
map: ?MixedSourceMap;
constructor(data: {
name: string,
id: string | number,
id: number,
code: string,
sourceCode: string,
sourcePath: string,
virtual?: ?boolean,
meta?: ?Metadata,
polyfill?: ?boolean,
map?: ?SourceMap,
map?: ?MixedSourceMap,
}) {
this.name = data.name;

View File

@ -20,8 +20,14 @@ export type CombinedSourceMap = {
file: string,
sections: Array<{
offset: {line: number, column: number},
map: SourceMap,
map: MixedSourceMap,
}>,
};
export type MixedSourceMap = SourceMap | CombinedSourceMap;
type FBExtensions = {x_facebook_offsets?: Array<number>};
export type MixedSourceMap
= SourceMap
| CombinedSourceMap
| (SourceMap & FBExtensions)
| (CombinedSourceMap & FBExtensions);