[react-packager][streamline oss] Move open sourced JS source to react-native-github

This commit is contained in:
Spencer Ahrens 2015-02-19 20:10:52 -08:00
commit 382e1553af
60 changed files with 5503 additions and 0 deletions

45
blacklist.js Normal file
View File

@ -0,0 +1,45 @@
/**
* Copyright 2004-present Facebook. All Rights Reserved.
*/
'use strict';
// Don't forget to everything listed here to `testConfig.json`
// modulePathIgnorePatterns.
var sharedBlacklist = [
'node_modules/JSAppServer',
'packager/react-packager',
'node_modules/parse/node_modules/xmlhttprequest/lib/XMLHttpRequest.js',
'node_modules/react-tools/src/utils/ImmutableObject.js',
'node_modules/react-tools/src/core/ReactInstanceHandles.js',
'node_modules/react-tools/src/event/EventPropagators.js',
'node_modules/jest-cli',
];
var webBlacklist = [
'.ios.js'
];
var iosBlacklist = [
'node_modules/react-tools/src/browser/ui/React.js',
'node_modules/react-tools/src/browser/eventPlugins/ResponderEventPlugin.js',
'node_modules/react-tools/src/browser/ReactTextComponent.js',
// 'node_modules/react-tools/src/vendor/core/ExecutionEnvironment.js',
'.web.js',
'.android.js',
];
function escapeRegExp(str) {
return str.replace(/[\-\[\]\/\{\}\(\)\*\+\?\.\\\^\$\|]/g, '\\$&');
}
function blacklist(isWeb) {
return new RegExp('(' +
sharedBlacklist
.concat(isWeb ? webBlacklist : iosBlacklist)
.map(escapeRegExp)
.join('|') +
')$'
);
}
module.exports = blacklist;

40
launchEditor.js Normal file
View File

@ -0,0 +1,40 @@
/**
* Copyright 2004-present Facebook. All Rights Reserved.
*/
'use strict';
var fs = require('fs');
var spawn = require('child_process').spawn;
var firstLaunch = true;
function guessEditor() {
if (firstLaunch) {
console.log('When you see Red Box with stack trace, you can click any ' +
'stack frame to jump to the source file. The packager will launch your ' +
'editor of choice. It will first look at REACT_EDITOR environment ' +
'variable, then at EDITOR. To set it up, you can add something like ' +
'REACT_EDITOR=atom to your .bashrc.');
firstLaunch = false;
}
var editor = process.env.REACT_EDITOR || process.env.EDITOR || 'subl';
return editor;
}
function launchEditor(fileName, lineNumber) {
if (!fs.existsSync(fileName)) {
return;
}
var argument = fileName;
if (lineNumber) {
argument += ':' + lineNumber;
}
var editor = guessEditor();
console.log('Opening ' + fileName + ' with ' + editor);
spawn(editor, [argument], { stdio: ['pipe', 'pipe', process.stderr] });
}
module.exports = launchEditor;

10
launchPackager.command Executable file
View File

@ -0,0 +1,10 @@
#!/bin/bash
# Set terminal title
echo -en "\033]0;React Packager\a"
clear
THIS_DIR=$(dirname "$0")
$THIS_DIR/packager.sh
echo "Process terminated. Press <enter> to close the window"
read

122
packager.js Normal file
View File

@ -0,0 +1,122 @@
/**
* Copyright 2004-present Facebook. All Rights Reserved.
*/
'use strict';
var fs = require('fs');
var path = require('path');
if (!fs.existsSync(path.resolve(__dirname, '..', 'node_modules'))) {
console.log(
'\n' +
'Could not find dependencies.\n' +
'Ensure dependencies are installed - ' +
'run \'npm install\' from project root.\n'
);
process.exit();
}
var ReactPackager = require('./react-packager');
var blacklist = require('./blacklist.js');
var connect = require('connect');
var http = require('http');
var launchEditor = require('./launchEditor.js');
var parseCommandLine = require('./parseCommandLine.js');
var options = parseCommandLine([{
command: 'port',
default: 8081,
}, {
command: 'root',
description: 'add another root(s) to be used by the packager in this project',
}]);
if (!options.projectRoots) {
options.projectRoots = [path.resolve(__dirname, '..')];
}
if (options.root) {
if (typeof options.root === 'string') {
options.projectRoots.push(path.resolve(options.root));
} else {
options.root.forEach(function(root) {
options.projectRoots.push(path.resolve(root));
});
}
}
console.log('\n' +
' ===============================================================\n' +
' | Running packager on port ' + options.port + '. \n' +
' | Keep this packager running while developing on any JS \n' +
' | projects. Feel free to close this tab and run your own \n' +
' | packager instance if you prefer. \n' +
' | \n' +
' | https://github.com/facebook/react-native \n' +
' | \n' +
' ===============================================================\n'
);
process.on('uncaughtException', function(e) {
console.error(e);
console.error(e.stack);
console.error('\n >>> ERROR: could not create packager - please shut down ' +
'any existing instances that are already running.\n\n');
});
runServer(options, function() {
console.log('\nReact packager ready.\n');
});
function loadRawBody(req, res, next) {
req.rawBody = '';
req.setEncoding('utf8');
req.on('data', function(chunk) {
req.rawBody += chunk;
});
req.on('end', function() {
next();
});
}
function openStackFrameInEditor(req, res, next) {
if (req.url === '/open-stack-frame') {
var frame = JSON.parse(req.rawBody);
launchEditor(frame.file, frame.lineNumber);
res.end('OK');
} else {
next();
}
}
function getAppMiddleware(options) {
return ReactPackager.middleware({
dev: true,
projectRoots: options.projectRoots,
blacklistRE: blacklist(false),
cacheVersion: '2',
transformModulePath: require.resolve('./transformer.js'),
});
}
function runServer(
options, /* {string projectRoot, bool web, bool dev} */
readyCallback
) {
var app = connect()
.use(loadRawBody)
.use(openStackFrameInEditor)
.use(getAppMiddleware(options));
options.projectRoots.forEach(function(root) {
app.use(connect.static(root));
});
app.use(connect.logger())
.use(connect.compress())
.use(connect.errorHandler());
return http.createServer(app).listen(options.port, readyCallback);
}

6
packager.sh Executable file
View File

@ -0,0 +1,6 @@
#!/bin/bash
ulimit -n 4096
THIS_DIR=$(dirname "$0")
node $THIS_DIR/packager.js "$@"

52
parseCommandLine.js Normal file
View File

@ -0,0 +1,52 @@
/**
* Copyright 2004-present Facebook. All Rights Reserved.
*
* Wrapper on-top of `optimist` in order to properly support boolean flags
* and have a slightly less akward API.
*
* Usage example:
* var argv = parseCommandLine([{
* command: 'web',
* description: 'Run in a web browser instead of iOS',
* default: true
* }])
*/
'use strict';
var optimist = require('optimist');
function parseCommandLine(config) {
// optimist default API requires you to write the command name three time
// This is a small wrapper to accept an object instead
for (var i = 0; i < config.length; ++i) {
optimist
.boolean(config[i].command)
.default(config[i].command, config[i].default)
.describe(config[i].command, config[i].description);
}
var argv = optimist.argv;
// optimist doesn't have support for --dev=false, instead it returns 'false'
for (var i = 0; i < config.length; ++i) {
var command = config[i].command;
if (argv[command] === undefined) {
argv[command] = config[i].default;
}
if (argv[command] === 'true') {
argv[command] = true;
}
if (argv[command] === 'false') {
argv[command] = false;
}
}
// Show --help
if (argv.help || argv.h) {
optimist.showHelp();
process.exit();
}
return argv;
}
module.exports = parseCommandLine;

0
react-packager/, Normal file
View File

86
react-packager/.jshintrc Normal file
View File

@ -0,0 +1,86 @@
{
"-W093": true,
"asi": false,
"bitwise": true,
"boss": false,
"browser": false,
"camelcase": true,
"couch": false,
"curly": true,
"debug": false,
"devel": true,
"dojo": false,
"eqeqeq": true,
"eqnull": true,
"esnext": true,
"evil": false,
"expr": true,
"forin": false,
"freeze": true,
"funcscope": true,
"gcl": false,
"globals": {
"Promise": true,
"React": true,
"XMLHttpRequest": true,
"document": true,
"location": true,
"window": true
},
"globalstrict": true,
"immed": false,
"indent": 2,
"iterator": false,
"jquery": false,
"lastsemic": false,
"latedef": false,
"laxbreak": true,
"laxcomma": false,
"loopfunc": false,
"maxcomplexity": false,
"maxdepth": false,
"maxerr": 50,
"maxlen": 80,
"maxparams": false,
"maxstatements": false,
"mootools": false,
"moz": false,
"multistr": false,
"newcap": true,
"noarg": true,
"node": true,
"noempty": false,
"nonbsp": true,
"nonew": true,
"nonstandard": false,
"notypeof": false,
"noyield": false,
"phantom": false,
"plusplus": false,
"predef": [
"afterEach",
"beforeEach",
"describe",
"expect",
"it",
"jest",
"pit"
],
"proto": false,
"prototypejs": false,
"quotmark": true,
"rhino": false,
"scripturl": false,
"shadow": false,
"smarttabs": false,
"strict": false,
"sub": false,
"supernew": false,
"trailing": true,
"undef": true,
"unused": true,
"validthis": false,
"worker": false,
"wsh": false,
"yui": false
}

View File

@ -0,0 +1,8 @@
*~
*.swm
*.swn
*.swp
*.DS_STORE
npm-debug.log
.cache
node_modules

5
react-packager/__mocks__/debug.js vendored Normal file
View File

@ -0,0 +1,5 @@
'use strict';
module.exports = function() {
return function() {};
};

26
react-packager/__mocks__/net.js vendored Normal file
View File

@ -0,0 +1,26 @@
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;
};

5
react-packager/example_project/bar.js vendored Normal file
View File

@ -0,0 +1,5 @@
/**
* @providesModule bar
*/
module.exports = setInterval;

View File

@ -0,0 +1,10 @@
{
"port": 3000,
"devPort": 3001,
"publicDir": "./public",
"rootPath": "../example_project",
"moduleOptions": {
"format": "haste",
"main": "index.js"
}
}

View File

@ -0,0 +1,23 @@
/**
* @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);
};

10
react-packager/example_project/index.js vendored Normal file
View File

@ -0,0 +1,10 @@
/**
* @providesModule index
* @jsx React.DOM
*/
require('main');
require('code');
var foo = require('foo');
foo('secret');

View File

@ -0,0 +1,46 @@
/**
* @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;

View File

@ -0,0 +1,22 @@
/**
* @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);
};

View File

@ -0,0 +1,51 @@
/**
* @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'));
});

View File

@ -0,0 +1,57 @@
/**
* @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();
});

View File

@ -0,0 +1,94 @@
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;
}

View File

@ -0,0 +1,30 @@
<!DOCTYPE html>
<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>

39
react-packager/index.js vendored Normal file
View File

@ -0,0 +1,39 @@
'use strict';
var Activity = require('./src/Activity');
var Server = require('./src/Server');
exports.middleware = function(options) {
var server = new Server(options);
return server.processRequest.bind(server);
};
exports.buildPackageFromUrl = function(options, reqUrl) {
Activity.disable();
// Don't start the filewatcher or the cache.
if (options.nonPersistent == null) {
options.nonPersistent = true;
}
var server = new Server(options);
return server.buildPackageFromUrl(reqUrl)
.then(function(p) {
server.end();
return p;
});
};
exports.getDependencies = function(options, main) {
Activity.disable();
// Don't start the filewatcher or the cache.
if (options.nonPersistent == null) {
options.nonPersistent = true;
}
var server = new Server(options);
return server.getDependencies(main)
.then(function(r) {
server.end();
return r.dependencies;
});
};

View File

@ -0,0 +1,10 @@
{
"name": "react-packager",
"version": "0.1.0",
"description": "",
"main": "index.js",
"jest": {
"unmockedModulePathPatterns": ["source-map"],
"testPathIgnorePatterns": ["JSAppServer/node_modules"]
}
}

View File

@ -0,0 +1,79 @@
jest.autoMockOff();
describe('Activity', function() {
var Activity;
var origConsoleLog = console.log;
beforeEach(function() {
console.log = jest.genMockFn();
Activity = require('../');
});
afterEach(function() {
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};
Activity.startEvent(EVENT_NAME, DATA);
jest.runOnlyPendingTimers();
expect(console.log.mock.calls.length).toBe(1);
var 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};
var 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];
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 expired eventId', function() {
var eid = Activity.startEvent('', '');
Activity.endEvent(eid);
expect(function() {
Activity.endEvent(eid);
}).toThrow('event(1) has already ended!');
});
});
describe('signal', function() {
it('writes a SIGNAL event out to the console', function() {
var EVENT_NAME = 'EVENT_NAME';
var 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];
expect(consoleMsg).toContain(EVENT_NAME);
expect(consoleMsg).toContain(JSON.stringify(DATA));
});
});
});

161
react-packager/src/Activity/index.js vendored Normal file
View File

@ -0,0 +1,161 @@
var COLLECTION_PERIOD = 1000;
var _endedEvents = Object.create(null);
var _eventStarts = Object.create(null);
var _queuedActions = [];
var _scheduledCollectionTimer = null;
var _uuid = 1;
var _enabled = true;
function endEvent(eventId) {
var eventEndTime = Date.now();
if (!_eventStarts[eventId]) {
_throw('event(' + eventId + ') is not a valid event id!');
}
if (_endedEvents[eventId]) {
_throw('event(' + eventId + ') has already ended!');
}
_scheduleAction({
action: 'endEvent',
eventId: eventId,
tstamp: eventEndTime
});
_endedEvents[eventId] = true;
}
function signal(eventName, data) {
var signalTime = Date.now();
if (eventName == null) {
_throw('No event name specified');
}
if (data == null) {
data = null;
}
_scheduleAction({
action: 'signal',
data: data,
eventName: eventName,
tstamp: signalTime
});
}
function startEvent(eventName, data) {
var eventStartTime = Date.now();
if (eventName == null) {
_throw('No event name specified');
}
if (data == null) {
data = null;
}
var eventId = _uuid++;
var action = {
action: 'startEvent',
data: data,
eventId: eventId,
eventName: eventName,
tstamp: eventStartTime,
};
_scheduleAction(action);
_eventStarts[eventId] = action;
return eventId;
}
function disable() {
_enabled = false;
}
function _runCollection() {
/* jshint -W084 */
var action;
while ((action = _queuedActions.shift())) {
_writeAction(action);
}
_scheduledCollectionTimer = null;
}
function _scheduleAction(action) {
_queuedActions.push(action);
if (_scheduledCollectionTimer === null) {
_scheduledCollectionTimer = setTimeout(_runCollection, COLLECTION_PERIOD);
}
}
/**
* This a utility function that throws an error message.
*
* The only purpose of this utility is to make APIs like
* startEvent/endEvent/signal inlineable in the JIT.
*
* (V8 can't inline functions that statically contain a `throw`, and probably
* won't be adding such a non-trivial optimization anytime soon)
*/
function _throw(msg) {
var err = new Error(msg);
// Strip off the call to _throw()
var stack = err.stack.split('\n');
stack.splice(1, 1);
err.stack = stack.join('\n');
throw err;
}
function _writeAction(action) {
if (!_enabled) {
return;
}
var data = action.data ? ': ' + JSON.stringify(action.data) : '';
var fmtTime = new Date(action.tstamp).toLocaleTimeString();
switch (action.action) {
case 'startEvent':
console.log(
'[' + fmtTime + '] ' +
'<START> ' + action.eventName +
data
);
break;
case 'endEvent':
var startAction = _eventStarts[action.eventId];
var startData = startAction.data ? ': ' + JSON.stringify(startAction.data) : '';
console.log(
'[' + fmtTime + '] ' +
'<END> ' + startAction.eventName +
'(' + (action.tstamp - startAction.tstamp) + 'ms)' +
startData
);
delete _eventStarts[action.eventId];
break;
case 'signal':
console.log(
'[' + fmtTime + '] ' +
' ' + action.eventName + '' +
data
);
break;
default:
_throw('Unexpected scheduled action type: ' + action.action);
}
}
exports.endEvent = endEvent;
exports.signal = signal;
exports.startEvent = startEvent;
exports.disable = disable;

