Как зеленый джуниор свой hot-reloader писал

от автора

Предыстория

Пару строк обо мне для общего понимания уровня автора и решаемой проблемы.

Меня зовут Евгений и я веб-разработчик зеленый junior frontend developer.
Еще какой-то год назад я работал в совершенно другой сфере и только в теории задумывался о смене профессии, но примерно в декабре 2018 нашел свое и начал действовать.
Примерно через полгода тотального обучения я устраиваюсь работать frontend-программистом. За плечами обучение фундаментальным вещам(мне хочется так думать), js, взаимодействие с DOM, react+redux. HTML и CSS самый минимум+ общее понимание о bootstrap и сборке, работа с git, командной строкой.
Помимо теории сделано пара учебных проектов, в том числе чат на react+redux, а так же пара попыток реализации каких-то своих задумок.
В общем, такой себе стандартный современный джентельменский набор для начинающего front’a.
Первые полторы недели настраиваю виртуальную машину, там куча всего и все мне незнакомо и непонятно.
По ходу дела знакомлюсь с новыми инструментами и технологиями: с базами данных(и ставлю себе очередную закладку в список «выучить»), putty, wincsp и пр.
Успешно прохожу эту полосу препятствий и перехожу к фронту.

Предисловие

Уже написав свой релоадер и эту статью, я нашел аналоги в том числе на Хабре. Однако все-таки решил опубликовать свой велосипед.

Начало

У нас довольно большой проект, доставшийся в наследство, написанный на angularJS, со всеми его прелестями. Мне после React’а он показался динозавром, но ничего, покупаю курсы по angularjs, быстро въезжаю и начинаю приносить пользу.

Положительное впечатление- проект написан хорошо, людьми с явно прямыми руками. Переменные с отличным понятным именованием, строение везде одинаковое и в целом вся логика весьма доступно и просто выражена.

Но и минусов хватает.

Первая проблема: проект собирается каким-то древним минимизатором и использовать современный синтаксис js нельзя. Никаких () => {}, const res = […data, subRes], async/await…

Вторая проблема: нет ни webpack, ни даже хотя бы gulp, а соответственно нет и привычного мне webpack-dev-server c его прекрасным hot reload.

Написал. Сохранил. F5. Неудобно. Боль? Не прям боль, но очень неудобно.

Третья проблема: сборка проекта .bat файлом, в котором часть проекта просто копируется, часть библиотек собираются без минимизации, часть минимизируются в один файл, остальные файлы проекта-в другой. Список библиотек в третьем файле. Список файлов для сборки в четвертом. И так далее.

Четвертая проблема: все библиотеки аккуратно лежат в папочке libs и подключаются скриптом в index.html. Все-все, кроме express и proxy для него(они не участвуют в сборке, а только для разработки).
И далеко не везде есть версии или указание на конкретную библиотеку.

На обучении я жил в прекрасном мире функционального программирования, полном es6+, webpack-dev-server, tdd, eslint, автоматической сборкой и так далее.

А тут во взрослом мире все совсем по-другому…

Завязка

Но работать мне нравится, препятствия рассматриваю как возможности саморазвития, компания хорошая, обстановка отличная, глаза горят!

В рабочее время выполняю рабочие задачи, в свободное пытаюсь что-то улучшить.

Середина июня, начинаю с попытки прикрутить webpack, но первый подход ожидает полный провал. Неделю мучаюсь, сильно от этого устаю, временно откладываю.

Решаю начать с малого — подключаю новый синтаксис через babel. Дописываю в наш волшебный build.bat первоначальную обработку babel’ем, но что-то ломает идиллию и наш старый минификатор спотыкается. Ищу проблему.

Спотыкается на одной из библиотек из аккуратной папочки libs. Смотрю файлы библиотек: они уже минифицированы и в старом синтаксисе.

Говорю babel — «ты сюда не ходи… код башка попадет, совсем плохо будет». Проверяю: все работает! Ура! Теперь мне доступны все те приятные новые стильные модные молодежные штуки! Первая победа. Приятно. Думаю по такому случаю переименовать скрипт в e.bat(e-Evgen), но не решаюсь.

Новый синтаксис так знаком и приятен, но мысли о кривой сборке не покидают меня.

