Support non-image assets in packager

Summary:
public
The packager currently assumes that all assets that are not JSON or JS files must be images. Although it is possible to add other extension types, they crash the packager if you try to require them, because it attempts to get their dimensions, assuming that they are an image.

This is a crude workaround for that problem, which skips the image-specific processing for non-image assets, but really it would be better if the packager was properly aware of different asset types and treated them differently (e.g. for sounds it could include the duration, for HTML pages it could parse and include linked CSS files, etc).

I've also added an example of using `require('...')` to load a packager-managed HTML page in the UIExplorer WebView example. In future I anticipate that all static asset types (sounds, fonts, etc.) could be handled in this way, which allows them to be edited or added/removed on the fly instead of needing to restart the app.

Reviewed By: martinbigio

Differential Revision: D2895619

fb-gh-sync-id: cd93794ca66bad838621cd7df3ff3c62b5645e85
This commit is contained in:
Nick Lockwood 2016-02-03 17:30:01 -08:00 коммит произвёл facebook-github-bot-7
Родитель 0e0f20c806
Коммит 81fb985335
8 изменённых файлов: 119 добавлений и 9 удалений

Просмотреть файл

@ -51,7 +51,7 @@ module.system=haste
munge_underscores=true
module.name_mapper='^image![a-zA-Z0-9$_-]+$' -> 'GlobalImageStub'
module.name_mapper='^[./a-zA-Z0-9$_-]+\.png$' -> 'RelativeImageStub'
module.name_mapper='^[./a-zA-Z0-9$_-]+\.\(bmp\|gif\|jpg\|jpeg\|png\|psd\|svg\|webp\|m4v\|mov\|mp4\|mpeg\|mpg\|webm\|aac\|aiff\|caf\|m4a\|mp3\|wav\|html\)$' -> 'RelativeImageStub'
suppress_type=$FlowIssue
suppress_type=$FlowFixMe

Просмотреть файл

