misc/class
lib/jquery_pnotify, lib/moment, lib/lodash, misc/notification, site/engine, misc/social
if( $.browser.msie && $.browser.version <= 8 ) include('lib/respond'); $._social.__cfg = {"init":[{"service":"basic"},{"fb_app_id":"1997094873850041","service":"fb"},{"vk_app_id":"2978320","service":"vk"},{"service":"twi"}],"like":[{"service":"fb"},{"service":"vk"},{"via":"","channel":"","hash_tag":"","service":"twi"}]}; window._SiteEngine = new classes.SiteEngine( { user_id: 0, controller: 'content_tape', action: 'view', content_css_version: '1459538664', social_enabled: 0} );

Faiwer

Блог web-программиста

Тернистый путь расширений в Chrome

3 ноября 2017

Довольно часто я сталкиваюсь с тем, что сторонние сайты работают не совсем так, как я бы хотел. И будучи web-программистом я пытаюсь на это дело как-то повлиять. Но одно дело, скажем, стили сайту подправить, и совсем другое вмешаться в его работу JavaScript-ом. Эта заметка о втором.

Итак. На этот раз жертвой пал puzzle-english. Ресурс для изучения английского. Среди прочего там есть возможно уникальная возможность тренировать аудирование на небольших заранее заготовленных фразах, записанных от нескольких дикторов. Предлагается на выбор по 3-5 слов на каждое произнесённое слово. Итого ты или кликаешь по кнопкам мышью, собирая фразу, или с клавиатуры клавишами цифр. Всё бы ничего, если бы не отсутствие поддержки чисел с num-панели. Поддерживаются только те, что над буквами. Что, лично для меня, страшно неудобно. Ну что ж, приступим.

Для начала я решил узнать как оно там вообще работает. Посмотрел все eventListener-ы на document и нашёл нужный методом перебора при debug-е. Он слушает keydown и несколько неуклюже обрабатывает event.which, поддерживая только один диапазон для чисел. Поиграл с ним и понял, что при "патченном" which num-цифры работают прекрасно. Написал небольшой скрипт в консоли примерно следующего толку и решил что дело в шляпе:

document.addEventListener('keydown',  evt =>
{
	if(evt.which > 96 && evt.which < 105)
	{
		const which = evt.which - 48;

		$.event.trigger(
		{
			type: 'keydown',
			which,
			originalEvent: { target: document.body }
		});
	}
});

Забыл упомянуть, puzzle-english агрессивно использует jQuery. Чем я не применул воспользоваться. Там ведь свои обёртки и свои рецепты. Обернул этот код расширением и... Обломался. Chrome не позволяет использовать со страницы жертвы ничего кроме DOM. Т.е. доступа к jQuery нет, доступа к любым кастомным window-полям тоже нет. Ничего нет. 

Ну не беда, подумал я и решил организовать keydown средствами браузера. Что у нас там есть? document.createEvent и initKeyboardEvent. Поковырялся — работает плохо. Да ещё и deprecated. Ок, а как ещё можно? Ага, у нас есть класс KeyboadEvent. Ok:

new KeyboardEvent('keydown', { which: 50 });

Ага. Нифига. В документации одно, в браузере другое. Плевал он на which и прочие поля. Что пишут нам интернеты? Ух, дичь какая... Зато работает:

window.evt = evt = document.createEvent("KeyboardEvent");
evt.initKeyboardEvent('keydown',
	true, false, null, 0, false,
	0, false, which, 0);
evt.__defineGetter__('which', () => which);
document.dispatchEvent(evt);

Правда только из консоли. Почему? Потому что браузер заменяет отправленные из расширения события их копиями в основной тред. В целях безопасности. И того мы снова теряем which.

Что в итоге? В итоге вспомнил, что там ещё мышью можно было кликать. Стало быть можно эмулировать клик. Просто вызвав у DOMElement-а метод click. С этим уже граблей не было.

В общем мораль такова: расширения скованы цепями. Попытки сделать что-то просто — могут упереться в открытые баги или просто запреты. Так и живём ;)

Лигатуры

1 ноября 2017

Иногда на чужих скриншотах я замечал, что привычные программистам конструкции вроде === и != могут быть отображены одним изящным глифом, вместо трёх разных. А тут наткнулся на то, что у человека такие штуки заработали в SublimeText 3. Стал разбираться как подключить и выяснил:

  1. Эти штуки называются лигатурами. И ваш текстовый редактор (или терминал) должен иметь их поддержку. Большинство современных профессиональных инструментов такую поддержку имеют.
  2. SublimeText 3 обзавёлся такой поддержкой совсем недавно. С версии 3146
  3. Необходимо установить в систему шрифт с поддержой лигатур. Варианты: Fira Code, MonoidIosevka и др. В целом их мало.
  4. В sublime-е шрифт меняется настройкой font_face.

О остановился на Fira Code. Начертание не привычное, оно отличается от того шрифта, что используется в st3 по умолчанию. Но в целом очень даже ничего. Вот его рекламная брошюрка:

Простейший способ выгрузить все свои слова с lingualeo

14 мая 2017

Рецепт не для программистов, но относительно опытных пользователей ПК:

  1. Открываем раздел Dictionary (Словарь) на lingualeo
  2. Проматываем словарь до упора вниз, чтобы сайт отобразил все слова (он их лениво подгружает)
  3. Открываем консоль разработчика (F12 или Ctrl+Shift+I)
  4. Выбираем в открывшейся панели таб "Console"
  5. Вставляем туда следующую команду: copy(Array.from(document.querySelectorAll('.dict-item-word')).map(el => [el.getAttribute('data-word-value'), el.querySelector('.translates').innerText]))
  6. Открываем этот или любой подобный сайт (который умеет конвертировать JSON в нужный вам формат)
  7. Вставляем в текстовую область через Ctrl+V (Cmd+V) текст из буфера обмена (в п.5 туда были помещены все слова в формате JSON)
  8. Кликаем по ссылке "Download the entire CSV" и сохраняем получившийся файл куда вам нужно (можно пересохранить его в нужный формат, поменять кодировку и пр. (по умолчанию UTF8)).

