Overview
We will be using Elixir, a little phoenix ( little ), and React 360. You can read up on each here:
It should be noted that it is, at the time of writing this, hard to find good supporting docs for React 360, it has been rebranded from React VR.
Configuring
Elixir
We need to meld two worlds here, but first we need to get a phoenix app up and running. Since we are not using ecto for this we disable that.
mix phx.new phovr --no-ecto
At this point we have our phoenix app up, you can at this point run it via
cd phovr
mix phx.server
to confirm.
Kill the server and lets get it ready for our React 360 configuration.
cd assets
rm -rf *
React 360
npm install -g react-360-cli
inside the assets directory:
react-360 init Hello360
setopt -s glob_dots
mv Hello360/*(N) ./
rmdir Hello360
Make sure you can get it up and running via:
npm start
VR Proxy configuration
Now that we have a React 360 app working, lets enable hot reloading.
We are going to create a proxy file that Elixir can initiate and have the proxy forward requests to our elixir.
in the assets create a proxy file with:
// redirect for api
const express = require('express'); //eslint-disable-line
const proxy = require('http-proxy-middleware'); //eslint-disable-line
const cors = require('cors');
const { spawn } = require('child_process');
const app = express();
app.use(cors());
app.use('/js/*', proxy({
target: 'http://localhost:8081/',
changeOrigin: true,
pathRewrite: {
'^/js': '/',
},
}));
app.use('/images/*', proxy({
target: 'http://localhost:8081/',
changeOrigin: true,
pathRewrite: {
'^/images': '/static_assets',
},
}));
const wsProxy = proxy('ws://localhost:4000', { changeOrigin: true });
app.use('/*', proxy({ target: 'http://localhost:4000', changeOrigin: true }));
app.use('/phoenix/', wsProxy);
app.use('/socket/', wsProxy);
const server = app.listen(4001, '0.0.0.0');
server.on('upgrade', wsProxy.upgrade);
const start = spawn('yarn', ['start']);
start.stdout.on('data', (data) => {
console.log(`stdout: ${data}`);
});
start.stderr.on('data', (data) => {
console.log(`stderr: ${data}`);
});
start.on('close', (code) => {
console.log(`child process exited with code ${code}`);
});
Since these are not dev deps, and outside of the needed scope we install the deps manually.
npm install express http-proxy-middleware cors child_process --dev
This will allow us to setup a redirect to the React 360 system as needed. We will rely on phoenix reload watcher for us to go have it reload.
Dev config
Inside your config directory and your
dev.exs
look for the section around line 14:
watchers: [node: ["node_modules/brunch/bin/brunch", "watch", "--stdin",
cd: Path.expand("../assets", __DIR__)]]
and change it to:
watchers: [
node: [
"proxy.js",
cd: Path.expand("../assets", __DIR__)]
]
Add in a new pattern to watch for:
~r{assets/.*(js|css|png|jpeg|jpg|gif|svg)$},
it should look like this now:
config :phovr, PhovrWeb.Endpoint,
live_reload: [
patterns: [
~r{assets/.*(js|css|png|jpeg|jpg|gif|svg)$},
~r{priv/static/.*(js|css|png|jpeg|jpg|gif|svg)$},
~r{priv/gettext/.*(po)$},
~r{lib/phovr_web/views/.*(ex)$},
~r{lib/phovr_web/templates/.*(eex)$}
]
]
We also need to configure a flag for our dev mode vs prod mode so that we don't watch and hot reload the wrong location.
config :phovr, :js_renderer,
is_prod: false
inside
prod.exs
config :phovr, :js_renderer,
is_prod: true
Layout
We are going to have phoenix tell the client what type of rendering to do.
You need to configure the
layout_view.ex
to render the proper tag as needed.
defmodule PhovrWeb.LayoutView do
use PhovrWeb, :view
def js_script_tag do
if Application.get_env(:phovr, :js_renderer)[:is_prod] do
~s(<script src="/js/client.bundle.js?platform=vr"></script>)
else
~s(<script src="/js/client.bundle?platform=vr"></script>)
end
end
def js_location_tag do
if Application.get_env(:phovr, :js_renderer)[:is_prod] do
"/js/index.bundle.js?platform=vr"
else
"/js/index.bundle?platform=vr&dev=true"
end
end
def css_link_tag do
if Application.get_env(:phovr, :js_renderer)[:is_prod] do
~s(<link rel="stylesheet" type="text/css" href="/css/app.css" media="screen,projection" />)
else
""
end
end
end
app html
For the
app.html.eex
we want to render differently.
<!DOCTYPE html>
<html lang="en">
<head>
<title>PhoVR</title>
<style>body { margin: 0; }</style>
<meta name="viewport" content="width=device-width, initial-scale=1, user-scalable=no">
</head>
<body>
<%= render @view_module, @view_template, assigns %>
<%= {:safe, js_script_tag() } %>
<script>
// Initialize the React 360 application
var getUrl = window.location;
function getAppUrl(){
var url = '<%= {:safe, js_location_tag() } %>';
if (url.match(/^\//)) {
return getUrl.protocol + "//" + getUrl.host + url;
}
return url;
}
React360.init(
getAppUrl(),
document.getElementById('container'),
{
assetRoot: 'images/',
}
);
</script>
</body>
</html>
For the
index.html.eex
we change it to just:
<div id="container"></div>
Running
We are now ready to go back up to the root directory from the cli and run:
mix phx.server
You will then want to load up http://localhost:4001
We do this proxy so that you can test things on remote devices that have to go to different renderers. Go ahead and change the text from "Welcome to React 360" to something else and watch it reload live.
When you close the server you will also need to kill the node process. I just use
killall node
to do it quickly
Packaging.
Because we have to package this puppy up differently and we don't use brunch, here is a quick overview.
cd apps/vr/assets
yarn install --ignore-engines
yarn bundle
mkdir -p ../priv/static/js || true
cp build/* ../priv/static/js
This will push all of the static files to the proper location for any type of packaging you might be doing for deployments.
Comments for Getting started with React 360 and Elixir