View File

@ -0,0 +1,34 @@
function ModuleDescriptor(fields) {
if (!fields.id) {
throw new Error('Missing required fields id');
}
this.id = fields.id;
if (!fields.path) {
throw new Error('Missing required fields path');
}
this.path = fields.path;
if (!fields.dependencies) {
throw new Error('Missing required fields dependencies');
}
this.dependencies = fields.dependencies;
this.resolveDependency = fields.resolveDependency;
this.entry = fields.entry || false;
this.isPolyfill = fields.isPolyfill || false;
this._fields = fields;
}
ModuleDescriptor.prototype.toJSON = function() {
return {
id: this.id,
path: this.path,
dependencies: this.dependencies
}
};
module.exports = ModuleDescriptor;

View File

@ -0,0 +1,101 @@
'use strict';
var fs = jest.genMockFromModule('fs');
fs.realpath.mockImpl(function(filepath, callback) {
var node;
try {
node = getToNode(filepath);
} catch (e) {
return callback(e);
}
if (node && typeof node === 'object' && node.SYMLINK != null) {
return callback(null, node.SYMLINK);
}
callback(null, filepath);
});
fs.readdir.mockImpl(function(filepath, callback) {
var node;
try {
node = getToNode(filepath);
if (node && typeof node === 'object' && node.SYMLINK != null) {
node = getToNode(node.SYMLINK);
}
} catch (e) {
return callback(e);
}
if (!(node && typeof node === 'object' && node.SYMLINK == null)) {
return callback(new Error(filepath + ' is not a directory.'));
}
callback(null, Object.keys(node));
});
fs.readFile.mockImpl(function(filepath, encoding, callback) {
try {
var node = getToNode(filepath);
// dir check
if (node && typeof node === 'object' && node.SYMLINK == null) {
callback(new Error('Trying to read a dir, ESIDR, or whatever'));
}
return callback(null, node);
} catch (e) {
return callback(e);
}
});
fs.lstat.mockImpl(function(filepath, callback) {
var node;
try {
node = getToNode(filepath);
} catch (e) {
return callback(e);
}
if (node && typeof node === 'object' && node.SYMLINK == null) {
callback(null, {
isDirectory: function() {
return true;
},
isSymbolicLink: function() {
return false;
}
});
} else {
callback(null, {
isDirectory: function() {
return false;
},
isSymbolicLink: function() {
if (typeof node === 'object' && node.SYMLINK) {
return true;
}
return false;
}
});
}
});
var filesystem;
fs.__setMockFilesystem = function(object) {
filesystem = object;
return filesystem;
};
function getToNode(filepath) {
var parts = filepath.split('/');
if (parts[0] !== '') {
throw new Error('Make sure all paths are absolute.');
}
var node = filesystem;
parts.slice(1).forEach(function(part) {
node = node[part];
});
return node;
}
module.exports = fs;

View File

@ -0,0 +1,731 @@
'use strict';
jest
.dontMock('../index')
.dontMock('q')
.dontMock('path')
.dontMock('absolute-path')
.dontMock('../../../../fb-path-utils')
.dontMock('../docblock')
.setMock('../../../ModuleDescriptor', function(data) {return data;});
var q = require('q');
describe('DependencyGraph', function() {
var DependencyGraph;
var fileWatcher;
var fs;
beforeEach(function() {
fs = require('fs');
DependencyGraph = require('../index');
fileWatcher = {
on: function() {
return this;
}
};
});
describe('getOrderedDependencies', function() {
pit('should get dependencies', function() {
var root = '/root';
fs.__setMockFilesystem({
'root': {
'index.js': [
'/**',
' * @providesModule index',
' */',
'require("a")'
].join('\n'),
'a.js': [
'/**',
' * @providesModule a',
' */',
].join('\n'),
}
});
var dgraph = new DependencyGraph({roots: [root], fileWatcher: fileWatcher});
return dgraph.load().then(function() {
expect(dgraph.getOrderedDependencies('/root/index.js'))
.toEqual([
{id: 'index', path: '/root/index.js', dependencies: ['a']},
{id: 'a', path: '/root/a.js', dependencies: []},
]);
});
});
pit('should get recursive dependencies', function() {
var root = '/root';
fs.__setMockFilesystem({
'root': {
'index.js': [
'/**',
' * @providesModule index',
' */',
'require("a")',
].join('\n'),
'a.js': [
'/**',
' * @providesModule a',
' */',
'require("index")',
].join('\n'),
}
});
var dgraph = new DependencyGraph({roots: [root], fileWatcher: fileWatcher});
return dgraph.load().then(function() {
expect(dgraph.getOrderedDependencies('/root/index.js'))
.toEqual([
{id: 'index', path: '/root/index.js', dependencies: ['a']},
{id: 'a', path: '/root/a.js', dependencies: ['index']},
]);
});
});
pit('should work with packages', function() {
var root = '/root';
fs.__setMockFilesystem({
'root': {
'index.js': [
'/**',
' * @providesModule index',
' */',
'require("aPackage")',
].join('\n'),
'aPackage': {
'package.json': JSON.stringify({
name: 'aPackage',
main: 'main.js'
}),
'main.js': 'lol'
}
}
});
var dgraph = new DependencyGraph({roots: [root], fileWatcher: fileWatcher});
return dgraph.load().then(function() {
expect(dgraph.getOrderedDependencies('/root/index.js'))
.toEqual([
{id: 'index', path: '/root/index.js', dependencies: ['aPackage']},
{ id: 'aPackage/main',
path: '/root/aPackage/main.js',
dependencies: []
},
]);
});
});
pit('should ignore malformed packages', function() {
var root = '/root';
fs.__setMockFilesystem({
'root': {
'index.js': [
'/**',
' * @providesModule index',
' */',
'require("aPackage")',
].join('\n'),
'aPackage': {
'package.json': 'lol',
'main.js': 'lol'
}
}
});
var dgraph = new DependencyGraph({roots: [root], fileWatcher: fileWatcher});
return dgraph.load().then(function() {
expect(dgraph.getOrderedDependencies('/root/index.js'))
.toEqual([
{id: 'index', path: '/root/index.js', dependencies: ['aPackage']},
]);
});
});
pit('can have multiple modules with the same name', function() {
var root = '/root';
fs.__setMockFilesystem({
'root': {
'index.js': [
'/**',
' * @providesModule index',
' */',
'require("b")',
].join('\n'),
'b.js': [
'/**',
' * @providesModule b',
' */',
].join('\n'),
'c.js': [
'/**',
' * @providesModule c',
' */',
].join('\n'),
'somedir': {
'somefile.js': [
'/**',
' * @providesModule index',
' */',
'require("c")',
].join('\n')
}
}
});
var dgraph = new DependencyGraph({roots: [root], fileWatcher: fileWatcher});
return dgraph.load().then(function() {
expect(dgraph.getOrderedDependencies('/root/somedir/somefile.js'))
.toEqual([
{ id: 'index',
path: '/root/somedir/somefile.js',
dependencies: ['c']
},
{ id: 'c',
path: '/root/c.js',
dependencies: []
},
]);
});
});
pit('providesModule wins when conflict with package', function() {
var root = '/root';
fs.__setMockFilesystem({
'root': {
'index.js': [
'/**',
' * @providesModule index',
' */',
'require("aPackage")',
].join('\n'),
'b.js': [
'/**',
' * @providesModule aPackage',
' */',
].join('\n'),
'aPackage': {
'package.json': JSON.stringify({
name: 'aPackage',
main: 'main.js'
}),
'main.js': 'lol'
}
}
});
var dgraph = new DependencyGraph({roots: [root], fileWatcher: fileWatcher});
return dgraph.load().then(function() {
expect(dgraph.getOrderedDependencies('/root/index.js'))
.toEqual([
{ id: 'index',
path: '/root/index.js',
dependencies: ['aPackage']
},
{ id: 'aPackage',
path: '/root/b.js',
dependencies: []
},
]);
});
});
pit('should be forgiving with missing requires', function() {
var root = '/root';
fs.__setMockFilesystem({
'root': {
'index.js': [
'/**',
' * @providesModule index',
' */',
'require("lolomg")',
].join('\n')
}
});
var dgraph = new DependencyGraph({roots: [root], fileWatcher: fileWatcher});
return dgraph.load().then(function() {
expect(dgraph.getOrderedDependencies('/root/index.js'))
.toEqual([
{ id: 'index',
path: '/root/index.js',
dependencies: ['lolomg']
}
]);
});
});
pit('should work with packages with subdirs', function() {
var root = '/root';
fs.__setMockFilesystem({
'root': {
'index.js': [
'/**',
' * @providesModule index',
' */',
'require("aPackage/subdir/lolynot")',
].join('\n'),
'aPackage': {
'package.json': JSON.stringify({
name: 'aPackage',
main: 'main.js'
}),
'main.js': 'lol',
'subdir': {
'lolynot.js': 'lolynot'
}
}
}
});
var dgraph = new DependencyGraph({roots: [root], fileWatcher: fileWatcher});
return dgraph.load().then(function() {
expect(dgraph.getOrderedDependencies('/root/index.js'))
.toEqual([
{ id: 'index',
path: '/root/index.js',
dependencies: ['aPackage/subdir/lolynot']
},
{ id: 'aPackage/subdir/lolynot',
path: '/root/aPackage/subdir/lolynot.js',
dependencies: []
},
]);
});
});
pit('should work with packages with symlinked subdirs', function() {
var root = '/root';
fs.__setMockFilesystem({
'symlinkedPackage': {
'package.json': JSON.stringify({
name: 'aPackage',
main: 'main.js'
}),
'main.js': 'lol',
'subdir': {
'lolynot.js': 'lolynot'
}
},
'root': {
'index.js': [
'/**',
' * @providesModule index',
' */',
'require("aPackage/subdir/lolynot")',
].join('\n'),
'aPackage': { SYMLINK: '/symlinkedPackage' },
}
});
var dgraph = new DependencyGraph({roots: [root], fileWatcher: fileWatcher});
return dgraph.load().then(function() {
expect(dgraph.getOrderedDependencies('/root/index.js'))
.toEqual([
{ id: 'index',
path: '/root/index.js',
dependencies: ['aPackage/subdir/lolynot']
},
{ id: 'aPackage/subdir/lolynot',
path: '/symlinkedPackage/subdir/lolynot.js',
dependencies: []
},
]);
});
});
pit('should work with relative modules in packages', function() {
var root = '/root';
fs.__setMockFilesystem({
'root': {
'index.js': [
'/**',
' * @providesModule index',
' */',
'require("aPackage")',
].join('\n'),
'aPackage': {
'package.json': JSON.stringify({
name: 'aPackage',
main: 'main.js'
}),
'main.js': 'require("./subdir/lolynot")',
'subdir': {
'lolynot.js': 'require("../other")'
},
'other.js': 'some code'
}
}
});
var dgraph = new DependencyGraph({roots: [root], fileWatcher: fileWatcher});
return dgraph.load().then(function() {
expect(dgraph.getOrderedDependencies('/root/index.js'))
.toEqual([
{ id: 'index',
path: '/root/index.js',
dependencies: ['aPackage']
},
{ id: 'aPackage/main',
path: '/root/aPackage/main.js',
dependencies: ['./subdir/lolynot']
},
{ id: 'aPackage/subdir/lolynot',
path: '/root/aPackage/subdir/lolynot.js',
dependencies: ['../other']
},
{ id: 'aPackage/other',
path: '/root/aPackage/other.js',
dependencies: []
},
]);
});
});
});
describe('file watch updating', function() {
var fileWatcher;
var triggerFileChange;
beforeEach(function() {
fileWatcher = {
on: function(eventType, callback) {
if (eventType !== 'all') {
throw new Error('Can only handle "all" event in watcher.');
}
triggerFileChange = callback;
return this;
}
};
});
pit('updates module dependencies', function() {
var root = '/root';
var filesystem = fs.__setMockFilesystem({
'root': {
'index.js': [
'/**',
' * @providesModule index',
' */',
'require("aPackage")',
'require("foo")'
].join('\n'),
'foo': [
'/**',
' * @providesModule foo',
' */',
'require("aPackage")'
].join('\n'),
'aPackage': {
'package.json': JSON.stringify({
name: 'aPackage',
main: 'main.js'
}),
'main.js': 'main',
}
}
});
var dgraph = new DependencyGraph({roots: [root], fileWatcher: fileWatcher});
return dgraph.load().then(function() {
filesystem.root['index.js'] =
filesystem.root['index.js'].replace('require("foo")', '');
triggerFileChange('change', 'index.js', root);
return dgraph.load().then(function() {
expect(dgraph.getOrderedDependencies('/root/index.js'))
.toEqual([
{ id: 'index',
path: '/root/index.js',
dependencies: ['aPackage']
},
{ id: 'aPackage/main',
path: '/root/aPackage/main.js',
dependencies: []
},
]);
});
});
});
pit('updates module dependencies on file change', function() {
var root = '/root';
var filesystem = fs.__setMockFilesystem({
'root': {
'index.js': [
'/**',
' * @providesModule index',
' */',
'require("aPackage")',
'require("foo")'
].join('\n'),
'foo.js': [
'/**',
' * @providesModule foo',
' */',
'require("aPackage")'
].join('\n'),
'aPackage': {
'package.json': JSON.stringify({
name: 'aPackage',
main: 'main.js'
}),
'main.js': 'main',
}
}
});
var dgraph = new DependencyGraph({roots: [root], fileWatcher: fileWatcher});
return dgraph.load().then(function() {
filesystem.root['index.js'] =
filesystem.root['index.js'].replace('require("foo")', '');
triggerFileChange('change', 'index.js', root);
return dgraph.load().then(function() {
expect(dgraph.getOrderedDependencies('/root/index.js'))
.toEqual([
{ id: 'index',
path: '/root/index.js',
dependencies: ['aPackage']
},
{ id: 'aPackage/main',
path: '/root/aPackage/main.js',
dependencies: []
},
]);
});
});
});
pit('updates module dependencies on file delete', function() {
var root = '/root';
var filesystem = fs.__setMockFilesystem({
'root': {
'index.js': [
'/**',
' * @providesModule index',
' */',
'require("aPackage")',
'require("foo")'
].join('\n'),
'foo.js': [
'/**',
' * @providesModule foo',
' */',
'require("aPackage")'
].join('\n'),
'aPackage': {
'package.json': JSON.stringify({
name: 'aPackage',
main: 'main.js'
}),
'main.js': 'main',
}
}
});
var dgraph = new DependencyGraph({roots: [root], fileWatcher: fileWatcher});
return dgraph.load().then(function() {
delete filesystem.root.foo;
triggerFileChange('delete', 'foo.js', root);
return dgraph.load().then(function() {
expect(dgraph.getOrderedDependencies('/root/index.js'))
.toEqual([
{ id: 'index',
path: '/root/index.js',
dependencies: ['aPackage', 'foo']
},
{ id: 'aPackage/main',
path: '/root/aPackage/main.js',
dependencies: []
},
]);
});
});
});
pit('updates module dependencies on file add', function() {
var root = '/root';
var filesystem = fs.__setMockFilesystem({
'root': {
'index.js': [
'/**',
' * @providesModule index',
' */',
'require("aPackage")',
'require("foo")'
].join('\n'),
'foo.js': [
'/**',
' * @providesModule foo',
' */',
'require("aPackage")'
].join('\n'),
'aPackage': {
'package.json': JSON.stringify({
name: 'aPackage',
main: 'main.js'
}),
'main.js': 'main',
}
}
});
var dgraph = new DependencyGraph({roots: [root], fileWatcher: fileWatcher});
return dgraph.load().then(function() {
filesystem.root['bar.js'] = [
'/**',
' * @providesModule bar',
' */',
'require("foo")'
].join('\n');
triggerFileChange('add', 'bar.js', root);
filesystem.root.aPackage['main.js'] = 'require("bar")';
triggerFileChange('change', 'aPackage/main.js', root);
return dgraph.load().then(function() {
expect(dgraph.getOrderedDependencies('/root/index.js'))
.toEqual([
{ id: 'index',
path: '/root/index.js',
dependencies: ['aPackage', 'foo']
},
{ id: 'aPackage/main',
path: '/root/aPackage/main.js',
dependencies: ['bar']
},
{ id: 'bar',
path: '/root/bar.js',
dependencies: ['foo']
},
{ id: 'foo',
path: '/root/foo.js',
dependencies: ['aPackage']
},
]);
});
});
});
pit('runs changes through ignore filter', function() {
var root = '/root';
var filesystem = fs.__setMockFilesystem({
'root': {
'index.js': [
'/**',
' * @providesModule index',
' */',
'require("aPackage")',
'require("foo")'
].join('\n'),
'foo.js': [
'/**',
' * @providesModule foo',
' */',
'require("aPackage")'
].join('\n'),
'aPackage': {
'package.json': JSON.stringify({
name: 'aPackage',
main: 'main.js'
}),
'main.js': 'main',
}
}
});
var dgraph = new DependencyGraph({
roots: [root],
fileWatcher: fileWatcher,
ignoreFilePath: function(filePath) {
if (filePath === '/root/bar.js') {
return true;
}
return false;
}
});
return dgraph.load().then(function() {
filesystem.root['bar.js'] = [
'/**',
' * @providesModule bar',
' */',
'require("foo")'
].join('\n');
triggerFileChange('add', 'bar.js', root);
filesystem.root.aPackage['main.js'] = 'require("bar")';
triggerFileChange('change', 'aPackage/main.js', root);
return dgraph.load().then(function() {
expect(dgraph.getOrderedDependencies('/root/index.js'))
.toEqual([
{ id: 'index',
path: '/root/index.js',
dependencies: ['aPackage', 'foo']
},
{ id: 'aPackage/main',
path: '/root/aPackage/main.js',
dependencies: ['bar']
},
{ id: 'foo',
path: '/root/foo.js',
dependencies: ['aPackage']
},
]);
});
});
});
pit('should ignore directory updates', function() {
var root = '/root';
var filesystem = fs.__setMockFilesystem({
'root': {
'index.js': [
'/**',
' * @providesModule index',
' */',
'require("aPackage")',
'require("foo")'
].join('\n'),
'foo.js': [
'/**',
' * @providesModule foo',
' */',
'require("aPackage")'
].join('\n'),
'aPackage': {
'package.json': JSON.stringify({
name: 'aPackage',
main: 'main.js'
}),
'main.js': 'main',
}
}
});
var dgraph = new DependencyGraph({roots: [root], fileWatcher: fileWatcher});
return dgraph.load().then(function() {
triggerFileChange('change', 'aPackage', '/root', {
isDirectory: function(){ return true; }
});
return dgraph.load().then(function() {
expect(dgraph.getOrderedDependencies('/root/index.js'))
.toEqual([
{ id: 'index',
path: '/root/index.js',
dependencies: ['aPackage', 'foo']
},
{ id: 'aPackage/main',
path: '/root/aPackage/main.js',
dependencies: []
},
{ id: 'foo',
path: '/root/foo.js',
dependencies: ['aPackage']
},
]);
});
});
});
});
});

