How to Deploy an Angular 2/Rails 5 App to Heroku

Initializing the App

The first step will be to initialize a new Rails 5 project. I’m calling mine rails_5_angular_2_deployment_example.
For the front-end we’ll use angular2-seed. Clone the repo into a subdirectory called client. (The name client, in this case, is important. If you call it something else, the deployment won’t work.)

We’re just interested in the angular2-seed code. If we keep it as a repository inside our project’s repository, there will be problems. Kill client/.git.

Now install the Node packages.
Before we try to deploy our app we’ll want to make sure it works locally. Run the following command to do a build.
This command will create a directory called client/dist/prod. If we want Rails to be able to serve something out of there, we’ll have to tell Rails about it somehow. We can do this by simply symlinking public/ to client/dist/prod/. Rails will look for public/index.html and find client/dist/prod/index.html and we’ll be in business.
Start the Rails server. If you navigate to localhost:3000 you should see “Howdy! Here’s a list of awesome computer scientists…”

Creating the Heroku App

The first step is somewhat self-explanatory:
We’ll need to use two buildpacks for this deployment. If we were deploying a plain old Rails app, Heroku would auto-detect Ruby and use the heroku/ruby buildpack. We also need a Node buildpack because in order to build our front-end app we need to run a Gulp command and in order to run Gulp commands we need to have Node packages installed.
We can tell Heroku about our two buildpacks like this:
The order matters. If we were to put the Ruby buildpack first and the Node buildpack second, Heroku would give us a single Node dyno which wouldn’t know how to run Ruby. By putting the Ruby buildpack second we get two dynos: a web dyno and a worker dyno, both of which know how to run Ruby.
The reason we’re using my https://github.com/jasonswett/heroku-buildpack-nodejs buildpack instead of the Heroku version is that I needed to modify the buildpack to look for package.json inside of the client directory instead of at the project root.
We need to do one last thing before we can push our code. Modify client/package.json to look like this:

