Wdrożenie aplikacji Node.js + Babel na Heroku

Node.js nie udostępnia jeszcze wszystkich funkcjonalności, które zostały wprowadzone w najnowszych wersjach standardu ECMAScript. Dla mnie jednym z ciekawszych i najbardziej przydatnych elementów jest możliwość importowania i eksportowania modułów. Niestety w obecnej wersji LTS (8.9.x) nie jest to wspierane. Aby zapewnić wsparcie do nowych właściwości z pomocą przychodzą takie narzędzia jak Babel. Jak jednak rozwijać aplikację Node.js z wykorzystaniem tego kompilatora, a później to wdrożyć na serwer? W tym poście chciałbym przedstawić sposób jak sobie poradzić i uruchomić aplikację na Heroku.

Rozwój aplikacji

Tak więc chcielibyśmy aby aplikacja, która będzie rozwijana w Node.js pozwalała nam na więcej niż jest to dostępne w standardzie. Idealnym rozwiązaniem będzie zastosowanie wspomnianego wcześniej Babela, dzięki któremu będziemy mogli tworzyć aplikację zgodną ze standardami, które zostaną wprowadzone w przyszłości, a samo narzędzie będzie dbało o kompilowanie kodu w taki sposób aby było możliwe jego uruchomienie w obecnej wersji.

Instalacja

Zakładam, że jakiś projekt już jest rozstawiony, więc teraz trzeba doinstalować odpowiednie pakiety z Babel, które pozwolą nam na kompilowanie kodu.

$ npm install --save-dev babel-cli
$ npm install --save-dev babel-preset-env

Pierwszy z nich udostępnia komendy w linii poleceń, drugi z kolei (preset-env) instaluje domyślne ustawienia dla najnowszych dostępnych wersji ECMAScript bez potrzeby definiowania z której wersji chcemy korzystać (ES2015, ES2016 czy ES2017).

Teraz trzeba dodać ustawienia do pliku package.json aby zdefiniować do jakiej wersji Node.js pliki powinny być kompilowane.

"babel": {
  "presets": [
    [
      "env",
      {
        "targets": {
          "node": "8.9"
        }
      }
    ]
  ]
}

Warto jeszcze w tym samym pliku zdefiniować skrypt npm, dzięki któremu w łatwy sposób będzie można skompilować istniejący kod.

"scripts": {
  "build": "babel src -d build"
}

Teraz zawsze gdy wywołamy polecenie npm run build cała aplikacja zostanie przekompilowana do odpowiedniej wersji Node.js i zostanie umieszczona w katalogu build (nie zapomnij dodać go do .gitignore). Teraz wystarczy uruchomić główny plik aplikacji znajdujący się w katalogu z nowo zbudowanymi plikami (node build/index.js).

Automatyzacja

Przedstawiony powyżej przykład działa jak powinien, jednak praca z takimi ustawieniami szybko stanie się męcząca i frustrująca. Mamy 21 wiek, przecież po każdej zmianie nie będziemy kompilować ręcznie naszego kodu z wykorzystaniem polecenia npm run build, wyłączać serwera i włączać go ponownie… Na to oczywiście też jest rozwiązanie w postaci dodatkowego pakietu.

Z pomocą przychodzi paczka nodemon, dzięki której raz uruchomiona aplikacja Node.js jest nasłuchiwana na wszelkie zmiany w kodzie i automatycznie restartowana. Pozwoli to na automatyzację procesu developerskiego…

$ npm install --save-dev nodemon

I teraz w package.json dodajemy kolejny skrypt ("start": "nodemon src/index.js --exec babel-node"), który będzie uruchamiał aplikację w trybie developerskim. Jak można zauważyć od razu zaznaczamy aby aplikacja była kompilowana przez babel-node tak więc w jednym poleceniu załatwiamy kilka rzeczy.

Teraz skrypty w package.json powinny wyglądać jak poniżej (jeśli wszystkie twoje pliki źródłowe znajdują się w katalogu src tak jak w moim wypadku):

"scripts": {
  "build": "babel src -d build",
  "start": "nodemon src/index.js --exec babel-node",
}

Polecenie w npm start uruchomi aplikację i już niczym nie trzeba się przejmować – można spokojnie kodzić aplikacje przyszłości 😉