View File

@ -0,0 +1,88 @@
/**
* Copyright 2013 Facebook, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
var docblockRe = /^\s*(\/\*\*(.|\r?\n)*?\*\/)/;
var ltrimRe = /^\s*/;
/**
* @param {String} contents
* @return {String}
*/
function extract(contents) {
var match = contents.match(docblockRe);
if (match) {
return match[0].replace(ltrimRe, '') || '';
}
return '';
}
var commentStartRe = /^\/\*\*?/;
var commentEndRe = /\*\/$/;
var wsRe = /[\t ]+/g;
var stringStartRe = /(\r?\n|^) *\*/g;
var multilineRe = /(?:^|\r?\n) *(@[^\r\n]*?) *\r?\n *([^@\r\n\s][^@\r\n]+?) *\r?\n/g;
var propertyRe = /(?:^|\r?\n) *@(\S+) *([^\r\n]*)/g;
/**
* @param {String} contents
* @return {Array}
*/
function parse(docblock) {
docblock = docblock
.replace(commentStartRe, '')
.replace(commentEndRe, '')
.replace(wsRe, ' ')
.replace(stringStartRe, '$1');
// Normalize multi-line directives
var prev = '';
while (prev != docblock) {
prev = docblock;
docblock = docblock.replace(multilineRe, "\n$1 $2\n");
}
docblock = docblock.trim();
var result = [];
var match;
while (match = propertyRe.exec(docblock)) {
result.push([match[1], match[2]]);
}
return result;
}
/**
* Same as parse but returns an object of prop: value instead of array of paris
* If a property appers more than once the last one will be returned
*
* @param {String} contents
* @return {Object}
*/
function parseAsObject(docblock) {
var pairs = parse(docblock);
var result = {};
for (var i = 0; i < pairs.length; i++) {
result[pairs[i][0]] = pairs[i][1];
}
return result;
}
exports.extract = extract;
exports.parse = parse;
exports.parseAsObject = parseAsObject;

View File

@ -0,0 +1,25 @@
var path = require('path');
var DependecyGraph = require('./');
var example_project = path.resolve(__dirname, '../../../../example_project');
var watcher = new (require('../../../FileWatcher'))({projectRoot: example_project});
var graph = new DependecyGraph({
fileWatcher: watcher,
root: example_project
});
graph.load().then(function() {
var index = path.join(example_project, 'index.js');
console.log(graph.getOrderedDependencies(index));
}).done();
watcher.getWatcher().then(function(watcher) {
watcher.on('all', function() {
setImmediate(function() {
graph.load().then(function() {
var index = path.join(example_project, 'index.js');
console.log(graph.getOrderedDependencies(index));
});
})
});
});

View File

