мы фанаты Пользовательские элементы здесь. Их дизайн делает их особенно поддается ленивой загрузке, что может быть благом для производительности.
Вдохновленный коллега Эксперименты, я недавно приступил к написанию простого автозагрузчика: всякий раз, когда в DOM появляется пользовательский элемент, мы хотим загрузить соответствующую реализацию, если она еще недоступна. Затем браузер заботится об обновлении таких элементов.
Скорее всего, вам все это на самом деле не понадобится; обычно есть более простой подход. Используемые преднамеренно методы, показанные здесь, все еще могут быть полезным дополнением к вашему набору инструментов.
Для согласованности мы хотим, чтобы наш автозагрузчик также был настраиваемым элементом, что также означает, что мы можем легко настроить его с помощью HTML. Но сначала давайте шаг за шагом определим эти неразрешенные пользовательские элементы:
class AutoLoader extends HTMLElement { connectedCallback() { let scope = this.parentNode; this.discover(scope); }
}
customElements.define("ce-autoloader", AutoLoader);
Предполагая, что мы загрузили этот модуль заранее (используя async
идеально), мы можем отбросить <ce-autoloader>
элемент в <body>
нашего документа. Это немедленно запустит процесс обнаружения для всех дочерних элементов <body>
, который теперь составляет наш корневой элемент. Мы могли бы ограничить обнаружение поддеревом нашего документа, добавив <ce-autoloader>
вместо этого к соответствующему элементу-контейнеру — действительно, у нас может быть даже несколько экземпляров для разных поддеревьев.
Конечно, нам еще предстоит реализовать это discover
метод (в рамках AutoLoader
класс выше):
discover(scope) { let candidates = [scope, ...scope.querySelectorAll("*")]; for(let el of candidates) { let tag = el.localName; if(tag.includes("-") && !customElements.get(tag)) { this.load(tag); } }
}
Здесь мы проверяем наш корневой элемент вместе с каждым потомком (*
). Если это пользовательский элемент — как указано в тегах через дефис — но еще не обновленный, мы попытаемся загрузить соответствующее определение. Запрашивать DOM таким образом может быть дорого, поэтому мы должны быть немного осторожны. Мы можем уменьшить нагрузку на основной поток, отложив эту работу:
connectedCallback() { let scope = this.parentNode; requestIdleCallback(() => { this.discover(scope); });
}
requestIdleCallback
пока не поддерживается повсеместно, но мы можем использовать requestAnimationFrame
как запасной вариант:
let defer = window.requestIdleCallback || requestAnimationFrame; class AutoLoader extends HTMLElement { connectedCallback() { let scope = this.parentNode; defer(() => { this.discover(scope); }); } // ...
}
Теперь можно переходить к реализации недостающих load
метод динамического внедрения <script>
элемент:
load(tag) { let el = document.createElement("script"); let res = new Promise((resolve, reject) => { el.addEventListener("load", ev => { resolve(null); }); el.addEventListener("error", ev => { reject(new Error("failed to locate custom-element definition")); }); }); el.src = this.elementURL(tag); document.head.appendChild(el); return res;
} elementURL(tag) { return `${this.rootDir}/${tag}.js`;
}
Обратите внимание на жестко закодированное соглашение в elementURL
, src
URL-адрес атрибута предполагает наличие каталога, в котором находятся все определения пользовательских элементов (например, <my-widget>
→ /components/my-widget.js
). Мы могли бы придумать более сложные стратегии, но этого достаточно для наших целей. Отнесение этого URL к отдельному методу позволяет при необходимости создавать подклассы для конкретного проекта:
class FancyLoader extends AutoLoader { elementURL(tag) { // fancy logic }
}
В любом случае обратите внимание, что мы полагаемся на this.rootDir
. Вот тут-то и появляется вышеупомянутая конфигурируемость. Давайте добавим соответствующий геттер:
get rootDir() { let uri = this.getAttribute("root-dir"); if(!uri) { throw new Error("cannot auto-load custom elements: missing `root-dir`"); } if(uri.endsWith("/")) { // remove trailing slash return uri.substring(0, uri.length - 1); } return uri;
}
Вы могли подумать о observedAttributes
сейчас, но это не делает вещи легче. Плюс обновление root-dir
во время выполнения кажется чем-то, что нам никогда не понадобится.
Теперь мы можем — и должны — настроить каталог наших элементов: <ce-autoloader root-dir="/components">
.
С этим наш автозагрузчик может выполнять свою работу. За исключением того, что это работает только один раз для элементов, которые уже существуют при инициализации автозагрузчика. Вероятно, мы также захотим учитывать динамически добавляемые элементы. Вот где MutationObserver
вступает в игру:
connectedCallback() { let scope = this.parentNode; defer(() => { this.discover(scope); }); let observer = this._observer = new MutationObserver(mutations => { for(let { addedNodes } of mutations) { for(let node of addedNodes) { defer(() => { this.discover(node); }); } } }); observer.observe(scope, { subtree: true, childList: true });
} disconnectedCallback() { this._observer.disconnect();
}
Таким образом, браузер уведомляет нас всякий раз, когда в DOM появляется новый элемент — или, скорее, наше соответствующее поддерево — которое мы затем используем для перезапуска процесса обнаружения. (Вы можете возразить, что мы заново изобретаем пользовательские элементы, и будете правы.)
Наш автозагрузчик теперь полностью функционален. Будущие усовершенствования могут учитывать потенциальные условия гонки и исследовать возможности оптимизации. Но, скорее всего, этого достаточно для большинства сценариев. Дайте мне знать в комментариях, если у вас другой подход, и мы сможем обменяться мнениями!
- SEO-контент и PR-распределение. Получите усиление сегодня.
- Платоблокчейн. Интеллект метавселенной Web3. Расширение знаний. Доступ здесь.
- Источник: https://css-tricks.com/an-approach-to-lazy-loading-custom-elements/
- 1
- a
- О нас
- выше
- Учетная запись
- на самом деле
- добавленный
- дополнение
- Все
- облегчать
- позволяет
- уже
- и
- подхода
- спорить
- около
- доступен
- браузер
- кандидатов
- не могу
- заботится
- тщательный
- шансы
- проверка
- ребенок
- класс
- как
- Комментарии
- сравнить
- Условия
- Container
- Соглашение
- соответствующий
- может
- курс
- изготовленный на заказ
- Проект
- различный
- открытие
- документ
- не
- DOM
- Падение
- динамично
- легче
- легко
- Разрабатывать
- элементы
- достаточно
- ошибка
- Эфир (ETH)
- EV
- Даже
- Каждая
- Кроме
- дорогим
- XNUMX ошибка
- вентиляторы
- First
- от
- полностью
- функциональная
- будущее
- будет
- хорошо
- здесь
- HTML
- HTTPS
- идеальный
- определения
- немедленно
- осуществлять
- реализация
- Осуществляющий
- in
- вместо
- исследовать
- IT
- JavaScript
- работа
- Вид
- Знать
- Длина
- ОГРАНИЧЕНИЯ
- мало
- загрузка
- погрузка
- посмотреть
- Главная
- сделать
- ДЕЛАЕТ
- означает
- метод
- может быть
- отсутствующий
- модуль
- БОЛЕЕ
- самых
- двигаться
- Mozilla
- с разными
- Необходимость
- необходимый
- Новые
- узел
- часть
- производительность
- Платон
- Платон Интеллектуальные данные
- ПлатонДанные
- Играть
- плюс
- потенциал
- вероятно
- процесс
- целей
- Гонки
- недавно
- удаление
- те
- возвращают
- корень
- Сценарии
- сфера
- кажется
- отдельный
- набор
- должен
- показанный
- просто
- одинарной
- So
- удалось
- Начало
- Шаг
- По-прежнему
- стратегий
- такие
- Поддержанный
- TAG
- принимает
- снижения вреда
- Ассоциация
- их
- вещи
- мышление
- в
- правда
- обновление
- повышен
- URI
- URL
- us
- использование
- обычно
- с помощью
- который
- будете
- Работа
- работает
- письмо
- ВАШЕ
- зефирнет