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
install
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.
by Edward Bond