В случае если разработчики поменяют вёрстку ― п.5 поломается :-) Увы

Конфигурация mocha и mocha.opts

30 апреля 2017

Случайно наткнулся на то, что можно вынести command line parameters для консольного запуска mocha в отдельный файл test/mocha.opts. Но тут же наткнулся на грабли. Файл должен лежать именно в test/mocha.opts, а у меня директория тестов называлась tests, и этого хватило. Т.е. механизм поиска файла сильно уступает аналогичному для, скажем, .eslintrc или .gitignore. Сам формат файла простой, пример:

--require test/setup.js
--compilers js:babel-core/register

Теперь mocha можно запускать без параметров. Интересная выресовывается картинка, много разных вспомогательных файлов: mocha.opts, .babelrc, .eslintrc, .gitignore, webpack.config.js, README.md, package.json и пр. шушера. А ведь не так давно мне было привычно размещать исходники прямо в корневой директории проекта, теперь же нужно им отдельную директорию создавать.

  • Во-первых чтобы они не утонули среди вспомогательных файлов
  • Во-вторых т.к. всё равно нужно куда-то итоговую сборку собирать
  • В третьих таким образом удобно избегать node_modules (к примеру для поиска по файлам проекта)

Написание тестов для React с использованием enzyme

29 апреля 2017

Сразу отмечу, что статья не претендует на истину. Это всего лишь мои заметки о том, как вообще можно написать и запустить простые тесты для React, т.к. тут оказалось много места где заблудиться\окопаться. Итак, начнём.

Babel

Раз React значит и JSX, а значит и babel. Но простой запуск mocha просто запускает nodejs, в котором никакого JSX нет (и наверное никогда не будет). Что делать? Поиск подсказывает, что нужно запускать mocha (далее речь идёт именно о mocha) с флагом --compilers js:babel-core/register. Что сие есть? Это флаг заставит mocha предварительно компилировать код используя babel-core/register, который грубо изнасилуетподменяет require() в nodejs, что позволяет ему предварительно изуродоскомпилировать кодовую базу в нормальный javascript

Import\Export и .babelrc

Ок, но как babel-ю подсказать, что использовать в качестве конфига? Точного внятного ответа пока дать не могу, но как минимум можно создать в корне json-файл с именем .babelrc, который будет содержать json-конфиг. Мне прокатило, но в более сложных ситуациях нужно будет искать вариант поделикатнее. Собственно, чтобы не дублировать конфиг, имеет смысл подключить его и в webpack.config.js. Сразу отмечу, что взять и распарсить .babelrc как json простым вызовом require() nodejs почему-то не смогла (видимо мешает отсутствие расширения .json), поэтому fs.readFileSync и JSON.parse вам в помощь. 

Полифилы, JSDom и прочая нецензурщина

nodeJS это nodeJS, а не браузер, а для ряда тестов потребуется имитация браузера. И похоже, что на данный момент изящных решений нет, т.к. кодовая база react-* грубо сверяется с глобальным наличием window & document. Мде. Помимо прочего всякие полифилы, вроде regenerator-а (ну или отключить транспайлинг async-await в генераторы ещё до этапа компиляции) нужны. 

Для решения вопроса с полифилами достаточно import 'babel-polyfill'. а вот с JSDom так просто не срастётся. Я воспользовался таким решением:

  • import 'jsdom-global/register';
  • принудительно зафиксировал jsdom на 8-й версии (а не 10-й)

Замечу, что жизненноважно подключать этот костыль с JSDom до подключения React, иначе груды неадекватных глупых ошибок без внятных описаний украсят вашу безмятежную жизнь. Суть в том, что в недрах разных react-либ window кешируется ещё на этапе подключения файлов. И в последствии какой-нибудь флаг вида canUseDom === false будет вам яростно мешаться.

Можно подключать эти либы в каждом тест-файле, но тогда вы столкнётесь с тем, что при запуске сразу пачки тестов, babel-polifil будет падать со словами "я уже подключён", jsdom тоже будет что-нибудь базлать. Разумная альтернатива вынести обе эти строки в некий setup.js, а потом его подключать флагом --require tests/setup.js.

Сами тесты

React поставляет в пакете react-dom ряд инструментов для тестирования. Но даже на официальной странице этих инструментов упоминается, что вам стоит воспользоваться некой либой под названием enzyme. Установка оказалась не совсем тривиальной, т.к. сильно зависит от версии вашего React-а. Очень внимательно прочитайте инструкции по установке. Заодно доставьте к нему что-нибудь вроде chai для удобства тестирования.

Отмечу, что enzyme предоставляет по сути 3 набора утилит для работы с вашими React компонентами. А именно shallow для быстрого и поверхностного рендеринга, render для работы уже с html, и mount для полноценной работы с DOM и life-cycle React-компонентов. В любых маломальски сложных ситуациях готовьтесь к танцам с бубнами. Например .simulate метод для вызова событий умеет только React-события, а повешанные через addEventListener уже нет. Готовьтесь к нефинормативным ошибкам, куда же без них. 

Примеры кода для тестов можно посмотреть здесь. По правде говоря я, на данный момент, не в восторге ни от самой либы, ни от её документации.