ПРЕДВАРИТЕЛЬНАЯ ВИЗУАЛИЗАЦИЯ В ANGULAR
Есть много причин для предварительного рендеринга, и многие не делают этого. Каждое приложение находит свой вариант использования для предварительного рендеринга, в то время как генерация статического сайта полностью связана с предварительным рендерингом, огромный веб-сайт, такой как stackoverflow, его избегает. Angular недавно добавил универсальный конструктор для предварительного рендеринга, но в этой серии я хочу исследовать другие способы.
Есть два метода, которые я хочу проверить при выполнении нашего собственного предварительного рендеринга.
- Старый добрый экспресс-сервер
- Дополнительный продукт конструктора пререндеринга Angular
На сервере также используется Angular RenderModule
, но это избыточно.
Сегодня мы собираемся создать наш Express-сервер для предварительного рендеринга страниц, а на следующей неделе мы рассмотрим различные аспекты создания многоязычного Angular-приложения одной сборки, о которых мы говорили ранее.
Некоторые заметки о проекте StackBlitz
Я создал простой проект StackBlitz для создания приложения SSR, но, к сожалению, мне не удалось создать
index.html
в клиенте. Если вы запуститеnpm run build:ssr
, он может застрять наindex.html
. Отмените шаг, продолжите и исправьте индекс самостоятельно. Я пропатчил файл в StackBlitz, но это означало, что создание предварительно отрендеренного файла индекса для корня не дало правильных результатов. Как бы то ни было, StackBlitz!
В этом проекте показан простой сервер Express, о котором мы говорили ранее в разделе Изоляция сервера.
Предоставленная версия Node не поддерживает
fetch
, поэтому мы используем библиотекуnode-fetch
, которая не являетсяcommonjs
, поэтому решение (согласно документации) состоит в том, чтобы импортировать ее следующим образом:
const fetch = (...args) => import('node-fetch')
.then(({ default: fetch }) => fetch(...args));
Запуск локального экспресс-сервера
Самый простой и простой способ — настроить локальный сервер Express и использовать простой fetch
в Node. fetch
доступна с версии Node 17, до тех пор вы можете использовать библиотеку node-fetch
.
Текущая настройка выглядит следующим образом:
- В папке
src
находятся модули Angular, в том числеapp.server.module
- Сборка создает клиентские файлы под
host/client
и SSR подhost/server/main.js
host/server.js
имеет изолированный сервер Express, на котором работает Angular на локальном порту.host/server/routes.js
имеет маршруты, которые импортируют AngularngExpressEngine
, экспортированные изapp.server.module
- Наш новый файл выборки находится под
host/prerender/fetch.js
Итак, сначала давайте создадим модуль выборки для предварительного рендеринга:
// host/prerender/fetch.js file async function renderToHtml(route, port) { // run url in localhost // do something with returned text // return } // export some function here: module.exports = async (port) => { // generate /client/static/route/index.html // my static routes, example routes const routes = ['', 'projects', 'projects/1'];
for (const route of routes) { await renderToHtml(route, port); } };
Мы адаптируем наш сервер, чтобы он что-то делал в случае установки переменной среды, например:
// host/server.js // ... // just when you start listening:
const port = process.env.PORT || 1212;
// assign a server to be able to close later // turn function to async to allow an await statement const server = app.listen(port, async function (err) { console.log('started to listen to port: ' + port); if (err) { console.log(err); return; } // if process.env.PRERENDER, then run this and close if (process.env.PRERENDER) { const prerender = require('./prerender/fetch'); // await fetch before you close here // pass the port to reuse it await prerender(port); server.close(); } });
Для запуска в режиме пререндеринга мы создаем быстрый скрипт npm
в корне папки host.
"prerender": "SET PRERENDER=true && node server"
Или в других, отличных от Windows (например, StackBlitz, найдите скрипт в корневых пакетах с cd host
первым)
"prerender": "PRERENDER=true node server”
Функция renderToHtml
должна делать следующее:
- получить маршрут в среде SSR
- сохранить выходную строку в файл
index.html
- поместите файл в путь, соответствующий маршруту
- сохраните его в легкодоступном месте не только для Express, но и для Firebase, Netlify и Surge. Таким образом, место назначения должно находиться внутри папки
client
(общедоступная папка облачных хостов).
Для этого мы будем использовать пакет Node fs/promises
, это позволит нам закрыть порт, когда закончите. Сейчас я решил поместить их в папку /client/static
. В Express это легко сделать. В других хостах, таких как Netlify, проще просто разместить их в корневом каталоге /client
.
// host/prerender/fetch.js const fs = require('fs/promises');
// this should be part of a config passed down from server listener // client/static for Express, or simply client for cloud hosts const prerenderOut = './client/static/';
async function renderToHtml(route, port, outFolder) { // fetch it const response = await fetch(`http://localhost:${port}/${route}`); if (response.ok) { const text = await response.text(); // the output folder is ./client/static/{route}, relative to root server file const d = outFolder + route; // mkdir recursive, creates the folder structure await fs.mkdir(d, {recursive: true}); // create index.html, and write text to it. await fs.writeFile(d + '/index.html', text); // loggin success console.log('ok', route, text.length); } else { // log errores console.log('not ok', route, response.status); }
}
module.exports = async (port) => { // generate /client/static/route/index.html // my static routes, example routes (you could run an API call to get all paths) const routes = ['', 'projects', 'projects/1'];
for (const route of routes) { await renderToHtml(route, port, prerenderOut); } }
Одна вещь, которую нужно улучшить, — это удалить статическую папку, прежде чем мы начнем ее создавать, чтобы убедиться, что мы получаем новую копию каждый раз, когда мы ее создаем. В облачных хостах все будет немного иначе.
// prerender/fetch.js
module.exports = async (port) => {
// ...
// remove static folder first
await fs.rm(prerenderOut, {recursive: true, force: true});
// ...
}
Запустите скрипт и посмотрите, как будут созданы файлы. Примечание: крутая вещь в универсальных пакетах Angular заключается в том, что они также создают встроенный критический CSS для каждого пути.
Экспресс маршруты
Чтобы обслуживать эти статические файлы в Express, создается новый статический адаптер, открывающий содержимое /client/static
в корень, поэтому в файле routes.js
(который содержит необходимые маршруты к серверу Angular SSR в Express):
// host/server/routes.js // this should be part of a config file passed down from server listener const rootPath = path.normalize(__dirname + '/../');
module.exports = function (app) { // expose static folder app.use('/', express.static(rootPath + 'client/static')); // ... other routes }
Чтобы проверить это, сначала мы перейдем в папку хоста и запустим node server
. Затем перейдите к localhost:1212
и, чтобы отличить статические файлы от файлов, обслуживаемых Angular, мы делаем следующее:
- Измените название статических файлов на то, что мы можем распознать, например «Статические…»
- Отключить JavaScript в браузере
Теперь, если мы видим, что заголовок изменился, значит, обслуживается статическая страница. Конечно, JavaScript будет гидратироваться при включении, и клиентская часть Angular вступит во владение.
Единая многоязычная сборка
Погружаясь в грех искажения Angular Localization, давайте создадим статические файлы для разных языков в одной сборке, сделаем для этого перерыв и вернемся на следующей неделе. 😴
Спасибо, что прочитали это короткое вступление, дайте мне знать, если у вас появились двойники.
РЕСУРСЫ
- Угловой — Пререндеринг
- Проект StackBlitz
- Экспресс API
- Файловая система NodeJs
- Выбор MDN
- Библиотека извлечения узлов