Анализ работы Guess.js в приложении Angular

Почему Guess.js?

Предзагрузка страниц  —  это особая функциональность или скрипт браузера. В режиме активации такой скрипт поручает браузеру загрузить ресурсы, которые может посетить пользователь, до фактического запроса нужного контента. Эти ресурсы загружаются и кэшируются для дальнейшего использования без выполнения явного запроса от пользователя. Данный процесс называется предварительной выборкой.

Механизм предзагрузки страниц повышает производительность, обеспечивая моментальную загрузку спрогнозированного контента. Однако он также может загрузить содержимое, невостребованное пользователем. Вот почему так важна “правильная” предзагрузка страниц, качественное выполнение которой улучшает пользовательский опыт.

Guess.js  —  это библиотека JavaScript и инструмент для автоматизации процесса прогнозируемой предварительной выборки. 

Знакомство с Guess.js

Библиотека Guess.js создана в апреле 2018 года. Текущая версия  —  0.4.22, и она находится еще в стадии альфа-версии. Guess.js выполняет предзагрузку страниц на основе данных, полученных такими аналитическими технологиями, как Google Аналитика и моделями МО. 

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

  • Прогнозирование следующей страницы (или страниц), которые с большей вероятностью посетит пользователь и предварительная выборка этих страниц.
     —  Страница: Предварительное отображение или предвыборка страницы, которую пользователь может открыть следующей. 
     —  Бандлы: Предварительная выборка бандлов (англ. bundle), связанных с N количеством первых страниц. 
  • Прогнозирование последующего фрагмента контента (статьи, продукта или видео), который может посмотреть пользователь. 
  • Прогнозирование типов виджетов, с которыми чаще всего взаимодействует пользователь, например игры. 

Guess.js поддерживает такие статические сайты, как Gatsby, Next.js, Nuxt.js и многое другое. В статье мы рассмотрим принцип работы Guess.js и для этой цели воспользуемся приложением Angular.

Анализ работы Guess.js в приложении Angular 

Исследование качества работы Guess.js проходит в рабочей среде Angular и состоит из 7 этапов. На первых трех мы создаем проект Angular с ленивой загрузкой для каждого маршрута, а на последующих четырех настраиваем и выполняем прогнозируемую предварительную выборку с помощью Guess.js.

  1. Создание проекта Angular.
  2. Настройка 3-х маршрутов: '/', '/one' и '/two'.
  3. Запуск приложения для просмотра загруженных бандлов. 
  4. Создание файла прогностической аналитики routes.json.
  5. Расширение Angular CLI для конфигурации Webpack. 
  6. Конфигурация Guess.js в приложении Angular. 
  7. Выполнение прогнозируемой предварительной выборки с помощью Guess.js.  

1. Создание проекта Angular 

Глобально устанавливаем Angular CLI:

% npm i -g @angular/cli

Проверяем версию:

% ng version
Angular CLI: 14.2.1

Установив ng, создаем новый проект: 

% ng new angular-guess
? Would you like to add Angular routing? Yes
? Which stylesheet format would you like to use? CSS

2. Настройка 3-х маршрутов: ‘/’, ‘/one’ и ‘/two’

Настраиваем 3 маршрута ('/', '/one' и '/two') и изменяем для них файлы приложения: 

На данном скриншоте вновь добавленные файлы выделены зеленым цветом, а измененные  —  желтым. 

Маршрут index, /

Модуль index определен в src/app/index/index.module.ts:

import { NgModule } from '@angular/core';
import { IndexComponent } from './index.component';
import { RouterModule } from '@angular/router';

@NgModule({
declarations: [IndexComponent],
imports: [
RouterModule.forChild([
{
path: '',
component: IndexComponent
}
])
]
})

export class IndexModule {}

Он определяет дочерний маршрут RouterModule (строки 8–13 в коде) с IndexComponent (строка 11).

IndexComponent определен в src/app/index/index.component.ts:

import { Component } from '@angular/core';

@Component({
selector: 'app-index',
template: 'Home1'
})

export class IndexComponent {}

Он определяет селектор как 'app-index' (строка 4). Шаблон  —  просто текст 'Home1' (строка 5). 

Маршрут /one

Модуль one определен в src/app/one/one.module.ts:

import { NgModule } from '@angular/core';
import { OneComponent } from './one.component';
import { RouterModule } from '@angular/router';

@NgModule({
declarations: [OneComponent],
imports: [
RouterModule.forChild([
{
path: '',
component: OneComponent
}
])
]
})

export class OneModule {}

Он определяет дочерний маршрут RouterModule (строки 8–13) с OneComponent (строка 11).

OneComponent определен в src/app/one/one.component.ts:

import { Component } from '@angular/core';

@Component({
selector: 'app-one',
template: 'One'
})

export class OneComponent {}

Он определяет селектор как 'app-one' (строка 4). Шаблон  —  просто текст 'One' (строка 5).