@ -0,0 +1,494 @@
'use strict';
var ModuleDescriptor = require('../../ModuleDescriptor');
var q = require('q');
var fs = require('fs');
var docblock = require('./docblock');
var path = require('path');
var isAbsolutePath = require('absolute-path');
var debug = require('debug')('DependecyGraph');
var util = require('util');
var readFile = q.nfbind(fs.readFile);
var readDir = q.nfbind(fs.readdir);
var lstat = q.nfbind(fs.lstat);
var realpath = q.nfbind(fs.realpath);
function DependecyGraph(options) {
this._roots = options.roots;
this._ignoreFilePath = options.ignoreFilePath || function(){};
this._loaded = false;
this._queue = this._roots.slice();
this._graph = Object.create(null);
this._packageByRoot = Object.create(null);
this._packagesById = Object.create(null);
this._moduleById = Object.create(null);
this._debugUpdateEvents = [];
this._fileWatcher = options.fileWatcher;
// Kick off the search process to precompute the dependency graph.
this._init();
}
DependecyGraph.prototype.load = function() {
return this._loading || (this._loading = this._search());
};
/**
* Given an entry file return an array of all the dependent module descriptors.
*/
DependecyGraph.prototype.getOrderedDependencies = function(entryPath) {
var absolutePath = this._getAbsolutePath(entryPath);
if (absolutePath == null) {
throw new Error('Cannot find entry file in any of the roots: ' + entryPath);
}
var module = this._graph[absolutePath];
if (module == null) {
throw new Error('Module with path "' + entryPath + '" is not in graph');
}
var self = this;
var deps = [];
var visited = Object.create(null);
// Node haste sucks. Id's aren't unique. So to make sure our entry point
// is the thing that ends up in our dependency list.
var graphMap = Object.create(this._moduleById);
graphMap[module.id] = module;
// Recursively collect the dependency list.
function collect(module) {
deps.push(module);
module.dependencies.forEach(function(name) {
var id = sansExtJs(name);
var dep = self.resolveDependency(module, id);
if (dep == null) {
debug(
'WARNING: Cannot find required module `%s` from module `%s`.',
name,
module.id
);
return;
}
if (!visited[dep.id]) {
visited[dep.id] = true;
collect(dep);
}
});
}
visited[module.id] = true;
collect(module);
return deps;
};
/**
* Given a module descriptor `fromModule` return the module descriptor for
* the required module `depModuleId`. It could be top-level or relative,
* or both.
*/
DependecyGraph.prototype.resolveDependency = function(
fromModule,
depModuleId
) {
var packageJson, modulePath, dep;
// Package relative modules starts with '.' or '..'.
if (depModuleId[0] !== '.') {
// 1. `depModuleId` is simply a top-level `providesModule`.
// 2. `depModuleId` is a package module but given the full path from the
// package, i.e. package_name/module_name
if (this._moduleById[sansExtJs(depModuleId)]) {
return this._moduleById[sansExtJs(depModuleId)];
}
// 3. `depModuleId` is a package and it's depending on the "main"
// resolution.
packageJson = this._packagesById[depModuleId];
// We are being forgiving here and raising an error because we could be
// processing a file that uses it's own require system.
if (packageJson == null) {
debug(
'WARNING: Cannot find required module `%s` from module `%s`.',
depModuleId,
fromModule.id
);
return;
}
var main = packageJson.main || 'index';
modulePath = withExtJs(path.join(packageJson._root, main));
dep = this._graph[modulePath];
if (dep == null) {
throw new Error(
'Cannot find package main file for pacakge: ' + packageJson._root
);
}
return dep;
} else {
// 4. `depModuleId` is a module defined in a package relative to
// `fromModule`.
packageJson = this._lookupPackage(fromModule.path);
if (packageJson == null) {
throw new Error(
'Expected relative module lookup from ' + fromModule.id + ' to ' +
depModuleId + ' to be within a package but no package.json found.'
);
}
// Example: depModuleId: ../a/b
// fromModule.path: /x/y/z
// modulePath: /x/y/a/b
var dir = path.dirname(fromModule.path);
modulePath = withExtJs(path.join(dir, depModuleId));
dep = this._graph[modulePath];
if (dep == null) {
debug(
'WARNING: Cannot find required module `%s` from module `%s`.' +
' Inferred required module path is %s',
depModuleId,
fromModule.id,
modulePath
);
return null;
}
return dep;
}
};
/**
* Intiates the filewatcher and kicks off the search process.
*/
DependecyGraph.prototype._init = function() {
var processChange = this._processFileChange.bind(this);
var watcher = this._fileWatcher;
this._loading = this.load().then(function() {
watcher.on('all', processChange);
});
};
/**
* Implements a DFS over the file system looking for modules and packages.
*/
DependecyGraph.prototype._search = function() {
var self = this;
var dir = this._queue.shift();
if (dir == null) {
return q.Promise.resolve(this._graph);
}
// Steps:
// 1. Read a dir and stat all the entries.
// 2. Filter the files and queue up the directories.
// 3. Process any package.json in the files
// 4. recur.
return readDir(dir)
.then(function(files){
return q.all(files.map(function(filePath) {
return realpath(path.join(dir, filePath)).catch(handleBrokenLink);
}));
})
.then(function(filePaths) {
filePaths = filePaths.filter(function(filePath) {
if (filePath == null) {
return false
}
return !self._ignoreFilePath(filePath);
});
var statsP = filePaths.map(function(filePath) {
return lstat(filePath).catch(handleBrokenLink);
});
return [
filePaths,
q.all(statsP)
];
})
.spread(function(files, stats) {
var modulePaths = files.filter(function(filePath, i) {
if (stats[i].isDirectory()) {
self._queue.push(filePath);
return false;
}
if (stats[i].isSymbolicLink()) {
return false;
}
return filePath.match(/\.js$/);
});
var processing = self._findAndProcessPackage(files, dir)
.then(function() {
return q.all(modulePaths.map(self._processModule.bind(self)));
});
return q.all([
processing,
self._search()
]);
})
.then(function() {
return self;
});
};
/**
* Given a list of files find a `package.json` file, and if found parse it
* and update indices.
*/
DependecyGraph.prototype._findAndProcessPackage = function(files, root) {
var self = this;
var packagePath;
for (var i = 0; i < files.length ; i++) {
var file = files[i];
if (path.basename(file) === 'package.json') {
packagePath = file;
break;
}
}
if (packagePath != null) {
return readFile(packagePath, 'utf8')
.then(function(content) {
var packageJson;
try {
packageJson = JSON.parse(content);
} catch (e) {
debug('WARNING: malformed package.json: ', packagePath);
return q();
}
if (packageJson.name == null) {
debug(
'WARNING: package.json `%s` is missing a name field',
packagePath
);
return q();
}
packageJson._root = root;
self._packageByRoot[root] = packageJson;
self._packagesById[packageJson.name] = packageJson;
return packageJson;
});
} else {
return q();
}
};
/**
* Parse a module and update indices.
*/
DependecyGraph.prototype._processModule = function(modulePath) {
var self = this;
return readFile(modulePath, 'utf8')
.then(function(content) {
var moduleDocBlock = docblock.parseAsObject(content);
var moduleData = { path: path.resolve(modulePath) };
if (moduleDocBlock.providesModule || moduleDocBlock.provides) {
moduleData.id =
moduleDocBlock.providesModule || moduleDocBlock.provides;
} else {
moduleData.id = self._lookupName(modulePath);
}
moduleData.dependencies = extractRequires(content);
var module = new ModuleDescriptor(moduleData);
self._updateGraphWithModule(module);
return module;
});
};
/**
* Compute the name of module relative to a package it may belong to.
*/
DependecyGraph.prototype._lookupName = function(modulePath) {
var packageJson = this._lookupPackage(modulePath);
if (packageJson == null) {
return path.resolve(modulePath);
} else {
var relativePath =
sansExtJs(path.relative(packageJson._root, modulePath));
return path.join(packageJson.name, relativePath);
}
};
DependecyGraph.prototype._deleteModule = function(module) {
delete this._graph[module.path];
// Others may keep a reference so we mark it as deleted.
module.deleted = true;
// Haste allows different module to have the same id.
if (this._moduleById[module.id] === module) {
delete this._moduleById[module.id];
}
};
/**
* Update the graph and indices with the module.
*/
DependecyGraph.prototype._updateGraphWithModule = function(module) {
if (this._graph[module.path]) {
this._deleteModule(this._graph[module.path]);
}
this._graph[module.path] = module;
if (this._moduleById[module.id]) {
debug(
'WARNING: Top-level module name conflict `%s`.\n' +
'module with path `%s` will replace `%s`',
module.id,
module.path,
this._moduleById[module.id].path
);
}
this._moduleById[module.id] = module;
};
/**
* Find the nearest package to a module.
*/
DependecyGraph.prototype._lookupPackage = function(modulePath) {
var packageByRoot = this._packageByRoot;
/**
* Auxiliary function to recursively lookup a package.
*/
function lookupPackage(currDir) {
// ideally we stop once we're outside root and this can be a simple child
// dir check. However, we have to support modules that was symlinked inside
// our project root.
if (currDir === '/') {
return null;
} else {
var packageJson = packageByRoot[currDir];
if (packageJson) {
return packageJson;
} else {
return lookupPackage(path.dirname(currDir));
}
}
}
return lookupPackage(path.dirname(modulePath));
};
/**
* Process a filewatcher change event.
*/
DependecyGraph.prototype._processFileChange = function(eventType, filePath, root, stat) {
var absPath = path.join(root, filePath);
if (this._ignoreFilePath(absPath)) {
return;
}
this._debugUpdateEvents.push({event: eventType, path: filePath});
if (eventType === 'delete') {
var module = this._graph[absPath];
if (module == null) {
return;
}
this._deleteModule(module);
} else if (!(stat && stat.isDirectory())) {
var self = this;
this._loading = this._loading.then(function() {
return self._processModule(absPath);
});
}
};
DependecyGraph.prototype.getDebugInfo = function() {
return '<h1>FileWatcher Update Events</h1>' +
'<pre>' + util.inspect(this._debugUpdateEvents) + '</pre>' +
'<h1> Graph dump </h1>' +
'<pre>' + util.inspect(this._graph) + '</pre>';
};
/**
* Searches all roots for the file and returns the first one that has file of the same path.
*/
DependecyGraph.prototype._getAbsolutePath = function(filePath) {
if (isAbsolutePath(filePath)) {
return filePath;
}
for (var i = 0, root; root = this._roots[i]; i++) {
var absPath = path.join(root, filePath);
if (this._graph[absPath]) {
return absPath;
}
}
return null;
};
/**
* Extract all required modules from a `code` string.
*/
var requireRe = /\brequire\s*\(\s*[\'"]([^"\']+)["\']\s*\)/g;
var blockCommentRe = /\/\*(.|\n)*?\*\//g;
var lineCommentRe = /\/\/.+(\n|$)/g;
function extractRequires(code) {
var deps = [];
code
.replace(blockCommentRe, '')
.replace(lineCommentRe, '')
.replace(requireRe, function(match, dep) {
deps.push(dep);
});
return deps;
}
/**
* `file` without the .js extension.
*/
function sansExtJs(file) {
if (file.match(/\.js$/)) {
return file.slice(0, -3);
} else {
return file;
}
}
/**
* `file` with the .js extension.
*/
function withExtJs(file) {
if (file.match(/\.js$/)) {
return file;
} else {
return file + '.js';
}
}
function handleBrokenLink(e) {
debug('WARNING: error stating, possibly broken symlink', e.message);
return q();
}
module.exports = DependecyGraph;

View File

@ -0,0 +1,195 @@
jest.dontMock('../')
.dontMock('q')
.setMock('../../ModuleDescriptor', function(data) {return data;});
var q = require('q');
describe('HasteDependencyResolver', function() {
var HasteDependencyResolver;
var DependencyGraph;
beforeEach(function() {
// For the polyfillDeps
require('path').join.mockImpl(function(a, b) {
return b;
});
HasteDependencyResolver = require('../');
DependencyGraph = require('../DependencyGraph');
});
describe('getDependencies', function() {
pit('should get dependencies with polyfills', function() {
var module = {id: 'index', path: '/root/index.js', dependencies: ['a']};
var deps = [module];
var depResolver = new HasteDependencyResolver({
projectRoot: '/root'
});
// Is there a better way? How can I mock the prototype instead?
var depGraph = depResolver._depGraph;
depGraph.getOrderedDependencies.mockImpl(function() {
return deps;
});
depGraph.load.mockImpl(function() {
return q();
});
return depResolver.getDependencies('/root/index.js')
.then(function(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'
],
},
module
]);
});
});
pit('should pass in more polyfills', function() {
var module = {id: 'index', path: '/root/index.js', dependencies: ['a']};
var deps = [module];
var depResolver = new HasteDependencyResolver({
projectRoot: '/root',
polyfillModuleNames: ['some module']
});
// Is there a better way? How can I mock the prototype instead?
var depGraph = depResolver._depGraph;
depGraph.getOrderedDependencies.mockImpl(function() {
return deps;
});
depGraph.load.mockImpl(function() {
return q();
});
return depResolver.getDependencies('/root/index.js')
.then(function(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'
],
},
{ path: 'some module',
id: 'some module',
isPolyfill: true,
dependencies: [
'polyfills/prelude.js',
'polyfills/require.js',
'polyfills/polyfills.js',
'polyfills/console.js',
'polyfills/error-guard.js',
]
},
module
]);
});
});
});
describe('wrapModule', function() {
it('should ', function() {
var depResolver = new HasteDependencyResolver({
projectRoot: '/root'
});
var depGraph = depResolver._depGraph;
var dependencies = ['x', 'y', 'z']
var code = [
'require("x")',
'require("y")',
'require("z")',
].join('\n');
depGraph.resolveDependency.mockImpl(function(fromModule, toModuleName) {
if (toModuleName === 'x') {
return {
id: 'changed'
};
} else if (toModuleName === 'y') {
return { id: 'y' };
}
return null;
});
var processedCode = depResolver.wrapModule({
id: 'test module',
path: '/root/test.js',
dependencies: dependencies
}, code);
expect(processedCode).toEqual([
"__d('test module',[\"changed\",\"y\"],function(global," +
" require, requireDynamic, requireLazy, module, exports) {" +
" require('changed')",
"require('y')",
'require("z")});',
].join('\n'));
});
});
});

View File

