feat(docs-infra): Convert AIO to use the new Service Worker 5.0.0. (#19795)
AIO is currently using a beta version of @angular/service-worker. Since that was implemented, the SW has been rewritten and released as part of Angular 5.0.0. This commit updates AIO to use the latest implementation, with an appropriate configuration file that caches the various AIO assets in useful ways. PR Close #19795
This commit is contained in:

committed by
Matias Niemelä

parent
66ffa360df
commit
be24f9f0cb
@ -2,6 +2,7 @@ import { BrowserModule } from '@angular/platform-browser';
|
||||
import { ErrorHandler, NgModule } from '@angular/core';
|
||||
import { HttpClientModule } from '@angular/common/http';
|
||||
import { BrowserAnimationsModule } from '@angular/platform-browser/animations';
|
||||
import { ServiceWorkerModule } from '@angular/service-worker';
|
||||
|
||||
import { Location, LocationStrategy, PathLocationStrategy } from '@angular/common';
|
||||
|
||||
@ -40,6 +41,8 @@ import { CustomElementsModule } from 'app/custom-elements/custom-elements.module
|
||||
import { SharedModule } from 'app/shared/shared.module';
|
||||
import { SwUpdatesModule } from 'app/sw-updates/sw-updates.module';
|
||||
|
||||
import {environment} from '../environments/environment';
|
||||
|
||||
// These are the hardcoded inline svg sources to be used by the `<mat-icon>` component
|
||||
export const svgIconProviders = [
|
||||
{
|
||||
@ -99,6 +102,7 @@ export const svgIconProviders = [
|
||||
MatToolbarModule,
|
||||
SwUpdatesModule,
|
||||
SharedModule,
|
||||
ServiceWorkerModule.register('/ngsw-worker.js', {enabled: environment.production}),
|
||||
],
|
||||
declarations: [
|
||||
AppComponent,
|
||||
|
@ -2,6 +2,8 @@ import { Injectable } from '@angular/core';
|
||||
|
||||
import { from as fromPromise, Observable } from 'rxjs';
|
||||
import { first, map, share } from 'rxjs/operators';
|
||||
import 'rxjs/add/operator/map';
|
||||
import 'rxjs/add/operator/share';
|
||||
|
||||
import { Logger } from 'app/shared/logger.service';
|
||||
|
||||
|
@ -1,6 +1,7 @@
|
||||
import { AfterViewInit, Component, ElementRef, OnDestroy, OnInit, QueryList, ViewChildren } from '@angular/core';
|
||||
import { asapScheduler as asap, combineLatest, Subject } from 'rxjs';
|
||||
import { startWith, subscribeOn, takeUntil } from 'rxjs/operators';
|
||||
import 'rxjs/add/operator/startWith';
|
||||
|
||||
import { ScrollService } from 'app/shared/scroll.service';
|
||||
import { TocItem, TocService } from 'app/shared/toc.service';
|
||||
|
@ -3,6 +3,7 @@ import { HttpClient, HttpErrorResponse } from '@angular/common/http';
|
||||
|
||||
import { AsyncSubject, Observable, of } from 'rxjs';
|
||||
import { catchError, switchMap, tap } from 'rxjs/operators';
|
||||
import 'rxjs/add/operator/do';
|
||||
|
||||
import { DocumentContents } from './document-contents';
|
||||
export { DocumentContents } from './document-contents';
|
||||
|
@ -5,7 +5,7 @@
|
||||
|
||||
var SEARCH_TERMS_URL = '/generated/docs/app/search-data.json';
|
||||
|
||||
// NOTE: This needs to be kept in sync with `ngsw-manifest.json`.
|
||||
// NOTE: This needs to be kept in sync with `ngsw-config.json`.
|
||||
importScripts('/assets/js/lunr.min.js');
|
||||
|
||||
var index;
|
||||
|
@ -1,13 +1,9 @@
|
||||
import { NgModule } from '@angular/core';
|
||||
import { ServiceWorkerModule } from '@angular/service-worker';
|
||||
|
||||
import { SwUpdatesService } from './sw-updates.service';
|
||||
|
||||
|
||||
@NgModule({
|
||||
imports: [
|
||||
ServiceWorkerModule
|
||||
],
|
||||
providers: [
|
||||
SwUpdatesService
|
||||
]
|
||||
|
@ -1,41 +1,41 @@
|
||||
import { ApplicationRef, ReflectiveInjector } from '@angular/core';
|
||||
import { fakeAsync, tick } from '@angular/core/testing';
|
||||
import { NgServiceWorker } from '@angular/service-worker';
|
||||
import { discardPeriodicTasks, fakeAsync, tick } from '@angular/core/testing';
|
||||
import { SwUpdate } from '@angular/service-worker';
|
||||
import { Subject } from 'rxjs';
|
||||
import { take } from 'rxjs/operators';
|
||||
|
||||
import { Logger } from 'app/shared/logger.service';
|
||||
import { SwUpdatesService } from './sw-updates.service';
|
||||
|
||||
|
||||
describe('SwUpdatesService', () => {
|
||||
let injector: ReflectiveInjector;
|
||||
let appRef: MockApplicationRef;
|
||||
let service: SwUpdatesService;
|
||||
let sw: MockNgServiceWorker;
|
||||
let swu: MockSwUpdate;
|
||||
let checkInterval: number;
|
||||
|
||||
// Helpers
|
||||
// NOTE:
|
||||
// Because `SwUpdatesService` uses the `debounceTime` operator, it needs to be instantiated and
|
||||
// Because `SwUpdatesService` uses the `interval` operator, it needs to be instantiated and
|
||||
// destroyed inside the `fakeAsync` zone (when `fakeAsync` is used for the test). Thus, we can't
|
||||
// run `setup()`/`tearDown()` in `beforeEach()`/`afterEach()` blocks. We use the `run()` helper
|
||||
// to call them inside each test's zone.
|
||||
const setup = () => {
|
||||
const setup = (isSwUpdateEnabled: boolean) => {
|
||||
injector = ReflectiveInjector.resolveAndCreate([
|
||||
{ provide: ApplicationRef, useClass: MockApplicationRef },
|
||||
{ provide: Logger, useClass: MockLogger },
|
||||
{ provide: NgServiceWorker, useClass: MockNgServiceWorker },
|
||||
{ provide: SwUpdate, useFactory: () => new MockSwUpdate(isSwUpdateEnabled) },
|
||||
SwUpdatesService
|
||||
]);
|
||||
|
||||
appRef = injector.get(ApplicationRef);
|
||||
service = injector.get(SwUpdatesService);
|
||||
sw = injector.get(NgServiceWorker);
|
||||
swu = injector.get(SwUpdate);
|
||||
checkInterval = (service as any).checkInterval;
|
||||
};
|
||||
const tearDown = () => service.ngOnDestroy();
|
||||
const run = (specFn: VoidFunction) => () => {
|
||||
setup();
|
||||
const run = (specFn: VoidFunction, isSwUpdateEnabled = true) => () => {
|
||||
setup(isSwUpdateEnabled);
|
||||
specFn();
|
||||
tearDown();
|
||||
};
|
||||
@ -46,109 +46,153 @@ describe('SwUpdatesService', () => {
|
||||
}));
|
||||
|
||||
it('should start checking for updates when instantiated (once the app stabilizes)', run(() => {
|
||||
expect(sw.checkForUpdate).not.toHaveBeenCalled();
|
||||
expect(swu.checkForUpdate).not.toHaveBeenCalled();
|
||||
|
||||
appRef.isStable.next(false);
|
||||
expect(sw.checkForUpdate).not.toHaveBeenCalled();
|
||||
expect(swu.checkForUpdate).not.toHaveBeenCalled();
|
||||
|
||||
appRef.isStable.next(true);
|
||||
expect(sw.checkForUpdate).toHaveBeenCalled();
|
||||
expect(swu.checkForUpdate).toHaveBeenCalled();
|
||||
}));
|
||||
|
||||
it('should schedule a new check if there is no update available', fakeAsync(run(() => {
|
||||
it('should periodically check for updates', fakeAsync(run(() => {
|
||||
appRef.isStable.next(true);
|
||||
sw.checkForUpdate.calls.reset();
|
||||
|
||||
sw.$$checkForUpdateSubj.next(false);
|
||||
expect(sw.checkForUpdate).not.toHaveBeenCalled();
|
||||
swu.checkForUpdate.calls.reset();
|
||||
|
||||
tick(checkInterval);
|
||||
expect(sw.checkForUpdate).toHaveBeenCalled();
|
||||
expect(sw.activateUpdate).not.toHaveBeenCalled();
|
||||
expect(swu.checkForUpdate).toHaveBeenCalledTimes(1);
|
||||
|
||||
tick(checkInterval);
|
||||
expect(swu.checkForUpdate).toHaveBeenCalledTimes(2);
|
||||
|
||||
appRef.isStable.next(false);
|
||||
|
||||
tick(checkInterval);
|
||||
expect(swu.checkForUpdate).toHaveBeenCalledTimes(3);
|
||||
|
||||
discardPeriodicTasks();
|
||||
})));
|
||||
|
||||
it('should activate new updates immediately', fakeAsync(run(() => {
|
||||
it('should activate available updates immediately', fakeAsync(run(() => {
|
||||
appRef.isStable.next(true);
|
||||
sw.checkForUpdate.calls.reset();
|
||||
expect(swu.activateUpdate).not.toHaveBeenCalled();
|
||||
|
||||
sw.$$checkForUpdateSubj.next(true);
|
||||
expect(sw.checkForUpdate).not.toHaveBeenCalled();
|
||||
|
||||
tick(checkInterval);
|
||||
expect(sw.checkForUpdate).not.toHaveBeenCalled();
|
||||
expect(sw.activateUpdate).toHaveBeenCalled();
|
||||
swu.$$availableSubj.next({available: {hash: 'foo'}});
|
||||
expect(swu.activateUpdate).toHaveBeenCalled();
|
||||
})));
|
||||
|
||||
it('should not pass a specific version to `NgServiceWorker.activateUpdate()`', fakeAsync(run(() => {
|
||||
it('should keep periodically checking for updates even after one is available/activated', fakeAsync(run(() => {
|
||||
appRef.isStable.next(true);
|
||||
sw.$$checkForUpdateSubj.next(true);
|
||||
tick(checkInterval);
|
||||
|
||||
expect(sw.activateUpdate).toHaveBeenCalledWith(null);
|
||||
})));
|
||||
|
||||
it('should schedule a new check after activating the update', fakeAsync(run(() => {
|
||||
appRef.isStable.next(true);
|
||||
sw.checkForUpdate.calls.reset();
|
||||
sw.$$checkForUpdateSubj.next(true);
|
||||
swu.checkForUpdate.calls.reset();
|
||||
|
||||
tick(checkInterval);
|
||||
expect(sw.checkForUpdate).not.toHaveBeenCalled();
|
||||
expect(swu.checkForUpdate).toHaveBeenCalledTimes(1);
|
||||
|
||||
sw.$$activateUpdateSubj.next();
|
||||
expect(sw.checkForUpdate).not.toHaveBeenCalled();
|
||||
swu.$$availableSubj.next({available: {hash: 'foo'}});
|
||||
|
||||
tick(checkInterval);
|
||||
expect(sw.checkForUpdate).toHaveBeenCalled();
|
||||
expect(swu.checkForUpdate).toHaveBeenCalledTimes(2);
|
||||
|
||||
tick(checkInterval);
|
||||
expect(swu.checkForUpdate).toHaveBeenCalledTimes(3);
|
||||
|
||||
discardPeriodicTasks();
|
||||
})));
|
||||
|
||||
it('should emit on `updateActivated` when an update has been activated', run(() => {
|
||||
const activatedVersions: (string|undefined)[] = [];
|
||||
service.updateActivated.subscribe(v => activatedVersions.push(v));
|
||||
|
||||
sw.$$updatesSubj.next({type: 'pending', version: 'foo'});
|
||||
sw.$$updatesSubj.next({type: 'activation', version: 'bar'});
|
||||
sw.$$updatesSubj.next({type: 'pending', version: 'baz'});
|
||||
sw.$$updatesSubj.next({type: 'activation', version: 'qux'});
|
||||
swu.$$availableSubj.next({available: {hash: 'foo'}});
|
||||
swu.$$activatedSubj.next({current: {hash: 'bar'}});
|
||||
swu.$$availableSubj.next({available: {hash: 'baz'}});
|
||||
swu.$$activatedSubj.next({current: {hash: 'qux'}});
|
||||
|
||||
expect(activatedVersions).toEqual(['bar', 'qux']);
|
||||
}));
|
||||
|
||||
describe('when `SwUpdate` is not enabled', () => {
|
||||
const runDeactivated = (specFn: VoidFunction) => run(specFn, false);
|
||||
|
||||
it('should not check for updates', fakeAsync(runDeactivated(() => {
|
||||
appRef.isStable.next(true);
|
||||
|
||||
tick(checkInterval);
|
||||
tick(checkInterval);
|
||||
|
||||
swu.$$availableSubj.next({available: {hash: 'foo'}});
|
||||
swu.$$activatedSubj.next({current: {hash: 'bar'}});
|
||||
|
||||
tick(checkInterval);
|
||||
tick(checkInterval);
|
||||
|
||||
expect(swu.checkForUpdate).not.toHaveBeenCalled();
|
||||
})));
|
||||
|
||||
it('should not activate available updates', fakeAsync(runDeactivated(() => {
|
||||
swu.$$availableSubj.next({available: {hash: 'foo'}});
|
||||
expect(swu.activateUpdate).not.toHaveBeenCalled();
|
||||
})));
|
||||
|
||||
it('should never emit on `updateActivated`', runDeactivated(() => {
|
||||
const activatedVersions: (string|undefined)[] = [];
|
||||
service.updateActivated.subscribe(v => activatedVersions.push(v));
|
||||
|
||||
swu.$$availableSubj.next({available: {hash: 'foo'}});
|
||||
swu.$$activatedSubj.next({current: {hash: 'bar'}});
|
||||
swu.$$availableSubj.next({available: {hash: 'baz'}});
|
||||
swu.$$activatedSubj.next({current: {hash: 'qux'}});
|
||||
|
||||
expect(activatedVersions).toEqual([]);
|
||||
}));
|
||||
});
|
||||
|
||||
describe('when destroyed', () => {
|
||||
it('should not schedule a new check for update (after current check)', fakeAsync(run(() => {
|
||||
appRef.isStable.next(true);
|
||||
sw.checkForUpdate.calls.reset();
|
||||
expect(swu.checkForUpdate).toHaveBeenCalled();
|
||||
|
||||
service.ngOnDestroy();
|
||||
sw.$$checkForUpdateSubj.next(false);
|
||||
swu.checkForUpdate.calls.reset();
|
||||
|
||||
tick(checkInterval);
|
||||
tick(checkInterval);
|
||||
|
||||
expect(sw.checkForUpdate).not.toHaveBeenCalled();
|
||||
expect(swu.checkForUpdate).not.toHaveBeenCalled();
|
||||
})));
|
||||
|
||||
it('should not schedule a new check for update (after activating an update)', fakeAsync(run(() => {
|
||||
appRef.isStable.next(true);
|
||||
sw.checkForUpdate.calls.reset();
|
||||
|
||||
sw.$$checkForUpdateSubj.next(true);
|
||||
expect(sw.activateUpdate).toHaveBeenCalled();
|
||||
expect(swu.checkForUpdate).toHaveBeenCalled();
|
||||
|
||||
service.ngOnDestroy();
|
||||
sw.$$activateUpdateSubj.next();
|
||||
swu.checkForUpdate.calls.reset();
|
||||
|
||||
swu.$$availableSubj.next({available: {hash: 'foo'}});
|
||||
swu.$$activatedSubj.next({current: {hash: 'baz'}});
|
||||
|
||||
tick(checkInterval);
|
||||
tick(checkInterval);
|
||||
|
||||
expect(sw.checkForUpdate).not.toHaveBeenCalled();
|
||||
expect(swu.checkForUpdate).not.toHaveBeenCalled();
|
||||
})));
|
||||
|
||||
it('should not activate available updates', fakeAsync(run(() => {
|
||||
service.ngOnDestroy();
|
||||
swu.$$availableSubj.next({available: {hash: 'foo'}});
|
||||
|
||||
expect(swu.activateUpdate).not.toHaveBeenCalled();
|
||||
})));
|
||||
|
||||
it('should stop emitting on `updateActivated`', run(() => {
|
||||
const activatedVersions: (string|undefined)[] = [];
|
||||
service.updateActivated.subscribe(v => activatedVersions.push(v));
|
||||
|
||||
sw.$$updatesSubj.next({type: 'pending', version: 'foo'});
|
||||
sw.$$updatesSubj.next({type: 'activation', version: 'bar'});
|
||||
swu.$$availableSubj.next({available: {hash: 'foo'}});
|
||||
swu.$$activatedSubj.next({current: {hash: 'bar'}});
|
||||
service.ngOnDestroy();
|
||||
sw.$$updatesSubj.next({type: 'pending', version: 'baz'});
|
||||
sw.$$updatesSubj.next({type: 'activation', version: 'qux'});
|
||||
swu.$$availableSubj.next({available: {hash: 'baz'}});
|
||||
swu.$$activatedSubj.next({current: {hash: 'qux'}});
|
||||
|
||||
expect(activatedVersions).toEqual(['bar']);
|
||||
}));
|
||||
@ -164,16 +208,18 @@ class MockLogger {
|
||||
log = jasmine.createSpy('MockLogger.log');
|
||||
}
|
||||
|
||||
class MockNgServiceWorker {
|
||||
$$activateUpdateSubj = new Subject<boolean>();
|
||||
$$checkForUpdateSubj = new Subject<boolean>();
|
||||
$$updatesSubj = new Subject<{type: string, version: string}>();
|
||||
class MockSwUpdate {
|
||||
$$availableSubj = new Subject<{available: {hash: string}}>();
|
||||
$$activatedSubj = new Subject<{current: {hash: string}}>();
|
||||
|
||||
updates = this.$$updatesSubj.asObservable();
|
||||
available = this.$$availableSubj.asObservable();
|
||||
activated = this.$$activatedSubj.asObservable();
|
||||
|
||||
activateUpdate = jasmine.createSpy('MockNgServiceWorker.activateUpdate')
|
||||
.and.callFake(() => this.$$activateUpdateSubj.pipe(take(1)));
|
||||
activateUpdate = jasmine.createSpy('MockSwUpdate.activateUpdate')
|
||||
.and.callFake(() => Promise.resolve());
|
||||
|
||||
checkForUpdate = jasmine.createSpy('MockNgServiceWorker.checkForUpdate')
|
||||
.and.callFake(() => this.$$checkForUpdateSubj.pipe(take(1)));
|
||||
checkForUpdate = jasmine.createSpy('MockSwUpdate.checkForUpdate')
|
||||
.and.callFake(() => Promise.resolve());
|
||||
|
||||
constructor(public isEnabled: boolean) {}
|
||||
}
|
||||
|
@ -1,7 +1,7 @@
|
||||
import { ApplicationRef, Injectable, OnDestroy } from '@angular/core';
|
||||
import { NgServiceWorker } from '@angular/service-worker';
|
||||
import { concat, Subject } from 'rxjs';
|
||||
import { debounceTime, defaultIfEmpty, filter, first, map, startWith, takeUntil, tap } from 'rxjs/operators';
|
||||
import { SwUpdate } from '@angular/service-worker';
|
||||
import { concat, interval, NEVER, Observable, Subject } from 'rxjs';
|
||||
import { first, map, takeUntil, tap } from 'rxjs/operators';
|
||||
|
||||
import { Logger } from 'app/shared/logger.service';
|
||||
|
||||
@ -11,63 +11,55 @@ import { Logger } from 'app/shared/logger.service';
|
||||
*
|
||||
* @description
|
||||
* 1. Checks for available ServiceWorker updates once instantiated.
|
||||
* 2. As long as there is no update available, re-checks every 6 hours.
|
||||
* 3. As soon as an update is detected, it activates the update and notifies interested parties.
|
||||
* 4. It continues to check for available updates.
|
||||
* 2. Re-checks every 6 hours.
|
||||
* 3. Whenever an update is available, it activates the update.
|
||||
*
|
||||
* @property
|
||||
* `updateActivated` {Observable<string>} - Emit the version hash whenever an update is activated.
|
||||
*/
|
||||
@Injectable()
|
||||
export class SwUpdatesService implements OnDestroy {
|
||||
private checkInterval = 1000 * 60 * 60 * 6; // 6 hours
|
||||
private checkInterval = 1000 * 60 * 60 * 6; // 6 hours
|
||||
private onDestroy = new Subject<void>();
|
||||
private checkForUpdateSubj = new Subject<void>();
|
||||
updateActivated = this.sw.updates.pipe(
|
||||
takeUntil(this.onDestroy),
|
||||
tap(evt => this.log(`Update event: ${JSON.stringify(evt)}`)),
|
||||
filter(({type}) => type === 'activation'),
|
||||
map(({version}) => version),
|
||||
);
|
||||
updateActivated: Observable<string>;
|
||||
|
||||
constructor(appRef: ApplicationRef, private logger: Logger, private sw: NgServiceWorker) {
|
||||
const appIsStable$ = appRef.isStable.pipe(first(v => v));
|
||||
const checkForUpdates$ = this.checkForUpdateSubj.pipe(debounceTime(this.checkInterval), startWith<void>(undefined));
|
||||
constructor(appRef: ApplicationRef, private logger: Logger, private swu: SwUpdate) {
|
||||
if (!swu.isEnabled) {
|
||||
this.updateActivated = NEVER.pipe(takeUntil(this.onDestroy));
|
||||
return;
|
||||
}
|
||||
|
||||
concat(appIsStable$, checkForUpdates$)
|
||||
.pipe(takeUntil(this.onDestroy))
|
||||
.subscribe(() => this.checkForUpdate());
|
||||
// Periodically check for updates (after the app is stabilized).
|
||||
const appIsStable = appRef.isStable.pipe(first(v => v));
|
||||
concat(appIsStable, interval(this.checkInterval))
|
||||
.pipe(
|
||||
tap(() => this.log('Checking for update...')),
|
||||
takeUntil(this.onDestroy),
|
||||
)
|
||||
.subscribe(() => this.swu.checkForUpdate());
|
||||
|
||||
// Activate available updates.
|
||||
this.swu.available
|
||||
.pipe(
|
||||
tap(evt => this.log(`Update available: ${JSON.stringify(evt)}`)),
|
||||
takeUntil(this.onDestroy),
|
||||
)
|
||||
.subscribe(() => this.swu.activateUpdate());
|
||||
|
||||
// Notify about activated updates.
|
||||
this.updateActivated = this.swu.activated.pipe(
|
||||
tap(evt => this.log(`Update activated: ${JSON.stringify(evt)}`)),
|
||||
map(evt => evt.current.hash),
|
||||
takeUntil(this.onDestroy),
|
||||
);
|
||||
}
|
||||
|
||||
ngOnDestroy() {
|
||||
this.onDestroy.next();
|
||||
}
|
||||
|
||||
private activateUpdate() {
|
||||
this.log('Activating update...');
|
||||
this.sw.activateUpdate(null as any) // expects a non-null string
|
||||
.subscribe(() => this.scheduleCheckForUpdate());
|
||||
}
|
||||
|
||||
private checkForUpdate() {
|
||||
this.log('Checking for update...');
|
||||
this.sw.checkForUpdate()
|
||||
.pipe(
|
||||
// Temp workaround for https://github.com/angular/mobile-toolkit/pull/137.
|
||||
// TODO (gkalpak): Remove once #137 is fixed.
|
||||
defaultIfEmpty(false),
|
||||
first(),
|
||||
tap(v => this.log(`Update available: ${v}`)),
|
||||
)
|
||||
.subscribe(v => v ? this.activateUpdate() : this.scheduleCheckForUpdate());
|
||||
}
|
||||
|
||||
private log(message: string) {
|
||||
const timestamp = (new Date).toISOString();
|
||||
this.logger.log(`[SwUpdates - ${timestamp}]: ${message}`);
|
||||
}
|
||||
|
||||
private scheduleCheckForUpdate() {
|
||||
this.checkForUpdateSubj.next();
|
||||
}
|
||||
}
|
||||
|
@ -20,7 +20,7 @@
|
||||
<link rel="apple-touch-icon" sizes="144x144" href="assets/images/favicons/favicon-144x144.png">
|
||||
<link rel="apple-touch-icon-precomposed" sizes="144x144" href="assets/images/favicons/favicon-144x144.png">
|
||||
|
||||
<!-- NOTE: These need to be kept in sync with `ngsw-manifest.json`. -->
|
||||
<!-- NOTE: These need to be kept in sync with `ngsw-config.json`. -->
|
||||
<link href="https://fonts.googleapis.com/icon?family=Material+Icons" rel="stylesheet">
|
||||
<link href="https://fonts.googleapis.com/css?family=Droid+Sans+Mono" rel="stylesheet">
|
||||
<link href="https://maxcdn.bootstrapcdn.com/font-awesome/4.7.0/css/font-awesome.min.css" rel="stylesheet">
|
||||
|
@ -1,6 +1,5 @@
|
||||
import { enableProdMode, ApplicationRef } from '@angular/core';
|
||||
import { enableProdMode } from '@angular/core';
|
||||
import { platformBrowserDynamic } from '@angular/platform-browser-dynamic';
|
||||
import { first } from 'rxjs/operators';
|
||||
|
||||
import { AppModule } from './app/app.module';
|
||||
import { environment } from './environments/environment';
|
||||
@ -9,11 +8,5 @@ if (environment.production) {
|
||||
enableProdMode();
|
||||
}
|
||||
|
||||
platformBrowserDynamic().bootstrapModule(AppModule).then(ref => {
|
||||
if (environment.production && 'serviceWorker' in (navigator as any)) {
|
||||
const appRef: ApplicationRef = ref.injector.get(ApplicationRef);
|
||||
appRef.isStable.pipe(first(v => v)).subscribe(() => {
|
||||
(navigator as any).serviceWorker.register('/worker-basic.min.js');
|
||||
});
|
||||
}
|
||||
});
|
||||
platformBrowserDynamic().bootstrapModule(AppModule);
|
||||
|
||||
|
75
aio/src/ngsw-config.json
Normal file
75
aio/src/ngsw-config.json
Normal file
@ -0,0 +1,75 @@
|
||||
{
|
||||
"index": "/index.html",
|
||||
"assetGroups": [
|
||||
{
|
||||
"name": "app-shell",
|
||||
"installMode": "prefetch",
|
||||
"updateMode": "prefetch",
|
||||
"resources": {
|
||||
"files": [
|
||||
"/index.html",
|
||||
"/pwa-manifest.json",
|
||||
"/app/search/search-worker.js",
|
||||
"/assets/images/favicons/favicon.ico",
|
||||
"/assets/js/*.js"
|
||||
],
|
||||
"urls": [
|
||||
"https://fonts.googleapis.com/**",
|
||||
"https://fonts.gstatic.com/s/**",
|
||||
"https://maxcdn.bootstrapcdn.com/**"
|
||||
],
|
||||
"versionedFiles": [
|
||||
"/*.bundle.css",
|
||||
"/*.bundle.js",
|
||||
"/*.chunk.js"
|
||||
]
|
||||
}
|
||||
}, {
|
||||
"name": "assets-eager",
|
||||
"installMode": "prefetch",
|
||||
"updateMode": "prefetch",
|
||||
"resources": {
|
||||
"files": [
|
||||
"/assets/images/**",
|
||||
"/generated/images/marketing/**",
|
||||
"!/assets/images/favicons/**",
|
||||
"!/**/_unused/**"
|
||||
]
|
||||
}
|
||||
}, {
|
||||
"name": "assets-lazy",
|
||||
"installMode": "lazy",
|
||||
"updateMode": "prefetch",
|
||||
"resources": {
|
||||
"files": [
|
||||
"/assets/images/favicons/**",
|
||||
"/generated/ie-polyfills.min.js",
|
||||
"!/**/_unused/**"
|
||||
]
|
||||
}
|
||||
}, {
|
||||
"name": "docs-index",
|
||||
"installMode": "prefetch",
|
||||
"updateMode": "prefetch",
|
||||
"resources": {
|
||||
"files": [
|
||||
"/generated/*.json",
|
||||
"/generated/docs/*.json",
|
||||
"/generated/docs/api/api-list.json",
|
||||
"/generated/docs/app/search-data.json"
|
||||
]
|
||||
}
|
||||
}, {
|
||||
"name": "docs-lazy",
|
||||
"installMode": "lazy",
|
||||
"updateMode": "lazy",
|
||||
"resources": {
|
||||
"files": [
|
||||
"/generated/docs/**/*.json",
|
||||
"/generated/images/**",
|
||||
"!/**/_unused/**"
|
||||
]
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
Reference in New Issue
Block a user