Updates from Tue 5 May

This commit is contained in:
Alex Kotliarskyi 2015-05-05 14:15:51 -07:00
commit 3e5308274e
8 changed files with 326 additions and 88 deletions

View File

@ -35,25 +35,34 @@
function getNativeLogFunction(level) { function getNativeLogFunction(level) {
return function() { return function() {
var str = Array.prototype.map.call(arguments, function(arg) { var str = Array.prototype.map.call(arguments, function(arg) {
if (arg == null) { var ret;
return arg === null ? 'null' : 'undefined'; var type = typeof arg;
} else if (typeof arg === 'string') { if (arg === null) {
return '"' + arg + '"'; ret = 'null';
} else if (arg === undefined) {
ret = 'undefined';
} else if (type === 'string') {
ret = '"' + arg + '"';
} else if (type === 'function') {
try {
ret = arg.toString();
} catch (e) {
ret = '[function unknown]';
}
} else { } else {
// Perform a try catch, just in case the object has a circular // Perform a try catch, just in case the object has a circular
// reference or stringify throws for some other reason. // reference or stringify throws for some other reason.
try { try {
return JSON.stringify(arg); ret = JSON.stringify(arg);
} catch (e) { } catch (e) {
if (typeof arg.toString === 'function') { if (typeof arg.toString === 'function') {
try { try {
return arg.toString(); ret = arg.toString();
} catch (E) { } catch (E) {}
return 'unknown';
}
} }
} }
} }
return ret || '["' + type + '" failed to stringify]';
}).join(', '); }).join(', ');
global.nativeLoggingHook(str, level); global.nativeLoggingHook(str, level);
}; };

View File

@ -11,6 +11,7 @@
jest jest
.dontMock('worker-farm') .dontMock('worker-farm')
.dontMock('os') .dontMock('os')
.dontMock('../../lib/ModuleTransport')
.dontMock('../index'); .dontMock('../index');
var OPTIONS = { var OPTIONS = {
@ -37,7 +38,7 @@ describe('Transformer', function() {
pit('should loadFileAndTransform', function() { pit('should loadFileAndTransform', function() {
workers.mockImpl(function(data, callback) { workers.mockImpl(function(data, callback) {
callback(null, { code: 'transformed' }); callback(null, { code: 'transformed', map: 'sourceMap' });
}); });
require('fs').readFile.mockImpl(function(file, callback) { require('fs').readFile.mockImpl(function(file, callback) {
callback(null, 'content'); callback(null, 'content');
@ -47,6 +48,7 @@ describe('Transformer', function() {
.then(function(data) { .then(function(data) {
expect(data).toEqual({ expect(data).toEqual({
code: 'transformed', code: 'transformed',
map: 'sourceMap',
sourcePath: 'file', sourcePath: 'file',
sourceCode: 'content' sourceCode: 'content'
}); });

View File

@ -14,6 +14,7 @@ var Cache = require('./Cache');
var workerFarm = require('worker-farm'); var workerFarm = require('worker-farm');
var declareOpts = require('../lib/declareOpts'); var declareOpts = require('../lib/declareOpts');
var util = require('util'); var util = require('util');
var ModuleTransport = require('../lib/ModuleTransport');
var readFile = Promise.promisify(fs.readFile); var readFile = Promise.promisify(fs.readFile);
@ -100,11 +101,12 @@ Transformer.prototype.loadFileAndTransform = function(filePath) {
throw formatError(res.error, filePath, sourceCode); throw formatError(res.error, filePath, sourceCode);
} }
return { return new ModuleTransport({
code: res.code, code: res.code,
map: res.map,
sourcePath: filePath, sourcePath: filePath,
sourceCode: sourceCode sourceCode: sourceCode,
}; });
} }
); );
}); });

View File

@ -11,6 +11,7 @@
var _ = require('underscore'); var _ = require('underscore');
var base64VLQ = require('./base64-vlq'); var base64VLQ = require('./base64-vlq');
var UglifyJS = require('uglify-js'); var UglifyJS = require('uglify-js');
var ModuleTransport = require('../lib/ModuleTransport');
module.exports = Package; module.exports = Package;
@ -19,22 +20,25 @@ function Package(sourceMapUrl) {
this._modules = []; this._modules = [];
this._assets = []; this._assets = [];
this._sourceMapUrl = sourceMapUrl; this._sourceMapUrl = sourceMapUrl;
this._shouldCombineSourceMaps = false;
} }
Package.prototype.setMainModuleId = function(moduleId) { Package.prototype.setMainModuleId = function(moduleId) {
this._mainModuleId = moduleId; this._mainModuleId = moduleId;
}; };
Package.prototype.addModule = function( Package.prototype.addModule = function(module) {
transformedCode, if (!(module instanceof ModuleTransport)) {
sourceCode, throw new Error('Expeceted a ModuleTransport object');
sourcePath }
) {
this._modules.push({ // If we get a map from the transformer we'll switch to a mode
transformedCode: transformedCode, // were we're combining the source maps as opposed to
sourceCode: sourceCode, if (!this._shouldCombineSourceMaps && module.map != null) {
sourcePath: sourcePath this._shouldCombineSourceMaps = true;
}); }
this._modules.push(module);
}; };
Package.prototype.addAsset = function(asset) { Package.prototype.addAsset = function(asset) {
@ -45,11 +49,12 @@ Package.prototype.finalize = function(options) {
options = options || {}; options = options || {};
if (options.runMainModule) { if (options.runMainModule) {
var runCode = ';require("' + this._mainModuleId + '");'; var runCode = ';require("' + this._mainModuleId + '");';
this.addModule( this.addModule(new ModuleTransport({
runCode, code: runCode,
runCode, virtual: true,
'RunMainModule.js' sourceCode: runCode,
); sourcePath: 'RunMainModule.js'
}));
} }
Object.freeze(this._modules); Object.freeze(this._modules);
@ -67,7 +72,7 @@ Package.prototype._assertFinalized = function() {
Package.prototype._getSource = function() { Package.prototype._getSource = function() {
if (this._source == null) { if (this._source == null) {
this._source = _.pluck(this._modules, 'transformedCode').join('\n'); this._source = _.pluck(this._modules, 'code').join('\n');
} }
return this._source; return this._source;
}; };
@ -136,10 +141,50 @@ Package.prototype.getMinifiedSourceAndMap = function() {
} }
}; };
/**
* I found a neat trick in the sourcemap spec that makes it easy
* to concat sourcemaps. The `sections` field allows us to combine
* the sourcemap easily by adding an offset. Tested on chrome.
* Seems like it's not yet in Firefox but that should be fine for
* now.
*/
Package.prototype._getCombinedSourceMaps = function(options) {
var result = {
version: 3,
file: 'bundle.js',
sections: [],
};
var line = 0;
this._modules.forEach(function(module) {
var map = module.map;
if (module.virtual) {
map = generateSourceMapForVirtualModule(module);
}
if (options.excludeSource) {
map = _.extend({}, map, {sourcesContent: []});
}
result.sections.push({
offset: { line: line, column: 0 },
map: map,
});
line += module.code.split('\n').length;
});
return result;
};
Package.prototype.getSourceMap = function(options) { Package.prototype.getSourceMap = function(options) {
this._assertFinalized(); this._assertFinalized();
options = options || {}; options = options || {};
if (this._shouldCombineSourceMaps) {
return this._getCombinedSourceMaps(options);
}
var mappings = this._getMappings(); var mappings = this._getMappings();
var map = { var map = {
file: 'bundle.js', file: 'bundle.js',
@ -168,13 +213,14 @@ Package.prototype._getMappings = function() {
// except for the lineno mappinp: curLineno - prevLineno = 1; Which is C. // except for the lineno mappinp: curLineno - prevLineno = 1; Which is C.
var line = 'AACA'; var line = 'AACA';
var moduleLines = Object.create(null);
var mappings = ''; var mappings = '';
for (var i = 0; i < modules.length; i++) { for (var i = 0; i < modules.length; i++) {
var module = modules[i]; var module = modules[i];
var transformedCode = module.transformedCode; var code = module.code;
var lastCharNewLine = false; var lastCharNewLine = false;
module.lines = 0; moduleLines[module.sourcePath] = 0;
for (var t = 0; t < transformedCode.length; t++) { for (var t = 0; t < code.length; t++) {
if (t === 0 && i === 0) { if (t === 0 && i === 0) {
mappings += firstLine; mappings += firstLine;
} else if (t === 0) { } else if (t === 0) {
@ -183,13 +229,15 @@ Package.prototype._getMappings = function() {
// This is the only place were we actually don't know the mapping ahead // This is the only place were we actually don't know the mapping ahead
// of time. When it's a new module (and not the first) the lineno // of time. When it's a new module (and not the first) the lineno
// mapping is 0 (current) - number of lines in prev module. // mapping is 0 (current) - number of lines in prev module.
mappings += base64VLQ.encode(0 - modules[i - 1].lines); mappings += base64VLQ.encode(
0 - moduleLines[modules[i - 1].sourcePath]
);
mappings += 'A'; mappings += 'A';
} else if (lastCharNewLine) { } else if (lastCharNewLine) {
module.lines++; moduleLines[module.sourcePath]++;
mappings += line; mappings += line;
} }
lastCharNewLine = transformedCode[t] === '\n'; lastCharNewLine = code[t] === '\n';
if (lastCharNewLine) { if (lastCharNewLine) {
mappings += ';'; mappings += ';';
} }
@ -218,7 +266,25 @@ Package.prototype.getDebugInfo = function() {
this._modules.map(function(m) { this._modules.map(function(m) {
return '<div> <h4> Path: </h4>' + m.sourcePath + '<br/> <h4> Source: </h4>' + return '<div> <h4> Path: </h4>' + m.sourcePath + '<br/> <h4> Source: </h4>' +
'<code><pre class="collapsed" onclick="this.classList.remove(\'collapsed\')">' + '<code><pre class="collapsed" onclick="this.classList.remove(\'collapsed\')">' +
_.escape(m.transformedCode) + '</pre></code></div>'; _.escape(m.code) + '</pre></code></div>';
}).join('\n'), }).join('\n'),
].join('\n'); ].join('\n');
}; };
function generateSourceMapForVirtualModule(module) {
// All lines map 1-to-1
var mappings = 'AAAA;';
for (var i = 1; i < module.code.split('\n').length; i++) {
mappings += 'AACA;';
}
return {
version: 3,
sources: [ module.sourcePath ],
names: [],
mappings: mappings,
file: module.sourcePath,
sourcesContent: [ module.code ],
};
}

View File

@ -13,11 +13,13 @@ jest.autoMockOff();
var SourceMapGenerator = require('source-map').SourceMapGenerator; var SourceMapGenerator = require('source-map').SourceMapGenerator;
describe('Package', function() { describe('Package', function() {
var ModuleTransport;
var Package; var Package;
var ppackage; var ppackage;
beforeEach(function() { beforeEach(function() {
Package = require('../Package'); Package = require('../Package');
ModuleTransport = require('../../lib/ModuleTransport');
ppackage = new Package('test_url'); ppackage = new Package('test_url');
ppackage.getSourceMap = jest.genMockFn().mockImpl(function() { ppackage.getSourceMap = jest.genMockFn().mockImpl(function() {
return 'test-source-map'; return 'test-source-map';
@ -26,8 +28,17 @@ describe('Package', function() {
describe('source package', function() { describe('source package', function() {
it('should create a package and get the source', function() { it('should create a package and get the source', function() {
ppackage.addModule('transformed foo;', 'source foo', 'foo path'); ppackage.addModule(new ModuleTransport({
ppackage.addModule('transformed bar;', 'source bar', 'bar path'); code: 'transformed foo;',
sourceCode: 'source foo',
sourcePath: 'foo path',
}));
ppackage.addModule(new ModuleTransport({
code: 'transformed bar;',
sourceCode: 'source bar',
sourcePath: 'bar path',
}));
ppackage.finalize({}); ppackage.finalize({});
expect(ppackage.getSource()).toBe([ expect(ppackage.getSource()).toBe([
'transformed foo;', 'transformed foo;',
@ -37,8 +48,18 @@ describe('Package', function() {
}); });
it('should create a package and add run module code', function() { it('should create a package and add run module code', function() {
ppackage.addModule('transformed foo;', 'source foo', 'foo path'); ppackage.addModule(new ModuleTransport({
ppackage.addModule('transformed bar;', 'source bar', 'bar path'); code: 'transformed foo;',
sourceCode: 'source foo',
sourcePath: 'foo path'
}));
ppackage.addModule(new ModuleTransport({
code: 'transformed bar;',
sourceCode: 'source bar',
sourcePath: 'bar path'
}));
ppackage.setMainModuleId('foo'); ppackage.setMainModuleId('foo');
ppackage.finalize({runMainModule: true}); ppackage.finalize({runMainModule: true});
expect(ppackage.getSource()).toBe([ expect(ppackage.getSource()).toBe([
@ -59,7 +80,11 @@ describe('Package', function() {
return minified; return minified;
}; };
ppackage.addModule('transformed foo;', 'source foo', 'foo path'); ppackage.addModule(new ModuleTransport({
code: 'transformed foo;',
sourceCode: 'source foo',
sourcePath: 'foo path'
}));
ppackage.finalize(); ppackage.finalize();
expect(ppackage.getMinifiedSourceAndMap()).toBe(minified); expect(ppackage.getMinifiedSourceAndMap()).toBe(minified);
}); });
@ -68,13 +93,104 @@ describe('Package', function() {
describe('sourcemap package', function() { describe('sourcemap package', function() {
it('should create sourcemap', function() { it('should create sourcemap', function() {
var p = new Package('test_url'); var p = new Package('test_url');
p.addModule('transformed foo;\n', 'source foo', 'foo path'); p.addModule(new ModuleTransport({
p.addModule('transformed bar;\n', 'source bar', 'bar path'); code: [
'transformed foo',
'transformed foo',
'transformed foo',
].join('\n'),
sourceCode: [
'source foo',
'source foo',
'source foo',
].join('\n'),
sourcePath: 'foo path',
}));
p.addModule(new ModuleTransport({
code: [
'transformed bar',
'transformed bar',
'transformed bar',
].join('\n'),
sourceCode: [
'source bar',
'source bar',
'source bar',
].join('\n'),
sourcePath: 'bar path',
}));
p.setMainModuleId('foo'); p.setMainModuleId('foo');
p.finalize({runMainModule: true}); p.finalize({runMainModule: true});
var s = p.getSourceMap(); var s = p.getSourceMap();
expect(s).toEqual(genSourceMap(p._modules)); expect(s).toEqual(genSourceMap(p._modules));
}); });
it('should combine sourcemaps', function() {
var p = new Package('test_url');
p.addModule(new ModuleTransport({
code: 'transformed foo;\n',
map: {name: 'sourcemap foo'},
sourceCode: 'source foo',
sourcePath: 'foo path'
}));
p.addModule(new ModuleTransport({
code: 'transformed foo;\n',
map: {name: 'sourcemap bar'},
sourceCode: 'source foo',
sourcePath: 'foo path'
}));
p.addModule(new ModuleTransport({
code: 'image module;\nimage module;',
virtual: true,
sourceCode: 'image module;\nimage module;',
sourcePath: 'image.png',
}));
p.setMainModuleId('foo');
p.finalize({runMainModule: true});
var s = p.getSourceMap();
expect(s).toEqual({
file: 'bundle.js',
version: 3,
sections: [
{ offset: { line: 0, column: 0 }, map: { name: 'sourcemap foo' } },
{ offset: { line: 2, column: 0 }, map: { name: 'sourcemap bar' } },
{
offset: {
column: 0,
line: 4
},
map: {
file: 'image.png',
mappings: 'AAAA;AACA;',
names: {},
sources: [ 'image.png' ],
sourcesContent: ['image module;\nimage module;'],
version: 3,
}
},
{
offset: {
column: 0,
line: 6
},
map: {
file: 'RunMainModule.js',
mappings: 'AAAA;',
names: {},
sources: [ 'RunMainModule.js' ],
sourcesContent: [';require("foo");'],
version: 3,
}
}
],
});
});
}); });
describe('getAssets()', function() { describe('getAssets()', function() {
@ -95,7 +211,7 @@ describe('Package', function() {
var packageLineNo = 0; var packageLineNo = 0;
for (var i = 0; i < modules.length; i++) { for (var i = 0; i < modules.length; i++) {
var module = modules[i]; var module = modules[i];
var transformedCode = module.transformedCode; var transformedCode = module.code;
var sourcePath = module.sourcePath; var sourcePath = module.sourcePath;
var sourceCode = module.sourceCode; var sourceCode = module.sourceCode;
var transformedLineCount = 0; var transformedLineCount = 0;

View File

@ -13,6 +13,7 @@ jest
.dontMock('path') .dontMock('path')
.dontMock('os') .dontMock('os')
.dontMock('underscore') .dontMock('underscore')
.dontMock('../../lib/ModuleTransport')
.setMock('uglify-js') .setMock('uglify-js')
.dontMock('../'); .dontMock('../');
@ -93,6 +94,7 @@ describe('Packager', function() {
.mockImpl(function(path) { .mockImpl(function(path) {
return Promise.resolve({ return Promise.resolve({
code: 'transformed ' + path, code: 'transformed ' + path,
map: 'sourcemap ' + path,
sourceCode: 'source ' + path, sourceCode: 'source ' + path,
sourcePath: path sourcePath: path
}); });
@ -117,16 +119,19 @@ describe('Packager', function() {
return packager.package('/root/foo.js', true, 'source_map_url') return packager.package('/root/foo.js', true, 'source_map_url')
.then(function(p) { .then(function(p) {
expect(p.addModule.mock.calls[0]).toEqual([ expect(p.addModule.mock.calls[0][0]).toEqual({
'lol transformed /root/foo.js lol', code: 'lol transformed /root/foo.js lol',
'source /root/foo.js', map: 'sourcemap /root/foo.js',
'/root/foo.js' sourceCode: 'source /root/foo.js',
]); sourcePath: '/root/foo.js',
expect(p.addModule.mock.calls[1]).toEqual([ });
'lol transformed /root/bar.js lol',
'source /root/bar.js', expect(p.addModule.mock.calls[1][0]).toEqual({
'/root/bar.js' code: 'lol transformed /root/bar.js lol',
]); map: 'sourcemap /root/bar.js',
sourceCode: 'source /root/bar.js',
sourcePath: '/root/bar.js'
});
var imgModule_DEPRECATED = { var imgModule_DEPRECATED = {
__packager_asset: true, __packager_asset: true,
@ -138,15 +143,15 @@ describe('Packager', function() {
deprecated: true, deprecated: true,
}; };
expect(p.addModule.mock.calls[2]).toEqual([ expect(p.addModule.mock.calls[2][0]).toEqual({
'lol module.exports = ' + code: 'lol module.exports = ' +
JSON.stringify(imgModule_DEPRECATED) + JSON.stringify(imgModule_DEPRECATED) +
'; lol', '; lol',
'module.exports = ' + sourceCode: 'module.exports = ' +
JSON.stringify(imgModule_DEPRECATED) + JSON.stringify(imgModule_DEPRECATED) +
';', ';',
'/root/img/img.png' sourcePath: '/root/img/img.png'
]); });
var imgModule = { var imgModule = {
__packager_asset: true, __packager_asset: true,
@ -160,21 +165,21 @@ describe('Packager', function() {
type: 'png', type: 'png',
}; };
expect(p.addModule.mock.calls[3]).toEqual([ expect(p.addModule.mock.calls[3][0]).toEqual({
'lol module.exports = ' + code: 'lol module.exports = ' +
JSON.stringify(imgModule) + JSON.stringify(imgModule) +
'; lol', '; lol',
'module.exports = ' + sourceCode: 'module.exports = ' +
JSON.stringify(imgModule) + JSON.stringify(imgModule) +
';', ';',
'/root/img/new_image.png' sourcePath: '/root/img/new_image.png'
]); });
expect(p.addModule.mock.calls[4]).toEqual([ expect(p.addModule.mock.calls[4][0]).toEqual({
'lol module.exports = {"json":true}; lol', code: 'lol module.exports = {"json":true}; lol',
'module.exports = {"json":true};', sourceCode: 'module.exports = {"json":true};',
'/root/file.json' sourcePath: '/root/file.json'
]); });
expect(p.finalize.mock.calls[0]).toEqual([ expect(p.finalize.mock.calls[0]).toEqual([
{runMainModule: true} {runMainModule: true}
@ -189,5 +194,4 @@ describe('Packager', function() {
]); ]);
}); });
}); });
}); });

View File

@ -14,9 +14,9 @@ var path = require('path');
var Promise = require('bluebird'); var Promise = require('bluebird');
var Transformer = require('../JSTransformer'); var Transformer = require('../JSTransformer');
var DependencyResolver = require('../DependencyResolver'); var DependencyResolver = require('../DependencyResolver');
var _ = require('underscore');
var Package = require('./Package'); var Package = require('./Package');
var Activity = require('../Activity'); var Activity = require('../Activity');
var ModuleTransport = require('../lib/ModuleTransport');
var declareOpts = require('../lib/declareOpts'); var declareOpts = require('../lib/declareOpts');
var imageSize = require('image-size'); var imageSize = require('image-size');
@ -125,12 +125,8 @@ Packager.prototype.package = function(main, runModule, sourceMapUrl, isDev) {
.then(function(transformedModules) { .then(function(transformedModules) {
Activity.endEvent(transformEventId); Activity.endEvent(transformEventId);
transformedModules.forEach(function(transformed) { transformedModules.forEach(function(moduleTransport) {
ppackage.addModule( ppackage.addModule(moduleTransport);
transformed.code,
transformed.sourceCode,
transformed.sourcePath
);
}); });
ppackage.finalize({ runMainModule: runModule }); ppackage.finalize({ runMainModule: runModule });
@ -163,11 +159,13 @@ Packager.prototype._transformModule = function(ppackage, module) {
var resolver = this._resolver; var resolver = this._resolver;
return transform.then(function(transformed) { return transform.then(function(transformed) {
return _.extend( var code = resolver.wrapModule(module, transformed.code);
{}, return new ModuleTransport({
transformed, code: code,
{code: resolver.wrapModule(module, transformed.code)} map: transformed.map,
); sourceCode: transformed.sourceCode,
sourcePath: transformed.sourcePath,
});
}); });
}; };
@ -191,11 +189,12 @@ Packager.prototype.generateAssetModule_DEPRECATED = function(ppackage, module) {
var code = 'module.exports = ' + JSON.stringify(img) + ';'; var code = 'module.exports = ' + JSON.stringify(img) + ';';
return { return new ModuleTransport({
code: code, code: code,
sourceCode: code, sourceCode: code,
sourcePath: module.path, sourcePath: module.path,
}; virtual: true,
});
}); });
}; };
@ -222,11 +221,12 @@ Packager.prototype.generateAssetModule = function(ppackage, module) {
var code = 'module.exports = ' + JSON.stringify(img) + ';'; var code = 'module.exports = ' + JSON.stringify(img) + ';';
return { return new ModuleTransport({
code: code, code: code,
sourceCode: code, sourceCode: code,
sourcePath: module.path, sourcePath: module.path,
}; virtual: true,
});
}); });
}; };
@ -234,11 +234,12 @@ function generateJSONModule(module) {
return readFile(module.path).then(function(data) { return readFile(module.path).then(function(data) {
var code = 'module.exports = ' + data.toString('utf8') + ';'; var code = 'module.exports = ' + data.toString('utf8') + ';';
return { return new ModuleTransport({
code: code, code: code,
sourceCode: code, sourceCode: code,
sourcePath: module.path, sourcePath: module.path,
}; virtual: true,
});
}); });
} }

View File

@ -0,0 +1,38 @@
/**
* 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';
function ModuleTransport(data) {
assertExists(data, 'code');
this.code = data.code;
assertExists(data, 'sourceCode');
this.sourceCode = data.sourceCode;
assertExists(data, 'sourcePath');
this.sourcePath = data.sourcePath;
this.virtual = data.virtual;
if (this.virtual && data.map) {
throw new Error('Virtual modules cannot have source maps');
}
this.map = data.map;
Object.freeze(this);
}
module.exports = ModuleTransport;
function assertExists(obj, field) {
if (obj[field] == null) {
throw new Error('Modules must have `' + field + '`');
}
}