Кода за тази глава можете да намерите тук.
В тази секция ще създадем сървърът, който ще отговаря за показването на нашето уеб приложение. Също така ще го настроим, така че да поддържа режими на разработка (development mode) и на производство (production mode).
💡 Express е може би най-известният фреймуърк за уеб приложения за Node. Предоставя много опростен и изчистен интерфейс за програмиране (API), и неговите свойства могат да бъдат надградени с middleware.
Сега ще настроим минимален Express сървър за сервиране на една HTML страница с малко CSS код.
- Изтрийте всичко от папката
src
Създайте следните файлове и директории:
- Създайте
public/css/style.css
файл, съдържащ:
body {
width: 960px;
margin: auto;
font-family: sans-serif;
}
h1 {
color: limegreen;
}
-
Създайте празна папка
src/client/
. -
Създайте празна папка
src/shared/
.
Това е папката къдато ще поставяме isomorphic / universal JavaScript код – файлове, които се използват и от клиентската част, и от сървърната. Чудесен пример за такъв споделен код са routes, както ще видите малко по-късно в това ръководство когато ще правим асинхронни извиквания. Тук имаме просто някои конфигурационни константи, служещи за пример.
- Създайте
src/shared/config.js
файл, съдържащ:
// @flow
export const WEB_PORT = process.env.PORT || 8000
export const STATIC_PATH = '/static'
export const APP_NAME = 'Hello App'
Ако Node процеса използван за стартиране на вашето приложение има променлива process.env.PORT
(такъв би бил случая ако използвате Heroku например), ще използва нея за порта. Ако няма такава, по подразбиране порта ще бъде 8000
.
- Създайте
src/shared/util.js
файл, съдържащ:
// @flow
// eslint-disable-next-line import/prefer-default-export
export const isProd = process.env.NODE_ENV === 'production'
Това е една полезна опция, с която да тествате дали сте в производствен режим (production mode) или не. Коментарът // eslint-disable-next-line import/prefer-default-export
е сложен, тъй като в момента имаме само един наименован файл, който експортираме. Можете да го премахнете когато добавяте други експорти в този файл.
- Изпълнете
yarn add express compression
compression
е Express middleware за активиране на Gzip компресия на сървъра.
- Създайте
src/server/index.js
файл, съдържащ:
// @flow
import compression from 'compression'
import express from 'express'
import { APP_NAME, STATIC_PATH, WEB_PORT } from '../shared/config'
import { isProd } from '../shared/util'
import renderApp from './render-app'
const app = express()
app.use(compression())
app.use(STATIC_PATH, express.static('dist'))
app.use(STATIC_PATH, express.static('public'))
app.get('/', (req, res) => {
res.send(renderApp(APP_NAME))
})
app.listen(WEB_PORT, () => {
// eslint-disable-next-line no-console
console.log(`Server running on port ${WEB_PORT} ${isProd ? '(production)' : '(development)'}.`)
})
Нищо чрезвичайно тук, това е почти Express варианта на Hello World с няколко допълнения. Тук ще използваме 2 различни директории за статични файлове. dist
за генерирани файлове и public
за декларирани такива.
- Създайте
src/server/render-app.js
файл, съдържащ:
// @flow
import { STATIC_PATH } from '../shared/config'
const renderApp = (title: string) =>
`<!doctype html>
<html>
<head>
<title>${title}</title>
<link rel="stylesheet" href="${STATIC_PATH}/css/style.css">
</head>
<body>
<h1>${title}</h1>
</body>
</html>
`
export default renderApp
Може би знаете, че обикновено се използваха темплейт енджини в бек-енд часта? Сега това вече не е нужно, тъй като JavaScript поддържа темплейт стрингове. Тук създаваме функция, която взима title
(заглавието) като параметър и го инжектира в title
и h1
таговете на страницата, връщайки завършен HTML стринг. Също така използваме константата STATIC_PATH
, която служи за основен път към всички наши статични ресурси.
В зависимост от вашия редактор, можете да имате осветяване на синтаксиса, което да е в сила за HTML код в темплейт стринговете. В Atom, ако поставите html
таг преди темплейт стринг (или какъвто и да било таг завършващ с html
, като например ilovehtml
), съдържанието ще се освети. Понякога използвам html
таг от common-tags
библиотеката, за да се възползвам от тази опция:
import { html } from `common-tags`
const template = html`
<div>Wow, colors!</div>
`
Нарочно не включих този трик в основата на това ръководство, тъй като изглежда, че работи само в Atom, а и не е идеално. Някои от вас потребителите на Atom могат да го намерят за полезно.
Както и да е, обратно на работа!
- В
package.json
променете вашияstart
скрипт както следва:"start": "babel-node src/server",
🏁 Изпълнете yarn start
и отворете localhost:8000
във вашия браузър. Ако всичко работи както се очаква би трябвало да видите празна страница с "Hello App" написано на заглавната лента и на самата страница, във вид на зелен текст.
Забележка: Някои процеси – обикновено такива, които очакват нещо да се случи, като например сървърните процеси – няма да ви позволят да въвеждате команди във вашия терминал докато не приключат работата си. За да прекратите такива процеси и да можете да използвате терминала си отново натиснете Ctrl+C. Ако искате да можете да въвеждате команди докато тези процеси работят можете да отворите нов таб на терминалния прозорец и да пишете в него. Също така има възможност тези процеси да бъдат стартирани и да работят в бекграунда, но това не е в обхвата на това ръководство.
💡 Nodemon е инструмент, чрез който вашия Node сървър се рестартира автоматично когато настъпят промени в даден файл в директорията.
Ще използваме Nodemon докато сме в режим на разработка (development mode).
-
Изпълнете
yarn add --dev nodemon
-
Променете вашият
scripts
обект, както следва:
"start": "yarn dev:start",
"dev:start": "nodemon --ignore lib --exec babel-node src/server",
Сега start
е просто указател към друга задача, dev:start
. Това ни дава слой на абстракция когато настройваме какво прави основната ни задача.
В задачата dev:start
, флагът --ignore lib
е, за да не се рестартира сървъра когато настъпят някакви промени в lib
директорията. Все още нямате такава директория, но ще я създадем в следващата секция и ще можем да видим ефекта му в действие. Nodemon обикновено използва node
функционалността, за да работи. В нашия случай, тъй като използваме Babel, ще настроим Nodemon да използва babel-node
. По този начин ще може да използваме цялата функционалност произлизаща от ES6/Flow кода.
🏁 Изпълнете yarn start
и отворете localhost:8000
. Променете APP_NAME
константата в src/shared/config.js
, което действие би трябвало да инициира рестартирането на сървъра в терминала. Опреснете страницата, за да видите обновеното заглавие. Обърнете внимание, че този автоматичен рестарт на сървъра е нещо различно от Hot Module Replacement, което е когато компонентите на страницата се обновяват в реално време. В нашия случай все още имаме нужда от ръчно опресняване, но поне не трябва да спираме процеса и да го рестартираме ръчно отново за да видим промените. Hot Module Replacement (моментално опресняване без нужда от ръчно такова, т.е. без нужда от натискане на F5, за Windows например) ще бъде представено в следващата секция.
💡 PM2 е мениджър на процеси за Node. Предлага функционалност за поддържане на "живи" процесите в производствена среда, както и много други опции за управление и наблюдение на такива.
Ще използваме PM2 докато сме в режим на производство (production mode).
- Изпълнете
yarn add --dev pm2
В производствена среда, бихме искали сървъра ни да работи колкото се може по-добре. babel-node
стартира целия Babel процес по транспилацията на файловете при всяко изпълнение, което е нещо, което не искаме да се случва в производствена среда. Ние имаме нужда Babel да извършва цялата тази работа предварително, за да може накрая нашия сървър да сервира файлове с добре познатия стар, чист, прекомпилиран ES5 код.
Едно от основните свойства на Babel е да вземе една папка с ES6 код (обикновено кръстена src
) и да я транспилира в папка с ES5 код (обикновено кръстена lib
).
Папката lib
се генерира автоматично, добра практика е да се чисти съдържанието й преди всеки нов билд, тъй като може да съдържа нежелани стари файлове. За тази цел съществува един чудесен пакет наречен rimraf
.
- Изпълнете
yarn add --dev rimraf
Нека да добавим следната prod:build
задача към нашия scripts
обект:
"prod:build": "rimraf lib && babel src -d lib --ignore .test.js",
-
Изпълнете
yarn prod:build
, това би трябвало да генерираlib
папка, съдържаща транспилирания код, с изключение на файлове завършващи на.test.js
(обърнете внимание, че.test.jsx
файлове също ще бъдат игнорирани с този параметър). -
Добавете
/lib/
във вашия.gitignore
файл
Едно последно нещо: ще подадем NODE_ENV
променливата към нашия PM2. Ако сте с Unix, бихте могли да направите това чрез изпълнението на NODE_ENV=production pm2
, но на Windows синтаксиса е различен. Ще използваме още един малък пакет, наречен cross-env
, за да направим възможна работата под Windows също така.
- Изпълнете
yarn add --dev cross-env
Нека да обновим нашия package.json
, както следва:
"scripts": {
"start": "yarn dev:start",
"dev:start": "nodemon --ignore lib --exec babel-node src/server",
"prod:build": "rimraf lib && babel src -d lib --ignore .test.js",
"prod:start": "cross-env NODE_ENV=production pm2 start lib/server && pm2 logs",
"prod:stop": "pm2 delete server",
"test": "eslint src && flow && jest --coverage",
"precommit": "yarn test",
"prepush": "yarn test"
},
🏁 Изпълнете yarn prod:build
, след това изпълнете yarn prod:start
. PM2 би трябвало да покаже активния процес. Отворете http://localhost:8000/
във вашия браузър и би трябвало да видите вашето приложение. Вашият терминален прозорец би трябвало да показва логовете, които би трябвало да са "Server running on port 8000 (production).". Забележете, че използвайки PM2, вашите процеси се изпълняват в бекграунда. Ако натиснете Ctrl+C, ще прекратите изпълнението на pm2 logs
командата, което е последната команда от нашата prod:start
поредица, но сървъра все още би трябвало да рендира страницата. Ако искате да спрете изпълнението на сървъра, изпълнете yarn prod:stop
Сега, след като имаме prod:build
задача, би било добре да проверим, че всичко работи коректно преди да запазваме (pushing) промени в репозиторито. Тъй като, вероятно не е необходимо да го стартираме при всеки опит за запазване (commit), препоръчвам да го добавите в prepush
задачата:
"prepush": "yarn test && yarn prod:build"
🏁 Изпълнете yarn prepush
или просто запазете вашите промени (push your files), за да стартирате процеса.
Забележка: В момента не разполагаме с никакви тестове за случая, така че Jest ще ни съобщи за това. Игнорирайте го за момента.
Следваща глава: 04 - Webpack, React, HMR
Назад към предишната глава или към съдържанието.