Любая серьезная разработка и развертывание, кроме создания прототипа и тестирования JSX, требуют настройки процесса сборки. Если такой процесс уже отработан, нужно добавить к нему Babel-преобразование. Но предположим, что никакого процесса сборки еще нет, и начнем с нуля.
Наша цель заключается в использовании JSX и любых других современных возможностей JavaScript, не дожидаясь от браузеров их реализации. Нужно настроить преобразование, запускаемое в фоновом режиме в процессе разработки. Процесс преобразования должен выдавать код, максимально приближенный к тому коду, который ваши пользователи будут запускать на живом сайте (то есть без преобразований на стороне клиента). Процесс также должен быть как можно более незаметным, чтобы не приходилось переключаться между контекстом разработки и контекстом сборки.
Относительно процессов разработки и сборки сообществом и экосистемой JavaScript предлагается множество вариантов. Будем придерживаться простого низкоуровневого варианта сборки и не пользоваться инструментальными средствами, выработав вместо этого собственный подход, исходя из предоставляемых возможностей, позволяющих:
• разобраться в происходящем;
• сделать после этого осознанный выбор средств для создания сборки;
• сконцентрироваться на всем, что касается React, не особо отвлекаясь на все остальное.
Начнем с определения типового образца нового приложения. Это приложение, выполняемое на стороне клиента, созданное в стиле одностраничного приложения — single-page application (SPA). В приложении используются JSX и множество нововведений, предлагаемых языком JavaScript, — предложения от стандартов ES5, ES6 (или ES2015) и будущего стандарта ES7.
В соответствии со сложившейся практикой вам понадобятся папки /css, /js и /images, а также файл index.html, позволяющий связать все эти папки вместе. Разобьем папку /js на /js/source (где будут сценарии с синтаксисом JSX) и /js/build (где будут сценарии, понятные браузерам). Кроме этого, существует еще такая категория, как /scripts, к которой относятся сценарии командной строки для выполнения сборки.
Структура каталогов для вашего типового (стандартного) приложения должна приобрести вид, показанный на рис. 5.1.
Рис. 5.1. Типовое приложение
Разобьем на части еще и каталоги /css и /js (рис. 5.2), чтобы в них были включены:
• файлы, общие для всего приложения;
• файлы, связанные с конкретными компонентами.
Это поможет сконцентрироваться на поддержке независимости компонентов, их узкой специализации и максимальной возможности многократного использования. В конечном счете хочется собрать свое собственное большое приложение, используя множество мелких компонентов, имеющих конкретное предназначение. В общем, в соответствии с принципом «разделяй и властвуй».
В довершение создадим для примера простой компонент под названием <Logo> (у приложений обычно бывают логотипы). По общепринятому соглашению имена компонентов начинаются с заглавной буквы, поэтому назовем его Logo, а не logo. Сохраняя согласованность всех относящихся к компонентам файлов, установим соглашение об использовании для реализации компонента путевого имени /js/source/components/Component.js, а для связанного с ним стилевого оформления — путевого имени /css/components/Component.css. Полная структура каталогов с простым компонентом <Logo> показана на рис. 5.2.
Рис. 5.2. Отдельные компоненты
Урегулировав вопрос со структурой каталогов, посмотрим, как заставить все, что в ней находится, работать в стиле Hello World. Файл index.html должен включать:
• все таблицы CSS в одном файле bundle.css;
• весь код JavaScript в одном файле bundle.js (включающем ваше приложение, а также его компоненты и их библиотечные зависимости, в том числе React);
• и, как всегда, <div id="app">, место, куда будет выложено ваше приложение:
<!DOCTYPE html>
<html>
<head>
<title>App</title>
<meta charset="utf-8">
<link rel="stylesheet" type="text/css" href="bundle.css">
</head>
<body>
<div id="app"></div>
<script src="bundle.js"></script>
</body>
</html>
Как ни странно, но наличие единственного .css-файла и единственного .js-файла доказывает свою эффективность для широкого круга приложений. Когда ваши приложения разрастутся до размеров таких приложений, как Facebook или Twitter, эти сценарии могут стать слишком большими для первоначальной загрузки, да и пользователю в любом случае на первых порах сразу все функциональные возможности не понадобятся. Вы установите загрузчик сценариев и стилей, чтобы иметь возможность загружать дополнительный код по мере возникновения в нем потребности (решение этой задачи возлагается на вас, и в очередной раз следует заметить, что в вашем распоряжении имеется масса готовых вариантов с открытым кодом). В данном сценарии единственные файлы .css и .js становятся в некотором роде файлами начальной загрузки, тем самым минимумом, благодаря которому появляется возможность как можно скорее вывести перед глазами пользователя какое-нибудь изображение. Следовательно, схема использования единственного файла по-прежнему имеет право на существование даже при условии увеличения приложения в объеме.
Вскоре вы увидите, как файлы bundle.js и bundle.css создаются из отдельных файлов. Но сначала рассмотрим, какой CSS-код и JS-код куда попадает.
В глобальном файле /css/app.css должны содержаться общие стили, предназначенные для всего приложения, и он должен иметь примерно следующий вид:
html {
background: white;
font: 16px Arial;
}
Помимо стилей, предназначенных для всего приложения, вам понадобятся конкретные стили для каждого компонента. В соответствии с соглашением об использовании единственного файла CSS (и единственного файла JS) в расчете на каждый React-компонент и об их размещении в каталоге /css/components (и /js/source/components) создадим файл /css/components/Logo.css следующего содержания:
.Logo {
background-image: url('..//books/28463/OEBPS/react-logo.svg');
background-size: cover;
display: inline-block;
height: 50px;
vertical-align: middle;
width: 50px;
}
Еще одно простое соглашение, которое может оказаться полезным, заключается в написании имен классов CSS с заглавной буквы и в наличии у корневого элемента компонента имени класса, совпадающего с именем компонента, следовательно, для него будет использоваться определение className="Logo".
Точка входа в приложение, где все начинается, находится в сценарии /js/source/app.js, поэтому им и займемся:
React.render(
<h1>
<Logo /> Welcome to The App!
</h1>,
document.getElementById('app')
);
И наконец, реализуем используемый в качестве примера React-компонент <Logo> в файле /js/source/components/Logo.js:
var Logo = React.createClass({
render: function() {
return <div className="Logo" />;
}
});
До сих пор в примерах, приведенных в этой книге, работа велась только с простыми компонентами, также обеспечивалась доступность React и ReactDOM в качестве глобальных переменных. По мере перехода к более сложным приложениям с несколькими компонентами потребуется более продуманная организация. Распыляться глобальными переменными довольно опасно (из-за тенденции возникновения конфликта имен), и зависимость от постоянного присутствия глобальных переменных также опасна (подумайте, что будет, если вы перейдете к другому комплектованию пакетов JS, где не соблюдается правило нахождения всего кода в единственном файле bundle.js).
Вам нужны модули.
Сообщество JavaScript выдвинуло несколько идей насчет модулей; одна из них — технология CommonJS — получила широкое распространение. Согласно этой технологии в файле содержится код, экспортирующий один или несколько идентификаторов (чаще всего объект, но это может быть и функция или даже отдельная переменная):
var Logo = React.createClass({/* ... */});
module.exports = Logo;
Одно из соглашений, которое может оказаться полезным, заключается в следующем: один модуль экспортирует что-либо одно (например, один React-компонент).
Теперь этому модулю требуется React, чтобы выполнить метод React.createClass(). Глобальных переменных больше нет, следовательно, React как глобальный идентификатор недоступен. Его нужно включить (или затребовать с помощью require) следующим образом:
var React = require('react');
var Logo = React.createClass({/* ... */});
module.exports = Logo;
Пусть это станет шаблоном для каждого компонента: объявление требований — в самом верху, экспорт — внизу, а между ними — сама реализация.
Спецификации ECMAScript предлагают развить эту идею и вводят новый синтаксис (в противоположность получаемому с помощью require() и module.exports). Он выгоден тем, что, когда дело касается транспиляции нового синтаксиса до «перевариваемого» браузером кода, Babel услужливо подставляет вам свое плечо.
При объявлении зависимостей от других модулей вместо:
var React = require('react');
используется:
import React from 'react';
А также при экспортировании из вашего модуля вместо:
module.exports = Logo;
используется:
export default Logo
Отсутствие точки с запятой в конце инструкции export — особенность, предусмотренная в ECMAScript, и в данной книге ошибкой не является.
Теперь в ECMAScript имеются классы, поэтому воспользуемся новым синтаксисом.
До:
var Logo = React.createClass({/* ... */});
После:
class Logo extends React.Component {/* ... */}
Если раньше «классы» React объявлялись с помощью объекта, то теперь с появлением реальных классов кое-что изменилось:
• в объекте больше нет произвольных свойств, только функции (методы). При надобности свойство присваивается объекту this внутри конструктора (будут даны дополнительные примеры и способы, которым нужно следовать);
• синтаксис метода теперь выглядит как render(){}, ключевые слова function больше не нужны;
• методы больше не разделены запятыми (,), как было в var obj = {a: 1, b: 2};.
class Logo extends React.Component {
someMethod() {
} // нет запятых
another() { // нет ключевого слова 'function'
}
render() {
return <div className="Logo" />;
}
}
По мере чтения книги будут встречаться и реализации других возможностей, предусмотренных в ECMAScript, но для типового образца, чтобы сдвинуть процесс создания нового приложения с мертвой точки как можно быстрее и с минимальными затратами, вполне достаточно упомянутых возможностей.
Теперь у вас есть index.html, а также предназначенные для приложения в целом CSS-таблицы (app.css), по одному файлу с таблицами CSS на каждый компонент (/css/components/Logo.css), точка входа в JavaScript-код приложения (app.js) и каждый React-компонент, реализованный в конкретном модуле (например, /js/source/components/Logo.js).
Финальная версия app.js имеет следующий вид:
'use strict'; // эта инструкция никогда не помешает
import React from 'react';
import ReactDOM from 'react-dom';
import Logo from './components/Logo';
ReactDOM.render(
<h1>
<Logo /> Welcome to The App!
</h1>,
document.getElementById('app')
);
А вот как выглядит Logo.js:
import React from 'react';
class Logo extends React.Component {
render() {
return <div className="Logo" />;
}
}
export default Logo
Заметили разницу между импортированием React и импортированием компонента Logo: from 'react' и from './components/Logo'? Последнее выражение выглядит как путь внутри каталога (это так и есть) — вы сообщаете модулю, что нужно извлечь зависимость из файла, расположенного относительно модуля, а первое выражение касается извлечения зависимости, установленной посредством npm из общедоступного места. Посмотрим, как выполняется эта работа и как волшебным образом заставить весь новый синтаксис и модули работать в браузере (включая даже старые браузеры IE!).
Настройки типового образца можно найти в хранилище кода, сопровождающем данную книгу, и воспользоваться ими для вставки в ваше приложение.
Прежде чем загрузить index.html и посмотреть его в работе, необходимо сделать следующее:
• создать файл bundle.css. Это простое объединение, не требующее использования обязательных инструментальных средств;
• сделать код понятным для браузеров. Для транспиляции вам понадобится Babel;
• создать файл bundle.js. Для этого воспользуемся таким средством, как Browserify.
Средство Browserify понадобится не только для объединения сценариев, но и для:
• разрешения и включения всех зависимостей. Вы просто даете ему путевое имя файла app.js, а оно затем вычисляет все зависимости (React, Logo.js и т.д.);
• включения реализации CommonJS, чтобы работали вызовы require(). Babel превращает все инструкции import в вызовы функций require().
В общем, нужно установить Babel и Browserify. Их установка выполняется с использованием npm (Node Package Manager — диспетчер пакетов Node), инструментального средства, поставляемого вместе с программной платформой Node.js.
Для установки Node.js перейдите по адресу и получите установщик, соответствующий вашей операционной системе. Следуйте инструкциям установщика (они помогут справиться с поставленной задачей). Теперь можно воспользоваться услугами, предоставляемыми утилитой npm.
Для проверки введите в своем терминале следующую команду:
$ npm --version
Если вы не имеете опыта работы с терминалом (командной строкой), то сейчас самое время его приобрести! Если у вас Mac OS X, щелкните на поиске Spotlight (значок которого в виде увеличительного стекла находится в верхнем правом углу) и наберите Terminal. Если у вас Windows, найдите меню Пуск (щелкните правой кнопкой мыши на значке окна в левом нижнем углу экрана), выберите пункт Выполнить и наберите powershell.
В этой книге все набираемые в терминале команды предваряются символом $, используемым в качестве подсказки, позволяющей отличить их от обычного кода. При наборе команды в терминале символ $ набирать не нужно.
Средство Browserify устанавливается с помощью npm путем набора в вашем терминале следующей команды:
$ npm install --global browserify
Для проверки работоспособности средства наберите следующую команду:
$ browserify --version
Для установки интерфейса командной строки Babel наберите следующую команду:
$ npm install --global babel-cli
Для проверки работоспособности наберите такую команду:
$ babel --version
Уловили суть?
Вообще-то лучше установить пакет Node локально без ключа --global, показанного в примерах. (Присмотримся к другой схеме, где global === bad?) При локальной установке у вас будут разные версии одних и тех же пакетов — в соответствии с потребностями каждого приложения, над которым вы работаете или которое вам нужно использовать. Но для Browserify и Babel глобальная установка пакетов предоставляет вам глобальный доступ (из любого каталога) к интерфейсу командной строки.
Осталось воспользоваться еще несколькими пакетами (и все будет готово):
• react, разумеется;
• react-dom (распространяется отдельно);
• babel-preset-react (предоставляет Babel поддержку для JSX и других полезных опций, связанных с React);
• babel-preset-es2015 (предоставляет вам поддержку новейших возможностей JavaScript).
Для локальной установки этих пакетов сначала перейдите в каталог своего приложения (например, с помощью команды cd ~/reactbook/reactbook-boiler):
$ npm install --save-dev react
$ npm install --save-dev react-dom
$ npm install --save-dev babel-preset-react
$ npm install --save-dev babel-preset-es2015
Нужно отметить, что теперь у вашего приложения есть каталог node_modules с локальными пакетами и их зависимостями. Два глобальных модуля (Babel, Browserify) находятся в каталоге node_modules, расположенном где-то в другом месте, определяемом вашей операционной системой (например, /usr/local/lib/node_modules или C:\Users{ваше_имя}\AppData\Roaming\npm\).
В процессе сборки выполняются три действия: объединение CSS, транспиляция JS и создание пакета JS. Это не сложнее запуска трех команд.
Сначала транспилируем код JavaScript с помощью Babel:
$ babel --presets react,es2015 js/source -d js/build
Эта команда означает, что нужно взять все файлы из каталога js/source, транспилировать их, задействовав возможности React и ES2015, и скопировать результат в js/build. Команда выведет следующую информацию:
js/source/app.js -> js/build/app.js
js/source/components/Logo.js -> js/build/components/Logo.js
И этот список будет расти по мере добавления новых компонентов.
Теперь настала очередь создания пакета:
$ browserify js/build/app.js -o bundle.js
Средству Browserify предписывается начать с файла app.js, учесть все зависимости и записать результат в файл bundle.js, который завершает инструкцию включения в вашем файле index.html. Чтобы проверить, что файл действительно был записан, наберите команду less bundle.js.
Создание пакета CSS выполняется (на данном этапе) настолько просто, что вам даже не нужно применять никаких специальных средств, следует просто объединить все CSS-файлы в один (используя команду cat). Но поскольку файл перемещается в другое место, ссылки на изображения утратят работоспособность, поэтому перепишем их с помощью вызова команды sed:
cat css/*/* css/*.css | sed 's/..\/..\/images/images/g'
> bundle.css
С задачей создания пакетов гораздо лучше справляется NPM, но пока нас вполне устраивает применяемый вариант.
На данный момент все необходимые действия завершены и можно уже посмотреть на результаты вашего упорного труда. Загрузите файл index.html в свой браузер — и увидите экран приветствия (рис. 5.3).
Рис. 5.3. Приглашение на вход в приложение
Предыдущие команды предназначены для использования в среде Linux или Mac OS X. Но аналогичные команды в Windows отличаются от них весьма незначительно. Первые две, за исключением разделителя каталогов, имеют идентичный вид.
То есть:
$ babel --presets react,es2015 js\source -d js\build
$ browserify js\build\app.js -o bundle.js
В Windows нет команды cat, но объединение можно выполнить следующим образом:
$ type css\components\* css\* > bundle.css
А для замены строк в файле (чтобы заставить CSS искать изображения в images, а не в ../../images) нужно применить слегка усовершенствованные возможности оболочки powershell:
$ (Get-Content bundle.css).replace('../../images', 'images') | Set-Content
bundle.css
Необходимость запуска процесса сборки при каждом внесении изменения в файл сильно докучает. К счастью, отслеживать изменения в каталоге можно из сценария, а также запускать сценарий сборки в автоматическом режиме.
Сначала поместим все три команды, составляющие процесс сборки, в файл scripts/build.sh:
# преобразование js
babel --presets react,es2015 js/source -d js/build
# создание пакета js
browserify js/build/app.js -o bundle.js
# создание пакета css
cat css/*/* css/*.css | sed 's/..\/..\/images/images/g'
> bundle.css
# готово
date; echo;
Затем установим NPM-пакет watch:
$ npm install --save-dev watch
Запуск команды watch похож на приказ отслеживать любые изменения в каталогах js/source/ и /css и в случае какого-либо изменения — запускать сценарий оболочки, находящийся в файле scripts/build.sh:
$ watch "sh scripts/build.sh" js/source css
> Watching js/source/
> Watching css/
js/source/app.js -> js/build/app.js
js/source/components/Logo.js -> js/build/components/Logo.js
Sat Jan 23 19:41:38 PST 2016
Разумеется, можно также поместить эту команду в файл scripts/watch.sh, чтобы всякий раз, приступая к работе над приложением, вы просто запускали команду:
$ sh scripts/watch.sh
…и пользовались готовым результатом. Вы можете вносить изменения в исходные файлы, затем обновлять страницу в браузере и просматривать новую сборку.
Теперь в развертывании вашего приложения нет ничего особенного, поскольку сборки создаются в ходе его разработки, поэтому особых сюрпризов ожидать не приходится. Прежде чем ваше приложение попадет к реальным пользователям, может все-таки понадобиться провести дополнительную обработку, например уменьшить размер кода и выполнить оптимизацию изображений.
В качестве примеров воспользуемся популярным средством уменьшения размера кода (минификатором) JS под названием uglify и минификатором CSS под названием cssshrink. Можно продолжить обработку, минифицировав HTML, проведя оптимизацию изображений, копируя файлы в сеть доставки контента content delivery network (CDN) и проведя другие нужные вам операции.
Файл scripts/deploy.sh должен приобрести следующий вид:
# удаление последней версии
rm -rf __deployme
mkdir __deployme
# сборка
sh scripts/build.sh
# минификация JS
uglify -s bundle.js -o __deployme/bundle.js
# минификация CSS
cssshrink bundle.css > __deployme/bundle.css
# копирование HTML и изображений
cp index.html __deployme/index.html
cp -r images/ __deployme/images/
# готово
date; echo;
После запуска сценария у вас должен появиться новый каталог __deployme, содержащий:
• файл index.html;
• bundle.css (минифицированный);
• bundle.js (минифицированный);
• папку images/.
Затем останется лишь скопировать этот каталог на ближайший сервер, чтобы приступить к обслуживанию ваших пользователей с применением вашего обновленного приложения.
Теперь у вас есть пример конвейерной сборки и развертывания в среде оболочки. Можете его расширить в соответствии с вашими текущими потребностями или же воспользоваться более приспособленными для сборки средствами (например, Grunt или Gulp), которые способны более полно удовлетворить ваши нужды.
Имея в арсенале все приемы сборки и транспиляции, следует продолжить путь и перейти к более увлекательным темам: созданию и тестированию реального приложения с использованием самых последних многочисленных возможностей, предлагаемых современным языком JavaScript.