Building Expo Snack
Hello!
I’m Satyajit Sahoo
callstack.io
@satya164
What is Snack
A playground for React Native
Expectations
RNPlay
A simple concept
What if we could just patch-in code that’s changing instead of rebundling every time?
Code from Editor
Patched with Eval
Snack runtime
Challenges
Babel Standalone
Transpile Code on the Fly
let transformedCode = null, error = null;
try {
transformedCode = babelStandalone.transform(
codeFromEditor, {
presets: [
'es2015',
'React',
'Expo'
],
}).code;
} catch (e) {
error = e;
}
Wrap to provide modules support
(function(require, module, exports) {
exports = {};
module = {exports: exports};
// Transformed code from previous slide goes here
return module;
})(function(name) {
var modules = global['$$$___Snack_Modules___$$$'];
if (name in modules) return modules[name];
throw new Error("Trying to import unknown module '" + name + "'");
});
Eval to get exported module
let m;
try {
if (wrappedCode)
m = eval(wrappedCode);
} catch (e) {
error = e;
}
let MyComponent = m;
if (m && m.exports && m.exports.__esModule) {
MyComponent = m.exports.default;
} else if (m && m.exports) {
MyComponent = m.exports;
} else {
MyComponent = undefined;
}
Eval to get exported module
let m;
try {
if (wrappedCode)
m = eval(wrappedCode);
} catch (e) {
error = e;
}
let MyComponent = m;
if (m && m.exports && m.exports.__esModule) {
MyComponent = m.exports.default;
} else if (m && m.exports) {
MyComponent = m.exports;
} else {
MyComponent = undefined;
}
Patch to catch errors
Object.getOwnPropertyNames(MyComponent).forEach(prop => {
const method = MyComponent[prop];
if (method && method.constructor && method.call && method.apply) {
const old = MyComponent[prop], _this = this;
MyComponent[prop] = function(...args) {
try {
const result = old.apply(this, args);
if (prop === 'render') _this.props.onError(null);
return result;
} catch (error) {
return _this._renderError(error);
}
};
}
});
Patch to catch errors
Object.getOwnPropertyNames(MyComponent).forEach(prop => {
const method = MyComponent[prop];
if (method && method.constructor && method.call && method.apply) {
const old = MyComponent[prop], _this = this;
MyComponent[prop] = function(...args) {
try {
const result = old.apply(this, args);
if (prop === 'render') _this.props.onError(null);
return result;
} catch (error) {
return _this._renderError(error);
}
};
}
});
Shortcomings
Import from NPM
Bundling
Asset management
var AssetRegistry = require('AssetRegistry');
module.exports = AssetRegistry.registerAsset({
httpServerLocation: 'https://s3.../',
name: 'doge.gif,
width: 200,
height: 200,
type: 'gif',
hash: [...],
fileHashes: [...],
scales: [1, 1.5, 2],
});
Metro Bundler
Metro Bundler
Snackager
bundle/
[name]@[version]
?platforms=
ios,android
Fetch metadata from npm
Build a fresh bundle and upload to S3, return it
Check S3 for existing bundle, if exists return it
The Editor
Usecases
native.directory
Future
Thank you