Предыстория


Пару строк обо мне для общего понимания уровня автора и решаемой проблемы.
Меня зовут Евгений и я веб-разработчик зеленый 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 недели мы использовали его каждый день и он исправно делал свое дело!

Комментарии (31)


  1. shaman4d
    04.09.2019 21:56
    +1

    «Еще какой-то год назад я работал в совершенно другой сфере и только в теории задумывался о смене профессии» — а чем вы занимались?


    1. euhoo Автор
      04.09.2019 22:35

      Продажами занимался последние лет 8.


  1. Dimtry44
    04.09.2019 22:35

    Иногда имеет смысл всякие волшебные функции не специфичные для приложения выносить в app-specific custom browser extension. Тогда можно гораздо больше контроля иметь и более вкусные функции реализовывать, при этом не смешивая кодовую базу приложения и вспомогательного кода.


    1. euhoo Автор
      04.09.2019 22:38

      Про такую штуку не слышал. Можешь поподробнее?


      1. Dimtry44
        04.09.2019 23:03

        Просто создаёте browser extension с логикой специфичной для вашего приложения. Т.е. это расширение которое знает про ваши environments, функции и т.п. Можно добавлять и контролировать практически всё что угодно.

        У нас есть расширение с функционалом общим для компании и отдельные практически для каждого приложения, очень помогает, а сделать легко.


  1. alexesDev
    04.09.2019 22:39
    +2

    Hot relead подтягивает только изменения и дает инструменты, чтобы это подхватит. У вас просто reload… Уверены, что https://www.browsersync.io/ не подошел бы?


    1. euhoo Автор
      04.09.2019 23:13

      Бегло погуглил: да, вероятно, подошел бы!
      Но в целом у меня получилась весьма удобная штука, настроенная конкретно под наш проект.
      Кстати, хочу отметить, что сейчас, когда уже настроил и webpack-dev-server, следующим шагом я донастроил сборку так, чтобы осталась возможность использовать мой «велосипед».
      В итоге могу выбирать что использовать: w-d-s или свой велоспед. И я большую часть времени использую велосипед. По 4 причинам:
      1.Обновление у меня быстрее, чем пересборка w-d-s.
      2.В ангуляре есть include в html файл из другого html файла. w-d-s при изменении такого «внутреннего» html файла не релоадит страницу, а мой велосипед-релоадит.
      3.Когда где-то отвалилось, в несобранной версии проще найти что и где.
      Правда, уверен, что 2 и 3 пункты можно докрутить, но пока не крутил.)
      4.Свое. Работает. Приятно)))


      1. funca
        04.09.2019 23:52

        Согласен с предыдущим оратором, у вас получился auto reload. Hot reload это про замену кода "на горячую", т.е. без остановки и перезагрузки приложения — ацкая магия, не уверен, что для angularjs получится это сделать. За исключением этого терминологического казуса, классный опыт и классная статья. Дерзайте, надеюсь на продолжение вашей истории.


        1. euhoo Автор
          05.09.2019 09:48

          Спасибо за слова поддержки. В терминологии пока — да — путаюсь! Следующим шагом есть мысли прикрутить как раз эту ацкую магию. Для начала хотя бы css. В начале статьи я давал ссылку, у ребят получилось, поизучаю их опыт. Надеюсь получится и будет, о чем написать!


  1. Jabher
    05.09.2019 01:14

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

    рискну предположить, что речь об Uglify — из старых популярных минификаторов есть только он и гугловый GCC, а GCC уже поддерживает ES6 довольно давно.


    Я раза четыре в свое время уже спотыкался об это, и каждый раз только через пару недель вспоминал, что у него есть форк Terser (https://github.com/terser-js/terser). API вроде на 100% совместимое — надо просто подменить функцию, которая вызывается.


    Если не угадал — ну… бывает)


    1. euhoo Автор
      05.09.2019 09:35

      yuicompressor-2.4.8


  1. codecity
    05.09.2019 05:08

    Мне после React'а он показался динозавром

    Динозавром или монстром, позвольте уточнить?


    1. euhoo Автор
      05.09.2019 09:39

      Динозавром. Я имел в виду, что в React я увидел технологии, которые в angularjs были в самом зачатке или даже которых еще не было, т.е. под «динозавром» надо понимать «древним вымирающим прародителем».
      Сейчас, когда уже более-менее вник в angularjs, он не кажется мне монстром, даже есть некоторые превосходства(в частности проще обмен данными между компонентами и глобальное хранилище сильно для меня проще, чем redux)


      1. Defersa
        05.09.2019 10:19

        Я вот первым попробовал Vuejs, после него пошел посмотреть React и ужаснулся. С ангуляром все еще хуже?


        1. euhoo Автор
          05.09.2019 12:15

          Не могу ответить, пока нет опыта с Vue.
          А чем ужаснулся в React? Он, вроде, прикольный и не сложный(разве что redux).
          Вообще, чем больше вникаю в Ангуляр 1.x, тем больше понимаю, что он просто другой и тоже вполне себе имеет право на жизнь, хоть и старый.


          1. Defersa
            05.09.2019 12:18
            +1

            (Сейчас ИМХО начинается) Ну по сравнению с Vue, React очень сложен — куча правил, меньшая интуитивность происходящего. И там и там я написал по 1.5 строчке кода, но впечатления у меня остались именно такие. Я вот пишу на чистом JS, и выбирая между ними, 100% отдам предпочтение Vue.


            1. euhoo Автор
              05.09.2019 12:29

              позже, когда будет время, обязательно попробую Vue)


      1. Zeitung
        05.09.2019 18:16

        У меня абсолютно обратные впечатления: переход от AngularJS/Angular2+ к React. Сразу скажу что у нас «кровавый энтерпрайз». То что в Angular«ах делается в 2 строчки в React требует написания собственных wrapper»ов и велосипедов (либо скачивание велосипеда с npm). В Redux так вообще въехать не могу: в пределах модуля Context проще и «out of the box» (ну конечно если из него не делать помойку и применять «правила здравого смысла»), а шерить общее состояния конечно нужно, но reactive approach из Observable«s выпиливает всю „магию“ напрочь и цепочки событий становятся просто супер очевидными.

        Написать код, который будет рендерить всю страницу на каждый чих можно и в React, при должном упорстве…

        Холивар я тут не начинаю, опыта с React ещё не много, наверное не проникся ещё… Хотя, конечно, контроллировать большую часть логики управляющей решением о рендеринге однозначно удобно.

        А ещё мне тяжело доверится инструменту от „тех кто написал самый глючный сайт в интернете“.


        1. euhoo Автор
          05.09.2019 19:45

          может, дело в направлении перехода?)))
          Я вот когда снова вернусь на react, буду пытаться как-то реализовать там модель emit-on, с которой познакомился в ангуляре


        1. Druu
          06.09.2019 08:23

          Написать код, который будет рендерить всю страницу на каждый чих можно и в React, при должном упорстве…

          Так реакт по дефолту и рендерит всю страницу на каждый чих :)
          Упорства требует как раз написать на реакте код так, чтобы он не рендерил. При этом стандартные оптимизации требуют перекраивать бизнес-логику и очень хрупки.


  1. ehots
    05.09.2019 10:36
    +1

    Теперь мне доступны все те приятные новые стильные модные молодежные штуки!

    Хорошо, если есть понимание зачем они вообще нужны, а не просто «сильно, модно, молодежно».
    А так прикольный велик, как минимум хорошо что не задушили инициативу, на легаси такое бывает частенько…


    1. euhoo Автор
      05.09.2019 12:16

      тут 2 момента, по моему скромному мнению:
      1. Чтобы не отстать, приходится все эти стильные молодежные штуки изучать и пробовать
      2. Часто действительно удобно)


  1. thevladmartin
    05.09.2019 12:16

    euhoo, а меня, как человека, пытающегося «вернуться» в айти, больше заинтересовало как вы нашли работу. Я сижу, учу Питон, С++ (раньше кодил на нем, уже давно не использовал), знаю Делфи на уровне хорошего джуниора (тапками прошу не кидаться), работал в сетях и сисадминстве (понятно, что не в компаниях-гигантах), полтора года в ИнфоБезе, самообразовываюсь, но уже полтора года работаю не в своей сфере, вернуться не выходит — не берут никуда, даже до интервью не доходит.

    P.S. События происходят в Польше, я иностранец. А говорили, что им тут айтишников не хватает, обманывали, выходит.


    1. Defersa
      05.09.2019 12:21

      К сожалению (или к счастью) работодателей очень пугает отсутствие знаний/практики в нужных им сферах, и это можно понят: проще взять человека, который 100% подходит, чем брать наугад и ждать его отдачи: от 0 до 150%.


      1. thevladmartin
        05.09.2019 12:24

        И я это могу понять, но хотя бы в сети вернуться, да хоть бы на самую начальную позицию. :\
        И языки вроде знаю, и инструментами пользоваться умею, но…
        Ладненько, пардон, просто крик души уже.


    1. euhoo Автор
      05.09.2019 12:25

      Не знаю, как в Польше, но в РФ(а конкретно я в МО) особых трудностей не возникло.
      Точнее будет сказать, я не считаю это трудностями.
      Я действительно много учился, разбирался. Если что-то не получалось — пытался все равно найти решение. Раскладывал сложную задачу на маленькие решаемые подзадачи.
      Во время обучения была одна задача, из-за которой мне пришлось перепройти предыдущие 2 темы, что заняло 2 недели.
      Мое резюме на hh просматривали примерно по 10-15 раз в день в дни, когда я утром его обновлял и меньше.когда не обновлял.
      Особо никуда не звали(редко), в основном я сам искал компании, которые нравились и сам туда пробивался. Про каждую что-то старался узнать, писал в каждую свое сопроводительное письмо, где описывал свои навыки в соответствии с их требованиями.
      В общем сам в основном действовал. Но тут мои 8 лет в продажниках помогли))
      Ну и не пугался требований, слал даже туда, где требования были завышены.
      По факту именно в такую компанию(где требования в описании к вакансии завышены) я и устроился. На месте оказалось, что в требованиях к вакансии было 80% вообще не того, чем я занимаюсь)))


  1. Druu
    05.09.2019 13:02

    А в чем проблема была взять готовый бойлерплейт для angularjs+webpack и просто перекинуть туда папку src?


  1. EaGames
    05.09.2019 14:23

    После прочтения заголовка я подумал: «О! отлично, узнаю как работает hot-reload».
    После прочтения статьи:

    location.reload()
    эммм…


    1. euhoo Автор
      05.09.2019 14:52

      В жизни вообще все гениальное — просто!


  1. VivAmigo
    05.09.2019 14:32

    Ну а я пока менеджером работаю, решил что ещё не готов для большого Java и С# рынка (отдела которого в нашем маленьком поселении нет). Хочу их прокопать под основание. В программирование ушёл с 5 класса (да здравствует один год на Basic и 4 года на pascal в школе, потом в колледже полгода Pascal, год C++, 2 года Java SE, полгода PHP). И у меня появился вопрос: Вы полностью обучились программированию и верстке за 1 год? Или всё же знаете только основы своей специализации? И как вы поняли, что пора выходить на рынок со своей «продукцией»?


    1. RyDmi
      06.09.2019 13:43

      А действительно можно «полностью» обучиться программированию?