Skip to content

Commit

Permalink
Add unbundling to packager
Browse files Browse the repository at this point in the history
Reviewed By: tadeuzagallo

Differential Revision: D2707409

fb-gh-sync-id: 30216c36066dae68d83622dba2d598e9dc0a29db
  • Loading branch information
davidaurelio authored and facebook-github-bot-7 committed Dec 1, 2015
1 parent b6f5c7f commit cc4a5d3
Show file tree
Hide file tree
Showing 15 changed files with 318 additions and 19 deletions.
12 changes: 10 additions & 2 deletions local-cli/bundle/bundle.js
Original file line number Diff line number Diff line change
Expand Up @@ -17,10 +17,18 @@ const outputPrepack = require('./output/prepack');
/**
* Builds the bundle starting to look for dependencies at the given entry path.
*/
function bundle(argv, config) {
function bundleWithOutput(argv, config, output) {
const args = parseCommandLine(bundleCommandLineArgs, argv);
const output = args.prepack ? outputPrepack : outputBundle;
if (!output) {
output = args.prepack ? outputPrepack : outputBundle;
}
return buildBundle(args, config, output);

}

function bundle(argv, config) {
return bundleWithOutput(argv, config);
}

module.exports = bundle;
module.exports.withOutput = bundleWithOutput;
2 changes: 1 addition & 1 deletion local-cli/bundle/output/bundle.js
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ function saveBundleAndMap(bundle, options, log) {
'bundle-encoding': encoding,
dev,
'sourcemap-output': sourcemapOutput,
} = options;
} = options;

log('start');
const codeWithMap = createCodeWithMap(bundle, dev);
Expand Down
126 changes: 126 additions & 0 deletions local-cli/bundle/output/unbundle.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,126 @@
/**
* 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 Promise = require('promise');
const writeFile = require('./writeFile');

const MAGIC_STARTUP_MODULE_ID = '';

function buildBundle(packagerClient, requestOptions) {
return packagerClient.buildBundle({...requestOptions, unbundle: true});
}

function saveUnbundle(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 unbundle output to:', bundleOutput);
const writeUnbundle = writeBuffers(
fs.createWriteStream(bundleOutput),
buildTableAndContents(startupCode, modules, encoding)
);

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;
}
}

/* global Buffer: true */
const nullByteBuffer = Buffer(1).fill(0);

const moduleToBuffer = ({name, code}, encoding) => ({
name,
buffer: Buffer.concat([
Buffer(code, encoding),
nullByteBuffer // create \0-terminated strings
])
});

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 :)
return buffer;
}

function buildModuleTable(buffers) {
// table format:
// - table_length: uint_32 length of all table entries in bytes
// - entries: entry...
//
// entry:
// - module_id: NUL terminated utf8 string
// - module_offset: uint_32 offset into the module string
// - module_length: uint_32 length of the module string, including terminating NUL byte

const numBuffers = buffers.length;

const tableLengthBuffer = uInt32Buffer(0);
let tableLength = 4; // the table length itself, 4 == tableLengthBuffer.length
let currentOffset = 0;

const offsetTable = [tableLengthBuffer];
for (let i = 0; i < numBuffers; i++) {
const {name, buffer: {length}} = buffers[i];
const entry = Buffer.concat([
Buffer(i === 0 ? MAGIC_STARTUP_MODULE_ID : name, 'utf8'),
nullByteBuffer,
uInt32Buffer(currentOffset),
uInt32Buffer(length)
]);
currentOffset += length;
tableLength += entry.length;
offsetTable.push(entry);
}

tableLengthBuffer.writeUInt32LE(tableLength, 0);
return Buffer.concat(offsetTable);
}