Testy

Co to za aplikacja bez testów, co nie? No to zainstalujmy jakąś bibliotekę do testowania aplikacji Node.js – ja ostatnio jestem zachwycony biblioteką stworzoną przez Facebooka – jest. Zawiera ona wszystkie niezbędne rzeczy do tworzenia testów i w zasadzie nie trzeba nic konfigurować. Instalujemy i działa – coś pięknego 😉

$ npm install --save-dev jest

Jedynym problemem jest być fakt, że nasza aplikacja może używać elementów, które jeszcze nie weszły oficjalnie do Node.js… Ale nic strasznego! Wystarczy doinstalować jedną paczkę, która zapewni odpowiednie wsparcie – babel-jest.

$ npm install --save-dev babel-jest

Do package.json dodajemy jeszcze skrypt do testów ("test": "jest") i to wszystko. Jeśli w projekcie znajdują się pliki nazwane zgodne z notacją **.spec.js* lub **.test.js* to zostaną zinterpretowane jako plik testowy.

Dla podsumowania tak może teraz wyglądać plik package.json:

{
  "name": "grapgql-nodejs-sandbox",
  "version": "1.0.0",
  "description": "GraphQL node sandbox",
  "main": "src/index.js",
  "scripts": {
    "start": "nodemon src/index.js --exec babel-node",
    "build": "babel src -d build",
    "test": "jest"
  },
  "repository": {
    "type": "git",
    "url": "git+https://github.com/mejt/graphql-nodejs-sandbox.git"
  },
  "author": "Mateusz Książek",
  "license": "ISC",
  "bugs": {
    "url": "https://github.com/mejt/graphql-nodejs-sandbox/issues"
  },
  "engines": {
    "node": "8.9.1"
  },
  "homepage": "https://github.com/mejt/graphql-nodejs-sandbox#readme",
  "dependencies": {
    "express": "^4.16.2",
  },
  "devDependencies": {
    "babel-cli": "^6.26.0",
    "babel-jest": "^22.0.4",
    "babel-preset-env": "^1.6.1",
    "jest": "^22.0.4",
    "nodemon": "^1.12.1",
  },
  "babel": {
    "presets": [
      [
        "env",
        {
          "targets": {
            "node": "8.9"
          }
        }
      ]
    ]
  }
}

Jak widać niewielkim nakładem pracy można stworzyć całkiem dobrze działające środowisko developerskie, które będzie wspierało wydajność programisty.

Wdrożenie na produkcję

Aplikacje nie są tworzone po to żeby istniały tylko w przestrzeni lokalnego komputera i działały w trybie developerskim 😉 W końcu trzeba będzie opublikować swoje „dzieło”. Bardzo dobrym i tanim (a w zasadzie darmowym) sposobem na przetestowanie i zaprezentowanie innym swojej aplikacji jest Heroku. Oczywiście darmowa wersja Heroku nie jest zbyt wydajna i nie nadaje się jako serwer pod produkcyjną aplikację, ale jak już wspominałem – do zaprezentowania postępu prac przed wejściem na głęboki ocean nadaje się idealnie.

Do zapewnienia Continuous Integration wykorzystam narzędzie Travis CI (darmowy dla projektów opensource), które będzie uruchamiało testy aplikacji i po każdej zmianie w branchu master będzie wykonywał automatyczny deployment na nasz serwer w Heroku.

Heroku

Stworzenie serwera w Heroku jest na tyle proste i intuicyjne, że nie ma nawet sensu tego opisywać. Nie trzeba nawet definiować jakiego rodzaju (w jakiej technologii) powinno to być środowisko bo przy pierwszym wdrożeniu zmian serwer automatycznie się dostosuje. Z naszej strony na tę chwilę wymagane jest tylko jedno zadanie, które pozwoli zdefiniować główny plik aplikacji.

W katalogu głównym aplikacji należy dodać plik Procfile, który jest interpretowany przez Heroku i będzie zawierał tylko jedną linię, która zdefiniuje jak uruchomić aplikację.

web: node ./build/index.js

Travis CI

