Ранее на этой неделе мы рассматривали возможность превращения базового приложения Angular в прогрессивное веб-приложение (здесь вы можете наверстать упущенное). Теперь, когда у нас есть приложение, которое является надежным и будет загружать контент из кеша даже при отсутствии сети, давайте сделаем наше приложение быстрым!

git clone --branch v1.0 https://github.com/MichaelSolati/ng-popular-movies-pwa.git
cd ng-popular-movies-pwa
npm install

Это приложение зависит от API The MovieDB. Получите ключ API (проверьте это) и поместите его как moviedb переменную среды в свои src/environments/environment.ts и src/environments/environment.prod.ts .

Давайте запустим наше приложение npm run start:pwa, а затем отключим JavaScript в нашем браузере. Все, что получит наш пользователь, - это черный экран:

Это определенно не поведение PWA и фактически связано с нашей последней темой о надежном приложении. Итак, давайте исправим это с помощью одного из инструментов в нашем ng-pwa-tools пакете, который мы добавили в наше приложение в прошлый раз. В частности, мы будем использовать инструмент ngu-app-shell.

Сначала мы собираемся перейти в src/app/app.module.ts файл и изменить наш импорт BrowserModule в строке 22 на BrowserModule.withServerTransition({ appId: 'ng-popular-movies-pwa' }). (Функция withServerTransition() настраивает наше приложение на основе браузера для перехода с предварительно обработанной страницы, подробности будут позже) Теперь давайте запустим наш ngu-app-shell.

./node_modules/.bin/ngu-app-shell --module src/app/app.module.ts

Вы должны увидеть, как вы авторизовались в своем терминале, и весь наш домашний маршрут отрисован! У нас есть весь наш HTML, CSS и даже данные, взятые из MovieDB. Наш ngu-app-shell сделал предварительный рендеринг нашего маршрута индекса почти так же, как это делает Angular Universal.

С предварительно обработанным домашним маршрутом нам не нужно беспокоиться, если у нашего пользователя отключен JavaScript или требуется время для загрузки и выполнения наших пакетов JS. У нас есть контент, уже преобразованный в HTML. Таким образом, мы можем использовать ngu-app-shell для замены нашего пустого dist/index.html отрендеренной страницей.

./node_modules/.bin/ngu-app-shell --module src/app/app.module.ts \
   --out dist/index.html

Пока мы здесь, давайте обновим наши npm скрипты следующим образом.

{
    "ng": "ng",
    "start": "ng serve",
    "start:pwa": "npm run build && cd dist && http-server",
    "build": "ng build --prod && npm run ngu-app-shell && npm run ngu-sw-manifest",
    "test": "ng test",
    "lint": "ng lint",
    "e2e": "ng e2e",
    "ngu-app-shell": "./node_modules/.bin/ngu-app-shell --module src/app/app.module.ts --out dist/index.html",
    "ngu-sw-manifest": "./node_modules/.bin/ngu-sw-manifest --module src/app/app.module.ts --out dist/ngsw-manifest.json"
}

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

Еще один способ ускорить работу нашего приложения - это «ленивая загрузка» частей нашего приложения. В Angular мы можем отложить загрузку модулей, что по сути означает, что мы можем группировать связанные части кода вместе и загружать эти части по запросу. Модули с отложенной загрузкой сокращают время запуска, поскольку не нужно загружать все сразу, а только то, что пользователю нужно, чтобы увидеть при первой загрузке приложения.

В нашей текущей структуре у нас есть только два маршрута, один модуль и, по сути, два компонента (я исключаю AppComponent, потому что все, что он делает, - это наша панель навигации). Итак, давайте создадим новый модуль для наших HomeComponent и MovieComponent и поместим компоненты в эти модули.

ng g m home
ng g m movie

Теперь давайте изменим наш src/app/home/home.module.ts, чтобы он выглядел так.

import { NgModule } from '@angular/core';
import { CommonModule } from '@angular/common';
import { MaterialModule } from '@angular/material';
import { RouterModule } from '@angular/router';
import { HomeComponent } from './home.component';
@NgModule({
  declarations: [
    HomeComponent
  ],
  imports: [
    CommonModule,
    MaterialModule,
    RouterModule.forChild([
      { path: '', pathMatch: 'full', component: HomeComponent }
    ])
  ]
})
export class HomeModule { }

Теперь мы изменим наш src/app/movie/movie.module.ts, сделав его похожим на наш HomeModule.

import { NgModule } from '@angular/core';
import { CommonModule } from '@angular/common';
import { MaterialModule } from '@angular/material';
import { RouterModule } from '@angular/router';
import { MovieComponent } from './movie.component';
@NgModule({
  declarations: [
    MovieComponent
  ],
  imports: [
    CommonModule,
    MaterialModule,
    RouterModule.forChild([
      { path: 'movie/:id', pathMatch: 'full', component: MovieComponent }
    ])
  ]
})
export class MovieModule { }

Мы также должны обновить src/app/app-routing.module.ts, чтобы отразить, что мы будем лениво загружать маршруты из наших модулей.

import { Routes, RouterModule } from '@angular/router';
const routes: Routes = [
  {
    path: '',
    loadChildren: 'app/home/home.module#HomeModule'
  }, {
    path: 'movie/:id',
    loadChildren: 'app/movie/movie.module#MovieModule'
  }, {
    path: 'movie',
    redirectTo: '/',
    pathMatch: 'full'
  }, {
    path: '**',
    redirectTo: '/'
  }
];
export const routing = RouterModule.forRoot(routes);

Наконец, мы обновим наш src/app/app.module.ts, чтобы отразить наш новый routing, а также удалим все ссылки на наши компоненты.

import { BrowserModule } from '@angular/platform-browser';
import { BrowserAnimationsModule } from '@angular/platform-browser/animations';
import { NgModule } from '@angular/core';
import { HttpModule } from '@angular/http';
import { MaterialModule } from '@angular/material';
import { MoviesService } from './services/movies.service';
import { NavbarService } from './services/navbar.service';
import { routing } from './app-routing.module';
import { AppComponent } from './app.component';
@NgModule({
  declarations: [
    AppComponent
  ],
  imports: [
    BrowserModule.withServerTransition({ appId: 'ng-popular-movies-pwa' }),
    HttpModule,
    BrowserAnimationsModule,
    MaterialModule,
    routing
  ],
  providers: [
    MoviesService,
    NavbarService
  ],
  bootstrap: [AppComponent]
})
export class AppModule {
  constructor(private _moviesService: MoviesService, private _navbarService: NavbarService) { }
}

Предварительно отрисовав наш домашний маршрут, а также отложив загрузку всех наших маршрутов, мы смогли не только сделать наше приложение быстрее, но и более надежным! Хотя у этого приложения может быть не так много маршрутов, из-за которых ленивая загрузка может сократить время начальной загрузки на несколько секунд, для ваших более крупных приложений это определенно будет.

Запустив тесты Lighthouse в нашем текущем приложении, мы можем увидеть, как наши оценки PWA и производительности увеличиваются с 36 (взяв оценку из предыдущей статьи, в которой не использовалось развернутое приложение) до 45 и 61 соответственно.

Вы можете посмотреть изменения, которые мы внесли в наш код, щелкнув здесь. Кроме того, если вы запустите Lighthouse в развернутой версии нашего приложения, вы начнете получать следующие результаты:

Финальная часть этой серии, озаглавленная PWA с Angular: привлекательность, доступна здесь.