Вопрос или проблема
В качестве примера, вот некоторые полифиллы, которые у меня есть для CookieChangeEvent
, который в данный момент не поддерживается Firefox и Safari.
import Cookies, {CookieAttributes} from "js-cookie"
export function listenForCookieChange(cookieName: string, onChange: (newValue: string | null) => void) : (() => void) {
// Если cookieStore доступен, мы можем использовать слушатель события изменения
if("cookieStore" in window) {
const changeListener = (event) => {
console.log(event)
const foundCookie = event.changed.find((cookie) => cookie.name === cookieName);
if (foundCookie) {
onChange(foundCookie.value);
return
}
const deletedCookie = event.deleted.find((cookie) => cookie.name === cookieName);
if(deletedCookie) {
onChange(null);
}
};
cookieStore.addEventListener("change", changeListener);
// Мы возвращаем функцию очистки для эффекта
return () => {
cookieStore.removeEventListener("change", changeListener);
};
}
// Если cookieStore недоступен, мы опрашиваем на изменения.
const interval = setInterval(() => {
const cookie = Cookies.get(cookieName);
if(cookie) {
onChange(cookie);
} else {
onChange(null);
}
}, 1000);
// Мы все равно возвращаем функцию очистки для эффекта
return () => {
clearInterval(interval);
}
}
Если браузер не поддерживает CookieStore, тогда я использую js-cookie
для удобных методов работы с cookies.
Признаю, что js-cookie занимает всего 800 байт в сжатом виде, так что в этом конкретном случае размер пакета является пустяком.
Но мне приходит в голову, что для приложения с множеством этих полифиллов, пользователи браузеров, которые поддерживают эти API, получат пакет с кодом, который они никогда не используют.
Существуют ли какие-либо движения в современных сборщиках, которые будут разбивать библиотеки полифиллов на части на основе user agent?
Динамический импорт решил бы эту проблему:
export async function listenForCookieChange(cookieName: string, onChange: (newValue: string | null) => void) : Promise<(() => void)> {
// Если cookieStore доступен, мы можем использовать слушатель события изменения
if("cookieStore" in window) {
//snip
}
const Cookies = await import("js-cookie");
// логика полифилла здесь
}
Но это требует преобразования синхронных функций в асинхронные, и если мы не загрузим модули предварительно где-либо, это приведет к более медленному опыту для пользователей полифиллов.
Ответ или решение
Вопрос оптимизации бандлов и исключения полифиллов из сборки в зависимости от поддержки браузером определённых API становится всё более актуальным в современных веб-разработках. Давайте рассмотрим несколько подходов, которые можно использовать для достижения этой цели.
1. Динамический Импорт
Как вы уже отметили, динамический импорт является одним из эффективных способов загружать полифиллы только в тех случаях, когда это необходимо. Преобразование функции в асинхронную может быть не идеальным решением для всех случаев, но это отличный способ исключения ненужного кода из бандла.
export async function listenForCookieChange(cookieName: string, onChange: (newValue: string | null) => void): Promise<() => void> {
if ("cookieStore" in window) {
// Логика для cookieStore
}
const Cookies = await import("js-cookie");
// Логика полифилла на основе js-cookie
}
2. Использование Babel и Полифиллов
Инструменты, такие как Babel, предоставляют возможность полифилла только для тех функций, которые действительно необходимы. С использованием плагина @babel/preset-env
и его опции useBuiltIns
, можно автоматически включать полифиллы только для тех браузеров, которые их требуют, что позволяет сократить размер итогового бандла.
Для этого в вашем babel.config.js
вы можете настроить:
module.exports = {
presets: [
[
'@babel/preset-env',
{
useBuiltIns: 'entry',
corejs: 3,
},
],
],
};
Это позволит Babel автоматически подключать необходимые полифиллы на основе целевых версий браузеров, определённых в browserslist
.
3. Split Chunks с помощью Webpack
Если вы используете Webpack, обратите внимание на функционал оптимизации разделения кода (code splitting). Вы можете настроить Webpack для создания отдельных чанков для полифиллов.
В вашем webpack.config.js
вы можете добавить:
optimization: {
splitChunks: {
cacheGroups: {
polyfills: {
test: /[\\/]node_modules[\\/]js-cookie[\\/]/,
name: 'polyfills',
chunks: 'all',
},
},
},
},
Таким образом, Webpack будет собирать полифиллы в отдельный бандл, который будет загружаться только при необходимости.
4. Пользовательская Логика и Условия
Если вы хотите полностью избежать загрузки обременительных полифиллов, рассматривайте возможность разработки логики проверки возможностей браузера. Например, вы можете проверять наличие определённых функций или API перед загрузкой полифилла:
if (!('cookieStore' in window)) {
const Cookies = await import('js-cookie');
// Логика полифилла
}
Заключение
Существует множество способов оптимизации работы с полифиллами и сокращения размера бандлов в зависимости от возможностей браузера. Динамический импорт, настройка Babel и использования Webpack для разделения кода — это практичные решения, позволяющие избежать включения ненужного кода в бандл.
Важно следить за производительностью и удобством использования кода, чтобы избежать негативного влияния на пользовательский опыт, особенно для пользователей старых браузеров.