@ -0,0 +1,130 @@
'use strict';
var path = require('path');
var FileWatcher = require('../../FileWatcher');
var DependencyGraph = require('./DependencyGraph');
var ModuleDescriptor = require('../ModuleDescriptor');
var DEFINE_MODULE_CODE =
'__d(' +
'\'_moduleName_\',' +
'_deps_,' +
'function(global, require, requireDynamic, requireLazy, module, exports) {'+
' _code_' +
'}' +
');';
var DEFINE_MODULE_REPLACE_RE = /_moduleName_|_code_|_deps_/g;
var REL_REQUIRE_STMT = /require\(['"]([\.\/0-9A-Z_$\-]*)['"]\)/gi;
function HasteDependencyResolver(config) {
this._fileWatcher = config.nonPersistent
? FileWatcher.createDummyWatcher()
: new FileWatcher(config.projectRoots);
this._depGraph = new DependencyGraph({
roots: config.projectRoots,
ignoreFilePath: function(filepath) {
return filepath.indexOf('__tests__') !== -1 ||
(config.blacklistRE && config.blacklistRE.test(filepath));
},
fileWatcher: this._fileWatcher
});
this._polyfillModuleNames = [
config.dev
? path.join(__dirname, 'polyfills/prelude_dev.js')
: path.join(__dirname, 'polyfills/prelude.js'),
path.join(__dirname, 'polyfills/require.js'),
path.join(__dirname, 'polyfills/polyfills.js'),
path.join(__dirname, 'polyfills/console.js'),
path.join(__dirname, 'polyfills/error-guard.js'),
].concat(
config.polyfillModuleNames || []
);
}
HasteDependencyResolver.prototype.getDependencies = function(main) {
var depGraph = this._depGraph;
var self = this;
return depGraph.load()
.then(function() {
var dependencies = depGraph.getOrderedDependencies(main);
var mainModuleId = dependencies[0].id;
self._prependPolyfillDependencies(dependencies);
return {
mainModuleId: mainModuleId,
dependencies: dependencies
};
});
};
HasteDependencyResolver.prototype._prependPolyfillDependencies = function(
dependencies
) {
var polyfillModuleNames = this._polyfillModuleNames;
if (polyfillModuleNames.length > 0) {
var polyfillModules = polyfillModuleNames.map(
function(polyfillModuleName, idx) {
return new ModuleDescriptor({
path: polyfillModuleName,
id: polyfillModuleName,
dependencies: polyfillModuleNames.slice(0, idx),
isPolyfill: true
});
}
);
dependencies.unshift.apply(dependencies, polyfillModules);
}
};
HasteDependencyResolver.prototype.wrapModule = function(module, code) {
if (module.isPolyfill) {
return code;
}
var depGraph = this._depGraph;
var resolvedDeps = Object.create(null);
var resolvedDepsArr = [];
for (var i = 0; i < module.dependencies.length; i++) {
var depName = module.dependencies[i];
var dep = this._depGraph.resolveDependency(module, depName);
if (dep) {
resolvedDeps[depName] = dep.id;
resolvedDepsArr.push(dep.id);
}
}
var relativizedCode =
code.replace(REL_REQUIRE_STMT, function(codeMatch, depName) {
var dep = resolvedDeps[depName];
if (dep != null) {
return 'require(\'' + dep + '\')';
} else {
return codeMatch;
}
});
return DEFINE_MODULE_CODE.replace(DEFINE_MODULE_REPLACE_RE, function(key) {
return {
'_moduleName_': module.id,
'_code_': relativizedCode,
'_deps_': JSON.stringify(resolvedDepsArr),
}[key];
});
};
HasteDependencyResolver.prototype.end = function() {
return this._fileWatcher.end();
};
HasteDependencyResolver.prototype.getDebugInfo = function() {
return this._depGraph.getDebugInfo();
};
module.exports = HasteDependencyResolver;

View File

@ -0,0 +1,141 @@
/**
* Copyright 2013 Facebook, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
* This pipes all of our console logging functions to native logging so that
* JavaScript errors in required modules show up in Xcode via NSLog.
*
* @provides console
* @polyfill
*/
(function(global) {
var OBJECT_COLUMN_NAME = '(index)';
function setupConsole(global) {
if (!global.nativeLoggingHook) {
return;
}
function doNativeLog() {
var str = Array.prototype.map.call(arguments, function(arg) {
if (arg == null) {
return arg === null ? 'null' : 'undefined';
} else if (typeof arg === 'string') {
return '"' + arg + '"';
} else {
// Perform a try catch, just in case the object has a circular
// reference or stringify throws for some other reason.
try {
return JSON.stringify(arg);
} catch (e) {
if (typeof arg.toString === 'function') {
try {
return arg.toString();
} catch (e) {
return 'unknown';
}
}
}
}
}).join(', ');
global.nativeLoggingHook(str);
};
var repeat = function(element, n) {
return Array.apply(null, Array(n)).map(function() { return element; });
};
function consoleTablePolyfill(rows) {
// convert object -> array
if (!Array.isArray(rows)) {
var data = rows;
rows = [];
for (var key in data) {
if (data.hasOwnProperty(key)) {
var row = data[key];
row[OBJECT_COLUMN_NAME] = key;
rows.push(row);
}
}
}
if (rows.length === 0) {
global.nativeLoggingHook('');
return;
}
var columns = Object.keys(rows[0]).sort();
var stringRows = [];
var columnWidths = [];
// Convert each cell to a string. Also
// figure out max cell width for each column
columns.forEach(function(k, i) {
columnWidths[i] = k.length;
for (var j = 0; j < rows.length; j++) {
var cellStr = rows[j][k].toString();
stringRows[j] = stringRows[j] || [];
stringRows[j][i] = cellStr;
columnWidths[i] = Math.max(columnWidths[i], cellStr.length);
}
});
// Join all elements in the row into a single string with | separators
// (appends extra spaces to each cell to make separators | alligned)
var joinRow = function(row, space) {
var cells = row.map(function(cell, i) {
var extraSpaces = repeat(' ', columnWidths[i] - cell.length).join('');
return cell + extraSpaces;
});
space = space || ' ';
return cells.join(space + '|' + space);
};
var separators = columnWidths.map(function(columnWidth) {
return repeat('-', columnWidth).join('');
});
var separatorRow = joinRow(separators, '-');
var header = joinRow(columns);
var table = [header, separatorRow];
for (var i = 0; i < rows.length; i++) {
table.push(joinRow(stringRows[i]));
}
// Notice extra empty line at the beginning.
// Native logging hook adds "RCTLog >" at the front of every
// logged string, which would shift the header and screw up
// the table
global.nativeLoggingHook('\n' + table.join('\n'));
};
global.console = {
error: doNativeLog,
info: doNativeLog,
log: doNativeLog,
warn: doNativeLog,
table: consoleTablePolyfill
};
};
if (typeof module !== 'undefined') {
module.exports = setupConsole;
} else {
setupConsole(global);
}
})(this);

View File

@ -0,0 +1,82 @@
/**
* The particular require runtime that we are using looks for a global
* `ErrorUtils` object and if it exists, then it requires modules with the
* error handler specified via ErrorUtils.setGlobalHandler by calling the
* require function with applyWithGuard. Since the require module is loaded
* before any of the modules, this ErrorUtils must be defined (and the handler
* set) globally before requiring anything.
*/
/* eslint global-strict:0 */
(function(global) {
var ErrorUtils = {
_inGuard: 0,
_globalHandler: null,
setGlobalHandler: function(fun) {
ErrorUtils._globalHandler = fun;
},
reportError: function(error) {
Error._globalHandler && ErrorUtils._globalHandler(error);
},
applyWithGuard: function(fun, context, args) {
try {
ErrorUtils._inGuard++;
return fun.apply(context, args);
} catch (e) {
ErrorUtils._globalHandler && ErrorUtils._globalHandler(e);
} finally {
ErrorUtils._inGuard--;
}
},
applyWithGuardIfNeeded: function(fun, context, args) {
if (ErrorUtils.inGuard()) {
return fun.apply(context, args);
} else {
ErrorUtils.applyWithGuard(fun, context, args);
}
},
inGuard: function() {
return ErrorUtils._inGuard;
},
guard: function(fun, name, context) {
if (typeof fun !== "function") {
console.warn('A function must be passed to ErrorUtils.guard, got ', fun);
return null;
}
name = name || fun.name || '<generated guard>';
function guarded() {
return (
ErrorUtils.applyWithGuard(
fun,
context || this,
arguments,
null,
name
)
);
}
return guarded;
}
};
global.ErrorUtils = ErrorUtils;
/**
* This is the error handler that is called when we encounter an exception
* when loading a module.
*/
function setupErrorGuard() {
var onError = function(e) {
global.console.error(
'Error: ' +
'\n stack: ' + e.stack +
'\n line: ' + e.line +
'\n message: ' + e.message,
e
);
};
global.ErrorUtils.setGlobalHandler(onError);
}
setupErrorGuard();
})(this);

View File

@ -0,0 +1,75 @@
/**
* Copyright 2013 Facebook, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
* This pipes all of our console logging functions to native logging so that
* JavaScript errors in required modules show up in Xcode via NSLog.
*
* @provides Object.es6
* @polyfill
*/
// WARNING: This is an optimized version that fails on hasOwnProperty checks
// and non objects. It's not spec-compliant. It's a perf optimization.
Object.assign = function(target, sources) {
if (__DEV__) {
if (target == null) {
throw new TypeError('Object.assign target cannot be null or undefined');
}
if (typeof target !== 'object' && typeof target !== 'function') {
throw new TypeError(
'In this environment the target of assign MUST be an object.' +
'This error is a performance optimization and not spec compliant.'
);
}
}
for (var nextIndex = 1; nextIndex < arguments.length; nextIndex++) {
var nextSource = arguments[nextIndex];
if (nextSource == null) {
continue;
}
if (__DEV__) {
if (typeof nextSource !== 'object' &&
typeof nextSource !== 'function') {
throw new TypeError(
'In this environment the target of assign MUST be an object.' +
'This error is a performance optimization and not spec compliant.'
);
}
}
// We don't currently support accessors nor proxies. Therefore this
// copy cannot throw. If we ever supported this then we must handle
// exceptions and side-effects.
for (var key in nextSource) {
if (__DEV__) {
var hasOwnProperty = Object.prototype.hasOwnProperty;
if (!hasOwnProperty.call(nextSource, key)) {
throw new TypeError(
'One of the sources to assign has an enumerable key on the ' +
'prototype chain. This is an edge case that we do not support. ' +
'This error is a performance optimization and not spec compliant.'
);
}
}
target[key] = nextSource[key];
}
}
return target;
};

View File

@ -0,0 +1 @@
__DEV__ = false;

View File

@ -0,0 +1 @@
__DEV__ = true;

View File

@ -0,0 +1,626 @@
(function(global) {
// avoid redefining require()
if (global.require) {
return;
}
var __DEV__ = global.__DEV__;
var toString = Object.prototype.toString;
/**
* module index: {
* mod1: {
* exports: { ... },
* id: 'mod1',
* dependencies: ['mod1', 'mod2'],
* factory: function() { ... },
* waitingMap: { mod1: 1, mod3: 1, mod4: 1 },
* waiting: 2
* }
* }
*/
var modulesMap = {},
/**
* inverse index: {
* mod1: [modules, waiting for mod1],
* mod2: [modules, waiting for mod2]
* }
*/
dependencyMap = {},
/**
* modules whose reference counts are set out of order
*/
predefinedRefCounts = {},
_counter = 0,
REQUIRE_WHEN_READY = 1,
USED_AS_TRANSPORT = 2,
hop = Object.prototype.hasOwnProperty;
function _debugUnresolvedDependencies(names) {
var unresolved = Array.prototype.slice.call(names);
var visited = {};
var ii, name, module, dependency;
while (unresolved.length) {
name = unresolved.shift();
if (visited[name]) {
continue;
}
visited[name] = true;
module = modulesMap[name];
if (!module || !module.waiting) {
continue;
}
for (ii = 0; ii < module.dependencies.length; ii++) {
dependency = module.dependencies[ii];
if (!modulesMap[dependency] || modulesMap[dependency].waiting) {
unresolved.push(dependency);
}
}
}
for (name in visited) if (hop.call(visited, name)) {
unresolved.push(name);
}
var messages = [];
for (ii = 0; ii < unresolved.length; ii++) {
name = unresolved[ii];
var message = name;
module = modulesMap[name];
if (!module) {
message += ' is not defined';
} else if (!module.waiting) {
message += ' is ready';
} else {
var unresolvedDependencies = [];
for (var jj = 0; jj < module.dependencies.length; jj++) {
dependency = module.dependencies[jj];
if (!modulesMap[dependency] || modulesMap[dependency].waiting) {
unresolvedDependencies.push(dependency);
}
}
message += ' is waiting for ' + unresolvedDependencies.join(', ');
}
messages.push(message);
}
return messages.join('\n');
}
/**
* This is mainly for logging in ModuleErrorLogger.
*/
function ModuleError(msg) {
this.name = 'ModuleError';
this.message = msg;
this.stack = Error(msg).stack;
this.framesToPop = 2;
}
ModuleError.prototype = Object.create(Error.prototype);
ModuleError.prototype.constructor = ModuleError;
var _performance =
global.performance ||
global.msPerformance ||
global.webkitPerformance || {};
if (!_performance.now) {
_performance = global.Date;
}
var _now = _performance ?
_performance.now.bind(_performance) : function(){return 0;};
var _factoryStackCount = 0;
var _factoryTime = 0;
var _totalFactories = 0;
/**
* The require function conforming to CommonJS spec:
* http://wiki.commonjs.org/wiki/Modules/1.1.1
*
* To define a CommonJS-compliant module add the providesModule
* Haste header to your file instead of @provides. Your file is going
* to be executed in a separate context. Every variable/function you
* define will be local (private) to that module. To export local members
* use "exports" variable or return the exported value at the end of your
* file. Your code will have access to the "module" object.
* The "module" object will have an "id" property that is the id of your
* current module. "module" object will also have "exports" property that
* is the same as "exports" variable passed into your module context.
* You can require other modules using their ids.
*
* Haste will automatically pick dependencies from require() calls. So
* you don't have to manually specify @requires in your header.
*
* You cannot require() modules from non-CommonJS files. Write a legacy stub
* (@providesLegacy) and use @requires instead.
*
* @example
*
* / **
* * @providesModule math
* * /
* exports.add = function() {
* var sum = 0, i = 0, args = arguments, l = args.length;
* while (i < l) {
* sum += args[i++];
* }
* return sum;
* };
*
* / **
* * @providesModule increment
* * /
* var add = require('math').add;
* return function(val) {
* return add(val, 1);
* };
*
* / **
* * @providesModule program
* * /
* var inc = require('increment');
* var a = 1;
* inc(a); // 2
*
* module.id == "program";
*
*
* @param {String} id
* @throws when module is not loaded or not ready to be required
*/
function require(id) {
var module = modulesMap[id], dep, i, msg;
if (module && module.exports) {
// If ref count is 1, this was the last call, so undefine the module.
// The ref count can be null or undefined, but those are never === 1.
if (module.refcount-- === 1) {
delete modulesMap[id];
}
return module.exports;
}
if (global.ErrorUtils && !global.ErrorUtils.inGuard()) {
return ErrorUtils.applyWithGuard(require, this, arguments);
}
if (!module) {
msg = 'Requiring unknown module "' + id + '"';
if (__DEV__) {
msg += '. It may not be loaded yet. Did you forget to run arc build?';
}
throw new ModuleError(msg);
}
if (module.hasError) {
throw new ModuleError(
'Requiring module "' + id + '" which threw an exception'
);
}
if (module.waiting) {
throw new ModuleError(
'Requiring module "' + id + '" with unresolved dependencies: ' +
_debugUnresolvedDependencies([id])
);
}
var exports = module.exports = {};
var factory = module.factory;
if (toString.call(factory) === '[object Function]') {
var args = [],
dependencies = module.dependencies,
length = dependencies.length,
ret;
if (module.special & USED_AS_TRANSPORT) {
length = Math.min(length, factory.length);
}
try {
for (i = 0; args.length < length; i++) {
dep = dependencies[i];
if (!module.inlineRequires[dep]) {
args.push(dep === 'module' ? module :
(dep === 'exports' ? exports :
require.call(null, dep)));
}
}
++_totalFactories;
if (_factoryStackCount++ === 0) {
_factoryTime -= _now();
}
try {
ret = factory.apply(module.context || global, args);
} catch (e) {
if (modulesMap.ex && modulesMap.erx) {
// when ErrorUtils is ready, ex and erx are ready. otherwise, we
// don't append module id to the error message but still throw it
var ex = require.call(null, 'ex');
var erx = require.call(null, 'erx');
var messageWithParams = erx(e.message);
if (messageWithParams[0].indexOf(' from module "%s"') < 0) {
messageWithParams[0] += ' from module "%s"';
messageWithParams[messageWithParams.length] = id;
}
e.message = ex.apply(null, messageWithParams);
}
throw e;
} finally {
if (--_factoryStackCount === 0) {
_factoryTime += _now();
}
}
} catch (e) {
module.hasError = true;
module.exports = null;
throw e;
}
if (ret) {
if (__DEV__) {
if (typeof ret != 'object' && typeof ret != 'function') {
throw new ModuleError(
'Factory for module "' + id + '" returned ' +
'an invalid value "' + ret + '". ' +
'Returned value should be either a function or an object.'
);
}
}
module.exports = ret;
}
} else {
module.exports = factory;
}
// If ref count is 1, this was the last call, so undefine the module.
// The ref count can be null or undefined, but those are never === 1.
if (module.refcount-- === 1) {
delete modulesMap[id];
}
return module.exports;
}
require.__getFactoryTime = function() {
return (_factoryStackCount ? _now() : 0) + _factoryTime;
};
require.__getTotalFactories = function() {
return _totalFactories;
};
/**
* The define function conforming to CommonJS proposal:
* http://wiki.commonjs.org/wiki/Modules/AsynchronousDefinition
*
* define() allows you to explicitly state dependencies of your module
* in javascript. It's most useful in non-CommonJS files.
*
* define() is used internally by haste as a transport for CommonJS
* modules. So there's no need to use define() if you use providesModule
*
* @example
* / **
* * @provides alpha
* * /
*
* // Sets up the module with ID of "alpha", that uses require,
* // exports and the module with ID of "beta":
* define("alpha", ["require", "exports", "beta"],
* function (require, exports, beta) {
* exports.verb = function() {
* return beta.verb();
* //Or:
* return require("beta").verb();
* }
* });
*
* / **
* * @provides alpha
* * /
* // An anonymous module could be defined (module id derived from filename)
* // that returns an object literal:
*
* define(["alpha"], function (alpha) {
* return {
* verb: function(){
* return alpha.verb() + 2;
* }
* };
* });
*
* / **
* * @provides alpha
* * /
* // A dependency-free module can define a direct object literal:
*
* define({
* add: function(x, y){
* return x + y;
* }
* });
*
* @param {String} id optional
* @param {Array} dependencies optional
* @param {Object|Function} factory
*/
function define(id, dependencies, factory,
_special, _context, _refCount, _inlineRequires) {
if (dependencies === undefined) {
dependencies = [];
factory = id;
id = _uid();
} else if (factory === undefined) {
factory = dependencies;
if (toString.call(id) === '[object Array]') {
dependencies = id;
id = _uid();
} else {
dependencies = [];
}
}
// Non-standard: we allow modules to be undefined. This is designed for
// temporary modules.
var canceler = { cancel: _undefine.bind(this, id) };
var record = modulesMap[id];
// Nonstandard hack: we call define with null deps and factory, but a
// non-null reference count (e.g. define('name', null, null, 0, null, 4))
// when this module is defined elsewhere and we just need to update the
// reference count. We use this hack to avoid having to expose another
// global function to increment ref counts.
if (record) {
if (_refCount) {
record.refcount += _refCount;
}
// Calling define() on a pre-existing module does not redefine it
return canceler;
} else if (!dependencies && !factory && _refCount) {
// If this module hasn't been defined yet, store the ref count. We'll use
// it when the module is defined later.
predefinedRefCounts[id] = (predefinedRefCounts[id] || 0) + _refCount;
return canceler;
} else {
// Defining a new module
record = { id: id };
record.refcount = (predefinedRefCounts[id] || 0) + (_refCount || 0);
delete predefinedRefCounts[id];
}
if (__DEV__) {
if (
!factory ||
(typeof factory != 'object' && typeof factory != 'function' &&
typeof factory != 'string')) {
throw new ModuleError(
'Invalid factory "' + factory + '" for module "' + id + '". ' +
'Factory should be either a function or an object.'
);
}
if (toString.call(dependencies) !== '[object Array]') {
throw new ModuleError(
'Invalid dependencies for module "' + id + '". ' +
'Dependencies must be passed as an array.'
);
}
}
record.factory = factory;
record.dependencies = dependencies;
record.context = _context;
record.special = _special;
record.inlineRequires = _inlineRequires || {};
record.waitingMap = {};
record.waiting = 0;
record.hasError = false;
modulesMap[id] = record;
_initDependencies(id);
return canceler;
}
function _undefine(id) {
if (!modulesMap[id]) {
return;
}
var module = modulesMap[id];
delete modulesMap[id];
for (var dep in module.waitingMap) {
if (module.waitingMap[dep]) {
delete dependencyMap[dep][id];
}
}
for (var ii = 0; ii < module.dependencies.length; ii++) {
dep = module.dependencies[ii];
if (modulesMap[dep]) {
if (modulesMap[dep].refcount-- === 1) {
_undefine(dep);
}
} else if (predefinedRefCounts[dep]) {
predefinedRefCounts[dep]--;
}
// Subtle: we won't account for this one fewer reference if we don't have
// the dependency's definition or reference count yet.
}
}
/**
* 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++;
}
function _addDependency(module, dep) {
// do not add duplicate dependencies and circ deps
if (!module.waitingMap[dep] && module.id !== dep) {
module.waiting++;
module.waitingMap[dep] = 1;
dependencyMap[dep] || (dependencyMap[dep] = {});
dependencyMap[dep][module.id] = 1;
}
}
function _initDependencies(id) {
var modulesToRequire = [];
var module = modulesMap[id];
var dep, i, subdep;
// initialize id's waitingMap
for (i = 0; i < module.dependencies.length; i++) {
dep = module.dependencies[i];
if (!modulesMap[dep]) {
_addDependency(module, dep);
} else if (modulesMap[dep].waiting) {
for (subdep in modulesMap[dep].waitingMap) {
if (modulesMap[dep].waitingMap[subdep]) {
_addDependency(module, subdep);
}
}
}
}
if (module.waiting === 0 && module.special & REQUIRE_WHEN_READY) {
modulesToRequire.push(id);
}
// update modules depending on id
if (dependencyMap[id]) {
var deps = dependencyMap[id];
var submodule;
dependencyMap[id] = undefined;
for (dep in deps) {
submodule = modulesMap[dep];
// add all deps of id
for (subdep in module.waitingMap) {
if (module.waitingMap[subdep]) {
_addDependency(submodule, subdep);
}
}
// remove id itself
if (submodule.waitingMap[id]) {
submodule.waitingMap[id] = undefined;
submodule.waiting--;
}
if (submodule.waiting === 0 &&
submodule.special & REQUIRE_WHEN_READY) {
modulesToRequire.push(dep);
}
}
}
// run everything that's ready
for (i = 0; i < modulesToRequire.length; i++) {
require.call(null, modulesToRequire[i]);
}
}
function _register(id, exports) {
var module = modulesMap[id] = { id: id };
module.exports = exports;
module.refcount = 0;
}
// pseudo name used in common-require
// see require() function for more info
_register('module', 0);
_register('exports', 0);
_register('define', define);
_register('global', global);
_register('require', require);
_register('requireDynamic', require);
_register('requireLazy', requireLazy);
define.amd = {};
global.define = define;
global.require = require;
global.requireDynamic = require;
global.requireLazy = requireLazy;
require.__debug = {
modules: modulesMap,
deps: dependencyMap,
printDependencyInfo: function() {
if (!global.console) {
return;
}
var names = Object.keys(require.__debug.deps);
global.console.log(_debugUnresolvedDependencies(names));
}
};
/**
* All @providesModule files are wrapped by this function by makehaste. It
* is a convenience function around define() that prepends a bunch of required
* modules (global, require, module, etc) so that we don't have to spit that
* 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'];
define(id, defaultDeps.concat(deps), factory, _special || USED_AS_TRANSPORT,
null, null, _inlineRequires);
};
})(this);

View File

@ -0,0 +1,12 @@
var HasteDependencyResolver = require('./haste');
var NodeDependencyResolver = require('./node');
module.exports = function createDependencyResolver(options) {
if (options.moduleFormat === 'haste') {
return new HasteDependencyResolver(options);
} else if (options.moduleFormat === 'node') {
return new NodeDependencyResolver(options);
} else {
throw new Error('unsupported');
}
};

View File

@ -0,0 +1,48 @@
var Promise = require('q').Promise;
var ModuleDescriptor = require('../ModuleDescriptor');
var mdeps = require('module-deps');
var path = require('path');
var fs = require('fs');
// var REQUIRE_RUNTIME = fs.readFileSync(
// path.join(__dirname, 'require.js')
// ).toString();
exports.getRuntimeCode = function() {
return REQUIRE_RUNTIME;
};
exports.wrapModule = function(id, source) {
return Promise.resolve(
'define(' + JSON.stringify(id) + ',' + ' function(exports, module) {\n'
+ source + '\n});'
);
};
exports.getDependencies = function(root, fileEntryPath) {
return new Promise(function(resolve, reject) {
fileEntryPath = path.join(process.cwd(), root, fileEntryPath);
var md = mdeps();
md.end({file: fileEntryPath});
var deps = [];
md.on('data', function(data) {
deps.push(
new ModuleDescriptor({
id: data.id,
deps: data.deps,
path: data.file,
entry: data.entry
})
);
});
md.on('end', function() {
resolve(deps);
});
});
};

View File

@ -0,0 +1,39 @@
'use strict';
jest.dontMock('../')
.dontMock('q')
.setMock('child_process', { exec: function(cmd, cb) { cb(null, '/usr/bin/watchman') } });
describe('FileWatcher', function() {
var FileWatcher;
var Watcher;
beforeEach(function() {
FileWatcher = require('../');
Watcher = require('sane').WatchmanWatcher;
Watcher.prototype.once.mockImplementation(function(type, callback) {
callback();
});
});
it('it should get the watcher instance when ready', function() {
var fileWatcher = new FileWatcher(['rootDir']);
return fileWatcher._loading.then(function(watchers) {
watchers.forEach(function(watcher) {
expect(watcher instanceof Watcher).toBe(true);
});
});
});
pit('it should end the watcher', function() {
var fileWatcher = new FileWatcher(['rootDir']);
Watcher.prototype.close.mockImplementation(function(callback) {
callback();
});
return fileWatcher.end().then(function() {
expect(Watcher.prototype.close).toBeCalled();
});
});
});

86
react-packager/src/FileWatcher/index.js vendored Normal file
View File

@ -0,0 +1,86 @@
'use strict';
var EventEmitter = require('events').EventEmitter;
var sane = require('sane');
var q = require('q');
var util = require('util');
var exec = require('child_process').exec;
var Promise = q.Promise;
var detectingWatcherClass = new Promise(function(resolve, reject) {
exec('which watchman', function(err, out) {
if (err || out.length === 0) {
resolve(sane.NodeWatcher);
} else {
resolve(sane.WatchmanWatcher);
}
});
});
module.exports = FileWatcher;
var MAX_WAIT_TIME = 3000;
function FileWatcher(projectRoots) {
var self = this;
this._loading = q.all(
projectRoots.map(createWatcher)
).then(function(watchers) {
watchers.forEach(function(watcher) {
watcher.on('all', function(type, filepath, root) {
self.emit('all', type, filepath, root);
});
});
return watchers;
});
this._loading.done();
}
util.inherits(FileWatcher, EventEmitter);
FileWatcher.prototype.end = function() {
return this._loading.then(function(watchers) {
watchers.forEach(function(watcher) {
delete watchersByRoot[watcher._root];
return q.ninvoke(watcher, 'close');
});
});
};
var watchersByRoot = Object.create(null);
function createWatcher(root) {
if (watchersByRoot[root] != null) {
return Promise.resolve(watchersByRoot[root]);
}
return detectingWatcherClass.then(function(Watcher) {
var watcher = new Watcher(root, {glob: '**/*.js'});
return new Promise(function(resolve, reject) {
var rejectTimeout = setTimeout(function() {
reject(new Error([
'Watcher took too long to load',
'Try running `watchman` from your terminal',
'https://facebook.github.io/watchman/docs/troubleshooting.html',
].join('\n')));
}, MAX_WAIT_TIME);
watcher.once('ready', function() {
clearTimeout(rejectTimeout);
watchersByRoot[root] = watcher;
watcher._root = root;
resolve(watcher);
});
});
});
}
FileWatcher.createDummyWatcher = function() {
var ev = new EventEmitter();
ev.end = function() {
return q();
};
return ev;
};

View File

@ -0,0 +1,129 @@
'use strict';
var path = require('path');
var version = require('../../package.json').version;
var tmpdir = require('os').tmpDir();
var pathUtils = require('../fb-path-utils');
var fs = require('fs');
var _ = require('underscore');
var q = require('q');
var Promise = q.Promise;
module.exports = Cache;
function Cache(projectConfig) {
this._cacheFilePath = cacheFilePath(projectConfig);
var data;
if (!projectConfig.resetCache) {
data = loadCacheSync(this._cacheFilePath);
} else {
data = Object.create(null);
}
this._data = data;
this._has = Object.prototype.hasOwnProperty.bind(data);
this._persistEventually = _.debounce(
this._persistCache.bind(this),
2000
);
}
Cache.prototype.get = function(filepath, loaderCb) {
if (!pathUtils.isAbsolutePath(filepath)) {
throw new Error('Use absolute paths');
}
var recordP = this._has(filepath)
? this._data[filepath]
: this._set(filepath, loaderCb(filepath));
return recordP.then(function(record) {
return record.data;
});
};
Cache.prototype._set = function(filepath, loaderPromise) {
return this._data[filepath] = loaderPromise.then(function(data) {
return [
data,
q.nfbind(fs.stat)(filepath)
];
}).spread(function(data, stat) {
this._persistEventually();
return {
data: data,
mtime: stat.mtime.getTime(),
};
}.bind(this));
};
Cache.prototype.invalidate = function(filepath){
if(this._has(filepath)) {
delete this._data[filepath];
}
}
Cache.prototype.end = function() {
return this._persistCache();
};
Cache.prototype._persistCache = function() {
if (this._persisting != null) {
return this._persisting;
}
var data = this._data;
var cacheFilepath = this._cacheFilePath;
return this._persisting = q.all(_.values(data))
.then(function(values) {
var json = Object.create(null);
Object.keys(data).forEach(function(key, i) {
json[key] = values[i];
});
return q.nfbind(fs.writeFile)(cacheFilepath, JSON.stringify(json));
})
.then(function() {
this._persisting = null;
return true;
}.bind(this));
};
function loadCacheSync(cacheFilepath) {
var ret = Object.create(null);
if (!fs.existsSync(cacheFilepath)) {
return ret;
}
var cacheOnDisk = JSON.parse(fs.readFileSync(cacheFilepath));
// Filter outdated cache and convert to promises.
Object.keys(cacheOnDisk).forEach(function(key) {
if (!fs.existsSync(key)) {
return;
}
var value = cacheOnDisk[key];
var stat = fs.statSync(key);
if (stat.mtime.getTime() === value.mtime) {
ret[key] = Promise.resolve(value);
}
});
return ret;
}
function cacheFilePath(projectConfig) {
var roots = projectConfig.projectRoots.join(',').split(path.sep).join('-');
var cacheVersion = projectConfig.cacheVersion || '0';
return path.join(
tmpdir,
[
'react-packager-cache',
version,
cacheVersion,
roots,
].join('-')
);
}

View File

@ -0,0 +1,5 @@
'use strict';
module.exports = function (data, callback) {
callback(null, {});
};

View File

@ -0,0 +1,202 @@
'use strict';
jest
.dontMock('underscore')
.dontMock('path')
.dontMock('q')
.dontMock('absolute-path')
.dontMock('../../fb-path-utils')
.dontMock('../Cache');
var q = require('q');
describe('JSTransformer Cache', function() {
var Cache;
beforeEach(function() {
require('os').tmpDir.mockImpl(function() {
return 'tmpDir';
});
Cache = require('../Cache');
});
describe('getting/settig', function() {
it('calls loader callback for uncached file', function() {
var cache = new Cache({projectRoots: ['/rootDir']});
var loaderCb = jest.genMockFn().mockImpl(function() {
return q();
});
cache.get('/rootDir/someFile', loaderCb);
expect(loaderCb).toBeCalledWith('/rootDir/someFile');
});
pit('gets the value from the loader callback', function() {
require('fs').stat.mockImpl(function(file, callback) {
callback(null, {
mtime: {
getTime: function() {}
}
});
});
var cache = new Cache({projectRoots: ['/rootDir']});
var loaderCb = jest.genMockFn().mockImpl(function() {
return q('lol');
});
return cache.get('/rootDir/someFile', loaderCb).then(function(value) {
expect(value).toBe('lol');
});
});
pit('caches the value after the first call', function() {
require('fs').stat.mockImpl(function(file, callback) {
callback(null, {
mtime: {
getTime: function() {}
}
});
});
var cache = new Cache({projectRoots: ['/rootDir']});
var loaderCb = jest.genMockFn().mockImpl(function() {
return q('lol');
});
return cache.get('/rootDir/someFile', loaderCb).then(function() {
var shouldNotBeCalled = jest.genMockFn();
return cache.get('/rootDir/someFile', shouldNotBeCalled)
.then(function(value) {
expect(shouldNotBeCalled).not.toBeCalled();
expect(value).toBe('lol');
});
});
});
});
describe('loading cache from disk', function() {
var fileStats;
beforeEach(function() {
fileStats = {
'/rootDir/someFile': {
mtime: {
getTime: function() {
return 22;
}
}
},
'/rootDir/foo': {
mtime: {
getTime: function() {
return 11;
}
}
}
};
var fs = require('fs');
fs.existsSync.mockImpl(function() {
return true;
});
fs.statSync.mockImpl(function(filePath) {
return fileStats[filePath];
});
fs.readFileSync.mockImpl(function() {
return JSON.stringify({
'/rootDir/someFile': {
mtime: 22,
data: 'oh hai'
},
'/rootDir/foo': {
mtime: 11,
data: 'lol wat'
}
});
});
});
pit('should load cache from disk', function() {
var cache = new Cache({projectRoots: ['/rootDir']});
var loaderCb = jest.genMockFn();
return cache.get('/rootDir/someFile', loaderCb).then(function(value) {
expect(loaderCb).not.toBeCalled();
expect(value).toBe('oh hai');
return cache.get('/rootDir/foo', loaderCb).then(function(value) {
expect(loaderCb).not.toBeCalled();
expect(value).toBe('lol wat');
});
});
});
pit('should not load outdated cache', function() {
require('fs').stat.mockImpl(function(file, callback) {
callback(null, {
mtime: {
getTime: function() {}
}
});
});
fileStats['/rootDir/foo'].mtime.getTime = function() {
return 123;
};
var cache = new Cache({projectRoots: ['/rootDir']});
var loaderCb = jest.genMockFn().mockImpl(function() {
return q('new value');
});
return cache.get('/rootDir/someFile', loaderCb).then(function(value) {
expect(loaderCb).not.toBeCalled();
expect(value).toBe('oh hai');
return cache.get('/rootDir/foo', loaderCb).then(function(value) {
expect(loaderCb).toBeCalled();
expect(value).toBe('new value');
});
});
});
});
describe('writing cache to disk', function() {
it('should write cache to disk', function() {
var index = 0;
var mtimes = [10, 20, 30];
var debounceIndex = 0;
require('underscore').debounce = function(callback) {
return function () {
if (++debounceIndex === 3) {
callback();
}
};
};
var fs = require('fs');
fs.stat.mockImpl(function(file, callback) {
callback(null, {
mtime: {
getTime: function() {
return mtimes[index++];
}
}
});
});
var cache = new Cache({projectRoots: ['/rootDir']});
cache.get('/rootDir/bar', function() {
return q('bar value');
});
cache.get('/rootDir/foo', function() {
return q('foo value');
});
cache.get('/rootDir/baz', function() {
return q('baz value');
});
jest.runAllTimers();
expect(fs.writeFile).toBeCalled();
});
});
});

View File

@ -0,0 +1,71 @@
'use strict';
jest
.dontMock('worker-farm')
.dontMock('q')
.dontMock('os')
.dontMock('../index');
var OPTIONS = {
transformModulePath: '/foo/bar'
};
describe('Transformer', function() {
var Transformer;
var workers;
beforeEach(function() {
workers = jest.genMockFn();
jest.setMock('worker-farm', jest.genMockFn().mockImpl(function() {
return workers;
}));
require('../Cache').prototype.get.mockImpl(function(filePath, callback) {
return callback();
});
require('fs').readFile.mockImpl(function(file, callback) {
callback(null, 'content');
});
Transformer = require('../');
});
pit('should loadFileAndTransform', function() {
workers.mockImpl(function(data, callback) {
callback(null, { code: 'transformed' });
});
require('fs').readFile.mockImpl(function(file, callback) {
callback(null, 'content');
});
return new Transformer(OPTIONS).loadFileAndTransform([], 'file', {})
.then(function(data) {
expect(data).toEqual({
code: 'transformed',
sourcePath: 'file',
sourceCode: 'content'
});
});
});
pit('should add file info to parse errors', function() {
require('fs').readFile.mockImpl(function(file, callback) {
callback(null, 'var x;\nvar answer = 1 = x;');
});
workers.mockImpl(function(data, callback) {
var esprimaError = new Error('Error: Line 2: Invalid left-hand side in assignment');
esprimaError.description = 'Invalid left-hand side in assignment';
esprimaError.lineNumber = 2;
esprimaError.column = 15;
callback(null, {error: esprimaError});
});
return new Transformer(OPTIONS).loadFileAndTransform([], 'foo-file.js', {})
.catch(function(error) {
expect(error.type).toEqual('TransformError');
expect(error.snippet).toEqual([
'var answer = 1 = x;',
' ^',
].join('\n'));
});
});
});

View File

@ -0,0 +1,112 @@
'use strict';
var os = require('os');
var fs = require('fs');
var q = require('q');
var Cache = require('./Cache');
var _ = require('underscore');
var workerFarm = require('worker-farm');
var readFile = q.nfbind(fs.readFile);
module.exports = Transformer;
Transformer.TransformError = TransformError;
function Transformer(projectConfig) {
this._cache = projectConfig.nonPersistent
? new DummyCache() : new Cache(projectConfig);
if (projectConfig.transformModulePath == null) {
this._failedToStart = q.Promise.reject(new Error('No transfrom module'));
} else {
this._workers = workerFarm(
{autoStart: true},
projectConfig.transformModulePath
);
}
}
Transformer.prototype.kill = function() {
this._workers && workerFarm.end(this._workers);
return this._cache.end();
};
Transformer.prototype.invalidateFile = function(filePath) {
this._cache.invalidate(filePath);
//TODO: We can read the file and put it into the cache right here
// This would simplify some caching logic as we can be sure that the cache is up to date
}
Transformer.prototype.loadFileAndTransform = function(
transformSets,
filePath,
options
) {
if (this._failedToStart) {
return this._failedToStart;
}
var workers = this._workers;
return this._cache.get(filePath, function() {
return readFile(filePath)
.then(function(buffer) {
var sourceCode = buffer.toString();
var opts = _.extend({}, options, {filename: filePath});
return q.nfbind(workers)({
transformSets: transformSets,
sourceCode: sourceCode,
options: opts,
}).then(
function(res) {
if (res.error) {
throw formatEsprimaError(res.error, filePath, sourceCode);
}
return {
code: res.code,
sourcePath: filePath,
sourceCode: sourceCode
};
}
);
});
});
};
function TransformError() {}
TransformError.__proto__ = SyntaxError.prototype;
function formatEsprimaError(err, filename, source) {
if (!(err.lineNumber && err.column)) {
return err;
}
var stack = err.stack.split('\n');
stack.shift();
var msg = 'TransformError: ' + err.description + ' ' + filename + ':' +
err.lineNumber + ':' + err.column;
var sourceLine = source.split('\n')[err.lineNumber - 1];
var snippet = sourceLine + '\n' + new Array(err.column - 1).join(' ') + '^';
stack.unshift(msg);
var error = new TransformError();
error.message = msg;
error.type = 'TransformError';
error.stack = stack.join('\n');
error.snippet = snippet;
error.filename = filename;
error.lineNumber = err.lineNumber;
error.column = err.column;
error.description = err.description;
return error;
}
function DummyCache() {}
DummyCache.prototype.get = function(filePath, loaderCb) {
return loaderCb();
};
DummyCache.prototype.end =
DummyCache.prototype.invalidate = function(){};

View File

@ -0,0 +1,26 @@
'use strict';
var transformer = require('./transformer');
module.exports = function (data, callback) {
var result;
try {
result = transformer.transform(
data.transformSets,
data.sourceCode,
data.options
);
} catch (e) {
return callback(null, {
error: {
lineNumber: e.lineNumber,
column: e.column,
message: e.message,
stack: e.stack,
description: e.description
}
});
}
callback(null, result);
};

132
react-packager/src/Packager/Package.js vendored Normal file
View File

@ -0,0 +1,132 @@
'use strict';
var _ = require('underscore');
var SourceMapGenerator = require('source-map').SourceMapGenerator;
var base64VLQ = require('./base64-vlq');
module.exports = Package;
function Package(sourceMapUrl) {
this._modules = [];
this._sourceMapUrl = sourceMapUrl;
}
Package.prototype.setMainModuleId = function(moduleId) {
this._mainModuleId = moduleId;
};
Package.prototype.addModule = function(
transformedCode,
sourceCode,
sourcePath
) {
this._modules.push({
transformedCode: transformedCode,
sourceCode: sourceCode,
sourcePath: sourcePath
});
};
Package.prototype.finalize = function(options) {
if (options.runMainModule) {
var runCode = ';require("' + this._mainModuleId + '");';
this.addModule(
runCode,
runCode,
'RunMainModule.js'
);
}
Object.freeze(this._modules);
Object.seal(this._modules);
};
Package.prototype.getSource = function() {
return this._source || (
this._source = _.pluck(this._modules, 'transformedCode').join('\n') + '\n' +
'RAW_SOURCE_MAP = ' + JSON.stringify(this.getSourceMap({excludeSource: true})) +
';\n' + '\/\/@ sourceMappingURL=' + this._sourceMapUrl
);
};
Package.prototype.getSourceMap = function(options) {
options = 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._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 mappings = '';
for (var i = 0; i < modules.length; i++) {
var module = modules[i];
var transformedCode = module.transformedCode;
var lastCharNewLine = false;
module.lines = 0;
for (var t = 0; t < transformedCode.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 - modules[i - 1].lines);
mappings += 'A';
} else if (lastCharNewLine) {
module.lines++;
mappings += line;
}
lastCharNewLine = transformedCode[t] === '\n';
if (lastCharNewLine) {
mappings += ';';
}
}
if (i != modules.length - 1) {
mappings += ';';
}
}
return mappings;
};
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.transformedCode) + '</pre></code></div>';
}).join('\n'),
].join('\n');
};