Конец июня-начало июля. Делаю второй подход, более основательный, но снова упираюсь в ошибки между webpack и angularjs. Снова неделя изысканий.

Как-то раз провожу несколько дней и частично ночей за поиском решения, натыкаюсь на крайне интересные выступления с конференции HolyJS, где ребята уже довольно глубоко копают в webpack. Продвигаюсь в его понимании, но материал пока не понимаю до конца.

Интерес укрепляется.

Коллега говорит — забей, проект сдавать через пару месяцев, уже не нужно этим заниматься.
Не забиваю, но откладываю — много работы, она выжимает меня всего, на внеклассные занятия времени пока что не остается.

Середина июля, мне в руки попадает похожий на наш проект с настроенной сборкой. Иду с ним в третий подход и практически настраиваю у нас webpack, но в конце ловлю новые ошибки, на решение которых времени уже не хватает, работа накатывает с новой интенсивностью + морально меня это опустошает, вновь откладываю это дело.

Основная часть

Середина августа. В итоге приятель рассказывает про изучение node.js и его желание написать собственный hot-reloader. Мысли о нашей сборке вспыхивают у меня с новой силой.

Задача: reload текущую страницу при обновлении файлов в проекте.
Особенности: все библиотеки подключаются в index.html, нельзя require, не говоря уже об import. Сборка перед reload пока не нужна, только reload. В сервере для разработки, который проксирует запросы на наш бэк, пакеты использовать я могу, а так же могу require!

Все это происходит в пятницу и я решаю, что в упрощенном варианте для нашего проекта мне вполне по силам реализовать технологию, которая избавит меня и моих коллег от F5.

Мыслительный процесс идет и в голове формируется видение решения.
Простейший сервер(как у нас), в нем я обойду всю папку и подпапки и сформирую дерево с датами изменений каждого файла.

Далее через каждые n миллисекунд буду обходить еще и еще и сравнивать значения времени изменений. Изменилось — reload. Приятель подсказывает — «не изобретай велосипед, есть watch в node.js». Отлично, буду использовать его. В server.js настрою watch за папкой проекта и по изменению чего-то внутри буду вызывать location.reload().

Первая итерация:

server.js

var express = require('express'); var app = express(); var server = require('http').Server(app);   const port = 9080; server.listen(port); app.use(express.static(__dirname + '/src')); location.reload(). 

Первая проблема — location- это не переменная node.js(в этот момент я обретаю понимание, почему мои попытки обращения к process.env на фронте тоже были безуспешны))).

Вторая проблема — как дать front’у понять, что нужно делать reload?

Выход — websocket! Идея заманчива + я с ними работал «на пол-шишки», когда писал чат, общее представление имею. Заодно делаю счетчик перезагрузок за сессию, добавляю переменную и обработку отдающему ее запросу.

Пробую:

server.js

var express = require('express'); // Подключаем express var app = express(); var server = require('http').Server(app); // Подключаем http через app var io = require('socket.io')(server); // Подключаем socket.io и указываем на сервер var fs = require('fs');  const port = 9080; server.listen(port); app.use(express.static(__dirname + '/src'));  let count = 0; app.get('/data', (req, res) => {   res.data = count;   res.send(`${count}`); })  const dir = './src'; fs.watch(dir, () => {   io.emit('change', {data: count});   count += 1; }) 

На фронте делаю простейший App на angularjs

app.js

