Официальный пример использования hot module replacement очень прост. Авторы предлагают создать файл style.css с одним стилем:
body {
background: red;
}
И файл entry.js, который использует основную фичу webpack, команду require, чтобы добавить .css файл к содержимому страницы. Ну и создает элемент типа input, на котором можно проверить hot module replacement:
require("./style.css");
document.write("<input type='text' />");
Далее предлагается запустить webpack с помощью заклинания
webpack-dev-server ./entry --hot --inline --module-bind 'css=style!css'
И открыть страницу, доступную по адресу localhost:8080/bundle. После чего можно наблюдать магию hot module replacement: если ввести в поле input какой-нибудь текст, переместить курсор на один из символов этого текста, а затем поменять цвет в файле style.css — то цвет фона страницы поменяется практически сразу, при этом не потеряется введенный текст и даже позиция курсора останется прежней.
Хорошая, удобная магия. Но после начала использования возникает много вопросов:
- Что это за ./entry?
- Что делают и чем отличаются --hot и --inline?
- Что это за --module-bind такой?
- Почему, если добавить react.js, hot reload перестает работать?
Начнем с самого простого. /entry и --module-bind — это читерские аргументы, которые позволяют в целях демонстрации запускать webpack без конфигурационного файла webpack.config.js. Первый позиционный аргумент всего лишь имя javascript файла, являющегося «точкой входа» в программу, именно его код будет запускаться при выполнении скомпилированной bundle. Многих разработчиков смущает то, что этот аргумент не выглядит как имя файла. На самом деле это имя файла. Просто в целях экономии символов авторы примера воспользовались одной из особенностей webpack: файлы в require и в командной строке можно указывать без расширения, webpack автоматически попробует найти такой файл .js (или с другими расширениями, если это настроено в конфигурации). Аргумент --module-bind позволяет без конфигурационного файла указать использумые загрузчики, в данном случае для файлов с расширением css будет использован сначала загрузчик css-loader а затем загрузчик style-loader. Как нетрудно догадаться, суффикс -loader тоже можно не указывать, и авторы примера польщуются этим для экономии нескольких символов и запутывания читателей.
На самом деле у webpack три режима работы автоматического обновления страницы. Самый простой режим называется iframe mode: он включается автоматически, если webpack запустить без ключей командной строки --inline и --hot, то есть вот так:
webpack-dev-server ./entry --module-bind 'css=style!css'
Запущенный веб сервер будет отдавать браузеру следующие страницы:
- localhost:8080/webpack-dev-server: Покажет меню, в котором можно посмотреть исходик созданной в памяти bundle или открыть в браузере специальную html страницу, единственное назначение которой — выполнить javascript код bundle
- localhost:8080/webpack-dev-server/: от предыдущей ссылки отличается присутствием слеша на конце. Список файлов в папке, где запущен сервер. Клик по файлу покажет его в iframe и будет автоматически перезагружать, если файл изменится.
- localhost:8080/webpack-dev-server/bundle: та самая страница из первого пункта. Открывается в iframe и автоматически перезагружается. Будет автоматически перезагружаться при изменении любого файла, который приводит к перекомпиляции bundle
- localhost:8080/ и localhost:8080/bundle: ловушка для невнимательных. То же что в первом и третьем пункте, но файлы и bundle открываются не в iframe. Перезагружаться не будет. Зачем она? Для второго режима работы, --inline. Зачем показывать в первом режиме работы? Чтобы запутать разработчиков, конечно же. Ну и чтобы раздавать статику без iframe.
Второй режим работы активируется ключем командной строки --inline и предсказуемо называется «inline» режимом. В этом режиме все несколько сложнее: в bundle добавляется модуль «refresh client», исходный код которого можно посмотреть в файле webpack-dev-server/client/index.js. Этот модуль будет загружен с помощью require перед вашим собственным кодом. Более того, если посмотреть в сгенерированный bundle (с помощью меню веб сервера, о котором я писал выше), то можно увидеть что этот require не совсем обычный:
/* WEBPACK VAR INJECTION */}.call(exports, "?http://localhost:8080"))
Это результат выполнения вот такого кода:
require("index?http://localhost:8080")
Этот слабодокументированный синтаксис «webpack resource query» позволяет передавать произвольные параметры в загружаемый через require код. В данном случае webpack-dev-server генерирует bundle, который при загрузке refresh client передает ему адрес запущенного на машине разработчика webpack-dev-server. Зачем ему адрес? Конечно же чтобы подключиться к нему через socketio и ждать нотификации об изменениях файлов. Получив такую нотификацию, refresh client перезагрузит страницу. По сути происходит то же что и с iframe, но без iframe. Это позволяет отлаживать чувствительный к url код и используется как вспомогательный механизм для третьего, самого интересного режима работы: hot module replacement
Как уже догадался внимательный читатель, третий режим работы включается добавлением ключа командной строки --hot, который возвращает нас к тому заклинанию, с которого началась эта статья. Но здесь не все так просто. «Hot module replacement» — это функциональность webpack, предназначенная не только для быстрой подгрузки изменений на машине разработчика, но и для обновления сайтов в production. При использовании ключа --hot и webpack-dev-server, и webpack собирает bundle с поддержкой hot module replacement: соответствующий код и api добавляется в загрузчик webpack, за это отвечает HotModuleReplacementPlugin. С помощью этого api разработчик может запрашивать свой сервер на предмет «а не обновилось ли что», отсылать команду «обновись» дереву модулей и управлять тем, как модули обновляются без перезагрузки страницы. Здесь два ключевых момента:
- Код, который узнает о факте обновления, должен написать разработчик. Webpack считает хеши модулей и предоставляет ajax api для загрузки обновления с сервера — но вызвать метод module.hot.check разработчик должен сам. Это не навязывает какой-то способ общения с сервером и позволяет разработчикам интегрировать hmr в существующие проекты: узнавать о наличии обновлений можно любым способом, начиная от кнопки «проверить обновления» с ajax запросом и заканчивая websocket подключением от страницы к backend.
- Webpack не обновляет модули сам. Он дает модулям возможность подписаться на callback module.hot.accept, module.hot.decline и module.hot.dispose чтобы реагировать на полученное от сервера обновление своего кода. К примеру, код модуля, отвечающего за загрузку css, может применить обновленные стили. А код модуля, создающего интерфейс ReactJS, вызвать новую версию render(), чтобы перерисовать себя.
Учитывая эти два момента, просто добавление кода hot module replacement ничего не даст — только увеличит размер bundle на несколько килобайт. Нужен еще код, который будет общаться с сервером, узнавать о наличии обновлений и вызывать module.hot.check. И такой код есть! webpack-dev-server, запущенный с ключем --hot, добавляет в собираемый bundle модуль «hot loader», исходник которого можно посмотреть в файле webpack/hot/dev-server.js. Этот модуль, так же как модуль «refresh client», будет загружен перед вашим кодом. Делает он интересную штуку: подписывается на dom event с именем webpackHotUpdate и при получении этого эвента использует hot module replacement api для обновления дерева модулей. Если модули не обновились (то есть в модулях либо нет кода обновления, либо код вернул статус невозможности обновиться), то hot loader перезагружает страницу целиком.
А кто же отсылает эвент webpackHotUpdate? Это делает «refresh client». Тот самый, который добавляется ключем --inline, поддерживает websocket подключение к webpack-dev-server и следит за изменениями файлов. При использовании ключа --hot, webpack-dev-server отправляет refresh client по websocket сообщение «hot», которое переключает refresh client в «hot mode». В этом режиме он перестает обновлять страницу сам, а вместо этого отсылает эвент webpackHotUpdate.
Последний вопрос: откуда берется код, который обновляет CSS стили? Как я уже написал выше, webpack сам ничего обновлять не будет и просто вызовет callback, на который может подписаться модуль. Откуда там этот callback? Сюрприз — style-loader имеет встроенную поддержку «hot module replacement». Специально для того, чтобы работал пример из документации.
- Если hot module replacement не работает — проверьте что выбран привильный режим и что используемые loader'ы его поддерживают. «refresh client» и «hot loader» отчитываются в лог о происходящем.
- Если вместо изменения части страницы она перезагружается целиком — тоже смотрите в лог, там вам расскажут какой из модулей не смог hot module replacement
- Технологию можно использовать не только при отладке на машине разработчика, для этого нужно будет реализовать на стороне клиента и сервера то, что за вас делает webpack-dev-server
- Поддержку hot module replacement можно добавлять в свои модули и радоваться мгновенному обновлению страницы без перезагрузки во время разработки. Соответствующее api довольно простое и неплохо документировано.
This entry passed through the Full-Text RSS service - if this is your content and you're reading it on someone else's site, please read the FAQ at http://ift.tt/jcXqJW.
Комментариев нет:
Отправить комментарий