fork unbundle output format per platform

Summary:
We decided to use different storage formats for android and ios. This diff changes the output format to asset files if the platform is `'android'`.

public

Reviewed By: martinbigio

Differential Revision: D2764739

fb-gh-sync-id: 4a5ac13ba7978112e9424573643e90cef2a1b75f
This commit is contained in:
David Aurelio 2015-12-16 11:59:22 -08:00 committed by facebook-github-bot-5
parent 1be5777265
commit 12778f3bc6
4 changed files with 185 additions and 34 deletions

View File

@ -0,0 +1,103 @@
/**
* 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.
*/
'use strict';
const fs = require('fs');
const mkdirp = require('mkdirp');
const path = require('path');
const Promise = require('promise');
const writeFile = require('../writeFile');
const writeSourceMap = require('./write-sourcemap');
const MODULES_DIR = 'js-modules';
/**
* Saves all JS modules of an app as single files
* The startup code (prelude, polyfills etc.) are written to the file
* designated by the `bundleOuput` option.
* All other modules go into a 'js-modules' folder that in the same parent
* directory as the startup file.
*/
function saveAsAssets(bundle, options, log) {
const {
'bundle-output': bundleOutput,
'bundle-encoding': encoding,
dev,
'sourcemap-output': sourcemapOutput,
} = options;
log('start');
const {startupCode, modules} = bundle.getUnbundle({minify: !dev});
log('finish');
log('Writing bundle output to:', bundleOutput);
const writeUnbundle =
Promise.all([
writeModules(path.dirname(bundleOutput), modules, encoding),
writeStartupFile(bundleOutput, startupCode, encoding)
]);
writeUnbundle.then(() => log('Done writing unbundle output'));
return Promise.all([writeUnbundle, writeSourceMap(sourcemapOutput, '', log)]);
}
function createDir(dirName) {
return new Promise((resolve, reject) =>
mkdirp(dirName, error => error ? reject(error) : resolve()));
}
function createDirectoriesForModules(modulesDir, modules) {
const dirNames =
modules.map(name => {
// get all needed directory names
const dir = path.dirname(name);
return dir === '.' ? modulesDir : path.join(modulesDir, dir);
})
.filter(Boolean) // remove empty directories
.sort()
.filter((dir, i, dirs) => {
// remove parent directories. After sorting, parent directories are
// located before child directories
const next = dirs[i + 1];
return !next || next !== dir && !next.startsWith(dir + path.sep);
});
return dirNames.reduce(
(promise, dirName) =>
promise.then(() => createDir(dirName)), Promise.resolve());
}
function writeModuleFile(module, modulesDir, encoding) {
const {name, code} = module;
return writeFile(path.join(modulesDir, name + '.js'), code, encoding);
}
function writeModuleFiles(modules, modulesDir, encoding) {
const writeFiles =
modules.map(module => writeModuleFile(module, modulesDir, encoding));
return Promise.all(writeFiles);
}
function writeModules(assetsDest, modules, encoding) {
const modulesDir = path.join(assetsDest, MODULES_DIR);
return (
createDirectoriesForModules(modulesDir, modules.map(({name}) => name))
.then(() => writeModuleFiles(modules, modulesDir, encoding))
);
}
function writeStartupFile(outputFile, code, encoding) {
return new Promise((resolve, reject) => {
fs.createWriteStream(outputFile).
write(code, encoding, error => error ? reject(error) : resolve());
});
}
module.exports = saveAsAssets;

View File

