ПРЕДВАРИТЕЛЬНАЯ ВИЗУАЛИЗАЦИЯ В ANGULAR

Есть много причин для предварительного рендеринга, и многие не делают этого. Каждое приложение находит свой вариант использования для предварительного рендеринга, в то время как генерация статического сайта полностью связана с предварительным рендерингом, огромный веб-сайт, такой как stackoverflow, его избегает. Angular недавно добавил универсальный конструктор для предварительного рендеринга, но в этой серии я хочу исследовать другие способы.

Есть два метода, которые я хочу проверить при выполнении нашего собственного предварительного рендеринга.

  1. Старый добрый экспресс-сервер
  2. Дополнительный продукт конструктора пререндеринга 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 имеет маршруты, которые импортируют Angular ngExpressEngine, экспортированные из 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, давайте создадим статические файлы для разных языков в одной сборке, сделаем для этого перерыв и вернемся на следующей неделе. 😴

Спасибо, что прочитали это короткое вступление, дайте мне знать, если у вас появились двойники.

РЕСУРСЫ

ПОХОЖИЕ СООБЩЕНИЯ