Skip to content

Commit

Permalink
Support non-image assets in packager
Browse files Browse the repository at this point in the history
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
  • Loading branch information
nicklockwood authored and facebook-github-bot-7 committed Feb 4, 2016
1 parent 0e0f20c commit 81fb985
Show file tree
Hide file tree
Showing 8 changed files with 119 additions and 9 deletions.
2 changes: 1 addition & 1 deletion .flowconfig
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
60 changes: 59 additions & 1 deletion Examples/UIExplorer/WebViewExample.js
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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';
Expand All @@ -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 {
Expand Down
25 changes: 25 additions & 0 deletions Examples/UIExplorer/helloworld.html
Original file line number Diff line number Diff line change
@@ -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>
7 changes: 6 additions & 1 deletion Libraries/Components/WebView/WebView.android.js
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand Down Expand Up @@ -98,6 +99,10 @@ var WebView = React.createClass({
*/
baseUrl: PropTypes.string,
}),
/*
* Used internally by packager.
*/
PropTypes.number,
]),

/**
Expand Down Expand Up @@ -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}
Expand Down
8 changes: 7 additions & 1 deletion Libraries/Components/WebView/WebView.ios.js
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -138,6 +139,10 @@ var WebView = React.createClass({
*/
baseUrl: PropTypes.string,
}),
/*
* Used internally by packager.
*/
PropTypes.number,
]),

/**
Expand Down Expand Up @@ -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}
Expand Down Expand Up @@ -432,6 +437,7 @@ var styles = StyleSheet.create({
flex: 1,
justifyContent: 'center',
alignItems: 'center',
height: 100,
},
webView: {
backgroundColor: '#ffffff',
Expand Down
7 changes: 6 additions & 1 deletion local-cli/server/runServer.js
Original file line number Diff line number Diff line change
Expand Up @@ -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,
});
Expand Down
12 changes: 9 additions & 3 deletions packager/react-packager/src/Bundler/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -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];
Expand All @@ -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,
Expand Down
7 changes: 6 additions & 1 deletion packager/react-packager/src/Server/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -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

This comment has been minimized.

Copy link
@gre

gre Feb 6, 2016

Contributor

Does this mean any extension not in this list will be unsupported? Is there a default catchall mecanism? For instance I would love to have support for .frag (fragment shader) or any .txt file (just as row text).

],
},
transformTimeoutInterval: {
type: 'number',
Expand Down

3 comments on commit 81fb985

@nicklockwood
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@gre There's currently no way to add a catch-all AFAICT. Feel free to add a PR with any additional extensions you want. Unfortunately they have to be added in three places at the moment (two js files, and the flow regex), but hopefully we can find a DRYer way to extend the list at some point.

@nicklockwood
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@gre note that require() for assets just returns the uri inside an object, it doesn't return the actual data - you'd still have to load that yourself if you aren't displaying the asset in a <WebView/> or <Image/> tag. You'll also need to unwrap the result using resolveAssetSource() if you want to pass the raw uri to an XMLHttpRequest, etc.

For text or shader code, you might be better off embedding them as a string literal in a .js file, and requiring that, so you get the raw data directly.

@gre
Copy link
Contributor

@gre gre commented on 81fb985 Feb 6, 2016

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ok make sense, thanks for your answer. I guess for now it's fine to just use .js as you said. I wish eventually we could have a way to have an extensible system (like for webpack loaders) so we can have third party plugins for specific logic for a given use-case (for instance using glslify https://github.com/stackgl/glslify-loader for shader code)

Please sign in to comment.