angular.module('App', []) .controller('myAppCtrl',['$scope', '$timeout','$http', ($scope, $timeout, $http) => {     $scope.title = 'Страничка для тестирования простейшего хот релоада без пересборки';     $scope.count = 0;     $scope.todo = [         'прикрутить рекурсивность папок,поискать стандартные методы',         'проверить на отслеживание node.js watch файлы других типов',         'периодически проходить отслеживаемую папку и смотреть,не появились ли в ней или вложенные файлы и папки',         'прикрутить линтер, codeclimate и travis к этому проекту'     ]     $scope.marks = [         'watcher не смотрит рекурсивно на каталоги внутри'     ]      // var socket = io();     //     socket.on('change', (data) => {     //         console.log(data.data);     //         $scope.count = data.data;     //         console.log('scope.count: ', $scope.count);     //         $scope.$digest();//     //         location.reload();//agfr     //     })      $http({method: 'GET',url: 'data'})     .then(response => {         $scope.count = response.data;//     }); }]) 

И отдельный модуль, который ее reload. Отдельный, чтобы в проект лишнего не попадало.

watch.js

var socket = io();         socket.on('change', () => {             location.reload();          }) 

Работает! Файлы кроме js тоже отслеживает(мало ли!): проверял .json, .css.
Проверяю вложенные папки — не работает.

Думаю, ладно, сейчас запилю рекурсивно. На всякий случай гуглю и — вуаля- есть готовое
решение.

Добавляю этот пакет.

server.js

var express = require('express'); // Подключаем express var app = express(); var server = require('http').Server(app); // Подключаем http через app var io = require('socket.io')(server); // Подключаем socket.io и указываем на сервер var fs = require('fs'); var watch = require('node-watch');  const port = 9080; server.listen(port); app.use(express.static(__dirname + '/src'));  let count = 0; let changed = []; app.get('/data', (req, res) => {   res.data = count;   res.send({count, changed}); }) const translate = {   remove: "удален",   update: "изменен" } watch('./', { recursive: true }, function(evt, name) {   io.emit('change', {data: count});    count += 1;   changed = [{name, evt}, ...changed]; }); 

Ура, работает!

Вспоминаю про eslint, codeclimate и travis.
Устанавливаю первый, добавляю остальное.
Подчищаю код, все var на const и так далее.

Linter ругается, что angular is not defined, но у меня особенности подключения библиотек в проекте диктуют такое поведение, отключаю. Заодно немного прикручиваю переменные из командной строки, запускаю, все работает!

В пн пришел на работу и прикрутил все это хозяйство на рабочий проект. Пришлось немного изменить, заодно внести правки, чтобы можно было менять некоторые параметры запуска из командной строки и исключения, чтобы не вотчил все подряд.

В итоге получилось вот так:

server.js

const express = require('express'), 			http = require('http'), 			watch = require('node-watch'), 			proxy = require('http-proxy-middleware'), 			app = express(), 			server = http.createServer(app), 			io = require('socket.io').listen(server), 			exeptions = ['git', 'js_babeled', 'node_modules', 'build', 'hotreload'], // исключения,которые вотчить не надо, файлы и папки 			backPortObj = { /* перечень машин,куда смотреть за back*/ }, 			address = process.argv[2] || /* адрес машины с back*/, 			localHostPort = process.argv[3] || 9080, 			backMachinePort = backPortObj[address] || /* порт на back машине*/, 			isHotReload = process.argv[4] || "y", // "n" || "y" 			target = `http://192.168.${address}:${backMachinePort}`, 			str = `Connected to machine: ${target}, hot reload: ${isHotReload === 'y' ? 'enabled' : 'disabled'}.`, 			link = `http://localhost:${localHostPort}/`;  server.listen(localHostPort); app .use('/bg-portal', proxy({   target,   changeOrigin: true,   ws: true })) .use(express.static('.'));  if (isHotReload === 'y') {   watch('./', { recursive: true }, (evt, name) => { 		let include = false; 		exeptions.forEach(item => { 			if (`${name}`.includes(item))	include = true; 		})  		if (!include) { 			console.log(name); 			io.emit('change', { evt, name, exeptions }); 		}; 	});  };  console.log(str); console.log(link); 

app.js

var socket = io.connect();  socket.on('change', ({ evt, name, exeptions }) => { 		location.reload(); }); 

запускающий скрипт в package.json просто вызывает server.js из-под node и запускается это вот так:

npm start 1.100 8080 

Написал. Сохранил.F5

В заключении хочу поблагодарить Ваню, моего друга, местами вдохновителя и главного пинателя, а так же Сашу — человека, которого я считаю своим наставником!

Вместо послесловия

А через 2 недели, в последний день своего испытательного срока, я таки прикрутил webpack и webpack-dev-server на наш проект, отправив тем самым свой hot reloader пылиться на полку истории.

Однако эти 2 недели мы использовали его каждый день и он исправно делал свое дело!


ссылка на оригинал статьи https://habr.com/ru/post/466237/


Комментарии

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *