Podczas hackathonu w jakim brałem udział tydzień temu - Node Knockout 2017, budowałem aplikację, którą - zgodnie z regulaminem wydarzenia - musiałem opublikować na Heroku. Przyzwyczaiłem się już do tego, że zawsze tam lądują moje aplikacje podczas wydarzeń takiego typu.
Spotkały mnie pewne problemy, które rozwiązałem i chciałem się podzielić moim rozwiązaniem. Nie uważam je za idealne. Jednak za wystarczające.
Disclaimer Zdaje sobie z tego sprawę, że wrzucanie katalogu
dist
do repozytorium jest bez sensowne, ponieważ Git nie powinien wersjonować plików zbundlowanych jednak - jak zawsze - istnieją wyjątki.
Etap 0 Heroku: Zakładanie konta
-
Zakładamy konto na: heroku.com
-
Panel użytkownika: dashboard.heroku.com
Heroku udostępnia panel dla użytkownika, gdzie można tworzyć nowe kontenery, gdzie uruchomiony jest system operacyjny Linux. Pojedynczy kontener w żargonie firmy nazywa się
Dyno
Więcej na ich temat w pomocy.
Etap 1 Heroku: Przygotowanie środowiska
- Tworzymy nową aplikację:
New
→Create new app
-
Wpisujemy nazwę dla naszej aplikacji w pole
App name
Wpisana nazwa jest na sprawdzana “w locie” pod kątem dostępności.
- Opcjonalnie: Wybieramy region:
Choose a region
→Europe
•
🎉 Aplikacja stworzona! 🎉
Możemy otworzyć aplikację w przeglądarce klikając w przycisk: Open app
Naszym oczom powinien ukazać się komunikat:
Heroku | Welcome to your new app!
•
Etap 2 Stworzenie projektu za pomocą Vue CLI
1. Instalacja
Copy + paste
npm install -g vue-cli
Po instalacji, w terminalu będzie dostępne polecenie vue
.
2. Wygenerowanie projektu za pomocą szablonu
Copy + paste
vue init webpack NAZWA_PROJEKTU
Wykorzystujemy jeden w predefiniowanej struktury aplikacji.
W projekcie vue-templates istnieje 6 różnych szablonów:
- webpack
- webpack-simple
- pwa
- simple
- browserify
- browserify-simple
3. Poprawienie wersji
Uwaga: W package.json
należy poprawić wersję, z uwagi na
problem, który wystąpi podczas instalacji zależności na Heroku
:
"engines": {
- "node": ">= 4.0.0",
- "npm": ">= 3.0.0"
+ "node": "8.x",
+ "npm": "5.x"
},
4. Instalacja zależności
Copy + paste
npm install
W głównym katalogu projektu stworzy się plik package-lock.json
,
w którym zostaną zapisane wszystkie dane na temat zainstalowanych paczek wraz
z ich wersjami.
Stacktrace z błędem
Jeśli nie zaktualizujemy wersji to będziemy świadkami błędu:
remote: -----> Building dependencies remote: Installing node modules (package.json + package-lock) remote: WARNING: You are likely using a version of node-tar or npm that is incompatible with this version of Node.js. remote: Please use either the version of npm that is bundled with Node.js, or a version of npm (> 5.5.1 or < 5.4.0) or node-tar (> 4.0.1) that is compatible with Node.js 9 and above. remote: /tmp/build_ab1c9272aa348395c069d548da02c8c4/.heroku/node/bin/node[273]: ../src/node_zlib.cc:437:static void node::{anonymous}::ZCtx::Init(const v8::FunctionCallbackInfo<v8::Value>&): Assertion `args.Length() == 7 && "init(windowBits, level, memLevel, strategy, writeResult, writeCallback," " dictionary)"' failed. remote: 1: node::Abort() [npm] remote: 2: node::Assert(char const* const (*) [4]) [npm] remote: 3: 0x1255aff [npm] remote: 4: v8::internal::FunctionCallbackArguments::Call(void (*)(v8::FunctionCallbackInfo<v8::Value> const&)) [npm] remote: 5: 0xb77f9c [npm] remote: 6: v8::internal::Builtin_HandleApiCall(int, v8::internal::Object**, v8::internal::Isolate*) [npm] remote: 7: 0x253e476842fd remote: /app/tmp/buildpacks/19862b8792e84bd8421ded4660b92dfd1c41d92e19ac0b38c90301adc8ae3e0bd512fa01998af18fc2f0d31a157e9c82e8fdceba1a05e5d29adb8dc2bfaf08e1/lib/dependencies.sh: line 111: 273 Aborted npm install --unsafe-perm --userconfig $build_dir/.npmrc 2>&1 remote: remote: -----> Build failed remote: remote: We're sorry this build is failing! You can troubleshoot common issues here: remote: https://devcenter.heroku.com/articles/troubleshooting-node-deploys remote: remote: Some possible problems: remote: remote: - Dangerous semver range (>) in engines.node remote: https://devcenter.heroku.com/articles/nodejs-support#specifying-a-node-js-version remote: remote: Love, remote: Heroku
5. Zamieniamy projekt w repozytorium
Copy + paste
git init
git add .
git commit -am "Initial import"
•
Teraz mamy dwie opcje:
- Dodajemy katalog
dist/
do repozytorium - Nie dodajemy katalogu
dist/
(rekomendowana)
Zobaczmy jak wyglądają obie opcje w realu!
•
Etap 2 → Opcja 1 Dodajemy katalog dist/
do repozytorium
1. Budujemy produkcyjną wersję aplikacji w katalogu dist/
Copy + paste
npm run build
2. Wyłączamy ignorowania przez Git
-a katalog dist/
Usuwamy linijkę w pliku .gitignore
:
node_modules/
- /dist/
npm-debug.log*
3. Dodajemy katalog dist/
do repozytorium
Copy + paste
git add .
git commit -am "Add distributed version"
4. Instalacja narzędzia, które będzie serwować katalog dist/
Copy + paste
npm install serve --save
5. Tworzymy zadanie run-script
, które uruchomi aplikację
"scripts": {
- "start": "npm run dev",
+ "start": "serve dist/",
+ "deploy": "npm run build && git add dist/ && git commit -am 'Deploy' && git push -f heroku master"
},
Opiszę co robią dodane zadania:
start
- uruchomi paczkęserve
, która będzie serwować katalogdist/
, jako główny katalog aplikacjideploy
- aby przyspieszyć proces deploymentu warto zrobić takie zadanie, które wykona za nas wszelkie prace związanie ze zbudowaniem wersji produkcyjnej
6. Zapisujemy stan projektu
Copy + paste
git add .
git commit -am "Serve static files"
Aplikacja jest gotowa do pierwszego release-u! 🏆
•
Etap 2 → Opcja 2 Nie dodajemy katalogu dist/
do repozytorium (rekomendowana)
1. Instalujemy paczkę, która będzie serwowała katalog dist/
Dla odmiany wykorzystajmy inną paczkę.
Tym razem niech to będzie http-server
.
Copy + paste
npm install http-server --save
2. Dodajemy nowe zadania
"scripts": {
"dev": "webpack-dev-server --inline --progress --config build/webpack.dev.conf.js",
"build": "node build/build.js",
+ "deploy": "git push -f heroku master",
+ "heroku-postbuild": "npm run build",
+ "start": "http-server dist/ -d false -g"
},
Opiszę nowo dodane polecenia:
deploy
- zadanie dzięki, któremu zaktualizujemy branchmaster
w Heroku, przez co zostanie zaktualizowany projekt w serwerowni.-
heroku-postbuild
- jest to hook (event handler), który zostanie uruchomiony przez Heroku w celu zbudowania aplikacji.Zadanie zostanie uruchomione po instalacji wszystkich zależności.
start
- uruchamia serwer HTTP, który będzie serwował katalogdist/
jakodirectoryRoot
, czy główny katalog aplikacji
3. Ustawmy zmienną środowiskową na Heroku
W panelu Heroku, w zakładce Settings
→ Config Variables
.
Należy w niej ustawić zmienną:
NPM_CONFIG_PRODUCTION = false
Zmienna jest ustawiona po to, aby podczas procesu budowania projektu Heroku
pobrało również zależności developerskie, które są potrzebne do zbudowania
aplikacji do katalogu dist/
.
Aplikacja jest gotowa do pierwszego release-u! 🏆
•
Etap 3 Deployment
Czas najwyższy, aby opublikować aplikację. Mamy kilka możliwości na deployment aplikacji. Dla mnie interesujące są 2 pierwsze:
- Automatyczny deployment z brancha na GitHubie
- Manualny deployment po wrzuceniu projektu do zdalnego repozytorium Git, trzymanego na Heroku.
Wybieram opcję numer 2 ponieważ pierwsza jest mniej elastyczna. Chciałbym coś wrzucić na Heroku, ale nie chciałbym publikować tych zmian na GitHuba.
1. Logujemy się do Heroku CLI
Copy + paste
heroku login
2. Łączymy repozytorium lokalne ze zdalnym na Heroku
Copy + paste
heroku git:remote -a NAZWA_PROJEKTU
3. Wypychamy zmiany na serwer 🚀
Wyślijmy nasze repozytorium na Heroku, aby zainicjować proces deploymentu:
Copy + paste
git push -f heroku master
Odśwież w przeglądarce wcześniej otworzoną stronę, aby zobaczyć:
•
🎊 Aplikacja opublikowana! 🎊
Więcej na temat deploymentu przeczytasz w pomocy.
Aby później deployować aplikację za pomocą jednego polecenia:
Copy + paste
npm run deploy
•
Jeśli znasz lepszy sposób na deployment napisz komentarz!