From e7d1d95d0de92a8861d9a3d52e14fa1f435821fb Mon Sep 17 00:00:00 2001 From: Amjad Masad Date: Fri, 27 Mar 2015 11:46:17 -0700 Subject: [PATCH] Update from Friday March 27 - [React Native] Sync from github | Amjad Masad - [react-packager] Watch asset roots for changes to update dependency graph | Amjad Masad - Fix sourceTree of RCTActionSheet.xcodeproj | Alex Kotliarskyi - Cancel contents animation before setting new contents in RCTNetworkImageView | Alex Akers - [react-packager] move dependencies to root package.json | Amjad Masad - Fix font crash on iOS < 8.2 | Nick Lockwood - [react-packager] Fix node v0.11.14 query parse bug | Amjad Masad - [ReactNative][Docs] Remove references to ReactNavigator from docs | Tadeu Zagallo - [CLI] react-native start won't run from dir with spaces | Amjad Masad - Revert .buckversion bumps. | Jakub Zika - [react_native] Update default bundle name to org.reactjs.native.* | Krzysztof Magiera - [react-packager] better error when main file not found | Amjad Masad - [React Kit] Remove embarrassing TODOs | Alex Akers - [ReactNative][MAdMan] Clean up after D1942269 | Philipp von Weitershausen - flowify a few more Libraries | Basil Hosmer - [ReactNative] PushNotificationIOS documentation | Eric Vicenti - [ReactNative][CustomComponents] Update old headers | Tadeu Zagallo - [ReactNative] UIViewControllerBasedStatusBarAppearance = NO in SampleApp | Alex Kotliarskyi - [React Native] Fix CocoaPods spec | Alex Akers - [ReactNative] Navigator Example Overhaul | Eric Vicenti - [React Native] Fix incorrect if-statement in RCTGeolocation | Alex Akers - [ReactNative] s/ReactKit/React/g | Tadeu Zagallo - [React Native] [FRC - Don't accept] View border support | Nick Lockwood - [Assets] Allow scripts to override assetRoots | Amjad Masad - [ReactNative] Navigator docs | Eric Vicenti - [ReactNative] License headers and renaming | Eric Vicenti - [React Native] Add CocoaPods spec | Tadeu Zagallo - Added explicit types for all view properties | Nick Lockwood - [ReactNative] s/ReactNavigator/Navigator/ | Tadeu Zagallo - [ReactNative] Add copyright header for code copied from the jQuery UI project | Martin Konicek - [ReactNative] PanResponder documentation | Eric Vicenti - [ReactNative] Add deep linking api | Tadeu Zagallo - [ReactNative] Add gitignore example for SampleApp | Alex Kotliarskyi - [ReactNative] Add react-native-start bin to react-native packge | Alex Kotliarskyi - [ReactNative] Update package.json to be npm-ready | Christopher Chedeau - [RFC][ReactNative] Integrate dev menu directly into RootView | Alex Kotliarskyi - flowify Libraries/ReactIOS | Marshall Roch - [WIP] Added support for italics and additional font weights | Nick Lockwood - [ReactNative] Improve View documentation | Christopher Chedeau - [react-packager] Readme | Amjad Masad - Fix for incorrect contentSize reported by RCTScrollView | Nick Lockwood - [ReactNative] Flow and doc formatting for NetInfo | Eric Vicenti - [ReactNative] Document AppStateIOS | Eric Vicenti - [MAdMan][Android] Make things look more Androidy | Philipp von Weitershausen - flowified Libraries from Avik | Basil Hosmer - flowify some Libraries | Basil Hosmer - [ReactKit] Add shake development menu | Alex Kotliarskyi - [ReactNative] Add debugger and change SampleApp files structure | Alex Kotliarskyi - Flowify ReactIOSEventEmitter | Marshall Roch - [react_native] JS files from D1941151: Allow fontWeight to be 100,200,...,900 | Krzysztof Magiera - [ReactNative] Add snapshot tests for examples | Spencer Ahrens - [ReactNative] bring back some native modules | Spencer Ahrens - [ReactNative] Rename JSNavigationStack to ReactNavigator, rename scene config | Eric Vicenti - [ReactNative] cleanup view example | Spencer Ahrens - Flowify a bunch of Libraries | Marshall Roch - [ReactNative] JSNavigationStack - Use key to blow away old scenes | Eric Vicenti - [ReactNative] Add more logging to RCTJSONParse | Sumeet Vaidya - Unfork UIManager | Nick Lockwood - [react-packager] kill non-standard RAW_SOURCE_MAP | Amjad Masad - Flowify Libraries/StyleSheet and Libraries/Text | Marshall Roch - [ReactNative] Fix OSS Dependency Issues | Eric Vicenti - [react-packager] Fix more issues with node modules | Amjad Masad - [ReactNative] rename navigationOperations to navigator | Eric Vicenti - JS files from D1936817: Add to XMLHttpRequest android and share code with ios | Olivia Bishop - flowify some Libraries | Basil Hosmer - last batch of UIExplorer flowification | Basil Hosmer - [ReactNative] JSNavigationStack rename routeMapper to renderSceneForRoute | Eric Vicenti - Flowify renderApplication | Marshall Roch - [ReactNative] OSS Responder example | Eric Vicenti - [ReactNative] Use oss TimerMixin | Tadeu Zagallo - [ReactNative] Remove auto permission request from setAppIconBadgeNumber | Tadeu Zagallo - [ReactNative] OSS snapshot tests | Spencer Ahrens - [ReactNative] OSS JSNavigationStack w/ Examples | Eric Vicenti - Fix build - remove relative import path | Jakub Zika - Bump .buckversion to a5b8b8ef45d714018ba3542cf98d48ef6aab7088. | Jakub Zika - [ReactNative] Open Source PushNotifications and move Badge Number methods and permission into it | Tadeu Zagallo - [react-packager] Fix regression with transform errors | Amjad Masad - Flowify TextStylePropTypes and fix a bug with unsupported props | Marshall Roch - [ReactNative] Remove `arc build` instructions from require | Alex Kotliarskyi - Flowify Library/Utilities/ | Marshall Roch - [react-packager] Default to index.js from main if it's a dir | Amjad Masad - [ReactNative] Use deprecated ix in TabBarExample | Amjad Masad - [ReactNative] Expanded license on obj-c files | Christopher Chedeau - [ReactNative] Expanded license on js files | Christopher Chedeau - [ReactNative] Fix React Devtools integration | Alex Kotliarskyi - [Text] Account for font leading so descenders are not clipped | Alex Kotliarskyi - [ReactNative] Expanded license on js packager files | Christopher Chedeau - more UIExplorer flow | Basil Hosmer - [react-packager] Pick up package changes while running | Amjad Masad - Added a graph view and a ReactNative metric that displays current queue and execution time for the JS thread. | Bryce Redd - [ReactNative] Add NativeModules and DeviceEventEmitter to react-native exports | Alex Kotliarskyi - [React Native] Fix iOS 7 crashes b/c missing Photos.fmwk | Alex Akers - UIExplorer flowification | Basil Hosmer - Add clearImmediate module | Marshall Roch - [ReactNative] Print directories packager is serving files from | Alex Kotliarskyi - Work around flow bug with exports | Marshall Roch - [ReactNative] Move packager/init.sh to GitHub | Alex Kotliarskyi - [ReactNative] Remove react-native/package.json | Christopher Chedeau - [ReactNative] Returning actual contentSize for RCTScrollViewManager | Henry Lung - declare timeoutID | Basil Hosmer - [ReactNative] Add root package.json name back | Tadeu Zagallo - [react-packager] Allow entry point extensions like .ios.js | Amjad Masad - [react-native] Use SpreadProperty to make react-docgen happy | Felix Kling - clean Examples/2048 | Basil Hosmer - [ReactNative] Adjust packager default root when running from within node_modules | Alex Kotliarskyi - [ReactNative] Add missing websocket dependency | Alex Kotliarskyi - [react-packager] change all but one `ix` to `require` | Amjad Masad - [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 --- packager.js | 6 +- packager.sh | 2 +- .../__tests__/DependencyGraph-test.js | 49 +++++++++ .../haste/DependencyGraph/index.js | 103 +++++++++++++----- .../src/DependencyResolver/haste/index.js | 14 +-- .../FileWatcher/__tests__/FileWatcher-test.js | 1 + react-packager/src/FileWatcher/index.js | 32 +++--- react-packager/src/Packager/index.js | 14 ++- react-packager/src/Server/index.js | 42 ++++++- 9 files changed, 198 insertions(+), 65 deletions(-) diff --git a/packager.js b/packager.js index 9503df6e..119336ed 100644 --- a/packager.js +++ b/packager.js @@ -69,11 +69,7 @@ if (options.assetRoots) { options.assetRoots = options.assetRoots.split(','); } } else { - if (__dirname.match(/node_modules\/react-native\/packager$/)) { - options.assetRoots = [path.resolve(__dirname, '../../..')]; - } else { - options.assetRoots = [path.resolve(__dirname, '..')]; - } + options.assetRoots = [path.resolve(__dirname, '..')]; } console.log('\n' + diff --git a/packager.sh b/packager.sh index 969ca1b2..93a017c3 100755 --- a/packager.sh +++ b/packager.sh @@ -10,4 +10,4 @@ ulimit -n 4096 THIS_DIR=$(dirname "$0") -node $THIS_DIR/packager.js "$@" +node "$THIS_DIR/packager.js" "$@" diff --git a/react-packager/src/DependencyResolver/haste/DependencyGraph/__tests__/DependencyGraph-test.js b/react-packager/src/DependencyResolver/haste/DependencyGraph/__tests__/DependencyGraph-test.js index 962a0694..69ed11e6 100644 --- a/react-packager/src/DependencyResolver/haste/DependencyGraph/__tests__/DependencyGraph-test.js +++ b/react-packager/src/DependencyResolver/haste/DependencyGraph/__tests__/DependencyGraph-test.js @@ -821,6 +821,55 @@ describe('DependencyGraph', function() { }); }); + pit('updates module dependencies on asset add', function() { + var root = '/root'; + var filesystem = fs.__setMockFilesystem({ + 'root': { + 'index.js': [ + '/**', + ' * @providesModule index', + ' */', + 'require("image!foo")' + ].join('\n'), + }, + }); + + var dgraph = new DependencyGraph({ + roots: [root], + assetRoots: [root], + assetExts: ['png'], + fileWatcher: fileWatcher + }); + + return dgraph.load().then(function() { + expect(dgraph.getOrderedDependencies('/root/index.js')) + .toEqual([ + { id: 'index', altId: '/root/index.js', + path: '/root/index.js', + dependencies: ['image!foo'] + } + ]); + + filesystem.root['foo.png'] = ''; + triggerFileChange('add', 'foo.png', root); + + return dgraph.load().then(function() { + expect(dgraph.getOrderedDependencies('/root/index.js')) + .toEqual([ + { id: 'index', altId: '/root/index.js', + path: '/root/index.js', + dependencies: ['image!foo'] + }, + { id: 'image!foo', + path: '/root/foo.png', + dependencies: [], + isAsset: true, + }, + ]); + }); + }); + }); + pit('runs changes through ignore filter', function() { var root = '/root'; var filesystem = fs.__setMockFilesystem({ diff --git a/react-packager/src/DependencyResolver/haste/DependencyGraph/index.js b/react-packager/src/DependencyResolver/haste/DependencyGraph/index.js index adb01282..6f09c5e8 100644 --- a/react-packager/src/DependencyResolver/haste/DependencyGraph/index.js +++ b/react-packager/src/DependencyResolver/haste/DependencyGraph/index.js @@ -86,7 +86,11 @@ DependecyGraph.prototype.load = function() { 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); + throw new NotFoundError( + 'Cannot find entry file %s in any of the roots: %j', + entryPath, + this._roots + ); } var module = this._graph[absolutePath]; @@ -478,7 +482,12 @@ DependecyGraph.prototype._lookupPackage = function(modulePath) { /** * Process a filewatcher change event. */ -DependecyGraph.prototype._processFileChange = function(eventType, filePath, root, stat) { +DependecyGraph.prototype._processFileChange = function( + eventType, + filePath, + root, + stat +) { var absPath = path.join(root, filePath); if (this._ignoreFilePath(absPath)) { return; @@ -486,6 +495,11 @@ DependecyGraph.prototype._processFileChange = function(eventType, filePath, root this._debugUpdateEvents.push({event: eventType, path: filePath}); + if (this._assetExts.indexOf(extname(filePath)) > -1) { + this._processAssetChange(eventType, absPath); + return; + } + var isPackage = path.basename(filePath) === 'package.json'; if (eventType === 'delete') { if (isPackage) { @@ -520,7 +534,8 @@ DependecyGraph.prototype.getDebugInfo = function() { }; /** - * Searches all roots for the file and returns the first one that has file of the same path. + * 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)) { @@ -543,12 +558,43 @@ DependecyGraph.prototype._buildAssetMap = function() { return q(); } - var self = this; - return buildAssetMap(this._assetRoots, this._assetExts) - .then(function(map) { - self._assetMap = map; - return map; + this._assetMap = Object.create(null); + return buildAssetMap( + this._assetRoots, + this._processAsset.bind(this) + ); +}; + +DependecyGraph.prototype._processAsset = function(file) { + var ext = extname(file); + if (this._assetExts.indexOf(ext) !== -1) { + var name = assetName(file, ext); + if (this._assetMap[name] != null) { + debug('Conflcting assets', name); + } + + this._assetMap[name] = new ModuleDescriptor({ + id: 'image!' + name, + path: path.resolve(file), + isAsset: true, + dependencies: [], }); + } +}; + +DependecyGraph.prototype._processAssetChange = function(eventType, file) { + if (this._assetMap == null) { + return; + } + + var name = assetName(file, extname(file)); + if (eventType === 'change' || eventType === 'delete') { + delete this._assetMap[name]; + } + + if (eventType === 'change' || eventType === 'add') { + this._processAsset(file); + } }; /** @@ -623,15 +669,14 @@ function readAndStatDir(dir) { * Given a list of roots and list of extensions find all the files in * the directory with that extension and build a map of those assets. */ -function buildAssetMap(roots, exts) { +function buildAssetMap(roots, processAsset) { var queue = roots.slice(0); - var map = Object.create(null); function search() { var root = queue.shift(); if (root == null) { - return q(map); + return q(); } return readAndStatDir(root).spread(function(files, stats) { @@ -639,21 +684,7 @@ function buildAssetMap(roots, exts) { if (stats[i].isDirectory()) { queue.push(file); } else { - var ext = path.extname(file).replace(/^\./, ''); - if (exts.indexOf(ext) !== -1) { - var assetName = path.basename(file, '.' + ext) - .replace(/@[\d\.]+x/, ''); - if (map[assetName] != null) { - debug('Conflcting assets', assetName); - } - - map[assetName] = new ModuleDescriptor({ - id: 'image!' + assetName, - path: path.resolve(file), - isAsset: true, - dependencies: [], - }); - } + processAsset(file); } }); @@ -664,4 +695,24 @@ function buildAssetMap(roots, exts) { return search(); } +function assetName(file, ext) { + return path.basename(file, '.' + ext).replace(/@[\d\.]+x/, ''); +} + +function extname(name) { + return path.extname(name).replace(/^\./, ''); +} + + +function NotFoundError() { + Error.call(this); + Error.captureStackTrace(this, this.constructor); + var msg = util.format.apply(util, arguments); + this.message = msg; + this.type = this.name = 'NotFoundError'; + this.status = 404; +} + +NotFoundError.__proto__ = Error.prototype; + module.exports = DependecyGraph; diff --git a/react-packager/src/DependencyResolver/haste/index.js b/react-packager/src/DependencyResolver/haste/index.js index 2edb3b52..0e46d5e8 100644 --- a/react-packager/src/DependencyResolver/haste/index.js +++ b/react-packager/src/DependencyResolver/haste/index.js @@ -51,15 +51,15 @@ var validateOpts = declareOpts({ type: 'array', default: [], }, + fileWatcher: { + type: 'object', + required: true, + }, }); function HasteDependencyResolver(options) { var opts = validateOpts(options); - this._fileWatcher = opts.nonPersistent - ? FileWatcher.createDummyWatcher() - : new FileWatcher(opts.projectRoots); - this._depGraph = new DependencyGraph({ roots: opts.projectRoots, assetRoots: opts.assetRoots, @@ -67,7 +67,7 @@ function HasteDependencyResolver(options) { return filepath.indexOf('__tests__') !== -1 || (opts.blacklistRE && opts.blacklistRE.test(filepath)); }, - fileWatcher: this._fileWatcher, + fileWatcher: opts.fileWatcher, }); @@ -164,10 +164,6 @@ HasteDependencyResolver.prototype.wrapModule = function(module, code) { }); }; -HasteDependencyResolver.prototype.end = function() { - return this._fileWatcher.end(); -}; - HasteDependencyResolver.prototype.getDebugInfo = function() { return this._depGraph.getDebugInfo(); }; diff --git a/react-packager/src/FileWatcher/__tests__/FileWatcher-test.js b/react-packager/src/FileWatcher/__tests__/FileWatcher-test.js index 213033c5..e24618dc 100644 --- a/react-packager/src/FileWatcher/__tests__/FileWatcher-test.js +++ b/react-packager/src/FileWatcher/__tests__/FileWatcher-test.js @@ -21,6 +21,7 @@ describe('FileWatcher', function() { var Watcher; beforeEach(function() { + require('mock-modules').dumpCache(); FileWatcher = require('../'); Watcher = require('sane').WatchmanWatcher; Watcher.prototype.once.mockImplementation(function(type, callback) { diff --git a/react-packager/src/FileWatcher/index.js b/react-packager/src/FileWatcher/index.js index c1633683..86ec962b 100644 --- a/react-packager/src/FileWatcher/index.js +++ b/react-packager/src/FileWatcher/index.js @@ -16,7 +16,7 @@ var exec = require('child_process').exec; var Promise = q.Promise; -var detectingWatcherClass = new Promise(function(resolve, reject) { +var detectingWatcherClass = new Promise(function(resolve) { exec('which watchman', function(err, out) { if (err || out.length === 0) { resolve(sane.NodeWatcher); @@ -30,14 +30,23 @@ module.exports = FileWatcher; var MAX_WAIT_TIME = 3000; -function FileWatcher(projectRoots) { - var self = this; +// Singleton +var fileWatcher = null; + +function FileWatcher(rootConfigs) { + if (fileWatcher) { + // This allows us to optimize watching in the future by merging roots etc. + throw new Error('FileWatcher can only be instantiated once'); + } + + fileWatcher = this; + this._loading = q.all( - projectRoots.map(createWatcher) + rootConfigs.map(createWatcher) ).then(function(watchers) { watchers.forEach(function(watcher) { watcher.on('all', function(type, filepath, root) { - self.emit('all', type, filepath, root); + fileWatcher.emit('all', type, filepath, root); }); }); return watchers; @@ -50,21 +59,14 @@ 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]); - } - +function createWatcher(rootConfig) { return detectingWatcherClass.then(function(Watcher) { - var watcher = new Watcher(root, {glob: ['**/*.js', '**/package.json']}); + var watcher = new Watcher(rootConfig.dir, rootConfig.globs); return new Promise(function(resolve, reject) { var rejectTimeout = setTimeout(function() { @@ -77,8 +79,6 @@ function createWatcher(root) { watcher.once('ready', function() { clearTimeout(rejectTimeout); - watchersByRoot[root] = watcher; - watcher._root = root; resolve(watcher); }); }); diff --git a/react-packager/src/Packager/index.js b/react-packager/src/Packager/index.js index ac7bd2e2..843efe75 100644 --- a/react-packager/src/Packager/index.js +++ b/react-packager/src/Packager/index.js @@ -56,6 +56,14 @@ var validateOpts = declareOpts({ type: 'array', required: false, }, + assetExts: { + type: 'array', + default: ['png'], + }, + fileWatcher: { + type: 'object', + required: true, + }, }); function Packager(options) { @@ -70,6 +78,7 @@ function Packager(options) { nonPersistent: opts.nonPersistent, moduleFormat: opts.moduleFormat, assetRoots: opts.assetRoots, + fileWatcher: opts.fileWatcher, }); this._transformer = new Transformer({ @@ -83,10 +92,7 @@ function Packager(options) { } Packager.prototype.kill = function() { - return q.all([ - this._transformer.kill(), - this._resolver.end(), - ]); + return this._transformer.kill(); }; Packager.prototype.package = function(main, runModule, sourceMapUrl, isDev) { diff --git a/react-packager/src/Server/index.js b/react-packager/src/Server/index.js index f40ebeea..23af55db 100644 --- a/react-packager/src/Server/index.js +++ b/react-packager/src/Server/index.js @@ -55,18 +55,49 @@ var validateOpts = declareOpts({ type: 'array', required: false, }, + assetExts: { + type: 'array', + default: ['png'], + }, }); function Server(options) { var opts = validateOpts(options); + this._projectRoots = opts.projectRoots; this._packages = Object.create(null); - this._packager = new Packager(opts); this._changeWatchers = []; + var watchRootConfigs = opts.projectRoots.map(function(dir) { + return { + dir: dir, + globs: [ + '**/*.js', + '**/package.json', + ] + }; + }); + + if (opts.assetRoots != null) { + watchRootConfigs = watchRootConfigs.concat( + opts.assetRoots.map(function(dir) { + return { + dir: dir, + globs: opts.assetExts.map(function(ext) { + return '**/*.' + ext; + }), + }; + }) + ); + } + this._fileWatcher = options.nonPersistent ? FileWatcher.createDummyWatcher() - : new FileWatcher(options.projectRoots); + : new FileWatcher(watchRootConfigs); + + var packagerOpts = Object.create(opts); + packagerOpts.fileWatcher = this._fileWatcher; + this._packager = new Packager(packagerOpts); var onFileChange = this._onFileChange.bind(this); this._fileWatcher.on('all', onFileChange); @@ -246,6 +277,9 @@ Server.prototype.processRequest = function(req, res, next) { function getOptionsFromUrl(reqUrl) { // `true` to parse the query param as an object. var urlObj = url.parse(reqUrl, true); + // node v0.11.14 bug see https://github.com/facebook/react-native/issues/218 + urlObj.query = urlObj.query || {}; + var pathname = urlObj.pathname; // Backwards compatibility. Options used to be as added as '.' to the @@ -281,11 +315,11 @@ function getBoolOptionFromQuery(query, opt, defaultVal) { } function handleError(res, error) { - res.writeHead(500, { + res.writeHead(error.status || 500, { 'Content-Type': 'application/json; charset=UTF-8', }); - if (error.type === 'TransformError') { + if (error.type === 'TransformError' || error.type === 'NotFoundError') { res.end(JSON.stringify(error)); } else { console.error(error.stack || error);