Эта статья была впервые опубликована на Medium.com .
Обновление: Это часть 1 моего CRA + SSR ряд:
- Обновление проекта Create-React-App для разделения кода SSR +
- Добавление государственного управления с Redux в проекте CRA + SSR
Проблема с CRA
С момента его создания Create-React-App Это отличный инструмент для быстрого прототипирования приложений реагирования, демонстрации и тестирования различных функций или методов. Это было, а все равно есть главная идея за ним. Его создатели даже поощряют разработчиков искать Альтернативы Если они хотят расширенные функции, такие как рендеринг на стороне сервера, использование Tymdercript или более настроек, или даже извлеките и скрипит со всеми конфигурациями.
Тем не менее, многие разработчики боятся или скептически извлечь, и предпочитают (в комплекте) в Оставайтесь заблокированы в пузыре CRA, либо для удобства, DENCERTTER of Config файлов, либо для всегда сохраняя инструменты сборки в курсе Отказ Вот почему Многие производственные приложения все еще используют оригинальные инструменты CRA Отказ
Но пока настройка CRA по умолчанию может быть в порядке для небольших проектов, не всегда достаточно для более крупных, более сложных приложений. К счастью, мы можем улучшить вещи даже без извлечения. Давайте попробуем сделать этот шаг за шагом. Это то, что мы будем покрывать:
- Серверный рендеринг
- Разделение кода с реагированным
- Кодовое расщепление на сервере
- Воспользовавшись любящими чанками WebPack
Сначала: рендеринг на стороне сервера
Вероятно, самая сложная часть этого обновления добавляет SSR. CRA по собственной не предлагает поддержку для этого, и ее Devs даже не планирует:
В конечном итоге сервер бокового рендеринга очень трудно добавить значимого способа, без учета самоуверенных решений. Мы не намерены принимать такие решения в это время. – Дэн Абрамов
Но наличие SSR – это то, что каждый разработчик жаждается. Итак, давайте начнем!
С целью этой статьи мы будем использовать Express.js. :
yarn add express
Создать /Сервер папка рядом с нашей /SRC папка. Инструменты сборки CRA не будут разобраться и скомпилировать что-либо за пределами SRC папка. Это нормально, как ни один код из Сервер будет использоваться внутри нашего клиентского приложения.
Теперь нам нужна точка входа для нашего сервера приложения. Создать файл в /server/index.js :
import express from 'express'; // we'll talk about this in a minute: import serverRenderer from './middleware/renderer'; const PORT = 3000; const path = require('path'); // initialize the application and create the routes const app = express(); const router = express.Router(); // root (/) should always serve our server rendered page router.use('^/$', serverRenderer); // other static resources should just be served as they are router.use(express.static( path.resolve(__dirname, '..', 'build'), { maxAge: '30d' }, )); // tell the app to use the above rules app.use(router); // start the app app.listen(PORT, (error) => { if (error) { return console.log('something bad happened', error); } console.log("listening on " + PORT + "..."); });
Так что с этим ServerRenderer вещь? Это экспресс-промежуточное программное обеспечение, которое сделает наш HTML. Наше /server/middleware/renderer.js Файл выглядит так:
import React from 'react' import ReactDOMServer from 'react-dom/server' // import our main App component import App from '../../src/App'; const path = require("path"); const fs = require("fs"); export default (req, res, next) => { // point to the html file created by CRA's build tool const filePath = path.resolve(__dirname, '..', '..', 'build', 'index.html'); fs.readFile(filePath, 'utf8', (err, **htmlData** ) => { if (err) { console.error('err', err); return res.status(404).end() } // render the app as a string const html = ReactDOMServer.renderToString(); // inject the rendered app into our html and send it return res.send( htmlData.replace( '', ` ${html}` ) ); }); }
Здесь есть один последний шаг: узел не говорит JSX, поэтому нам нужно транситировать наш код с Babel. Для этого создайте /server/bootstrap.js Файл, который скажет узел, как интерпретировать наш код (мы фактически используем это как точка входа):
require('ignore-styles'); require('babel-register')({ ignore: [/(node_modules)/], presets: ['es2015', 'react-app'] }); require('./index');
Вам также нужно будет установить эти дополнительные пакеты: Babel-Preset-ES2015 , Babel-Preset-React-App , Игнорирование-стили .
Это оно! Теперь запустите сервер:
node server/bootstrap.js
Вы можете продлить свое серверное приложение, однако, что вы хотите, но вышеизложенно является минимальным, который вам понадобится для рендеринга на стороне сервера A приложение Rection с CRA.
Если ваш проект использует React Router , вам также нужно будет захватить все пути и применить ServerRenderer Отказ Для этого просто добавьте еще одно правило маршрутизатора в своем приложении сервера:
// ... // anything else should act as our index page // react-router will take care of everything router.use('*', serverRenderer); app.use(router);
Далее: разделение кода
Имея только один огромный пакет JS со всем, содержащимся в вашем приложении, все еще отстой! Вы должны загрузить весь файл со всеми частями вашего приложения, даже если ваш начальный рендер невелик, и будет использовать только пару компонентов. Лучшим подходом состоит в том, чтобы загрузить только то, что пользователю понадобится немедленно, затем ленивый загружаю другие части приложения, поскольку они требуются. Итак, давайте разделимся!
Как описано в Документация , CRA предлагает поддержку расщеплению кода, используя динамический импорт. Есть также Обширное сообщение о том, как создавать асинхронные компоненты в проекте CRA. Но удобнее, мы можем использовать компонент более высокого порядка, который упростит нашу работу: React-Loadable Отказ
Давайте создадим /src/somecomponent.js В нашем приложении CRA:
import React from 'react'; export default () => (Hi, I'm async.
);
Теперь давайте сделаем этот компонент Async, импортируя его с реагированием на нашем главном App.js :
import Loadable from 'react-loadable'; const AsyncComponent = Loadable({ loader: () => import("./SomeComponent"), loading: () =>loading..., }); class App extends Component { render() { return (// ...); } }
Я призываю вас прочитать операционную документацию, поскольку она упаковывает много функций, но сохраняя простоту, которую вы видите выше.
Это оно! Теперь ваше приложение загрузит Сомпонент модуль, когда ваш AsyncComponent будет представлять. Блок в Загрузка PROP – это то, что делает оказываемыми в то время как Требуемый кусок загружен с сервера и анализируют.
Обратите внимание, что CRA создал новый файл под названием 0. [Хэш] .chunk.js Отказ
Если вы запустите приложение сейчас с Пряжа начать , вы увидите, что только главный пакет загружен. После первоначального визуализации другой запрос получит наш новый кусок. А Загрузка … Сообщение отображается до тех пор, пока файл не будет загружен и не проанализирован.
Кодовое расщепление на сервере
Чтобы сделать эту работу с SSR, нам нужно будет внести небольшие настройки. Во-первых, для узла понять эту динамику Импорт () нам понадобится несколько плагинов Babel. Добавь их к нашему bootstrap.js Варианты Бабел-Регистрация:
require('babel-register')({ ignore: [/(node_modules)/], presets: ['es2015', 'react-app'], plugins: [ 'syntax-dynamic-import', 'dynamic-import-node', 'react-loadable/babel' ] });
Затем наше серверное приложение необходимо будет загрузить все модули перед рендерингом, поэтому мы не получаем это загрузочное сообщение, представленное на сервере. Нам нужен фактический компонент. Для этого мы будем использовать помощник от реагирования, который предварительно загружает все асинковые компоненты. Обновить /server/index.js как это:
Loadable.preloadAll().then(() => { app.listen(PORT, (error) => { // ... console.log("listening on " + PORT + "..."); }); });
Также на клиенте давайте использовать гидрат вместо оказывать :
ReactDOM.hydrate(, document.getElementById('root') );
Намного лучше! Хотя мы все еще видим загрузку сообщения. Это потому, что приложение загружает только main.js пучок. После того, как пучок проанализируется, приложение работает, но наш другой кусок не загружен. Таким образом, при гидратации наш код запросит его, и он будет представлять собой загрузку сообщения до завершения загрузки. Мы не хотим этого.
Вместо этого мы хотим идентифицировать, на сервере рендеринга, все кусочки требуются, а затем отправляют их рядом с главным связке в HTML, то нам нужно ждать всех активов для загрузки перед увлажнением. Давайте начнем с идентификации требуемых кусков.
Воспользовавшись любящими чанками WebPack
Куски CRA по умолчанию уродливы и непредсказуемы. Мы можем улучшить это, назвав наши куски, используя Относительно новая особенность WebPack : кусок имена.
Все, что нам нужно, это комментарий внутри Импорт () :
const AsyncComponent = Loadable({ loader: () => import(/* webpackChunkName: "myNamedChunk" */'./SomeComponent'), loading: () =>loading..., modules: ['myNamedChunk'] });
Последняя строка сообщает, что этот компонент требует куска с именем Mynamedchunk Отказ Давайте построим первым:
Лучше. Посмотрим, что в нашем /build/asset-manifest.json файл:
{ "main.css": "static/css/main.c17080f1.css", "main.css.map": "static/css/main.c17080f1.css.map", "main.js": "static/js/main.d142da83.js", "main.js.map": "static/js/main.d142da83.js.map", "myNamedChunk.js": "static/js/myNamedChunk.7acfcfe3.chunk.js", "myNamedChunk.js.map": "static/js/myNamedChun.7acfcfe3.chunk.js.map" }
Большой! Мы видим приятное предсказуемое имя сопоставленным с фактическим файлом Hazed Filename. Но как мы можем знать, какие куски нам на самом деле нужно загрузить? В более широком применении мы обязательно не хотим их всех. Lucky (вы угадали), нажав на реагирование, приходит еще раз в спасение. Давайте обновим наше промежуточное программное обеспечение Renderer:
const modules = []; // render the app as a string const html = ReactDOMServer.renderToString(modules.push(m)}> ); console.log(modules);
Запустите сервер и обновите страницу, и вы увидите в консоли, массив, содержащий все имена кусков, которые сервер используется для рендеринга начального состояния приложения (кроме основного пучка). Нам просто нужно найти это на фактические файлы JavaScript:
import manifest from '../../build/asset-manifest.json'; const extractAssets = (assets, chunks) => Object.keys(assets) .filter(asset => chunks.indexOf(asset.replace('.js', '')) > -1) .map(k => assets[k]); // then, after Loadable.Capture console.log(extractAssets(manifest, modules));
Теперь добавьте их как теги скрипта в конце Тело :
const extraChunks = extractAssets(manifest, modules) .map(c => ``); return res.send( htmlData .replace( '', `${html}` ) .replace( '', extraChunks.join('') + '' ) );
Один последний шаг, нам нужно дождаться документа загрузки всех активов перед увлажнением. Итак, мы меняем index.js На клиенте:
import Loadable from 'react-loadable'; window.onload = () => { Loadable.preloadReady().then(() => { ReactDOM.hydrate(, document.getElementById('root') ); }); };
Давайте строим в последний раз и запустите сервер:
Обратите внимание на требуемый кусок, загруженный до того, как главный пучок работает и запросиет его. Таким образом, у гидратации мы уверены, что у нас есть все необходимые модули, тем самым избавляясь от уродливых Загрузка … сообщение.
Резюме
Мы прошли долгий путь, но нам удалось обновить наше приложение CRA CRA в полную настройку SSR + для разделения кода на несколько шагов, не выбрасывая и без использования других шаблонов, котельных или кадров.
Я использовал все упомянутое здесь на потрясающем проекте, в настоящее время я развиваю: Snipit.io , совместный инструмент для экономии и организации кода-фрагментов в облаке. Кроме того, что обсуждалось в этой статье, Snipit Также используются Redux, React Rounter и Stylitron (для таких компонентов). Приложение Server использует общий сеанс с приложением Backend, поэтому серверное средство Render знает, если пользователь вошел в систему или нет, и не отображает компоненты соответственно.
Я, вероятно, напишу другую статью о том, как все работает.
Наконец, вы можете увидеть полный список со всеми файлами, которые мы изменены здесь, из пустой приложения CRA в snipit.io/public/Lists/1365 Отказ Вы также можете добавить в Blankmark этот список для дальнейшего использования, если вы создадите учетную запись и войдите в систему. Сделайте это, это бесплатно!