CRA + Phoenix

August 15, 2018
post dev

Working on some new projects and found myself again trying to find the best way to integrate a JS frontend with a Phoenix app that renders the login page and some static public pages and then redirects to a JS single page app for the actual app. I have been there before but JS dependency management is hell and I’d rather not go there so I tried out create-react-app which looks like a nice way to get a working react app up and running quickly and maintainable.

To get CRA run:

  yarn create react-app

and add ~/.yarn/bin to your path. After that you can run create-react-app [app name] anywhere on your system

In your phoenix project go into the directory that has your assets folder and remove it. Then run:

  create-react-app project-name
  mv project-name assets

This gives you a working CRA inside the Phoenix assets directory, victory! I was happy till I stumbeled upon that little issue of marrying CRA to Phoenix in a nice way for development that does not require me to eject the app and loose the maintainability aspect of CRA. Turns out I am not the only one that has this issues: https://github.com/facebook/create-react-app/issues/1070 But fortunately the solution is pretty simple: https://www.npmjs.com/package/cra-build-watch

To get cra-build-watch working change into your assets folder and run:

  yarn add -D cra-build-watch

add the watch step tp your package.json

  {
    "scripts": {
      "watch": "cra-build-watch -b ../priv/static -p /[your-path-to-react-app]",
    }
  }

and configure your config/dev.exs to call the build-watcher you just added like this:

  watchers: [
    yarn: [
      "run",
      "watch",
      cd: Path.expand("../assets", __DIR__)
    ]
  ] 

Finally have something like this in your template rendering your react app:

    <div id="root"></div>
    <script type="text/javascript" src="<%= static_path(@conn, "/js/bundle.js") %>"></script>

Done. Next steps are making sure everything works nicely for production builds as well and replace the template logic with a dynamic one that picks up the right file name depending on the environment.