diff --git a/debugger.html b/debugger.html
new file mode 100644
index 00000000..f7cabc5f
--- /dev/null
+++ b/debugger.html
@@ -0,0 +1,112 @@
+
+
+
+
+
+
+React Native Debugger
+
+
+
+
+
+ React Native JS code runs inside this Chrome tab
+
+ Press ⌘⌥J to open Developer Tools. Enable Pause On Caught Exceptions for a better debugging experience.
+ Status: Loading
+
+
diff --git a/init.sh b/init.sh
new file mode 100755
index 00000000..6a42b9ae
--- /dev/null
+++ b/init.sh
@@ -0,0 +1,44 @@
+#!/usr/bin/env ruby
+
+def cp(src, dest, app_name)
+ if File.directory?(src)
+ Dir.mkdir(dest) unless Dir.exists?(dest)
+ else
+ content = File.read(src)
+ .gsub("SampleApp", app_name)
+ .gsub("Examples/#{app_name}/", "")
+ .gsub("../../Libraries/", "node_modules/react-native/Libraries/")
+ .gsub("../../ReactKit/", "node_modules/react-native/ReactKit/")
+ File.write(dest, content)
+ end
+end
+
+def main(dest, app_name)
+ source = File.expand_path("../../Examples/SampleApp", __FILE__)
+ files = Dir.chdir(source) { Dir["**/*"] }
+ .reject { |file| file["project.xcworkspace"] || file["xcuserdata"] }
+ .each { |file|
+ new_file = file.gsub("SampleApp", app_name)
+ cp File.join(source, file), File.join(dest, new_file), app_name
+ }
+end
+
+if ARGV.count == 0
+ puts "Usage: #{__FILE__} "
+ puts ""
+ puts "This script will bootstrap new React Native app in current folder"
+else
+ app_name = ARGV.first
+ dest = Dir.pwd
+ puts "Setting up new React Native app in #{dest}"
+ puts ""
+
+ main(dest, app_name)
+
+ puts "Next steps:"
+ puts ""
+ puts " Open #{app_name}.xcproject in Xcode"
+ puts " Hit Run button"
+ puts ""
+end
+
diff --git a/launchChromeDevTools.applescript b/launchChromeDevTools.applescript
new file mode 100755
index 00000000..4384b3ae
--- /dev/null
+++ b/launchChromeDevTools.applescript
@@ -0,0 +1,41 @@
+#!/usr/bin/env osascript
+
+
+on run argv
+ set theURL to item 1 of argv
+
+ tell application "Google Chrome"
+ activate
+
+ if (count every window) = 0 then
+ make new window
+ end if
+
+ -- Find a tab currently running the debugger
+ set found to false
+ set theTabIndex to -1
+ repeat with theWindow in every window
+ set theTabIndex to 0
+ repeat with theTab in every tab of theWindow
+ set theTabIndex to theTabIndex + 1
+ if theTab's URL is theURL then
+ set found to true
+ exit repeat
+ end if
+ end repeat
+
+ if found then
+ exit repeat
+ end if
+ end repeat
+
+ if found then
+ set index of theWindow to 1
+ set theWindow's active tab index to theTabIndex
+ else
+ tell window 1
+ make new tab with properties {URL:theURL}
+ end tell
+ end if
+ end tell
+end run
diff --git a/package.json b/package.json
index 6033c216..0afcd3c3 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
{
- "name": "react-native",
- "version": "0.1.0",
+ "name": "react-native-cli",
+ "version": "0.1.1",
"description": "Build native apps with React!",
"repository": {
"type": "git",
diff --git a/packager.js b/packager.js
index ca4e5c67..26917237 100644
--- a/packager.js
+++ b/packager.js
@@ -16,12 +16,14 @@ if (!fs.existsSync(path.resolve(__dirname, '..', 'node_modules'))) {
process.exit();
}
+var exec = require('child_process').exec;
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 webSocketProxy = require('./webSocketProxy.js');
var options = parseCommandLine([{
command: 'port',
@@ -31,7 +33,11 @@ var options = parseCommandLine([{
description: 'add another root(s) to be used by the packager in this project',
}]);
-if (!options.projectRoots) {
+if (options.projectRoots) {
+ if (!Array.isArray(options.projectRoots)) {
+ options.projectRoots = options.projectRoots.split(',');
+ }
+} else {
options.projectRoots = [path.resolve(__dirname, '..')];
}
@@ -45,6 +51,10 @@ if (options.root) {
}
}
+if (!options.assetRoots) {
+ options.assetRoots = [path.resolve(__dirname, '..')];
+}
+
console.log('\n' +
' ===============================================================\n' +
' | Running packager on port ' + options.port + '. \n' +
@@ -64,10 +74,12 @@ process.on('uncaughtException', function(e) {
'any existing instances that are already running.\n\n');
});
-runServer(options, function() {
+var server = runServer(options, function() {
console.log('\nReact packager ready.\n');
});
+webSocketProxy.attachToServer(server, '/debugger-proxy');
+
function loadRawBody(req, res, next) {
req.rawBody = '';
req.setEncoding('utf8');
@@ -91,12 +103,37 @@ function openStackFrameInEditor(req, res, next) {
}
}
+function getDevToolsLauncher(options) {
+ return function(req, res, next) {
+ if (req.url === '/debugger-ui') {
+ var debuggerPath = path.join(__dirname, 'debugger.html');
+ res.writeHead(200, {'Content-Type': 'text/html'});
+ fs.createReadStream(debuggerPath).pipe(res);
+ } else if (req.url === '/launch-chrome-devtools') {
+ var debuggerURL = 'http://localhost:' + options.port + '/debugger-ui';
+ var script = 'launchChromeDevTools.applescript';
+ console.log('Launching Dev Tools...');
+ exec(path.join(__dirname, script) + ' ' + debuggerURL, function(err, stdout, stderr) {
+ if (err) {
+ console.log('Failed to run ' + script, err);
+ }
+ console.log(stdout);
+ console.warn(stderr);
+ });
+ res.end('OK');
+ } else {
+ next();
+ }
+ };
+}
+
function getAppMiddleware(options) {
return ReactPackager.middleware({
projectRoots: options.projectRoots,
blacklistRE: blacklist(false),
cacheVersion: '2',
transformModulePath: require.resolve('./transformer.js'),
+ assetRoots: options.assetRoots,
});
}
@@ -107,6 +144,7 @@ function runServer(
var app = connect()
.use(loadRawBody)
.use(openStackFrameInEditor)
+ .use(getDevToolsLauncher(options))
.use(getAppMiddleware(options));
options.projectRoots.forEach(function(root) {
diff --git a/react-packager/src/DependencyResolver/haste/DependencyGraph/index.js b/react-packager/src/DependencyResolver/haste/DependencyGraph/index.js
index a7bf1f53..918b1e06 100644
--- a/react-packager/src/DependencyResolver/haste/DependencyGraph/index.js
+++ b/react-packager/src/DependencyResolver/haste/DependencyGraph/index.js
@@ -134,13 +134,12 @@ DependecyGraph.prototype.resolveDependency = function(
fromModule,
depModuleId
) {
-
if (this._assetMap != null) {
// Process asset requires.
var assetMatch = depModuleId.match(/^image!(.+)/);
if (assetMatch && assetMatch[1]) {
if (!this._assetMap[assetMatch[1]]) {
- console.warn('Cannot find asset: ' + assetMatch[1]);
+ debug('WARINING: Cannot find asset:', assetMatch[1]);
return null;
}
return this._assetMap[assetMatch[1]];
diff --git a/react-packager/src/JSTransformer/Cache.js b/react-packager/src/JSTransformer/Cache.js
index b9c5b8c1..bad0dadb 100644
--- a/react-packager/src/JSTransformer/Cache.js
+++ b/react-packager/src/JSTransformer/Cache.js
@@ -1,13 +1,14 @@
'use strict';
-var path = require('path');
-var version = require('../../../../package.json').version;
-var tmpdir = require('os').tmpDir();
-var isAbsolutePath = require('absolute-path');
+var _ = require('underscore');
+var crypto = require('crypto');
var declareOpts = require('../lib/declareOpts');
var fs = require('fs');
-var _ = require('underscore');
+var isAbsolutePath = require('absolute-path');
+var path = require('path');
var q = require('q');
+var tmpdir = require('os').tmpDir();
+var version = require('../../../../package.json').version;
var Promise = q.Promise;
@@ -146,15 +147,15 @@ function loadCacheSync(cachePath) {
}
function cacheFilePath(options) {
+ var hash = crypto.createHash('md5');
+ hash.update(version);
+
var roots = options.projectRoots.join(',').split(path.sep).join('-');
+ hash.update(roots);
+
var cacheVersion = options.cacheVersion || '0';
- return path.join(
- tmpdir,
- [
- 'react-packager-cache',
- version,
- cacheVersion,
- roots,
- ].join('-')
- );
+ hash.update(cacheVersion);
+
+ var name = 'react-packager-cache-' + hash.digest('hex');
+ return path.join(tmpdir, name);
}
diff --git a/react-packager/src/JSTransformer/__tests__/Cache-test.js b/react-packager/src/JSTransformer/__tests__/Cache-test.js
index 97a50097..f5b55f05 100644
--- a/react-packager/src/JSTransformer/__tests__/Cache-test.js
+++ b/react-packager/src/JSTransformer/__tests__/Cache-test.js
@@ -4,6 +4,7 @@ jest
.dontMock('underscore')
.dontMock('path')
.dontMock('absolute-path')
+ .dontMock('crypto')
.dontMock('../Cache');
var q = require('q');
@@ -19,7 +20,7 @@ describe('JSTransformer Cache', function() {
Cache = require('../Cache');
});
- describe('getting/settig', function() {
+ describe('getting/setting', function() {
it('calls loader callback for uncached file', function() {
var cache = new Cache({projectRoots: ['/rootDir']});
var loaderCb = jest.genMockFn().mockImpl(function() {
diff --git a/react-packager/src/JSTransformer/index.js b/react-packager/src/JSTransformer/index.js
index 35785e6e..00e49d5d 100644
--- a/react-packager/src/JSTransformer/index.js
+++ b/react-packager/src/JSTransformer/index.js
@@ -59,7 +59,7 @@ function Transformer(options) {
this._failedToStart = q.Promise.reject(new Error('No transfrom module'));
} else {
this._workers = workerFarm(
- {autoStart: true},
+ {autoStart: true, maxConcurrentCallsPerWorker: 1},
options.transformModulePath
);
}
diff --git a/react-packager/src/Server/index.js b/react-packager/src/Server/index.js
index 7df686ad..09a3c640 100644
--- a/react-packager/src/Server/index.js
+++ b/react-packager/src/Server/index.js
@@ -8,6 +8,7 @@ var Packager = require('../Packager');
var Activity = require('../Activity');
var setImmediate = require('timers').setImmediate;
var q = require('q');
+var _ = require('underscore');
module.exports = Server;
@@ -62,6 +63,12 @@ function Server(options) {
var onFileChange = this._onFileChange.bind(this);
this._fileWatcher.on('all', onFileChange);
+
+ var self = this;
+ this._debouncedFileChangeHandler = _.debounce(function(filePath) {
+ self._rebuildPackages(filePath);
+ self._informChangeWatchers();
+ }, 50, true);
}
Server.prototype._onFileChange = function(type, filepath, root) {
@@ -69,8 +76,7 @@ Server.prototype._onFileChange = function(type, filepath, root) {
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));
- setImmediate(this._informChangeWatchers.bind(this));
+ this._debouncedFileChangeHandler(absPath);
};
Server.prototype._rebuildPackages = function() {
@@ -78,13 +84,16 @@ Server.prototype._rebuildPackages = function() {
var packages = this._packages;
Object.keys(packages).forEach(function(key) {
var options = getOptionsFromUrl(key);
- packages[key] = buildPackage(options).then(function(p) {
- // Make a throwaway call to getSource to cache the source string.
- p.getSource({
- inlineSourceMap: options.dev,
- minify: options.minify,
+ // Wait for a previous build (if exists) to finish.
+ packages[key] = (packages[key] || q()).then(function() {
+ return buildPackage(options).then(function(p) {
+ // Make a throwaway call to getSource to cache the source string.
+ p.getSource({
+ inlineSourceMap: options.dev,
+ minify: options.minify,
+ });
+ return p;
});
- return p;
});
});
};
diff --git a/webSocketProxy.js b/webSocketProxy.js
new file mode 100644
index 00000000..dada059a
--- /dev/null
+++ b/webSocketProxy.js
@@ -0,0 +1,41 @@
+/**
+ * Copyright 2004-present Facebook. All Rights Reserved.
+ */
+
+'use strict';
+
+var WebSocketServer = require('ws').Server;
+
+function attachToServer(server, path) {
+ var wss = new WebSocketServer({
+ server: server,
+ path: path
+ });
+ var clients = [];
+
+ wss.on('connection', function(ws) {
+ clients.push(ws);
+
+ var allClientsExcept = function(ws) {
+ return clients.filter(function(cn) { return cn !== ws; });
+ };
+
+ ws.onerror = function() {
+ clients = allClientsExcept(ws);
+ };
+
+ ws.onclose = function() {
+ clients = allClientsExcept(ws);
+ };
+
+ ws.on('message', function(message) {
+ allClientsExcept(ws).forEach(function(cn) {
+ cn.send(message);
+ });
+ });
+ });
+}
+
+module.exports = {
+ attachToServer: attachToServer
+};