Новогодний IMaskjs 6 — React Native, Pipes, ESM


    Всем привет!


    Я только что выпустил релиз шестой версии библиотеки imaskjs. После каждого мажорного релиза мне кажется, что это последняя версия. Библиотеке уже не один год, она стабильна и достаточно популярна среди сообщества. Что там еще можно сделать да еще и на мажорную версию? Во всех своих проектах я стараюсь делать задачи так, чтобы больше к ним не возвращаться в рамках текущего контекста. Что может измениться в маске? — Браузеры не меняют свои API, javascript все такой же, бизнес задачи все те же — телефоны, карты, числа. Почему нельзя уже остановиться, довести до ума и больше не трогать?


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


    Но серьезных оснований для изменений вроде бы нет. На самом деле есть. В моем случае с маской основные изменения касаются экосистемы javascript: сам язык давно вышел за пределы браузера и распространяется туда, где раньше использовались нативные инструменты, что накладывает новые требования к инструментам. Также появляются новые UI-фреймворки, требующие некоторой адаптации. Хотя мне кажется интерфейс библиотеки сделан максимально просто и удобно, поэтому нет и не может быть проблем с интеграцией вообще ни с чем, во всяком случае в браузере. Но тут я взялся за работу над плагином для React Native.


    Лезем в трубу на React Native


    Насколько я знаю, React Native — это масштабный проект, который живет уже очень давно, и серьезные люди даже использует в проде. Пару лет назад я попробовал использовать RN на одном небольшом тестовом проекте, и на этом моя практика закончилась — я ушел в веб. Приблизительно в то же самое время, я сделал плагин для маски на RN и долго и упорно пытался заставить его работать как мне бы хотелось, но так и не смог. Я нашел несколько багов в репозитории RN на тему обработки ввода и позиционирования курсора, успокоился и решил немного подождать.


    Прошло больше года и я подумал, что пришло время вернуться и уже разделаться с плагином. Оказалось, что за это время не изменилось практически ничего: старые баги были закрыты из-за отсутствия активности, немного изменился интерфейс позиционирования курсора ну и собственно все. Мне бы хотелось думать, что проблема в моей библиотеке, я все поправлю, и заработает. Увы, на мой взгляд маска уже имеет максимально простой интерфейс — большая часть логики вынесена в ядро, для обработки ввода нужно всего лишь два состояния — курсор и значение до изменения и после. По-моему проще некуда, но мне так и не удалось завести RN.


    Одна из сложностей заключается в том, что последовательность событий ввода в RN отличается от браузера. Например в браузере на событие input курсор уже находится в новой позиции, но в RN существует отдельное событие onSelectionChange, которое возникает после изменения значения, причем только на iOS, на Android наоборот. И второй момент — на RN нельзя менять значение в поле в обработчике события изменения значения, то же самое с позиционированием курсора. Поэтому неизбежны задержки в изменении, и как следствие дерганье текста и курсора. Этим багам уже почти три года, поэтому не вижу смысла больше ждать — выкатил плагин как есть, но использовать не советую. Скорее сделал для привлечения внимания общества: может баги поправят в RN, а может кто-то подключит к IMask обработку событий в нативном коде, где таких проблем можно избежать.


    Своя труба


    Теперь немного о хорошем. Маску теперь можно использовать для форматирования и конвертирования:


    const numberPipe = IMask.createPipe({
      mask: Number,
      scale: 2,
      thousandsSeparator: ' ',
      normalizeZeros: true,
      padFractionalZeros: true,
    });
    // `numberPipe` - это обычная функция
    numberPipe('1'); // "1,00"

    Можно переиспользовать одну маску и для работы с вводом и для форматирования, например на форме:


    // используем на элементе формы
    const mask = IMask(el, {
      mask: Number,
      scale: 2,
      thousandsSeparator: ' ',
      normalizeZeros: true,
      padFractionalZeros: true,
    });
    
    // можно использовать pipe напрямую для конвертации значения,
    // если не будет переиспользована
    IMask.pipe(
      1,  // значение
      mask.masked, // достаем маску из обертки
      IMask.PIPE_TYPE.TYPED,  // опционально формат входного значения
      IMask.PIPE_TYPE.UNMASKED // опционально формат выходного значения
    )
    // 1.00

    В Angular плагине для удобства обернул в местный Pipe.
    Опять же максимально просто и функционально, реализация буквально в 5 строк кода — это к слову как показатель удачной архитектуры. Тем не менее по 5 строк кода добавляется уже не первый раз и размерчик всей библиотеки набежал уже серьезный.


    ESM модули


    Пару лет назад когда маска была около 30 Кб я всем говорил, что для такой маски это немного. Сейчас она уже почти 60 Кб и даже мне показалось многовато. Есть одно простое решение — gzip брать только то, что нужно. В идеале сборщик должен автоматически проанализировать зависимости и выбросить все что не используется. Хотя ESM модули есть в маске уже давно, но treeshaking почти не работал, т.к. ссылки на внутренние сущности находились в корневом объекте IMask. Это было сделано из-за присутствия циклических зависимостей внутри маски, а еще, потому что мне хотелось чтобы маску можно было импортировать как один корневой объект, так и отдельно по частям. Например так сделано в React:


    import { Component } from 'react';
    import React from 'react';
    React.Component === Component; // true

    В IMask 6.0 жесткая связь между модулями была разорвана и появилась возможность импортировать отдельные модули:


    // вместо старого подхода
    // import IMask from 'imask'; // импорт фабрики и всех модулей
    
    import IMask from 'imask/esm/imask'; // импорт только фабрики, без модулей
    // подключение необходимых модулей
    import 'imask/esm/masked/number';
    // теперь маска будет понимать числовые маски, но никакие другие доступны не будут
    const numberMask = IMask(element, { mask: Number });

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


    Ну и на закуску: в новой версии появилась поддержка contenteditable и web-компонентов! Спасибо комьюнити за обратную связь и пул реквесты.


    С каждым новым релизом баги становятся все тоньше, пожелания все экзотичнее. Вот опять кажется дело движется к концу. И так похоже будет бесконечно, пока проект не умрет или не появится что-то лучшее. А может платформа поменяется и появятся новые интерфейсы. Но судя по всему сейчас браузеры находятся где-то между молодостью и зрелостью, а после смерти text-mask, альтернатив для IMask по функционалу не видно, да и я еще могу найти каплю времени на open source, поэтому маске быть.


    Всем успехов и вдохновения в Новом Году, с Наступающим!

    Средняя зарплата в IT

    110 000 ₽/мес.
    Средняя зарплата по всем IT-специализациям на основании 8 477 анкет, за 2-ое пол. 2020 года Узнать свою зарплату
    Реклама
    AdBlock похитил этот баннер, но баннеры не зубы — отрастут

    Подробнее

    Комментарии 1

      0
      Вот уже какой год пользуюсь cleave.js (количество звезд впечатляет), о вашем проекте узнал только что.

      В cleave.js очень не хватает возможности лимитировать ввода min/max (есть issue в котором люди продолжают ставить +1, но авторы не торопятся внедрять эту фичу), у вас я смотрю такая фича реализована и это здорово

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

      Самое читаемое