Он действительно огромный — просто посмотрите на него:
Эта штука весит 103кб (в сжатом виде). Больше чем код приложения — интернет-магазин -(58kb) и сравнима со всем остальным кодом в vendor бандле (156kb) — включающем react
, react-dom
, react-router
, moment.js
, lodash
и кучу других библиотек. Что еще хуже — firebase
нужен не на всех страницах, и очень часто не нужен к моменту загрузку сайта.
Что можно с этим сделать?
Не сликом много, как оказалось. Включение отдельных модулей не работало (на тот момент в webpack@2) да и помогло бы не слишком сильно — все равно требовалось бы включить auth
и database
+ модуль app
(42kb + 40kb + 3kb) — что дало бы 83% исходного размера так или иначе. Кроме того, сами модули auth
и database
совершенно монолитны (наглядно видно на скриншоте выше) и уже сжаты лучше некуда.
К слову — сжаты они с помощью
Clouse Compiler
или чем там Google пользуется на текущий момент — удачи минифицировать банлд с помощьюuglify
и не сломать ничего.
Но что-то же нужно делать!
Конечно. 103kb кода валяющиющийся мертвым грузом в бандле — это очень неприятно. Подумайте — люди жалуются на размер 'react' и переходят на inferno
/preact
— а react
+react-dom
весит всего 39kb в сжатом виде и они работают не покладая рук.
Но раз невозможно уменьшить размер этого куска — мы просто отложим его загрузку до того момента, когда он будет реально нужен.
И сделаем это так что никто и не заметит :)
Webpack и dynamic import спешит на помощь
Для сервера это предельно просто:
// firebaseImport.server.js
import * as firebase from 'firebase/firebase-node'
export default function importFirebase() {
return Promise.resolve(firebase)
}
Для клиента — чуть сложнее, но все равно просто:
// firebaseImport.browser.js
export default function importFirebase() {
// динамический import, вернет Promise
// "магические" комментарии в импорте дадут возможность получить вменяемое имя
// а не `0.js`
return import(/* webpackChunkName: 'firebase' */
/* webpackMode: 'lazy' */
'firebase/firebase-browser')
}
Это все необходимо для универсального рендеринга на сервере. Если ваше приложение работает только на клиене — просто игнорируйте серверную часть
Далее нужно сделать полиморфный импорт в webpack-конфиге:
// browser webpack-config
resolve: {
alias: {
'firebaseImport$': path.join('path', 'to', 'your', 'firebaseImport.browser.js')
}
}
// ...
// server webpack-config
resolve: {
alias: {
'firebaseImport$': path.join('path', 'to', 'your', 'firebaseImport.server.js')
}
}
// ...
Да, мы собираем сервер webpack-ом. Постарайтесь нас не осуждать, нам нужны работающие
import
ы в универсальном коде, а node.js не понимает их нативно.
Теперь require(firebaseImport$)
вернет Promise который сразу выполнен на сервере и который лениво загрузит firebase на клиенте. После первой загрузки на клиенте этот импорт тоже станет 'выполненым', и последующие обращения к firebase будут уже почти мгновенными.
Далее нужно инициализировать клиент firebase:
export default function firebase() {
return importFirebase().then((firebase) => {
// Собственно инициализция firebase. Обычно что-то вроде этого:
const app = firebase.initializeApp({
apiKey: '<your-api-key>',
authDomain: '<your-auth-domain>',
databaseURL: '<your-database-url>',
storageBucket: '<your-storage-bucket>',
messagingSenderId: '<your-sender-id>'
})
// возвращаем реально используемые интерфейсы:
return {
database: app.database()
auth: app.auth()
}
})
}
И собственно всё. Конечно, теперь использование firebase стало более многословным:
// до:
import firebase from 'what/ever/firebase'
const {auth} = firebase
export function signIn({email, password}) {
auth.signInWithEmailAndPassword(email, password)
.then((user) => {
// ...
})
}
// ---
// после:
import firebase from 'what/ever/firebase'
export function signIn({email, password}) {
firebase().then(({auth}) => {
auth.signInWithEmailAndPassword(email, password)
.then((user) => {
// ...
})
})
}
Но результат того стоит:
Несколько дополнительных моментов
- Нужно не забыть добавить обработку ошибок через
catch()
ко всем промисам (ну и зарепортить их); - firebase.js можно получить на клиенте к моменту загрузки основных бандлов в большинстве браузеров — простым добавлением
<link rel="preload" href="/assets/firebase.js" as="script">
вhead
; - Инициализация firebase может поругаться с
hot-loader
webpack
'a, нам помгло использованиеdefault
приложения а не именованного (https://firebase.google.com/docs/web/setup); - Крайне полезно время от времени запускать webpack-bundle-analyzer — размер некоторых пакетов может вас неприятно удивить (momen.js и его локали могут даже шокировать);
- Пост находится в react.js хабе потому что говорю webpack-подразумеваю react, но (очевидно) данный метод напрямую ни от реакта ни от firebase не зависит, может быть использован для любых библиотек которые нужны "лениво";
Автор: romanonthego