Код для этой главы доступен тут.
В этом разделе мы создадим сервер, который будет генерировать наше веб приложение. Также мы настроим для этого сервера режимы разработки и production.
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/
.
Эта папка - место в которое мы поместим изоморфный / универсальный 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 или нет. Комментарий // eslint-disable-next-line import/prefer-default-export
нужен изза того, что у нас только один именованный экспорт в это файле. Вы можете его убрать как только добавите сюда экспорт других переменных.
- Выполните
yarn add express compression
compression
- это промежуточное ПО для Express активирующее 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)'}.`)
})
Здесь ничего особенного, это практически 'Hello World' для Express плюс несколько дополнительных импортов. Мы здесь используем две отдельных директории для статических файлов. 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
Возможно, вам привычно использовать шаблонизаторы при работе с back-end? Что ж, теперь их можно считать довольно устаревшимы с тех пор, как JavaScript стал поддерживать шаблонные строки. Здесь мы создали функцию, которая принимает в качестве параметра title
и вставляет это значение в тэги title
и h1
, возвращая строку с полноценной HTML страницей. Мы также используем константу STATIC_PATH
для задания базового пути для всех наших статических элементов.
В зависимости от используемого текстового редактора, возможна подсветка синтаксиса HTML кода внутри шаблонных строк. В Атоме, если вы добавите префикс html
к шаблонной строке (или любой другой префикс, заканчивающийся на html
, как ilovehtml
), то содержимое этой строки автоматически будет подсвечиваться. Я иногда использую html
тэг из библиотеки common-tags
, чтобы воспользоваться данной возможностью:
import { html } from `common-tags`
const template = html`
<div>Wow, colors!</div>
`
Я не стал включать этот трюк в boilerplate этого руководства, поскольку это, похоже работает только в Атоме, и это не идеальный подход. Однако для тех, кто использует Атом, это может быть полезным.
В любом случае вернемся к нашему проекту.
- В
package.json
измените скриптstart
таким образом:"start": "babel-node src/server",
🏁 Запустите yarn start
и откройте localhost:8000
в браузере. Если все заработало как и ожидалось, то вы увидете пустую страницу с надписями "Hello App" в названии вкладки и на зеленом заголовке страницы.
Примечание: Некоторые процессы (обычно процессы, ожидающие своего завершения, как, например, сервер) не позволяют вам вводить команды в терминале пока они не завершатся. Чтобы прервать подобный процесс и получить обратно приглашение к вводу, нажмите Ctrl+C. Как вариант, вы можете открыть еще одну вкладку с терминалом, если хотите, чтобы процесс работал, пока вы вводите команды. Вы также можете запустить эти процессы в фоне, но это вне рамок данного руководства.
💡 Nodemon - утилита для автоматического перезапуска сервера Node при изменении файлов в директории.
Мы будем использовать Nodemon в режиме разработки
-
Запустите
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 будет представлен в следующей главе.
💡 PM2 - это менеджер процессов для Node, обеспечивающий жизнеспособность вашего приложения и предлагающий тонны возможностей по управлению и мониторингу.
Мы будем использовать PM2 в режиме production
- Выполните
yarn add --dev pm2
В production вы хотите, чтобы сервер был настолько производительным, насколько это возможно. babel-node
начинает процесс транспиляции всех файлов при каждом перезапуске, чего вы бы хотели избежать в production. Нам нужно, чтобы Babel выполнил всю эту работу заранее, и сервер выдавал обычные старые предкомпилированные ES5 файлы.
Одной из основных возможностей Babel является способность взять папку с ES6 кодом (обычно src
) и транспилировать его в папку с ES5 кодом (обычно lib
).
Поскольку папка lib
автогенерируется, хорошей практикой будет очищать ее перед каждым новым построением, поскольку она может содержать нежелательные старые файлы. rimraf
– простой лаконичный пакет, для удаления файлов с кроссплатформенной поддержкой.
- Запустите
yarn add --dev rimraf
Давайте добавим следующую задачу prod:build
в package.json
:
"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
, было бы здорово проверять все ли работает хорошо перед тем как закачивать код в репозиторий. Поскольку, возможно не требуется запускать его перед каждым коммитом, я предлагаю добавить это в задачу prepush
:
"prepush": "yarn test && yarn prod:build"
🏁 Запустите yarn prepush
или просто начните загружать файлы (push), чтобы запустить этот процесс.
Примечание: У нас пока нет никаких тестов, так что Jest пожалуется на это. Пока что проигнорируйте это.
Следующий раздел: 04 - Webpack, React, HMR
Назад в предыдущий раздел или содержание.