View File

@ -0,0 +1,5 @@
var SourceMapGenerator = jest.genMockFn();
SourceMapGenerator.prototype.addMapping = jest.genMockFn();
SourceMapGenerator.prototype.setSourceContent = jest.genMockFn();
SourceMapGenerator.prototype.toJSON = jest.genMockFn();
exports.SourceMapGenerator = SourceMapGenerator;

View File

@ -0,0 +1,95 @@
'use strict';
jest
.dontMock('underscore')
.dontMock('../base64-vlq')
.dontMock('source-map')
.dontMock('../Package');
var SourceMapGenerator = require('source-map').SourceMapGenerator;
describe('Package', function() {
var Package;
var ppackage;
beforeEach(function() {
Package = require('../Package');
ppackage = new Package('test_url');
ppackage.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('transformed foo;', 'source foo', 'foo path');
ppackage.addModule('transformed bar;', 'source bar', 'bar path');
ppackage.finalize({});
expect(ppackage.getSource()).toBe([
'transformed foo;',
'transformed bar;',
'RAW_SOURCE_MAP = "test-source-map";',
'\/\/@ sourceMappingURL=test_url',
].join('\n'));
});
it('should create a package and add run module code', function() {
ppackage.addModule('transformed foo;', 'source foo', 'foo path');
ppackage.addModule('transformed bar;', 'source bar', 'bar path');
ppackage.setMainModuleId('foo');
ppackage.finalize({runMainModule: true});
expect(ppackage.getSource()).toBe([
'transformed foo;',
'transformed bar;',
';require("foo");',
'RAW_SOURCE_MAP = "test-source-map";',
'\/\/@ sourceMappingURL=test_url',
].join('\n'));
});
});
describe('sourcemap package', function() {
it('should create sourcemap', function() {
var ppackage = new Package('test_url');
ppackage.addModule('transformed foo;\n', 'source foo', 'foo path');
ppackage.addModule('transformed bar;\n', 'source bar', 'bar path');
ppackage.setMainModuleId('foo');
ppackage.finalize({runMainModule: true});
var s = ppackage.getSourceMap();
expect(s).toEqual(genSourceMap(ppackage._modules));
});
});
});
function genSourceMap(modules) {
var sourceMapGen = new SourceMapGenerator({file: 'bundle.js', version: 3});
var packageLineNo = 0;
for (var i = 0; i < modules.length; i++) {
var module = modules[i];
var transformedCode = module.transformedCode;
var sourcePath = module.sourcePath;
var sourceCode = module.sourceCode;
var transformedLineCount = 0;
var lastCharNewLine = false;
for (var t = 0; t < transformedCode.length; t++) {
if (t === 0 || lastCharNewLine) {
sourceMapGen.addMapping({
generated: {line: packageLineNo + 1, column: 0},
original: {line: transformedLineCount + 1, column: 0},
source: sourcePath
});
}
lastCharNewLine = transformedCode[t] === '\n';
if (lastCharNewLine) {
transformedLineCount++;
packageLineNo++;
}
}
packageLineNo++;
sourceMapGen.setSourceContent(
sourcePath,
sourceCode
);
}
return sourceMapGen.toJSON();
};