Маршрут /two

Модуль two определен в src/app/two/two.module.ts:

import { NgModule } from '@angular/core';
import { TwoComponent } from './two.component';
import { RouterModule } from '@angular/router';

@NgModule({
declarations: [TwoComponent],
imports: [
RouterModule.forChild([
{
path: '',
component: TwoComponent
}
])
]
})

export class TwoModule {}

Он определяет дочерний маршрут RouterModule (строки 8–13) с TwoComponent (строка 11).

TwoComponent определен в src/app/two/two.component.ts:

import { Component } from '@angular/core';

@Component({
selector: 'app-two',
template: 'Two'
})

export class TwoComponent {}

Он определяет селектор как 'app-two' (строка 4). Шаблон  —  просто текст 'Two' (строка 5).

Файлы приложения 

src/app/app-routing.module.ts определяет маршруты приложения:

import { NgModule } from '@angular/core';
import { RouterModule, Routes } from '@angular/router';

const routes: Routes = [
{
path: '',
pathMatch: 'full',
loadChildren: () => import('./index/index.module').then(m => m.IndexModule)
},
{
path: 'one',
loadChildren: () => import('./one/one.module').then(m => m.OneModule)
},
{
path: 'two',
loadChildren: () => import('./two/two.module').then(m => m.TwoModule)
},
];

@NgModule({
imports: [RouterModule.forRoot(routes)],
exports: [RouterModule]
})

export class AppRoutingModule { }

Маршруты определены в следующих строках: строки 5–9  — '' (маршрут index); строки 10–13  —  '/one'; строки 14–17 —  '/two'. Все они являются маршрутами с ленивой загрузкой (строки 8, 12 и 16). 

src/app/app.component.html определяет UI приложения, как показано ниже: 

<a routerLink="">Home</a>
<a routerLink="one">One</a>
<a routerLink="two">Two</a>

<br>

<router-outlet></router-outlet>

В верхней части указаны 3 ссылки: Home (строка 1), One (строка 2) и Two (строка 3). 

Строка 5 указывает место переноса строки.

Строка 7  —  это router-outlet, плейсхолдер для компонента выбранного маршрута. 

src/app/app.component.css определяет CSS приложения:

a {
margin-right: 5px;
}

Он просто добавляет интервал в 5 пикселей между ссылками. 

3. Запуск приложения для просмотра загруженных бандлов 

Запускаем приложение: ng serve.

Это базовое приложение, отображающее содержимое выбранного маршрута. 

Из-за применения ленивой загрузки каждая страница отправляет запрос к соответствующим бандлам JavaScript. Для просмотра сетевых запросов используется браузер Firefox. 

Чистим кэш перед выполнением: 

Мы работаем с Firefox, поскольку в его сетевом инспекторе есть столбец transferred. Он показывает количество фактически переданных байтов для загрузки ресурса или объясняет в сообщении, почему ресурс не был передан, возможно, кэширован cached.

Скриншот индексной страницы: 

На нем отчетливо видно, что загружен только один бандл: src_app_index_index_module_ts.js.

4. Создание файла прогностической аналитики routes.json

Как правило, в реальном приложении задействуются инструменты отслеживания и анализа или модели МО. В данном случае для простоты мы создадим файл прогностической аналитики routes.json:

{
"/": {
"/one": 80,
"/two": 20
},
"/one": {
"/": 90,
"/two": 10
},
"/two": {
"/": 90,
"/one": 10
}
}

Строка 2 подразумевает, что текущий сеанс  —  это '/'. В 80 сеансах пользователи посетили '/one' (строка 3), а в 20 сеансах  —  '/two' (строка 4).

Строка 6 указывает, что текущий сеанс  —  '/one'. В 90 сеансах пользователи посетили '/' (строка 7), а в 10 сеансах  —  '/two' (строка 8).

В строке 10 текущим сеансом значится '/two'. В 90 сеансах пользователи посетили '/' (строка 11), а в 10 сеансах  —  '/one' (строка 12).

5.Расширение Angular CLI для конфигурации Webpack

Angular 5 предоставлял команду ng eject, извлекающую базовую конфигурацию Webpack для кастомизации. В Angular 6 ей на смену пришли так называемые задачи-компоновщики builders, выполняющие кастомизацию: 

% npm i -D @angular-builders/custom-webpack

@angular-builders/custom-webpack является частью devDependencies в package.json:

"devDependencies": {
"@angular-builders/custom-webpack": "^14.0.1"
}

В angular.json изменяем значение builder с @angular-devkit/build-angular:browser на @angular-builders/custom-webpack:browser (строка 14 в нижепредставленном коде) и добавляем customWebpackConfig в options (строки 16–18):

