Updates from Tue 8 Sep

This commit is contained in:
Christopher Chedeau 2015-09-08 16:54:44 -07:00
commit 1a428c2c90
18 changed files with 475 additions and 103 deletions

View File

@ -59,6 +59,14 @@ var options = parseCommandLine([{
type: 'string', type: 'string',
default: require.resolve('./transformer.js'), default: require.resolve('./transformer.js'),
description: 'Specify a custom transformer to be used (absolute path)' 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) { if (options.projectRoots) {
@ -229,6 +237,7 @@ function getAppMiddleware(options) {
transformModulePath: transformerPath, transformModulePath: transformerPath,
assetRoots: options.assetRoots, assetRoots: options.assetRoots,
assetExts: ['png', 'jpeg', 'jpg'], assetExts: ['png', 'jpeg', 'jpg'],
resetCache: options.resetCache || options['reset-cache'],
polyfillModuleNames: [ polyfillModuleNames: [
require.resolve( require.resolve(
'../Libraries/JavaScriptAppEngine/polyfills/document.js' '../Libraries/JavaScriptAppEngine/polyfills/document.js'

View File

@ -1,6 +1,7 @@
'use strict'; 'use strict';
jest jest
.dontMock('../../lib/getPlatformExtension')
.dontMock('../../lib/getAssetDataFromName') .dontMock('../../lib/getAssetDataFromName')
.dontMock('../'); .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', () => { pit('should work for the simple case with jpg', () => {
const server = new AssetServer({ const server = new AssetServer({
projectRoots: ['/root'], 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', () => { pit('should support multiple project roots', () => {
const server = new AssetServer({ const server = new AssetServer({
projectRoots: ['/root', '/root2'], projectRoots: ['/root', '/root2'],

View File

@ -20,7 +20,6 @@ const stat = Promise.denodeify(fs.stat);
const readDir = Promise.denodeify(fs.readdir); const readDir = Promise.denodeify(fs.readdir);
const readFile = Promise.denodeify(fs.readFile); const readFile = Promise.denodeify(fs.readFile);
const validateOpts = declareOpts({ const validateOpts = declareOpts({
projectRoots: { projectRoots: {
type: 'array', type: 'array',
@ -39,9 +38,9 @@ class AssetServer {
this._assetExts = opts.assetExts; this._assetExts = opts.assetExts;
} }
get(assetPath) { get(assetPath, platform = null) {
const assetData = getAssetDataFromName(assetPath); 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++) { for (let i = 0; i < record.scales.length; i++) {
if (record.scales[i] >= assetData.resolution) { if (record.scales[i] >= assetData.resolution) {
return readFile(record.files[i]); return readFile(record.files[i]);
@ -52,14 +51,14 @@ class AssetServer {
}); });
} }
getAssetData(assetPath) { getAssetData(assetPath, platform = null) {
const nameData = getAssetDataFromName(assetPath); const nameData = getAssetDataFromName(assetPath);
const data = { const data = {
name: nameData.name, name: nameData.name,
type: nameData.type, type: nameData.type,
}; };
return this._getAssetRecord(assetPath).then(record => { return this._getAssetRecord(assetPath, platform).then(record => {
data.scales = record.scales; data.scales = record.scales;
return Promise.all( return Promise.all(
@ -85,9 +84,10 @@ class AssetServer {
* 1. We first parse the directory of the asset * 1. We first parse the directory of the asset
* 2. We check to find a matching directory in one of the project roots * 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 * 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); const filename = path.basename(assetPath);
return ( return (
@ -104,11 +104,20 @@ class AssetServer {
const files = res[1]; const files = res[1];
const assetData = getAssetDataFromName(filename); const assetData = getAssetDataFromName(filename);
const map = this._buildAssetMap(dir, files); const map = this._buildAssetMap(dir, files, platform);
const record = map[assetData.assetName];
let record;
if (platform != null){
record = map[getAssetKey(assetData.assetName, platform)] ||
map[assetData.assetName];
} else {
record = map[assetData.assetName];
}
if (!record) { if (!record) {
throw new Error('Asset not found'); throw new Error(
`Asset not found: ${assetPath} for platform: ${platform}`
);
} }
return record; return record;
@ -141,9 +150,10 @@ class AssetServer {
const map = Object.create(null); const map = Object.create(null);
assets.forEach(function(asset, i) { assets.forEach(function(asset, i) {
const file = files[i]; const file = files[i];
let record = map[asset.assetName]; const assetKey = getAssetKey(asset.assetName, asset.platform);
let record = map[assetKey];
if (!record) { if (!record) {
record = map[asset.assetName] = { record = map[assetKey] = {
scales: [], scales: [],
files: [], files: [],
}; };
@ -151,6 +161,7 @@ class AssetServer {
let insertIndex; let insertIndex;
const length = record.scales.length; const length = record.scales.length;
for (insertIndex = 0; insertIndex < length; insertIndex++) { for (insertIndex = 0; insertIndex < length; insertIndex++) {
if (asset.resolution < record.scales[insertIndex]) { if (asset.resolution < record.scales[insertIndex]) {
break; break;
@ -164,4 +175,12 @@ class AssetServer {
} }
} }
function getAssetKey(assetName, platform) {
if (platform != null) {
return `${assetName} : ${platform}`;
} else {
return assetName;
}
}
module.exports = AssetServer; module.exports = AssetServer;

View File

@ -115,13 +115,18 @@ class Bundle {
getMinifiedSourceAndMap() { getMinifiedSourceAndMap() {
this._assertFinalized(); this._assertFinalized();
if (this._minifiedSourceAndMap) {
return this._minifiedSourceAndMap;
}
const source = this._getSource(); const source = this._getSource();
try { try {
return UglifyJS.minify(source, { this._minifiedSourceAndMap = UglifyJS.minify(source, {
fromString: true, fromString: true,
outSourceMap: 'bundle.js', outSourceMap: 'bundle.js',
inSourceMap: this.getSourceMap(), inSourceMap: this.getSourceMap(),
}); });
return this._minifiedSourceAndMap;
} catch(e) { } catch(e) {
// Sometimes, when somebody is using a new syntax feature that we // Sometimes, when somebody is using a new syntax feature that we
// don't yet have transform for, the untransformed line is sent to // don't yet have transform for, the untransformed line is sent to
@ -186,6 +191,10 @@ class Bundle {
options = options || {}; options = options || {};
if (options.minify) {
return this.getMinifiedSourceAndMap().map;
}
if (this._shouldCombineSourceMaps) { if (this._shouldCombineSourceMaps) {
return this._getCombinedSourceMaps(options); return this._getCombinedSourceMaps(options);
} }

View File

@ -154,7 +154,7 @@ class Bundler {
bundle.setMainModuleId(result.mainModuleId); bundle.setMainModuleId(result.mainModuleId);
return Promise.all( return Promise.all(
result.dependencies.map( result.dependencies.map(
module => this._transformModule(bundle, module).then(transformed => { module => this._transformModule(bundle, module, platform).then(transformed => {
if (bar) { if (bar) {
bar.tick(); bar.tick();
} }
@ -182,13 +182,13 @@ class Bundler {
return this._resolver.getDependencies(main, { dev: isDev, platform }); return this._resolver.getDependencies(main, { dev: isDev, platform });
} }
_transformModule(bundle, module) { _transformModule(bundle, module, platform = null) {
let transform; let transform;
if (module.isAsset_DEPRECATED()) { if (module.isAsset_DEPRECATED()) {
transform = this.generateAssetModule_DEPRECATED(bundle, module); transform = this.generateAssetModule_DEPRECATED(bundle, module);
} else if (module.isAsset()) { } else if (module.isAsset()) {
transform = this.generateAssetModule(bundle, module); transform = this.generateAssetModule(bundle, module, platform);
} else if (module.isJSON()) { } else if (module.isJSON()) {
transform = generateJSONModule(module); transform = generateJSONModule(module);
} else { } else {
@ -243,12 +243,12 @@ class Bundler {
}); });
} }
generateAssetModule(bundle, module) { generateAssetModule(bundle, module, platform = null) {
const relPath = getPathRelativeToRoot(this._projectRoots, module.path); const relPath = getPathRelativeToRoot(this._projectRoots, module.path);
return Promise.all([ return Promise.all([
sizeOf(module.path), sizeOf(module.path),
this._assetServer.getAssetData(relPath), this._assetServer.getAssetData(relPath, platform),
]).then(function(res) { ]).then(function(res) {
const dimensions = res[0]; const dimensions = res[0];
const assetData = res[1]; const assetData = res[1];

View File

@ -16,6 +16,7 @@ jest
.dontMock('../../crawlers') .dontMock('../../crawlers')
.dontMock('../../crawlers/node') .dontMock('../../crawlers/node')
.dontMock('../../replacePatterns') .dontMock('../../replacePatterns')
.dontMock('../../../lib/getPlatformExtension')
.dontMock('../../../lib/getAssetDataFromName') .dontMock('../../../lib/getAssetDataFromName')
.dontMock('../../fastfs') .dontMock('../../fastfs')
.dontMock('../../AssetModule_DEPRECATED') .dontMock('../../AssetModule_DEPRECATED')
@ -424,6 +425,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() { pit('Deprecated and relative assets can live together', function() {
var root = '/root'; var root = '/root';
fs.__setMockFilesystem({ fs.__setMockFilesystem({

View File

@ -1,4 +1,4 @@
/** /**
* Copyright (c) 2015-present, Facebook, Inc. * Copyright (c) 2015-present, Facebook, Inc.
* All rights reserved. * All rights reserved.
* *
@ -17,6 +17,7 @@ const crawl = require('../crawlers');
const debug = require('debug')('DependencyGraph'); const debug = require('debug')('DependencyGraph');
const declareOpts = require('../../lib/declareOpts'); const declareOpts = require('../../lib/declareOpts');
const getAssetDataFromName = require('../../lib/getAssetDataFromName'); const getAssetDataFromName = require('../../lib/getAssetDataFromName');
const getPontentialPlatformExt = require('../../lib/getPlatformExtension');
const isAbsolutePath = require('absolute-path'); const isAbsolutePath = require('absolute-path');
const path = require('path'); const path = require('path');
const util = require('util'); const util = require('util');
@ -274,7 +275,7 @@ class DependencyGraph {
// `platformExt` could be set in the `setup` method. // `platformExt` could be set in the `setup` method.
if (!this._platformExt) { if (!this._platformExt) {
const platformExt = getPlatformExt(entryPath); const platformExt = getPontentialPlatformExt(entryPath);
if (platformExt && this._opts.platforms.indexOf(platformExt) > -1) { if (platformExt && this._opts.platforms.indexOf(platformExt) > -1) {
this._platformExt = platformExt; this._platformExt = platformExt;
} else { } else {
@ -390,12 +391,18 @@ class DependencyGraph {
return Promise.resolve().then(() => { return Promise.resolve().then(() => {
if (this._isAssetFile(potentialModulePath)) { if (this._isAssetFile(potentialModulePath)) {
const {name, type} = getAssetDataFromName(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 // We arbitrarly grab the first one, because scale selection
// will happen somewhere // will happen somewhere
const [assetFile] = this._fastfs.matches( const [assetFile] = this._fastfs.matches(
path.dirname(potentialModulePath), path.dirname(potentialModulePath),
pattern new RegExp(pattern)
); );
if (assetFile) { if (assetFile) {
@ -496,7 +503,7 @@ class DependencyGraph {
const modules = this._hasteMap[name]; const modules = this._hasteMap[name];
if (this._platformExt != null) { if (this._platformExt != null) {
for (let i = 0; i < modules.length; i++) { 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]; return modules[i];
} }
} }
@ -662,15 +669,6 @@ function normalizePath(modulePath) {
return modulePath.replace(/\/$/, ''); 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); util.inherits(NotFoundError, Error);
module.exports = DependencyGraph; module.exports = DependencyGraph;

View File

@ -245,8 +245,16 @@ describe('processRequest', () => {
expect(res.end).toBeCalledWith('i am image'); 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');
}); });
}); });

View File

@ -278,7 +278,8 @@ class Server {
_processAssetsRequest(req, res) { _processAssetsRequest(req, res) {
const urlObj = url.parse(req.url, true); const urlObj = url.parse(req.url, true);
const assetPath = urlObj.pathname.match(/^\/assets\/(.+)$/); 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( .then(
data => res.end(data), data => res.end(data),
error => { error => {
@ -286,7 +287,7 @@ class Server {
res.writeHead('404'); res.writeHead('404');
res.end('Asset not found'); res.end('Asset not found');
} }
).done(); ).done(() => Activity.endEvent(assetEvent));
} }
_processProfile(req, res) { _processProfile(req, res) {
@ -370,7 +371,9 @@ class Server {
res.end(bundleSource); res.end(bundleSource);
Activity.endEvent(startReqEventId); Activity.endEvent(startReqEventId);
} else if (requestType === 'map') { } 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.setHeader('Content-Type', 'application/json');
res.end(sourceMap); res.end(sourceMap);
Activity.endEvent(startReqEventId); Activity.endEvent(startReqEventId);

View File

@ -12,6 +12,7 @@ const Bundle = require('../Bundler/Bundle');
const Promise = require('promise'); const Promise = require('promise');
const bser = require('bser'); const bser = require('bser');
const debug = require('debug')('ReactPackager:SocketClient'); const debug = require('debug')('ReactPackager:SocketClient');
const fs = require('fs');
const net = require('net'); const net = require('net');
const path = require('path'); const path = require('path');
const tmpdir = require('os').tmpdir(); const tmpdir = require('os').tmpdir();
@ -29,7 +30,16 @@ class SocketClient {
this._sock = net.connect(sockPath); this._sock = net.connect(sockPath);
this._ready = new Promise((resolve, reject) => { this._ready = new Promise((resolve, reject) => {
this._sock.on('connect', () => resolve(this)); 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}`;
if (fs.existsSync(LOG_PATH)) {
e.message += '\nServer logs:\n' + fs.readFileSync(LOG_PATH, 'utf8');
}
reject(e);
});
}); });
this._resolvers = Object.create(null); this._resolvers = Object.create(null);

View File

@ -16,6 +16,7 @@ const fs = require('fs');
const net = require('net'); const net = require('net');
const MAX_IDLE_TIME = 30 * 1000; const MAX_IDLE_TIME = 30 * 1000;
const MAX_STARTUP_TIME = 5 * 60 * 1000;
class SocketServer { class SocketServer {
constructor(sockPath, options) { constructor(sockPath, options) {
@ -35,13 +36,15 @@ class SocketServer {
process.on('exit', () => fs.unlinkSync(sockPath)); process.on('exit', () => fs.unlinkSync(sockPath));
}); });
}); });
this._numConnections = 0;
this._server.on('connection', (sock) => this._handleConnection(sock)); this._server.on('connection', (sock) => this._handleConnection(sock));
// Disable the file watcher. // Disable the file watcher.
options.nonPersistent = true; options.nonPersistent = true;
this._packagerServer = new Server(options); this._packagerServer = new Server(options);
this._jobs = 0; this._jobs = 0;
this._dieEventually(); this._dieEventually(MAX_STARTUP_TIME);
} }
onReady() { onReady() {
@ -50,10 +53,13 @@ class SocketServer {
_handleConnection(sock) { _handleConnection(sock) {
debug('connection to server', process.pid); debug('connection to server', process.pid);
this._numConnections++;
sock.on('close', () => this._numConnections--);
const bunser = new bser.BunserBuf(); const bunser = new bser.BunserBuf();
sock.on('data', (buf) => bunser.append(buf)); sock.on('data', (buf) => bunser.append(buf));
bunser.on('value', (m) => this._handleMessage(sock, m)); bunser.on('value', (m) => this._handleMessage(sock, m));
bunser.on('error', (e) => console.error(e));
} }
_handleMessage(sock, m) { _handleMessage(sock, m) {
@ -113,15 +119,15 @@ class SocketServer {
})); }));
} }
_dieEventually() { _dieEventually(delay = MAX_IDLE_TIME) {
clearTimeout(this._deathTimer); clearTimeout(this._deathTimer);
this._deathTimer = setTimeout(() => { this._deathTimer = setTimeout(() => {
if (this._jobs <= 0) { if (this._jobs <= 0 && this._numConnections <= 0) {
debug('server dying', process.pid); debug('server dying', process.pid);
process.exit(); process.exit();
} }
this._dieEventually(); this._dieEventually();
}, MAX_IDLE_TIME); }, delay);
} }
static listenOnServerIPCMessages() { static listenOnServerIPCMessages() {

View File

@ -20,7 +20,7 @@ const path = require('path');
const tmpdir = require('os').tmpdir(); const tmpdir = require('os').tmpdir();
const {spawn} = require('child_process'); const {spawn} = require('child_process');
const CREATE_SERVER_TIMEOUT = 60000; const CREATE_SERVER_TIMEOUT = 5 * 60 * 1000;
const SocketInterface = { const SocketInterface = {
getOrCreateSocketFor(options) { getOrCreateSocketFor(options) {
@ -42,8 +42,16 @@ const SocketInterface = {
if (fs.existsSync(sockPath)) { if (fs.existsSync(sockPath)) {
var sock = net.connect(sockPath); var sock = net.connect(sockPath);
sock.on('connect', () => { sock.on('connect', () => {
SocketClient.create(sockPath).then(
client => {
sock.end(); sock.end();
resolve(SocketClient.create(sockPath)); resolve(client);
},
error => {
sock.end();
reject(error);
}
);
}); });
sock.on('error', (e) => { sock.on('error', (e) => {
try { try {

View File

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

View File

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

View File

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

View File

@ -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'; 'use strict';
var path = require('path'); const path = require('path');
const getPlatformExtension = require('./getPlatformExtension');
function getAssetDataFromName(filename) { 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); const match = filename.match(re);
var resolution; let resolution;
if (!(match && match[1])) { if (!(match && match[1])) {
resolution = 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 { return {
resolution: resolution, resolution: resolution,
assetName: assetName, assetName: assetName,
type: ext.slice(1), type: ext.slice(1),
name: path.basename(assetName, ext) name: path.basename(assetName, ext),
platform: platformExt,
}; };
} }

View File

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

View File

@ -39,6 +39,7 @@ function transform(srcTxt, filename, options) {
'es7.objectRestSpread', 'es7.objectRestSpread',
'flow', 'flow',
'react', 'react',
'react.displayName',
'regenerator', 'regenerator',
], ],
plugins: plugins, plugins: plugins,