mirror of https://github.com/status-im/metro.git
Updates from Tue 25 Aug
This commit is contained in:
commit
39681a6649
|
@ -23,7 +23,7 @@ window.localStorage.removeItem('sessionID');
|
|||
window.onbeforeunload = function() {
|
||||
if (sessionID) {
|
||||
return 'If you reload this page, it is going to break the debugging session. ' +
|
||||
'You should ⌘+R in the iOS simulator to reload.';
|
||||
'You should press ⌘R in simulator to reload.';
|
||||
}
|
||||
};
|
||||
|
||||
|
@ -76,10 +76,10 @@ function connectToDebuggerProxy() {
|
|||
|
||||
ws.onopen = function() {
|
||||
if (sessionID) {
|
||||
setStatus('Debugger session #' + sessionID + ' active');
|
||||
setStatus('Debugger session #' + sessionID + ' active.');
|
||||
ws.send(JSON.stringify({replyID: parseInt(sessionID, 10)}));
|
||||
} else {
|
||||
setStatus('Waiting, press ⌘R in simulator to reload and connect');
|
||||
setStatus('Waiting, press <span class="shortcut">⌘R</span> in simulator to reload and connect.');
|
||||
}
|
||||
};
|
||||
|
||||
|
@ -126,7 +126,8 @@ function loadScript(src, callback) {
|
|||
font-weight: 200;
|
||||
}
|
||||
.shortcut {
|
||||
font-family: monospace;
|
||||
font-family: "Monaco", monospace;
|
||||
font-size: medium;
|
||||
color: #eee;
|
||||
background-color: #333;
|
||||
padding: 4px;
|
||||
|
@ -175,10 +176,10 @@ function loadScript(src, callback) {
|
|||
</div>
|
||||
<div class="content">
|
||||
<p>
|
||||
React Native JS code runs inside this Chrome tab
|
||||
React Native JS code runs inside this Chrome tab.
|
||||
</p>
|
||||
<p>Press <span class="shortcut">⌘⌥J</span> to open Developer Tools. Enable <a href="http://stackoverflow.com/a/17324511/232122" target="_blank">Pause On Caught Exceptions</a> for a better debugging experience.</p>
|
||||
<p>Status: <span id="status">Loading</span></p>
|
||||
<p>Status: <span id="status">Loading...</span></p>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
|
|
|
@ -10,17 +10,23 @@
|
|||
|
||||
var chalk = require('chalk');
|
||||
var exec = require('child_process').exec;
|
||||
var Activity = require('./react-packager/src/Activity');
|
||||
var url = require('url');
|
||||
var Activity = require('./react-packager').Activity;
|
||||
|
||||
var hasWarned = {};
|
||||
var DISABLE_FLOW_CHECK = true; // temporarily disable while we figure out versioning issues.
|
||||
|
||||
function getFlowTypeCheckMiddleware(options) {
|
||||
return function(req, res, next) {
|
||||
var isBundle = req.url.indexOf('.bundle') !== -1;
|
||||
if (DISABLE_FLOW_CHECK || options.skipflow || !isBundle) {
|
||||
var reqObj = url.parse(req.url);
|
||||
var isFlowCheck = (reqObj.path.match(/^\/flow\//));
|
||||
|
||||
if (!isFlowCheck) {
|
||||
return next();
|
||||
}
|
||||
if (options.skipflow) {
|
||||
_endSkipFlow(res);
|
||||
return;
|
||||
}
|
||||
if (options.flowroot || options.projectRoots.length === 1) {
|
||||
var flowroot = options.flowroot || options.projectRoots[0];
|
||||
} else {
|
||||
|
@ -28,7 +34,8 @@ function getFlowTypeCheckMiddleware(options) {
|
|||
hasWarned.noRoot = true;
|
||||
console.warn('flow: No suitable root');
|
||||
}
|
||||
return next();
|
||||
_endFlowBad(res);
|
||||
return;
|
||||
}
|
||||
exec('command -v flow >/dev/null 2>&1', function(error, stdout) {
|
||||
if (error) {
|
||||
|
@ -37,7 +44,8 @@ function getFlowTypeCheckMiddleware(options) {
|
|||
console.warn(chalk.yellow('flow: Skipping because not installed. Install with ' +
|
||||
'`brew install flow`.'));
|
||||
}
|
||||
return next();
|
||||
_endFlowBad(res);
|
||||
return;
|
||||
} else {
|
||||
return doFlowTypecheck(res, flowroot, next);
|
||||
}
|
||||
|
@ -51,7 +59,8 @@ function doFlowTypecheck(res, flowroot, next) {
|
|||
exec(flowCmd, function(flowError, stdout, stderr) {
|
||||
Activity.endEvent(eventId);
|
||||
if (!flowError) {
|
||||
return next();
|
||||
_endFlowOk(res);
|
||||
return;
|
||||
} else {
|
||||
try {
|
||||
var flowResponse = JSON.parse(stdout);
|
||||
|
@ -73,16 +82,13 @@ function doFlowTypecheck(res, flowroot, next) {
|
|||
errorNum++;
|
||||
});
|
||||
var error = {
|
||||
status: 500,
|
||||
status: 200,
|
||||
message: 'Flow found type errors. If you think these are wrong, ' +
|
||||
'make sure your flow bin and .flowconfig are up to date, or ' +
|
||||
'disable with --skipflow.',
|
||||
type: 'FlowError',
|
||||
errors: errors,
|
||||
};
|
||||
console.error(chalk.yellow('flow: Error running command `' + flowCmd +
|
||||
'`:\n' + JSON.stringify(error))
|
||||
);
|
||||
res.writeHead(error.status, {
|
||||
'Content-Type': 'application/json; charset=UTF-8',
|
||||
});
|
||||
|
@ -93,6 +99,13 @@ function doFlowTypecheck(res, flowroot, next) {
|
|||
hasWarned.noConfig = true;
|
||||
console.warn(chalk.yellow('flow: ' + stderr));
|
||||
}
|
||||
_endFlowBad(res);
|
||||
} else if (flowError.code === 3) {
|
||||
if (!hasWarned.timeout) {
|
||||
hasWarned.timeout = true;
|
||||
console.warn(chalk.yellow('flow: ' + stdout));
|
||||
}
|
||||
_endSkipFlow(res);
|
||||
} else {
|
||||
if (!hasWarned.brokenFlow) {
|
||||
hasWarned.brokenFlow = true;
|
||||
|
@ -101,11 +114,37 @@ function doFlowTypecheck(res, flowroot, next) {
|
|||
'`.\n' + 'stderr: `' + stderr + '`'
|
||||
));
|
||||
}
|
||||
_endFlowBad(res);
|
||||
}
|
||||
return next();
|
||||
return;
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function _endRes(res, message, code, silentError) {
|
||||
res.writeHead(code, {
|
||||
'Content-Type': 'application/json; charset=UTF-8',
|
||||
});
|
||||
res.end(JSON.stringify({
|
||||
message: message,
|
||||
errors: [],
|
||||
silentError: silentError,
|
||||
}));
|
||||
}
|
||||
|
||||
function _endFlowOk(res) {
|
||||
_endRes(res, 'No Flow Error', '200', true);
|
||||
}
|
||||
|
||||
function _endFlowBad(res) {
|
||||
// we want to show that flow failed
|
||||
// status 200 is need for the fetch to not be rejected
|
||||
_endRes(res, 'Flow failed to run! Please look at the console for more details.', '200', false);
|
||||
}
|
||||
|
||||
function _endSkipFlow(res) {
|
||||
_endRes(res, 'Flow was skipped, check the server options', '200', true);
|
||||
}
|
||||
|
||||
module.exports = getFlowTypeCheckMiddleware;
|
||||
|
|
|
@ -1,35 +0,0 @@
|
|||
/**
|
||||
* 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.
|
||||
*/
|
||||
|
||||
var EventEmitter = require('events').EventEmitter;
|
||||
var servers = {};
|
||||
exports.createServer = function(listener) {
|
||||
var server = {
|
||||
_listener: listener,
|
||||
|
||||
socket: new EventEmitter(),
|
||||
|
||||
listen: function(path) {
|
||||
listener(this.socket);
|
||||
servers[path] = this;
|
||||
}
|
||||
};
|
||||
|
||||
server.socket.setEncoding = function() {};
|
||||
server.socket.write = function(data) {
|
||||
this.emit('data', data);
|
||||
};
|
||||
|
||||
return server;
|
||||
};
|
||||
|
||||
exports.connect = function(options) {
|
||||
var server = servers[options.path || options.port];
|
||||
return server.socket;
|
||||
};
|
|
@ -1,12 +0,0 @@
|
|||
/**
|
||||
* 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.
|
||||
*
|
||||
* @providesModule bar
|
||||
*/
|
||||
|
||||
module.exports = setInterval;
|
|
@ -1,10 +0,0 @@
|
|||
{
|
||||
"port": 3000,
|
||||
"devPort": 3001,
|
||||
"publicDir": "./public",
|
||||
"rootPath": "../example_project",
|
||||
"moduleOptions": {
|
||||
"format": "haste",
|
||||
"main": "index.js"
|
||||
}
|
||||
}
|
|
@ -1,30 +0,0 @@
|
|||
/**
|
||||
* 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.
|
||||
*
|
||||
* @providesModule foo
|
||||
*/
|
||||
|
||||
|
||||
var bar = require('bar');
|
||||
|
||||
class Logger {
|
||||
log() {
|
||||
console.log('youll have to change me lol');
|
||||
}
|
||||
}
|
||||
|
||||
class SecretLogger extends Logger {
|
||||
log(secret) {
|
||||
console.log('logging ', secret);
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = (secret) => {
|
||||
if (secret !== 'secret') throw new Error('wrong secret');
|
||||
bar(new SecretLogger().log.bind(SecretLogger, secret), 400);
|
||||
};
|
|
@ -1,16 +0,0 @@
|
|||
/**
|
||||
* 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.
|
||||
*
|
||||
* @providesModule index
|
||||
*/
|
||||
|
||||
require('main');
|
||||
require('code');
|
||||
|
||||
var foo = require('foo');
|
||||
foo('secret');
|
|
@ -1,53 +0,0 @@
|
|||
/**
|
||||
* 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.
|
||||
*
|
||||
* @providesModule Channel
|
||||
*/
|
||||
|
||||
var XHR = require('XHR');
|
||||
|
||||
/**
|
||||
* Client implementation of a server-push channel.
|
||||
*
|
||||
* @see Channel.js for full documentation
|
||||
*/
|
||||
var channel = null, at = null, delay = 0;
|
||||
var Channel = {};
|
||||
|
||||
Channel.connect = function() {
|
||||
var url = '/pull';
|
||||
if (channel) {
|
||||
url += '?channel=' + channel + '&at=' + at;
|
||||
}
|
||||
XHR.get(url, function(err, xhr) {
|
||||
if (err) {
|
||||
delay = Math.min(Math.max(1000, delay * 2), 30000);
|
||||
} else {
|
||||
var res = xhr.responseText;
|
||||
res = JSON.parse(res);
|
||||
|
||||
delay = 0;
|
||||
|
||||
// Cache channel state
|
||||
channel = res.channel;
|
||||
at = res.at;
|
||||
|
||||
var messages = res.messages;
|
||||
messages.forEach(function(message) {
|
||||
var ev = document.createEvent('CustomEvent');
|
||||
ev.initCustomEvent(message.event, true, true, message.detail);
|
||||
window.dispatchEvent(ev);
|
||||
});
|
||||
}
|
||||
|
||||
// Reconnect
|
||||
setTimeout(Channel.connect, delay);
|
||||
});
|
||||
};
|
||||
|
||||
module.exports = Channel;
|
|
@ -1,29 +0,0 @@
|
|||
/**
|
||||
* 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.
|
||||
*
|
||||
* @providesModule XHR
|
||||
*/
|
||||
|
||||
function request(method, url, callback) {
|
||||
var xhr = new XMLHttpRequest();
|
||||
xhr.open(method, url);
|
||||
xhr.onreadystatechange = function() {
|
||||
if (xhr.readyState === 4) {
|
||||
if (xhr.status === 200) {
|
||||
callback(null, xhr);
|
||||
} else {
|
||||
callback(new Error('status = ' + xhr.status, xhr));
|
||||
}
|
||||
}
|
||||
};
|
||||
xhr.send();
|
||||
}
|
||||
|
||||
exports.get = function(url, callback) {
|
||||
request('GET', url, callback);
|
||||
};
|
|
@ -1,58 +0,0 @@
|
|||
/**
|
||||
* 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.
|
||||
*
|
||||
* @providesModule code
|
||||
*/
|
||||
var XHR = require('XHR');
|
||||
|
||||
var $ = function(sel) {return document.querySelector(sel);};
|
||||
|
||||
function getListItems(files) {
|
||||
var items = [];
|
||||
files.forEach(function(file) {
|
||||
var displayName = file.name + (file.type == 1 ? '/' : '');
|
||||
items.push(
|
||||
React.DOM.li({
|
||||
className: 'type' + file.type,
|
||||
key: file.ino
|
||||
}, displayName)
|
||||
);
|
||||
if (file.type === 1) {
|
||||
items.push(getListItems(file.nodes));
|
||||
}
|
||||
});
|
||||
|
||||
return React.DOM.ol(null, items);
|
||||
}
|
||||
|
||||
var FileList = React.createClass({
|
||||
getInitialState: function() {
|
||||
return {files: []};
|
||||
},
|
||||
|
||||
componentDidMount: function() {
|
||||
XHR.get(
|
||||
this.props.source,
|
||||
function(err, xhr) {
|
||||
if (err) {throw err;}
|
||||
|
||||
var files = JSON.parse(xhr.responseText);
|
||||
this.setState({files: files});
|
||||
}.bind(this)
|
||||
);
|
||||
},
|
||||
|
||||
render: function() {
|
||||
return getListItems(this.state.files);
|
||||
}
|
||||
});
|
||||
|
||||
window.addEventListener('load', function() {
|
||||
React.render(React.createElement(FileList, {source: '/files'}),
|
||||
$('#code'));
|
||||
});
|
|
@ -1,64 +0,0 @@
|
|||
/**
|
||||
* 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.
|
||||
*
|
||||
* @providesModule main
|
||||
*/
|
||||
var Channel = require('Channel');
|
||||
|
||||
function toArray(arr) {return Array.prototype.slice.apply(arr);}
|
||||
function $(sel) {return document.querySelector(sel);}
|
||||
function $$(sel) {return toArray(document.querySelectorAll(sel));}
|
||||
|
||||
window.addEventListener('load', function() {
|
||||
function channelLog() {
|
||||
var args = Array.prototype.slice.apply(arguments);
|
||||
var ts = new Date();
|
||||
var el = document.createElement('li');
|
||||
args.unshift(ts.getHours() + ':' +
|
||||
('0' + ts.getMinutes()).substr(0,2) + ':' +
|
||||
('0' + ts.getSeconds()).substr(0,2));
|
||||
el.className = 'console-entry';
|
||||
el.innerHTML = args.join(' ');
|
||||
$('#console').appendChild(el);
|
||||
el.scrollIntoView();
|
||||
}
|
||||
|
||||
global.addEventListener('ChannelInit', function(event) {
|
||||
$('#console').innerHTML = '';
|
||||
channelLog(event.type);
|
||||
});
|
||||
|
||||
global.addEventListener('ChannelLog', function(event) {
|
||||
channelLog.apply(null, event.detail);
|
||||
});
|
||||
|
||||
// Tab pane support
|
||||
function showTab(paneId) {
|
||||
paneId = paneId.replace(/\W/g, '');
|
||||
if (paneId) {
|
||||
$$('#nav-panes > div').forEach(function(pane) {
|
||||
pane.classList.toggle('active', pane.id === paneId);
|
||||
});
|
||||
$$('#nav-tabs li').forEach(function(tab) {
|
||||
tab.classList.toggle('active',
|
||||
tab.getAttribute('data-pane') === paneId);
|
||||
});
|
||||
global.history.replaceState(null, null, '#' + paneId);
|
||||
}
|
||||
}
|
||||
|
||||
$('#nav-tabs').onclick = function(e) {
|
||||
showTab(e.target.getAttribute('data-pane'));
|
||||
};
|
||||
|
||||
// Show current pane
|
||||
showTab(location.hash);
|
||||
|
||||
// Connect to server-push channel
|
||||
Channel.connect();
|
||||
});
|
|
@ -1,104 +0,0 @@
|
|||
/**
|
||||
* 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.
|
||||
*/
|
||||
|
||||
|
||||
html {
|
||||
font-family: sans-serif;
|
||||
}
|
||||
body {
|
||||
margin-right: 200px
|
||||
}
|
||||
|
||||
#nav-tabs {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
position: absolute;
|
||||
top: 0px;
|
||||
left: 0px;
|
||||
right: 0px;
|
||||
background-color: #eee;
|
||||
border-bottom: solid 1px black;
|
||||
font-size: 10pt;
|
||||
font-weight: bold;
|
||||
vertical-align: bottom;
|
||||
line-height: 20px;
|
||||
height: 29px;
|
||||
}
|
||||
#nav-tabs li {
|
||||
padding: 0 10px;
|
||||
margin: 0;
|
||||
border-bottom-width: 0;
|
||||
display:inline-block;
|
||||
cursor: pointer;
|
||||
line-height: 29px;
|
||||
}
|
||||
#nav-tabs li:first-child {
|
||||
color: #666;
|
||||
}
|
||||
#nav-tabs li.active {
|
||||
background-color: #fff;
|
||||
}
|
||||
|
||||
#nav-panes {
|
||||
position: absolute;
|
||||
top: 30px;
|
||||
left: 0px;
|
||||
right: 0px;
|
||||
bottom: 0px;
|
||||
scroll: auto;
|
||||
overflow: auto;
|
||||
background-color: #fff;
|
||||
}
|
||||
|
||||
#nav-panes .pane {
|
||||
display: none;
|
||||
}
|
||||
#nav-panes .active {
|
||||
display: block;
|
||||
}
|
||||
|
||||
.pane {
|
||||
padding: 10px;
|
||||
}
|
||||
|
||||
#console {
|
||||
padding-left: 5px;
|
||||
}
|
||||
#console li {
|
||||
font-size: 10pt;
|
||||
font-family: monospace;
|
||||
white-space: nowrap;
|
||||
margin: 0;
|
||||
list-style: none;
|
||||
}
|
||||
|
||||
#code > ol {
|
||||
font-size: 10pt;
|
||||
font-family: monospace;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
cursor: pointer;
|
||||
}
|
||||
#code ol ol {
|
||||
margin-left: 1em;
|
||||
padding-left: 1em;
|
||||
border-left: dashed 1px #ddd;
|
||||
}
|
||||
#code li {
|
||||
color: #000;
|
||||
font-weight: normal;
|
||||
list-style: none;
|
||||
line-height: 1.2em;
|
||||
}
|
||||
#code .type1 {
|
||||
color: #009;
|
||||
}
|
||||
#code .type2 {
|
||||
color: #909;
|
||||
}
|
|
@ -1,38 +0,0 @@
|
|||
<!DOCTYPE html>
|
||||
<!--
|
||||
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.
|
||||
-->
|
||||
<html>
|
||||
<head>
|
||||
<link rel="stylesheet" type="text/css" href="css/index.css">
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<ul id="nav-tabs">
|
||||
<li data-pane="main">JS App Server</li>
|
||||
<li data-pane="console">Console</li>
|
||||
<li data-pane="code">Code</li>
|
||||
<li data-pane="activity">Activity</li>
|
||||
</ul>
|
||||
|
||||
<div id="nav-panes">
|
||||
<div id="main" class="pane">
|
||||
<p>Welcome to the react packager project.</p>
|
||||
<a href="http://localhost:3000/resource?path=index.js">Get example index.js package</a>
|
||||
</div>
|
||||
|
||||
<div id="console" class="pane"></div>
|
||||
<div id="code" class="pane"></div>
|
||||
<div id="activity" class="pane">react packager tasks in progress</div>
|
||||
</div>
|
||||
|
||||
<script src="http://fb.me/react-0.12.0.js"></script>
|
||||
<script src="http://localhost:3000/resource?path=index.js"></script>
|
||||
<script type="text/javascript">require('index');</script>
|
||||
</body>
|
||||
</html>
|
|
@ -16,24 +16,31 @@ useGracefulFs();
|
|||
|
||||
var Activity = require('./src/Activity');
|
||||
var Server = require('./src/Server');
|
||||
var SocketInterface = require('./src/SocketInterface');
|
||||
|
||||
exports.middleware = function(options) {
|
||||
var server = new Server(options);
|
||||
return server.processRequest.bind(server);
|
||||
};
|
||||
|
||||
exports.buildPackage = function(options, packageOptions) {
|
||||
exports.Activity = Activity;
|
||||
|
||||
// Renamed "package" to "bundle". But maintain backwards
|
||||
// compat.
|
||||
exports.buildPackage =
|
||||
exports.buildBundle = function(options, bundleOptions) {
|
||||
var server = createServer(options);
|
||||
return server.buildPackage(packageOptions)
|
||||
return server.buildBundle(bundleOptions)
|
||||
.then(function(p) {
|
||||
server.end();
|
||||
return p;
|
||||
});
|
||||
};
|
||||
|
||||
exports.buildPackageFromUrl = function(options, reqUrl) {
|
||||
exports.buildPackageFromUrl =
|
||||
exports.buildBundleFromUrl = function(options, reqUrl) {
|
||||
var server = createServer(options);
|
||||
return server.buildPackageFromUrl(reqUrl)
|
||||
return server.buildBundleFromUrl(reqUrl)
|
||||
.then(function(p) {
|
||||
server.end();
|
||||
return p;
|
||||
|
@ -49,17 +56,44 @@ exports.getDependencies = function(options, main) {
|
|||
});
|
||||
};
|
||||
|
||||
exports.createClientFor = function(options) {
|
||||
return SocketInterface.getOrCreateSocketFor(options);
|
||||
};
|
||||
|
||||
process.on('message', function(m) {
|
||||
if (m && m.type && m.type === 'createSocketServer') {
|
||||
console.log('server got ipc message', m);
|
||||
var options = m.data.options;
|
||||
|
||||
// regexp doesn't naturally serialize to json.
|
||||
options.blacklistRE = new RegExp(options.blacklistRE.source);
|
||||
|
||||
SocketInterface.createSocketServer(
|
||||
m.data.sockPath,
|
||||
m.data.options
|
||||
).then(
|
||||
function() {
|
||||
console.log('succesfully created server', m);
|
||||
process.send({ type: 'createdServer' });
|
||||
},
|
||||
function(error) {
|
||||
console.log('error creating server', error.code);
|
||||
if (error.code === 'EADDRINUSE') {
|
||||
// Server already listening, this may happen if multiple
|
||||
// clients where started in quick succussion (buck).
|
||||
process.send({ type: 'createdServer' });
|
||||
} else {
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
).done();
|
||||
}
|
||||
});
|
||||
|
||||
function useGracefulFs() {
|
||||
var fs = require('fs');
|
||||
var gracefulFs = require('graceful-fs');
|
||||
|
||||
// A bit sneaky but it's not straightforward to update all the
|
||||
// modules we depend on.
|
||||
Object.keys(fs).forEach(function(method) {
|
||||
if (typeof fs[method] === 'function' && gracefulFs[method]) {
|
||||
fs[method] = gracefulFs[method];
|
||||
}
|
||||
});
|
||||
gracefulFs.gracefulify(fs);
|
||||
}
|
||||
|
||||
function createServer(options) {
|
||||
|
|
|
@ -13,64 +13,63 @@ jest.setMock('chalk', {
|
|||
dim: function(s) { return s; },
|
||||
});
|
||||
|
||||
describe('Activity', function() {
|
||||
var Activity;
|
||||
describe('Activity', () => {
|
||||
const origConsoleLog = console.log;
|
||||
let Activity;
|
||||
|
||||
var origConsoleLog = console.log;
|
||||
|
||||
beforeEach(function() {
|
||||
beforeEach(() => {
|
||||
console.log = jest.genMockFn();
|
||||
Activity = require('../');
|
||||
jest.runOnlyPendingTimers();
|
||||
});
|
||||
|
||||
afterEach(function() {
|
||||
afterEach(() => {
|
||||
console.log = origConsoleLog;
|
||||
});
|
||||
|
||||
describe('startEvent', function() {
|
||||
it('writes a START event out to the console', function() {
|
||||
var EVENT_NAME = 'EVENT_NAME';
|
||||
var DATA = {someData: 42};
|
||||
describe('startEvent', () => {
|
||||
it('writes a START event out to the console', () => {
|
||||
const EVENT_NAME = 'EVENT_NAME';
|
||||
const DATA = {someData: 42};
|
||||
|
||||
Activity.startEvent(EVENT_NAME, DATA);
|
||||
jest.runOnlyPendingTimers();
|
||||
|
||||
expect(console.log.mock.calls.length).toBe(1);
|
||||
var consoleMsg = console.log.mock.calls[0][0];
|
||||
const consoleMsg = console.log.mock.calls[0][0];
|
||||
expect(consoleMsg).toContain('START');
|
||||
expect(consoleMsg).toContain(EVENT_NAME);
|
||||
expect(consoleMsg).toContain(JSON.stringify(DATA));
|
||||
});
|
||||
});
|
||||
|
||||
describe('endEvent', function() {
|
||||
it('writes an END event out to the console', function() {
|
||||
var EVENT_NAME = 'EVENT_NAME';
|
||||
var DATA = {someData: 42};
|
||||
describe('endEvent', () => {
|
||||
it('writes an END event out to the console', () => {
|
||||
const EVENT_NAME = 'EVENT_NAME';
|
||||
const DATA = {someData: 42};
|
||||
|
||||
var eventID = Activity.startEvent(EVENT_NAME, DATA);
|
||||
const eventID = Activity.startEvent(EVENT_NAME, DATA);
|
||||
Activity.endEvent(eventID);
|
||||
jest.runOnlyPendingTimers();
|
||||
|
||||
expect(console.log.mock.calls.length).toBe(2);
|
||||
var consoleMsg = console.log.mock.calls[1][0];
|
||||
const consoleMsg = console.log.mock.calls[1][0];
|
||||
expect(consoleMsg).toContain('END');
|
||||
expect(consoleMsg).toContain(EVENT_NAME);
|
||||
expect(consoleMsg).toContain(JSON.stringify(DATA));
|
||||
});
|
||||
|
||||
it('throws when called with an invalid eventId', function() {
|
||||
expect(function() {
|
||||
Activity.endEvent(42);
|
||||
}).toThrow('event(42) is not a valid event id!');
|
||||
it('throws when called with an invalid eventId', () => {
|
||||
expect(() => Activity.endEvent(42)).toThrow(
|
||||
'event(42) is not a valid event id!',
|
||||
);
|
||||
});
|
||||
|
||||
it('throws when called with an expired eventId', function() {
|
||||
var eid = Activity.startEvent('', '');
|
||||
it('throws when called with an expired eventId', () => {
|
||||
const eid = Activity.startEvent('', '');
|
||||
Activity.endEvent(eid);
|
||||
|
||||
expect(function() {
|
||||
expect(() => {
|
||||
Activity.endEvent(eid);
|
||||
}).toThrow('event(3) has already ended!');
|
||||
|
||||
|
@ -78,17 +77,16 @@ describe('Activity', function() {
|
|||
});
|
||||
});
|
||||
|
||||
describe('signal', function() {
|
||||
it('writes a SIGNAL event out to the console', function() {
|
||||
|
||||
var EVENT_NAME = 'EVENT_NAME';
|
||||
var DATA = {someData: 42};
|
||||
describe('signal', () => {
|
||||
it('writes a SIGNAL event out to the console', () => {
|
||||
const EVENT_NAME = 'EVENT_NAME';
|
||||
const DATA = {someData: 42};
|
||||
|
||||
Activity.signal(EVENT_NAME, DATA);
|
||||
jest.runOnlyPendingTimers();
|
||||
|
||||
expect(console.log.mock.calls.length).toBe(1);
|
||||
var consoleMsg = console.log.mock.calls[0][0];
|
||||
const consoleMsg = console.log.mock.calls[0][0];
|
||||
expect(consoleMsg).toContain(EVENT_NAME);
|
||||
expect(consoleMsg).toContain(JSON.stringify(DATA));
|
||||
});
|
||||
|
|
|
@ -8,19 +8,22 @@
|
|||
*/
|
||||
'use strict';
|
||||
|
||||
var chalk = require('chalk');
|
||||
const chalk = require('chalk');
|
||||
const events = require('events');
|
||||
|
||||
var COLLECTION_PERIOD = 1000;
|
||||
const COLLECTION_PERIOD = 1000;
|
||||
|
||||
var _endedEvents = Object.create(null);
|
||||
var _eventStarts = Object.create(null);
|
||||
var _queuedActions = [];
|
||||
var _scheduledCollectionTimer = null;
|
||||
var _uuid = 1;
|
||||
var _enabled = true;
|
||||
const _endedEvents = Object.create(null);
|
||||
const _eventStarts = Object.create(null);
|
||||
const _queuedActions = [];
|
||||
const _eventEmitter = new events.EventEmitter();
|
||||
|
||||
let _scheduledCollectionTimer = null;
|
||||
let _uuid = 1;
|
||||
let _enabled = true;
|
||||
|
||||
function endEvent(eventId) {
|
||||
var eventEndTime = Date.now();
|
||||
const eventEndTime = Date.now();
|
||||
|
||||
if (!_eventStarts[eventId]) {
|
||||
_throw('event(' + eventId + ') is not a valid event id!');
|
||||
|
@ -39,7 +42,7 @@ function endEvent(eventId) {
|
|||
}
|
||||
|
||||
function signal(eventName, data) {
|
||||
var signalTime = Date.now();
|
||||
const signalTime = Date.now();
|
||||
|
||||
if (eventName == null) {
|
||||
_throw('No event name specified');
|
||||
|
@ -58,7 +61,7 @@ function signal(eventName, data) {
|
|||
}
|
||||
|
||||
function startEvent(eventName, data) {
|
||||
var eventStartTime = Date.now();
|
||||
const eventStartTime = Date.now();
|
||||
|
||||
if (eventName == null) {
|
||||
_throw('No event name specified');
|
||||
|
@ -68,8 +71,8 @@ function startEvent(eventName, data) {
|
|||
data = null;
|
||||
}
|
||||
|
||||
var eventId = _uuid++;
|
||||
var action = {
|
||||
const eventId = _uuid++;
|
||||
const action = {
|
||||
action: 'startEvent',
|
||||
data: data,
|
||||
eventId: eventId,
|
||||
|
@ -88,7 +91,7 @@ function disable() {
|
|||
|
||||
function _runCollection() {
|
||||
/* jshint -W084 */
|
||||
var action;
|
||||
let action;
|
||||
while ((action = _queuedActions.shift())) {
|
||||
_writeAction(action);
|
||||
}
|
||||
|
@ -98,6 +101,7 @@ function _runCollection() {
|
|||
|
||||
function _scheduleAction(action) {
|
||||
_queuedActions.push(action);
|
||||
_eventEmitter.emit(action.action, action);
|
||||
|
||||
if (_scheduledCollectionTimer === null) {
|
||||
_scheduledCollectionTimer = setTimeout(_runCollection, COLLECTION_PERIOD);
|
||||
|
@ -114,10 +118,10 @@ function _scheduleAction(action) {
|
|||
* won't be adding such a non-trivial optimization anytime soon)
|
||||
*/
|
||||
function _throw(msg) {
|
||||
var err = new Error(msg);
|
||||
const err = new Error(msg);
|
||||
|
||||
// Strip off the call to _throw()
|
||||
var stack = err.stack.split('\n');
|
||||
const stack = err.stack.split('\n');
|
||||
stack.splice(1, 1);
|
||||
err.stack = stack.join('\n');
|
||||
|
||||
|
@ -129,8 +133,8 @@ function _writeAction(action) {
|
|||
return;
|
||||
}
|
||||
|
||||
var data = action.data ? ': ' + JSON.stringify(action.data) : '';
|
||||
var fmtTime = new Date(action.tstamp).toLocaleTimeString();
|
||||
const data = action.data ? ': ' + JSON.stringify(action.data) : '';
|
||||
const fmtTime = new Date(action.tstamp).toLocaleTimeString();
|
||||
|
||||
switch (action.action) {
|
||||
case 'startEvent':
|
||||
|
@ -142,8 +146,8 @@ function _writeAction(action) {
|
|||
break;
|
||||
|
||||
case 'endEvent':
|
||||
var startAction = _eventStarts[action.eventId];
|
||||
var startData = startAction.data ? ': ' + JSON.stringify(startAction.data) : '';
|
||||
const startAction = _eventStarts[action.eventId];
|
||||
const startData = startAction.data ? ': ' + JSON.stringify(startAction.data) : '';
|
||||
console.log(chalk.dim(
|
||||
'[' + fmtTime + '] ' +
|
||||
'<END> ' + startAction.eventName +
|
||||
|
@ -171,3 +175,4 @@ exports.endEvent = endEvent;
|
|||
exports.signal = signal;
|
||||
exports.startEvent = startEvent;
|
||||
exports.disable = disable;
|
||||
exports.eventEmitter = _eventEmitter;
|
||||
|
|
|
@ -8,22 +8,22 @@ jest
|
|||
.mock('crypto')
|
||||
.mock('fs');
|
||||
|
||||
var Promise = require('promise');
|
||||
const Promise = require('promise');
|
||||
|
||||
describe('AssetServer', function() {
|
||||
var AssetServer;
|
||||
var crypto;
|
||||
var fs;
|
||||
describe('AssetServer', () => {
|
||||
let AssetServer;
|
||||
let crypto;
|
||||
let fs;
|
||||
|
||||
beforeEach(function() {
|
||||
beforeEach(() => {
|
||||
AssetServer = require('../');
|
||||
crypto = require('crypto');
|
||||
fs = require('fs');
|
||||
});
|
||||
|
||||
describe('assetServer.get', function() {
|
||||
pit('should work for the simple case', function() {
|
||||
var server = new AssetServer({
|
||||
describe('assetServer.get', () => {
|
||||
pit('should work for the simple case', () => {
|
||||
const server = new AssetServer({
|
||||
projectRoots: ['/root'],
|
||||
assetExts: ['png'],
|
||||
});
|
||||
|
@ -40,15 +40,15 @@ describe('AssetServer', function() {
|
|||
return Promise.all([
|
||||
server.get('imgs/b.png'),
|
||||
server.get('imgs/b@1x.png'),
|
||||
]).then(function(resp) {
|
||||
resp.forEach(function(data) {
|
||||
expect(data).toBe('b image');
|
||||
});
|
||||
});
|
||||
]).then(resp =>
|
||||
resp.forEach(data =>
|
||||
expect(data).toBe('b image')
|
||||
)
|
||||
);
|
||||
});
|
||||
|
||||
pit('should work for the simple case with jpg', function() {
|
||||
var server = new AssetServer({
|
||||
pit('should work for the simple case with jpg', () => {
|
||||
const server = new AssetServer({
|
||||
projectRoots: ['/root'],
|
||||
assetExts: ['png', 'jpg'],
|
||||
});
|
||||
|
@ -65,16 +65,16 @@ describe('AssetServer', function() {
|
|||
return Promise.all([
|
||||
server.get('imgs/b.jpg'),
|
||||
server.get('imgs/b.png'),
|
||||
]).then(function(data) {
|
||||
]).then(data =>
|
||||
expect(data).toEqual([
|
||||
'jpeg image',
|
||||
'png image',
|
||||
]);
|
||||
});
|
||||
])
|
||||
);
|
||||
});
|
||||
|
||||
pit('should pick the bigger one', function() {
|
||||
var server = new AssetServer({
|
||||
pit('should pick the bigger one', () => {
|
||||
const server = new AssetServer({
|
||||
projectRoots: ['/root'],
|
||||
assetExts: ['png'],
|
||||
});
|
||||
|
@ -90,13 +90,13 @@ describe('AssetServer', function() {
|
|||
}
|
||||
});
|
||||
|
||||
return server.get('imgs/b@3x.png').then(function(data) {
|
||||
expect(data).toBe('b4 image');
|
||||
});
|
||||
return server.get('imgs/b@3x.png').then(data =>
|
||||
expect(data).toBe('b4 image')
|
||||
);
|
||||
});
|
||||
|
||||
pit('should support multiple project roots', function() {
|
||||
var server = new AssetServer({
|
||||
pit('should support multiple project roots', () => {
|
||||
const server = new AssetServer({
|
||||
projectRoots: ['/root', '/root2'],
|
||||
assetExts: ['png'],
|
||||
});
|
||||
|
@ -116,27 +116,23 @@ describe('AssetServer', function() {
|
|||
},
|
||||
});
|
||||
|
||||
return server.get('newImages/imgs/b.png').then(function(data) {
|
||||
expect(data).toBe('b1 image');
|
||||
});
|
||||
return server.get('newImages/imgs/b.png').then(data =>
|
||||
expect(data).toBe('b1 image')
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe('assetSerer.getAssetData', function() {
|
||||
pit('should get assetData', function() {
|
||||
var hash = {
|
||||
describe('assetSerer.getAssetData', () => {
|
||||
pit('should get assetData', () => {
|
||||
const hash = {
|
||||
update: jest.genMockFn(),
|
||||
digest: jest.genMockFn(),
|
||||
};
|
||||
|
||||
hash.digest.mockImpl(function() {
|
||||
return 'wow such hash';
|
||||
});
|
||||
crypto.createHash.mockImpl(function() {
|
||||
return hash;
|
||||
});
|
||||
hash.digest.mockImpl(() => 'wow such hash');
|
||||
crypto.createHash.mockImpl(() => hash);
|
||||
|
||||
var server = new AssetServer({
|
||||
const server = new AssetServer({
|
||||
projectRoots: ['/root'],
|
||||
assetExts: ['png'],
|
||||
});
|
||||
|
@ -152,7 +148,7 @@ describe('AssetServer', function() {
|
|||
}
|
||||
});
|
||||
|
||||
return server.getAssetData('imgs/b.png').then(function(data) {
|
||||
return server.getAssetData('imgs/b.png').then(data => {
|
||||
expect(hash.update.mock.calls.length).toBe(4);
|
||||
expect(data).toEqual({
|
||||
type: 'png',
|
||||
|
@ -163,20 +159,16 @@ describe('AssetServer', function() {
|
|||
});
|
||||
});
|
||||
|
||||
pit('should get assetData for non-png images', function() {
|
||||
var hash = {
|
||||
pit('should get assetData for non-png images', () => {
|
||||
const hash = {
|
||||
update: jest.genMockFn(),
|
||||
digest: jest.genMockFn(),
|
||||
};
|
||||
|
||||
hash.digest.mockImpl(function() {
|
||||
return 'wow such hash';
|
||||
});
|
||||
crypto.createHash.mockImpl(function() {
|
||||
return hash;
|
||||
});
|
||||
hash.digest.mockImpl(() => 'wow such hash');
|
||||
crypto.createHash.mockImpl(() => hash);
|
||||
|
||||
var server = new AssetServer({
|
||||
const server = new AssetServer({
|
||||
projectRoots: ['/root'],
|
||||
assetExts: ['png', 'jpeg'],
|
||||
});
|
||||
|
@ -192,7 +184,7 @@ describe('AssetServer', function() {
|
|||
}
|
||||
});
|
||||
|
||||
return server.getAssetData('imgs/b.jpg').then(function(data) {
|
||||
return server.getAssetData('imgs/b.jpg').then(data => {
|
||||
expect(hash.update.mock.calls.length).toBe(4);
|
||||
expect(data).toEqual({
|
||||
type: 'jpg',
|
||||
|
|
|
@ -8,20 +8,20 @@
|
|||
*/
|
||||
'use strict';
|
||||
|
||||
var declareOpts = require('../lib/declareOpts');
|
||||
var getAssetDataFromName = require('../lib/getAssetDataFromName');
|
||||
var path = require('path');
|
||||
var Promise = require('promise');
|
||||
var fs = require('fs');
|
||||
var crypto = require('crypto');
|
||||
const Promise = require('promise');
|
||||
|
||||
var stat = Promise.denodeify(fs.stat);
|
||||
var readDir = Promise.denodeify(fs.readdir);
|
||||
var readFile = Promise.denodeify(fs.readFile);
|
||||
const crypto = require('crypto');
|
||||
const declareOpts = require('../lib/declareOpts');
|
||||
const fs = require('fs');
|
||||
const getAssetDataFromName = require('../lib/getAssetDataFromName');
|
||||
const path = require('path');
|
||||
|
||||
module.exports = AssetServer;
|
||||
const stat = Promise.denodeify(fs.stat);
|
||||
const readDir = Promise.denodeify(fs.readdir);
|
||||
const readFile = Promise.denodeify(fs.readFile);
|
||||
|
||||
var validateOpts = declareOpts({
|
||||
|
||||
const validateOpts = declareOpts({
|
||||
projectRoots: {
|
||||
type: 'array',
|
||||
required: true,
|
||||
|
@ -32,135 +32,136 @@ var validateOpts = declareOpts({
|
|||
},
|
||||
});
|
||||
|
||||
function AssetServer(options) {
|
||||
var opts = validateOpts(options);
|
||||
this._roots = opts.projectRoots;
|
||||
this._assetExts = opts.assetExts;
|
||||
}
|
||||
class AssetServer {
|
||||
constructor(options) {
|
||||
const opts = validateOpts(options);
|
||||
this._roots = opts.projectRoots;
|
||||
this._assetExts = opts.assetExts;
|
||||
}
|
||||
|
||||
/**
|
||||
* Given a request for an image by path. That could contain a resolution
|
||||
* postfix, we need to find that image (or the closest one to it's resolution)
|
||||
* in one of the project roots:
|
||||
*
|
||||
* 1. We first parse the directory of the asset
|
||||
* 2. We check to find a matching directory in one of the project roots
|
||||
* 3. We then build a map of all assets and their scales in this directory
|
||||
* 4. Then pick the closest resolution (rounding up) to the requested one
|
||||
*/
|
||||
|
||||
AssetServer.prototype._getAssetRecord = function(assetPath) {
|
||||
var filename = path.basename(assetPath);
|
||||
|
||||
return findRoot(
|
||||
this._roots,
|
||||
path.dirname(assetPath)
|
||||
).then(function(dir) {
|
||||
return Promise.all([
|
||||
dir,
|
||||
readDir(dir),
|
||||
]);
|
||||
}).then(function(res) {
|
||||
var dir = res[0];
|
||||
var files = res[1];
|
||||
var assetData = getAssetDataFromName(filename);
|
||||
|
||||
var map = buildAssetMap(dir, files);
|
||||
var record = map[assetData.assetName];
|
||||
|
||||
if (!record) {
|
||||
throw new Error('Asset not found');
|
||||
}
|
||||
|
||||
return record;
|
||||
});
|
||||
};
|
||||
|
||||
AssetServer.prototype.get = function(assetPath) {
|
||||
var assetData = getAssetDataFromName(assetPath);
|
||||
return this._getAssetRecord(assetPath).then(function(record) {
|
||||
for (var i = 0; i < record.scales.length; i++) {
|
||||
if (record.scales[i] >= assetData.resolution) {
|
||||
return readFile(record.files[i]);
|
||||
get(assetPath) {
|
||||
const assetData = getAssetDataFromName(assetPath);
|
||||
return this._getAssetRecord(assetPath).then(record => {
|
||||
for (let i = 0; i < record.scales.length; i++) {
|
||||
if (record.scales[i] >= assetData.resolution) {
|
||||
return readFile(record.files[i]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return readFile(record.files[record.files.length - 1]);
|
||||
});
|
||||
};
|
||||
return readFile(record.files[record.files.length - 1]);
|
||||
});
|
||||
}
|
||||
|
||||
AssetServer.prototype.getAssetData = function(assetPath) {
|
||||
var nameData = getAssetDataFromName(assetPath);
|
||||
var data = {
|
||||
name: nameData.name,
|
||||
type: nameData.type,
|
||||
};
|
||||
getAssetData(assetPath) {
|
||||
const nameData = getAssetDataFromName(assetPath);
|
||||
const data = {
|
||||
name: nameData.name,
|
||||
type: nameData.type,
|
||||
};
|
||||
|
||||
return this._getAssetRecord(assetPath).then(function(record) {
|
||||
data.scales = record.scales;
|
||||
return this._getAssetRecord(assetPath).then(record => {
|
||||
data.scales = record.scales;
|
||||
|
||||
return Promise.all(
|
||||
record.files.map(function(file) {
|
||||
return stat(file);
|
||||
return Promise.all(
|
||||
record.files.map(file => stat(file))
|
||||
);
|
||||
}).then(stats => {
|
||||
const hash = crypto.createHash('md5');
|
||||
|
||||
stats.forEach(fstat =>
|
||||
hash.update(fstat.mtime.getTime().toString())
|
||||
);
|
||||
|
||||
data.hash = hash.digest('hex');
|
||||
return data;
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Given a request for an image by path. That could contain a resolution
|
||||
* postfix, we need to find that image (or the closest one to it's resolution)
|
||||
* in one of the project roots:
|
||||
*
|
||||
* 1. We first parse the directory of the asset
|
||||
* 2. We check to find a matching directory in one of the project roots
|
||||
* 3. We then build a map of all assets and their scales in this directory
|
||||
* 4. Then pick the closest resolution (rounding up) to the requested one
|
||||
*/
|
||||
_getAssetRecord(assetPath) {
|
||||
const filename = path.basename(assetPath);
|
||||
|
||||
return (
|
||||
this._findRoot(
|
||||
this._roots,
|
||||
path.dirname(assetPath)
|
||||
)
|
||||
.then(dir => Promise.all([
|
||||
dir,
|
||||
readDir(dir),
|
||||
]))
|
||||
.then(res => {
|
||||
const dir = res[0];
|
||||
const files = res[1];
|
||||
const assetData = getAssetDataFromName(filename);
|
||||
|
||||
const map = this._buildAssetMap(dir, files);
|
||||
const record = map[assetData.assetName];
|
||||
|
||||
if (!record) {
|
||||
throw new Error('Asset not found');
|
||||
}
|
||||
|
||||
return record;
|
||||
})
|
||||
);
|
||||
}).then(function(stats) {
|
||||
var hash = crypto.createHash('md5');
|
||||
}
|
||||
|
||||
stats.forEach(function(fstat) {
|
||||
hash.update(fstat.mtime.getTime().toString());
|
||||
});
|
||||
|
||||
data.hash = hash.digest('hex');
|
||||
return data;
|
||||
});
|
||||
};
|
||||
|
||||
function findRoot(roots, dir) {
|
||||
return Promise.all(
|
||||
roots.map(function(root) {
|
||||
var absPath = path.join(root, dir);
|
||||
return stat(absPath).then(function(fstat) {
|
||||
return {path: absPath, isDirectory: fstat.isDirectory()};
|
||||
}, function (err) {
|
||||
return {path: absPath, isDirectory: false};
|
||||
});
|
||||
})
|
||||
).then(
|
||||
function(stats) {
|
||||
for (var i = 0; i < stats.length; i++) {
|
||||
_findRoot(roots, dir) {
|
||||
return Promise.all(
|
||||
roots.map(root => {
|
||||
const absPath = path.join(root, dir);
|
||||
return stat(absPath).then(fstat => {
|
||||
return {path: absPath, isDirectory: fstat.isDirectory()};
|
||||
}, err => {
|
||||
return {path: absPath, isDirectory: false};
|
||||
});
|
||||
})
|
||||
).then(stats => {
|
||||
for (let i = 0; i < stats.length; i++) {
|
||||
if (stats[i].isDirectory) {
|
||||
return stats[i].path;
|
||||
}
|
||||
}
|
||||
throw new Error('Could not find any directories');
|
||||
}
|
||||
);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function buildAssetMap(dir, files) {
|
||||
var assets = files.map(getAssetDataFromName);
|
||||
var map = Object.create(null);
|
||||
assets.forEach(function(asset, i) {
|
||||
var file = files[i];
|
||||
var record = map[asset.assetName];
|
||||
if (!record) {
|
||||
record = map[asset.assetName] = {
|
||||
scales: [],
|
||||
files: [],
|
||||
};
|
||||
}
|
||||
|
||||
var insertIndex;
|
||||
var length = record.scales.length;
|
||||
for (insertIndex = 0; insertIndex < length; insertIndex++) {
|
||||
if (asset.resolution < record.scales[insertIndex]) {
|
||||
break;
|
||||
_buildAssetMap(dir, files) {
|
||||
const assets = files.map(getAssetDataFromName);
|
||||
const map = Object.create(null);
|
||||
assets.forEach(function(asset, i) {
|
||||
const file = files[i];
|
||||
let record = map[asset.assetName];
|
||||
if (!record) {
|
||||
record = map[asset.assetName] = {
|
||||
scales: [],
|
||||
files: [],
|
||||
};
|
||||
}
|
||||
}
|
||||
record.scales.splice(insertIndex, 0, asset.resolution);
|
||||
record.files.splice(insertIndex, 0, path.join(dir, file));
|
||||
});
|
||||
|
||||
return map;
|
||||
let insertIndex;
|
||||
const length = record.scales.length;
|
||||
for (insertIndex = 0; insertIndex < length; insertIndex++) {
|
||||
if (asset.resolution < record.scales[insertIndex]) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
record.scales.splice(insertIndex, 0, asset.resolution);
|
||||
record.files.splice(insertIndex, 0, path.join(dir, file));
|
||||
});
|
||||
|
||||
return map;
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = AssetServer;
|
||||
|
|
|
@ -0,0 +1,337 @@
|
|||
/**
|
||||
* 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 _ = require('underscore');
|
||||
const base64VLQ = require('./base64-vlq');
|
||||
const UglifyJS = require('uglify-js');
|
||||
const ModuleTransport = require('../lib/ModuleTransport');
|
||||
|
||||
const SOURCEMAPPING_URL = '\n\/\/@ sourceMappingURL=';
|
||||
|
||||
class Bundle {
|
||||
constructor(sourceMapUrl) {
|
||||
this._finalized = false;
|
||||
this._modules = [];
|
||||
this._assets = [];
|
||||
this._sourceMapUrl = sourceMapUrl;
|
||||
this._shouldCombineSourceMaps = false;
|
||||
}
|
||||
|
||||
setMainModuleId(moduleId) {
|
||||
this._mainModuleId = moduleId;
|
||||
}
|
||||
|
||||
addModule(module) {
|
||||
if (!(module instanceof ModuleTransport)) {
|
||||
throw new Error('Expeceted a ModuleTransport object');
|
||||
}
|
||||
|
||||
// If we get a map from the transformer we'll switch to a mode
|
||||
// were we're combining the source maps as opposed to
|
||||
if (!this._shouldCombineSourceMaps && module.map != null) {
|
||||
this._shouldCombineSourceMaps = true;
|
||||
}
|
||||
|
||||
this._modules.push(module);
|
||||
}
|
||||
|
||||
getModules() {
|
||||
return this._modules;
|
||||
}
|
||||
|
||||
addAsset(asset) {
|
||||
this._assets.push(asset);
|
||||
}
|
||||
|
||||
finalize(options) {
|
||||
options = options || {};
|
||||
if (options.runMainModule) {
|
||||
const runCode = ';require("' + this._mainModuleId + '");';
|
||||
this.addModule(new ModuleTransport({
|
||||
code: runCode,
|
||||
virtual: true,
|
||||
sourceCode: runCode,
|
||||
sourcePath: 'RunMainModule.js'
|
||||
}));
|
||||
}
|
||||
|
||||
Object.freeze(this._modules);
|
||||
Object.seal(this._modules);
|
||||
Object.freeze(this._assets);
|
||||
Object.seal(this._assets);
|
||||
this._finalized = true;
|
||||
}
|
||||
|
||||
_assertFinalized() {
|
||||
if (!this._finalized) {
|
||||
throw new Error('Bundle needs to be finalized before getting any source');
|
||||
}
|
||||
}
|
||||
|
||||
_getSource() {
|
||||
if (this._source == null) {
|
||||
this._source = _.pluck(this._modules, 'code').join('\n');
|
||||
}
|
||||
return this._source;
|
||||
}
|
||||
|
||||
_getInlineSourceMap() {
|
||||
if (this._inlineSourceMap == null) {
|
||||
const sourceMap = this.getSourceMap({excludeSource: true});
|
||||
/*eslint-env node*/
|
||||
const encoded = new Buffer(JSON.stringify(sourceMap)).toString('base64');
|
||||
this._inlineSourceMap = 'data:application/json;base64,' + encoded;
|
||||
}
|
||||
return this._inlineSourceMap;
|
||||
}
|
||||
|
||||
getSource(options) {
|
||||
this._assertFinalized();
|
||||
|
||||
options = options || {};
|
||||
|
||||
if (options.minify) {
|
||||
return this.getMinifiedSourceAndMap().code;
|
||||
}
|
||||
|
||||
let source = this._getSource();
|
||||
|
||||
if (options.inlineSourceMap) {
|
||||
source += SOURCEMAPPING_URL + this._getInlineSourceMap();
|
||||
} else if (this._sourceMapUrl) {
|
||||
source += SOURCEMAPPING_URL + this._sourceMapUrl;
|
||||
}
|
||||
|
||||
return source;
|
||||
}
|
||||
|
||||
getMinifiedSourceAndMap() {
|
||||
this._assertFinalized();
|
||||
|
||||
const source = this._getSource();
|
||||
try {
|
||||
return UglifyJS.minify(source, {
|
||||
fromString: true,
|
||||
outSourceMap: 'bundle.js',
|
||||
inSourceMap: this.getSourceMap(),
|
||||
});
|
||||
} catch(e) {
|
||||
// Sometimes, when somebody is using a new syntax feature that we
|
||||
// don't yet have transform for, the untransformed line is sent to
|
||||
// uglify, and it chokes on it. This code tries to print the line
|
||||
// and the module for easier debugging
|
||||
let errorMessage = 'Error while minifying JS\n';
|
||||
if (e.line) {
|
||||
errorMessage += 'Transformed code line: "' +
|
||||
source.split('\n')[e.line - 1] + '"\n';
|
||||
}
|
||||
if (e.pos) {
|
||||
let fromIndex = source.lastIndexOf('__d(\'', e.pos);
|
||||
if (fromIndex > -1) {
|
||||
fromIndex += '__d(\''.length;
|
||||
const toIndex = source.indexOf('\'', fromIndex);
|
||||
errorMessage += 'Module name (best guess): ' +
|
||||
source.substring(fromIndex, toIndex) + '\n';
|
||||
}
|
||||
}
|
||||
errorMessage += e.toString();
|
||||
throw new Error(errorMessage);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 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.
|
||||
*/
|
||||
_getCombinedSourceMaps(options) {
|
||||
const result = {
|
||||
version: 3,
|
||||
file: 'bundle.js',
|
||||
sections: [],
|
||||
};
|
||||
|
||||
let line = 0;
|
||||
this._modules.forEach(function(module) {
|
||||
let 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;
|
||||
}
|
||||
|
||||
getSourceMap(options) {
|
||||
this._assertFinalized();
|
||||
|
||||
options = options || {};
|
||||
|
||||
if (this._shouldCombineSourceMaps) {
|
||||
return this._getCombinedSourceMaps(options);
|
||||
}
|
||||
|
||||
const mappings = this._getMappings();
|
||||
const map = {
|
||||
file: 'bundle.js',
|
||||
sources: _.pluck(this._modules, 'sourcePath'),
|
||||
version: 3,
|
||||
names: [],
|
||||
mappings: mappings,
|
||||
sourcesContent: options.excludeSource
|
||||
? [] : _.pluck(this._modules, 'sourceCode')
|
||||
};
|
||||
return map;
|
||||
}
|
||||
|
||||
getAssets() {
|
||||
return this._assets;
|
||||
}
|
||||
|
||||
_getMappings() {
|
||||
const modules = this._modules;
|
||||
|
||||
// The first line mapping in our package is basically the base64vlq code for
|
||||
// zeros (A).
|
||||
const firstLine = 'AAAA';
|
||||
|
||||
// Most other lines in our mappings are all zeros (for module, column etc)
|
||||
// except for the lineno mappinp: curLineno - prevLineno = 1; Which is C.
|
||||
const line = 'AACA';
|
||||
|
||||
const moduleLines = Object.create(null);
|
||||
let mappings = '';
|
||||
for (let i = 0; i < modules.length; i++) {
|
||||
const module = modules[i];
|
||||
const code = module.code;
|
||||
let lastCharNewLine = false;
|
||||
moduleLines[module.sourcePath] = 0;
|
||||
for (let t = 0; t < code.length; t++) {
|
||||
if (t === 0 && i === 0) {
|
||||
mappings += firstLine;
|
||||
} else if (t === 0) {
|
||||
mappings += 'AC';
|
||||
|
||||
// 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
|
||||
// mapping is 0 (current) - number of lines in prev module.
|
||||
mappings += base64VLQ.encode(
|
||||
0 - moduleLines[modules[i - 1].sourcePath]
|
||||
);
|
||||
mappings += 'A';
|
||||
} else if (lastCharNewLine) {
|
||||
moduleLines[module.sourcePath]++;
|
||||
mappings += line;
|
||||
}
|
||||
lastCharNewLine = code[t] === '\n';
|
||||
if (lastCharNewLine) {
|
||||
mappings += ';';
|
||||
}
|
||||
}
|
||||
if (i !== modules.length - 1) {
|
||||
mappings += ';';
|
||||
}
|
||||
}
|
||||
return mappings;
|
||||
}
|
||||
|
||||
getJSModulePaths() {
|
||||
return this._modules.filter(function(module) {
|
||||
// Filter out non-js files. Like images etc.
|
||||
return !module.virtual;
|
||||
}).map(function(module) {
|
||||
return module.sourcePath;
|
||||
});
|
||||
}
|
||||
|
||||
getDebugInfo() {
|
||||
return [
|
||||
'<div><h3>Main Module:</h3> ' + this._mainModuleId + '</div>',
|
||||
'<style>',
|
||||
'pre.collapsed {',
|
||||
' height: 10px;',
|
||||
' width: 100px;',
|
||||
' display: block;',
|
||||
' text-overflow: ellipsis;',
|
||||
' overflow: hidden;',
|
||||
' cursor: pointer;',
|
||||
'}',
|
||||
'</style>',
|
||||
'<h3> Module paths and transformed code: </h3>',
|
||||
this._modules.map(function(m) {
|
||||
return '<div> <h4> Path: </h4>' + m.sourcePath + '<br/> <h4> Source: </h4>' +
|
||||
'<code><pre class="collapsed" onclick="this.classList.remove(\'collapsed\')">' +
|
||||
_.escape(m.code) + '</pre></code></div>';
|
||||
}).join('\n'),
|
||||
].join('\n');
|
||||
}
|
||||
|
||||
toJSON() {
|
||||
if (!this._finalized) {
|
||||
throw new Error('Cannot serialize bundle unless finalized');
|
||||
}
|
||||
|
||||
return {
|
||||
modules: this._modules,
|
||||
assets: this._assets,
|
||||
sourceMapUrl: this._sourceMapUrl,
|
||||
shouldCombineSourceMaps: this._shouldCombineSourceMaps,
|
||||
mainModuleId: this._mainModuleId,
|
||||
};
|
||||
}
|
||||
|
||||
static fromJSON(json) {
|
||||
const bundle = new Bundle(json.sourceMapUrl);
|
||||
bundle._mainModuleId = json.mainModuleId;
|
||||
bundle._assets = json.assets;
|
||||
bundle._modules = json.modules;
|
||||
bundle._sourceMapUrl = json.sourceMapUrl;
|
||||
|
||||
Object.freeze(bundle._modules);
|
||||
Object.seal(bundle._modules);
|
||||
Object.freeze(bundle._assets);
|
||||
Object.seal(bundle._assets);
|
||||
bundle._finalized = true;
|
||||
|
||||
return bundle;
|
||||
}
|
||||
}
|
||||
|
||||
function generateSourceMapForVirtualModule(module) {
|
||||
// All lines map 1-to-1
|
||||
let mappings = 'AAAA;';
|
||||
|
||||
for (let 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.sourceCode ],
|
||||
};
|
||||
}
|
||||
|
||||
module.exports = Bundle;
|
|
@ -12,35 +12,35 @@ jest.autoMockOff();
|
|||
|
||||
var SourceMapGenerator = require('source-map').SourceMapGenerator;
|
||||
|
||||
describe('Package', function() {
|
||||
describe('Bundle', function() {
|
||||
var ModuleTransport;
|
||||
var Package;
|
||||
var ppackage;
|
||||
var Bundle;
|
||||
var bundle;
|
||||
|
||||
beforeEach(function() {
|
||||
Package = require('../Package');
|
||||
Bundle = require('../Bundle');
|
||||
ModuleTransport = require('../../lib/ModuleTransport');
|
||||
ppackage = new Package('test_url');
|
||||
ppackage.getSourceMap = jest.genMockFn().mockImpl(function() {
|
||||
bundle = new Bundle('test_url');
|
||||
bundle.getSourceMap = jest.genMockFn().mockImpl(function() {
|
||||
return 'test-source-map';
|
||||
});
|
||||
});
|
||||
|
||||
describe('source package', function() {
|
||||
it('should create a package and get the source', function() {
|
||||
ppackage.addModule(new ModuleTransport({
|
||||
describe('source bundle', function() {
|
||||
it('should create a bundle and get the source', function() {
|
||||
bundle.addModule(new ModuleTransport({
|
||||
code: 'transformed foo;',
|
||||
sourceCode: 'source foo',
|
||||
sourcePath: 'foo path',
|
||||
}));
|
||||
ppackage.addModule(new ModuleTransport({
|
||||
bundle.addModule(new ModuleTransport({
|
||||
code: 'transformed bar;',
|
||||
sourceCode: 'source bar',
|
||||
sourcePath: 'bar path',
|
||||
}));
|
||||
|
||||
ppackage.finalize({});
|
||||
expect(ppackage.getSource()).toBe([
|
||||
bundle.finalize({});
|
||||
expect(bundle.getSource()).toBe([
|
||||
'transformed foo;',
|
||||
'transformed bar;',
|
||||
'\/\/@ sourceMappingURL=test_url'
|
||||
|
@ -48,7 +48,7 @@ describe('Package', function() {
|
|||
});
|
||||
|
||||
it('should be ok to leave out the source map url', function() {
|
||||
var p = new Package();
|
||||
var p = new Bundle();
|
||||
p.addModule(new ModuleTransport({
|
||||
code: 'transformed foo;',
|
||||
sourceCode: 'source foo',
|
||||
|
@ -67,22 +67,22 @@ describe('Package', function() {
|
|||
].join('\n'));
|
||||
});
|
||||
|
||||
it('should create a package and add run module code', function() {
|
||||
ppackage.addModule(new ModuleTransport({
|
||||
it('should create a bundle and add run module code', function() {
|
||||
bundle.addModule(new ModuleTransport({
|
||||
code: 'transformed foo;',
|
||||
sourceCode: 'source foo',
|
||||
sourcePath: 'foo path'
|
||||
}));
|
||||
|
||||
ppackage.addModule(new ModuleTransport({
|
||||
bundle.addModule(new ModuleTransport({
|
||||
code: 'transformed bar;',
|
||||
sourceCode: 'source bar',
|
||||
sourcePath: 'bar path'
|
||||
}));
|
||||
|
||||
ppackage.setMainModuleId('foo');
|
||||
ppackage.finalize({runMainModule: true});
|
||||
expect(ppackage.getSource()).toBe([
|
||||
bundle.setMainModuleId('foo');
|
||||
bundle.finalize({runMainModule: true});
|
||||
expect(bundle.getSource()).toBe([
|
||||
'transformed foo;',
|
||||
'transformed bar;',
|
||||
';require("foo");',
|
||||
|
@ -100,19 +100,19 @@ describe('Package', function() {
|
|||
return minified;
|
||||
};
|
||||
|
||||
ppackage.addModule(new ModuleTransport({
|
||||
bundle.addModule(new ModuleTransport({
|
||||
code: 'transformed foo;',
|
||||
sourceCode: 'source foo',
|
||||
sourcePath: 'foo path'
|
||||
}));
|
||||
ppackage.finalize();
|
||||
expect(ppackage.getMinifiedSourceAndMap()).toBe(minified);
|
||||
bundle.finalize();
|
||||
expect(bundle.getMinifiedSourceAndMap()).toBe(minified);
|
||||
});
|
||||
});
|
||||
|
||||
describe('sourcemap package', function() {
|
||||
describe('sourcemap bundle', function() {
|
||||
it('should create sourcemap', function() {
|
||||
var p = new Package('test_url');
|
||||
var p = new Bundle('test_url');
|
||||
p.addModule(new ModuleTransport({
|
||||
code: [
|
||||
'transformed foo',
|
||||
|
@ -143,11 +143,11 @@ describe('Package', function() {
|
|||
p.setMainModuleId('foo');
|
||||
p.finalize({runMainModule: true});
|
||||
var s = p.getSourceMap();
|
||||
expect(s).toEqual(genSourceMap(p._modules));
|
||||
expect(s).toEqual(genSourceMap(p.getModules()));
|
||||
});
|
||||
|
||||
it('should combine sourcemaps', function() {
|
||||
var p = new Package('test_url');
|
||||
var p = new Bundle('test_url');
|
||||
|
||||
p.addModule(new ModuleTransport({
|
||||
code: 'transformed foo;\n',
|
||||
|
@ -215,7 +215,7 @@ describe('Package', function() {
|
|||
|
||||
describe('getAssets()', function() {
|
||||
it('should save and return asset objects', function() {
|
||||
var p = new Package('test_url');
|
||||
var p = new Bundle('test_url');
|
||||
var asset1 = {};
|
||||
var asset2 = {};
|
||||
p.addAsset(asset1);
|
||||
|
@ -227,7 +227,7 @@ describe('Package', function() {
|
|||
|
||||
describe('getJSModulePaths()', function() {
|
||||
it('should return module paths', function() {
|
||||
var p = new Package('test_url');
|
||||
var p = new Bundle('test_url');
|
||||
p.addModule(new ModuleTransport({
|
||||
code: 'transformed foo;\n',
|
||||
sourceCode: 'source foo',
|
||||
|
@ -248,7 +248,7 @@ describe('Package', function() {
|
|||
|
||||
function genSourceMap(modules) {
|
||||
var sourceMapGen = new SourceMapGenerator({file: 'bundle.js', version: 3});
|
||||
var packageLineNo = 0;
|
||||
var bundleLineNo = 0;
|
||||
for (var i = 0; i < modules.length; i++) {
|
||||
var module = modules[i];
|
||||
var transformedCode = module.code;
|
||||
|
@ -259,7 +259,7 @@ describe('Package', function() {
|
|||
for (var t = 0; t < transformedCode.length; t++) {
|
||||
if (t === 0 || lastCharNewLine) {
|
||||
sourceMapGen.addMapping({
|
||||
generated: {line: packageLineNo + 1, column: 0},
|
||||
generated: {line: bundleLineNo + 1, column: 0},
|
||||
original: {line: transformedLineCount + 1, column: 0},
|
||||
source: sourcePath
|
||||
});
|
||||
|
@ -267,10 +267,10 @@ describe('Package', function() {
|
|||
lastCharNewLine = transformedCode[t] === '\n';
|
||||
if (lastCharNewLine) {
|
||||
transformedLineCount++;
|
||||
packageLineNo++;
|
||||
bundleLineNo++;
|
||||
}
|
||||
}
|
||||
packageLineNo++;
|
||||
bundleLineNo++;
|
||||
sourceMapGen.setSourceContent(
|
||||
sourcePath,
|
||||
sourceCode
|
|
@ -9,7 +9,7 @@
|
|||
'use strict';
|
||||
|
||||
jest
|
||||
.setMock('worker-farm', function() { return function() {};})
|
||||
.setMock('worker-farm', () => () => undefined)
|
||||
.dontMock('underscore')
|
||||
.dontMock('../../lib/ModuleTransport')
|
||||
.setMock('uglify-js')
|
||||
|
@ -20,13 +20,14 @@ jest.mock('fs');
|
|||
|
||||
var Promise = require('promise');
|
||||
|
||||
describe('Packager', function() {
|
||||
describe('Bundler', function() {
|
||||
var getDependencies;
|
||||
var wrapModule;
|
||||
var Packager;
|
||||
var packager;
|
||||
var Bundler;
|
||||
var bundler;
|
||||
var assetServer;
|
||||
var modules;
|
||||
var ProgressBar;
|
||||
|
||||
beforeEach(function() {
|
||||
getDependencies = jest.genMockFn();
|
||||
|
@ -38,7 +39,7 @@ describe('Packager', function() {
|
|||
};
|
||||
});
|
||||
|
||||
Packager = require('../');
|
||||
Bundler = require('../');
|
||||
|
||||
require('fs').statSync.mockImpl(function() {
|
||||
return {
|
||||
|
@ -50,38 +51,61 @@ describe('Packager', function() {
|
|||
callback(null, '{"json":true}');
|
||||
});
|
||||
|
||||
ProgressBar = require('progress');
|
||||
|
||||
assetServer = {
|
||||
getAssetData: jest.genMockFn(),
|
||||
};
|
||||
|
||||
packager = new Packager({
|
||||
bundler = new Bundler({
|
||||
projectRoots: ['/root'],
|
||||
assetServer: assetServer,
|
||||
});
|
||||
|
||||
|
||||
function createModule({
|
||||
path,
|
||||
id,
|
||||
dependencies,
|
||||
isAsset,
|
||||
isAsset_DEPRECATED,
|
||||
isJSON,
|
||||
resolution,
|
||||
}) {
|
||||
return {
|
||||
path,
|
||||
resolution,
|
||||
getDependencies() { return Promise.resolve(dependencies); },
|
||||
getName() { return Promise.resolve(id); },
|
||||
isJSON() { return isJSON; },
|
||||
isAsset() { return isAsset; },
|
||||
isAsset_DEPRECATED() { return isAsset_DEPRECATED; },
|
||||
};
|
||||
}
|
||||
|
||||
modules = [
|
||||
{id: 'foo', path: '/root/foo.js', dependencies: []},
|
||||
{id: 'bar', path: '/root/bar.js', dependencies: []},
|
||||
{
|
||||
id: 'image!img',
|
||||
createModule({id: 'foo', path: '/root/foo.js', dependencies: []}),
|
||||
createModule({id: 'bar', path: '/root/bar.js', dependencies: []}),
|
||||
createModule({
|
||||
path: '/root/img/img.png',
|
||||
id: 'image!img',
|
||||
isAsset_DEPRECATED: true,
|
||||
dependencies: [],
|
||||
resolution: 2,
|
||||
},
|
||||
{
|
||||
}),
|
||||
createModule({
|
||||
id: 'new_image.png',
|
||||
path: '/root/img/new_image.png',
|
||||
isAsset: true,
|
||||
resolution: 2,
|
||||
dependencies: []
|
||||
},
|
||||
{
|
||||
}),
|
||||
createModule({
|
||||
id: 'package/file.json',
|
||||
path: '/root/file.json',
|
||||
isJSON: true,
|
||||
dependencies: [],
|
||||
},
|
||||
}),
|
||||
];
|
||||
|
||||
getDependencies.mockImpl(function() {
|
||||
|
@ -119,8 +143,8 @@ describe('Packager', function() {
|
|||
});
|
||||
});
|
||||
|
||||
pit('create a package', function() {
|
||||
return packager.package('/root/foo.js', true, 'source_map_url')
|
||||
pit('create a bundle', function() {
|
||||
return bundler.bundle('/root/foo.js', true, 'source_map_url')
|
||||
.then(function(p) {
|
||||
expect(p.addModule.mock.calls[0][0]).toEqual({
|
||||
code: 'lol transformed /root/foo.js lol',
|
||||
|
@ -194,51 +218,24 @@ describe('Packager', function() {
|
|||
{runMainModule: true}
|
||||
]);
|
||||
|
||||
expect(p.addAsset.mock.calls[0]).toEqual([
|
||||
expect(p.addAsset.mock.calls).toContain([
|
||||
imgModule_DEPRECATED
|
||||
]);
|
||||
|
||||
expect(p.addAsset.mock.calls[1]).toEqual([
|
||||
expect(p.addAsset.mock.calls).toContain([
|
||||
imgModule
|
||||
]);
|
||||
|
||||
// TODO(amasad) This fails with 0 != 5 in OSS
|
||||
//expect(ProgressBar.prototype.tick.mock.calls.length).toEqual(modules.length);
|
||||
});
|
||||
});
|
||||
|
||||
pit('gets the list of dependencies', function() {
|
||||
return packager.getDependencies('/root/foo.js', true)
|
||||
.then(({dependencies}) => {
|
||||
expect(dependencies).toEqual([
|
||||
{
|
||||
dependencies: [],
|
||||
id: 'foo',
|
||||
path: '/root/foo.js',
|
||||
},
|
||||
{
|
||||
dependencies: [],
|
||||
id: 'bar',
|
||||
path: '/root/bar.js',
|
||||
},
|
||||
{
|
||||
dependencies: [],
|
||||
id: 'image!img',
|
||||
isAsset_DEPRECATED: true,
|
||||
path: '/root/img/img.png',
|
||||
resolution: 2,
|
||||
},
|
||||
{
|
||||
dependencies: [],
|
||||
id: 'new_image.png',
|
||||
isAsset: true,
|
||||
path: '/root/img/new_image.png',
|
||||
resolution: 2,
|
||||
},
|
||||
{
|
||||
dependencies: [],
|
||||
id: 'package/file.json',
|
||||
isJSON: true,
|
||||
path: '/root/file.json',
|
||||
},
|
||||
]);
|
||||
});
|
||||
pit('gets the list of dependencies from the resolver', function() {
|
||||
return bundler.getDependencies('/root/foo.js', true)
|
||||
.then(
|
||||
() => expect(getDependencies)
|
||||
.toBeCalledWith('/root/foo.js', { dev: true })
|
||||
);
|
||||
});
|
||||
});
|
|
@ -0,0 +1,309 @@
|
|||
/**
|
||||
* 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 assert = require('assert');
|
||||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
const Promise = require('promise');
|
||||
const ProgressBar = require('progress');
|
||||
const Cache = require('../Cache');
|
||||
const Transformer = require('../JSTransformer');
|
||||
const DependencyResolver = require('../DependencyResolver');
|
||||
const Bundle = require('./Bundle');
|
||||
const Activity = require('../Activity');
|
||||
const ModuleTransport = require('../lib/ModuleTransport');
|
||||
const declareOpts = require('../lib/declareOpts');
|
||||
const imageSize = require('image-size');
|
||||
|
||||
const sizeOf = Promise.denodeify(imageSize);
|
||||
const readFile = Promise.denodeify(fs.readFile);
|
||||
|
||||
const validateOpts = declareOpts({
|
||||
projectRoots: {
|
||||
type: 'array',
|
||||
required: true,
|
||||
},
|
||||
blacklistRE: {
|
||||
type: 'object', // typeof regex is object
|
||||
},
|
||||
moduleFormat: {
|
||||
type: 'string',
|
||||
default: 'haste',
|
||||
},
|
||||
polyfillModuleNames: {
|
||||
type: 'array',
|
||||
default: [],
|
||||
},
|
||||
cacheVersion: {
|
||||
type: 'string',
|
||||
default: '1.0',
|
||||
},
|
||||
resetCache: {
|
||||
type: 'boolean',
|
||||
default: false,
|
||||
},
|
||||
transformModulePath: {
|
||||
type:'string',
|
||||
required: false,
|
||||
},
|
||||
nonPersistent: {
|
||||
type: 'boolean',
|
||||
default: false,
|
||||
},
|
||||
assetRoots: {
|
||||
type: 'array',
|
||||
required: false,
|
||||
},
|
||||
assetExts: {
|
||||
type: 'array',
|
||||
default: ['png'],
|
||||
},
|
||||
fileWatcher: {
|
||||
type: 'object',
|
||||
required: true,
|
||||
},
|
||||
assetServer: {
|
||||
type: 'object',
|
||||
required: true,
|
||||
},
|
||||
transformTimeoutInterval: {
|
||||
type: 'number',
|
||||
required: false,
|
||||
},
|
||||
});
|
||||
|
||||
class Bundler {
|
||||
|
||||
constructor(options) {
|
||||
const opts = this._opts = validateOpts(options);
|
||||
|
||||
opts.projectRoots.forEach(verifyRootExists);
|
||||
|
||||
this._cache = new Cache({
|
||||
resetCache: opts.resetCache,
|
||||
cacheVersion: opts.cacheVersion,
|
||||
projectRoots: opts.projectRoots,
|
||||
transformModulePath: opts.transformModulePath,
|
||||
});
|
||||
|
||||
this._resolver = new DependencyResolver({
|
||||
projectRoots: opts.projectRoots,
|
||||
blacklistRE: opts.blacklistRE,
|
||||
polyfillModuleNames: opts.polyfillModuleNames,
|
||||
moduleFormat: opts.moduleFormat,
|
||||
assetRoots: opts.assetRoots,
|
||||
fileWatcher: opts.fileWatcher,
|
||||
assetExts: opts.assetExts,
|
||||
cache: this._cache,
|
||||
});
|
||||
|
||||
this._transformer = new Transformer({
|
||||
projectRoots: opts.projectRoots,
|
||||
blacklistRE: opts.blacklistRE,
|
||||
cache: this._cache,
|
||||
transformModulePath: opts.transformModulePath,
|
||||
});
|
||||
|
||||
this._projectRoots = opts.projectRoots;
|
||||
this._assetServer = opts.assetServer;
|
||||
}
|
||||
|
||||
kill() {
|
||||
this._transformer.kill();
|
||||
return this._cache.end();
|
||||
}
|
||||
|
||||
bundle(main, runModule, sourceMapUrl, isDev, platform) {
|
||||
const bundle = new Bundle(sourceMapUrl);
|
||||
const findEventId = Activity.startEvent('find dependencies');
|
||||
let transformEventId;
|
||||
|
||||
return this.getDependencies(main, isDev, platform).then((result) => {
|
||||
Activity.endEvent(findEventId);
|
||||
transformEventId = Activity.startEvent('transform');
|
||||
|
||||
let bar;
|
||||
if (process.stdout.isTTY) {
|
||||
bar = new ProgressBar('transforming [:bar] :percent :current/:total', {
|
||||
complete: '=',
|
||||
incomplete: ' ',
|
||||
width: 40,
|
||||
total: result.dependencies.length,
|
||||
});
|
||||
}
|
||||
|
||||
bundle.setMainModuleId(result.mainModuleId);
|
||||
return Promise.all(
|
||||
result.dependencies.map(
|
||||
module => this._transformModule(bundle, module).then(transformed => {
|
||||
if (bar) {
|
||||
bar.tick();
|
||||
}
|
||||
return transformed;
|
||||
})
|
||||
)
|
||||
);
|
||||
}).then((transformedModules) => {
|
||||
Activity.endEvent(transformEventId);
|
||||
|
||||
transformedModules.forEach(function(moduleTransport) {
|
||||
bundle.addModule(moduleTransport);
|
||||
});
|
||||
|
||||
bundle.finalize({ runMainModule: runModule });
|
||||
return bundle;
|
||||
});
|
||||
}
|
||||
|
||||
invalidateFile(filePath) {
|
||||
this._transformer.invalidateFile(filePath);
|
||||
}
|
||||
|
||||
getDependencies(main, isDev, platform) {
|
||||
return this._resolver.getDependencies(main, { dev: isDev, platform });
|
||||
}
|
||||
|
||||
_transformModule(bundle, module) {
|
||||
let transform;
|
||||
|
||||
if (module.isAsset_DEPRECATED()) {
|
||||
transform = this.generateAssetModule_DEPRECATED(bundle, module);
|
||||
} else if (module.isAsset()) {
|
||||
transform = this.generateAssetModule(bundle, module);
|
||||
} else if (module.isJSON()) {
|
||||
transform = generateJSONModule(module);
|
||||
} else {
|
||||
transform = this._transformer.loadFileAndTransform(
|
||||
path.resolve(module.path)
|
||||
);
|
||||
}
|
||||
|
||||
const resolver = this._resolver;
|
||||
return transform.then(
|
||||
transformed => resolver.wrapModule(module, transformed.code).then(
|
||||
code => new ModuleTransport({
|
||||
code: code,
|
||||
map: transformed.map,
|
||||
sourceCode: transformed.sourceCode,
|
||||
sourcePath: transformed.sourcePath,
|
||||
virtual: transformed.virtual,
|
||||
})
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
getGraphDebugInfo() {
|
||||
return this._resolver.getDebugInfo();
|
||||
}
|
||||
|
||||
generateAssetModule_DEPRECATED(bundle, module) {
|
||||
return Promise.all([
|
||||
sizeOf(module.path),
|
||||
module.getName(),
|
||||
]).then(([dimensions, id]) => {
|
||||
const img = {
|
||||
__packager_asset: true,
|
||||
isStatic: true,
|
||||
path: module.path,
|
||||
uri: id.replace(/^[^!]+!/, ''),
|
||||
width: dimensions.width / module.resolution,
|
||||
height: dimensions.height / module.resolution,
|
||||
deprecated: true,
|
||||
};
|
||||
|
||||
bundle.addAsset(img);
|
||||
|
||||
const code = 'module.exports = ' + JSON.stringify(img) + ';';
|
||||
|
||||
return new ModuleTransport({
|
||||
code: code,
|
||||
sourceCode: code,
|
||||
sourcePath: module.path,
|
||||
virtual: true,
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
generateAssetModule(bundle, module) {
|
||||
const relPath = getPathRelativeToRoot(this._projectRoots, module.path);
|
||||
|
||||
return Promise.all([
|
||||
sizeOf(module.path),
|
||||
this._assetServer.getAssetData(relPath),
|
||||
]).then(function(res) {
|
||||
const dimensions = res[0];
|
||||
const assetData = res[1];
|
||||
const img = {
|
||||
__packager_asset: true,
|
||||
fileSystemLocation: path.dirname(module.path),
|
||||
httpServerLocation: path.join('/assets', path.dirname(relPath)),
|
||||
width: dimensions.width / module.resolution,
|
||||
height: dimensions.height / module.resolution,
|
||||
scales: assetData.scales,
|
||||
hash: assetData.hash,
|
||||
name: assetData.name,
|
||||
type: assetData.type,
|
||||
};
|
||||
|
||||
bundle.addAsset(img);
|
||||
|
||||
const ASSET_TEMPLATE = 'module.exports = require("AssetRegistry").registerAsset(%json);';
|
||||
const code = ASSET_TEMPLATE.replace('%json', JSON.stringify(img));
|
||||
|
||||
return new ModuleTransport({
|
||||
code: code,
|
||||
sourceCode: code,
|
||||
sourcePath: module.path,
|
||||
virtual: true,
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
function generateJSONModule(module) {
|
||||
return readFile(module.path).then(function(data) {
|
||||
const code = 'module.exports = ' + data.toString('utf8') + ';';
|
||||
|
||||
return new ModuleTransport({
|
||||
code: code,
|
||||
sourceCode: code,
|
||||
sourcePath: module.path,
|
||||
virtual: true,
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function getPathRelativeToRoot(roots, absPath) {
|
||||
for (let i = 0; i < roots.length; i++) {
|
||||
const relPath = path.relative(roots[i], absPath);
|
||||
if (relPath[0] !== '.') {
|
||||
return relPath;
|
||||
}
|
||||
}
|
||||
|
||||
throw new Error(
|
||||
'Expected root module to be relative to one of the project roots'
|
||||
);
|
||||
}
|
||||
|
||||
function verifyRootExists(root) {
|
||||
// Verify that the root exists.
|
||||
assert(fs.statSync(root).isDirectory(), 'Root has to be a valid directory');
|
||||
}
|
||||
|
||||
class DummyCache {
|
||||
get(filepath, field, loaderCb) {
|
||||
return loaderCb();
|
||||
}
|
||||
|
||||
end(){}
|
||||
invalidate(filepath){}
|
||||
}
|
||||
module.exports = Bundler;
|
|
@ -0,0 +1,256 @@
|
|||
/**
|
||||
* 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';
|
||||
|
||||
jest
|
||||
.dontMock('../index');
|
||||
|
||||
const Promise = require('promise');
|
||||
|
||||
describe('BundlesLayout', () => {
|
||||
var BundlesLayout;
|
||||
var DependencyResolver;
|
||||
|
||||
beforeEach(() => {
|
||||
BundlesLayout = require('../index');
|
||||
DependencyResolver = require('../../DependencyResolver');
|
||||
});
|
||||
|
||||
describe('generate', () => {
|
||||
function newBundlesLayout() {
|
||||
return new BundlesLayout({
|
||||
dependencyResolver: new DependencyResolver(),
|
||||
});
|
||||
}
|
||||
|
||||
function isPolyfill() {
|
||||
return false;
|
||||
}
|
||||
|
||||
function dep(path) {
|
||||
return {
|
||||
path: path,
|
||||
isPolyfill: isPolyfill,
|
||||
};
|
||||
}
|
||||
|
||||
pit('should bundle sync dependencies', () => {
|
||||
DependencyResolver.prototype.getDependencies.mockImpl((path) => {
|
||||
switch (path) {
|
||||
case '/root/index.js':
|
||||
return Promise.resolve({
|
||||
dependencies: [dep('/root/index.js'), dep('/root/a.js')],
|
||||
asyncDependencies: [],
|
||||
});
|
||||
case '/root/a.js':
|
||||
return Promise.resolve({
|
||||
dependencies: [dep('/root/a.js')],
|
||||
asyncDependencies: [],
|
||||
});
|
||||
default:
|
||||
throw 'Undefined path: ' + path;
|
||||
}
|
||||
});
|
||||
|
||||
return newBundlesLayout().generateLayout(['/root/index.js']).then(bundles =>
|
||||
expect(bundles).toEqual({
|
||||
id: 'bundle.0',
|
||||
modules: ['/root/index.js', '/root/a.js'],
|
||||
children: [],
|
||||
})
|
||||
);
|
||||
});
|
||||
|
||||
pit('should separate async dependencies into different bundle', () => {
|
||||
DependencyResolver.prototype.getDependencies.mockImpl((path) => {
|
||||
switch (path) {
|
||||
case '/root/index.js':
|
||||
return Promise.resolve({
|
||||
dependencies: [dep('/root/index.js')],
|
||||
asyncDependencies: [['/root/a.js']],
|
||||
});
|
||||
case '/root/a.js':
|
||||
return Promise.resolve({
|
||||
dependencies: [dep('/root/a.js')],
|
||||
asyncDependencies: [],
|
||||
});
|
||||
default:
|
||||
throw 'Undefined path: ' + path;
|
||||
}
|
||||
});
|
||||
|
||||
return newBundlesLayout().generateLayout(['/root/index.js']).then(bundles =>
|
||||
expect(bundles).toEqual({
|
||||
id: 'bundle.0',
|
||||
modules: ['/root/index.js'],
|
||||
children: [{
|
||||
id:'bundle.0.1',
|
||||
modules: ['/root/a.js'],
|
||||
children: [],
|
||||
}],
|
||||
})
|
||||
);
|
||||
});
|
||||
|
||||
pit('separate async dependencies of async dependencies', () => {
|
||||
DependencyResolver.prototype.getDependencies.mockImpl((path) => {
|
||||
switch (path) {
|
||||
case '/root/index.js':
|
||||
return Promise.resolve({
|
||||
dependencies: [dep('/root/index.js')],
|
||||
asyncDependencies: [['/root/a.js']],
|
||||
});
|
||||
case '/root/a.js':
|
||||
return Promise.resolve({
|
||||
dependencies: [dep('/root/a.js')],
|
||||
asyncDependencies: [['/root/b.js']],
|
||||
});
|
||||
case '/root/b.js':
|
||||
return Promise.resolve({
|
||||
dependencies: [dep('/root/b.js')],
|
||||
asyncDependencies: [],
|
||||
});
|
||||
default:
|
||||
throw 'Undefined path: ' + path;
|
||||
}
|
||||
});
|
||||
|
||||
return newBundlesLayout().generateLayout(['/root/index.js']).then(bundles =>
|
||||
expect(bundles).toEqual({
|
||||
id: 'bundle.0',
|
||||
modules: ['/root/index.js'],
|
||||
children: [{
|
||||
id: 'bundle.0.1',
|
||||
modules: ['/root/a.js'],
|
||||
children: [{
|
||||
id: 'bundle.0.1.2',
|
||||
modules: ['/root/b.js'],
|
||||
children: [],
|
||||
}],
|
||||
}],
|
||||
})
|
||||
);
|
||||
});
|
||||
|
||||
pit('separate bundle sync dependencies of async ones on same bundle', () => {
|
||||
DependencyResolver.prototype.getDependencies.mockImpl((path) => {
|
||||
switch (path) {
|
||||
case '/root/index.js':
|
||||
return Promise.resolve({
|
||||
dependencies: [dep('/root/index.js')],
|
||||
asyncDependencies: [['/root/a.js']],
|
||||
});
|
||||
case '/root/a.js':
|
||||
return Promise.resolve({
|
||||
dependencies: [dep('/root/a.js'), dep('/root/b.js')],
|
||||
asyncDependencies: [],
|
||||
});
|
||||
case '/root/b.js':
|
||||
return Promise.resolve({
|
||||
dependencies: [dep('/root/b.js')],
|
||||
asyncDependencies: [],
|
||||
});
|
||||
default:
|
||||
throw 'Undefined path: ' + path;
|
||||
}
|
||||
});
|
||||
|
||||
return newBundlesLayout().generateLayout(['/root/index.js']).then(bundles =>
|
||||
expect(bundles).toEqual({
|
||||
id: 'bundle.0',
|
||||
modules: ['/root/index.js'],
|
||||
children: [{
|
||||
id: 'bundle.0.1',
|
||||
modules: ['/root/a.js', '/root/b.js'],
|
||||
children: [],
|
||||
}],
|
||||
})
|
||||
);
|
||||
});
|
||||
|
||||
pit('separate cache in which bundle is each dependency', () => {
|
||||
DependencyResolver.prototype.getDependencies.mockImpl((path) => {
|
||||
switch (path) {
|
||||
case '/root/index.js':
|
||||
return Promise.resolve({
|
||||
dependencies: [dep('/root/index.js'), dep('/root/a.js')],
|
||||
asyncDependencies: [],
|
||||
});
|
||||
case '/root/a.js':
|
||||
return Promise.resolve({
|
||||
dependencies: [dep('/root/a.js')],
|
||||
asyncDependencies: [['/root/b.js']],
|
||||
});
|
||||
case '/root/b.js':
|
||||
return Promise.resolve({
|
||||
dependencies: [dep('/root/b.js')],
|
||||
asyncDependencies: [],
|
||||
});
|
||||
default:
|
||||
throw 'Undefined path: ' + path;
|
||||
}
|
||||
});
|
||||
|
||||
return newBundlesLayout().generateLayout(['/root/index.js']).then(
|
||||
bundles => expect(bundles).toEqual({
|
||||
id: 'bundle.0',
|
||||
modules: ['/root/index.js', '/root/a.js'],
|
||||
children: [{
|
||||
id: 'bundle.0.1',
|
||||
modules: ['/root/b.js'],
|
||||
children: [],
|
||||
}],
|
||||
})
|
||||
);
|
||||
});
|
||||
|
||||
pit('separate cache in which bundle is each dependency', () => {
|
||||
DependencyResolver.prototype.getDependencies.mockImpl((path) => {
|
||||
switch (path) {
|
||||
case '/root/index.js':
|
||||
return Promise.resolve({
|
||||
dependencies: [dep('/root/index.js'), dep('/root/a.js')],
|
||||
asyncDependencies: [['/root/b.js'], ['/root/c.js']],
|
||||
});
|
||||
case '/root/a.js':
|
||||
return Promise.resolve({
|
||||
dependencies: [dep('/root/a.js')],
|
||||
asyncDependencies: [],
|
||||
});
|
||||
case '/root/b.js':
|
||||
return Promise.resolve({
|
||||
dependencies: [dep('/root/b.js')],
|
||||
asyncDependencies: [['/root/d.js']],
|
||||
});
|
||||
case '/root/c.js':
|
||||
return Promise.resolve({
|
||||
dependencies: [dep('/root/c.js')],
|
||||
asyncDependencies: [],
|
||||
});
|
||||
case '/root/d.js':
|
||||
return Promise.resolve({
|
||||
dependencies: [dep('/root/d.js')],
|
||||
asyncDependencies: [],
|
||||
});
|
||||
default:
|
||||
throw 'Undefined path: ' + path;
|
||||
}
|
||||
});
|
||||
|
||||
var layout = newBundlesLayout();
|
||||
return layout.generateLayout(['/root/index.js']).then(() => {
|
||||
expect(layout.getBundleIDForModule('/root/index.js')).toBe('bundle.0');
|
||||
expect(layout.getBundleIDForModule('/root/a.js')).toBe('bundle.0');
|
||||
expect(layout.getBundleIDForModule('/root/b.js')).toBe('bundle.0.1');
|
||||
expect(layout.getBundleIDForModule('/root/c.js')).toBe('bundle.0.2');
|
||||
expect(layout.getBundleIDForModule('/root/d.js')).toBe('bundle.0.1.3');
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
612
react-packager/src/BundlesLayout/__tests__/BundlesLayoutIntegration-test.js
vendored
Normal file
612
react-packager/src/BundlesLayout/__tests__/BundlesLayoutIntegration-test.js
vendored
Normal file
|
@ -0,0 +1,612 @@
|
|||
/**
|
||||
* 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';
|
||||
|
||||
jest
|
||||
.dontMock('absolute-path')
|
||||
.dontMock('crypto')
|
||||
.dontMock('underscore')
|
||||
.dontMock('path')
|
||||
.dontMock('../index')
|
||||
.dontMock('../../lib/getAssetDataFromName')
|
||||
.dontMock('../../DependencyResolver/crawlers')
|
||||
.dontMock('../../DependencyResolver/crawlers/node')
|
||||
.dontMock('../../DependencyResolver/DependencyGraph/docblock')
|
||||
.dontMock('../../DependencyResolver/fastfs')
|
||||
.dontMock('../../DependencyResolver/replacePatterns')
|
||||
.dontMock('../../DependencyResolver')
|
||||
.dontMock('../../DependencyResolver/DependencyGraph')
|
||||
.dontMock('../../DependencyResolver/AssetModule_DEPRECATED')
|
||||
.dontMock('../../DependencyResolver/AssetModule')
|
||||
.dontMock('../../DependencyResolver/Module')
|
||||
.dontMock('../../DependencyResolver/Package')
|
||||
.dontMock('../../DependencyResolver/Polyfill')
|
||||
.dontMock('../../DependencyResolver/ModuleCache');
|
||||
|
||||
const Promise = require('promise');
|
||||
const path = require('path');
|
||||
|
||||
jest.mock('fs');
|
||||
|
||||
describe('BundlesLayout', () => {
|
||||
var BundlesLayout;
|
||||
var Cache;
|
||||
var DependencyResolver;
|
||||
var fileWatcher;
|
||||
var fs;
|
||||
|
||||
const polyfills = [
|
||||
'polyfills/prelude_dev.js',
|
||||
'polyfills/prelude.js',
|
||||
'polyfills/require.js',
|
||||
'polyfills/polyfills.js',
|
||||
'polyfills/console.js',
|
||||
'polyfills/error-guard.js',
|
||||
'polyfills/String.prototype.es6.js',
|
||||
'polyfills/Array.prototype.es6.js',
|
||||
];
|
||||
const baseFs = getBaseFs();
|
||||
|
||||
beforeEach(() => {
|
||||
fs = require('fs');
|
||||
BundlesLayout = require('../index');
|
||||
Cache = require('../../Cache');
|
||||
DependencyResolver = require('../../DependencyResolver');
|
||||
|
||||
fileWatcher = {
|
||||
on: () => this,
|
||||
isWatchman: () => Promise.resolve(false)
|
||||
};
|
||||
});
|
||||
|
||||
describe('generate', () => {
|
||||
function newBundlesLayout() {
|
||||
const resolver = new DependencyResolver({
|
||||
projectRoots: ['/root', '/' + __dirname.split('/')[1]],
|
||||
fileWatcher: fileWatcher,
|
||||
cache: new Cache(),
|
||||
assetExts: ['js', 'png'],
|
||||
assetRoots: ['/root'],
|
||||
});
|
||||
|
||||
return new BundlesLayout({dependencyResolver: resolver});
|
||||
}
|
||||
|
||||
function stripPolyfills(bundle) {
|
||||
return Promise
|
||||
.all(bundle.children.map(childModule => stripPolyfills(childModule)))
|
||||
.then(children => {
|
||||
const modules = bundle.modules
|
||||
.filter(moduleName => { // filter polyfills
|
||||
for (let p of polyfills) {
|
||||
if (moduleName.indexOf(p) !== -1) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
});
|
||||
|
||||
return {
|
||||
id: bundle.id,
|
||||
modules: modules,
|
||||
children: children,
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
function setMockFilesystem(mockFs) {
|
||||
fs.__setMockFilesystem(Object.assign(mockFs, baseFs));
|
||||
}
|
||||
|
||||
pit('should bundle single-module app', () => {
|
||||
setMockFilesystem({
|
||||
'root': {
|
||||
'index.js': `
|
||||
/**
|
||||
* @providesModule index
|
||||
*/`,
|
||||
}
|
||||
});
|
||||
|
||||
return newBundlesLayout().generateLayout(['/root/index.js']).then(bundles =>
|
||||
stripPolyfills(bundles).then(resolvedBundles =>
|
||||
expect(resolvedBundles).toEqual({
|
||||
id: 'bundle.0',
|
||||
modules: ['/root/index.js'],
|
||||
children: [],
|
||||
})
|
||||
)
|
||||
);
|
||||
});
|
||||
|
||||
pit('should bundle dependant modules', () => {
|
||||
setMockFilesystem({
|
||||
'root': {
|
||||
'index.js': `
|
||||
/**
|
||||
* @providesModule index
|
||||
*/
|
||||
require("a");`,
|
||||
'a.js': `
|
||||
/**
|
||||
* @providesModule a
|
||||
*/`,
|
||||
}
|
||||
});
|
||||
|
||||
return newBundlesLayout().generateLayout(['/root/index.js']).then(bundles =>
|
||||
stripPolyfills(bundles).then(resolvedBundles =>
|
||||
expect(resolvedBundles).toEqual({
|
||||
id: 'bundle.0',
|
||||
modules: ['/root/index.js', '/root/a.js'],
|
||||
children: [],
|
||||
})
|
||||
)
|
||||
);
|
||||
});
|
||||
|
||||
pit('should split bundles for async dependencies', () => {
|
||||
setMockFilesystem({
|
||||
'root': {
|
||||
'index.js': `
|
||||
/**
|
||||
* @providesModule index
|
||||
*/
|
||||
System.import("a");`,
|
||||
'a.js': `
|
||||
/**,
|
||||
* @providesModule a
|
||||
*/`,
|
||||
}
|
||||
});
|
||||
|
||||
return newBundlesLayout().generateLayout(['/root/index.js']).then(bundles =>
|
||||
stripPolyfills(bundles).then(resolvedBundles =>
|
||||
expect(resolvedBundles).toEqual({
|
||||
id: 'bundle.0',
|
||||
modules: ['/root/index.js'],
|
||||
children: [{
|
||||
id: 'bundle.0.1',
|
||||
modules: ['/root/a.js'],
|
||||
children: [],
|
||||
}],
|
||||
})
|
||||
)
|
||||
);
|
||||
});
|
||||
|
||||
pit('should split into multiple bundles separate async dependencies', () => {
|
||||
setMockFilesystem({
|
||||
'root': {
|
||||
'index.js': `
|
||||
/**
|
||||
* @providesModule index
|
||||
*/
|
||||
System.import("a");
|
||||
System.import("b");`,
|
||||
'a.js': `
|
||||
/**,
|
||||
* @providesModule a
|
||||
*/`,
|
||||
'b.js': `
|
||||
/**
|
||||
* @providesModule b
|
||||
*/`,
|
||||
}
|
||||
});
|
||||
|
||||
return newBundlesLayout().generateLayout(['/root/index.js']).then(bundles =>
|
||||
stripPolyfills(bundles).then(resolvedBundles =>
|
||||
expect(resolvedBundles).toEqual({
|
||||
id: 'bundle.0',
|
||||
modules: ['/root/index.js'],
|
||||
children: [
|
||||
{
|
||||
id: 'bundle.0.1',
|
||||
modules: ['/root/a.js'],
|
||||
children: [],
|
||||
}, {
|
||||
id: 'bundle.0.2',
|
||||
modules: ['/root/b.js'],
|
||||
children: [],
|
||||
},
|
||||
],
|
||||
})
|
||||
)
|
||||
);
|
||||
});
|
||||
|
||||
pit('should fully traverse sync dependencies', () => {
|
||||
setMockFilesystem({
|
||||
'root': {
|
||||
'index.js': `
|
||||
/**
|
||||
* @providesModule index
|
||||
*/
|
||||
require("a");
|
||||
System.import("b");`,
|
||||
'a.js': `
|
||||
/**,
|
||||
* @providesModule a
|
||||
*/`,
|
||||
'b.js': `
|
||||
/**
|
||||
* @providesModule b
|
||||
*/`,
|
||||
}
|
||||
});
|
||||
|
||||
return newBundlesLayout().generateLayout(['/root/index.js']).then(bundles =>
|
||||
stripPolyfills(bundles).then(resolvedBundles =>
|
||||
expect(resolvedBundles).toEqual({
|
||||
id: 'bundle.0',
|
||||
modules: ['/root/index.js', '/root/a.js'],
|
||||
children: [{
|
||||
id: 'bundle.0.1',
|
||||
modules: ['/root/b.js'],
|
||||
children: [],
|
||||
}],
|
||||
})
|
||||
)
|
||||
);
|
||||
});
|
||||
|
||||
pit('should include sync dependencies async dependencies might have', () => {
|
||||
setMockFilesystem({
|
||||
'root': {
|
||||
'index.js': `
|
||||
/**
|
||||
* @providesModule index
|
||||
*/
|
||||
System.import("a");`,
|
||||
'a.js': `
|
||||
/**,
|
||||
* @providesModule a
|
||||
*/,
|
||||
require("b");`,
|
||||
'b.js': `
|
||||
/**
|
||||
* @providesModule b
|
||||
*/
|
||||
require("c");`,
|
||||
'c.js': `
|
||||
/**
|
||||
* @providesModule c
|
||||
*/`,
|
||||
}
|
||||
});
|
||||
|
||||
return newBundlesLayout().generateLayout(['/root/index.js']).then(bundles =>
|
||||
stripPolyfills(bundles).then(resolvedBundles =>
|
||||
expect(resolvedBundles).toEqual({
|
||||
id: 'bundle.0',
|
||||
modules: ['/root/index.js'],
|
||||
children: [{
|
||||
id: 'bundle.0.1',
|
||||
modules: ['/root/a.js', '/root/b.js', '/root/c.js'],
|
||||
children: [],
|
||||
}],
|
||||
})
|
||||
)
|
||||
);
|
||||
});
|
||||
|
||||
pit('should allow duplicated dependencies across bundles', () => {
|
||||
setMockFilesystem({
|
||||
'root': {
|
||||
'index.js': `
|
||||
/**
|
||||
* @providesModule index
|
||||
*/
|
||||
System.import("a");
|
||||
System.import("b");`,
|
||||
'a.js': `
|
||||
/**,
|
||||
* @providesModule a
|
||||
*/,
|
||||
require("c");`,
|
||||
'b.js': `
|
||||
/**
|
||||
* @providesModule b
|
||||
*/
|
||||
require("c");`,
|
||||
'c.js': `
|
||||
/**
|
||||
* @providesModule c
|
||||
*/`,
|
||||
}
|
||||
});
|
||||
|
||||
return newBundlesLayout().generateLayout(['/root/index.js']).then(bundles =>
|
||||
stripPolyfills(bundles).then(resolvedBundles =>
|
||||
expect(resolvedBundles).toEqual({
|
||||
id: 'bundle.0',
|
||||
modules: ['/root/index.js'],
|
||||
children: [
|
||||
{
|
||||
id: 'bundle.0.1',
|
||||
modules: ['/root/a.js', '/root/c.js'],
|
||||
children: [],
|
||||
},
|
||||
{
|
||||
id: 'bundle.0.2',
|
||||
modules: ['/root/b.js', '/root/c.js'],
|
||||
children: [],
|
||||
},
|
||||
],
|
||||
})
|
||||
)
|
||||
);
|
||||
});
|
||||
|
||||
pit('should put in separate bundles async dependencies of async dependencies', () => {
|
||||
setMockFilesystem({
|
||||
'root': {
|
||||
'index.js': `
|
||||
/**
|
||||
* @providesModule index
|
||||
*/
|
||||
System.import("a");`,
|
||||
'a.js': `
|
||||
/**,
|
||||
* @providesModule a
|
||||
*/,
|
||||
System.import("b");`,
|
||||
'b.js': `
|
||||
/**
|
||||
* @providesModule b
|
||||
*/
|
||||
require("c");`,
|
||||
'c.js': `
|
||||
/**
|
||||
* @providesModule c
|
||||
*/`,
|
||||
}
|
||||
});
|
||||
|
||||
return newBundlesLayout().generateLayout(['/root/index.js']).then(bundles =>
|
||||
stripPolyfills(bundles).then(resolvedBundles =>
|
||||
expect(resolvedBundles).toEqual({
|
||||
id: 'bundle.0',
|
||||
modules: ['/root/index.js'],
|
||||
children: [
|
||||
{
|
||||
id: 'bundle.0.1',
|
||||
modules: ['/root/a.js'],
|
||||
children: [{
|
||||
id: 'bundle.0.1.2',
|
||||
modules: ['/root/b.js', '/root/c.js'],
|
||||
children: [],
|
||||
}],
|
||||
},
|
||||
],
|
||||
})
|
||||
)
|
||||
);
|
||||
});
|
||||
|
||||
pit('should put image dependencies into separate bundles', () => {
|
||||
setMockFilesystem({
|
||||
'root': {
|
||||
'index.js': `
|
||||
/**
|
||||
* @providesModule index
|
||||
*/
|
||||
System.import("a");`,
|
||||
'a.js':`
|
||||
/**,
|
||||
* @providesModule a
|
||||
*/,
|
||||
require("./img.png");`,
|
||||
'img.png': '',
|
||||
}
|
||||
});
|
||||
|
||||
return newBundlesLayout().generateLayout(['/root/index.js']).then(bundles =>
|
||||
stripPolyfills(bundles).then(resolvedBundles =>
|
||||
expect(resolvedBundles).toEqual({
|
||||
id: 'bundle.0',
|
||||
modules: ['/root/index.js'],
|
||||
children: [{
|
||||
id: 'bundle.0.1',
|
||||
modules: ['/root/a.js', '/root/img.png'],
|
||||
children: [],
|
||||
}],
|
||||
})
|
||||
)
|
||||
);
|
||||
});
|
||||
|
||||
pit('should put image dependencies across bundles', () => {
|
||||
setMockFilesystem({
|
||||
'root': {
|
||||
'index.js': `
|
||||
/**
|
||||
* @providesModule index
|
||||
*/
|
||||
System.import("a");
|
||||
System.import("b");`,
|
||||
'a.js':`
|
||||
/**,
|
||||
* @providesModule a
|
||||
*/,
|
||||
require("./img.png");`,
|
||||
'b.js':`
|
||||
/**,
|
||||
* @providesModule b
|
||||
*/,
|
||||
require("./img.png");`,
|
||||
'img.png': '',
|
||||
}
|
||||
});
|
||||
|
||||
return newBundlesLayout().generateLayout(['/root/index.js']).then(bundles =>
|
||||
stripPolyfills(bundles).then(resolvedBundles =>
|
||||
expect(resolvedBundles).toEqual({
|
||||
id: 'bundle.0',
|
||||
modules: ['/root/index.js'],
|
||||
children: [
|
||||
{
|
||||
id: 'bundle.0.1',
|
||||
modules: ['/root/a.js', '/root/img.png'],
|
||||
children: [],
|
||||
},
|
||||
{
|
||||
id: 'bundle.0.2',
|
||||
modules: ['/root/b.js', '/root/img.png'],
|
||||
children: [],
|
||||
},
|
||||
],
|
||||
})
|
||||
)
|
||||
);
|
||||
});
|
||||
|
||||
pit('could async require asset', () => {
|
||||
setMockFilesystem({
|
||||
'root': {
|
||||
'index.js': `
|
||||
/**
|
||||
* @providesModule index
|
||||
*/
|
||||
System.import("./img.png");`,
|
||||
'img.png': '',
|
||||
}
|
||||
});
|
||||
|
||||
return newBundlesLayout().generateLayout(['/root/index.js']).then(bundles =>
|
||||
stripPolyfills(bundles).then(resolvedBundles =>
|
||||
expect(resolvedBundles).toEqual({
|
||||
id: 'bundle.0',
|
||||
modules: ['/root/index.js'],
|
||||
children: [{
|
||||
id: 'bundle.0.1',
|
||||
modules: ['/root/img.png'],
|
||||
children: [],
|
||||
}],
|
||||
})
|
||||
)
|
||||
);
|
||||
});
|
||||
|
||||
pit('should include deprecated assets into separate bundles', () => {
|
||||
setMockFilesystem({
|
||||
'root': {
|
||||
'index.js': `
|
||||
/**
|
||||
* @providesModule index
|
||||
*/
|
||||
System.import("a");`,
|
||||
'a.js':`
|
||||
/**,
|
||||
* @providesModule a
|
||||
*/,
|
||||
require("image!img");`,
|
||||
'img.png': '',
|
||||
}
|
||||
});
|
||||
|
||||
return newBundlesLayout().generateLayout(['/root/index.js']).then(bundles =>
|
||||
stripPolyfills(bundles).then(resolvedBundles =>
|
||||
expect(resolvedBundles).toEqual({
|
||||
id: 'bundle.0',
|
||||
modules: ['/root/index.js'],
|
||||
children: [{
|
||||
id: 'bundle.0.1',
|
||||
modules: ['/root/a.js', '/root/img.png'],
|
||||
children: [],
|
||||
}],
|
||||
})
|
||||
)
|
||||
);
|
||||
});
|
||||
|
||||
pit('could async require deprecated asset', () => {
|
||||
setMockFilesystem({
|
||||
'root': {
|
||||
'index.js': `
|
||||
/**
|
||||
* @providesModule index
|
||||
*/
|
||||
System.import("image!img");`,
|
||||
'img.png': '',
|
||||
}
|
||||
});
|
||||
|
||||
return newBundlesLayout().generateLayout(['/root/index.js']).then(bundles =>
|
||||
stripPolyfills(bundles).then(resolvedBundles =>
|
||||
expect(resolvedBundles).toEqual({
|
||||
id: 'bundle.0',
|
||||
modules: ['/root/index.js'],
|
||||
children: [{
|
||||
id: 'bundle.0.1',
|
||||
modules: ['/root/img.png'],
|
||||
children: [],
|
||||
}],
|
||||
})
|
||||
)
|
||||
);
|
||||
});
|
||||
|
||||
pit('should put packages into bundles', () => {
|
||||
setMockFilesystem({
|
||||
'root': {
|
||||
'index.js': `
|
||||
/**
|
||||
* @providesModule index
|
||||
*/
|
||||
System.import("aPackage");`,
|
||||
'aPackage': {
|
||||
'package.json': JSON.stringify({
|
||||
name: 'aPackage',
|
||||
main: './main.js',
|
||||
browser: {
|
||||
'./main.js': './client.js',
|
||||
},
|
||||
}),
|
||||
'main.js': 'some other code',
|
||||
'client.js': 'some code',
|
||||
},
|
||||
}
|
||||
});
|
||||
|
||||
return newBundlesLayout().generateLayout(['/root/index.js']).then(bundles =>
|
||||
stripPolyfills(bundles).then(resolvedBundles =>
|
||||
expect(resolvedBundles).toEqual({
|
||||
id: 'bundle.0',
|
||||
modules: ['/root/index.js'],
|
||||
children: [{
|
||||
id: 'bundle.0.1',
|
||||
modules: ['/root/aPackage/client.js'],
|
||||
children: [],
|
||||
}],
|
||||
})
|
||||
)
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
function getBaseFs() {
|
||||
const p = path.join(__dirname, '../../../DependencyResolver/polyfills').substring(1);
|
||||
const root = {};
|
||||
let currentPath = root;
|
||||
|
||||
p.split('/').forEach(part => {
|
||||
const child = {};
|
||||
currentPath[part] = child;
|
||||
currentPath = child;
|
||||
});
|
||||
|
||||
polyfills.forEach(polyfill =>
|
||||
currentPath[polyfill.split('/')[1]] = ''
|
||||
);
|
||||
|
||||
return root;
|
||||
}
|
||||
});
|
|
@ -0,0 +1,117 @@
|
|||
/**
|
||||
* 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 _ = require('underscore');
|
||||
const declareOpts = require('../lib/declareOpts');
|
||||
|
||||
const validateOpts = declareOpts({
|
||||
dependencyResolver: {
|
||||
type: 'object',
|
||||
required: true,
|
||||
},
|
||||
});
|
||||
|
||||
const BUNDLE_PREFIX = 'bundle';
|
||||
|
||||
/**
|
||||
* Class that takes care of separating the graph of dependencies into
|
||||
* separate bundles
|
||||
*/
|
||||
class BundlesLayout {
|
||||
constructor(options) {
|
||||
const opts = validateOpts(options);
|
||||
this._resolver = opts.dependencyResolver;
|
||||
|
||||
this._moduleToBundle = Object.create(null);
|
||||
}
|
||||
|
||||
generateLayout(entryPaths, isDev) {
|
||||
var currentBundleID = 0;
|
||||
const rootBundle = {
|
||||
id: BUNDLE_PREFIX + '.' + currentBundleID++,
|
||||
modules: [],
|
||||
children: [],
|
||||
};
|
||||
var pending = [{paths: entryPaths, bundle: rootBundle}];
|
||||
|
||||
return promiseWhile(
|
||||
() => pending.length > 0,
|
||||
() => rootBundle,
|
||||
() => {
|
||||
const {paths, bundle} = pending.shift();
|
||||
|
||||
// pending sync dependencies we still need to explore for the current
|
||||
// pending dependency
|
||||
const pendingSyncDeps = paths;
|
||||
|
||||
// accum variable for sync dependencies of the current pending
|
||||
// dependency we're processing
|
||||
const syncDependencies = Object.create(null);
|
||||
|
||||
return promiseWhile(
|
||||
() => pendingSyncDeps.length > 0,
|
||||
() => {
|
||||
const dependencies = Object.keys(syncDependencies);
|
||||
if (dependencies.length > 0) {
|
||||
bundle.modules = dependencies;
|
||||
}
|
||||
},
|
||||
index => {
|
||||
const pendingSyncDep = pendingSyncDeps.shift();
|
||||
return this._resolver
|
||||
.getDependencies(pendingSyncDep, {dev: isDev})
|
||||
.then(deps => {
|
||||
deps.dependencies.forEach(dep => {
|
||||
if (dep.path !== pendingSyncDep && !dep.isPolyfill()) {
|
||||
pendingSyncDeps.push(dep.path);
|
||||
}
|
||||
syncDependencies[dep.path] = true;
|
||||
this._moduleToBundle[dep.path] = bundle.id;
|
||||
});
|
||||
deps.asyncDependencies.forEach(asyncDeps => {
|
||||
const childBundle = {
|
||||
id: bundle.id + '.' + currentBundleID++,
|
||||
modules: [],
|
||||
children: [],
|
||||
};
|
||||
|
||||
bundle.children.push(childBundle);
|
||||
pending.push({paths: asyncDeps, bundle: childBundle});
|
||||
});
|
||||
});
|
||||
},
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
getBundleIDForModule(path) {
|
||||
return this._moduleToBundle[path];
|
||||
}
|
||||
}
|
||||
|
||||
// Runs the body Promise meanwhile the condition callback is satisfied.
|
||||
// Once it's not satisfied anymore, it returns what the results callback
|
||||
// indicates
|
||||
function promiseWhile(condition, result, body) {
|
||||
return _promiseWhile(condition, result, body, 0);
|
||||
}
|
||||
|
||||
function _promiseWhile(condition, result, body, index) {
|
||||
if (!condition()) {
|
||||
return Promise.resolve(result());
|
||||
}
|
||||
|
||||
return body(index).then(() =>
|
||||
_promiseWhile(condition, result, body, index + 1)
|
||||
);
|
||||
}
|
||||
|
||||
module.exports = BundlesLayout;
|
|
@ -8,13 +8,26 @@
|
|||
*/
|
||||
'use strict';
|
||||
|
||||
class Cache {
|
||||
get(filepath, field, cb) {
|
||||
return cb(filepath);
|
||||
}
|
||||
const mockColor = () => {
|
||||
return {
|
||||
bold: () => { return { }; },
|
||||
};
|
||||
};
|
||||
|
||||
invalidate(filepath) { }
|
||||
end() { }
|
||||
}
|
||||
mockColor.bold = function() {
|
||||
return {};
|
||||
};
|
||||
|
||||
module.exports = Cache;
|
||||
module.exports = {
|
||||
dim: s => s,
|
||||
magenta: mockColor,
|
||||
white: mockColor,
|
||||
blue: mockColor,
|
||||
yellow: mockColor,
|
||||
green: mockColor,
|
||||
bold: mockColor,
|
||||
red: mockColor,
|
||||
cyan: mockColor,
|
||||
gray: mockColor,
|
||||
black: mockColor,
|
||||
};
|
||||
|
|
|
@ -215,6 +215,7 @@ class Cache {
|
|||
hash.update(options.transformModulePath);
|
||||
|
||||
var name = 'react-packager-cache-' + hash.digest('hex');
|
||||
|
||||
return path.join(tmpdir, name);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -5,6 +5,13 @@ const Promise = require('promise');
|
|||
const getAssetDataFromName = require('../lib/getAssetDataFromName');
|
||||
|
||||
class AssetModule extends Module {
|
||||
constructor(...args) {
|
||||
super(...args);
|
||||
const { resolution, name, type } = getAssetDataFromName(this.path);
|
||||
this.resolution = resolution;
|
||||
this._name = name;
|
||||
this._type = type;
|
||||
}
|
||||
|
||||
isHaste() {
|
||||
return Promise.resolve(false);
|
||||
|
@ -14,33 +21,31 @@ class AssetModule extends Module {
|
|||
return Promise.resolve([]);
|
||||
}
|
||||
|
||||
getAsyncDependencies() {
|
||||
return Promise.resolve([]);
|
||||
}
|
||||
|
||||
_read() {
|
||||
return Promise.resolve({});
|
||||
}
|
||||
|
||||
getName() {
|
||||
return super.getName().then(id => {
|
||||
const {name, type} = getAssetDataFromName(this.path);
|
||||
return id.replace(/\/[^\/]+$/, `/${name}.${type}`);
|
||||
});
|
||||
}
|
||||
|
||||
getPlainObject() {
|
||||
return this.getName().then(name => this.addReference({
|
||||
path: this.path,
|
||||
isJSON: false,
|
||||
isAsset: true,
|
||||
isAsset_DEPRECATED: false,
|
||||
isPolyfill: false,
|
||||
resolution: getAssetDataFromName(this.path).resolution,
|
||||
id: name,
|
||||
dependencies: [],
|
||||
}));
|
||||
return super.getName().then(
|
||||
id => id.replace(/\/[^\/]+$/, `/${this._name}.${this._type}`)
|
||||
);
|
||||
}
|
||||
|
||||
hash() {
|
||||
return `AssetModule : ${this.path}`;
|
||||
}
|
||||
|
||||
isJSON() {
|
||||
return false;
|
||||
}
|
||||
|
||||
isAsset() {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = AssetModule;
|
||||
|
|
|
@ -5,36 +5,45 @@ const Promise = require('promise');
|
|||
const getAssetDataFromName = require('../lib/getAssetDataFromName');
|
||||
|
||||
class AssetModule_DEPRECATED extends Module {
|
||||
constructor(...args) {
|
||||
super(...args);
|
||||
const {resolution, name} = getAssetDataFromName(this.path);
|
||||
this.resolution = resolution;
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
isHaste() {
|
||||
return Promise.resolve(false);
|
||||
}
|
||||
|
||||
getName() {
|
||||
return Promise.resolve(this.name);
|
||||
return Promise.resolve(`image!${this.name}`);
|
||||
}
|
||||
|
||||
getDependencies() {
|
||||
return Promise.resolve([]);
|
||||
}
|
||||
|
||||
getPlainObject() {
|
||||
const {name, resolution} = getAssetDataFromName(this.path);
|
||||
|
||||
return Promise.resolve(this.addReference({
|
||||
path: this.path,
|
||||
id: `image!${name}`,
|
||||
resolution,
|
||||
isAsset_DEPRECATED: true,
|
||||
dependencies: [],
|
||||
isJSON: false,
|
||||
isPolyfill: false,
|
||||
isAsset: false,
|
||||
}));
|
||||
getAsyncDependencies() {
|
||||
return Promise.resolve([]);
|
||||
}
|
||||
|
||||
hash() {
|
||||
return `AssetModule_DEPRECATED : ${this.path}`;
|
||||
}
|
||||
|
||||
isJSON() {
|
||||
return false;
|
||||
}
|
||||
|
||||
isAsset_DEPRECATED() {
|
||||
return true;
|
||||
}
|
||||
|
||||
resolution() {
|
||||
return getAssetDataFromName(this.path).resolution;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
module.exports = AssetModule_DEPRECATED;
|
||||
|
|
|
@ -36,6 +36,24 @@ describe('DependencyGraph', function() {
|
|||
var fileWatcher;
|
||||
var fs;
|
||||
|
||||
function getOrderedDependenciesAsJSON(dgraph, entry) {
|
||||
return dgraph.getOrderedDependencies(entry).then(
|
||||
deps => Promise.all(deps.map(dep => Promise.all([
|
||||
dep.getName(),
|
||||
dep.getDependencies(),
|
||||
]).then(([name, dependencies]) => ({
|
||||
path: dep.path,
|
||||
isJSON: dep.isJSON(),
|
||||
isAsset: dep.isAsset(),
|
||||
isAsset_DEPRECATED: dep.isAsset_DEPRECATED(),
|
||||
isPolyfill: dep.isPolyfill(),
|
||||
resolution: dep.resolution,
|
||||
id: name,
|
||||
dependencies
|
||||
})))
|
||||
));
|
||||
}
|
||||
|
||||
beforeEach(function() {
|
||||
fs = require('fs');
|
||||
Cache = require('../../../Cache');
|
||||
|
@ -76,7 +94,7 @@ describe('DependencyGraph', function() {
|
|||
assetExts: ['png', 'jpg'],
|
||||
cache: cache,
|
||||
});
|
||||
return dgraph.getOrderedDependencies('/root/index.js').then(function(deps) {
|
||||
return getOrderedDependenciesAsJSON(dgraph, '/root/index.js').then(function(deps) {
|
||||
expect(deps)
|
||||
.toEqual([
|
||||
{
|
||||
|
@ -134,7 +152,7 @@ describe('DependencyGraph', function() {
|
|||
assetExts: ['png', 'jpg'],
|
||||
cache: cache,
|
||||
});
|
||||
return dgraph.getOrderedDependencies('/root/index.js').then(function(deps) {
|
||||
return getOrderedDependenciesAsJSON(dgraph, '/root/index.js').then(function(deps) {
|
||||
expect(deps)
|
||||
.toEqual([
|
||||
{
|
||||
|
@ -186,7 +204,7 @@ describe('DependencyGraph', function() {
|
|||
assetExts: ['png', 'jpg'],
|
||||
cache: cache,
|
||||
});
|
||||
return dgraph.getOrderedDependencies('/root/index.js').then(function(deps) {
|
||||
return getOrderedDependenciesAsJSON(dgraph, '/root/index.js').then(function(deps) {
|
||||
expect(deps)
|
||||
.toEqual([
|
||||
{
|
||||
|
@ -246,7 +264,7 @@ describe('DependencyGraph', function() {
|
|||
assetRoots_DEPRECATED: ['/root/imgs'],
|
||||
cache: cache,
|
||||
});
|
||||
return dgraph.getOrderedDependencies('/root/index.js').then(function(deps) {
|
||||
return getOrderedDependenciesAsJSON(dgraph, '/root/index.js').then(function(deps) {
|
||||
expect(deps)
|
||||
.toEqual([
|
||||
{
|
||||
|
@ -298,7 +316,7 @@ describe('DependencyGraph', function() {
|
|||
assetExts: ['png', 'jpg'],
|
||||
cache: cache,
|
||||
});
|
||||
return dgraph.getOrderedDependencies('/root/index.js').then(function(deps) {
|
||||
return getOrderedDependenciesAsJSON(dgraph, '/root/index.js').then(function(deps) {
|
||||
expect(deps)
|
||||
.toEqual([
|
||||
{
|
||||
|
@ -355,7 +373,7 @@ describe('DependencyGraph', function() {
|
|||
assetExts: ['png', 'jpg'],
|
||||
cache: cache,
|
||||
});
|
||||
return dgraph.getOrderedDependencies('/root/index.js').then(function(deps) {
|
||||
return getOrderedDependenciesAsJSON(dgraph, '/root/index.js').then(function(deps) {
|
||||
expect(deps)
|
||||
.toEqual([
|
||||
{
|
||||
|
@ -433,7 +451,7 @@ describe('DependencyGraph', function() {
|
|||
assetRoots_DEPRECATED: ['/root/imgs'],
|
||||
cache: cache,
|
||||
});
|
||||
return dgraph.getOrderedDependencies('/root/index.js').then(function(deps) {
|
||||
return getOrderedDependenciesAsJSON(dgraph, '/root/index.js').then(function(deps) {
|
||||
expect(deps)
|
||||
.toEqual([
|
||||
{
|
||||
|
@ -495,7 +513,7 @@ describe('DependencyGraph', function() {
|
|||
assetExts: ['png', 'jpg'],
|
||||
cache: cache,
|
||||
});
|
||||
return dgraph.getOrderedDependencies('/root/index.js').then(function(deps) {
|
||||
return getOrderedDependenciesAsJSON(dgraph, '/root/index.js').then(function(deps) {
|
||||
expect(deps)
|
||||
.toEqual([
|
||||
{
|
||||
|
@ -548,7 +566,7 @@ describe('DependencyGraph', function() {
|
|||
assetExts: ['png', 'jpg'],
|
||||
cache: cache,
|
||||
});
|
||||
return dgraph.getOrderedDependencies('/root/index.js').then(function(deps) {
|
||||
return getOrderedDependenciesAsJSON(dgraph, '/root/index.js').then(function(deps) {
|
||||
expect(deps)
|
||||
.toEqual([
|
||||
{
|
||||
|
@ -601,7 +619,7 @@ describe('DependencyGraph', function() {
|
|||
assetExts: ['png', 'jpg'],
|
||||
cache: cache,
|
||||
});
|
||||
return dgraph.getOrderedDependencies('/root/index.js').then(function(deps) {
|
||||
return getOrderedDependenciesAsJSON(dgraph, '/root/index.js').then(function(deps) {
|
||||
expect(deps)
|
||||
.toEqual([
|
||||
{
|
||||
|
@ -662,7 +680,7 @@ describe('DependencyGraph', function() {
|
|||
assetExts: ['png', 'jpg'],
|
||||
cache: cache,
|
||||
});
|
||||
return dgraph.getOrderedDependencies('/root/index.js').then(function(deps) {
|
||||
return getOrderedDependenciesAsJSON(dgraph, '/root/index.js').then(function(deps) {
|
||||
expect(deps)
|
||||
.toEqual([
|
||||
{
|
||||
|
@ -719,7 +737,7 @@ describe('DependencyGraph', function() {
|
|||
assetExts: ['png', 'jpg'],
|
||||
cache: cache,
|
||||
});
|
||||
return dgraph.getOrderedDependencies('/root/index.js').then(function(deps) {
|
||||
return getOrderedDependenciesAsJSON(dgraph, '/root/index.js').then(function(deps) {
|
||||
expect(deps)
|
||||
.toEqual([
|
||||
{
|
||||
|
@ -770,7 +788,7 @@ describe('DependencyGraph', function() {
|
|||
assetExts: ['png', 'jpg'],
|
||||
cache: cache,
|
||||
});
|
||||
return dgraph.getOrderedDependencies('/root/index.js').then(function(deps) {
|
||||
return getOrderedDependenciesAsJSON(dgraph, '/root/index.js').then(function(deps) {
|
||||
expect(deps)
|
||||
.toEqual([
|
||||
{
|
||||
|
@ -820,7 +838,7 @@ describe('DependencyGraph', function() {
|
|||
assetExts: ['png', 'jpg'],
|
||||
cache: cache,
|
||||
});
|
||||
return dgraph.getOrderedDependencies('/root/index.js').then(function(deps) {
|
||||
return getOrderedDependenciesAsJSON(dgraph, '/root/index.js').then(function(deps) {
|
||||
expect(deps)
|
||||
.toEqual([
|
||||
{
|
||||
|
@ -867,7 +885,7 @@ describe('DependencyGraph', function() {
|
|||
assetExts: ['png', 'jpg'],
|
||||
cache: cache,
|
||||
});
|
||||
return dgraph.getOrderedDependencies('/root/index.js').then(function(deps) {
|
||||
return getOrderedDependenciesAsJSON(dgraph, '/root/index.js').then(function(deps) {
|
||||
expect(deps)
|
||||
.toEqual([
|
||||
{
|
||||
|
@ -918,7 +936,7 @@ describe('DependencyGraph', function() {
|
|||
assetExts: ['png', 'jpg'],
|
||||
cache: cache,
|
||||
});
|
||||
return dgraph.getOrderedDependencies('/root/index.js').then(function(deps) {
|
||||
return getOrderedDependenciesAsJSON(dgraph, '/root/index.js').then(function(deps) {
|
||||
expect(deps)
|
||||
.toEqual([
|
||||
{
|
||||
|
@ -967,7 +985,7 @@ describe('DependencyGraph', function() {
|
|||
assetExts: ['png', 'jpg'],
|
||||
cache: cache,
|
||||
});
|
||||
return dgraph.getOrderedDependencies('/root/index.js').then(function(deps) {
|
||||
return getOrderedDependenciesAsJSON(dgraph, '/root/index.js').then(function(deps) {
|
||||
expect(deps)
|
||||
.toEqual([
|
||||
{
|
||||
|
@ -1021,7 +1039,7 @@ describe('DependencyGraph', function() {
|
|||
assetExts: ['png', 'jpg'],
|
||||
cache: cache,
|
||||
});
|
||||
return dgraph.getOrderedDependencies('/root/somedir/somefile.js').then(function(deps) {
|
||||
return getOrderedDependenciesAsJSON(dgraph, '/root/somedir/somefile.js').then(function(deps) {
|
||||
expect(deps)
|
||||
.toEqual([
|
||||
{
|
||||
|
@ -1079,7 +1097,7 @@ describe('DependencyGraph', function() {
|
|||
assetExts: ['png', 'jpg'],
|
||||
cache: cache,
|
||||
});
|
||||
return dgraph.getOrderedDependencies('/root/index.js').then(function(deps) {
|
||||
return getOrderedDependenciesAsJSON(dgraph, '/root/index.js').then(function(deps) {
|
||||
expect(deps)
|
||||
.toEqual([
|
||||
{
|
||||
|
@ -1127,7 +1145,7 @@ describe('DependencyGraph', function() {
|
|||
assetExts: ['png', 'jpg'],
|
||||
cache: cache,
|
||||
});
|
||||
return dgraph.getOrderedDependencies('/root/index.js').then(function(deps) {
|
||||
return getOrderedDependenciesAsJSON(dgraph, '/root/index.js').then(function(deps) {
|
||||
expect(deps)
|
||||
.toEqual([
|
||||
{
|
||||
|
@ -1174,7 +1192,7 @@ describe('DependencyGraph', function() {
|
|||
assetExts: ['png', 'jpg'],
|
||||
cache: cache,
|
||||
});
|
||||
return dgraph.getOrderedDependencies('/root/index.js').then(function(deps) {
|
||||
return getOrderedDependenciesAsJSON(dgraph, '/root/index.js').then(function(deps) {
|
||||
expect(deps)
|
||||
.toEqual([
|
||||
{
|
||||
|
@ -1233,7 +1251,7 @@ describe('DependencyGraph', function() {
|
|||
assetExts: ['png', 'jpg'],
|
||||
cache: cache,
|
||||
});
|
||||
return dgraph.getOrderedDependencies('/root/index.js').then(function(deps) {
|
||||
return getOrderedDependenciesAsJSON(dgraph, '/root/index.js').then(function(deps) {
|
||||
expect(deps)
|
||||
.toEqual([
|
||||
{
|
||||
|
@ -1292,7 +1310,7 @@ describe('DependencyGraph', function() {
|
|||
assetExts: ['png', 'jpg'],
|
||||
cache: cache,
|
||||
});
|
||||
return dgraph.getOrderedDependencies('/root/index.js').then(function(deps) {
|
||||
return getOrderedDependenciesAsJSON(dgraph, '/root/index.js').then(function(deps) {
|
||||
expect(deps)
|
||||
.toEqual([
|
||||
{
|
||||
|
@ -1371,7 +1389,7 @@ describe('DependencyGraph', function() {
|
|||
assetExts: ['png', 'jpg'],
|
||||
cache: cache,
|
||||
});
|
||||
return dgraph.getOrderedDependencies('/root/index.js').then(function(deps) {
|
||||
return getOrderedDependenciesAsJSON(dgraph, '/root/index.js').then(function(deps) {
|
||||
expect(deps)
|
||||
.toEqual([
|
||||
{
|
||||
|
@ -1428,7 +1446,7 @@ describe('DependencyGraph', function() {
|
|||
assetExts: ['png', 'jpg'],
|
||||
cache: cache,
|
||||
});
|
||||
return dgraph.getOrderedDependencies('/root/index.js').then(function(deps) {
|
||||
return getOrderedDependenciesAsJSON(dgraph, '/root/index.js').then(function(deps) {
|
||||
expect(deps)
|
||||
.toEqual([
|
||||
{
|
||||
|
@ -1485,7 +1503,7 @@ describe('DependencyGraph', function() {
|
|||
assetExts: ['png', 'jpg'],
|
||||
cache: cache,
|
||||
});
|
||||
return dgraph.getOrderedDependencies('/root/index.js').then(function(deps) {
|
||||
return getOrderedDependenciesAsJSON(dgraph, '/root/index.js').then(function(deps) {
|
||||
expect(deps)
|
||||
.toEqual([
|
||||
{
|
||||
|
@ -1542,7 +1560,7 @@ describe('DependencyGraph', function() {
|
|||
assetExts: ['png', 'jpg'],
|
||||
cache: cache,
|
||||
});
|
||||
return dgraph.getOrderedDependencies('/root/index.js').then(function(deps) {
|
||||
return getOrderedDependenciesAsJSON(dgraph, '/root/index.js').then(function(deps) {
|
||||
expect(deps)
|
||||
.toEqual([
|
||||
{
|
||||
|
@ -1614,7 +1632,7 @@ describe('DependencyGraph', function() {
|
|||
assetExts: ['png', 'jpg'],
|
||||
cache: cache,
|
||||
});
|
||||
return dgraph.getOrderedDependencies('/root/index.js').then(function(deps) {
|
||||
return getOrderedDependenciesAsJSON(dgraph, '/root/index.js').then(function(deps) {
|
||||
expect(deps)
|
||||
.toEqual([
|
||||
{ id: 'index',
|
||||
|
@ -1717,7 +1735,7 @@ describe('DependencyGraph', function() {
|
|||
assetExts: ['png', 'jpg'],
|
||||
cache: cache,
|
||||
});
|
||||
return dgraph.getOrderedDependencies('/root/index.js').then(function(deps) {
|
||||
return getOrderedDependenciesAsJSON(dgraph, '/root/index.js').then(function(deps) {
|
||||
expect(deps)
|
||||
.toEqual([
|
||||
{ id: 'index',
|
||||
|
@ -1798,7 +1816,7 @@ describe('DependencyGraph', function() {
|
|||
assetExts: ['png', 'jpg'],
|
||||
cache: cache,
|
||||
});
|
||||
return dgraph.getOrderedDependencies('/root/index.js').then(function(deps) {
|
||||
return getOrderedDependenciesAsJSON(dgraph, '/root/index.js').then(function(deps) {
|
||||
expect(deps)
|
||||
.toEqual([
|
||||
{
|
||||
|
@ -1880,7 +1898,7 @@ describe('DependencyGraph', function() {
|
|||
assetExts: ['png', 'jpg'],
|
||||
cache: cache,
|
||||
});
|
||||
return dgraph.getOrderedDependencies('/root/index.ios.js').then(function(deps) {
|
||||
return getOrderedDependenciesAsJSON(dgraph, '/root/index.ios.js').then(function(deps) {
|
||||
expect(deps)
|
||||
.toEqual([
|
||||
{
|
||||
|
@ -1963,7 +1981,7 @@ describe('DependencyGraph', function() {
|
|||
assetExts: ['png', 'jpg'],
|
||||
cache: cache,
|
||||
});
|
||||
return dgraph.getOrderedDependencies('/root/index.js').then(function(deps) {
|
||||
return getOrderedDependenciesAsJSON(dgraph, '/root/index.js').then(function(deps) {
|
||||
expect(deps)
|
||||
.toEqual([
|
||||
{
|
||||
|
@ -2060,7 +2078,7 @@ describe('DependencyGraph', function() {
|
|||
assetExts: ['png', 'jpg'],
|
||||
cache: cache,
|
||||
});
|
||||
return dgraph.getOrderedDependencies('/root/index.js').then(function(deps) {
|
||||
return getOrderedDependenciesAsJSON(dgraph, '/root/index.js').then(function(deps) {
|
||||
expect(deps)
|
||||
.toEqual([
|
||||
{
|
||||
|
@ -2146,7 +2164,7 @@ describe('DependencyGraph', function() {
|
|||
assetExts: ['png', 'jpg'],
|
||||
cache: cache,
|
||||
});
|
||||
return dgraph.getOrderedDependencies('/root/index.js').then(function(deps) {
|
||||
return getOrderedDependenciesAsJSON(dgraph, '/root/index.js').then(function(deps) {
|
||||
expect(deps)
|
||||
.toEqual([
|
||||
{
|
||||
|
@ -2251,7 +2269,7 @@ describe('DependencyGraph', function() {
|
|||
assetExts: ['png', 'jpg'],
|
||||
cache: cache,
|
||||
});
|
||||
return dgraph.getOrderedDependencies('/root/index.js').then(function(deps) {
|
||||
return getOrderedDependenciesAsJSON(dgraph, '/root/index.js').then(function(deps) {
|
||||
expect(deps)
|
||||
.toEqual([
|
||||
{
|
||||
|
@ -2320,7 +2338,7 @@ describe('DependencyGraph', function() {
|
|||
assetExts: ['png', 'jpg'],
|
||||
cache: cache,
|
||||
});
|
||||
return dgraph.getOrderedDependencies('/react-tools/index.js').then(function(deps) {
|
||||
return getOrderedDependenciesAsJSON(dgraph, '/react-tools/index.js').then(function(deps) {
|
||||
expect(deps)
|
||||
.toEqual([
|
||||
{
|
||||
|
@ -2377,7 +2395,7 @@ describe('DependencyGraph', function() {
|
|||
assetExts: ['png', 'jpg'],
|
||||
cache: cache,
|
||||
});
|
||||
return dgraph.getOrderedDependencies('/root/index.js').then(function(deps) {
|
||||
return getOrderedDependenciesAsJSON(dgraph, '/root/index.js').then(function(deps) {
|
||||
expect(deps)
|
||||
.toEqual([
|
||||
{
|
||||
|
@ -2422,7 +2440,7 @@ describe('DependencyGraph', function() {
|
|||
assetExts: ['png', 'jpg'],
|
||||
cache: cache,
|
||||
});
|
||||
return dgraph.getOrderedDependencies('/root/index.js').then(function(deps) {
|
||||
return getOrderedDependenciesAsJSON(dgraph, '/root/index.js').then(function(deps) {
|
||||
expect(deps)
|
||||
.toEqual([
|
||||
{
|
||||
|
@ -2483,7 +2501,7 @@ describe('DependencyGraph', function() {
|
|||
assetExts: ['png', 'jpg'],
|
||||
cache: cache,
|
||||
});
|
||||
return dgraph.getOrderedDependencies('/root/index.ios.js').then(function(deps) {
|
||||
return getOrderedDependenciesAsJSON(dgraph, '/root/index.ios.js').then(function(deps) {
|
||||
expect(deps)
|
||||
.toEqual([
|
||||
{
|
||||
|
@ -2539,7 +2557,7 @@ describe('DependencyGraph', function() {
|
|||
assetExts: ['png', 'jpg'],
|
||||
cache: cache,
|
||||
});
|
||||
return dgraph.getOrderedDependencies('/root/index.ios.js').then(function(deps) {
|
||||
return getOrderedDependenciesAsJSON(dgraph, '/root/index.ios.js').then(function(deps) {
|
||||
expect(deps)
|
||||
.toEqual([
|
||||
{
|
||||
|
@ -2588,7 +2606,7 @@ describe('DependencyGraph', function() {
|
|||
assetExts: ['png', 'jpg'],
|
||||
cache: cache,
|
||||
});
|
||||
return dgraph.getOrderedDependencies('/root/index.ios.js').then(function(deps) {
|
||||
return getOrderedDependenciesAsJSON(dgraph, '/root/index.ios.js').then(function(deps) {
|
||||
expect(deps)
|
||||
.toEqual([
|
||||
{
|
||||
|
@ -2672,11 +2690,11 @@ describe('DependencyGraph', function() {
|
|||
assetExts: ['png', 'jpg'],
|
||||
cache: cache,
|
||||
});
|
||||
return dgraph.getOrderedDependencies('/root/index.js').then(function() {
|
||||
return getOrderedDependenciesAsJSON(dgraph, '/root/index.js').then(function() {
|
||||
filesystem.root['index.js'] =
|
||||
filesystem.root['index.js'].replace('require("foo")', '');
|
||||
triggerFileChange('change', 'index.js', root, mockStat);
|
||||
return dgraph.getOrderedDependencies('/root/index.js').then(function(deps) {
|
||||
return getOrderedDependenciesAsJSON(dgraph, '/root/index.js').then(function(deps) {
|
||||
expect(deps)
|
||||
.toEqual([
|
||||
{
|
||||
|
@ -2737,11 +2755,11 @@ describe('DependencyGraph', function() {
|
|||
assetExts: ['png', 'jpg'],
|
||||
cache: cache,
|
||||
});
|
||||
return dgraph.getOrderedDependencies('/root/index.js').then(function() {
|
||||
return getOrderedDependenciesAsJSON(dgraph, '/root/index.js').then(function() {
|
||||
filesystem.root['index.js'] =
|
||||
filesystem.root['index.js'].replace('require("foo")', '');
|
||||
triggerFileChange('change', 'index.js', root, mockStat);
|
||||
return dgraph.getOrderedDependencies('/root/index.js').then(function(deps) {
|
||||
return getOrderedDependenciesAsJSON(dgraph, '/root/index.js').then(function(deps) {
|
||||
expect(deps)
|
||||
.toEqual([
|
||||
{
|
||||
|
@ -2802,10 +2820,10 @@ describe('DependencyGraph', function() {
|
|||
assetExts: ['png', 'jpg'],
|
||||
cache: cache,
|
||||
});
|
||||
return dgraph.getOrderedDependencies('/root/index.js').then(function() {
|
||||
return getOrderedDependenciesAsJSON(dgraph, '/root/index.js').then(function() {
|
||||
delete filesystem.root.foo;
|
||||
triggerFileChange('delete', 'foo.js', root);
|
||||
return dgraph.getOrderedDependencies('/root/index.js').then(function(deps) {
|
||||
return getOrderedDependenciesAsJSON(dgraph, '/root/index.js').then(function(deps) {
|
||||
expect(deps)
|
||||
.toEqual([
|
||||
{
|
||||
|
@ -2866,7 +2884,7 @@ describe('DependencyGraph', function() {
|
|||
assetExts: ['png', 'jpg'],
|
||||
cache: cache,
|
||||
});
|
||||
return dgraph.getOrderedDependencies('/root/index.js').then(function() {
|
||||
return getOrderedDependenciesAsJSON(dgraph, '/root/index.js').then(function() {
|
||||
filesystem.root['bar.js'] = [
|
||||
'/**',
|
||||
' * @providesModule bar',
|
||||
|
@ -2878,7 +2896,7 @@ describe('DependencyGraph', function() {
|
|||
filesystem.root.aPackage['main.js'] = 'require("bar")';
|
||||
triggerFileChange('change', 'aPackage/main.js', root, mockStat);
|
||||
|
||||
return dgraph.getOrderedDependencies('/root/index.js').then(function(deps) {
|
||||
return getOrderedDependenciesAsJSON(dgraph, '/root/index.js').then(function(deps) {
|
||||
expect(deps)
|
||||
.toEqual([
|
||||
{
|
||||
|
@ -2949,7 +2967,7 @@ describe('DependencyGraph', function() {
|
|||
cache: cache,
|
||||
});
|
||||
|
||||
return dgraph.getOrderedDependencies('/root/index.js').then(function(deps) {
|
||||
return getOrderedDependenciesAsJSON(dgraph, '/root/index.js').then(function(deps) {
|
||||
expect(deps)
|
||||
.toEqual([
|
||||
{
|
||||
|
@ -2968,7 +2986,7 @@ describe('DependencyGraph', function() {
|
|||
filesystem.root['foo.png'] = '';
|
||||
triggerFileChange('add', 'foo.png', root, mockStat);
|
||||
|
||||
return dgraph.getOrderedDependencies('/root/index.js').then(function(deps2) {
|
||||
return getOrderedDependenciesAsJSON(dgraph, '/root/index.js').then(function(deps2) {
|
||||
expect(deps2)
|
||||
.toEqual([
|
||||
{
|
||||
|
@ -3021,7 +3039,7 @@ describe('DependencyGraph', function() {
|
|||
cache: cache,
|
||||
});
|
||||
|
||||
return dgraph.getOrderedDependencies('/root/index.js').then(function(deps) {
|
||||
return getOrderedDependenciesAsJSON(dgraph, '/root/index.js').then(function(deps) {
|
||||
expect(deps)
|
||||
.toEqual([
|
||||
{ id: 'index',
|
||||
|
@ -3039,7 +3057,7 @@ describe('DependencyGraph', function() {
|
|||
filesystem.root['foo.png'] = '';
|
||||
triggerFileChange('add', 'foo.png', root, mockStat);
|
||||
|
||||
return dgraph.getOrderedDependencies('/root/index.js').then(function(deps2) {
|
||||
return getOrderedDependenciesAsJSON(dgraph, '/root/index.js').then(function(deps2) {
|
||||
expect(deps2)
|
||||
.toEqual([
|
||||
{
|
||||
|
@ -3108,7 +3126,7 @@ describe('DependencyGraph', function() {
|
|||
},
|
||||
cache: cache,
|
||||
});
|
||||
return dgraph.getOrderedDependencies('/root/index.js').then(function() {
|
||||
return getOrderedDependenciesAsJSON(dgraph, '/root/index.js').then(function() {
|
||||
filesystem.root['bar.js'] = [
|
||||
'/**',
|
||||
' * @providesModule bar',
|
||||
|
@ -3120,7 +3138,7 @@ describe('DependencyGraph', function() {
|
|||
filesystem.root.aPackage['main.js'] = 'require("bar")';
|
||||
triggerFileChange('change', 'aPackage/main.js', root, mockStat);
|
||||
|
||||
return dgraph.getOrderedDependencies('/root/index.js').then(function(deps) {
|
||||
return getOrderedDependenciesAsJSON(dgraph, '/root/index.js').then(function(deps) {
|
||||
expect(deps)
|
||||
.toEqual([
|
||||
{
|
||||
|
@ -3193,11 +3211,11 @@ describe('DependencyGraph', function() {
|
|||
assetExts: ['png', 'jpg'],
|
||||
cache: cache,
|
||||
});
|
||||
return dgraph.getOrderedDependencies('/root/index.js').then(function() {
|
||||
return getOrderedDependenciesAsJSON(dgraph, '/root/index.js').then(function() {
|
||||
triggerFileChange('change', 'aPackage', '/root', {
|
||||
isDirectory: function(){ return true; }
|
||||
});
|
||||
return dgraph.getOrderedDependencies('/root/index.js').then(function(deps) {
|
||||
return getOrderedDependenciesAsJSON(dgraph, '/root/index.js').then(function(deps) {
|
||||
expect(deps)
|
||||
.toEqual([
|
||||
{
|
||||
|
@ -3264,7 +3282,7 @@ describe('DependencyGraph', function() {
|
|||
assetExts: ['png', 'jpg'],
|
||||
cache: cache,
|
||||
});
|
||||
return dgraph.getOrderedDependencies('/root/index.js').then(function() {
|
||||
return getOrderedDependenciesAsJSON(dgraph, '/root/index.js').then(function() {
|
||||
filesystem.root['index.js'] = filesystem.root['index.js'].replace(/aPackage/, 'bPackage');
|
||||
triggerFileChange('change', 'index.js', root, mockStat);
|
||||
|
||||
|
@ -3274,7 +3292,7 @@ describe('DependencyGraph', function() {
|
|||
});
|
||||
triggerFileChange('change', 'package.json', '/root/aPackage', mockStat);
|
||||
|
||||
return dgraph.getOrderedDependencies('/root/index.js').then(function(deps) {
|
||||
return getOrderedDependenciesAsJSON(dgraph, '/root/index.js').then(function(deps) {
|
||||
expect(deps)
|
||||
.toEqual([
|
||||
{
|
||||
|
@ -3331,7 +3349,7 @@ describe('DependencyGraph', function() {
|
|||
assetExts: ['png', 'jpg'],
|
||||
cache: cache,
|
||||
});
|
||||
return dgraph.getOrderedDependencies('/root/index.js').then(function() {
|
||||
return getOrderedDependenciesAsJSON(dgraph, '/root/index.js').then(function() {
|
||||
filesystem.root.aPackage['package.json'] = JSON.stringify({
|
||||
name: 'aPackage',
|
||||
main: 'main.js',
|
||||
|
@ -3339,7 +3357,7 @@ describe('DependencyGraph', function() {
|
|||
});
|
||||
triggerFileChange('change', 'package.json', '/root/aPackage', mockStat);
|
||||
|
||||
return dgraph.getOrderedDependencies('/root/index.js').then(function(deps) {
|
||||
return getOrderedDependenciesAsJSON(dgraph, '/root/index.js').then(function(deps) {
|
||||
expect(deps)
|
||||
.toEqual([
|
||||
{
|
||||
|
@ -3396,14 +3414,14 @@ describe('DependencyGraph', function() {
|
|||
assetExts: ['png', 'jpg'],
|
||||
cache: cache,
|
||||
});
|
||||
return dgraph.getOrderedDependencies('/root/index.js').then(function() {
|
||||
return getOrderedDependenciesAsJSON(dgraph, '/root/index.js').then(function() {
|
||||
filesystem.root.aPackage['package.json'] = JSON.stringify({
|
||||
name: 'bPackage',
|
||||
main: 'main.js',
|
||||
});
|
||||
triggerFileChange('change', 'package.json', '/root/aPackage', mockStat);
|
||||
|
||||
return dgraph.getOrderedDependencies('/root/index.js').then(function(deps) {
|
||||
return getOrderedDependenciesAsJSON(dgraph, '/root/index.js').then(function(deps) {
|
||||
expect(deps)
|
||||
.toEqual([
|
||||
{
|
||||
|
@ -3459,7 +3477,7 @@ describe('DependencyGraph', function() {
|
|||
assetExts: ['png', 'jpg'],
|
||||
cache: cache,
|
||||
});
|
||||
return dgraph.getOrderedDependencies('/root/index.js').then(function(deps) {
|
||||
return getOrderedDependenciesAsJSON(dgraph, '/root/index.js').then(function(deps) {
|
||||
expect(deps)
|
||||
.toEqual([
|
||||
{
|
||||
|
@ -3500,7 +3518,7 @@ describe('DependencyGraph', function() {
|
|||
filesystem.root.node_modules.foo['main.js'] = 'lol';
|
||||
triggerFileChange('change', 'main.js', '/root/node_modules/foo', mockStat);
|
||||
|
||||
return dgraph.getOrderedDependencies('/root/index.js').then(function(deps2) {
|
||||
return getOrderedDependenciesAsJSON(dgraph, '/root/index.js').then(function(deps2) {
|
||||
expect(deps2)
|
||||
.toEqual([
|
||||
{
|
||||
|
@ -3559,7 +3577,7 @@ describe('DependencyGraph', function() {
|
|||
assetExts: ['png', 'jpg'],
|
||||
cache: cache,
|
||||
});
|
||||
return dgraph.getOrderedDependencies('/root/index.js').then(function(deps) {
|
||||
return getOrderedDependenciesAsJSON(dgraph, '/root/index.js').then(function(deps) {
|
||||
filesystem.root.node_modules.foo['package.json'] = JSON.stringify({
|
||||
name: 'foo',
|
||||
main: 'main.js',
|
||||
|
@ -3567,7 +3585,7 @@ describe('DependencyGraph', function() {
|
|||
});
|
||||
triggerFileChange('change', 'package.json', '/root/node_modules/foo', mockStat);
|
||||
|
||||
return dgraph.getOrderedDependencies('/root/index.js').then(function(deps2) {
|
||||
return getOrderedDependenciesAsJSON(dgraph, '/root/index.js').then(function(deps2) {
|
||||
expect(deps2)
|
||||
.toEqual([
|
||||
{
|
||||
|
|
|
@ -13,7 +13,6 @@ const AssetModule_DEPRECATED = require('../AssetModule_DEPRECATED');
|
|||
const Fastfs = require('../fastfs');
|
||||
const ModuleCache = require('../ModuleCache');
|
||||
const Promise = require('promise');
|
||||
const _ = require('underscore');
|
||||
const crawl = require('../crawlers');
|
||||
const debug = require('debug')('DependencyGraph');
|
||||
const declareOpts = require('../../lib/declareOpts');
|
||||
|
@ -70,7 +69,7 @@ class DependencyGraph {
|
|||
constructor(options) {
|
||||
this._opts = validateOpts(options);
|
||||
this._hasteMap = Object.create(null);
|
||||
this._immediateResolutionCache = Object.create(null);
|
||||
this._resetResolutionCache();
|
||||
this._cache = this._opts.cache;
|
||||
this.load();
|
||||
}
|
||||
|
@ -80,7 +79,8 @@ class DependencyGraph {
|
|||
return this._loading;
|
||||
}
|
||||
|
||||
const crawlActivity = Activity.startEvent('fs crawl');
|
||||
const depGraphActivity = Activity.startEvent('Building Dependency Graph');
|
||||
const crawlActivity = Activity.startEvent('Crawling File System');
|
||||
const allRoots = this._opts.roots.concat(this._opts.assetRoots_DEPRECATED);
|
||||
this._crawling = crawl(allRoots, {
|
||||
ignore: this._opts.ignoreFilePath,
|
||||
|
@ -89,10 +89,15 @@ class DependencyGraph {
|
|||
});
|
||||
this._crawling.then((files) => Activity.endEvent(crawlActivity));
|
||||
|
||||
this._fastfs = new Fastfs(this._opts.roots, this._opts.fileWatcher, {
|
||||
ignore: this._opts.ignoreFilePath,
|
||||
crawling: this._crawling,
|
||||
});
|
||||
this._fastfs = new Fastfs(
|
||||
'JavaScript',
|
||||
this._opts.roots,
|
||||
this._opts.fileWatcher,
|
||||
{
|
||||
ignore: this._opts.ignoreFilePath,
|
||||
crawling: this._crawling,
|
||||
}
|
||||
);
|
||||
|
||||
this._fastfs.on('change', this._processFileChange.bind(this));
|
||||
|
||||
|
@ -102,19 +107,31 @@ class DependencyGraph {
|
|||
this._fastfs.build()
|
||||
.then(() => {
|
||||
const hasteActivity = Activity.startEvent('Building Haste Map');
|
||||
this._buildHasteMap().then(() => Activity.endEvent(hasteActivity));
|
||||
return this._buildHasteMap().then(() => Activity.endEvent(hasteActivity));
|
||||
}),
|
||||
this._buildAssetMap_DEPRECATED(),
|
||||
]);
|
||||
]).then(() =>
|
||||
Activity.endEvent(depGraphActivity)
|
||||
);
|
||||
|
||||
return this._loading;
|
||||
}
|
||||
|
||||
resolveDependency(fromModule, toModuleName) {
|
||||
if (fromModule._ref) {
|
||||
fromModule = fromModule._ref;
|
||||
setup({ platform }) {
|
||||
if (platform && this._opts.platforms.indexOf(platform) === -1) {
|
||||
throw new Error('Unrecognized platform: ' + platform);
|
||||
}
|
||||
|
||||
// TODO(amasad): This is a potential race condition. Mutliple requests could
|
||||
// interfere with each other. This needs a refactor to fix -- which will
|
||||
// follow this diff.
|
||||
if (this._platformExt !== platform) {
|
||||
this._resetResolutionCache();
|
||||
}
|
||||
this._platformExt = platform;
|
||||
}
|
||||
|
||||
resolveDependency(fromModule, toModuleName) {
|
||||
const resHash = resolutionHash(fromModule.path, toModuleName);
|
||||
|
||||
if (this._immediateResolutionCache[resHash]) {
|
||||
|
@ -163,33 +180,7 @@ class DependencyGraph {
|
|||
|
||||
getOrderedDependencies(entryPath) {
|
||||
return this.load().then(() => {
|
||||
const absPath = this._getAbsolutePath(entryPath);
|
||||
|
||||
if (absPath == null) {
|
||||
throw new NotFoundError(
|
||||
'Could not find source file at %s',
|
||||
entryPath
|
||||
);
|
||||
}
|
||||
|
||||
const absolutePath = path.resolve(absPath);
|
||||
|
||||
if (absolutePath == null) {
|
||||
throw new NotFoundError(
|
||||
'Cannot find entry file %s in any of the roots: %j',
|
||||
entryPath,
|
||||
this._opts.roots
|
||||
);
|
||||
}
|
||||
|
||||
const platformExt = getPlatformExt(entryPath);
|
||||
if (platformExt && this._opts.platforms.indexOf(platformExt) > -1) {
|
||||
this._platformExt = platformExt;
|
||||
} else {
|
||||
this._platformExt = null;
|
||||
}
|
||||
|
||||
const entry = this._moduleCache.getModule(absolutePath);
|
||||
const entry = this._getModuleForEntryPath(entryPath);
|
||||
const deps = [];
|
||||
const visited = Object.create(null);
|
||||
visited[entry.hash()] = true;
|
||||
|
@ -226,7 +217,22 @@ class DependencyGraph {
|
|||
};
|
||||
|
||||
return collect(entry)
|
||||
.then(() => Promise.all(deps.map(dep => dep.getPlainObject())));
|
||||
.then(() => deps);
|
||||
});
|
||||
}
|
||||
|
||||
getAsyncDependencies(entryPath) {
|
||||
return this.load().then(() => {
|
||||
const mod = this._getModuleForEntryPath(entryPath);
|
||||
return mod.getAsyncDependencies().then(bundles =>
|
||||
Promise
|
||||
.all(bundles.map(bundle =>
|
||||
Promise.all(bundle.map(
|
||||
dep => this.resolveDependency(mod, dep)
|
||||
))
|
||||
))
|
||||
.then(bs => bs.map(bundle => bundle.map(dep => dep.path)))
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -246,6 +252,39 @@ class DependencyGraph {
|
|||
return null;
|
||||
}
|
||||
|
||||
_getModuleForEntryPath(entryPath) {
|
||||
const absPath = this._getAbsolutePath(entryPath);
|
||||
|
||||
if (absPath == null) {
|
||||
throw new NotFoundError(
|
||||
'Could not find source file at %s',
|
||||
entryPath
|
||||
);
|
||||
}
|
||||
|
||||
const absolutePath = path.resolve(absPath);
|
||||
|
||||
if (absolutePath == null) {
|
||||
throw new NotFoundError(
|
||||
'Cannot find entry file %s in any of the roots: %j',
|
||||
entryPath,
|
||||
this._opts.roots
|
||||
);
|
||||
}
|
||||
|
||||
// `platformExt` could be set in the `setup` method.
|
||||
if (!this._platformExt) {
|
||||
const platformExt = getPlatformExt(entryPath);
|
||||
if (platformExt && this._opts.platforms.indexOf(platformExt) > -1) {
|
||||
this._platformExt = platformExt;
|
||||
} else {
|
||||
this._platformExt = null;
|
||||
}
|
||||
}
|
||||
|
||||
return this._moduleCache.getModule(absolutePath);
|
||||
}
|
||||
|
||||
_resolveHasteDependency(fromModule, toModuleName) {
|
||||
toModuleName = normalizePath(toModuleName);
|
||||
|
||||
|
@ -510,6 +549,7 @@ class DependencyGraph {
|
|||
this._assetMap_DEPRECATED = Object.create(null);
|
||||
|
||||
const fastfs = new Fastfs(
|
||||
'Assets',
|
||||
this._opts.assetRoots_DEPRECATED,
|
||||
this._opts.fileWatcher,
|
||||
{ ignore: this._opts.ignoreFilePath, crawling: this._crawling }
|
||||
|
@ -549,7 +589,7 @@ class DependencyGraph {
|
|||
_processFileChange(type, filePath, root, fstat) {
|
||||
// It's really hard to invalidate the right module resolution cache
|
||||
// so we just blow it up with every file change.
|
||||
this._immediateResolutionCache = Object.create(null);
|
||||
this._resetResolutionCache();
|
||||
|
||||
const absPath = path.join(root, filePath);
|
||||
if ((fstat && fstat.isDirectory()) ||
|
||||
|
@ -585,6 +625,10 @@ class DependencyGraph {
|
|||
});
|
||||
}
|
||||
}
|
||||
|
||||
_resetResolutionCache() {
|
||||
this._immediateResolutionCache = Object.create(null);
|
||||
}
|
||||
}
|
||||
|
||||
function assetName(file, ext) {
|
||||
|
|
|
@ -1,6 +1,13 @@
|
|||
/**
|
||||
* 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 docblock = require('./DependencyGraph/docblock');
|
||||
const isAbsolutePath = require('absolute-path');
|
||||
const path = require('path');
|
||||
|
@ -69,6 +76,10 @@ class Module {
|
|||
this._cache.invalidate(this.path);
|
||||
}
|
||||
|
||||
getAsyncDependencies() {
|
||||
return this._read().then(data => data.asyncDependencies);
|
||||
}
|
||||
|
||||
_read() {
|
||||
if (!this._reading) {
|
||||
this._reading = this._fastfs.readFile(this.path).then(content => {
|
||||
|
@ -85,7 +96,9 @@ class Module {
|
|||
if ('extern' in moduleDocBlock) {
|
||||
data.dependencies = [];
|
||||
} else {
|
||||
data.dependencies = extractRequires(content);
|
||||
var dependencies = extractRequires(content);
|
||||
data.dependencies = dependencies.sync;
|
||||
data.asyncDependencies = dependencies.async;
|
||||
}
|
||||
|
||||
return data;
|
||||
|
@ -95,49 +108,73 @@ class Module {
|
|||
return this._reading;
|
||||
}
|
||||
|
||||
getPlainObject() {
|
||||
return Promise.all([
|
||||
this.getName(),
|
||||
this.getDependencies(),
|
||||
]).then(([name, dependencies]) => this.addReference({
|
||||
path: this.path,
|
||||
isJSON: path.extname(this.path) === '.json',
|
||||
isAsset: false,
|
||||
isAsset_DEPRECATED: false,
|
||||
isPolyfill: false,
|
||||
resolution: undefined,
|
||||
id: name,
|
||||
dependencies
|
||||
}));
|
||||
}
|
||||
|
||||
hash() {
|
||||
return `Module : ${this.path}`;
|
||||
}
|
||||
|
||||
addReference(obj) {
|
||||
Object.defineProperty(obj, '_ref', { value: this });
|
||||
return obj;
|
||||
isJSON() {
|
||||
return path.extname(this.path) === '.json';
|
||||
}
|
||||
|
||||
isAsset() {
|
||||
return false;
|
||||
}
|
||||
|
||||
isPolyfill() {
|
||||
return false;
|
||||
}
|
||||
|
||||
isAsset_DEPRECATED() {
|
||||
return false;
|
||||
}
|
||||
|
||||
toJSON() {
|
||||
return {
|
||||
hash: this.hash(),
|
||||
isJSON: this.isJSON(),
|
||||
isAsset: this.isAsset(),
|
||||
isAsset_DEPRECATED: this.isAsset_DEPRECATED(),
|
||||
type: this.type,
|
||||
path: this.path,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Extract all required modules from a `code` string.
|
||||
*/
|
||||
var blockCommentRe = /\/\*(.|\n)*?\*\//g;
|
||||
var lineCommentRe = /\/\/.+(\n|$)/g;
|
||||
const blockCommentRe = /\/\*(.|\n)*?\*\//g;
|
||||
const lineCommentRe = /\/\/.+(\n|$)/g;
|
||||
function extractRequires(code /*: string*/) /*: Array<string>*/ {
|
||||
var deps = [];
|
||||
var deps = {
|
||||
sync: [],
|
||||
async: [],
|
||||
};
|
||||
|
||||
code
|
||||
.replace(blockCommentRe, '')
|
||||
.replace(lineCommentRe, '')
|
||||
// Parse sync dependencies. See comment below for further detils.
|
||||
.replace(replacePatterns.IMPORT_RE, (match, pre, quot, dep, post) => {
|
||||
deps.push(dep);
|
||||
deps.sync.push(dep);
|
||||
return match;
|
||||
})
|
||||
.replace(replacePatterns.REQUIRE_RE, function(match, pre, quot, dep, post) {
|
||||
deps.push(dep);
|
||||
// Parse the sync dependencies this module has. When the module is
|
||||
// required, all it's sync dependencies will be loaded into memory.
|
||||
// Sync dependencies can be defined either using `require` or the ES6
|
||||
// `import` syntax:
|
||||
// var dep1 = require('dep1');
|
||||
.replace(replacePatterns.REQUIRE_RE, (match, pre, quot, dep, post) => {
|
||||
deps.sync.push(dep);
|
||||
})
|
||||
// Parse async dependencies this module has. As opposed to what happens
|
||||
// with sync dependencies, when the module is required, it's async
|
||||
// dependencies won't be loaded into memory. This is deferred till the
|
||||
// code path gets to the import statement:
|
||||
// System.import('dep1')
|
||||
.replace(replacePatterns.SYSTEM_IMPORT_RE, (match, pre, quot, dep, post) => {
|
||||
deps.async.push([dep]);
|
||||
return match;
|
||||
});
|
||||
|
||||
return deps;
|
||||
|
|
|
@ -0,0 +1,38 @@
|
|||
'use strict';
|
||||
|
||||
const Promise = require('promise');
|
||||
const Module = require('./Module');
|
||||
|
||||
class Polyfill extends Module {
|
||||
constructor({ path, id, dependencies }) {
|
||||
super(path);
|
||||
this._id = id;
|
||||
this._dependencies = dependencies;
|
||||
}
|
||||
|
||||
isHaste() {
|
||||
return Promise.resolve(false);
|
||||
}
|
||||
|
||||
getName() {
|
||||
return Promise.resolve(this._id);
|
||||
}
|
||||
|
||||
getPackage() {
|
||||
return null;
|
||||
}
|
||||
|
||||
getDependencies() {
|
||||
return Promise.resolve(this._dependencies);
|
||||
}
|
||||
|
||||
isJSON() {
|
||||
return false;
|
||||
}
|
||||
|
||||
isPolyfill() {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = Polyfill;
|
|
@ -9,23 +9,24 @@
|
|||
'use strict';
|
||||
|
||||
jest.dontMock('../')
|
||||
.dontMock('q')
|
||||
.dontMock('../replacePatterns')
|
||||
.setMock('chalk', { dim: function(s) { return s; } });
|
||||
.dontMock('underscore')
|
||||
.dontMock('../replacePatterns');
|
||||
|
||||
jest.mock('path');
|
||||
|
||||
var Promise = require('promise');
|
||||
var _ = require('underscore');
|
||||
|
||||
describe('HasteDependencyResolver', function() {
|
||||
var HasteDependencyResolver;
|
||||
|
||||
function createModule(o) {
|
||||
o.getPlainObject = () => Promise.resolve(o);
|
||||
return o;
|
||||
}
|
||||
var Module;
|
||||
var Polyfill;
|
||||
|
||||
beforeEach(function() {
|
||||
Module = require('../Module');
|
||||
Polyfill = require('../Polyfill');
|
||||
Polyfill.mockClear();
|
||||
|
||||
// For the polyfillDeps
|
||||
require('path').join.mockImpl(function(a, b) {
|
||||
return b;
|
||||
|
@ -33,12 +34,16 @@ describe('HasteDependencyResolver', function() {
|
|||
HasteDependencyResolver = require('../');
|
||||
});
|
||||
|
||||
function createModule(id, dependencies) {
|
||||
var module = new Module();
|
||||
module.getName.mockImpl(() => Promise.resolve(id));
|
||||
module.getDependencies.mockImpl(() => Promise.resolve(dependencies));
|
||||
return module;
|
||||
}
|
||||
|
||||
describe('getDependencies', function() {
|
||||
pit('should get dependencies with polyfills', function() {
|
||||
var module = createModule({
|
||||
id: 'index',
|
||||
path: '/root/index.js', dependencies: ['a']
|
||||
});
|
||||
var module = createModule('index');
|
||||
var deps = [module];
|
||||
|
||||
var depResolver = new HasteDependencyResolver({
|
||||
|
@ -57,7 +62,8 @@ describe('HasteDependencyResolver', function() {
|
|||
return depResolver.getDependencies('/root/index.js', { dev: false })
|
||||
.then(function(result) {
|
||||
expect(result.mainModuleId).toEqual('index');
|
||||
expect(result.dependencies).toEqual([
|
||||
expect(result.dependencies[result.dependencies.length - 1]).toBe(module);
|
||||
expect(_.pluck(Polyfill.mock.calls, 0)).toEqual([
|
||||
{ path: 'polyfills/prelude.js',
|
||||
id: 'polyfills/prelude.js',
|
||||
isPolyfill: true,
|
||||
|
@ -115,18 +121,12 @@ describe('HasteDependencyResolver', function() {
|
|||
'polyfills/String.prototype.es6.js',
|
||||
],
|
||||
},
|
||||
module
|
||||
]);
|
||||
});
|
||||
});
|
||||
|
||||
pit('should get dependencies with polyfills', function() {
|
||||
var module = createModule({
|
||||
id: 'index',
|
||||
path: '/root/index.js',
|
||||
dependencies: ['a'],
|
||||
});
|
||||
|
||||
var module = createModule('index');
|
||||
var deps = [module];
|
||||
|
||||
var depResolver = new HasteDependencyResolver({
|
||||
|
@ -145,75 +145,15 @@ describe('HasteDependencyResolver', function() {
|
|||
return depResolver.getDependencies('/root/index.js', { dev: true })
|
||||
.then(function(result) {
|
||||
expect(result.mainModuleId).toEqual('index');
|
||||
expect(result.dependencies).toEqual([
|
||||
{ path: 'polyfills/prelude_dev.js',
|
||||
id: 'polyfills/prelude_dev.js',
|
||||
isPolyfill: true,
|
||||
dependencies: []
|
||||
},
|
||||
{ path: 'polyfills/require.js',
|
||||
id: 'polyfills/require.js',
|
||||
isPolyfill: true,
|
||||
dependencies: ['polyfills/prelude_dev.js']
|
||||
},
|
||||
{ path: 'polyfills/polyfills.js',
|
||||
id: 'polyfills/polyfills.js',
|
||||
isPolyfill: true,
|
||||
dependencies: ['polyfills/prelude_dev.js', 'polyfills/require.js']
|
||||
},
|
||||
{ id: 'polyfills/console.js',
|
||||
isPolyfill: true,
|
||||
path: 'polyfills/console.js',
|
||||
dependencies: [
|
||||
'polyfills/prelude_dev.js',
|
||||
'polyfills/require.js',
|
||||
'polyfills/polyfills.js'
|
||||
],
|
||||
},
|
||||
{ id: 'polyfills/error-guard.js',
|
||||
isPolyfill: true,
|
||||
path: 'polyfills/error-guard.js',
|
||||
dependencies: [
|
||||
'polyfills/prelude_dev.js',
|
||||
'polyfills/require.js',
|
||||
'polyfills/polyfills.js',
|
||||
'polyfills/console.js'
|
||||
],
|
||||
},
|
||||
{ id: 'polyfills/String.prototype.es6.js',
|
||||
isPolyfill: true,
|
||||
path: 'polyfills/String.prototype.es6.js',
|
||||
dependencies: [
|
||||
'polyfills/prelude_dev.js',
|
||||
'polyfills/require.js',
|
||||
'polyfills/polyfills.js',
|
||||
'polyfills/console.js',
|
||||
'polyfills/error-guard.js'
|
||||
],
|
||||
},
|
||||
{ id: 'polyfills/Array.prototype.es6.js',
|
||||
isPolyfill: true,
|
||||
path: 'polyfills/Array.prototype.es6.js',
|
||||
dependencies: [
|
||||
'polyfills/prelude_dev.js',
|
||||
'polyfills/require.js',
|
||||
'polyfills/polyfills.js',
|
||||
'polyfills/console.js',
|
||||
'polyfills/error-guard.js',
|
||||
'polyfills/String.prototype.es6.js'
|
||||
],
|
||||
},
|
||||
module
|
||||
]);
|
||||
expect(depGraph.getOrderedDependencies).toBeCalledWith('/root/index.js');
|
||||
expect(result.dependencies[0]).toBe(Polyfill.mock.instances[0]);
|
||||
expect(result.dependencies[result.dependencies.length - 1])
|
||||
.toBe(module);
|
||||
});
|
||||
});
|
||||
|
||||
pit('should pass in more polyfills', function() {
|
||||
var module = createModule({
|
||||
id: 'index',
|
||||
path: '/root/index.js',
|
||||
dependencies: ['a']
|
||||
});
|
||||
var module = createModule('index');
|
||||
var deps = [module];
|
||||
|
||||
var depResolver = new HasteDependencyResolver({
|
||||
|
@ -231,66 +171,9 @@ describe('HasteDependencyResolver', function() {
|
|||
});
|
||||
|
||||
return depResolver.getDependencies('/root/index.js', { dev: false })
|
||||
.then(function(result) {
|
||||
.then((result) => {
|
||||
expect(result.mainModuleId).toEqual('index');
|
||||
expect(result.dependencies).toEqual([
|
||||
{ path: 'polyfills/prelude.js',
|
||||
id: 'polyfills/prelude.js',
|
||||
isPolyfill: true,
|
||||
dependencies: []
|
||||
},
|
||||
{ path: 'polyfills/require.js',
|
||||
id: 'polyfills/require.js',
|
||||
isPolyfill: true,
|
||||
dependencies: ['polyfills/prelude.js']
|
||||
},
|
||||
{ path: 'polyfills/polyfills.js',
|
||||
id: 'polyfills/polyfills.js',
|
||||
isPolyfill: true,
|
||||
dependencies: ['polyfills/prelude.js', 'polyfills/require.js']
|
||||
},
|
||||
{ id: 'polyfills/console.js',
|
||||
isPolyfill: true,
|
||||
path: 'polyfills/console.js',
|
||||
dependencies: [
|
||||
'polyfills/prelude.js',
|
||||
'polyfills/require.js',
|
||||
'polyfills/polyfills.js'
|
||||
],
|
||||
},
|
||||
{ id: 'polyfills/error-guard.js',
|
||||
isPolyfill: true,
|
||||
path: 'polyfills/error-guard.js',
|
||||
dependencies: [
|
||||
'polyfills/prelude.js',
|
||||
'polyfills/require.js',
|
||||
'polyfills/polyfills.js',
|
||||
'polyfills/console.js'
|
||||
],
|
||||
},
|
||||
{ id: 'polyfills/String.prototype.es6.js',
|
||||
isPolyfill: true,
|
||||
path: 'polyfills/String.prototype.es6.js',
|
||||
dependencies: [
|
||||
'polyfills/prelude.js',
|
||||
'polyfills/require.js',
|
||||
'polyfills/polyfills.js',
|
||||
'polyfills/console.js',
|
||||
'polyfills/error-guard.js'
|
||||
],
|
||||
},
|
||||
{ id: 'polyfills/Array.prototype.es6.js',
|
||||
isPolyfill: true,
|
||||
path: 'polyfills/Array.prototype.es6.js',
|
||||
dependencies: [
|
||||
'polyfills/prelude.js',
|
||||
'polyfills/require.js',
|
||||
'polyfills/polyfills.js',
|
||||
'polyfills/console.js',
|
||||
'polyfills/error-guard.js',
|
||||
'polyfills/String.prototype.es6.js',
|
||||
],
|
||||
},
|
||||
expect(Polyfill.mock.calls[result.dependencies.length - 2]).toEqual([
|
||||
{ path: 'some module',
|
||||
id: 'some module',
|
||||
isPolyfill: true,
|
||||
|
@ -304,7 +187,6 @@ describe('HasteDependencyResolver', function() {
|
|||
'polyfills/Array.prototype.es6.js'
|
||||
]
|
||||
},
|
||||
module
|
||||
]);
|
||||
});
|
||||
});
|
||||
|
@ -463,25 +345,21 @@ describe('HasteDependencyResolver', function() {
|
|||
|
||||
depGraph.resolveDependency.mockImpl(function(fromModule, toModuleName) {
|
||||
if (toModuleName === 'x') {
|
||||
return Promise.resolve(createModule({
|
||||
id: 'changed'
|
||||
}));
|
||||
return Promise.resolve(createModule('changed'));
|
||||
} else if (toModuleName === 'y') {
|
||||
return Promise.resolve(createModule({ id: 'Y' }));
|
||||
return Promise.resolve(createModule('Y'));
|
||||
}
|
||||
|
||||
return Promise.resolve(null);
|
||||
});
|
||||
|
||||
return depResolver.wrapModule({
|
||||
id: 'test module',
|
||||
path: '/root/test.js',
|
||||
dependencies: dependencies
|
||||
}, code).then(processedCode => {
|
||||
|
||||
return depResolver.wrapModule(
|
||||
createModule('test module', ['x', 'y']),
|
||||
code
|
||||
).then(processedCode => {
|
||||
expect(processedCode).toEqual([
|
||||
'__d(\'test module\',["changed","Y"],function(global,' +
|
||||
' require, requireDynamic, requireLazy, module, exports) { ' +
|
||||
'__d(\'test module\',["changed","Y"],function(global, require,' +
|
||||
' module, exports) { ' +
|
||||
"import'x';",
|
||||
"import 'changed';",
|
||||
"import 'changed' ;",
|
||||
|
|
|
@ -0,0 +1,104 @@
|
|||
/**
|
||||
* 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';
|
||||
|
||||
jest
|
||||
.dontMock('absolute-path')
|
||||
.dontMock('../fastfs')
|
||||
.dontMock('../replacePatterns')
|
||||
.dontMock('../DependencyGraph/docblock')
|
||||
.dontMock('../../FileWatcher')
|
||||
.dontMock('../Module');
|
||||
|
||||
jest
|
||||
.mock('fs');
|
||||
|
||||
describe('Module', () => {
|
||||
var Fastfs;
|
||||
var Module;
|
||||
var ModuleCache;
|
||||
var Promise;
|
||||
var fs;
|
||||
|
||||
const FileWatcher = require('../../FileWatcher');
|
||||
const fileWatcher = new FileWatcher(['/root']);
|
||||
|
||||
beforeEach(function() {
|
||||
Fastfs = require('../fastfs');
|
||||
Module = require('../Module');
|
||||
ModuleCache = require('../ModuleCache');
|
||||
Promise = require('promise');
|
||||
fs = require('fs');
|
||||
});
|
||||
|
||||
describe('Async Dependencies', () => {
|
||||
function expectAsyncDependenciesToEqual(expected) {
|
||||
var fastfs = new Fastfs(
|
||||
'test',
|
||||
['/root'],
|
||||
fileWatcher,
|
||||
{crawling: Promise.resolve(['/root/index.js']), ignore: []},
|
||||
);
|
||||
|
||||
return fastfs.build().then(() => {
|
||||
var module = new Module('/root/index.js', fastfs, new ModuleCache(fastfs));
|
||||
|
||||
return module.getAsyncDependencies().then(actual =>
|
||||
expect(actual).toEqual(expected)
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
pit('should recognize single dependency', () => {
|
||||
fs.__setMockFilesystem({
|
||||
'root': {
|
||||
'index.js': 'System.import("dep1")',
|
||||
}
|
||||
});
|
||||
|
||||
return expectAsyncDependenciesToEqual([['dep1']]);
|
||||
});
|
||||
|
||||
pit('should parse single quoted dependencies', () => {
|
||||
fs.__setMockFilesystem({
|
||||
'root': {
|
||||
'index.js': 'System.import(\'dep1\')',
|
||||
}
|
||||
});
|
||||
|
||||
return expectAsyncDependenciesToEqual([['dep1']]);
|
||||
});
|
||||
|
||||
pit('should parse multiple async dependencies on the same module', () => {
|
||||
fs.__setMockFilesystem({
|
||||
'root': {
|
||||
'index.js': [
|
||||
'System.import("dep1")',
|
||||
'System.import("dep2")',
|
||||
].join('\n'),
|
||||
}
|
||||
});
|
||||
|
||||
return expectAsyncDependenciesToEqual([
|
||||
['dep1'],
|
||||
['dep2'],
|
||||
]);
|
||||
});
|
||||
|
||||
pit('parse fine new lines', () => {
|
||||
fs.__setMockFilesystem({
|
||||
'root': {
|
||||
'index.js': 'System.import(\n"dep1"\n)',
|
||||
}
|
||||
});
|
||||
|
||||
return expectAsyncDependenciesToEqual([['dep1']]);
|
||||
});
|
||||
});
|
||||
});
|
|
@ -14,8 +14,9 @@ const stat = Promise.denodeify(fs.stat);
|
|||
const hasOwn = Object.prototype.hasOwnProperty;
|
||||
|
||||
class Fastfs extends EventEmitter {
|
||||
constructor(roots, fileWatcher, {ignore, crawling}) {
|
||||
constructor(name, roots, fileWatcher, {ignore, crawling}) {
|
||||
super();
|
||||
this._name = name;
|
||||
this._fileWatcher = fileWatcher;
|
||||
this._ignore = ignore;
|
||||
this._roots = roots.map(root => new File(root, { isDir: true }));
|
||||
|
@ -29,7 +30,7 @@ class Fastfs extends EventEmitter {
|
|||
);
|
||||
|
||||
return this._crawling.then(files => {
|
||||
const fastfsActivity = Activity.startEvent('Building in-memory fs');
|
||||
const fastfsActivity = Activity.startEvent('Building in-memory fs for ' + this._name);
|
||||
files.forEach(filePath => {
|
||||
if (filePath.match(rootsPattern)) {
|
||||
const newFile = new File(filePath, { isDir: false });
|
||||
|
@ -155,7 +156,6 @@ class Fastfs extends EventEmitter {
|
|||
this._getAndAssertRoot(file.path).addChild(file);
|
||||
}
|
||||
|
||||
|
||||
_processFileChange(type, filePath, root, fstat) {
|
||||
const absPath = path.join(root, filePath);
|
||||
if (this._ignore(absPath) || (fstat && fstat.isDirectory())) {
|
||||
|
|
|
@ -11,6 +11,7 @@
|
|||
var path = require('path');
|
||||
var DependencyGraph = require('./DependencyGraph');
|
||||
var replacePatterns = require('./replacePatterns');
|
||||
var Polyfill = require('./Polyfill');
|
||||
var declareOpts = require('../lib/declareOpts');
|
||||
var Promise = require('promise');
|
||||
|
||||
|
@ -26,10 +27,6 @@ var validateOpts = declareOpts({
|
|||
type: 'array',
|
||||
default: [],
|
||||
},
|
||||
nonPersistent: {
|
||||
type: 'boolean',
|
||||
default: false,
|
||||
},
|
||||
moduleFormat: {
|
||||
type: 'string',
|
||||
default: 'haste',
|
||||
|
@ -76,6 +73,10 @@ var getDependenciesValidateOpts = declareOpts({
|
|||
type: 'boolean',
|
||||
default: true,
|
||||
},
|
||||
platform: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
},
|
||||
});
|
||||
|
||||
HasteDependencyResolver.prototype.getDependencies = function(main, options) {
|
||||
|
@ -83,18 +84,24 @@ HasteDependencyResolver.prototype.getDependencies = function(main, options) {
|
|||
|
||||
var depGraph = this._depGraph;
|
||||
var self = this;
|
||||
return depGraph.load().then(
|
||||
() => depGraph.getOrderedDependencies(main).then(
|
||||
dependencies => {
|
||||
const mainModuleId = dependencies[0].id;
|
||||
|
||||
depGraph.setup({ platform: opts.platform });
|
||||
|
||||
return Promise.all([
|
||||
depGraph.getOrderedDependencies(main),
|
||||
depGraph.getAsyncDependencies(main),
|
||||
]).then(
|
||||
([dependencies, asyncDependencies]) => dependencies[0].getName().then(
|
||||
mainModuleId => {
|
||||
self._prependPolyfillDependencies(
|
||||
dependencies,
|
||||
opts.dev
|
||||
opts.dev,
|
||||
);
|
||||
|
||||
return {
|
||||
mainModuleId: mainModuleId,
|
||||
dependencies: dependencies
|
||||
mainModuleId,
|
||||
dependencies,
|
||||
asyncDependencies,
|
||||
};
|
||||
}
|
||||
)
|
||||
|
@ -118,7 +125,7 @@ HasteDependencyResolver.prototype._prependPolyfillDependencies = function(
|
|||
].concat(this._polyfillModuleNames);
|
||||
|
||||
var polyfillModules = polyfillModuleNames.map(
|
||||
(polyfillModuleName, idx) => ({
|
||||
(polyfillModuleName, idx) => new Polyfill({
|
||||
path: polyfillModuleName,
|
||||
id: polyfillModuleName,
|
||||
dependencies: polyfillModuleNames.slice(0, idx),
|
||||
|
@ -130,23 +137,26 @@ HasteDependencyResolver.prototype._prependPolyfillDependencies = function(
|
|||
};
|
||||
|
||||
HasteDependencyResolver.prototype.wrapModule = function(module, code) {
|
||||
if (module.isPolyfill) {
|
||||
if (module.isPolyfill()) {
|
||||
return Promise.resolve(code);
|
||||
}
|
||||
|
||||
const resolvedDeps = Object.create(null);
|
||||
const resolvedDepsArr = [];
|
||||
|
||||
return Promise.all(
|
||||
module.dependencies.map(depName => {
|
||||
return this._depGraph.resolveDependency(module, depName)
|
||||
.then((dep) => dep && dep.getPlainObject().then(mod => {
|
||||
if (mod) {
|
||||
resolvedDeps[depName] = mod.id;
|
||||
resolvedDepsArr.push(mod.id);
|
||||
}
|
||||
}));
|
||||
})
|
||||
return module.getDependencies().then(
|
||||
dependencies => Promise.all(dependencies.map(
|
||||
depName => this._depGraph.resolveDependency(module, depName)
|
||||
.then(depModule => {
|
||||
if (depModule) {
|
||||
return depModule.getName().then(name => {
|
||||
resolvedDeps[depName] = name;
|
||||
resolvedDepsArr.push(name);
|
||||
});
|
||||
}
|
||||
})
|
||||
)
|
||||
)
|
||||
).then(() => {
|
||||
const relativizeCode = (codeMatch, pre, quot, depName, post) => {
|
||||
const depId = resolvedDeps[depName];
|
||||
|
@ -157,13 +167,15 @@ HasteDependencyResolver.prototype.wrapModule = function(module, code) {
|
|||
}
|
||||
};
|
||||
|
||||
return defineModuleCode({
|
||||
code: code
|
||||
return module.getName().then(
|
||||
name => defineModuleCode({
|
||||
code: code
|
||||
.replace(replacePatterns.IMPORT_RE, relativizeCode)
|
||||
.replace(replacePatterns.REQUIRE_RE, relativizeCode),
|
||||
deps: JSON.stringify(resolvedDepsArr),
|
||||
moduleName: module.id,
|
||||
});
|
||||
deps: JSON.stringify(resolvedDepsArr),
|
||||
moduleName: name,
|
||||
})
|
||||
);
|
||||
});
|
||||
};
|
||||
|
||||
|
@ -176,8 +188,7 @@ function defineModuleCode({moduleName, code, deps}) {
|
|||
`__d(`,
|
||||
`'${moduleName}',`,
|
||||
`${deps},`,
|
||||
'function(global, require, ',
|
||||
'requireDynamic, requireLazy, module, exports) {',
|
||||
'function(global, require, module, exports) {',
|
||||
` ${code}`,
|
||||
'\n});',
|
||||
].join('');
|
||||
|
|
|
@ -376,6 +376,12 @@
|
|||
var str = Array.prototype.map.call(arguments, function(arg) {
|
||||
return inspect(arg, {depth: 10});
|
||||
}).join(', ');
|
||||
if (str.slice(0, 10) === "'Warning: " && level >= LOG_LEVELS.error) {
|
||||
// React warnings use console.error so that a stack trace is shown,
|
||||
// but we don't (currently) want these to show a redbox
|
||||
// (Note: Logic duplicated in ExceptionsManager.js.)
|
||||
level = LOG_LEVELS.warn;
|
||||
}
|
||||
global.nativeLoggingHook(str, level);
|
||||
};
|
||||
}
|
||||
|
|
|
@ -303,6 +303,18 @@
|
|||
return _totalFactories;
|
||||
};
|
||||
|
||||
/**
|
||||
* Asynchronously loads any missing dependency and executes the provided
|
||||
* callback once all of them are satisfied.
|
||||
*
|
||||
* Note that the dependencies on the provided array must be string literals
|
||||
* as the packager uses this information to figure out how the modules are
|
||||
* packaged into different bundles.
|
||||
*/
|
||||
require.ensure = function(dependencies, callback) {
|
||||
throw '`require.ensure` is still not supported';
|
||||
};
|
||||
|
||||
/**
|
||||
* The define function conforming to CommonJS proposal:
|
||||
* http://wiki.commonjs.org/wiki/Modules/AsynchronousDefinition
|
||||
|
@ -464,56 +476,6 @@
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Special version of define that executes the factory as soon as all
|
||||
* dependencies are met.
|
||||
*
|
||||
* define() does just that, defines a module. Module's factory will not be
|
||||
* called until required by other module. This makes sense for most of our
|
||||
* library modules: we do not want to execute the factory unless it's being
|
||||
* used by someone.
|
||||
*
|
||||
* On the other hand there are modules, that you can call "entrance points".
|
||||
* You want to run the "factory" method for them as soon as all dependencies
|
||||
* are met.
|
||||
*
|
||||
* @example
|
||||
*
|
||||
* define('BaseClass', [], function() { return ... });
|
||||
* // ^^ factory for BaseClass was just stored in modulesMap
|
||||
*
|
||||
* define('SubClass', ['BaseClass'], function() { ... });
|
||||
* // SubClass module is marked as ready (waiting == 0), factory is just
|
||||
* // stored
|
||||
*
|
||||
* define('OtherClass, ['BaseClass'], function() { ... });
|
||||
* // OtherClass module is marked as ready (waiting == 0), factory is just
|
||||
* // stored
|
||||
*
|
||||
* requireLazy(['SubClass', 'ChatConfig'],
|
||||
* function() { ... });
|
||||
* // ChatRunner is waiting for ChatConfig to come
|
||||
*
|
||||
* define('ChatConfig', [], { foo: 'bar' });
|
||||
* // at this point ChatRunner is marked as ready, and its factory
|
||||
* // executed + all dependent factories are executed too: BaseClass,
|
||||
* // SubClass, ChatConfig notice that OtherClass's factory won't be
|
||||
* // executed unless explicitly required by someone
|
||||
*
|
||||
* @param {Array} dependencies
|
||||
* @param {Object|Function} factory
|
||||
*/
|
||||
function requireLazy(dependencies, factory, context) {
|
||||
return define(
|
||||
dependencies,
|
||||
factory,
|
||||
undefined,
|
||||
REQUIRE_WHEN_READY,
|
||||
context,
|
||||
1
|
||||
);
|
||||
}
|
||||
|
||||
function _uid() {
|
||||
return '__mod__' + _counter++;
|
||||
}
|
||||
|
@ -595,12 +557,8 @@
|
|||
|
||||
_register('global', global);
|
||||
_register('require', require);
|
||||
_register('requireDynamic', require);
|
||||
_register('requireLazy', requireLazy);
|
||||
|
||||
global.require = require;
|
||||
global.requireDynamic = require;
|
||||
global.requireLazy = requireLazy;
|
||||
|
||||
require.__debug = {
|
||||
modules: modulesMap,
|
||||
|
@ -621,8 +579,7 @@
|
|||
* out for every module which would be a lot of extra bytes.
|
||||
*/
|
||||
global.__d = function(id, deps, factory, _special, _inlineRequires) {
|
||||
var defaultDeps = ['global', 'require', 'requireDynamic', 'requireLazy',
|
||||
'module', 'exports'];
|
||||
var defaultDeps = ['global', 'require', 'module', 'exports'];
|
||||
define(id, defaultDeps.concat(deps), factory, _special || USED_AS_TRANSPORT,
|
||||
null, null, _inlineRequires);
|
||||
};
|
||||
|
|
|
@ -11,3 +11,5 @@
|
|||
|
||||
exports.IMPORT_RE = /(\bimport\s+?(?:.+\s+?from\s+?)?)(['"])([^'"]+)(\2)/g;
|
||||
exports.REQUIRE_RE = /(\brequire\s*?\(\s*?)(['"])([^'"]+)(\2\s*?\))/g;
|
||||
exports.SYSTEM_IMPORT_RE = /(\bSystem\.import\s*?\(\s*?)(['"])([^'"]+)(\2\s*?\))/g;
|
||||
|
||||
|
|
|
@ -8,19 +8,24 @@
|
|||
*/
|
||||
'use strict';
|
||||
|
||||
var fs = require('fs');
|
||||
var Promise = require('promise');
|
||||
var workerFarm = require('worker-farm');
|
||||
var declareOpts = require('../lib/declareOpts');
|
||||
var util = require('util');
|
||||
var ModuleTransport = require('../lib/ModuleTransport');
|
||||
const ModuleTransport = require('../lib/ModuleTransport');
|
||||
const Promise = require('promise');
|
||||
const declareOpts = require('../lib/declareOpts');
|
||||
const fs = require('fs');
|
||||
const util = require('util');
|
||||
const workerFarm = require('worker-farm');
|
||||
|
||||
var readFile = Promise.denodeify(fs.readFile);
|
||||
const readFile = Promise.denodeify(fs.readFile);
|
||||
|
||||
module.exports = Transformer;
|
||||
Transformer.TransformError = TransformError;
|
||||
// Avoid memory leaks caused in workers. This number seems to be a good enough number
|
||||
// to avoid any memory leak while not slowing down initial builds.
|
||||
// TODO(amasad): Once we get bundle splitting, we can drive this down a bit more.
|
||||
const MAX_CALLS_PER_WORKER = 600;
|
||||
|
||||
var validateOpts = declareOpts({
|
||||
// Worker will timeout if one of the callers timeout.
|
||||
const DEFAULT_MAX_CALL_TIME = 30000;
|
||||
|
||||
const validateOpts = declareOpts({
|
||||
projectRoots: {
|
||||
type: 'array',
|
||||
required: true,
|
||||
|
@ -40,51 +45,58 @@ var validateOpts = declareOpts({
|
|||
type: 'object',
|
||||
required: true,
|
||||
},
|
||||
transformTimeoutInterval: {
|
||||
type: 'number',
|
||||
default: DEFAULT_MAX_CALL_TIME,
|
||||
}
|
||||
});
|
||||
|
||||
function Transformer(options) {
|
||||
var opts = validateOpts(options);
|
||||
class Transformer {
|
||||
constructor(options) {
|
||||
const opts = this._opts = validateOpts(options);
|
||||
|
||||
this._cache = opts.cache;
|
||||
this._cache = opts.cache;
|
||||
|
||||
if (options.transformModulePath != null) {
|
||||
this._workers = workerFarm(
|
||||
{autoStart: true, maxConcurrentCallsPerWorker: 1},
|
||||
options.transformModulePath
|
||||
);
|
||||
if (opts.transformModulePath != null) {
|
||||
this._workers = workerFarm({
|
||||
autoStart: true,
|
||||
maxConcurrentCallsPerWorker: 1,
|
||||
maxCallsPerWorker: MAX_CALLS_PER_WORKER,
|
||||
maxCallTime: opts.transformTimeoutInterval,
|
||||
}, opts.transformModulePath);
|
||||
|
||||
this._transform = Promise.denodeify(this._workers);
|
||||
}
|
||||
}
|
||||
|
||||
Transformer.prototype.kill = function() {
|
||||
this._workers && workerFarm.end(this._workers);
|
||||
};
|
||||
|
||||
Transformer.prototype.invalidateFile = function(filePath) {
|
||||
this._cache.invalidate(filePath);
|
||||
};
|
||||
|
||||
Transformer.prototype.loadFileAndTransform = function(filePath) {
|
||||
if (this._transform == null) {
|
||||
return Promise.reject(new Error('No transfrom module'));
|
||||
this._transform = Promise.denodeify(this._workers);
|
||||
}
|
||||
}
|
||||
|
||||
var transform = this._transform;
|
||||
return this._cache.get(filePath, 'transformedSource', function() {
|
||||
// TODO: use fastfs to avoid reading file from disk again
|
||||
return readFile(filePath)
|
||||
.then(function(buffer) {
|
||||
var sourceCode = buffer.toString();
|
||||
kill() {
|
||||
this._workers && workerFarm.end(this._workers);
|
||||
}
|
||||
|
||||
return transform({
|
||||
sourceCode: sourceCode,
|
||||
filename: filePath,
|
||||
}).then(
|
||||
function(res) {
|
||||
invalidateFile(filePath) {
|
||||
this._cache.invalidate(filePath);
|
||||
}
|
||||
|
||||
loadFileAndTransform(filePath) {
|
||||
if (this._transform == null) {
|
||||
return Promise.reject(new Error('No transfrom module'));
|
||||
}
|
||||
|
||||
return this._cache.get(
|
||||
filePath,
|
||||
'transformedSource',
|
||||
// TODO: use fastfs to avoid reading file from disk again
|
||||
() => readFile(filePath).then(
|
||||
buffer => {
|
||||
const sourceCode = buffer.toString('utf8');
|
||||
|
||||
return this._transform({
|
||||
sourceCode,
|
||||
filename: filePath,
|
||||
}).then(res => {
|
||||
if (res.error) {
|
||||
console.warn(
|
||||
'Error property on the result value form the transformer',
|
||||
'Error property on the result value from the transformer',
|
||||
'module is deprecated and will be removed in future versions.',
|
||||
'Please pass an error object as the first argument to the callback'
|
||||
);
|
||||
|
@ -97,13 +109,28 @@ Transformer.prototype.loadFileAndTransform = function(filePath) {
|
|||
sourcePath: filePath,
|
||||
sourceCode: sourceCode,
|
||||
});
|
||||
}
|
||||
);
|
||||
}).catch(function(err) {
|
||||
throw formatError(err, filePath);
|
||||
});
|
||||
});
|
||||
};
|
||||
}).catch(err => {
|
||||
if (err.type === 'TimeoutError') {
|
||||
const timeoutErr = new Error(
|
||||
`TimeoutError: transforming ${filePath} took longer than ` +
|
||||
`${this._opts.transformTimeoutInterval / 1000} seconds.\n` +
|
||||
`You can adjust timeout via the 'transformTimeoutInterval' option`
|
||||
);
|
||||
timeoutErr.type = 'TimeoutError';
|
||||
throw timeoutErr;
|
||||
}
|
||||
|
||||
throw formatError(err, filePath);
|
||||
});
|
||||
})
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
module.exports = Transformer;
|
||||
|
||||
Transformer.TransformError = TransformError;
|
||||
|
||||
function TransformError() {
|
||||
Error.captureStackTrace && Error.captureStackTrace(this, TransformError);
|
||||
|
|
|
@ -1,300 +0,0 @@
|
|||
/**
|
||||
* 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';
|
||||
|
||||
var _ = require('underscore');
|
||||
var base64VLQ = require('./base64-vlq');
|
||||
var UglifyJS = require('uglify-js');
|
||||
var ModuleTransport = require('../lib/ModuleTransport');
|
||||
|
||||
module.exports = Package;
|
||||
|
||||
var SOURCEMAPPING_URL = '\n\/\/@ sourceMappingURL=';
|
||||
|
||||
function Package(sourceMapUrl) {
|
||||
this._finalized = false;
|
||||
this._modules = [];
|
||||
this._assets = [];
|
||||
this._sourceMapUrl = sourceMapUrl;
|
||||
this._shouldCombineSourceMaps = false;
|
||||
}
|
||||
|
||||
Package.prototype.setMainModuleId = function(moduleId) {
|
||||
this._mainModuleId = moduleId;
|
||||
};
|
||||
|
||||
Package.prototype.addModule = function(module) {
|
||||
if (!(module instanceof ModuleTransport)) {
|
||||
throw new Error('Expeceted a ModuleTransport object');
|
||||
}
|
||||
|
||||
// If we get a map from the transformer we'll switch to a mode
|
||||
// were we're combining the source maps as opposed to
|
||||
if (!this._shouldCombineSourceMaps && module.map != null) {
|
||||
this._shouldCombineSourceMaps = true;
|
||||
}
|
||||
|
||||
this._modules.push(module);
|
||||
};
|
||||
|
||||
Package.prototype.addAsset = function(asset) {
|
||||
this._assets.push(asset);
|
||||
};
|
||||
|
||||
Package.prototype.finalize = function(options) {
|
||||
options = options || {};
|
||||
if (options.runMainModule) {
|
||||
var runCode = ';require("' + this._mainModuleId + '");';
|
||||
this.addModule(new ModuleTransport({
|
||||
code: runCode,
|
||||
virtual: true,
|
||||
sourceCode: runCode,
|
||||
sourcePath: 'RunMainModule.js'
|
||||
}));
|
||||
}
|
||||
|
||||
Object.freeze(this._modules);
|
||||
Object.seal(this._modules);
|
||||
Object.freeze(this._assets);
|
||||
Object.seal(this._assets);
|
||||
this._finalized = true;
|
||||
};
|
||||
|
||||
Package.prototype._assertFinalized = function() {
|
||||
if (!this._finalized) {
|
||||
throw new Error('Package need to be finalized before getting any source');
|
||||
}
|
||||
};
|
||||
|
||||
Package.prototype._getSource = function() {
|
||||
if (this._source == null) {
|
||||
this._source = _.pluck(this._modules, 'code').join('\n');
|
||||
}
|
||||
return this._source;
|
||||
};
|
||||
|
||||
Package.prototype._getInlineSourceMap = function() {
|
||||
if (this._inlineSourceMap == null) {
|
||||
var sourceMap = this.getSourceMap({excludeSource: true});
|
||||
var encoded = new Buffer(JSON.stringify(sourceMap)).toString('base64');
|
||||
this._inlineSourceMap = 'data:application/json;base64,' + encoded;
|
||||
}
|
||||
return this._inlineSourceMap;
|
||||
};
|
||||
|
||||
Package.prototype.getSource = function(options) {
|
||||
this._assertFinalized();
|
||||
|
||||
options = options || {};
|
||||
|
||||
if (options.minify) {
|
||||
return this.getMinifiedSourceAndMap().code;
|
||||
}
|
||||
|
||||
var source = this._getSource();
|
||||
|
||||
if (options.inlineSourceMap) {
|
||||
source += SOURCEMAPPING_URL + this._getInlineSourceMap();
|
||||
} else if (this._sourceMapUrl) {
|
||||
source += SOURCEMAPPING_URL + this._sourceMapUrl;
|
||||
}
|
||||
|
||||
return source;
|
||||
};
|
||||
|
||||
Package.prototype.getMinifiedSourceAndMap = function() {
|
||||
this._assertFinalized();
|
||||
|
||||
var source = this._getSource();
|
||||
try {
|
||||
return UglifyJS.minify(source, {
|
||||
fromString: true,
|
||||
outSourceMap: 'bundle.js',
|
||||
inSourceMap: this.getSourceMap(),
|
||||
});
|
||||
} catch(e) {
|
||||
// Sometimes, when somebody is using a new syntax feature that we
|
||||
// don't yet have transform for, the untransformed line is sent to
|
||||
// uglify, and it chokes on it. This code tries to print the line
|
||||
// and the module for easier debugging
|
||||
var errorMessage = 'Error while minifying JS\n';
|
||||
if (e.line) {
|
||||
errorMessage += 'Transformed code line: "' +
|
||||
source.split('\n')[e.line - 1] + '"\n';
|
||||
}
|
||||
if (e.pos) {
|
||||
var fromIndex = source.lastIndexOf('__d(\'', e.pos);
|
||||
if (fromIndex > -1) {
|
||||
fromIndex += '__d(\''.length;
|
||||
var toIndex = source.indexOf('\'', fromIndex);
|
||||
errorMessage += 'Module name (best guess): ' +
|
||||
source.substring(fromIndex, toIndex) + '\n';
|
||||
}
|
||||
}
|
||||
errorMessage += e.toString();
|
||||
throw new Error(errorMessage);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* 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) {
|
||||
this._assertFinalized();
|
||||
|
||||
options = options || {};
|
||||
|
||||
if (this._shouldCombineSourceMaps) {
|
||||
return this._getCombinedSourceMaps(options);
|
||||
}
|
||||
|
||||
var mappings = this._getMappings();
|
||||
var map = {
|
||||
file: 'bundle.js',
|
||||
sources: _.pluck(this._modules, 'sourcePath'),
|
||||
version: 3,
|
||||
names: [],
|
||||
mappings: mappings,
|
||||
sourcesContent: options.excludeSource
|
||||
? [] : _.pluck(this._modules, 'sourceCode')
|
||||
};
|
||||
return map;
|
||||
};
|
||||
|
||||
Package.prototype.getAssets = function() {
|
||||
return this._assets;
|
||||
};
|
||||
|
||||
Package.prototype._getMappings = function() {
|
||||
var modules = this._modules;
|
||||
|
||||
// The first line mapping in our package is basically the base64vlq code for
|
||||
// zeros (A).
|
||||
var firstLine = 'AAAA';
|
||||
|
||||
// Most other lines in our mappings are all zeros (for module, column etc)
|
||||
// except for the lineno mappinp: curLineno - prevLineno = 1; Which is C.
|
||||
var line = 'AACA';
|
||||
|
||||
var moduleLines = Object.create(null);
|
||||
var mappings = '';
|
||||
for (var i = 0; i < modules.length; i++) {
|
||||
var module = modules[i];
|
||||
var code = module.code;
|
||||
var lastCharNewLine = false;
|
||||
moduleLines[module.sourcePath] = 0;
|
||||
for (var t = 0; t < code.length; t++) {
|
||||
if (t === 0 && i === 0) {
|
||||
mappings += firstLine;
|
||||
} else if (t === 0) {
|
||||
mappings += 'AC';
|
||||
|
||||
// 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
|
||||
// mapping is 0 (current) - number of lines in prev module.
|
||||
mappings += base64VLQ.encode(
|
||||
0 - moduleLines[modules[i - 1].sourcePath]
|
||||
);
|
||||
mappings += 'A';
|
||||
} else if (lastCharNewLine) {
|
||||
moduleLines[module.sourcePath]++;
|
||||
mappings += line;
|
||||
}
|
||||
lastCharNewLine = code[t] === '\n';
|
||||
if (lastCharNewLine) {
|
||||
mappings += ';';
|
||||
}
|
||||
}
|
||||
if (i !== modules.length - 1) {
|
||||
mappings += ';';
|
||||
}
|
||||
}
|
||||
return mappings;
|
||||
};
|
||||
|
||||
Package.prototype.getJSModulePaths = function() {
|
||||
return this._modules.filter(function(module) {
|
||||
// Filter out non-js files. Like images etc.
|
||||
return !module.virtual;
|
||||
}).map(function(module) {
|
||||
return module.sourcePath;
|
||||
});
|
||||
};
|
||||
|
||||
Package.prototype.getDebugInfo = function() {
|
||||
return [
|
||||
'<div><h3>Main Module:</h3> ' + this._mainModuleId + '</div>',
|
||||
'<style>',
|
||||
'pre.collapsed {',
|
||||
' height: 10px;',
|
||||
' width: 100px;',
|
||||
' display: block;',
|
||||
' text-overflow: ellipsis;',
|
||||
' overflow: hidden;',
|
||||
' cursor: pointer;',
|
||||
'}',
|
||||
'</style>',
|
||||
'<h3> Module paths and transformed code: </h3>',
|
||||
this._modules.map(function(m) {
|
||||
return '<div> <h4> Path: </h4>' + m.sourcePath + '<br/> <h4> Source: </h4>' +
|
||||
'<code><pre class="collapsed" onclick="this.classList.remove(\'collapsed\')">' +
|
||||
_.escape(m.code) + '</pre></code></div>';
|
||||
}).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.sourceCode ],
|
||||
};
|
||||
}
|
|
@ -1,289 +0,0 @@
|
|||
/**
|
||||
* 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';
|
||||
|
||||
var assert = require('assert');
|
||||
var fs = require('fs');
|
||||
var path = require('path');
|
||||
var Promise = require('promise');
|
||||
var Cache = require('../Cache');
|
||||
var Transformer = require('../JSTransformer');
|
||||
var DependencyResolver = require('../DependencyResolver');
|
||||
var Package = require('./Package');
|
||||
var Activity = require('../Activity');
|
||||
var ModuleTransport = require('../lib/ModuleTransport');
|
||||
var declareOpts = require('../lib/declareOpts');
|
||||
var imageSize = require('image-size');
|
||||
|
||||
var sizeOf = Promise.denodeify(imageSize);
|
||||
var readFile = Promise.denodeify(fs.readFile);
|
||||
|
||||
var validateOpts = declareOpts({
|
||||
projectRoots: {
|
||||
type: 'array',
|
||||
required: true,
|
||||
},
|
||||
blacklistRE: {
|
||||
type: 'object', // typeof regex is object
|
||||
},
|
||||
moduleFormat: {
|
||||
type: 'string',
|
||||
default: 'haste',
|
||||
},
|
||||
polyfillModuleNames: {
|
||||
type: 'array',
|
||||
default: [],
|
||||
},
|
||||
cacheVersion: {
|
||||
type: 'string',
|
||||
default: '1.0',
|
||||
},
|
||||
resetCache: {
|
||||
type: 'boolean',
|
||||
default: false,
|
||||
},
|
||||
transformModulePath: {
|
||||
type:'string',
|
||||
required: false,
|
||||
},
|
||||
nonPersistent: {
|
||||
type: 'boolean',
|
||||
default: false,
|
||||
},
|
||||
assetRoots: {
|
||||
type: 'array',
|
||||
required: false,
|
||||
},
|
||||
assetExts: {
|
||||
type: 'array',
|
||||
default: ['png'],
|
||||
},
|
||||
fileWatcher: {
|
||||
type: 'object',
|
||||
required: true,
|
||||
},
|
||||
assetServer: {
|
||||
type: 'object',
|
||||
required: true,
|
||||
}
|
||||
});
|
||||
|
||||
function Packager(options) {
|
||||
var opts = this._opts = validateOpts(options);
|
||||
|
||||
opts.projectRoots.forEach(verifyRootExists);
|
||||
|
||||
this._cache = opts.nonPersistent
|
||||
? new DummyCache()
|
||||
: new Cache({
|
||||
resetCache: opts.resetCache,
|
||||
cacheVersion: opts.cacheVersion,
|
||||
projectRoots: opts.projectRoots,
|
||||
transformModulePath: opts.transformModulePath,
|
||||
});
|
||||
|
||||
this._resolver = new DependencyResolver({
|
||||
projectRoots: opts.projectRoots,
|
||||
blacklistRE: opts.blacklistRE,
|
||||
polyfillModuleNames: opts.polyfillModuleNames,
|
||||
nonPersistent: opts.nonPersistent,
|
||||
moduleFormat: opts.moduleFormat,
|
||||
assetRoots: opts.assetRoots,
|
||||
fileWatcher: opts.fileWatcher,
|
||||
assetExts: opts.assetExts,
|
||||
cache: this._cache,
|
||||
});
|
||||
|
||||
this._transformer = new Transformer({
|
||||
projectRoots: opts.projectRoots,
|
||||
blacklistRE: opts.blacklistRE,
|
||||
cache: this._cache,
|
||||
transformModulePath: opts.transformModulePath,
|
||||
});
|
||||
|
||||
this._projectRoots = opts.projectRoots;
|
||||
this._assetServer = opts.assetServer;
|
||||
}
|
||||
|
||||
Packager.prototype.kill = function() {
|
||||
this._transformer.kill();
|
||||
return this._cache.end();
|
||||
};
|
||||
|
||||
Packager.prototype.package = function(main, runModule, sourceMapUrl, isDev) {
|
||||
var ppackage = new Package(sourceMapUrl);
|
||||
|
||||
var transformModule = this._transformModule.bind(this, ppackage);
|
||||
var findEventId = Activity.startEvent('find dependencies');
|
||||
var transformEventId;
|
||||
|
||||
return this.getDependencies(main, isDev)
|
||||
.then(function(result) {
|
||||
Activity.endEvent(findEventId);
|
||||
transformEventId = Activity.startEvent('transform');
|
||||
|
||||
ppackage.setMainModuleId(result.mainModuleId);
|
||||
return Promise.all(
|
||||
result.dependencies.map(transformModule)
|
||||
);
|
||||
})
|
||||
.then(function(transformedModules) {
|
||||
Activity.endEvent(transformEventId);
|
||||
|
||||
transformedModules.forEach(function(moduleTransport) {
|
||||
ppackage.addModule(moduleTransport);
|
||||
});
|
||||
|
||||
ppackage.finalize({ runMainModule: runModule });
|
||||
return ppackage;
|
||||
});
|
||||
};
|
||||
|
||||
Packager.prototype.invalidateFile = function(filePath) {
|
||||
this._transformer.invalidateFile(filePath);
|
||||
};
|
||||
|
||||
Packager.prototype.getDependencies = function(main, isDev) {
|
||||
return this._resolver.getDependencies(main, { dev: isDev });
|
||||
};
|
||||
|
||||
Packager.prototype._transformModule = function(ppackage, module) {
|
||||
var transform;
|
||||
|
||||
if (module.isAsset_DEPRECATED) {
|
||||
transform = this.generateAssetModule_DEPRECATED(ppackage, module);
|
||||
} else if (module.isAsset) {
|
||||
transform = this.generateAssetModule(ppackage, module);
|
||||
} else if (module.isJSON) {
|
||||
transform = generateJSONModule(module);
|
||||
} else {
|
||||
transform = this._transformer.loadFileAndTransform(
|
||||
path.resolve(module.path)
|
||||
);
|
||||
}
|
||||
|
||||
var resolver = this._resolver;
|
||||
return transform.then(
|
||||
transformed => resolver.wrapModule(module, transformed.code).then(
|
||||
code => new ModuleTransport({
|
||||
code: code,
|
||||
map: transformed.map,
|
||||
sourceCode: transformed.sourceCode,
|
||||
sourcePath: transformed.sourcePath,
|
||||
virtual: transformed.virtual,
|
||||
})
|
||||
)
|
||||
);
|
||||
};
|
||||
|
||||
Packager.prototype.getGraphDebugInfo = function() {
|
||||
return this._resolver.getDebugInfo();
|
||||
};
|
||||
|
||||
Packager.prototype.generateAssetModule_DEPRECATED = function(ppackage, module) {
|
||||
return sizeOf(module.path).then(function(dimensions) {
|
||||
var img = {
|
||||
__packager_asset: true,
|
||||
isStatic: true,
|
||||
path: module.path,
|
||||
uri: module.id.replace(/^[^!]+!/, ''),
|
||||
width: dimensions.width / module.resolution,
|
||||
height: dimensions.height / module.resolution,
|
||||
deprecated: true,
|
||||
};
|
||||
|
||||
ppackage.addAsset(img);
|
||||
|
||||
var code = 'module.exports = ' + JSON.stringify(img) + ';';
|
||||
|
||||
return new ModuleTransport({
|
||||
code: code,
|
||||
sourceCode: code,
|
||||
sourcePath: module.path,
|
||||
virtual: true,
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
Packager.prototype.generateAssetModule = function(ppackage, module) {
|
||||
var relPath = getPathRelativeToRoot(this._projectRoots, module.path);
|
||||
|
||||
return Promise.all([
|
||||
sizeOf(module.path),
|
||||
this._assetServer.getAssetData(relPath),
|
||||
]).then(function(res) {
|
||||
var dimensions = res[0];
|
||||
var assetData = res[1];
|
||||
var img = {
|
||||
__packager_asset: true,
|
||||
fileSystemLocation: path.dirname(module.path),
|
||||
httpServerLocation: path.join('/assets', path.dirname(relPath)),
|
||||
width: dimensions.width / module.resolution,
|
||||
height: dimensions.height / module.resolution,
|
||||
scales: assetData.scales,
|
||||
hash: assetData.hash,
|
||||
name: assetData.name,
|
||||
type: assetData.type,
|
||||
};
|
||||
|
||||
ppackage.addAsset(img);
|
||||
|
||||
var ASSET_TEMPLATE = 'module.exports = require("AssetRegistry").registerAsset(%json);';
|
||||
var code = ASSET_TEMPLATE.replace('%json', JSON.stringify(img));
|
||||
|
||||
return new ModuleTransport({
|
||||
code: code,
|
||||
sourceCode: code,
|
||||
sourcePath: module.path,
|
||||
virtual: true,
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
function generateJSONModule(module) {
|
||||
return readFile(module.path).then(function(data) {
|
||||
var code = 'module.exports = ' + data.toString('utf8') + ';';
|
||||
|
||||
return new ModuleTransport({
|
||||
code: code,
|
||||
sourceCode: code,
|
||||
sourcePath: module.path,
|
||||
virtual: true,
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function getPathRelativeToRoot(roots, absPath) {
|
||||
for (var i = 0; i < roots.length; i++) {
|
||||
var relPath = path.relative(roots[i], absPath);
|
||||
if (relPath[0] !== '.') {
|
||||
return relPath;
|
||||
}
|
||||
}
|
||||
|
||||
throw new Error(
|
||||
'Expected root module to be relative to one of the project roots'
|
||||
);
|
||||
}
|
||||
|
||||
function verifyRootExists(root) {
|
||||
// Verify that the root exists.
|
||||
assert(fs.statSync(root).isDirectory(), 'Root has to be a valid directory');
|
||||
}
|
||||
|
||||
class DummyCache {
|
||||
get(filepath, field, loaderCb) {
|
||||
return loaderCb();
|
||||
}
|
||||
|
||||
end(){}
|
||||
invalidate(filepath){}
|
||||
}
|
||||
|
||||
module.exports = Packager;
|
|
@ -8,70 +8,55 @@
|
|||
*/
|
||||
'use strict';
|
||||
|
||||
jest.setMock('worker-farm', function() { return function() {}; })
|
||||
jest.setMock('worker-farm', function() { return () => {}; })
|
||||
.dontMock('os')
|
||||
.dontMock('path')
|
||||
.dontMock('url')
|
||||
.setMock('timers', {
|
||||
setImmediate: function(fn) {
|
||||
return setTimeout(fn, 0);
|
||||
}
|
||||
})
|
||||
.setMock('timers', { setImmediate: (fn) => setTimeout(fn, 0) })
|
||||
.setMock('uglify-js')
|
||||
.dontMock('../')
|
||||
.setMock('chalk', { dim: function(s) { return s; } });
|
||||
|
||||
var Promise = require('promise');
|
||||
const Promise = require('promise');
|
||||
|
||||
describe('processRequest', function() {
|
||||
describe('processRequest', () => {
|
||||
var server;
|
||||
var Packager;
|
||||
var Bundler;
|
||||
var FileWatcher;
|
||||
|
||||
var options = {
|
||||
const options = {
|
||||
projectRoots: ['root'],
|
||||
blacklistRE: null,
|
||||
cacheVersion: null,
|
||||
polyfillModuleNames: null
|
||||
};
|
||||
|
||||
var makeRequest = function(requestHandler, requrl) {
|
||||
return new Promise(function(resolve) {
|
||||
requestHandler(
|
||||
{ url: requrl },
|
||||
{
|
||||
setHeader: jest.genMockFunction(),
|
||||
end: function(res) {
|
||||
resolve(res);
|
||||
}
|
||||
},
|
||||
{
|
||||
next: function() {}
|
||||
}
|
||||
);
|
||||
});
|
||||
};
|
||||
const makeRequest = (reqHandler, requrl) => new Promise(resolve =>
|
||||
reqHandler(
|
||||
{ url: requrl },
|
||||
{
|
||||
setHeader: jest.genMockFunction(),
|
||||
end: res => resolve(res),
|
||||
},
|
||||
{ next: () => {} },
|
||||
)
|
||||
);
|
||||
|
||||
var invalidatorFunc = jest.genMockFunction();
|
||||
var watcherFunc = jest.genMockFunction();
|
||||
const invalidatorFunc = jest.genMockFunction();
|
||||
const watcherFunc = jest.genMockFunction();
|
||||
var requestHandler;
|
||||
var triggerFileChange;
|
||||
|
||||
beforeEach(function() {
|
||||
Packager = require('../../Packager');
|
||||
beforeEach(() => {
|
||||
Bundler = require('../../Bundler');
|
||||
FileWatcher = require('../../FileWatcher');
|
||||
|
||||
Packager.prototype.package = jest.genMockFunction().mockImpl(function() {
|
||||
return Promise.resolve({
|
||||
getSource: function() {
|
||||
return 'this is the source';
|
||||
},
|
||||
getSourceMap: function() {
|
||||
return 'this is the source map';
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
Bundler.prototype.bundle = jest.genMockFunction().mockImpl(() =>
|
||||
Promise.resolve({
|
||||
getSource: () => 'this is the source',
|
||||
getSourceMap: () => 'this is the source map',
|
||||
})
|
||||
);
|
||||
|
||||
FileWatcher.prototype.on = function(eventType, callback) {
|
||||
if (eventType !== 'all') {
|
||||
|
@ -82,130 +67,141 @@ describe('processRequest', function() {
|
|||
return this;
|
||||
};
|
||||
|
||||
Packager.prototype.invalidateFile = invalidatorFunc;
|
||||
Bundler.prototype.invalidateFile = invalidatorFunc;
|
||||
|
||||
var Server = require('../');
|
||||
const Server = require('../');
|
||||
server = new Server(options);
|
||||
requestHandler = server.processRequest.bind(server);
|
||||
});
|
||||
|
||||
pit('returns JS bundle source on request of *.bundle',function() {
|
||||
pit('returns JS bundle source on request of *.bundle', () => {
|
||||
return makeRequest(
|
||||
requestHandler,
|
||||
'mybundle.bundle?runModule=true'
|
||||
).then(function(response) {
|
||||
expect(response).toEqual('this is the source');
|
||||
});
|
||||
).then(response =>
|
||||
expect(response).toEqual('this is the source')
|
||||
);
|
||||
});
|
||||
|
||||
pit('returns JS bundle source on request of *.bundle (compat)',function() {
|
||||
pit('returns JS bundle source on request of *.bundle (compat)', () => {
|
||||
return makeRequest(
|
||||
requestHandler,
|
||||
'mybundle.runModule.bundle'
|
||||
).then(function(response) {
|
||||
expect(response).toEqual('this is the source');
|
||||
});
|
||||
).then(response =>
|
||||
expect(response).toEqual('this is the source')
|
||||
);
|
||||
});
|
||||
|
||||
pit('returns sourcemap on request of *.map', function() {
|
||||
pit('returns sourcemap on request of *.map', () => {
|
||||
return makeRequest(
|
||||
requestHandler,
|
||||
'mybundle.map?runModule=true'
|
||||
).then(function(response) {
|
||||
expect(response).toEqual('"this is the source map"');
|
||||
});
|
||||
).then(response =>
|
||||
expect(response).toEqual('"this is the source map"')
|
||||
);
|
||||
});
|
||||
|
||||
pit('works with .ios.js extension', function() {
|
||||
pit('works with .ios.js extension', () => {
|
||||
return makeRequest(
|
||||
requestHandler,
|
||||
'index.ios.includeRequire.bundle'
|
||||
).then(function(response) {
|
||||
).then(response => {
|
||||
expect(response).toEqual('this is the source');
|
||||
expect(Packager.prototype.package).toBeCalledWith(
|
||||
expect(Bundler.prototype.bundle).toBeCalledWith(
|
||||
'index.ios.js',
|
||||
true,
|
||||
'index.ios.includeRequire.map',
|
||||
true
|
||||
true,
|
||||
undefined
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
pit('watches all files in projectRoot', function() {
|
||||
pit('passes in the platform param', function() {
|
||||
return makeRequest(
|
||||
requestHandler,
|
||||
'index.bundle?platform=ios'
|
||||
).then(function(response) {
|
||||
expect(response).toEqual('this is the source');
|
||||
expect(Bundler.prototype.bundle).toBeCalledWith(
|
||||
'index.js',
|
||||
true,
|
||||
'index.map',
|
||||
true,
|
||||
'ios',
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
pit('watches all files in projectRoot', () => {
|
||||
return makeRequest(
|
||||
requestHandler,
|
||||
'mybundle.bundle?runModule=true'
|
||||
).then(function() {
|
||||
).then(() => {
|
||||
expect(watcherFunc.mock.calls[0][0]).toEqual('all');
|
||||
expect(watcherFunc.mock.calls[0][1]).not.toBe(null);
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
describe('file changes', function() {
|
||||
pit('invalides files in package when file is updated', function() {
|
||||
describe('file changes', () => {
|
||||
pit('invalides files in bundle when file is updated', () => {
|
||||
return makeRequest(
|
||||
requestHandler,
|
||||
'mybundle.bundle?runModule=true'
|
||||
).then(function() {
|
||||
var onFileChange = watcherFunc.mock.calls[0][1];
|
||||
).then(() => {
|
||||
const onFileChange = watcherFunc.mock.calls[0][1];
|
||||
onFileChange('all','path/file.js', options.projectRoots[0]);
|
||||
expect(invalidatorFunc.mock.calls[0][0]).toEqual('root/path/file.js');
|
||||
});
|
||||
});
|
||||
|
||||
pit('rebuilds the packages that contain a file when that file is changed', function() {
|
||||
var packageFunc = jest.genMockFunction();
|
||||
packageFunc
|
||||
pit('rebuilds the bundles that contain a file when that file is changed', () => {
|
||||
const bundleFunc = jest.genMockFunction();
|
||||
bundleFunc
|
||||
.mockReturnValueOnce(
|
||||
Promise.resolve({
|
||||
getSource: function() {
|
||||
return 'this is the first source';
|
||||
},
|
||||
getSourceMap: function() {},
|
||||
getSource: () => 'this is the first source',
|
||||
getSourceMap: () => {},
|
||||
})
|
||||
)
|
||||
.mockReturnValue(
|
||||
Promise.resolve({
|
||||
getSource: function() {
|
||||
return 'this is the rebuilt source';
|
||||
},
|
||||
getSourceMap: function() {},
|
||||
getSource: () => 'this is the rebuilt source',
|
||||
getSourceMap: () => {},
|
||||
})
|
||||
);
|
||||
|
||||
Packager.prototype.package = packageFunc;
|
||||
Bundler.prototype.bundle = bundleFunc;
|
||||
|
||||
var Server = require('../../Server');
|
||||
const Server = require('../../Server');
|
||||
server = new Server(options);
|
||||
|
||||
requestHandler = server.processRequest.bind(server);
|
||||
|
||||
|
||||
return makeRequest(requestHandler, 'mybundle.bundle?runModule=true')
|
||||
.then(function(response) {
|
||||
.then(response => {
|
||||
expect(response).toEqual('this is the first source');
|
||||
expect(packageFunc.mock.calls.length).toBe(1);
|
||||
expect(bundleFunc.mock.calls.length).toBe(1);
|
||||
triggerFileChange('all','path/file.js', options.projectRoots[0]);
|
||||
jest.runAllTimers();
|
||||
jest.runAllTimers();
|
||||
})
|
||||
.then(function() {
|
||||
expect(packageFunc.mock.calls.length).toBe(2);
|
||||
.then(() => {
|
||||
expect(bundleFunc.mock.calls.length).toBe(2);
|
||||
return makeRequest(requestHandler, 'mybundle.bundle?runModule=true')
|
||||
.then(function(response) {
|
||||
expect(response).toEqual('this is the rebuilt source');
|
||||
});
|
||||
.then(response =>
|
||||
expect(response).toEqual('this is the rebuilt source')
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('/onchange endpoint', function() {
|
||||
describe('/onchange endpoint', () => {
|
||||
var EventEmitter;
|
||||
var req;
|
||||
var res;
|
||||
|
||||
beforeEach(function() {
|
||||
beforeEach(() => {
|
||||
EventEmitter = require.requireActual('events').EventEmitter;
|
||||
req = new EventEmitter();
|
||||
req.url = '/onchange';
|
||||
|
@ -215,14 +211,14 @@ describe('processRequest', function() {
|
|||
};
|
||||
});
|
||||
|
||||
it('should hold on to request and inform on change', function() {
|
||||
it('should hold on to request and inform on change', () => {
|
||||
server.processRequest(req, res);
|
||||
triggerFileChange('all', 'path/file.js', options.projectRoots[0]);
|
||||
jest.runAllTimers();
|
||||
expect(res.end).toBeCalledWith(JSON.stringify({changed: true}));
|
||||
});
|
||||
|
||||
it('should not inform changes on disconnected clients', function() {
|
||||
it('should not inform changes on disconnected clients', () => {
|
||||
server.processRequest(req, res);
|
||||
req.emit('close');
|
||||
jest.runAllTimers();
|
||||
|
@ -232,56 +228,52 @@ describe('processRequest', function() {
|
|||
});
|
||||
});
|
||||
|
||||
describe('/assets endpoint', function() {
|
||||
describe('/assets endpoint', () => {
|
||||
var AssetServer;
|
||||
beforeEach(function() {
|
||||
beforeEach(() => {
|
||||
AssetServer = require('../../AssetServer');
|
||||
});
|
||||
|
||||
it('should serve simple case', function() {
|
||||
var req = {
|
||||
url: '/assets/imgs/a.png',
|
||||
};
|
||||
var res = {
|
||||
end: jest.genMockFn(),
|
||||
};
|
||||
it('should serve simple case', () => {
|
||||
const req = {url: '/assets/imgs/a.png'};
|
||||
const res = {end: jest.genMockFn()};
|
||||
|
||||
AssetServer.prototype.get.mockImpl(function() {
|
||||
return Promise.resolve('i am image');
|
||||
});
|
||||
AssetServer.prototype.get.mockImpl(() => Promise.resolve('i am image'));
|
||||
|
||||
server.processRequest(req, res);
|
||||
jest.runAllTimers();
|
||||
expect(res.end).toBeCalledWith('i am image');
|
||||
});
|
||||
|
||||
it('should return 404', function() {
|
||||
it('should return 404', () => {
|
||||
|
||||
});
|
||||
});
|
||||
|
||||
describe('buildPackage(options)', function() {
|
||||
it('Calls the packager with the correct args', function() {
|
||||
server.buildPackage({
|
||||
describe('buildbundle(options)', () => {
|
||||
it('Calls the bundler with the correct args', () => {
|
||||
server.buildBundle({
|
||||
entryFile: 'foo file'
|
||||
});
|
||||
expect(Packager.prototype.package).toBeCalledWith(
|
||||
expect(Bundler.prototype.bundle).toBeCalledWith(
|
||||
'foo file',
|
||||
true,
|
||||
undefined,
|
||||
true
|
||||
true,
|
||||
undefined
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe('buildPackageFromUrl(options)', function() {
|
||||
it('Calls the packager with the correct args', function() {
|
||||
server.buildPackageFromUrl('/path/to/foo.bundle?dev=false&runModule=false');
|
||||
expect(Packager.prototype.package).toBeCalledWith(
|
||||
describe('buildBundleFromUrl(options)', () => {
|
||||
it('Calls the bundler with the correct args', () => {
|
||||
server.buildBundleFromUrl('/path/to/foo.bundle?dev=false&runModule=false');
|
||||
expect(Bundler.prototype.bundle).toBeCalledWith(
|
||||
'path/to/foo.js',
|
||||
false,
|
||||
'/path/to/foo.map',
|
||||
false
|
||||
false,
|
||||
undefined
|
||||
);
|
||||
});
|
||||
});
|
||||
|
|
|
@ -8,21 +8,20 @@
|
|||
*/
|
||||
'use strict';
|
||||
|
||||
var url = require('url');
|
||||
var path = require('path');
|
||||
var declareOpts = require('../lib/declareOpts');
|
||||
var FileWatcher = require('../FileWatcher');
|
||||
var Packager = require('../Packager');
|
||||
var Activity = require('../Activity');
|
||||
var AssetServer = require('../AssetServer');
|
||||
var Promise = require('promise');
|
||||
var _ = require('underscore');
|
||||
var exec = require('child_process').exec;
|
||||
var fs = require('fs');
|
||||
const Activity = require('../Activity');
|
||||
const AssetServer = require('../AssetServer');
|
||||
const FileWatcher = require('../FileWatcher');
|
||||
const Bundler = require('../Bundler');
|
||||
const Promise = require('promise');
|
||||
|
||||
module.exports = Server;
|
||||
const _ = require('underscore');
|
||||
const declareOpts = require('../lib/declareOpts');
|
||||
const exec = require('child_process').exec;
|
||||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
const url = require('url');
|
||||
|
||||
var validateOpts = declareOpts({
|
||||
const validateOpts = declareOpts({
|
||||
projectRoots: {
|
||||
type: 'array',
|
||||
required: true,
|
||||
|
@ -62,117 +61,13 @@ var validateOpts = declareOpts({
|
|||
type: 'array',
|
||||
default: ['png', 'jpg', 'jpeg', 'bmp', 'gif', 'webp'],
|
||||
},
|
||||
transformTimeoutInterval: {
|
||||
type: 'number',
|
||||
required: false,
|
||||
},
|
||||
});
|
||||
|
||||
function Server(options) {
|
||||
var opts = validateOpts(options);
|
||||
|
||||
this._projectRoots = opts.projectRoots;
|
||||
this._packages = Object.create(null);
|
||||
this._changeWatchers = [];
|
||||
|
||||
var assetGlobs = opts.assetExts.map(function(ext) {
|
||||
return '**/*.' + ext;
|
||||
});
|
||||
|
||||
var watchRootConfigs = opts.projectRoots.map(function(dir) {
|
||||
return {
|
||||
dir: dir,
|
||||
globs: [
|
||||
'**/*.js',
|
||||
'**/*.json',
|
||||
].concat(assetGlobs),
|
||||
};
|
||||
});
|
||||
|
||||
if (opts.assetRoots != null) {
|
||||
watchRootConfigs = watchRootConfigs.concat(
|
||||
opts.assetRoots.map(function(dir) {
|
||||
return {
|
||||
dir: dir,
|
||||
globs: assetGlobs,
|
||||
};
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
this._fileWatcher = options.nonPersistent
|
||||
? FileWatcher.createDummyWatcher()
|
||||
: new FileWatcher(watchRootConfigs);
|
||||
|
||||
this._assetServer = new AssetServer({
|
||||
projectRoots: opts.projectRoots,
|
||||
assetExts: opts.assetExts,
|
||||
});
|
||||
|
||||
var packagerOpts = Object.create(opts);
|
||||
packagerOpts.fileWatcher = this._fileWatcher;
|
||||
packagerOpts.assetServer = this._assetServer;
|
||||
this._packager = new Packager(packagerOpts);
|
||||
|
||||
var onFileChange = this._onFileChange.bind(this);
|
||||
this._fileWatcher.on('all', onFileChange);
|
||||
|
||||
var self = this;
|
||||
this._debouncedFileChangeHandler = _.debounce(function(filePath) {
|
||||
self._rebuildPackages(filePath);
|
||||
self._informChangeWatchers();
|
||||
}, 50);
|
||||
}
|
||||
|
||||
Server.prototype._onFileChange = function(type, filepath, root) {
|
||||
var absPath = path.join(root, filepath);
|
||||
this._packager.invalidateFile(absPath);
|
||||
// Make sure the file watcher event runs through the system before
|
||||
// we rebuild the packages.
|
||||
this._debouncedFileChangeHandler(absPath);
|
||||
};
|
||||
|
||||
Server.prototype._rebuildPackages = function() {
|
||||
var buildPackage = this.buildPackage.bind(this);
|
||||
var packages = this._packages;
|
||||
|
||||
Object.keys(packages).forEach(function(optionsJson) {
|
||||
var options = JSON.parse(optionsJson);
|
||||
// Wait for a previous build (if exists) to finish.
|
||||
packages[optionsJson] = (packages[optionsJson] || Promise.resolve()).finally(function() {
|
||||
// With finally promise callback we can't change the state of the promise
|
||||
// so we need to reassign the promise.
|
||||
packages[optionsJson] = buildPackage(options).then(function(p) {
|
||||
// Make a throwaway call to getSource to cache the source string.
|
||||
p.getSource({
|
||||
inlineSourceMap: options.inlineSourceMap,
|
||||
minify: options.minify,
|
||||
});
|
||||
return p;
|
||||
});
|
||||
});
|
||||
return packages[optionsJson];
|
||||
});
|
||||
};
|
||||
|
||||
Server.prototype._informChangeWatchers = function() {
|
||||
var watchers = this._changeWatchers;
|
||||
var headers = {
|
||||
'Content-Type': 'application/json; charset=UTF-8',
|
||||
};
|
||||
|
||||
watchers.forEach(function(w) {
|
||||
w.res.writeHead(205, headers);
|
||||
w.res.end(JSON.stringify({ changed: true }));
|
||||
});
|
||||
|
||||
this._changeWatchers = [];
|
||||
};
|
||||
|
||||
Server.prototype.end = function() {
|
||||
Promise.all([
|
||||
this._fileWatcher.end(),
|
||||
this._packager.kill(),
|
||||
]);
|
||||
};
|
||||
|
||||
var packageOpts = declareOpts({
|
||||
const bundleOpts = declareOpts({
|
||||
sourceMapUrl: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
|
@ -197,247 +92,365 @@ var packageOpts = declareOpts({
|
|||
type: 'boolean',
|
||||
default: false,
|
||||
},
|
||||
platform: {
|
||||
type: 'string',
|
||||
required: false,
|
||||
}
|
||||
});
|
||||
|
||||
Server.prototype.buildPackage = function(options) {
|
||||
var opts = packageOpts(options);
|
||||
class Server {
|
||||
constructor(options) {
|
||||
const opts = validateOpts(options);
|
||||
|
||||
return this._packager.package(
|
||||
opts.entryFile,
|
||||
opts.runModule,
|
||||
opts.sourceMapUrl,
|
||||
opts.dev
|
||||
);
|
||||
};
|
||||
this._projectRoots = opts.projectRoots;
|
||||
this._bundles = Object.create(null);
|
||||
this._changeWatchers = [];
|
||||
|
||||
Server.prototype.buildPackageFromUrl = function(reqUrl) {
|
||||
var options = getOptionsFromUrl(reqUrl);
|
||||
return this.buildPackage(options);
|
||||
};
|
||||
const assetGlobs = opts.assetExts.map(ext => '**/*.' + ext);
|
||||
|
||||
Server.prototype.getDependencies = function(main) {
|
||||
return this._packager.getDependencies(main);
|
||||
};
|
||||
var watchRootConfigs = opts.projectRoots.map(dir => {
|
||||
return {
|
||||
dir: dir,
|
||||
globs: [
|
||||
'**/*.js',
|
||||
'**/*.json',
|
||||
].concat(assetGlobs),
|
||||
};
|
||||
});
|
||||
|
||||
Server.prototype._processDebugRequest = function(reqUrl, res) {
|
||||
var ret = '<!doctype html>';
|
||||
var pathname = url.parse(reqUrl).pathname;
|
||||
var parts = pathname.split('/').filter(Boolean);
|
||||
if (parts.length === 1) {
|
||||
ret += '<div><a href="/debug/packages">Cached Packages</a></div>';
|
||||
ret += '<div><a href="/debug/graph">Dependency Graph</a></div>';
|
||||
res.end(ret);
|
||||
} else if (parts[1] === 'packages') {
|
||||
ret += '<h1> Cached Packages </h1>';
|
||||
Promise.all(Object.keys(this._packages).map(function(optionsJson) {
|
||||
return this._packages[optionsJson].then(function(p) {
|
||||
ret += '<div><h2>' + optionsJson + '</h2>';
|
||||
ret += p.getDebugInfo();
|
||||
});
|
||||
}, this)).then(
|
||||
function() { res.end(ret); },
|
||||
function(e) {
|
||||
res.writeHead(500);
|
||||
res.end('Internal Error');
|
||||
console.log(e.stack);
|
||||
}
|
||||
);
|
||||
} else if (parts[1] === 'graph'){
|
||||
ret += '<h1> Dependency Graph </h2>';
|
||||
ret += this._packager.getGraphDebugInfo();
|
||||
res.end(ret);
|
||||
} else {
|
||||
res.writeHead('404');
|
||||
res.end('Invalid debug request');
|
||||
return;
|
||||
}
|
||||
};
|
||||
|
||||
Server.prototype._processOnChangeRequest = function(req, res) {
|
||||
var watchers = this._changeWatchers;
|
||||
|
||||
watchers.push({
|
||||
req: req,
|
||||
res: res,
|
||||
});
|
||||
|
||||
req.on('close', function() {
|
||||
for (var i = 0; i < watchers.length; i++) {
|
||||
if (watchers[i] && watchers[i].req === req) {
|
||||
watchers.splice(i, 1);
|
||||
break;
|
||||
}
|
||||
if (opts.assetRoots != null) {
|
||||
watchRootConfigs = watchRootConfigs.concat(
|
||||
opts.assetRoots.map(dir => {
|
||||
return {
|
||||
dir: dir,
|
||||
globs: assetGlobs,
|
||||
};
|
||||
})
|
||||
);
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
Server.prototype._processAssetsRequest = function(req, res) {
|
||||
var urlObj = url.parse(req.url, true);
|
||||
var assetPath = urlObj.pathname.match(/^\/assets\/(.+)$/);
|
||||
this._assetServer.get(assetPath[1])
|
||||
.then(
|
||||
function(data) {
|
||||
res.end(data);
|
||||
},
|
||||
function(error) {
|
||||
console.error(error.stack);
|
||||
res.writeHead('404');
|
||||
res.end('Asset not found');
|
||||
}
|
||||
).done();
|
||||
};
|
||||
this._fileWatcher = options.nonPersistent
|
||||
? FileWatcher.createDummyWatcher()
|
||||
: new FileWatcher(watchRootConfigs);
|
||||
|
||||
Server.prototype._processProfile = function(req, res) {
|
||||
console.log('Dumping profile information...');
|
||||
var dumpName = '/tmp/dump_' + Date.now() + '.json';
|
||||
var prefix = process.env.TRACE_VIEWER_PATH || '';
|
||||
var cmd = path.join(prefix, 'trace2html') + ' ' + dumpName;
|
||||
fs.writeFileSync(dumpName, req.rawBody);
|
||||
exec(cmd, function (error) {
|
||||
if (error) {
|
||||
if (error.code === 127) {
|
||||
console.error(
|
||||
'\n** Failed executing `' + cmd + '` **\n\n' +
|
||||
'Google trace-viewer is required to visualize the data, do you have it installled?\n\n' +
|
||||
'You can get it at:\n\n' +
|
||||
' https://github.com/google/trace-viewer\n\n' +
|
||||
'If it\'s not in your path, you can set a custom path with:\n\n' +
|
||||
' TRACE_VIEWER_PATH=/path/to/trace-viewer\n\n' +
|
||||
'NOTE: Your profile data was kept at:\n\n' +
|
||||
' ' + dumpName
|
||||
);
|
||||
} else {
|
||||
console.error('Unknown error', error);
|
||||
}
|
||||
res.end();
|
||||
return;
|
||||
this._assetServer = new AssetServer({
|
||||
projectRoots: opts.projectRoots,
|
||||
assetExts: opts.assetExts,
|
||||
});
|
||||
|
||||
const bundlerOpts = Object.create(opts);
|
||||
bundlerOpts.fileWatcher = this._fileWatcher;
|
||||
bundlerOpts.assetServer = this._assetServer;
|
||||
this._bundler = new Bundler(bundlerOpts);
|
||||
|
||||
this._fileWatcher.on('all', this._onFileChange.bind(this));
|
||||
|
||||
this._debouncedFileChangeHandler = _.debounce(filePath => {
|
||||
this._rebuildBundles(filePath);
|
||||
this._informChangeWatchers();
|
||||
}, 50);
|
||||
}
|
||||
|
||||
end() {
|
||||
Promise.all([
|
||||
this._fileWatcher.end(),
|
||||
this._bundler.kill(),
|
||||
]);
|
||||
}
|
||||
|
||||
buildBundle(options) {
|
||||
const opts = bundleOpts(options);
|
||||
return this._bundler.bundle(
|
||||
opts.entryFile,
|
||||
opts.runModule,
|
||||
opts.sourceMapUrl,
|
||||
opts.dev,
|
||||
opts.platform
|
||||
);
|
||||
}
|
||||
|
||||
buildBundleFromUrl(reqUrl) {
|
||||
const options = this._getOptionsFromUrl(reqUrl);
|
||||
return this.buildBundle(options);
|
||||
}
|
||||
|
||||
getDependencies(main) {
|
||||
return this._bundler.getDependencies(main);
|
||||
}
|
||||
|
||||
_onFileChange(type, filepath, root) {
|
||||
const absPath = path.join(root, filepath);
|
||||
this._bundler.invalidateFile(absPath);
|
||||
// Make sure the file watcher event runs through the system before
|
||||
// we rebuild the bundles.
|
||||
this._debouncedFileChangeHandler(absPath);
|
||||
}
|
||||
|
||||
_rebuildBundles() {
|
||||
const buildBundle = this.buildBundle.bind(this);
|
||||
const bundles = this._bundles;
|
||||
|
||||
Object.keys(bundles).forEach(function(optionsJson) {
|
||||
const options = JSON.parse(optionsJson);
|
||||
// Wait for a previous build (if exists) to finish.
|
||||
bundles[optionsJson] = (bundles[optionsJson] || Promise.resolve()).finally(function() {
|
||||
// With finally promise callback we can't change the state of the promise
|
||||
// so we need to reassign the promise.
|
||||
bundles[optionsJson] = buildBundle(options).then(function(p) {
|
||||
// Make a throwaway call to getSource to cache the source string.
|
||||
p.getSource({
|
||||
inlineSourceMap: options.inlineSourceMap,
|
||||
minify: options.minify,
|
||||
});
|
||||
return p;
|
||||
});
|
||||
});
|
||||
return bundles[optionsJson];
|
||||
});
|
||||
}
|
||||
|
||||
_informChangeWatchers() {
|
||||
const watchers = this._changeWatchers;
|
||||
const headers = {
|
||||
'Content-Type': 'application/json; charset=UTF-8',
|
||||
};
|
||||
|
||||
watchers.forEach(function(w) {
|
||||
w.res.writeHead(205, headers);
|
||||
w.res.end(JSON.stringify({ changed: true }));
|
||||
});
|
||||
|
||||
this._changeWatchers = [];
|
||||
}
|
||||
|
||||
_processDebugRequest(reqUrl, res) {
|
||||
var ret = '<!doctype html>';
|
||||
const pathname = url.parse(reqUrl).pathname;
|
||||
const parts = pathname.split('/').filter(Boolean);
|
||||
if (parts.length === 1) {
|
||||
ret += '<div><a href="/debug/bundles">Cached Bundles</a></div>';
|
||||
ret += '<div><a href="/debug/graph">Dependency Graph</a></div>';
|
||||
res.end(ret);
|
||||
} else if (parts[1] === 'bundles') {
|
||||
ret += '<h1> Cached Bundles </h1>';
|
||||
Promise.all(Object.keys(this._bundles).map(optionsJson =>
|
||||
this._bundles[optionsJson].then(p => {
|
||||
ret += '<div><h2>' + optionsJson + '</h2>';
|
||||
ret += p.getDebugInfo();
|
||||
})
|
||||
)).then(
|
||||
() => res.end(ret),
|
||||
e => {
|
||||
res.writeHead(500);
|
||||
res.end('Internal Error');
|
||||
console.log(e.stack);
|
||||
}
|
||||
);
|
||||
} else if (parts[1] === 'graph'){
|
||||
ret += '<h1> Dependency Graph </h2>';
|
||||
ret += this._bundler.getGraphDebugInfo();
|
||||
res.end(ret);
|
||||
} else {
|
||||
exec('rm ' + dumpName);
|
||||
exec('open ' + dumpName.replace(/json$/, 'html'), function (error) {
|
||||
if (error) {
|
||||
console.error(error);
|
||||
res.writeHead('404');
|
||||
res.end('Invalid debug request');
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
_processOnChangeRequest(req, res) {
|
||||
const watchers = this._changeWatchers;
|
||||
|
||||
watchers.push({
|
||||
req: req,
|
||||
res: res,
|
||||
});
|
||||
|
||||
req.on('close', () => {
|
||||
for (let i = 0; i < watchers.length; i++) {
|
||||
if (watchers[i] && watchers[i].req === req) {
|
||||
watchers.splice(i, 1);
|
||||
break;
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
_processAssetsRequest(req, res) {
|
||||
const urlObj = url.parse(req.url, true);
|
||||
const assetPath = urlObj.pathname.match(/^\/assets\/(.+)$/);
|
||||
this._assetServer.get(assetPath[1])
|
||||
.then(
|
||||
data => res.end(data),
|
||||
error => {
|
||||
console.error(error.stack);
|
||||
res.writeHead('404');
|
||||
res.end('Asset not found');
|
||||
}
|
||||
).done();
|
||||
}
|
||||
|
||||
_processProfile(req, res) {
|
||||
console.log('Dumping profile information...');
|
||||
const dumpName = '/tmp/dump_' + Date.now() + '.json';
|
||||
const prefix = process.env.TRACE_VIEWER_PATH || '';
|
||||
const cmd = path.join(prefix, 'trace2html') + ' ' + dumpName;
|
||||
fs.writeFileSync(dumpName, req.rawBody);
|
||||
exec(cmd, error => {
|
||||
if (error) {
|
||||
if (error.code === 127) {
|
||||
console.error(
|
||||
'\n** Failed executing `' + cmd + '` **\n\n' +
|
||||
'Google trace-viewer is required to visualize the data, do you have it installled?\n\n' +
|
||||
'You can get it at:\n\n' +
|
||||
' https://github.com/google/trace-viewer\n\n' +
|
||||
'If it\'s not in your path, you can set a custom path with:\n\n' +
|
||||
' TRACE_VIEWER_PATH=/path/to/trace-viewer\n\n' +
|
||||
'NOTE: Your profile data was kept at:\n\n' +
|
||||
' ' + dumpName
|
||||
);
|
||||
} else {
|
||||
console.error('Unknown error', error);
|
||||
}
|
||||
res.end();
|
||||
});
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
Server.prototype.processRequest = function(req, res, next) {
|
||||
var urlObj = url.parse(req.url, true);
|
||||
var pathname = urlObj.pathname;
|
||||
|
||||
var requestType;
|
||||
if (pathname.match(/\.bundle$/)) {
|
||||
requestType = 'bundle';
|
||||
} else if (pathname.match(/\.map$/)) {
|
||||
requestType = 'map';
|
||||
} else if (pathname.match(/^\/debug/)) {
|
||||
this._processDebugRequest(req.url, res);
|
||||
return;
|
||||
} else if (pathname.match(/^\/onchange\/?$/)) {
|
||||
this._processOnChangeRequest(req, res);
|
||||
return;
|
||||
} else if (pathname.match(/^\/assets\//)) {
|
||||
this._processAssetsRequest(req, res);
|
||||
return;
|
||||
} else if (pathname.match(/^\/profile\/?$/)) {
|
||||
this._processProfile(req, res);
|
||||
return;
|
||||
} else {
|
||||
next();
|
||||
return;
|
||||
}
|
||||
|
||||
var startReqEventId = Activity.startEvent('request:' + req.url);
|
||||
var options = getOptionsFromUrl(req.url);
|
||||
var optionsJson = JSON.stringify(options);
|
||||
var building = this._packages[optionsJson] || this.buildPackage(options);
|
||||
|
||||
this._packages[optionsJson] = building;
|
||||
building.then(
|
||||
function(p) {
|
||||
if (requestType === 'bundle') {
|
||||
var bundleSource = p.getSource({
|
||||
inlineSourceMap: options.inlineSourceMap,
|
||||
minify: options.minify,
|
||||
return;
|
||||
} else {
|
||||
exec('rm ' + dumpName);
|
||||
exec('open ' + dumpName.replace(/json$/, 'html'), err => {
|
||||
if (err) {
|
||||
console.error(err);
|
||||
}
|
||||
res.end();
|
||||
});
|
||||
res.setHeader('Content-Type', 'application/javascript');
|
||||
res.end(bundleSource);
|
||||
Activity.endEvent(startReqEventId);
|
||||
} else if (requestType === 'map') {
|
||||
var sourceMap = JSON.stringify(p.getSourceMap());
|
||||
res.setHeader('Content-Type', 'application/json');
|
||||
res.end(sourceMap);
|
||||
Activity.endEvent(startReqEventId);
|
||||
}
|
||||
},
|
||||
this._handleError.bind(this, res, optionsJson)
|
||||
).done();
|
||||
};
|
||||
|
||||
Server.prototype._handleError = function(res, packageID, error) {
|
||||
res.writeHead(error.status || 500, {
|
||||
'Content-Type': 'application/json; charset=UTF-8',
|
||||
});
|
||||
|
||||
if (error.type === 'TransformError' || error.type === 'NotFoundError') {
|
||||
error.errors = [{
|
||||
description: error.description,
|
||||
filename: error.filename,
|
||||
lineNumber: error.lineNumber,
|
||||
}];
|
||||
res.end(JSON.stringify(error));
|
||||
|
||||
if (error.type === 'NotFoundError') {
|
||||
delete this._packages[packageID];
|
||||
}
|
||||
} else {
|
||||
console.error(error.stack || error);
|
||||
res.end(JSON.stringify({
|
||||
type: 'InternalError',
|
||||
message: 'react-packager has encountered an internal error, ' +
|
||||
'please check your terminal error output for more details',
|
||||
}));
|
||||
}
|
||||
};
|
||||
|
||||
function getOptionsFromUrl(reqUrl) {
|
||||
// `true` to parse the query param as an object.
|
||||
var urlObj = url.parse(reqUrl, true);
|
||||
// node v0.11.14 bug see https://github.com/facebook/react-native/issues/218
|
||||
urlObj.query = urlObj.query || {};
|
||||
|
||||
var pathname = decodeURIComponent(urlObj.pathname);
|
||||
|
||||
// Backwards compatibility. Options used to be as added as '.' to the
|
||||
// entry module name. We can safely remove these options.
|
||||
var entryFile = pathname.replace(/^\//, '').split('.').filter(function(part) {
|
||||
if (part === 'includeRequire' || part === 'runModule' ||
|
||||
part === 'bundle' || part === 'map') {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}).join('.') + '.js';
|
||||
|
||||
return {
|
||||
sourceMapUrl: pathname.replace(/\.bundle$/, '.map'),
|
||||
entryFile: entryFile,
|
||||
dev: getBoolOptionFromQuery(urlObj.query, 'dev', true),
|
||||
minify: getBoolOptionFromQuery(urlObj.query, 'minify'),
|
||||
runModule: getBoolOptionFromQuery(urlObj.query, 'runModule', true),
|
||||
inlineSourceMap: getBoolOptionFromQuery(
|
||||
urlObj.query,
|
||||
'inlineSourceMap',
|
||||
false
|
||||
),
|
||||
};
|
||||
}
|
||||
|
||||
function getBoolOptionFromQuery(query, opt, defaultVal) {
|
||||
if (query[opt] == null && defaultVal != null) {
|
||||
return defaultVal;
|
||||
});
|
||||
}
|
||||
|
||||
return query[opt] === 'true' || query[opt] === '1';
|
||||
processRequest(req, res, next) {
|
||||
const urlObj = url.parse(req.url, true);
|
||||
var pathname = urlObj.pathname;
|
||||
|
||||
var requestType;
|
||||
if (pathname.match(/\.bundle$/)) {
|
||||
requestType = 'bundle';
|
||||
} else if (pathname.match(/\.map$/)) {
|
||||
requestType = 'map';
|
||||
} else if (pathname.match(/\.assets$/)) {
|
||||
requestType = 'assets';
|
||||
} else if (pathname.match(/^\/debug/)) {
|
||||
this._processDebugRequest(req.url, res);
|
||||
return;
|
||||
} else if (pathname.match(/^\/onchange\/?$/)) {
|
||||
this._processOnChangeRequest(req, res);
|
||||
return;
|
||||
} else if (pathname.match(/^\/assets\//)) {
|
||||
this._processAssetsRequest(req, res);
|
||||
return;
|
||||
} else if (pathname.match(/^\/profile\/?$/)) {
|
||||
this._processProfile(req, res);
|
||||
return;
|
||||
} else {
|
||||
next();
|
||||
return;
|
||||
}
|
||||
|
||||
const startReqEventId = Activity.startEvent('request:' + req.url);
|
||||
const options = this._getOptionsFromUrl(req.url);
|
||||
const optionsJson = JSON.stringify(options);
|
||||
const building = this._bundles[optionsJson] || this.buildBundle(options);
|
||||
|
||||
this._bundles[optionsJson] = building;
|
||||
building.then(
|
||||
p => {
|
||||
if (requestType === 'bundle') {
|
||||
var bundleSource = p.getSource({
|
||||
inlineSourceMap: options.inlineSourceMap,
|
||||
minify: options.minify,
|
||||
});
|
||||
res.setHeader('Content-Type', 'application/javascript');
|
||||
res.end(bundleSource);
|
||||
Activity.endEvent(startReqEventId);
|
||||
} else if (requestType === 'map') {
|
||||
var sourceMap = JSON.stringify(p.getSourceMap());
|
||||
res.setHeader('Content-Type', 'application/json');
|
||||
res.end(sourceMap);
|
||||
Activity.endEvent(startReqEventId);
|
||||
} else if (requestType === 'assets') {
|
||||
var assetsList = JSON.stringify(p.getAssets());
|
||||
res.setHeader('Content-Type', 'application/json');
|
||||
res.end(assetsList);
|
||||
Activity.endEvent(startReqEventId);
|
||||
}
|
||||
},
|
||||
this._handleError.bind(this, res, optionsJson)
|
||||
).done();
|
||||
}
|
||||
|
||||
_handleError(res, bundleID, error) {
|
||||
res.writeHead(error.status || 500, {
|
||||
'Content-Type': 'application/json; charset=UTF-8',
|
||||
});
|
||||
|
||||
if (error.type === 'TransformError' || error.type === 'NotFoundError') {
|
||||
error.errors = [{
|
||||
description: error.description,
|
||||
filename: error.filename,
|
||||
lineNumber: error.lineNumber,
|
||||
}];
|
||||
res.end(JSON.stringify(error));
|
||||
|
||||
if (error.type === 'NotFoundError') {
|
||||
delete this._bundles[bundleID];
|
||||
}
|
||||
} else {
|
||||
console.error(error.stack || error);
|
||||
res.end(JSON.stringify({
|
||||
type: 'InternalError',
|
||||
message: 'react-packager has encountered an internal error, ' +
|
||||
'please check your terminal error output for more details',
|
||||
}));
|
||||
}
|
||||
}
|
||||
|
||||
_getOptionsFromUrl(reqUrl) {
|
||||
// `true` to parse the query param as an object.
|
||||
const urlObj = url.parse(reqUrl, true);
|
||||
// node v0.11.14 bug see https://github.com/facebook/react-native/issues/218
|
||||
urlObj.query = urlObj.query || {};
|
||||
|
||||
const pathname = decodeURIComponent(urlObj.pathname);
|
||||
|
||||
// Backwards compatibility. Options used to be as added as '.' to the
|
||||
// entry module name. We can safely remove these options.
|
||||
const entryFile = pathname.replace(/^\//, '').split('.').filter(part => {
|
||||
if (part === 'includeRequire' || part === 'runModule' ||
|
||||
part === 'bundle' || part === 'map' || part === 'assets') {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}).join('.') + '.js';
|
||||
|
||||
return {
|
||||
sourceMapUrl: pathname.replace(/\.bundle$/, '.map'),
|
||||
entryFile: entryFile,
|
||||
dev: this._getBoolOptionFromQuery(urlObj.query, 'dev', true),
|
||||
minify: this._getBoolOptionFromQuery(urlObj.query, 'minify'),
|
||||
runModule: this._getBoolOptionFromQuery(urlObj.query, 'runModule', true),
|
||||
inlineSourceMap: this._getBoolOptionFromQuery(
|
||||
urlObj.query,
|
||||
'inlineSourceMap',
|
||||
false
|
||||
),
|
||||
platform: urlObj.query.platform,
|
||||
};
|
||||
}
|
||||
|
||||
_getBoolOptionFromQuery(query, opt, defaultVal) {
|
||||
if (query[opt] == null && defaultVal != null) {
|
||||
return defaultVal;
|
||||
}
|
||||
|
||||
return query[opt] === 'true' || query[opt] === '1';
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = Server;
|
||||
|
|
|
@ -0,0 +1,101 @@
|
|||
/**
|
||||
* 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 Bundle = require('../Bundler/Bundle');
|
||||
const Promise = require('promise');
|
||||
const bser = require('bser');
|
||||
const debug = require('debug')('ReactPackager:SocketClient');
|
||||
const net = require('net');
|
||||
|
||||
class SocketClient {
|
||||
static create(sockPath) {
|
||||
return new SocketClient(sockPath).onReady();
|
||||
}
|
||||
|
||||
constructor(sockPath) {
|
||||
debug('connecting to', sockPath);
|
||||
|
||||
this._sock = net.connect(sockPath);
|
||||
this._ready = new Promise((resolve, reject) => {
|
||||
this._sock.on('connect', () => resolve(this));
|
||||
this._sock.on('error', (e) => reject(e));
|
||||
});
|
||||
|
||||
this._resolvers = Object.create(null);
|
||||
const bunser = new bser.BunserBuf();
|
||||
this._sock.on('data', (buf) => bunser.append(buf));
|
||||
|
||||
bunser.on('value', (message) => this._handleMessage(message));
|
||||
}
|
||||
|
||||
onReady() {
|
||||
return this._ready;
|
||||
}
|
||||
|
||||
getDependencies(main) {
|
||||
return this._send({
|
||||
type: 'getDependencies',
|
||||
data: main,
|
||||
});
|
||||
}
|
||||
|
||||
buildBundle(options) {
|
||||
return this._send({
|
||||
type: 'buildBundle',
|
||||
data: options,
|
||||
}).then(json => Bundle.fromJSON(json));
|
||||
}
|
||||
|
||||
_send(message) {
|
||||
message.id = uid();
|
||||
this._sock.write(bser.dumpToBuffer(message));
|
||||
return new Promise((resolve, reject) => {
|
||||
this._resolvers[message.id] = {resolve, reject};
|
||||
});
|
||||
}
|
||||
|
||||
_handleMessage(message) {
|
||||
if (!(message && message.id && message.type)) {
|
||||
throw new Error(
|
||||
'Malformed message from server ' + JSON.stringify(message)
|
||||
);
|
||||
}
|
||||
|
||||
debug('got message with type', message.type);
|
||||
|
||||
const resolver = this._resolvers[message.id];
|
||||
if (!resolver) {
|
||||
throw new Error(
|
||||
'Unrecognized message id (message already resolved or never existed'
|
||||
);
|
||||
}
|
||||
|
||||
delete this._resolvers[message.id];
|
||||
|
||||
if (message.type === 'error') {
|
||||
// TODO convert to an error
|
||||
resolver.reject(message.data);
|
||||
} else {
|
||||
resolver.resolve(message.data);
|
||||
}
|
||||
}
|
||||
|
||||
close() {
|
||||
debug('closing connection');
|
||||
this._sock.end();
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = SocketClient;
|
||||
|
||||
function uid(len) {
|
||||
len = len || 7;
|
||||
return Math.random().toString(35).substr(2, len);
|
||||
}
|
|
@ -0,0 +1,141 @@
|
|||
/**
|
||||
* 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 Server = require('../Server');
|
||||
const bser = require('bser');
|
||||
const debug = require('debug')('ReactPackager:SocketServer');
|
||||
const fs = require('fs');
|
||||
const net = require('net');
|
||||
|
||||
const MAX_IDLE_TIME = 10 * 60 * 1000;
|
||||
|
||||
class SocketServer {
|
||||
constructor(sockPath, options) {
|
||||
this._server = net.createServer();
|
||||
this._server.listen(sockPath);
|
||||
this._ready = new Promise((resolve, reject) => {
|
||||
this._server.on('error', (e) => reject(e));
|
||||
this._server.on('listening', () => {
|
||||
debug(
|
||||
'Process %d listening on socket path %s ' +
|
||||
'for server with options %j',
|
||||
process.pid,
|
||||
sockPath,
|
||||
options
|
||||
);
|
||||
resolve(this);
|
||||
});
|
||||
});
|
||||
this._server.on('connection', (sock) => this._handleConnection(sock));
|
||||
|
||||
// Disable the file watcher.
|
||||
options.nonPersistent = true;
|
||||
this._packagerServer = new Server(options);
|
||||
this._jobs = 0;
|
||||
this._dieEventually();
|
||||
|
||||
process.on('exit', () => fs.unlinkSync(sockPath));
|
||||
}
|
||||
|
||||
onReady() {
|
||||
return this._ready;
|
||||
}
|
||||
|
||||
_handleConnection(sock) {
|
||||
debug('connection to server', process.pid);
|
||||
|
||||
const bunser = new bser.BunserBuf();
|
||||
sock.on('data', (buf) => bunser.append(buf));
|
||||
|
||||
bunser.on('value', (m) => this._handleMessage(sock, m));
|
||||
}
|
||||
|
||||
_handleMessage(sock, m) {
|
||||
if (!m || !m.id || !m.data) {
|
||||
console.error('SocketServer recieved a malformed message: %j', m);
|
||||
}
|
||||
|
||||
debug('got request', m);
|
||||
|
||||
// Debounce the kill timer.
|
||||
this._dieEventually();
|
||||
|
||||
const handleError = (error) => {
|
||||
debug('request error', error);
|
||||
this._jobs--;
|
||||
this._reply(sock, m.id, 'error', error.stack);
|
||||
};
|
||||
|
||||
switch (m.type) {
|
||||
case 'getDependencies':
|
||||
this._jobs++;
|
||||
this._packagerServer.getDependencies(m.data).then(
|
||||
({ dependencies }) => this._reply(sock, m.id, 'result', dependencies),
|
||||
handleError,
|
||||
);
|
||||
break;
|
||||
|
||||
case 'buildBundle':
|
||||
this._jobs++;
|
||||
this._packagerServer.buildBundle(m.data).then(
|
||||
(result) => this._reply(sock, m.id, 'result', result),
|
||||
handleError,
|
||||
);
|
||||
break;
|
||||
|
||||
default:
|
||||
this._reply(sock, m.id, 'error', 'Unknown message type: ' + m.type);
|
||||
}
|
||||
}
|
||||
|
||||
_reply(sock, id, type, data) {
|
||||
debug('request finished', type);
|
||||
|
||||
this._jobs--;
|
||||
data = toJSON(data);
|
||||
|
||||
sock.write(bser.dumpToBuffer({
|
||||
id,
|
||||
type,
|
||||
data,
|
||||
}));
|
||||
}
|
||||
|
||||
_dieEventually() {
|
||||
clearTimeout(this._deathTimer);
|
||||
this._deathTimer = setTimeout(() => {
|
||||
if (this._jobs <= 0) {
|
||||
debug('server dying', process.pid);
|
||||
process.exit(1);
|
||||
}
|
||||
this._dieEventually();
|
||||
}, MAX_IDLE_TIME);
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = SocketServer;
|
||||
|
||||
// TODO move this to bser code.
|
||||
function toJSON(object) {
|
||||
if (!(object && typeof object === 'object')) {
|
||||
return object;
|
||||
}
|
||||
|
||||
if (object.toJSON) {
|
||||
return object.toJSON();
|
||||
}
|
||||
|
||||
for (var p in object) {
|
||||
object[p] = toJSON(object[p]);
|
||||
}
|
||||
|
||||
return object;
|
||||
}
|
|
@ -0,0 +1,112 @@
|
|||
/**
|
||||
* 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';
|
||||
|
||||
jest.setMock('worker-farm', function() { return () => {}; })
|
||||
.setMock('uglify-js')
|
||||
.mock('net')
|
||||
.dontMock('../SocketClient');
|
||||
|
||||
describe('SocketClient', () => {
|
||||
let SocketClient;
|
||||
let sock;
|
||||
let bunser;
|
||||
|
||||
beforeEach(() => {
|
||||
SocketClient = require('../SocketClient');
|
||||
|
||||
const {EventEmitter} = require.requireActual('events');
|
||||
sock = new EventEmitter();
|
||||
sock.write = jest.genMockFn();
|
||||
|
||||
require('net').connect.mockImpl(() => sock);
|
||||
|
||||
const bser = require('bser');
|
||||
bunser = new EventEmitter();
|
||||
require('bser').BunserBuf.mockImpl(() => bunser);
|
||||
bser.dumpToBuffer.mockImpl((a) => a);
|
||||
|
||||
require('../../Bundler/Bundle').fromJSON.mockImpl((a) => a);
|
||||
});
|
||||
|
||||
pit('create a connection', () => {
|
||||
const client = new SocketClient('/sock');
|
||||
sock.emit('connect');
|
||||
return client.onReady().then(c => {
|
||||
expect(c).toBe(client);
|
||||
expect(require('net').connect).toBeCalledWith('/sock');
|
||||
});
|
||||
});
|
||||
|
||||
pit('buildBundle', () => {
|
||||
const client = new SocketClient('/sock');
|
||||
sock.emit('connect');
|
||||
const options = { entryFile: '/main' };
|
||||
|
||||
const promise = client.buildBundle(options);
|
||||
|
||||
expect(sock.write).toBeCalled();
|
||||
const message = sock.write.mock.calls[0][0];
|
||||
expect(message.type).toBe('buildBundle');
|
||||
expect(message.data).toEqual(options);
|
||||
expect(typeof message.id).toBe('string');
|
||||
|
||||
bunser.emit('value', {
|
||||
id: message.id,
|
||||
type: 'result',
|
||||
data: { bundle: 'foo' },
|
||||
});
|
||||
|
||||
return promise.then(bundle => expect(bundle).toEqual({ bundle: 'foo' }));
|
||||
});
|
||||
|
||||
pit('getDependencies', () => {
|
||||
const client = new SocketClient('/sock');
|
||||
sock.emit('connect');
|
||||
const main = '/main';
|
||||
|
||||
const promise = client.getDependencies(main);
|
||||
|
||||
expect(sock.write).toBeCalled();
|
||||
const message = sock.write.mock.calls[0][0];
|
||||
expect(message.type).toBe('getDependencies');
|
||||
expect(message.data).toEqual(main);
|
||||
expect(typeof message.id).toBe('string');
|
||||
|
||||
bunser.emit('value', {
|
||||
id: message.id,
|
||||
type: 'result',
|
||||
data: ['a', 'b', 'c'],
|
||||
});
|
||||
|
||||
return promise.then(result => expect(result).toEqual(['a', 'b', 'c']));
|
||||
});
|
||||
|
||||
pit('handle errors', () => {
|
||||
const client = new SocketClient('/sock');
|
||||
sock.emit('connect');
|
||||
const main = '/main';
|
||||
|
||||
const promise = client.getDependencies(main);
|
||||
|
||||
expect(sock.write).toBeCalled();
|
||||
const message = sock.write.mock.calls[0][0];
|
||||
expect(message.type).toBe('getDependencies');
|
||||
expect(message.data).toEqual(main);
|
||||
expect(typeof message.id).toBe('string');
|
||||
|
||||
bunser.emit('value', {
|
||||
id: message.id,
|
||||
type: 'error',
|
||||
data: 'some error'
|
||||
});
|
||||
|
||||
return promise.catch(m => expect(m).toBe('some error'));
|
||||
});
|
||||
});
|
|
@ -0,0 +1,87 @@
|
|||
/**
|
||||
* 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';
|
||||
|
||||
jest.setMock('worker-farm', function() { return () => {}; })
|
||||
.setMock('uglify-js')
|
||||
.mock('child_process')
|
||||
.dontMock('../');
|
||||
|
||||
describe('SocketInterface', () => {
|
||||
let SocketInterface;
|
||||
let SocketClient;
|
||||
|
||||
beforeEach(() => {
|
||||
SocketInterface = require('../');
|
||||
SocketClient = require('../SocketClient');
|
||||
});
|
||||
|
||||
describe('getOrCreateSocketFor', () => {
|
||||
pit('creates socket path by hashing options', () => {
|
||||
const fs = require('fs');
|
||||
fs.existsSync = jest.genMockFn().mockImpl(() => true);
|
||||
|
||||
// Check that given two equivelant server options, we end up with the same
|
||||
// socket path.
|
||||
const options1 = { projectRoots: ['/root'], transformModulePath: '/root/foo' };
|
||||
const options2 = { transformModulePath: '/root/foo', projectRoots: ['/root'] };
|
||||
const options3 = { projectRoots: ['/root', '/root2'] };
|
||||
|
||||
return SocketInterface.getOrCreateSocketFor(options1).then(() => {
|
||||
expect(SocketClient.create).toBeCalled();
|
||||
return SocketInterface.getOrCreateSocketFor(options2).then(() => {
|
||||
expect(SocketClient.create.mock.calls.length).toBe(2);
|
||||
expect(SocketClient.create.mock.calls[0]).toEqual(SocketClient.create.mock.calls[1]);
|
||||
return SocketInterface.getOrCreateSocketFor(options3).then(() => {
|
||||
expect(SocketClient.create.mock.calls.length).toBe(3);
|
||||
expect(SocketClient.create.mock.calls[1]).not.toEqual(SocketClient.create.mock.calls[2]);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
pit('should fork a server', () => {
|
||||
const fs = require('fs');
|
||||
fs.existsSync = jest.genMockFn().mockImpl(() => false);
|
||||
let sockPath;
|
||||
let callback;
|
||||
|
||||
require('child_process').spawn.mockImpl(() => ({
|
||||
on: (event, cb) => callback = cb,
|
||||
send: (message) => {
|
||||
expect(message.type).toBe('createSocketServer');
|
||||
expect(message.data.options).toEqual({ projectRoots: ['/root'] });
|
||||
expect(message.data.sockPath).toContain('react-packager');
|
||||
sockPath = message.data.sockPath;
|
||||
|
||||
setImmediate(() => callback({ type: 'createdServer' }));
|
||||
},
|
||||
unref: () => undefined,
|
||||
disconnect: () => undefined,
|
||||
}));
|
||||
|
||||
return SocketInterface.getOrCreateSocketFor({ projectRoots: ['/root'] })
|
||||
.then(() => {
|
||||
expect(SocketClient.create).toBeCalledWith(sockPath);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('createSocketServer', () => {
|
||||
pit('creates a server', () => {
|
||||
require('../SocketServer').mockImpl((sockPath, options) => {
|
||||
expect(sockPath).toBe('/socket');
|
||||
expect(options).toEqual({ projectRoots: ['/root'] });
|
||||
return { onReady: () => Promise.resolve() };
|
||||
});
|
||||
|
||||
return SocketInterface.createSocketServer('/socket', { projectRoots: ['/root'] });
|
||||
});
|
||||
});
|
||||
});
|
|
@ -0,0 +1,94 @@
|
|||
/**
|
||||
* 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';
|
||||
|
||||
jest.setMock('worker-farm', function() { return () => {}; })
|
||||
.setMock('uglify-js')
|
||||
.mock('net')
|
||||
.mock('fs')
|
||||
.dontMock('../SocketServer');
|
||||
|
||||
describe('SocketServer', () => {
|
||||
let PackagerServer;
|
||||
let SocketServer;
|
||||
let netServer;
|
||||
let bunser;
|
||||
|
||||
beforeEach(() => {
|
||||
SocketServer = require('../SocketServer');
|
||||
|
||||
const {EventEmitter} = require.requireActual('events');
|
||||
netServer = new EventEmitter();
|
||||
netServer.listen = jest.genMockFn();
|
||||
require('net').createServer.mockImpl(() => netServer);
|
||||
|
||||
const bser = require('bser');
|
||||
bunser = new EventEmitter();
|
||||
bser.BunserBuf.mockImpl(() => bunser);
|
||||
bser.dumpToBuffer.mockImpl((a) => a);
|
||||
|
||||
PackagerServer = require('../../Server');
|
||||
});
|
||||
|
||||
pit('create a server', () => {
|
||||
const server = new SocketServer('/sock', { projectRoots: ['/root'] });
|
||||
netServer.emit('listening');
|
||||
return server.onReady().then(s => {
|
||||
expect(s).toBe(server);
|
||||
expect(netServer.listen).toBeCalledWith('/sock');
|
||||
});
|
||||
});
|
||||
|
||||
pit('handles getDependencies message', () => {
|
||||
const server = new SocketServer('/sock', { projectRoots: ['/root'] });
|
||||
netServer.emit('listening');
|
||||
return server.onReady().then(() => {
|
||||
const sock = { on: jest.genMockFn(), write: jest.genMockFn() };
|
||||
netServer.emit('connection', sock);
|
||||
PackagerServer.prototype.getDependencies.mockImpl(
|
||||
() => Promise.resolve({ dependencies: ['a', 'b', 'c'] })
|
||||
);
|
||||
bunser.emit('value', { type: 'getDependencies', id: 1, data: '/main' });
|
||||
expect(PackagerServer.prototype.getDependencies).toBeCalledWith('/main');
|
||||
|
||||
// Run pending promises.
|
||||
return Promise.resolve().then(() => {
|
||||
expect(sock.write).toBeCalledWith(
|
||||
{ id: 1, type: 'result', data: ['a', 'b', 'c']}
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
pit('handles buildBundle message', () => {
|
||||
const server = new SocketServer('/sock', { projectRoots: ['/root'] });
|
||||
netServer.emit('listening');
|
||||
return server.onReady().then(() => {
|
||||
const sock = { on: jest.genMockFn(), write: jest.genMockFn() };
|
||||
netServer.emit('connection', sock);
|
||||
PackagerServer.prototype.buildBundle.mockImpl(
|
||||
() => Promise.resolve({ bundle: 'foo' })
|
||||
);
|
||||
bunser.emit(
|
||||
'value',
|
||||
{ type: 'buildBundle', id: 1, data: { options: 'bar' } }
|
||||
);
|
||||
expect(PackagerServer.prototype.buildBundle).toBeCalledWith(
|
||||
{ options: 'bar' }
|
||||
);
|
||||
|
||||
// Run pending promises.
|
||||
return Promise.resolve().then(() => {
|
||||
expect(sock.write).toBeCalledWith(
|
||||
{ id: 1, type: 'result', data: { bundle: 'foo' }}
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
|
@ -0,0 +1,102 @@
|
|||
/**
|
||||
* 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 SocketClient = require('./SocketClient');
|
||||
const SocketServer = require('./SocketServer');
|
||||
const _ = require('underscore');
|
||||
const crypto = require('crypto');
|
||||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
const tmpdir = require('os').tmpdir();
|
||||
const {spawn} = require('child_process');
|
||||
|
||||
const CREATE_SERVER_TIMEOUT = 10000;
|
||||
|
||||
const SocketInterface = {
|
||||
getOrCreateSocketFor(options) {
|
||||
return new Promise((resolve, reject) => {
|
||||
const hash = crypto.createHash('md5');
|
||||
Object.keys(options).sort().forEach(key => {
|
||||
if (options[key] && typeof options[key] !== 'string') {
|
||||
hash.update(JSON.stringify(options[key]));
|
||||
} else {
|
||||
hash.update(options[key]);
|
||||
}
|
||||
});
|
||||
|
||||
const sockPath = path.join(
|
||||
tmpdir,
|
||||
'react-packager-' + hash.digest('hex')
|
||||
);
|
||||
|
||||
if (fs.existsSync(sockPath)) {
|
||||
resolve(SocketClient.create(sockPath));
|
||||
return;
|
||||
}
|
||||
|
||||
const logPath = path.join(tmpdir, 'react-packager.log');
|
||||
|
||||
const timeout = setTimeout(
|
||||
() => reject(
|
||||
new Error(
|
||||
'Took too long to start server. Server logs: \n' +
|
||||
fs.readFileSync(logPath, 'utf8')
|
||||
)
|
||||
),
|
||||
CREATE_SERVER_TIMEOUT,
|
||||
);
|
||||
|
||||
const log = fs.openSync(logPath, 'a');
|
||||
|
||||
// Enable server debugging by default since it's going to a log file.
|
||||
const env = _.clone(process.env);
|
||||
env.DEBUG = 'ReactPackager:SocketServer';
|
||||
|
||||
// We have to go through the main entry point to make sure
|
||||
// we go through the babel require hook.
|
||||
const child = spawn(
|
||||
process.execPath,
|
||||
[path.join(__dirname, '..', '..', 'index.js')],
|
||||
{
|
||||
detached: true,
|
||||
env: env,
|
||||
stdio: ['ipc', log, log]
|
||||
}
|
||||
);
|
||||
|
||||
child.unref();
|
||||
|
||||
child.on('message', m => {
|
||||
if (m && m.type && m.type === 'createdServer') {
|
||||
clearTimeout(timeout);
|
||||
child.disconnect();
|
||||
resolve(SocketClient.create(sockPath));
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
if (options.blacklistRE) {
|
||||
options.blacklistRE = { source: options.blacklistRE.source };
|
||||
}
|
||||
|
||||
child.send({
|
||||
type: 'createSocketServer',
|
||||
data: { sockPath, options }
|
||||
});
|
||||
});
|
||||
},
|
||||
|
||||
createSocketServer(sockPath, options) {
|
||||
return new SocketServer(sockPath, options).onReady();
|
||||
}
|
||||
};
|
||||
|
||||
module.exports = SocketInterface;
|
|
@ -10,7 +10,14 @@
|
|||
|
||||
var fs = jest.genMockFromModule('fs');
|
||||
|
||||
function asyncCallback(callback) {
|
||||
return function() {
|
||||
setImmediate(() => callback.apply(this, arguments));
|
||||
};
|
||||
}
|
||||
|
||||
fs.realpath.mockImpl(function(filepath, callback) {
|
||||
callback = asyncCallback(callback);
|
||||
var node;
|
||||
try {
|
||||
node = getToNode(filepath);
|
||||
|
@ -24,6 +31,7 @@ fs.realpath.mockImpl(function(filepath, callback) {
|
|||
});
|
||||
|
||||
fs.readdir.mockImpl(function(filepath, callback) {
|
||||
callback = asyncCallback(callback);
|
||||
var node;
|
||||
try {
|
||||
node = getToNode(filepath);
|
||||
|
@ -42,6 +50,7 @@ fs.readdir.mockImpl(function(filepath, callback) {
|
|||
});
|
||||
|
||||
fs.readFile.mockImpl(function(filepath, encoding, callback) {
|
||||
callback = asyncCallback(callback);
|
||||
if (arguments.length === 2) {
|
||||
callback = encoding;
|
||||
encoding = null;
|
||||
|
@ -60,6 +69,7 @@ fs.readFile.mockImpl(function(filepath, encoding, callback) {
|
|||
});
|
||||
|
||||
fs.stat.mockImpl(function(filepath, callback) {
|
||||
callback = asyncCallback(callback);
|
||||
var node;
|
||||
try {
|
||||
node = getToNode(filepath);
|
||||
|
|
|
@ -13,6 +13,12 @@
|
|||
var babel = require('babel-core');
|
||||
|
||||
function transform(srcTxt, filename, options) {
|
||||
var plugins = [];
|
||||
|
||||
if (process.env.NODE_ENV === 'production') {
|
||||
plugins = plugins.concat(['node-env-inline', 'dunderscore-dev-inline']);
|
||||
}
|
||||
|
||||
var result = babel.transform(srcTxt, {
|
||||
retainLines: true,
|
||||
compact: true,
|
||||
|
@ -35,6 +41,7 @@ function transform(srcTxt, filename, options) {
|
|||
'react',
|
||||
'regenerator',
|
||||
],
|
||||
plugins: plugins,
|
||||
sourceFileName: filename,
|
||||
sourceMaps: false,
|
||||
extra: options || {},
|
||||
|
|
Loading…
Reference in New Issue