{
"$schema": "./node_modules/@angular/cli/lib/config/schema.json",
"version": 1,
"newProjectRoot": "projects",
"projects": {
"angular-guess": {
"projectType": "application",
"schematics": {},
"root": "",
"sourceRoot": "src",
"prefix": "app",
"architect": {
"build": {
"builder": "@angular-builders/custom-webpack:browser",
"options": {
"customWebpackConfig": {
"path": "./extend.webpack.config.js"
},
"outputPath": "dist/angular-guess",
"index": "src/index.html",
"main": "src/main.ts",
"polyfills": "src/polyfills.ts",
"tsConfig": "tsconfig.app.json",
"assets": [
"src/favicon.ico",
"src/assets"
],
"styles": [
"src/styles.css"
],
"scripts": []
},
"configurations": {
...
},
"defaultConfiguration": "production"
},
...
}
}
}
}

Теперь extend.webpack.config.js можно применить для настройки Guess.js в Webpack.

6. Конфигурация Guess.js в приложении Angular

Устанавливаем пакеты Guess.js следующей командой: 

% npm i -D guess-webpack guess-parser

Пакет guess-webpack содержит плагин Guess.js Webpack, а пакет guess-parser —  набор парсеров для статического анализа приложений.  

Оба пакета используются в extend.webpack.config.js:

const { GuessPlugin } = require('guess-webpack');
const { parseRoutes } = require('guess-parser');

module.exports = {
plugins: [
new GuessPlugin({
// В качестве альтернативы вы можете предоставить ID представления Google Аналитики
// GA: 'XXXXXX',
reportProvider() {
return Promise.resolve(JSON.parse(require('fs').readFileSync('./routes.json')));
},
runtime: {
delegate: false
},
routeProvider() {
return parseRoutes('.');
}
})
]
};

Строки 9–11: reportProvider возвращает аналитические данные, на основе которых GuessPlugin создает модель МО. В упрощенном случае он считывает статический файл routes.json. Чаще всего для указания ID представления Google Аналитики применяется GA. Guess.js получает данные из аккаунта Google Аналитики и автоматически формирует отчет.

Строки 12–14: runtime задает конфигурацию времени выполнения, где delegate в значении false. Это позволяет Guess.js обрабатывать предварительную выборку бандлов. 

Строки 15–17: routeProvider делегирует свой вызов parseRoutes, который возвращает установленное соотношение между маршрутами и блоками кода JavaScript (англ. chunck). 

7. Выполнение прогнозируемой предварительной выборки с помощью Guess.js

Мы настроили Guess.js в приложении Angular. Прогнозируемая предварительная выборка работает только в среде продакшн. 

Создаем продакшн-сборку с помощью команды: 

% npm run build

Переходим в директорию dist для запуска продакшн-сборки: 

% cd dist/angular-guess && serve -s .

Переходим в браузер Firefox и чистим кэш.

  • Посещаем индексную страницу: 

Бандл index (в розовой рамке) 159.40d7a09f1a145796.js загружен. Бандл one (в красной рамке) 248.e52910797ae356c5.js предварительно загружен, при этом в столбце Initiator (инициатор) указано other.

На основании файла прогностической аналитики routes.json пользователь, находясь на http://localhost:3000, с большей вероятностью посетит /one, чем /two:

"/": {
"/one": 80,
"/two": 20
}

Следовательно, http://localhost:3000/ загружает бандлы не для /two, а для / и /one

  • Нажимаем ссылку One для перехода на /one:

Предварительно загруженный бандл 248.e52910797ae356c5.js (в зеленой рамке) загружен как cached.

На основании файла прогностической аналитики routes.json пользователь, находясь на http://localhost:3000/one, с большей вероятностью посетит /, чем /two:

"/one": {
"/": 90,
"/two": 10
}

Бандл для / уже загружен, и нет необходимости предварительно загружать бандл для /two.

  • Переходим по ссылке Two на /two:

Бандл 628.a486e5775634c464.js (оранжевой рамке) загружен. 

На основании файла прогностической аналитики routes.json пользователь, находясь на http://localhost:3000/two, с большей вероятностью посетит /, чем /one:

"/two": {
"/": 90,
"/one": 10
}

Бандл для / уже загружен, и нет необходимости предварительно загружать бандл для /one.

На этом этапе все бандлы загружены. 

Guess.js грамотно загружает или выполняет предзагрузку. С видео вышеописанного процесса можно ознакомиться по ссылке.

Репозиторий с полным вариантом кода представлен по этой ссылке

Заключение

Guess.js  —  это библиотека JavaScript и инструмент для автоматизации процесса прогнозируемой предварительной выборки. Мы изучили ее работу в приложении Angular и убедились, что она отлично справляется с поставленной задачей. Надеемся, что вскоре эта инновационная технология будет официально представлена.

Читайте также:

Читайте нас в TelegramVK и Дзен


Перевод статьи Jennifer Fu: Evaluating Guess.js in Angular Applications

Предыдущая статья4 расширения VS Code, которые пригодятся дата-инженеру
Следующая статьяБудьте благодарны за массивы JavaScript: сравнение с языком C