Поиск по метке: JavaScript
Embedded webpack application
Возникла необходимость внедрять готовый SPA в другой проект на страницу. Проект сразу писался под это дело, но всё равно возник ряд проблем, которые нужно было решать. Напишу про 2 из них сюда как шпаргалку.
webpack & dynamic import
Что если ваш проект использует динамический импорт? Например System.import. В этом случае webpack сделает для подгружаемых файлов отдельный chunk. И грузить он его будет исходя из publicPath в настройках webpack. Но ведь embedded решения могут быть внедрены по любому пути. Как быть? Всё просто, пишем в точке входа что-нибудь типа: __webpack_public_path__ = window.myPredefinedPath и дело в шляпе. Магия. До загрузки приложения нужно определить этот window.myPredefinedPath, webpack его увидит и будет использовать. Сразу отмечу, что надо писать как есть, не window.__webpack...=, а как-будто эта переменная уже определена в коде. Если на неё ругается eslint не беда, пишем: // eslint-disable-line и он более не ругается.
webpack css & url
Возникла проблема с тем, что в стилях указаны пути к шрифтам так, что webpack их найти не может (отдельная тема почему так). Да и не должен, него его это забота. Однако он пытается от-revolve-ить все пути, что находит в css. Лечится установкой url: false в cssLoader-е. После этой настройки пути отдаются как есть без каких-либо проверок.
React 16 & Context decorators
С обновлением React до 16 версии мы получили новый вид контекста, на замену старому. Теперь это не мутное API, как было раньше, а утверждённый вариант, с которым можно смело в бой. В чём же ключевые различия?
- Старый вариант был многословным. Новый вариант компактнее.
- Старый вариант не обновлял связанные с контекстом компоненты при изменении контекста. По сути контекст должен был быть имутабельным. Новый вариант обновляет связанные с ним компоненты.
- Старый вариант можно было использовать в любом методе компонента. Новый только в render-е.
- Старый вариант не имел пенальти по производительности и не требовал использования HOC-ов, не увеличивал иерархию древа. Новый вариант всё это делает. Скажем для connect-а из redux-а потребуется аж 2 дополнительных уровня иерархии.
- Старый вариант был оформлен в виде static полей класса, новый вариант в виде function as tag content. И то и другое слегка уродливо. Или не слегка. JSX в целом страшненький. А тут...
В итоге послевкусие специфическое. С одной стороны API хотя бы устаканили. С другой стороны сделали его каким-то очень неудобным и вообще кривоватым. Такое впечатление возникает в целом, глядя на новые возможности из 16+. Но тут я остановлюсь на работе с контекстом.
Писать вот так:
render()
{
return <Consumer>{arg1 =>
<Consumer2>{arg2 =>
<div>/* some code with them */</div>
}</Consumer2>
}</Consumer>
}
… невыносимо. Хочется удобства. В итоге это вылилось в такой вариант:
@renderContext('arg1', 'arg2')
render(arg1, arg2)
{
return <div>/* some code with them */</div>;
}
По сути я воспользовался декораторами из ES7, которые пока под некоторым вопросом. Но т.к. JSX в стандарте тоже нет, то плевать. Что делает данный декоратор? Он оборачивает метод render обёртками из кода выше. Но делает это автоматически. А в исходный render метод передаёт все контексты как аргументы функции в том же порядке.
Никакой магии в этом нет и все эти обёртки над обёртками никуда не испарились, но теперь хотя бы скрыты с глаз долой. Помимо прочего я написал ещё несколько декораторов. Один для <Provider/>-ов, один для HOC, один для удобтва i18n. Чувствую в декораторы затянет меня с головой. Ух злая эта штука. Это не к добру :)
Сам декоратор устроен вот так:
const renderContext = (...keys) =>
function(target, methodName, descriptor)
{
const render = descriptor.value;
descriptor.value = keys
.map(key => list[key].Consumer)
.reverse()
.reduce((inner, Consumer) =>
{
return function(...args)
{
return <Consumer>
{arg => inner.call(this, ...args, arg)}
</Consumer>;
};
}, render);
return descriptor;
};
Забирайте кому надо ;)
onMouseMove & movementX & movementY
Ковыряясь с доступными в Event полями я наткнулся на два незнакомых, которые ныне есть во всех современных браузерах. А именно: movementX & movementY. Показывают сдвиг мыши относительно предыдущего вызова callback-а. Чертовски удобно, т.к. с ними можно не считать эти сдвиги вручную. Эх, ещё бы выдали штатную возможность узнать положение мыши относительно заданного DOMElement-а без груды кода с offsetTop-ми и прочими костылями.
Тернистый путь расширений в Chrome
Довольно часто я сталкиваюсь с тем, что сторонние сайты работают не совсем так, как я бы хотел. И будучи 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. С этим уже граблей не было.
В общем мораль такова: расширения скованы цепями. Попытки сделать что-то просто — могут упереться в открытые баги или просто запреты. Так и живём ;)
Простейший способ выгрузить все свои слова с lingualeo
Рецепт не для программистов, но относительно опытных пользователей ПК:
- Открываем раздел Dictionary (Словарь) на lingualeo
- Проматываем словарь до упора вниз, чтобы сайт отобразил все слова (он их лениво подгружает)
- Открываем консоль разработчика (F12 или Ctrl+Shift+I)
- Выбираем в открывшейся панели таб "Console"
- Вставляем туда следующую команду: copy(Array.from(document.querySelectorAll('.dict-item-word')).map(el => [el.getAttribute('data-word-value'), el.querySelector('.translates').innerText]))
- Открываем этот или любой подобный сайт (который умеет конвертировать JSON в нужный вам формат)
- Вставляем в текстовую область через Ctrl+V (Cmd+V) текст из буфера обмена (в п.5 туда были помещены все слова в формате JSON)
- Кликаем по ссылке "Download the entire CSV" и сохраняем получившийся файл куда вам нужно (можно пересохранить его в нужный формат, поменять кодировку и пр. (по умолчанию UTF8)).
В случае если разработчики поменяют вёрстку ― п.5 поломается :-) Увы