View File

@ -0,0 +1,83 @@
'use strict';
jest
.setMock('worker-farm', function() { return function() {};})
.dontMock('path')
.dontMock('q')
.dontMock('os')
.dontMock('underscore')
.dontMock('../');
var q = require('q');
describe('Packager', function() {
var getDependencies;
var wrapModule;
var Packager;
beforeEach(function() {
getDependencies = jest.genMockFn();
wrapModule = jest.genMockFn();
require('../../DependencyResolver').mockImpl(function() {
return {
getDependencies: getDependencies,
wrapModule: wrapModule,
};
});
Packager = require('../');
});
pit('create a package', function() {
require('fs').statSync.mockImpl(function() {
return {
isDirectory: function() {return true;}
};
});
var packager = new Packager({projectRoots: []});
var modules = [
{id: 'foo', path: '/root/foo.js', dependencies: []},
{id: 'bar', path: '/root/bar.js', dependencies: []},
];
getDependencies.mockImpl(function() {
return q({
mainModuleId: 'foo',
dependencies: modules
});
});
require('../../JSTransformer').prototype.loadFileAndTransform
.mockImpl(function(tsets, path) {
return q({
code: 'transformed ' + path,
sourceCode: 'source ' + path,
sourcePath: path
});
});
wrapModule.mockImpl(function(module, code) {
return 'lol ' + code + ' lol';
});
return packager.package('/root/foo.js', true, 'source_map_url')
.then(function(p) {
expect(p.addModule.mock.calls[0]).toEqual([
'lol transformed /root/foo.js lol',
'source /root/foo.js',
'/root/foo.js'
]);
expect(p.addModule.mock.calls[1]).toEqual([
'lol transformed /root/bar.js lol',
'source /root/bar.js',
'/root/bar.js'
]);
expect(p.finalize.mock.calls[0]).toEqual([
{runMainModule: true}
]);
});
});
});

View File