function buildTableAndContents(startupCode, modules, encoding) {
const buffers = buildModuleBuffers(startupCode, modules, encoding);
const table = buildModuleTable(buffers, encoding);
return [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';
21 changes: 21 additions & 0 deletions local-cli/bundle/unbundle.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
/**
* 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 bundleWithOutput = require('./bundle').withOutput;
const outputUnbundle = require('./output/unbundle');

/**
* Builds the bundle starting to look for dependencies at the given entry path.
*/
function unbundle(argv, config) {
return bundleWithOutput(argv, config, outputUnbundle);
}

module.exports = unbundle;
2 changes: 2 additions & 0 deletions local-cli/cli.js
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ var runAndroid = require('./runAndroid/runAndroid');
var server = require('./server/server');
var TerminalAdapter = require('yeoman-environment/lib/adapter.js');
var yeoman = require('yeoman-environment');
var unbundle = require('./bundle/unbundle');
var upgrade = require('./upgrade/upgrade');

var fs = require('fs');
Expand All @@ -40,6 +41,7 @@ gracefulFs.gracefulify(fs);
var documentedCommands = {
'start': [server, 'starts the webserver'],
'bundle': [bundle, 'builds the javascript bundle for offline use'],
'unbundle': [unbundle, 'builds javascript as "unbundle" for offline use'],
'new-library': [library, 'generates a native library bridge'],
'link': [link, 'Adds a third-party library to your project. Example: react-native link awesome-camera'],
'android': [generateWrapper, 'generates an Android project for your app'],
Expand Down
39 changes: 39 additions & 0 deletions packager/react-packager/src/Bundler/Bundle.js
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,14 @@ const Activity = require('../Activity');

const SOURCEMAPPING_URL = '\n\/\/@ sourceMappingURL=';

const minifyCode = code =>
UglifyJS.minify(code, {fromString: true, ascii_only: true}).code;
const getCode = x => x.code;
const getMinifiedCode = x => minifyCode(x.code);
const getNameAndCode = ({name, code}) => ({name, code});
const getNameAndMinifiedCode =
({name, code}) => ({name, code: minifyCode(code)});

class Bundle {
constructor(sourceMapUrl) {
this._finalized = false;
Expand All @@ -24,6 +32,8 @@ class Bundle {
this._sourceMap = false;
this._sourceMapUrl = sourceMapUrl;
this._shouldCombineSourceMaps = false;
this._numPrependedModules = 0;
this._numRequireCalls = 0;
}

setMainModuleId(moduleId) {
Expand All @@ -48,6 +58,10 @@ class Bundle {
return this._modules;
}

setNumPrependedModules(n) {
this._numPrependedModules = n;
}

addAsset(asset) {
this._assets.push(asset);
}
Expand Down Expand Up @@ -76,6 +90,7 @@ class Bundle {
sourceCode: code,
sourcePath: name + '.js',
}));
this._numRequireCalls += 1;
}

_assertFinalized() {
Expand Down Expand Up @@ -141,6 +156,26 @@ class Bundle {
return source;
}

getUnbundle({minify}) {
const allModules = this._modules.slice();
const prependedModules = this._numPrependedModules;
const requireCalls = this._numRequireCalls;

const modules =
allModules
.splice(prependedModules, allModules.length - requireCalls - prependedModules);
const startupCode =
allModules
.map(minify ? getMinifiedCode : getCode)
.join('\n');

return {
startupCode,
modules:
modules.map(minify ? getNameAndMinifiedCode : getNameAndCode)
};
}

getMinifiedSourceAndMap(dev) {
this._assertFinalized();

Expand Down Expand Up @@ -336,6 +371,8 @@ class Bundle {
assets: this._assets,
sourceMapUrl: this._sourceMapUrl,
mainModuleId: this._mainModuleId,
numPrependedModules: this._numPrependedModules,
numRequireCalls: this._numRequireCalls,
};
}

Expand All @@ -345,6 +382,8 @@ class Bundle {
bundle._assets = json.assets;
bundle._modules = json.modules;
bundle._sourceMapUrl = json.sourceMapUrl;
bundle._numPrependedModules = json.numPrependedModules;
bundle._numRequireCalls = json.numRequireCalls;

Object.freeze(bundle._modules);
Object.seal(bundle._modules);
Expand Down
10 changes: 9 additions & 1 deletion packager/react-packager/src/Bundler/__tests__/Bundler-test.js
Original file line number Diff line number Diff line change
Expand Up @@ -130,7 +130,10 @@ describe('Bundler', function() {
});

wrapModule.mockImpl(function(response, module, code) {
return Promise.resolve('lol ' + code + ' lol');
return module.getName().then(name => ({
name,
code: 'lol ' + code + ' lol'
}));
});

sizeOf.mockImpl(function(path, cb) {
Expand Down Expand Up @@ -160,13 +163,15 @@ describe('Bundler', function() {
sourceMapUrl: 'source_map_url',
}).then(function(p) {
expect(p.addModule.mock.calls[0][0]).toEqual({
name: 'foo',
code: 'lol transformed /root/foo.js lol',
map: 'sourcemap /root/foo.js',
sourceCode: 'source /root/foo.js',
sourcePath: '/root/foo.js',
});

expect(p.addModule.mock.calls[1][0]).toEqual({
name: 'bar',
code: 'lol transformed /root/bar.js lol',
map: 'sourcemap /root/bar.js',
sourceCode: 'source /root/bar.js',
Expand All @@ -183,6 +188,7 @@ describe('Bundler', function() {
};

expect(p.addModule.mock.calls[2][0]).toEqual({
name: 'image!img',
code: 'lol module.exports = ' +
JSON.stringify(imgModule_DEPRECATED) +
'; lol',
Expand Down Expand Up @@ -212,6 +218,7 @@ describe('Bundler', function() {
};

expect(p.addModule.mock.calls[3][0]).toEqual({
name: 'new_image.png',
code: 'lol module.exports = require("AssetRegistry").registerAsset(' +
JSON.stringify(imgModule) +
'); lol',
Expand All @@ -224,6 +231,7 @@ describe('Bundler', function() {
});

expect(p.addModule.mock.calls[4][0]).toEqual({
name: 'package/file.json',
code: 'lol module.exports = {"json":true}; lol',
sourceCode: 'module.exports = {"json":true};',
sourcePath: '/root/file.json',
Expand Down
10 changes: 7 additions & 3 deletions packager/react-packager/src/Bundler/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -140,14 +140,15 @@ class Bundler {
sourceMapUrl,
dev: isDev,
platform,
unbundle: isUnbundle,
}) {
// Const cannot have the same name as the method (babel/babel#2834)
const bbundle = new Bundle(sourceMapUrl);
const findEventId = Activity.startEvent('find dependencies');
let transformEventId;

const moduleSystem = this._resolver.getModuleSystemDependencies(
{ dev: isDev, platform }
{ dev: isDev, platform, isUnbundle }
);

return this.getDependencies(entryFile, isDev, platform).then((response) => {
Expand All @@ -168,6 +169,8 @@ class Bundler {
}

bbundle.setMainModuleId(response.mainModuleId);
bbundle.setNumPrependedModules(
response.numPrependedDependencies + moduleSystem.length);
return Promise.all(
dependencies.map(
module => this._transformModule(
Expand Down Expand Up @@ -317,8 +320,9 @@ class Bundler {
module,
transformed.code
).then(
code => new ModuleTransport({
code: code,
({code, name}) => new ModuleTransport({
code,
name,
map: transformed.map,
sourceCode: transformed.sourceCode,
sourcePath: transformed.sourcePath,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ class ResolutionResponse {
this.asyncDependencies = [];
this.mainModuleId = null;
this.mocks = null;
this.numPrependedDependencies = 0;
this._mappings = Object.create(null);
this._finalized = false;
}
Expand Down Expand Up @@ -50,6 +51,7 @@ class ResolutionResponse {
prependDependency(module) {
this._assertNotFinalized();
this.dependencies.unshift(module);
this.numPrependedDependencies += 1;
}

pushAsyncDependency(dependency) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -627,7 +627,8 @@ describe('Resolver', function() {
createModule('test module', ['x', 'y']),
code
).then(processedCode => {
expect(processedCode).toEqual([
expect(processedCode.name).toEqual('test module');
expect(processedCode.code).toEqual([
'__d(\'test module\',function(global, require,' +
' module, exports) { ' +
// single line import
Expand Down
Loading

0 comments on commit cc4a5d3

Please sign in to comment.