Ранее на этой неделе мы рассматривали возможность превращения базового приложения 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: привлекательность, доступна здесь.