Updates from Thu 19 Mar

- [ReactNative] Add root package.json name back | Tadeu Zagallo
- [react-packager] Make sure projectRoots is converted to an array | Amjad Masad
- [ReactNative] Init script that bootstraps new Xcode project | Alex Kotliarskyi
- [ReactNative] New SampleApp | Alex Kotliarskyi
- [ReactNative] Touchable invoke press on longPress when longPress handler missing | Eric Vicenti
- [ReactNative] Commit missing RCTWebSocketDebugger.xcodeproj | Alex Kotliarskyi
- [ReactNative] Add Custom Components folder | Christopher Chedeau
- [react-packager] Hash cache file name information to avoid long names | Amjad Masad
- [ReactNative] Put all iOS-related files in a subfolder | Alex Kotliarskyi
- [react-packager] Fix OOM | Amjad Masad
- [ReactNative] Bring Chrome debugger to OSS. Part 2 | Alex Kotliarskyi
- [ReactNative] Add search to UIExplorer | Tadeu Zagallo
- [ReactNative][RFC] Bring Chrome debugger to OSS. Part 1 | Alex Kotliarskyi
- [ReactNative] Return the appropriate status code from XHR | Tadeu Zagallo
- [ReactNative] Make JS stack traces in Xcode prettier | Alex Kotliarskyi
- [ReactNative] Remove duplicate package.json with the same name | Christopher Chedeau
- [ReactNative] Remove invariant from require('react-native') | Christopher Chedeau
- [ReactNative] Remove ListViewDataSource from require('react-native') | Christopher Chedeau
- [react-packager] Add assetRoots option | Amjad Masad
- Convert UIExplorer to ListView | Christopher Chedeau
- purge rni | Basil Hosmer
- [ReactNative] s/render*View/render/ in <WebView> | Christopher Chedeau
This commit is contained in:
Christopher Chedeau 2015-03-20 08:43:51 -07:00
parent e3025bb624
commit d11511f5dd
11 changed files with 316 additions and 30 deletions

112
debugger.html Normal file
View File

@ -0,0 +1,112 @@
<!doctype html>
<html>
<head>
<meta charset=utf-8>
<!-- Fake favicon, to avoid extra request to server -->
<link rel="icon" href="data:;base64,iVBORw0KGgo=">
<title>React Native Debugger</title>
<script>
(function() {
var sessionID = window.localStorage.getItem('sessionID');
window.localStorage.removeItem('sessionID');
window.onbeforeunload = function() {
if (sessionID) {
return 'If you reload this page, it is going to break the debugging session. ' +
'You should ⌘+R in the iOS simulator to reload.';
}
};
function setStatus(status) {
document.getElementById('status').innerHTML = status;
}
var messageHandlers = {
// This method is a bit hacky. Catalyst asks for a new clean JS runtime.
// The easiest way to do this is to reload this page. That also means that
// web socket connection will be lost. To send reply back we need to remember
// message id
'prepareJSRuntime': function(message) {
window.onbeforeunload = undefined;
window.localStorage.setItem('sessionID', message.id);
window.location.reload();
},
'executeApplicationScript:sourceURL:onComplete:': function(message, sendReply) {
for (var key in message.inject) {
window[key] = JSON.parse(message.inject[key]);
}
loadScript(message.url, sendReply.bind(null, null));
},
'executeJSCall:method:arguments:callback:': function(message, sendReply) {
var returnValue = [[], [], [], [], []];
try {
if (window && window.require) {
returnValue = window.require(message.moduleName)[message.moduleMethod].apply(null, message.arguments);
}
} finally {
sendReply(JSON.stringify(returnValue));
}
}
}
var ws = new WebSocket('ws://' + window.location.host + '/debugger-proxy');
ws.onopen = function() {
if (sessionID) {
setStatus('Debugger session #' + sessionID + ' active');
ws.send(JSON.stringify({replyID: parseInt(sessionID, 10)}));
} else {
setStatus('Waiting for simulator');
}
}
ws.onmessage = function(message) {
var object = JSON.parse(message.data);
var sendReply = function(result) {
ws.send(JSON.stringify({replyID: object.id, result: result}));
}
var handler = messageHandlers[object.method];
if (handler) {
handler(object, sendReply);
} else {
console.warn('Unknown method: ' + object.method);
}
}
ws.onclose = function() {
setStatus('Disconnected from proxy. Is node server running?');
}
function loadScript(src, callback) {
var script = document.createElement('script');
script.type = 'text/javascript';
script.src = src;
script.onload = callback;
document.head.appendChild(script);
}
})();
</script>
<style type="text/css">
.shortcut {
font-family: monospace;
color: #eee;
background-color: #333;
padding: 4px;
border-radius: 4px;
letter-spacing: 3px;
}
body {
font-size: large;
}
</style>
</head>
<body>
<p>
React Native JS code runs inside this Chrome tab
</p>
<p>Press <span class="shortcut">⌘⌥J</span> to open Developer Tools. Enable <a href="http://stackoverflow.com/a/17324511/232122" target="_blank">Pause On Caught Exceptions</a> for a better debugging experience.</p>
<p>Status: <span id="status">Loading</span></p>
</body>
</html>

44
init.sh Executable file
View File

@ -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__} <ProjectNameInCamelCase>"
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

View File

@ -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

View File

@ -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",

View File

@ -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) {

View File

@ -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]];

View File

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

View File

@ -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() {

View File

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

View File

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

41
webSocketProxy.js Normal file
View File

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