@ -10,16 +10,19 @@
const fs = require('fs');
const Promise = require('promise');
const writeFile = require('./writeFile');
const writeSourceMap = require('./write-sourcemap');
const MAGIC_UNBUNDLE_FILE_HEADER = 0xFB0BD1E5;
const MAGIC_STARTUP_MODULE_ID = '';
function buildBundle(packagerClient, requestOptions) {
return packagerClient.buildBundle({...requestOptions, unbundle: true});
}
function saveUnbundle(bundle, options, log) {
/**
* Saves all JS modules of an app as a single file, separated with null bytes.
* The file begins with an offset table that contains module ids and their
* lengths/offsets.
* The module id for the startup code (prelude, polyfills etc.) is the
* empty string.
*/
function saveAsIndexedFile(bundle, options, log) {
const {
'bundle-output': bundleOutput,
'bundle-encoding': encoding,
@ -39,21 +42,24 @@ function saveUnbundle(bundle, options, log) {
writeUnbundle.then(() => log('Done writing unbundle output'));
if (sourcemapOutput) {
log('Writing sourcemap output to:', sourcemapOutput);
const writeMap = writeFile(sourcemapOutput, '', null);
writeMap.then(() => log('Done writing sourcemap output'));
return Promise.all([writeUnbundle, writeMap]);
} else {
return writeUnbundle;
}
return Promise.all([writeUnbundle, writeSourceMap(sourcemapOutput, '', log)]);
}
/* global Buffer: true */
const fileHeader = Buffer(4);
fileHeader.writeUInt32LE(MAGIC_UNBUNDLE_FILE_HEADER);
const nullByteBuffer = Buffer(1).fill(0);
function writeBuffers(stream, buffers) {
buffers.forEach(buffer => stream.write(buffer));
return new Promise((resolve, reject) => {
stream.on('error', reject);
stream.on('finish', () => resolve());
stream.end();
});
}
const moduleToBuffer = ({name, code}, encoding) => ({
name,
buffer: Buffer.concat([
@ -62,16 +68,9 @@ const moduleToBuffer = ({name, code}, encoding) => ({
])
});
function buildModuleBuffers(startupCode, modules, encoding) {
return (
[moduleToBuffer({name: '', code: startupCode}, encoding)]
.concat(modules.map(module => moduleToBuffer(module, encoding)))
);
}
function uInt32Buffer(n) {
const buffer = Buffer(4);
buffer.writeUInt32LE(n, 0); // let's assume LE for now :)
buffer.writeUInt32LE(n, 0);
return buffer;
}
@ -109,21 +108,17 @@ function buildModuleTable(buffers) {
return Buffer.concat(offsetTable);
}
function buildModuleBuffers(startupCode, modules, encoding) {
return (
[moduleToBuffer({name: '', code: startupCode}, encoding)]
.concat(modules.map(module => moduleToBuffer(module, encoding)))
);
}
function buildTableAndContents(startupCode, modules, encoding) {
const buffers = buildModuleBuffers(startupCode, modules, encoding);
const table = buildModuleTable(buffers, encoding);
return [fileHeader, table].concat(buffers.map(({buffer}) => buffer));
}
function writeBuffers(stream, buffers) {
buffers.forEach(buffer => stream.write(buffer));
return new Promise((resolve, reject) => {
stream.on('error', reject);
stream.on('finish', () => resolve());
stream.end();
});
}
exports.build = buildBundle;
exports.save = saveUnbundle;
exports.formatName = 'bundle';
module.exports = saveAsIndexedFile;

View File

@ -0,0 +1,29 @@
/**
* 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.
*/
'use strict';
const asIndexedFile = require('./as-indexed-file');
const asAssets = require('./as-assets');
function buildBundle(packagerClient, requestOptions) {
return packagerClient.buildBundle({...requestOptions, unbundle: true});
}
function saveUnbundle(bundle, options, log) {
// 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
return options.platform === 'android' ?
asAssets(bundle, options, log) :
asIndexedFile(bundle, options, log);
}
exports.build = buildBundle;
exports.save = saveUnbundle;
exports.formatName = 'bundle';

View File

@ -0,0 +1,24 @@
/**
* 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.
*/
'use strict';
const Promise = require('promise');
const writeFile = require('../writeFile');
function writeSourcemap(fileName, contents, log) {
if (!fileName) {
return Promise.resolve();
}
log('Writing sourcemap output to:', fileName);
const writeMap = writeFile(fileName, '', null);
writeMap.then(() => log('Done writing sourcemap output'));
return writeMap;
}
module.exports = writeSourcemap;