Pull to refresh

firebase.js ПРОСТО ОГРОМНЫЙ (и что мы можем с этим сделать)

Reading time 3 min
Views 17K

Он действительно огромный — просто посмотрите на него:
image
Эта штука весит 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) => {
        // ...
      })
  })
}

Но результат того стоит:
image
image


Несколько дополнительных моментов


  • Нужно не забыть добавить обработку ошибок через 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 не зависит, может быть использован для любых библиотек которые нужны "лениво";
Tags:
Hubs:
+22
Comments 23
Comments Comments 23

Articles