@ -0,0 +1,168 @@
/* -*- Mode: js; js-indent-level: 2; -*- */
/*
* Copyright 2011 Mozilla Foundation and contributors
* Licensed under the New BSD license. See LICENSE or:
* http://opensource.org/licenses/BSD-3-Clause
*
* Based on the Base 64 VLQ implementation in Closure Compiler:
* https://code.google.com/p/closure-compiler/source/browse/trunk/src/com/google/debugging/sourcemap/Base64VLQ.java
*
* Copyright 2011 The Closure Compiler Authors. All rights reserved.
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are
* met:
*
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* * Redistributions in binary form must reproduce the above
* copyright notice, this list of conditions and the following
* disclaimer in the documentation and/or other materials provided
* with the distribution.
* * Neither the name of Google Inc. nor the names of its
* contributors may be used to endorse or promote products derived
* from this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
* OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
var charToIntMap = {};
var intToCharMap = {};
'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/'
.split('')
.forEach(function (ch, index) {
charToIntMap[ch] = index;
intToCharMap[index] = ch;
});
var base64 = {};
/**
* Encode an integer in the range of 0 to 63 to a single base 64 digit.
*/
base64.encode = function base64_encode(aNumber) {
if (aNumber in intToCharMap) {
return intToCharMap[aNumber];
}
throw new TypeError("Must be between 0 and 63: " + aNumber);
};
/**
* Decode a single base 64 digit to an integer.
*/
base64.decode = function base64_decode(aChar) {
if (aChar in charToIntMap) {
return charToIntMap[aChar];
}
throw new TypeError("Not a valid base 64 digit: " + aChar);
};
// A single base 64 digit can contain 6 bits of data. For the base 64 variable
// length quantities we use in the source map spec, the first bit is the sign,
// the next four bits are the actual value, and the 6th bit is the
// continuation bit. The continuation bit tells us whether there are more
// digits in this value following this digit.
//
// Continuation
// | Sign
// | |
// V V
// 101011
var VLQ_BASE_SHIFT = 5;
// binary: 100000
var VLQ_BASE = 1 << VLQ_BASE_SHIFT;
// binary: 011111
var VLQ_BASE_MASK = VLQ_BASE - 1;
// binary: 100000
var VLQ_CONTINUATION_BIT = VLQ_BASE;
/**
* Converts from a two-complement value to a value where the sign bit is
* placed in the least significant bit. For example, as decimals:
* 1 becomes 2 (10 binary), -1 becomes 3 (11 binary)
* 2 becomes 4 (100 binary), -2 becomes 5 (101 binary)
*/
function toVLQSigned(aValue) {
return aValue < 0
? ((-aValue) << 1) + 1
: (aValue << 1) + 0;
}
/**
* Converts to a two-complement value from a value where the sign bit is
* placed in the least significant bit. For example, as decimals:
* 2 (10 binary) becomes 1, 3 (11 binary) becomes -1
* 4 (100 binary) becomes 2, 5 (101 binary) becomes -2
*/
function fromVLQSigned(aValue) {
var isNegative = (aValue & 1) === 1;
var shifted = aValue >> 1;
return isNegative
? -shifted
: shifted;
}
/**
* Returns the base 64 VLQ encoded value.
*/
exports.encode = function base64VLQ_encode(aValue) {
var encoded = "";
var digit;
var vlq = toVLQSigned(aValue);
do {
digit = vlq & VLQ_BASE_MASK;
vlq >>>= VLQ_BASE_SHIFT;
if (vlq > 0) {
// There are still more digits in this value, so we must make sure the
// continuation bit is marked.
digit |= VLQ_CONTINUATION_BIT;
}
encoded += base64.encode(digit);
} while (vlq > 0);
return encoded;
};
/**
* Decodes the next base 64 VLQ value from the given string and returns the
* value and the rest of the string via the out parameter.
*/
exports.decode = function base64VLQ_decode(aStr, aOutParam) {
var i = 0;
var strLen = aStr.length;
var result = 0;
var shift = 0;
var continuation, digit;
do {
if (i >= strLen) {
throw new Error("Expected more digits in base 64 VLQ value.");
}
digit = base64.decode(aStr.charAt(i++));
continuation = !!(digit & VLQ_CONTINUATION_BIT);
digit &= VLQ_BASE_MASK;
result = result + (digit << shift);
shift += VLQ_BASE_SHIFT;
} while (continuation);
aOutParam.value = fromVLQSigned(result);
aOutParam.rest = aStr.slice(i);
};

127
react-packager/src/Packager/index.js vendored Normal file
View File

@ -0,0 +1,127 @@
'use strict';
var assert = require('assert');
var fs = require('fs');
var path = require('path');
var q = require('q');
var Promise = require('q').Promise;
var Transformer = require('../JSTransformer');
var DependencyResolver = require('../DependencyResolver');
var _ = require('underscore');
var Package = require('./Package');
var Activity = require('../Activity');
var DEFAULT_CONFIG = {
/**
* RegExp used to ignore paths when scanning the filesystem to calculate the
* dependency graph.
*/
blacklistRE: null,
/**
* The kind of module system/transport wrapper to use for the modules bundled
* in the package.
*/
moduleFormat: 'haste',
/**
* An ordered list of module names that should be considered as dependencies
* of every module in the system. The list is ordered because every item in
* the list will have an implicit dependency on all items before it.
*
* (This ordering is necessary to build, for example, polyfills that build on
* each other)
*/
polyfillModuleNames: [],
nonPersistent: false,
};
function Packager(projectConfig) {
projectConfig.projectRoots.forEach(verifyRootExists);
this._config = Object.create(DEFAULT_CONFIG);
for (var key in projectConfig) {
this._config[key] = projectConfig[key];
}
this._resolver = new DependencyResolver(this._config);
this._transformer = new Transformer(projectConfig);
}
Packager.prototype.kill = function() {
return q.all([
this._transformer.kill(),
this._resolver.end(),
]);
};
Packager.prototype.package = function(main, runModule, sourceMapUrl) {
var transformModule = this._transformModule.bind(this);
var ppackage = new Package(sourceMapUrl);
var findEventId = Activity.startEvent('find dependencies');
var transformEventId;
return this.getDependencies(main)
.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(transformed) {
ppackage.addModule(
transformed.code,
transformed.sourceCode,
transformed.sourcePath
);
});
ppackage.finalize({ runMainModule: runModule });
return ppackage;
});
};
Packager.prototype.invalidateFile = function(filePath) {
this._transformer.invalidateFile(filePath);
}
Packager.prototype.getDependencies = function(main) {
return this._resolver.getDependencies(main);
};
Packager.prototype._transformModule = function(module) {
var resolver = this._resolver;
return this._transformer.loadFileAndTransform(
['es6'],
path.resolve(module.path),
this._config.transformer || {}
).then(function(transformed) {
return _.extend(
{},
transformed,
{code: resolver.wrapModule(module, transformed.code)}
);
});
};
function verifyRootExists(root) {
// Verify that the root exists.
assert(fs.statSync(root).isDirectory(), 'Root has to be a valid directory');
}
Packager.prototype.getGraphDebugInfo = function() {
return this._resolver.getDebugInfo();
};
module.exports = Packager;

View File

@ -0,0 +1,158 @@
jest.setMock('worker-farm', function(){ return function(){}; })
.dontMock('q')
.dontMock('os')
.dontMock('errno/custom')
.dontMock('path')
.dontMock('url')
.dontMock('../');
var server = require('../');
var q = require('q');
describe('processRequest', function(){
var server;
var Activity;
var Packager;
var FileWatcher;
var options = {
projectRoots: ['root'],
blacklistRE: null,
cacheVersion: null,
polyfillModuleNames: null
};
var makeRequest = function(requestHandler, requrl){
var deferred = q.defer();
requestHandler({
url: requrl
},{
end: function(res){
deferred.resolve(res);
}
},{
next: function(){}
}
);
return deferred.promise;
};
var invalidatorFunc = jest.genMockFunction();
var watcherFunc = jest.genMockFunction();
var requestHandler;
beforeEach(function(){
Activity = require('../../Activity');
Packager = require('../../Packager');
FileWatcher = require('../../FileWatcher')
Packager.prototype.package = function(main, runModule, sourceMapUrl) {
return q({
getSource: function(){
return "this is the source"
},
getSourceMap: function(){
return "this is the source map"
}
})
};
FileWatcher.prototype.on = watcherFunc;
Packager.prototype.invalidateFile = invalidatorFunc;
var Server = require('../');
server = new Server(options);
requestHandler = server.processRequest.bind(server);
});
pit('returns JS bundle source on request of *.bundle',function(){
result = makeRequest(requestHandler,'mybundle.includeRequire.runModule.bundle');
return result.then(function(response){
expect(response).toEqual("this is the source");
});
});
pit('returns sourcemap on request of *.map', function(){
result = makeRequest(requestHandler,'mybundle.includeRequire.runModule.bundle.map');
return result.then(function(response){
expect(response).toEqual('"this is the source map"');
});
});
pit('watches all files in projectRoot', function(){
result = makeRequest(requestHandler,'mybundle.includeRequire.runModule.bundle');
return result.then(function(response){
expect(watcherFunc.mock.calls[0][0]).toEqual('all');
expect(watcherFunc.mock.calls[0][1]).not.toBe(null);
})
});
describe('file changes', function() {
var triggerFileChange;
beforeEach(function() {
FileWatcher.prototype.on = function(eventType, callback) {
if (eventType !== 'all') {
throw new Error('Can only handle "all" event in watcher.');
}
triggerFileChange = callback;
return this;
};
});
pit('invalides files in package when file is updated', function() {
result = makeRequest(requestHandler,'mybundle.includeRequire.runModule.bundle');
return result.then(function(response){
var 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
.mockReturnValueOnce(
q({
getSource: function(){
return "this is the first source"
},
getSourceMap: function(){},
})
)
.mockReturnValue(
q({
getSource: function(){
return "this is the rebuilt source"
},
getSourceMap: function(){},
})
);
Packager.prototype.package = packageFunc;
var Server = require('../../Server');
var server = new Server(options);
requestHandler = server.processRequest.bind(server);
return makeRequest(requestHandler,'mybundle.includeRequire.runModule.bundle')
.then(function(response){
expect(response).toEqual("this is the first source");
expect(packageFunc.mock.calls.length).toBe(1);
triggerFileChange('all','path/file.js', options.projectRoots[0]);
jest.runAllTimers();
})
.then(function(){
expect(packageFunc.mock.calls.length).toBe(2);
return makeRequest(requestHandler,'mybundle.includeRequire.runModule.bundle')
.then(function(response){
expect(response).toEqual("this is the rebuilt source");
});
});
});
});
});

173
react-packager/src/Server/index.js vendored Normal file
View File

@ -0,0 +1,173 @@
var url = require('url');
var path = require('path');
var FileWatcher = require('../FileWatcher')
var Packager = require('../Packager');
var Activity = require('../Activity');
var q = require('q');
module.exports = Server;
function Server(options) {
this._projectRoots = options.projectRoots;
this._packages = Object.create(null);
this._packager = new Packager({
projectRoots: options.projectRoots,
blacklistRE: options.blacklistRE,
polyfillModuleNames: options.polyfillModuleNames || [],
runtimeCode: options.runtimeCode,
cacheVersion: options.cacheVersion,
resetCache: options.resetCache,
dev: options.dev,
transformModulePath: options.transformModulePath,
nonPersistent: options.nonPersistent,
});
this._fileWatcher = options.nonPersistent
? FileWatcher.createDummyWatcher()
: new FileWatcher(options.projectRoots);
var onFileChange = this._onFileChange.bind(this);
this._fileWatcher.on('all', onFileChange);
}
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.
setImmediate(this._rebuildPackages.bind(this, absPath))
};
Server.prototype._rebuildPackages = function(filepath) {
var buildPackage = this._buildPackage.bind(this);
var packages = this._packages;
Object.keys(packages).forEach(function(key) {
var options = getOptionsFromPath(url.parse(key).pathname);
packages[key] = buildPackage(options).then(function(p) {
// Make a throwaway call to getSource to cache the source string.
p.getSource();
return p;
});
});
};
Server.prototype.end = function() {
q.all([
this._fileWatcher.end(),
this._packager.kill(),
]);
};
Server.prototype._buildPackage = function(options) {
return this._packager.package(
options.main,
options.runModule,
options.sourceMapUrl
);
};
Server.prototype.buildPackageFromUrl = function(reqUrl) {
var options = getOptionsFromPath(url.parse(reqUrl).pathname);
return this._buildPackage(options);
};
Server.prototype.getDependencies = function(main) {
return this._packager.getDependencies(main);
};
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>';
q.all(Object.keys(this._packages).map(function(url) {
return this._packages[url].then(function(p) {
ret += '<div><h2>' + url + '</h2>';
ret += p.getDebugInfo();
});
}, this)).then(
function() { res.end(ret); },
function(e) {
res.wrteHead(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.processRequest = function(req, res, next) {
var requestType;
if (req.url.match(/\.bundle$/)) {
requestType = 'bundle';
} else if (req.url.match(/\.map$/)) {
requestType = 'map';
} else if (req.url.match(/^\/debug/)) {
this._processDebugRequest(req.url, res);
return;
} else {
return next();
}
var startReqEventId = Activity.startEvent('request:' + req.url);
var options = getOptionsFromPath(url.parse(req.url).pathname);
var building = this._packages[req.url] || this._buildPackage(options)
this._packages[req.url] = building;
building.then(
function(p) {
if (requestType === 'bundle') {
res.end(p.getSource());
Activity.endEvent(startReqEventId);
} else if (requestType === 'map') {
res.end(JSON.stringify(p.getSourceMap()));
Activity.endEvent(startReqEventId);
}
},
function(error) {
handleError(res, error);
}
).done();
};
function getOptionsFromPath(pathname) {
var parts = pathname.split('.');
// Remove the leading slash.
var main = parts[0].slice(1) + '.js';
return {
runModule: parts.slice(1).some(function(part) {
return part === 'runModule';
}),
main: main,
sourceMapUrl: parts.slice(0, -1).join('.') + '.map'
};
}
function handleError(res, error) {
res.writeHead(500, {
'Content-Type': 'application/json; charset=UTF-8',
});
if (error.type === 'TransformError') {
res.end(JSON.stringify(error));
} 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',
}));
}
}

View File

@ -0,0 +1,14 @@
var absolutePath = require('absolute-path');
var path = require('path');
var pathIsInside = require('path-is-inside');
function isAbsolutePath(pathStr) {
return absolutePath(pathStr);
}
function isChildPath(parentPath, childPath) {
return pathIsInside(parentPath, childPath);
}
exports.isAbsolutePath = isAbsolutePath;
exports.isChildPath = isChildPath;

57
transformer.js Normal file
View File

@ -0,0 +1,57 @@
/**
* Copyright 2004-present Facebook. All Rights Reserved.
*
* Note: This is a fork of the fb-specific transform.js
*/
'use strict';
var jstransform = require('jstransform').transform;
var reactVisitors =
require('react-tools/vendor/fbtransform/visitors').getAllVisitors();
var staticTypeSyntax =
require('jstransform/visitors/type-syntax').visitorList;
// Note that reactVisitors now handles ES6 classes, rest parameters, arrow
// functions, template strings, and object short notation.
var visitorList = reactVisitors;
function transform(transformSets, srcTxt, options) {
options = options || {};
// These tranforms mostly just erase type annotations and static typing
// related statements, but they were conflicting with other tranforms.
// Running them first solves that problem
var staticTypeSyntaxResult = jstransform(
staticTypeSyntax,
srcTxt
);
return jstransform(visitorList, staticTypeSyntaxResult.code);
}
module.exports = function(data, callback) {
var result;
try {
result = transform(
data.transformSets,
data.sourceCode,
data.options
);
} catch (e) {
return callback(null, {
error: {
lineNumber: e.lineNumber,
column: e.column,
message: e.message,
stack: e.stack,
description: e.description
}
});
}
callback(null, result);
};
// export for use in jest
module.exports.transform = transform;