{
  "name": "angular2-seed",
  "version": "0.0.0",
  "description": "Seed for Angular 2 apps",
  "repository": {
    "url": "https://github.com/mgechev/angular2-seed"
  },
  "scripts": {
    "build.dev": "gulp build.dev --color",
    "build.dev.watch": "gulp build.dev.watch --color",
    "build.e2e": "gulp build.e2e --color",
    "build.prod": "gulp build.prod --color",
    "build.test": "gulp build.test --color",
    "build.test.watch": "gulp build.test.watch --color",
    "docs": "npm run gulp -- build.docs --color && npm run gulp -- serve.docs --color",
    "e2e": "protractor",
    "e2e.live": "protractor --elementExplorer",
    "gulp": "gulp",
    "karma": "karma",
    "karma.start": "karma start",
    "postinstall": "typings install && gulp check.versions && npm prune && gulp build.prod",
    "reinstall": "npm cache clean && npm install",
    "serve.coverage": "remap-istanbul -b src/ -i coverage/coverage-final.json -o coverage -t html && npm run gulp -- serve.coverage --color",
    "serve.dev": "gulp serve.dev --color",
    "serve.e2e": "gulp serve.e2e --color",
    "serve.prod": "gulp serve.prod --color",
    "start": "gulp serve.dev --color",
    "tasks.list": "gulp --tasks-simple --color",
    "test": "gulp test --color",
    "webdriver-start": "webdriver-manager start",
    "webdriver-update": "webdriver-manager update"
  },
  "author": "Minko Gechev <mgechev>",
  "license": "MIT",
  "devDependencies": {
  },
  "dependencies": {
    "angular2": "2.0.0-beta.15",
    "es6-module-loader": "^0.17.8",
    "es6-promise": "^3.1.2",
    "es6-shim": "0.35.0",
    "reflect-metadata": "0.1.2",
    "rxjs": "5.0.0-beta.2",
    "systemjs": "~0.19.25",
    "zone.js": "^0.6.10",
    "async": "^1.4.2",
    "autoprefixer": "^6.3.3",
    "browser-sync": "^2.11.2",
    "chalk": "^1.1.3",
    "codelyzer": "0.0.12",
    "colorguard": "^1.1.1",
    "connect": "^3.4.1",
    "connect-history-api-fallback": "^1.1.0",
    "connect-livereload": "^0.5.3",
    "cssnano": "^3.5.2",
    "doiuse": "^2.3.0",
    "event-stream": "^3.3.2",
    "express": "~4.13.1",
    "express-history-api-fallback": "^2.0.0",
    "extend": "^3.0.0",
    "gulp": "^3.9.1",
    "gulp-cached": "^1.1.0",
    "gulp-concat": "^2.6.0",
    "gulp-filter": "^4.0.0",
    "gulp-inject": "^4.0.0",
    "gulp-inline-ng2-template": "^1.1.2",
    "gulp-load-plugins": "^1.2.0",
    "gulp-plumber": "~1.1.0",
    "gulp-postcss": "^6.1.0",
    "gulp-shell": "~0.5.2",
    "gulp-sourcemaps": "git+https://github.com/floridoo/gulp-sourcemaps.git#master",
    "gulp-template": "^3.1.0",
    "gulp-tslint": "^4.3.3",
    "gulp-typedoc": "^1.2.1",
    "gulp-typescript": "~2.12.1",
    "gulp-uglify": "^1.5.3",
    "gulp-util": "^3.0.7",
    "gulp-watch": "^4.3.5",
    "is-ci": "^1.0.8",
    "isstream": "^0.1.2",
    "jasmine-core": "~2.4.1",
    "jasmine-spec-reporter": "^2.4.0",
    "karma": "~0.13.22",
    "karma-chrome-launcher": "~0.2.2",
    "karma-coverage": "^0.5.5",
    "karma-ie-launcher": "^0.2.0",
    "karma-jasmine": "~0.3.8",
    "karma-mocha-reporter": "^2.0.0",
    "karma-phantomjs-launcher": "^1.0.0",
    "merge-stream": "^1.0.0",
    "open": "0.0.5",
    "phantomjs-prebuilt": "^2.1.4",
    "postcss-reporter": "^1.3.3",
    "protractor": "^3.0.0",
    "remap-istanbul": "git+https://github.com/SitePen/remap-istanbul.git#master",
    "rimraf": "^2.5.2",
    "run-sequence": "^1.1.0",
    "semver": "^5.1.0",
    "serve-static": "^1.10.2",
    "slash": "~1.0.0",
    "stream-series": "^0.1.1",
    "stylelint": "^5.3.0",
    "stylelint-config-standard": "^5.0.0",
    "systemjs-builder": "^0.15.14",
    "tiny-lr": "^0.2.1",
    "traceur": "^0.0.91",
    "ts-node": "^0.7.1",
    "tslint": "^3.7.0-dev.2",
    "tslint-stylish": "2.1.0-beta",
    "typedoc": "^0.3.12",
    "typescript": "~1.8.10",
    "typings": "^0.7.12",
    "vinyl-buffer": "^1.0.0",
    "vinyl-source-stream": "^1.1.0",
    "yargs": "^4.2.0"
  }
}

We’ve done two things here. First, we added gulp build.prod to the postinstall script. This will force Heroku to do a build as part of the deployment process. Second, we move everything from devDependencies to dependencies. Since it’s a production environment, Node won’t pick up the devDependencies, but we need those.
By the way, why not just build locally, commit the generated code, and push that? You could do that and it would work. The reason I didn’t want to is that it’s not a good idea to commit build artifacts to version control. You end up with a bunch of “changed” files every time you do a build, which not only doesn’t make sense but serves as a distraction. I’ve worked on projects before that commit build artifacts to version control and it has been painful.
After all our changes are committed we can do a push:

Now open the app in Heroku.
You should see the same Angular 2 Seed app in the production environment. Obviously, the Angular app isn’t talking to Rails but it is coexisting with it, and that’s the hard part.

Comments

Popular posts from this blog

Installing Wowza Streaming Engine on ubuntu

Highcharts with grouped categories

Completely Uninstall and Install Rubymine on ubuntu