Połączenie repozytorium, które znajduje się w serwisie GitHub z Travisem jest równie proste jak założenie serwera w Heroku więc tego kroku również nie ma co opisywać. Warto jednak przejść do ustawień, bo to już może być ciekawsze 😉

W katalogu głównym aplikacji trzeba utworzyć kolejny plik, tym razem .travis.yml, który będzie rozpoznawany przez serwis Travis CI. Na początek warto zdefiniować podstawy, czyli jaki język powinien być obsługiwany i jakie wersje. W sekcji script ustawiane są rzeczy, które powinny zostać wykonane na CI, w tym wypadku interesuje nas tylko uruchomienie testów. Warto jeszcze dodać cache dla katalogu node_modules bo jak wiadomo może on zajmować sporo miejsca i pozwoli to przyspieszyć proces budowania.

language: node_js
node_js:
- '8'
cache:
  directories:
  - node_modules
script:
  - npm test

Tak zdefiniowany plik konfiguracyjny dla Travisa pozwoli już na przeprowadzanie testów aplikacji, jednak naszym celem było automatycznie wdrażanie aplikacji zawsze kiedy testy przejdą na głównym branchu. Do zdefiniowania deploymentu aplikacji w pliku konfiguracyjnym bardzo przydatne może okazać się narzędzie [Travis CLI], które udostępnia szereg możliwości z poziomu linii poleceń. Niestety jest to gem więc trzeba posiadać zainstalowany silnik Ruby na swojej maszynie…

Zakładając, że klient dla Travisa został zainstalowany i skonfigurowany według opisu można przejść do konkretów. W linii poleceń (będąc wewnątrz głównego katalogu aplikacji) należy wpisać polecenie:

$ npm install --save-dev babel-cli
$ travis setup heroku

Powinny pojawić się pytania odnośnie nazwy repozytorium i nazwy aplikacji w Heroku, a także czy zakodować klucz API (na co odpowiadamy tak). Wtedy do wcześniej utworzonego pliku .travis.yml doklei się kilka linii kodu:

deploy:
  provider: heroku
  api_key:
    secure: "API_KEY"
  app: "APP_NAME"
  on:
    repo: "USER/REPO"

Ale zanim wyślemy aplikację na serwer to może warto ją najpierw zbudować? W tym celu do pliku .travis.yml trzeba dodać jeszcze jedną sekcję, która zostanie wykonana przed procesem deploymentu.

before_deploy:
  - npm run build

W tym miejscu trzeba dodać jeszcze jedną opcję, która pozwoli na wysyłanie gotowej, zbudowanej aplikacji na serwer. Wystarczy dodać opcję skip_cleanup: true do sekcji deploy.

Cały plik konfiguracyjny .travis.yml powinien wyglądać mniej więcej jak poniżej:

language: node_js
node_js:
- '8'
cache:
  directories:
  - node_modules
script:
  - npm test
before_deploy:
  - npm run build
deploy:
  provider: heroku
  skip_cleanup: true
  api_key:
    secure: "API_KEY"
  app: "APP_NAME"
  on:
    repo: "USER/REPO"

Podsumowanie

Tak przygotowane pliki konfiguracyjne i zewnętrzne paczki NPM pozwalają na całkowitą automatyzację projektu od momentu developmentu po samo wdrożenie na serwer. Jak widać nie jest to ani specjalnie skomplikowane ani czasochłonne, dlatego warto czasem poświęcić chwilkę na początku projektu aby uprościć pracę w przyszłości. W tym poście opisałem rozwiązania zastosowane w repozytorium, które wykorzystane było w innym poście na tym blogu.

Mam nadzieję, że wszystkie kroki zostały w jasny sposób opisane i będą działać, ale gdyby coś poszło źle to zachęcam do zostawienia komentarza.

Programista skupiony głównie wokół technologii webowych, ale nie przywiązujący się do konkretnych języków i narzędzi. Skoncentrowany na ciągłym rozwoju, zwolennik ruchu Software Crafmanship. Na codzień pracując w DAZN ma okazję rozwijać interesujący projekt do streamingu wydarzeń sportowych. Prywatnie fan sportu, a szczególnie piłki nożnej. Po godzinach próbuje również swoich sił w piwowarstwie domowym.
PODZIEL SIĘ