From bcbe99b3afb67c3959aa70f2c9d028a13124ae4a Mon Sep 17 00:00:00 2001 From: Tadeu Zagallo Date: Thu, 3 Sep 2015 12:02:39 -0700 Subject: [PATCH 1/9] [ReactNative][Packager] Fix source maps for minified sources Summary: The packager was ignoring minification for source map requests. --- react-packager/src/Bundler/Bundle.js | 4 ++++ react-packager/src/Server/index.js | 4 +++- 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/react-packager/src/Bundler/Bundle.js b/react-packager/src/Bundler/Bundle.js index b7920ebb..88431cbf 100644 --- a/react-packager/src/Bundler/Bundle.js +++ b/react-packager/src/Bundler/Bundle.js @@ -186,6 +186,10 @@ class Bundle { options = options || {}; + if (options.minify) { + return this.getMinifiedSourceAndMap().map; + } + if (this._shouldCombineSourceMaps) { return this._getCombinedSourceMaps(options); } diff --git a/react-packager/src/Server/index.js b/react-packager/src/Server/index.js index d727fd52..7d65d0a2 100644 --- a/react-packager/src/Server/index.js +++ b/react-packager/src/Server/index.js @@ -370,7 +370,9 @@ class Server { res.end(bundleSource); Activity.endEvent(startReqEventId); } else if (requestType === 'map') { - var sourceMap = JSON.stringify(p.getSourceMap()); + var sourceMap = JSON.stringify(p.getSourceMap({ + minify: options.minify, + })); res.setHeader('Content-Type', 'application/json'); res.end(sourceMap); Activity.endEvent(startReqEventId); From 734edf5cd427f20c068a670d2ae17ac0625162f9 Mon Sep 17 00:00:00 2001 From: Amjad Masad Date: Thu, 3 Sep 2015 14:42:47 -0700 Subject: [PATCH 2/9] [react-packager] Socket Server should not die while there active connections Summary: Saw an issue with a build because of an ENONT error: https://fb.facebook.com/groups/716936458354972/permalink/923628747685741/ My hypothesis: 1. We issue a ping to the socket (in SocketInterface/index.js) a decides if the available socket is alive 2. We see that it's alive but by the time we actually connect to it the server would've died Solution: 1. The server shouldn't die as long as there are clients connected to it (currently it only stay alive as long as there are jobs) 2. The "ping" should only disconnect once the client is connected 3. Finally, have a better error message than ENOENT --- react-packager/src/SocketInterface/SocketClient.js | 6 +++++- react-packager/src/SocketInterface/SocketServer.js | 7 ++++++- react-packager/src/SocketInterface/index.js | 12 ++++++++++-- 3 files changed, 21 insertions(+), 4 deletions(-) diff --git a/react-packager/src/SocketInterface/SocketClient.js b/react-packager/src/SocketInterface/SocketClient.js index e20e5b89..6ccfd481 100644 --- a/react-packager/src/SocketInterface/SocketClient.js +++ b/react-packager/src/SocketInterface/SocketClient.js @@ -29,7 +29,11 @@ class SocketClient { this._sock = net.connect(sockPath); this._ready = new Promise((resolve, reject) => { this._sock.on('connect', () => resolve(this)); - this._sock.on('error', (e) => reject(e)); + this._sock.on('error', (e) => { + e.message = `Error connecting to server on ${sockPath}` + + `with error: ${e.message}`; + reject(e); + }); }); this._resolvers = Object.create(null); diff --git a/react-packager/src/SocketInterface/SocketServer.js b/react-packager/src/SocketInterface/SocketServer.js index b292b2bf..abdc094b 100644 --- a/react-packager/src/SocketInterface/SocketServer.js +++ b/react-packager/src/SocketInterface/SocketServer.js @@ -35,6 +35,8 @@ class SocketServer { process.on('exit', () => fs.unlinkSync(sockPath)); }); }); + + this._numConnections = 0; this._server.on('connection', (sock) => this._handleConnection(sock)); // Disable the file watcher. @@ -50,10 +52,13 @@ class SocketServer { _handleConnection(sock) { debug('connection to server', process.pid); + this._numConnections++; + sock.on('close', () => this._numConnections--); const bunser = new bser.BunserBuf(); sock.on('data', (buf) => bunser.append(buf)); bunser.on('value', (m) => this._handleMessage(sock, m)); + bunser.on('error', (e) => console.error(e)); } _handleMessage(sock, m) { @@ -116,7 +121,7 @@ class SocketServer { _dieEventually() { clearTimeout(this._deathTimer); this._deathTimer = setTimeout(() => { - if (this._jobs <= 0) { + if (this._jobs <= 0 && this._numConnections <= 0) { debug('server dying', process.pid); process.exit(); } diff --git a/react-packager/src/SocketInterface/index.js b/react-packager/src/SocketInterface/index.js index 19b39bf0..f3dce21a 100644 --- a/react-packager/src/SocketInterface/index.js +++ b/react-packager/src/SocketInterface/index.js @@ -42,8 +42,16 @@ const SocketInterface = { if (fs.existsSync(sockPath)) { var sock = net.connect(sockPath); sock.on('connect', () => { - sock.end(); - resolve(SocketClient.create(sockPath)); + SocketClient.create(sockPath).then( + client => { + sock.end(); + resolve(client); + }, + error => { + sock.end(); + reject(error); + } + ); }); sock.on('error', (e) => { try { From 79abdb225025a1197645c568c58249f908d09684 Mon Sep 17 00:00:00 2001 From: Tadeu Zagallo Date: Fri, 4 Sep 2015 12:06:44 -0700 Subject: [PATCH 3/9] [RectNative][Packager] Cache minification result Summary: The packager just cached the result of the bundle, but would minify it on every request. Change it to cache the minification result. --- react-packager/src/Bundler/Bundle.js | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/react-packager/src/Bundler/Bundle.js b/react-packager/src/Bundler/Bundle.js index 88431cbf..6f20b49d 100644 --- a/react-packager/src/Bundler/Bundle.js +++ b/react-packager/src/Bundler/Bundle.js @@ -115,13 +115,18 @@ class Bundle { getMinifiedSourceAndMap() { this._assertFinalized(); + if (this._minifiedSourceAndMap) { + return this._minifiedSourceAndMap; + } + const source = this._getSource(); try { - return UglifyJS.minify(source, { + this._minifiedSourceAndMap = UglifyJS.minify(source, { fromString: true, outSourceMap: 'bundle.js', inSourceMap: this.getSourceMap(), }); + return this._minifiedSourceAndMap; } catch(e) { // Sometimes, when somebody is using a new syntax feature that we // don't yet have transform for, the untransformed line is sent to From b7d76074a06b9742387543df266fc5302a939eba Mon Sep 17 00:00:00 2001 From: Amjad Masad Date: Fri, 4 Sep 2015 15:21:58 -0700 Subject: [PATCH 4/9] [react-packager] Support platform extensions in image requires Summary: We don't currently support platform extensions in asset modules. This adds supports for it: ``` require('./a.png'); ``` Will require 'a.ios.png' if it exists and 'a.png' if it doesn't. --- .../AssetServer/__tests__/AssetServer-test.js | 69 ++++++++++ react-packager/src/AssetServer/index.js | 39 ++++-- .../__tests__/DependencyGraph-test.js | 85 ++++++++++++ .../DependencyGraph/index.js | 26 ++-- .../src/Server/__tests__/Server-test.js | 10 +- react-packager/src/Server/index.js | 5 +- .../__tests__/extractAssetResolution-test.js | 52 -------- .../__tests__/getAssetDataFromName-test.js | 123 ++++++++++++++++++ .../__tests__/getPotentialPlatformExt-test.js | 24 ++++ .../src/lib/getAssetDataFromName.js | 38 +++++- .../src/lib/getPlatformExtension.js | 28 ++++ 11 files changed, 413 insertions(+), 86 deletions(-) delete mode 100644 react-packager/src/lib/__tests__/extractAssetResolution-test.js create mode 100644 react-packager/src/lib/__tests__/getAssetDataFromName-test.js create mode 100644 react-packager/src/lib/__tests__/getPotentialPlatformExt-test.js create mode 100644 react-packager/src/lib/getPlatformExtension.js diff --git a/react-packager/src/AssetServer/__tests__/AssetServer-test.js b/react-packager/src/AssetServer/__tests__/AssetServer-test.js index 54ef7012..d3688914 100644 --- a/react-packager/src/AssetServer/__tests__/AssetServer-test.js +++ b/react-packager/src/AssetServer/__tests__/AssetServer-test.js @@ -1,6 +1,7 @@ 'use strict'; jest + .dontMock('../../lib/getPlatformExtension') .dontMock('../../lib/getAssetDataFromName') .dontMock('../'); @@ -47,6 +48,43 @@ describe('AssetServer', () => { ); }); + pit('should work for the simple case with platform ext', () => { + const server = new AssetServer({ + projectRoots: ['/root'], + assetExts: ['png'], + }); + + fs.__setMockFilesystem({ + 'root': { + imgs: { + 'b.ios.png': 'b ios image', + 'b.android.png': 'b android image', + 'c.png': 'c general image', + 'c.android.png': 'c android image', + } + } + }); + + return Promise.all([ + server.get('imgs/b.png', 'ios').then( + data => expect(data).toBe('b ios image') + ), + server.get('imgs/b.png', 'android').then( + data => expect(data).toBe('b android image') + ), + server.get('imgs/c.png', 'android').then( + data => expect(data).toBe('c android image') + ), + server.get('imgs/c.png', 'ios').then( + data => expect(data).toBe('c general image') + ), + server.get('imgs/c.png').then( + data => expect(data).toBe('c general image') + ), + ]); + }); + + pit('should work for the simple case with jpg', () => { const server = new AssetServer({ projectRoots: ['/root'], @@ -95,6 +133,37 @@ describe('AssetServer', () => { ); }); + pit('should pick the bigger one with platform ext', () => { + const server = new AssetServer({ + projectRoots: ['/root'], + assetExts: ['png'], + }); + + fs.__setMockFilesystem({ + 'root': { + imgs: { + 'b@1x.png': 'b1 image', + 'b@2x.png': 'b2 image', + 'b@4x.png': 'b4 image', + 'b@4.5x.png': 'b4.5 image', + 'b@1x.ios.png': 'b1 ios image', + 'b@2x.ios.png': 'b2 ios image', + 'b@4x.ios.png': 'b4 ios image', + 'b@4.5x.ios.png': 'b4.5 ios image', + } + } + }); + + return Promise.all([ + server.get('imgs/b@3x.png').then(data => + expect(data).toBe('b4 image') + ), + server.get('imgs/b@3x.png', 'ios').then(data => + expect(data).toBe('b4 ios image') + ), + ]); + }); + pit('should support multiple project roots', () => { const server = new AssetServer({ projectRoots: ['/root', '/root2'], diff --git a/react-packager/src/AssetServer/index.js b/react-packager/src/AssetServer/index.js index f442f6b8..5e4c1127 100644 --- a/react-packager/src/AssetServer/index.js +++ b/react-packager/src/AssetServer/index.js @@ -20,7 +20,6 @@ const stat = Promise.denodeify(fs.stat); const readDir = Promise.denodeify(fs.readdir); const readFile = Promise.denodeify(fs.readFile); - const validateOpts = declareOpts({ projectRoots: { type: 'array', @@ -39,9 +38,9 @@ class AssetServer { this._assetExts = opts.assetExts; } - get(assetPath) { + get(assetPath, platform = null) { const assetData = getAssetDataFromName(assetPath); - return this._getAssetRecord(assetPath).then(record => { + return this._getAssetRecord(assetPath, platform).then(record => { for (let i = 0; i < record.scales.length; i++) { if (record.scales[i] >= assetData.resolution) { return readFile(record.files[i]); @@ -85,9 +84,10 @@ class AssetServer { * 1. We first parse the directory of the asset * 2. We check to find a matching directory in one of the project roots * 3. We then build a map of all assets and their scales in this directory - * 4. Then pick the closest resolution (rounding up) to the requested one + * 4. Then try to pick platform-specific asset records + * 5. Then pick the closest resolution (rounding up) to the requested one */ - _getAssetRecord(assetPath) { + _getAssetRecord(assetPath, platform = null) { const filename = path.basename(assetPath); return ( @@ -104,11 +104,20 @@ class AssetServer { const files = res[1]; const assetData = getAssetDataFromName(filename); - const map = this._buildAssetMap(dir, files); - const record = map[assetData.assetName]; + const map = this._buildAssetMap(dir, files, platform); + + let record; + if (platform != null){ + record = map[getAssetKey(assetData.assetName, platform)] || + map[assetData.assetName]; + } else { + record = map[assetData.assetName]; + } if (!record) { - throw new Error('Asset not found'); + throw new Error( + `Asset not found: ${assetPath} for platform: ${platform}` + ); } return record; @@ -141,9 +150,10 @@ class AssetServer { const map = Object.create(null); assets.forEach(function(asset, i) { const file = files[i]; - let record = map[asset.assetName]; + const assetKey = getAssetKey(asset.assetName, asset.platform); + let record = map[assetKey]; if (!record) { - record = map[asset.assetName] = { + record = map[assetKey] = { scales: [], files: [], }; @@ -151,6 +161,7 @@ class AssetServer { let insertIndex; const length = record.scales.length; + for (insertIndex = 0; insertIndex < length; insertIndex++) { if (asset.resolution < record.scales[insertIndex]) { break; @@ -164,4 +175,12 @@ class AssetServer { } } +function getAssetKey(assetName, platform) { + if (platform != null) { + return `${assetName} : ${platform}`; + } else { + return assetName; + } +} + module.exports = AssetServer; diff --git a/react-packager/src/DependencyResolver/DependencyGraph/__tests__/DependencyGraph-test.js b/react-packager/src/DependencyResolver/DependencyGraph/__tests__/DependencyGraph-test.js index bf4f6596..6dc2bc7e 100644 --- a/react-packager/src/DependencyResolver/DependencyGraph/__tests__/DependencyGraph-test.js +++ b/react-packager/src/DependencyResolver/DependencyGraph/__tests__/DependencyGraph-test.js @@ -16,6 +16,7 @@ jest .dontMock('../../crawlers') .dontMock('../../crawlers/node') .dontMock('../../replacePatterns') + .dontMock('../../../lib/getPlatformExtension') .dontMock('../../../lib/getAssetDataFromName') .dontMock('../../fastfs') .dontMock('../../AssetModule_DEPRECATED') @@ -423,6 +424,90 @@ describe('DependencyGraph', function() { }); }); + pit('should respect platform extension in assets', function() { + var root = '/root'; + fs.__setMockFilesystem({ + 'root': { + 'index.js': [ + '/**', + ' * @providesModule index', + ' */', + 'require("./imgs/a.png");', + 'require("./imgs/b.png");', + 'require("./imgs/c.png");', + ].join('\n'), + 'imgs': { + 'a@1.5x.ios.png': '', + 'b@.7x.ios.png': '', + 'c.ios.png': '', + 'c@2x.ios.png': '', + }, + 'package.json': JSON.stringify({ + name: 'rootPackage' + }), + } + }); + + var dgraph = new DependencyGraph({ + roots: [root], + fileWatcher: fileWatcher, + assetExts: ['png', 'jpg'], + cache: cache, + }); + + dgraph.setup({ platform: 'ios' }); + + return getOrderedDependenciesAsJSON(dgraph, '/root/index.js').then(function(deps) { + expect(deps) + .toEqual([ + { + id: 'index', + path: '/root/index.js', + dependencies: [ + './imgs/a.png', + './imgs/b.png', + './imgs/c.png', + ], + isAsset: false, + isAsset_DEPRECATED: false, + isJSON: false, + isPolyfill: false, + resolution: undefined, + }, + { + id: 'rootPackage/imgs/a.png', + path: '/root/imgs/a@1.5x.ios.png', + resolution: 1.5, + dependencies: [], + isAsset: true, + isAsset_DEPRECATED: false, + isJSON: false, + isPolyfill: false, + }, + { + id: 'rootPackage/imgs/b.png', + path: '/root/imgs/b@.7x.ios.png', + resolution: 0.7, + dependencies: [], + isAsset: true, + isAsset_DEPRECATED: false, + isJSON: false, + isPolyfill: false, + }, + { + id: 'rootPackage/imgs/c.png', + path: '/root/imgs/c.ios.png', + resolution: 1, + dependencies: [], + isAsset: true, + isAsset_DEPRECATED: false, + isJSON: false, + isPolyfill: false, + }, + ]); + }); + }); + pit('Deprecated and relative assets can live together', function() { var root = '/root'; fs.__setMockFilesystem({ diff --git a/react-packager/src/DependencyResolver/DependencyGraph/index.js b/react-packager/src/DependencyResolver/DependencyGraph/index.js index f2b77f4d..0820c14a 100644 --- a/react-packager/src/DependencyResolver/DependencyGraph/index.js +++ b/react-packager/src/DependencyResolver/DependencyGraph/index.js @@ -1,4 +1,4 @@ -/** + /** * Copyright (c) 2015-present, Facebook, Inc. * All rights reserved. * @@ -17,6 +17,7 @@ const crawl = require('../crawlers'); const debug = require('debug')('DependencyGraph'); const declareOpts = require('../../lib/declareOpts'); const getAssetDataFromName = require('../../lib/getAssetDataFromName'); +const getPontentialPlatformExt = require('../../lib/getPlatformExtension'); const isAbsolutePath = require('absolute-path'); const path = require('path'); const util = require('util'); @@ -274,7 +275,7 @@ class DependencyGraph { // `platformExt` could be set in the `setup` method. if (!this._platformExt) { - const platformExt = getPlatformExt(entryPath); + const platformExt = getPontentialPlatformExt(entryPath); if (platformExt && this._opts.platforms.indexOf(platformExt) > -1) { this._platformExt = platformExt; } else { @@ -390,12 +391,18 @@ class DependencyGraph { return Promise.resolve().then(() => { if (this._isAssetFile(potentialModulePath)) { const {name, type} = getAssetDataFromName(potentialModulePath); - const pattern = new RegExp('^' + name + '(@[\\d\\.]+x)?\\.' + type); + + let pattern = '^' + name + '(@[\\d\\.]+x)?'; + if (this._platformExt != null) { + pattern += '(\\.' + this._platformExt + ')?'; + } + pattern += '\\.' + type; + // We arbitrarly grab the first one, because scale selection // will happen somewhere const [assetFile] = this._fastfs.matches( path.dirname(potentialModulePath), - pattern + new RegExp(pattern) ); if (assetFile) { @@ -496,7 +503,7 @@ class DependencyGraph { const modules = this._hasteMap[name]; if (this._platformExt != null) { for (let i = 0; i < modules.length; i++) { - if (getPlatformExt(modules[i].path) === this._platformExt) { + if (getPontentialPlatformExt(modules[i].path) === this._platformExt) { return modules[i]; } } @@ -662,15 +669,6 @@ function normalizePath(modulePath) { return modulePath.replace(/\/$/, ''); } -// Extract platform extension: index.ios.js -> ios -function getPlatformExt(file) { - const parts = path.basename(file).split('.'); - if (parts.length < 3) { - return null; - } - return parts[parts.length - 2]; -} - util.inherits(NotFoundError, Error); module.exports = DependencyGraph; diff --git a/react-packager/src/Server/__tests__/Server-test.js b/react-packager/src/Server/__tests__/Server-test.js index 5152054e..64af474a 100644 --- a/react-packager/src/Server/__tests__/Server-test.js +++ b/react-packager/src/Server/__tests__/Server-test.js @@ -244,8 +244,16 @@ describe('processRequest', () => { expect(res.end).toBeCalledWith('i am image'); }); - it('should return 404', () => { + it('should parse the platform option', () => { + const req = {url: '/assets/imgs/a.png?platform=ios'}; + const res = {end: jest.genMockFn()}; + AssetServer.prototype.get.mockImpl(() => Promise.resolve('i am image')); + + server.processRequest(req, res); + jest.runAllTimers(); + expect(AssetServer.prototype.get).toBeCalledWith('imgs/a.png', 'ios'); + expect(res.end).toBeCalledWith('i am image'); }); }); diff --git a/react-packager/src/Server/index.js b/react-packager/src/Server/index.js index 7d65d0a2..e889a357 100644 --- a/react-packager/src/Server/index.js +++ b/react-packager/src/Server/index.js @@ -278,7 +278,8 @@ class Server { _processAssetsRequest(req, res) { const urlObj = url.parse(req.url, true); const assetPath = urlObj.pathname.match(/^\/assets\/(.+)$/); - this._assetServer.get(assetPath[1]) + const assetEvent = Activity.startEvent(`processing asset request ${assetPath[1]}`); + this._assetServer.get(assetPath[1], urlObj.query.platform) .then( data => res.end(data), error => { @@ -286,7 +287,7 @@ class Server { res.writeHead('404'); res.end('Asset not found'); } - ).done(); + ).done(() => Activity.endEvent(assetEvent)); } _processProfile(req, res) { diff --git a/react-packager/src/lib/__tests__/extractAssetResolution-test.js b/react-packager/src/lib/__tests__/extractAssetResolution-test.js deleted file mode 100644 index d0309ca6..00000000 --- a/react-packager/src/lib/__tests__/extractAssetResolution-test.js +++ /dev/null @@ -1,52 +0,0 @@ -'use strict'; - -jest.autoMockOff(); -var getAssetDataFromName = require('../getAssetDataFromName'); - -describe('getAssetDataFromName', function() { - it('should extract resolution simple case', function() { - var data = getAssetDataFromName('test@2x.png'); - expect(data).toEqual({ - assetName: 'test.png', - resolution: 2, - type: 'png', - name: 'test', - }); - }); - - it('should default resolution to 1', function() { - var data = getAssetDataFromName('test.png'); - expect(data).toEqual({ - assetName: 'test.png', - resolution: 1, - type: 'png', - name: 'test', - }); - }); - - it('should support float', function() { - var data = getAssetDataFromName('test@1.1x.png'); - expect(data).toEqual({ - assetName: 'test.png', - resolution: 1.1, - type: 'png', - name: 'test', - }); - - data = getAssetDataFromName('test@.1x.png'); - expect(data).toEqual({ - assetName: 'test.png', - resolution: 0.1, - type: 'png', - name: 'test', - }); - - data = getAssetDataFromName('test@0.2x.png'); - expect(data).toEqual({ - assetName: 'test.png', - resolution: 0.2, - type: 'png', - name: 'test', - }); - }); -}); diff --git a/react-packager/src/lib/__tests__/getAssetDataFromName-test.js b/react-packager/src/lib/__tests__/getAssetDataFromName-test.js new file mode 100644 index 00000000..d6711001 --- /dev/null +++ b/react-packager/src/lib/__tests__/getAssetDataFromName-test.js @@ -0,0 +1,123 @@ +/** + * Copyright (c) 2015-present, Facebook, Inc. + * All rights reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE file in the root directory of this source tree. An additional grant + * of patent rights can be found in the PATENTS file in the same directory. + */ +'use strict'; + +jest.dontMock('../getPlatformExtension') + .dontMock('../getAssetDataFromName'); + +describe('getAssetDataFromName', () => { + let getAssetDataFromName; + + beforeEach(() => { + getAssetDataFromName = require('../getAssetDataFromName'); + }); + + it('should get data from name', () => { + expect(getAssetDataFromName('a/b/c.png')).toEqual({ + resolution: 1, + assetName: 'a/b/c.png', + type: 'png', + name: 'c', + platform: null, + }); + + expect(getAssetDataFromName('a/b/c@1x.png')).toEqual({ + resolution: 1, + assetName: 'a/b/c.png', + type: 'png', + name: 'c', + platform: null, + }); + + expect(getAssetDataFromName('a/b/c@2.5x.png')).toEqual({ + resolution: 2.5, + assetName: 'a/b/c.png', + type: 'png', + name: 'c', + platform: null, + }); + + expect(getAssetDataFromName('a/b/c.ios.png')).toEqual({ + resolution: 1, + assetName: 'a/b/c.png', + type: 'png', + name: 'c', + platform: 'ios', + }); + + expect(getAssetDataFromName('a/b/c@1x.ios.png')).toEqual({ + resolution: 1, + assetName: 'a/b/c.png', + type: 'png', + name: 'c', + platform: 'ios', + }); + + expect(getAssetDataFromName('a/b/c@2.5x.ios.png')).toEqual({ + resolution: 2.5, + assetName: 'a/b/c.png', + type: 'png', + name: 'c', + platform: 'ios', + }); + }); + + describe('resolution extraction', () => { + it('should extract resolution simple case', () => { + var data = getAssetDataFromName('test@2x.png'); + expect(data).toEqual({ + assetName: 'test.png', + resolution: 2, + type: 'png', + name: 'test', + platform: null, + }); + }); + + it('should default resolution to 1', () => { + var data = getAssetDataFromName('test.png'); + expect(data).toEqual({ + assetName: 'test.png', + resolution: 1, + type: 'png', + name: 'test', + platform: null, + }); + }); + + it('should support float', () => { + var data = getAssetDataFromName('test@1.1x.png'); + expect(data).toEqual({ + assetName: 'test.png', + resolution: 1.1, + type: 'png', + name: 'test', + platform: null, + }); + + data = getAssetDataFromName('test@.1x.png'); + expect(data).toEqual({ + assetName: 'test.png', + resolution: 0.1, + type: 'png', + name: 'test', + platform: null, + }); + + data = getAssetDataFromName('test@0.2x.png'); + expect(data).toEqual({ + assetName: 'test.png', + resolution: 0.2, + type: 'png', + name: 'test', + platform: null, + }); + }); + }); +}); diff --git a/react-packager/src/lib/__tests__/getPotentialPlatformExt-test.js b/react-packager/src/lib/__tests__/getPotentialPlatformExt-test.js new file mode 100644 index 00000000..5f734880 --- /dev/null +++ b/react-packager/src/lib/__tests__/getPotentialPlatformExt-test.js @@ -0,0 +1,24 @@ +/** + * Copyright (c) 2015-present, Facebook, Inc. + * All rights reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE file in the root directory of this source tree. An additional grant + * of patent rights can be found in the PATENTS file in the same directory. + */ +'use strict'; + +jest.dontMock('../getPlatformExtension'); + +describe('getPlatformExtension', function() { + it('should get platform ext', function() { + var getPlatformExtension = require('../getPlatformExtension'); + expect(getPlatformExtension('a.ios.js')).toBe('ios'); + expect(getPlatformExtension('a.android.js')).toBe('android'); + expect(getPlatformExtension('/b/c/a.ios.js')).toBe('ios'); + expect(getPlatformExtension('/b/c.android/a.ios.js')).toBe('ios'); + expect(getPlatformExtension('/b/c/a@1.5x.ios.png')).toBe('ios'); + expect(getPlatformExtension('/b/c/a@1.5x.lol.png')).toBe(null); + expect(getPlatformExtension('/b/c/a.lol.png')).toBe(null); + }); +}); diff --git a/react-packager/src/lib/getAssetDataFromName.js b/react-packager/src/lib/getAssetDataFromName.js index c4848fd1..33fa13ce 100644 --- a/react-packager/src/lib/getAssetDataFromName.js +++ b/react-packager/src/lib/getAssetDataFromName.js @@ -1,14 +1,29 @@ + /** + * Copyright (c) 2015-present, Facebook, Inc. + * All rights reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE file in the root directory of this source tree. An additional grant + * of patent rights can be found in the PATENTS file in the same directory. + */ 'use strict'; -var path = require('path'); +const path = require('path'); +const getPlatformExtension = require('./getPlatformExtension'); function getAssetDataFromName(filename) { - var ext = path.extname(filename); + const ext = path.extname(filename); + const platformExt = getPlatformExtension(filename); - var re = new RegExp('@([\\d\\.]+)x\\' + ext + '$'); + let pattern = '@([\\d\\.]+)x'; + if (platformExt != null) { + pattern += '(\\.' + platformExt + ')?'; + } + pattern += '\\' + ext + '$'; + const re = new RegExp(pattern); - var match = filename.match(re); - var resolution; + const match = filename.match(re); + let resolution; if (!(match && match[1])) { resolution = 1; @@ -19,12 +34,21 @@ function getAssetDataFromName(filename) { } } - var assetName = match ? filename.replace(re, ext) : filename; + let assetName; + if (match) { + assetName = filename.replace(re, ext); + } else if (platformExt != null) { + assetName = filename.replace(new RegExp(`\\.${platformExt}\\${ext}`), ext); + } else { + assetName = filename; + } + return { resolution: resolution, assetName: assetName, type: ext.slice(1), - name: path.basename(assetName, ext) + name: path.basename(assetName, ext), + platform: platformExt, }; } diff --git a/react-packager/src/lib/getPlatformExtension.js b/react-packager/src/lib/getPlatformExtension.js new file mode 100644 index 00000000..3f34da42 --- /dev/null +++ b/react-packager/src/lib/getPlatformExtension.js @@ -0,0 +1,28 @@ +/** + * Copyright (c) 2015-present, Facebook, Inc. + * All rights reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE file in the root directory of this source tree. An additional grant + * of patent rights can be found in the PATENTS file in the same directory. + */ +'use strict'; + +const path = require('path'); + +const SUPPORTED_PLATFORM_EXTS = ['android', 'ios']; + +const re = new RegExp( + '[^\\.]+\\.(' + SUPPORTED_PLATFORM_EXTS.join('|') + ')\\.\\w+$' +); + +// Extract platform extension: index.ios.js -> ios +function getPlatformExtension(file) { + const match = file.match(re); + if (match && match[1]) { + return match[1]; + } + return null; +} + +module.exports = getPlatformExtension; From 4b14a2f78332d5020a3ea4c6cfb49ea6e39e8a4d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mart=C3=ADn=20Bigio?= Date: Mon, 7 Sep 2015 01:23:21 -0700 Subject: [PATCH 5/9] [react-packager] Add command line option to reset cache on OSS --- packager.js | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/packager.js b/packager.js index 8f0f0800..b5f78969 100644 --- a/packager.js +++ b/packager.js @@ -59,6 +59,14 @@ var options = parseCommandLine([{ type: 'string', default: require.resolve('./transformer.js'), description: 'Specify a custom transformer to be used (absolute path)' +}, { + command: 'resetCache', + description: 'Removes cached files', + default: false, +}, { + command: 'reset-cache', + description: 'Removes cached files', + default: false, }]); if (options.projectRoots) { @@ -229,6 +237,7 @@ function getAppMiddleware(options) { transformModulePath: transformerPath, assetRoots: options.assetRoots, assetExts: ['png', 'jpeg', 'jpg'], + resetCache: options.resetCache || options['reset-cache'], polyfillModuleNames: [ require.resolve( '../Libraries/JavaScriptAppEngine/polyfills/document.js' From 2727f4b5ea0ecacbb28c679d4bda656bdfa8a0b1 Mon Sep 17 00:00:00 2001 From: Amjad Masad Date: Mon, 7 Sep 2015 10:44:02 -0700 Subject: [PATCH 6/9] [react-packager] Allow a longer startup time before the server dies Summary: 1. When the server starts up, it only gives itself 30 second to live before receiving any connections/jobs 2. There is a startup cost with starting the server and handshaking 3. The server dies before the client has a chance to connect to it Solution: 1. While the server should die pretty fast after it's done it's work, we should have a longer timeout for starting it 2. I also added accompanying server logs with client connection errors --- react-packager/src/SocketInterface/SocketClient.js | 8 +++++++- react-packager/src/SocketInterface/SocketServer.js | 7 ++++--- 2 files changed, 11 insertions(+), 4 deletions(-) diff --git a/react-packager/src/SocketInterface/SocketClient.js b/react-packager/src/SocketInterface/SocketClient.js index 6ccfd481..32e3b25d 100644 --- a/react-packager/src/SocketInterface/SocketClient.js +++ b/react-packager/src/SocketInterface/SocketClient.js @@ -12,6 +12,7 @@ const Bundle = require('../Bundler/Bundle'); const Promise = require('promise'); const bser = require('bser'); const debug = require('debug')('ReactPackager:SocketClient'); +const fs = require('fs'); const net = require('net'); const path = require('path'); const tmpdir = require('os').tmpdir(); @@ -30,8 +31,13 @@ class SocketClient { this._ready = new Promise((resolve, reject) => { this._sock.on('connect', () => resolve(this)); this._sock.on('error', (e) => { - e.message = `Error connecting to server on ${sockPath}` + + e.message = `Error connecting to server on ${sockPath} ` + `with error: ${e.message}`; + + if (fs.existsSync(LOG_PATH)) { + e.message += '\nServer logs:\n' + fs.readFileSync(LOG_PATH, 'utf8'); + } + reject(e); }); }); diff --git a/react-packager/src/SocketInterface/SocketServer.js b/react-packager/src/SocketInterface/SocketServer.js index abdc094b..888dcf78 100644 --- a/react-packager/src/SocketInterface/SocketServer.js +++ b/react-packager/src/SocketInterface/SocketServer.js @@ -16,6 +16,7 @@ const fs = require('fs'); const net = require('net'); const MAX_IDLE_TIME = 30 * 1000; +const MAX_STARTUP_TIME = 5 * 60 * 1000; class SocketServer { constructor(sockPath, options) { @@ -43,7 +44,7 @@ class SocketServer { options.nonPersistent = true; this._packagerServer = new Server(options); this._jobs = 0; - this._dieEventually(); + this._dieEventually(MAX_STARTUP_TIME); } onReady() { @@ -118,7 +119,7 @@ class SocketServer { })); } - _dieEventually() { + _dieEventually(delay = MAX_IDLE_TIME) { clearTimeout(this._deathTimer); this._deathTimer = setTimeout(() => { if (this._jobs <= 0 && this._numConnections <= 0) { @@ -126,7 +127,7 @@ class SocketServer { process.exit(); } this._dieEventually(); - }, MAX_IDLE_TIME); + }, delay); } static listenOnServerIPCMessages() { From 7beb248c064b5fed216246b91d9c9bb5f18b95d1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mart=C3=ADn=20Bigio?= Date: Tue, 8 Sep 2015 04:56:45 -0700 Subject: [PATCH 7/9] [react-packager] Bump ipc timeout --- react-packager/src/SocketInterface/index.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/react-packager/src/SocketInterface/index.js b/react-packager/src/SocketInterface/index.js index f3dce21a..eab83fa4 100644 --- a/react-packager/src/SocketInterface/index.js +++ b/react-packager/src/SocketInterface/index.js @@ -20,7 +20,7 @@ const path = require('path'); const tmpdir = require('os').tmpdir(); const {spawn} = require('child_process'); -const CREATE_SERVER_TIMEOUT = 60000; +const CREATE_SERVER_TIMEOUT = 5 * 60 * 1000; const SocketInterface = { getOrCreateSocketFor(options) { From 47eec251bc32e7fc1c8a38ba45b777c10deb7ba0 Mon Sep 17 00:00:00 2001 From: Ben Alpert Date: Tue, 8 Sep 2015 08:13:51 -0700 Subject: [PATCH 8/9] [ReactNative] Enable displayName transformer in open source --- transformer.js | 1 + 1 file changed, 1 insertion(+) diff --git a/transformer.js b/transformer.js index 8f7a48c2..b3ab96d0 100644 --- a/transformer.js +++ b/transformer.js @@ -39,6 +39,7 @@ function transform(srcTxt, filename, options) { 'es7.objectRestSpread', 'flow', 'react', + 'react.displayName', 'regenerator', ], plugins: plugins, From 802578a78e3a68636d698de0eef46560c4a3e2e2 Mon Sep 17 00:00:00 2001 From: Alex Kotliarskyi Date: Tue, 8 Sep 2015 11:45:03 -0700 Subject: [PATCH 9/9] [ReactNative] Pipe `platform` option all the way to the asset server --- react-packager/src/AssetServer/index.js | 4 ++-- react-packager/src/Bundler/index.js | 10 +++++----- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/react-packager/src/AssetServer/index.js b/react-packager/src/AssetServer/index.js index 5e4c1127..5783e991 100644 --- a/react-packager/src/AssetServer/index.js +++ b/react-packager/src/AssetServer/index.js @@ -51,14 +51,14 @@ class AssetServer { }); } - getAssetData(assetPath) { + getAssetData(assetPath, platform = null) { const nameData = getAssetDataFromName(assetPath); const data = { name: nameData.name, type: nameData.type, }; - return this._getAssetRecord(assetPath).then(record => { + return this._getAssetRecord(assetPath, platform).then(record => { data.scales = record.scales; return Promise.all( diff --git a/react-packager/src/Bundler/index.js b/react-packager/src/Bundler/index.js index 8f76ce10..5f2ead1c 100644 --- a/react-packager/src/Bundler/index.js +++ b/react-packager/src/Bundler/index.js @@ -154,7 +154,7 @@ class Bundler { bundle.setMainModuleId(result.mainModuleId); return Promise.all( result.dependencies.map( - module => this._transformModule(bundle, module).then(transformed => { + module => this._transformModule(bundle, module, platform).then(transformed => { if (bar) { bar.tick(); } @@ -182,13 +182,13 @@ class Bundler { return this._resolver.getDependencies(main, { dev: isDev, platform }); } - _transformModule(bundle, module) { + _transformModule(bundle, module, platform = null) { let transform; if (module.isAsset_DEPRECATED()) { transform = this.generateAssetModule_DEPRECATED(bundle, module); } else if (module.isAsset()) { - transform = this.generateAssetModule(bundle, module); + transform = this.generateAssetModule(bundle, module, platform); } else if (module.isJSON()) { transform = generateJSONModule(module); } else { @@ -243,12 +243,12 @@ class Bundler { }); } - generateAssetModule(bundle, module) { + generateAssetModule(bundle, module, platform = null) { const relPath = getPathRelativeToRoot(this._projectRoots, module.path); return Promise.all([ sizeOf(module.path), - this._assetServer.getAssetData(relPath), + this._assetServer.getAssetData(relPath, platform), ]).then(function(res) { const dimensions = res[0]; const assetData = res[1];