@ -50,7 +50,7 @@ var WebViewExample = React.createClass({
handleTextInputChange: function(event) {
var url = event.nativeEvent.text;
if (!/^[a-zA-Z-_]:/.test(url)) {
if (!/^[a-zA-Z-_]+:/.test(url)) {
url = 'http://' + url;
}
this.inputText = url;
@ -231,6 +231,34 @@ var styles = StyleSheet.create({
},
});
const HTML = `
<!DOCTYPE html>\n
<html>
<head>
<title>Hello Static World</title>
<meta http-equiv="content-type" content="text/html; charset=utf-8">
<meta name="viewport" content="width=320, user-scalable=no">
<style type="text/css">
body {
margin: 0;
padding: 0;
font: 62.5% arial, sans-serif;
background: #ccc;
}
h1 {
padding: 45px;
margin: 0;
text-align: center;
color: #33f;
}
</style>
</head>
<body>
<h1>Hello Static World</h1>
</body>
</html>
`;
exports.displayName = (undefined: ?string);
exports.title = '<WebView>';
exports.description = 'Base component to display web content';
@ -239,6 +267,36 @@ exports.examples = [
title: 'Simple Browser',
render(): ReactElement { return <WebViewExample />; }
},
{
title: 'Bundled HTML',
render(): ReactElement {
return (
<WebView
style={{
backgroundColor: BGWASH,
height: 100,
}}
source={require('./helloworld.html')}
scalesPageToFit={true}
/>
);
}
},
{
title: 'Static HTML',
render(): ReactElement {
return (
<WebView
style={{
backgroundColor: BGWASH,
height: 100,
}}
source={{html: HTML}}
scalesPageToFit={true}
/>
);
}
},
{
title: 'POST Test',
render(): ReactElement {

Просмотреть файл

@ -0,0 +1,25 @@
<!DOCTYPE html>
<html>
<head>
<title>Hello Bundled World</title>
<meta http-equiv="content-type" content="text/html; charset=utf-8">
<meta name="viewport" content="width=320, user-scalable=no">
<style type="text/css">
body {
margin: 0;
padding: 0;
font: 62.5% arial, sans-serif;
background: #ccc;
}
h1 {
padding: 45px;
margin: 0;
text-align: center;
color: #f33;
}
</style>
</head>
<body>
<h1>Hello Bundled World</h1>
</body>
</html>

Просмотреть файл

@ -21,6 +21,7 @@ var deprecatedPropType = require('deprecatedPropType');
var keyMirror = require('keyMirror');
var merge = require('merge');
var requireNativeComponent = require('requireNativeComponent');
var resolveAssetSource = require('resolveAssetSource');
var PropTypes = React.PropTypes;
@ -98,6 +99,10 @@ var WebView = React.createClass({
*/
baseUrl: PropTypes.string,
}),
/*
* Used internally by packager.
*/
PropTypes.number,
]),
/**
@ -193,7 +198,7 @@ var WebView = React.createClass({
ref={RCT_WEBVIEW_REF}
key="webViewKey"
style={webViewStyles}
source={source}
source={resolveAssetSource(source)}
injectedJavaScript={this.props.injectedJavaScript}
userAgent={this.props.userAgent}
javaScriptEnabled={javaScriptEnabled}

Просмотреть файл

@ -25,6 +25,7 @@ var invariant = require('invariant');
var keyMirror = require('keyMirror');
var processDecelerationRate = require('processDecelerationRate');
var requireNativeComponent = require('requireNativeComponent');
var resolveAssetSource = require('resolveAssetSource');
var PropTypes = React.PropTypes;
var RCTWebViewManager = require('NativeModules').WebViewManager;
@ -138,6 +139,10 @@ var WebView = React.createClass({
*/
baseUrl: PropTypes.string,
}),
/*
* Used internally by packager.
*/
PropTypes.number,
]),
/**
@ -304,7 +309,7 @@ var WebView = React.createClass({
ref={RCT_WEBVIEW_REF}
key="webViewKey"
style={webViewStyles}
source={source}
source={resolveAssetSource(source)}
injectedJavaScript={this.props.injectedJavaScript}
bounces={this.props.bounces}
scrollEnabled={this.props.scrollEnabled}
@ -432,6 +437,7 @@ var styles = StyleSheet.create({
flex: 1,
justifyContent: 'center',
alignItems: 'center',
height: 100,
},
webView: {
backgroundColor: '#ffffff',

Просмотреть файл

@ -75,7 +75,12 @@ function getPackagerServer(args, config) {
getTransformOptionsModulePath: config.getTransformOptionsModulePath,
transformModulePath: transformerPath,
assetRoots: args.assetRoots,
assetExts: ['png', 'jpg', 'jpeg', 'bmp', 'gif', 'webp'],
assetExts: [
'bmp', 'gif', 'jpg', 'jpeg', 'png', 'psd', 'svg', 'webp', // Image formats
'm4v', 'mov', 'mp4', 'mpeg', 'mpg', 'webm', // Video formats
'aac', 'aiff', 'caf', 'm4a', 'mp3', 'wav', // Audio formats
'html', // Document formats
],
resetCache: args.resetCache || args['reset-cache'],
verbose: args.verbose,
});

12
packager/react-packager/src/Bundler/index.js поставляемый
Просмотреть файл

@ -554,8 +554,14 @@ class Bundler {
assetUrlPath = assetUrlPath.replace(/\\/g, '/');
}
// Test extension against all types supported by image-size module.
// If it's not one of these, we won't treat it as an image.
let isImage = [
'png', 'jpg', 'jpeg', 'bmp', 'gif', 'webp', 'psd', 'svg', 'tiff'
].indexOf(path.extname(module.path).slice(1)) !== -1;
return Promise.all([
sizeOf(module.path),
isImage ? sizeOf(module.path) : null,
this._assetServer.getAssetData(relPath, platform),
]).then(function(res) {
const dimensions = res[0];
@ -564,8 +570,8 @@ class Bundler {
__packager_asset: true,
fileSystemLocation: path.dirname(module.path),
httpServerLocation: assetUrlPath,
width: dimensions.width / module.resolution,
height: dimensions.height / module.resolution,
width: dimensions ? dimensions.width / module.resolution : undefined,
height: dimensions ? dimensions.height / module.resolution : undefined,
scales: assetData.scales,
files: assetData.files,
hash: assetData.hash,

7
packager/react-packager/src/Server/index.js поставляемый
Просмотреть файл

@ -58,7 +58,12 @@ const validateOpts = declareOpts({
},
assetExts: {
type: 'array',
default: ['png', 'jpg', 'jpeg', 'bmp', 'gif', 'webp'],
default: [
'bmp', 'gif', 'jpg', 'jpeg', 'png', 'psd', 'svg', 'webp', // Image formats
'm4v', 'mov', 'mp4', 'mpeg', 'mpg', 'webm', // Video formats
'aac', 'aiff', 'caf', 'm4a', 'mp3', 'wav', // Audio formats
'html', // Document formats
],
},
transformTimeoutInterval: {
type: 'number',