Постоянным вызовом, с которым сталкиваются передние разработчики, является производительность наших приложений. Как мы можем предоставить нашим пользователям надежное и полнофункциональное приложение, не заставляя их ждать вечность загрузки страницы? Методы, используемые для ускорения веб-сайта, настолько многочисленны, что часто бывает непонятно, когда решить, на что направить нашу энергию при оптимизации производительности и скорости.
К счастью, решение не так сложно, как иногда может показаться. В этой статье я расскажу об одном из наиболее эффективных методов, используемых крупными веб-приложениями для ускорения работы пользователей. Я перейду к пакету, чтобы упростить это и убедиться, что мы можем быстрее доставить наше приложение пользователям, чтобы они не заметили, что что-то изменилось.
Что это значит для веб-сайта, чтобы быть быстрым?
Вопрос о веб-производительности настолько же глубок, насколько и широк. Ради этого поста я постараюсь определить производительность в самых простых терминах: отправляйте как можно меньше, так быстро, как можете. Конечно, это может быть упрощением проблемы, но практически мы можем добиться значительного улучшения скорости, просто отправляя пользователю меньше данных для быстрой загрузки и отправки этих данных .
В этой статье я сосредоточусь на первой части этого определения — отправке наименьшего возможного объема информации в браузер пользователя.
Неизбежно, что самыми серьезными препятствиями в замедлении наших приложений являются изображения и JavaScript. В этом посте я собираюсь показать вам, как справиться с проблемой больших пакетов приложений и ускорить работу нашего сайта.
Реакция загружаемая
React Loadable — это пакет, который позволяет нам лениво загружать наш JavaScript только тогда, когда это требуется приложением. Конечно, не все веб-сайты используют React, но ради краткости я собираюсь сосредоточиться на реализации React Loadable в серверном приложении, построенном с использованием Webpack. Конечным результатом будет множество файлов JavaScript, автоматически доставляемых в браузер пользователя, когда этот код необходим. Если вы хотите опробовать законченный код, вы можете клонировать исходный код примера из нашего репозитория GitHub .
Используя наше предыдущее определение, это просто означает, что мы отправляем пользователю меньше информации заранее, чтобы данные могли загружаться быстрее, и наш пользователь получал более производительный сайт.
1. Добавьте React Loadable
к вашему компоненту
Я возьму пример компонента React, MyComponent
. Я предполагаю, что этот компонент состоит из двух файлов, MyComponent/MyComponent.jsx
и MyComponent/index.js
.
В этих двух файлах я определяю компонент React точно так же, как обычно в MyComponent.jsx
. В index.js
я импортирую компонент React и реэкспортирую его — на этот раз в функции Loadable
. Используя функцию import
ECMAScript, я могу указать Webpack, что я ожидаю, что этот файл будет загружен динамически. Этот шаблон позволяет мне легко загружать любой компонент, который я уже написал. Это также позволяет мне разделить логику между отложенной загрузкой и рендерингом. Это может показаться сложным, но вот как это будет выглядеть на практике:
1
2
3
4
5
6
7
|
// MyComponent/MyComponent.jsx
export default () => (
<div>
This component will be lazy-loaded!
</div>
)
|
1
2
3
4
5
6
7
8
9
|
// MyComponent/index.js
import Loadable from ‘react-loadable’
export default Loadable({
// The import below tells webpack to
// separate this code into another bundle
loader: import(‘./MyComponent’)
})
|
Затем я могу импортировать свой компонент точно так же, как обычно:
1
2
3
4
5
|
// anotherComponent/index.js
import MyComponent from ‘./MyComponent’
export default () => <MyComponent />
|
Я теперь ввел React MyComponent
в MyComponent
. Я могу добавить больше логики к этому компоненту позже, если я выберу — это может включать введение состояния загрузки или обработчика ошибок для компонента. Благодаря Webpack, когда мы запустим нашу сборку, мне теперь будут предоставлены два отдельных app.min.js
JavaScript: app.min.js
— это наш обычный пакет приложений, а myComponent.min.js
содержит код, который мы только что написали. Я расскажу, как доставить эти пакеты в браузер чуть позже.
2. Упростите настройку с Babel
Обычно я должен был бы включить две дополнительные опции при передаче объекта в функцию webpack
, modules
и webpack
. Они помогают Webpack определить, какие модули мы должны включать. К счастью, мы можем избавиться от необходимости включать эти два параметра в каждый компонент, используя плагин react-loadable/babel
. Это автоматически включает в себя следующие параметры для нас:
1
2
3
4
5
6
7
|
// input file
import Loadable from ‘react-loadable’
export default Loadable({
loader: () => import(‘./MyComponent’)
})
|
01
02
03
04
05
06
07
08
09
10
|
// output file
import Loadable from ‘react-loadable’
import path from ‘path’
export default Loadable({
loader: () => import(‘./MyComponent’),
webpack: () => [require.resolveWeak(‘./MyComponent’)],
modules: [path.join(__dirname, ‘./MyComponent’)]
})
|
Я могу включить этот плагин, добавив его в мой список плагинов в моем файле .babelrc , например так:
1
2
3
|
{
«plugins»: [«react-loadable/babel»]
}
|
Теперь я на шаг ближе к ленивой загрузке нашего компонента. Однако в моем случае я имею дело с рендерингом на стороне сервера. В настоящее время сервер не сможет рендерить наши лениво загруженные компоненты.
3. Рендеринг компонентов на сервере
В моем серверном приложении у меня есть стандартная конфигурация, которая выглядит примерно так:
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
|
// server/index.js
app.get(‘/’, (req, res) => {
const markup = ReactDOMServer.renderToString(
<MyApp/>
)
res.send(`
<html>
<body>
<div id=»root»>${markup}</div>
<script src=»/build/app.min.js»></script>
</body>
</html>
`)
})
app.listen(8080, () => {
console.log(‘Running…’)
})
|
Первым шагом будет инструктировать React Loadable, что я хочу, чтобы все модули были предварительно загружены. Это позволяет мне решить, какие из них должны быть немедленно загружены на клиенте. Я делаю это, изменяя мой файл server/index.js
следующим образом:
1
2
3
4
5
6
7
|
// server/index.js
Loadable.preloadAll().then(() => {
app.listen(8080, () => {
console.log(‘Running…’)
})
})
|
Следующим шагом будет помещение всех компонентов, которые я хочу визуализировать, в массив, чтобы мы могли позже определить, какие компоненты требуют немедленной загрузки. Это так, чтобы HTML можно было возвращать с правильными пакетами JavaScript, включенными через теги скрипта (подробнее об этом позже) Сейчас я собираюсь изменить файл моего сервера следующим образом:
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
|
// server/index.js
import Loadable from ‘react-loadable’
app.get(‘/’, (req, res) => {
const modules = []
const markup = ReactDOMServer.renderToString(
<Loadable.Capture report={moduleName => modules.push(moduleName)}>
<MyApp/>
</Loadable>
)
res.send(`
<html>
<body>
<div id=»root»>${markup}</div>
<script src=»/build/app.min.js»></script>
</body>
</html>
`)
})
Loadable.preloadAll().then(() => {
app.listen(8080, () => {
console.log(‘Running…’)
})
})
|
Каждый раз, когда используется компонент, для которого требуется React Loadable
, он будет добавлен в массив modules
. Это автоматический процесс, выполняемый React Loadable
, так что это все, что с нашей стороны требуется для этого процесса.
Теперь у нас есть список модулей, которые, как мы знаем, должны быть визуализированы немедленно. Проблема, с которой мы сейчас сталкиваемся, заключается в сопоставлении этих модулей с пакетами, которые Webpack автоматически создал для нас.
4. Сопоставление пакетов Webpack с модулями
Итак, теперь я дал указание Webpack создать myComponent.min.js , и я знаю, что MyComponent
используется немедленно, поэтому мне нужно загрузить этот пакет в исходной полезной нагрузке HTML, которую мы доставляем пользователю. К счастью, React Loadable также позволяет нам достичь этого. В моем конфигурационном файле клиента Webpack мне нужно включить новый плагин:
1
2
3
4
5
6
7
8
9
|
// webpack.client.config.js
import { ReactLoadablePlugin } from ‘react-loadable/webpack’
plugins: [
new ReactLoadablePlugin({
filename: ‘./build/loadable-manifest.json’
})
]
|
Файл loadable-manifest.json предоставит мне сопоставление между модулями и пакетами, чтобы я мог использовать массив modules
я настроил ранее, для загрузки пакетов, которые, как я знаю, мне понадобятся. В моем случае этот файл может выглядеть примерно так:
1
2
3
4
5
|
// build/loadable-manifest.json
{
«MyComponent»: «/build/myComponent.min.js»
}
|
Это также потребует, чтобы общий файл манифеста Webpack включал отображение между модулями и файлами для внутренних целей Webpack. Я могу сделать это, включив другой плагин Webpack:
1
2
3
4
5
6
|
plugins: [
new webpack.optimize.CommonsChunkPlugin({
name: ‘manifest’,
minChunks: Infinity
})
]
|
5. Включение комплектов в ваш HTML
Последний шаг при загрузке наших динамических пакетов на сервер — включить их в HTML-код, который мы предоставляем пользователю. Для этого шага я собираюсь объединить вывод шагов 3 и 4. Я могу начать с изменения файла сервера, который я создал выше:
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
|
// server/index.js
import Loadable from ‘react-loadable’
import { getBundles } from ‘react-loadable/webpack’
import manifest from ‘./build/loadable-manifest.json’
app.get(‘/’, (req, res) => {
const modules = []
const markup = ReactDOMServer.renderToString(
<Loadable.Capture report={moduleName => modules.push(moduleName)}>
<MyApp/>
</Loadable>
)
const bundles = getBundles(manifest, modules)
// My rendering logic below …
})
Loadable.preloadAll().then(() => {
app.listen(8080, () => {
console.log(‘Running…’)
})
})
|
В этом я импортировал манифест и попросил React Loadable создать массив с отображениями модуля / пакета. Единственное, что мне осталось сделать, это отобразить эти пакеты в строку HTML:
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
|
// server/index.js
app.get(‘/’, (req, res) => {
// My App & modules logic
res.send(`
<html>
<body>
<div id=»root»>${markup}</div>
<script src=»/build/manifest.min.js»></script>
${bundles.map(({ file }) =>
`<script src=»/build/${file}»></script>`
}).join(‘\n’)}
<script src=»/build/app.min.js»></script>
</body>
</html>
`)
})
Loadable.preloadAll().then(() => {
app.listen(8080, () => {
console.log(‘Running…’)
})
})
|
6. Загрузите серверные пакеты на клиент
Последний шаг к использованию пакетов, которые мы загрузили на сервер, — это использование их на клиенте. Сделать это очень просто — я могу просто поручить React Loadable
предварительно загрузить любые модули, которые будут сразу же доступны:
01
02
03
04
05
06
07
08
09
10
11
12
13
14
|
// client/index.js
import React from ‘react’
import { hydrate } from ‘react-dom’
import Loadable from ‘react-loadable’
import MyApplication from ‘./MyApplication’
Loadable.preloadReady().then(() => {
hydrate(
<MyApplication />,
document.getElementById(‘root’)
);
});
|
Вывод
Следуя этому процессу, я могу разбить свой пакет приложений на столько маленьких пакетов, сколько мне нужно. Таким образом, мое приложение отправляет меньше пользователю и только тогда, когда оно ему нужно. Я сократил объем кода, который нужно отправить, чтобы он мог быть отправлен быстрее. Это может значительно повысить производительность для более крупных приложений. Он также может настроить небольшие приложения для быстрого роста в случае необходимости.