Skip to content

Commit

Permalink
Server Rendering Updates
Browse files Browse the repository at this point in the history
* Dynamically generate sitemaps [resolves #97]
* Remove interval when rendering on server  [fixes #99]
* 1.3.8
  • Loading branch information
clarkmalmgren committed Jan 1, 2018
1 parent 95ec6ec commit 8bb7dc6
Show file tree
Hide file tree
Showing 8 changed files with 116 additions and 41 deletions.
2 changes: 1 addition & 1 deletion package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion package.json
@@ -1,6 +1,6 @@
{
"name": "bolingbrook-church",
"version": "1.3.7",
"version": "1.3.8",
"license": "MIT",
"angular-cli": {},
"scripts": {
Expand Down
21 changes: 15 additions & 6 deletions server.ts
Expand Up @@ -2,12 +2,12 @@
import 'zone.js/dist/zone-node';
import 'reflect-metadata';

import { renderModuleFactory } from '@angular/platform-server';
import { enableProdMode } from '@angular/core';

import * as express from 'express';
import { join } from 'path';
import { readFileSync } from 'fs';
import { renderModuleFactory } from '@angular/platform-server';
import { enableProdMode } from '@angular/core';
import * as express from 'express';
import { join } from 'path';
import { readFileSync } from 'fs';
import { Sitemap } from './server/sitemap';

// Faster server renders w/ Prod mode (dev mode never needed)
enableProdMode();
Expand All @@ -18,6 +18,8 @@ const app = express();
const PORT = process.env.PORT || 4000;
const DIST_FOLDER = join(process.cwd(), 'dist');

const sitemap = new Sitemap();

// Our index.html we'll use as our template
const template = readFileSync(join(DIST_FOLDER, 'browser', 'index.html')).toString();

Expand All @@ -43,6 +45,13 @@ app.engine('html', (_, options, callback) => {
app.set('view engine', 'html');
app.set('views', join(DIST_FOLDER, 'browser'));


// Render the Sitemap
app.get('*/sitemap.txt', (req, res) => {
res.set('Content-Type', 'plain/text');
sitemap.render().subscribe(map => { res.send(map); });
});

// Server static files from /browser
app.get('*.*', express.static(join(DIST_FOLDER, 'browser')));

Expand Down
45 changes: 45 additions & 0 deletions server/sitemap.ts
@@ -0,0 +1,45 @@
import { Observable, Observer } from '../src/app/services/observable';
import * as https from 'https';

export class Sitemap {

static DATABASE_URL: string = 'https://bolingbrook-church.firebaseio.com';
static HOST: string = 'https://bolingbrook.church';
static PAGES: string[] = [
'/',
'/about',
'/beliefs',
'/connect',
'/events',
'/friends-family-sabbath',
'/giving',
'/locations',
'/newsletter',
'/sermons',
'/serve',
'/serve/ask',
'/serve/fusion',
'/serve/media',
].map(p => Sitemap.HOST + p);

sermons(): Observable<string[]> {
return Observable.create((observer: Observer<string[]>) => {
https.get(`${Sitemap.DATABASE_URL}/data/sermons.json`, res => {
let body = '';
res.on('data', data => { body += data; });
res.on('end', () => {
const sermons = JSON.parse(body);
observer.next(Object.keys(sermons));
observer.complete();
});
});
})
}

render(): Observable<string> {
return this.sermons()
.map(dates => dates.map(d => Sitemap.HOST + '/sermons/' + d))
.map(sermons => Sitemap.PAGES.concat(sermons))
.map(urls => urls.join('\n'));
}
}
13 changes: 9 additions & 4 deletions src/app/components/pages/sermons/sermon.spec.ts
@@ -1,8 +1,10 @@
import { expect, sinon, async, MockBuilder, callCount, stubOf, spyOf } from 'testing';
import { ActivatedRoute } from '@angular/router';
import { SermonComponent } from './sermon';
import { MockAperture } from '../../../services/aperture.mock';
import {
Analytics,
Aperture,
FeatureToggles,
Observable,
SeriesImageService,
Expand All @@ -16,6 +18,8 @@ import {
/* tslint:disable: no-unused-expression */
describe('SermonComponent', () => {

const aperture = new MockAperture();

describe('ngOnInit', () => {

it('should subscribe to video state changes', async(() => {
Expand All @@ -27,11 +31,12 @@ describe('SermonComponent', () => {
.with('params', Observable.empty())
.build();


const togglesService = MockBuilder.of(TogglesService)
.withStub('getToggles', Observable.empty())
.build();

const sermon = new SermonComponent(activatedRoute, null, null, null, null, null, togglesService, youtubeService);
const sermon = new SermonComponent(activatedRoute, null, aperture, null, null, null, null, togglesService, youtubeService);

sermon.ngOnInit();

Expand Down Expand Up @@ -59,7 +64,7 @@ describe('SermonComponent', () => {
.withStub('getToggles', Observable.empty())
.build();

const sermon = new SermonComponent(activatedRoute, analytics, null, null, null, null, togglesService, youtubeService);
const sermon = new SermonComponent(activatedRoute, analytics, aperture, null, null, null, null, togglesService, youtubeService);
sermon.interval = () => Observable.from(['', '']);
sermon.videoState = VideoState.PLAYING;
sermon.sermon = { youtube: 'Jesus4Life' } as Sermon;
Expand Down Expand Up @@ -90,7 +95,7 @@ describe('SermonComponent', () => {
const togglesService = MockBuilder.of(TogglesService)
.withStub('getToggles', Observable.empty())
.build();
const sermon = new SermonComponent(activatedRoute, analytics, null, null, null, null, togglesService, youtubeService);
const sermon = new SermonComponent(activatedRoute, analytics, aperture, null, null, null, null, togglesService, youtubeService);
sermon.interval = () => Observable.of('');
sermon.videoState = VideoState.PLAYING;
sermon.live = true;
Expand Down Expand Up @@ -128,7 +133,7 @@ describe('SermonComponent', () => {
.withStub('getToggles', Observable.empty())
.build();

const sermon = new SermonComponent(activatedRoute, analytics, null, null, null, null, togglesService, youtubeService);
const sermon = new SermonComponent(activatedRoute, analytics, aperture, null, null, null, null, togglesService, youtubeService);
sermon.interval = () => Observable.of('');
sermon.videoState = state;
sermon.sermon = { youtube: 'Jesus4Life' } as Sermon;
Expand Down
4 changes: 3 additions & 1 deletion src/app/components/pages/sermons/sermon.ts
Expand Up @@ -5,6 +5,7 @@ import { Autoclean } from '../../template

import {
Analytics,
Aperture,
FeatureToggles,
Observable,
SeriesImageService,
Expand Down Expand Up @@ -34,6 +35,7 @@ export class SermonComponent extends Autoclean implements OnInit {
constructor(
private activatedRoute: ActivatedRoute,
private analytics: Analytics,
private aperture: Aperture,
private imageService: SeriesImageService,
private meta: Meta,
private sanitizer: DomSanitizer,
Expand All @@ -45,7 +47,7 @@ export class SermonComponent extends Autoclean implements OnInit {
}

interval(): Observable<any> {
return Observable.interval(this.analyticsInterval);
return this.aperture.browser ? Observable.interval(this.analyticsInterval) : Observable.empty();
}

ngOnInit() {
Expand Down
29 changes: 1 addition & 28 deletions src/app/services/analytics.spec.ts
Expand Up @@ -11,34 +11,7 @@ import { Analytics } from './analytics';
import { Aperture } from './aperture';
import { Env } from './env';
import { Observable } from './observable';

class MockAperture extends Aperture {
scrollTo(options?: ScrollToOptions): void {
throw new Error('Method not implemented.');
}
open(url?: string, target?: string, features?: string, replace?: boolean): Aperture {
throw new Error('Method not implemented.');
}
set(key: string, value: any): void {
throw new Error('Method not implemented.');
}
now(): number {
return performance.now()
}
create(target: any): Aperture {
throw new Error('Method not implemented.');
}
observableWindowEvent(eventName: string): Observable<Event> {
throw new Error('Method not implemented.');
}
constructor(private wrapped: Function) {
super();
}

get ga(): Function {
return this.wrapped;
}
}
import { MockAperture } from './aperture.mock';

/* tslint:disable: no-unused-expression */
describe('Analytics', () => {
Expand Down
41 changes: 41 additions & 0 deletions src/app/services/aperture.mock.ts
@@ -0,0 +1,41 @@
import { Aperture } from './aperture';
import { Observable } from './observable';

export class MockAperture extends Aperture {

browser = true;

constructor(
private _ga: Function = () => { }
) {
super();
}

scrollTo(options?: ScrollToOptions): void {
throw new Error('Method not implemented.');
}

open(url?: string, target?: string, features?: string, replace?: boolean): Aperture {
throw new Error('Method not implemented.');
}

set(key: string, value: any): void {
throw new Error('Method not implemented.');
}

now(): number {
return performance.now()
}

create(target: any): Aperture {
throw new Error('Method not implemented.');
}

observableWindowEvent(eventName: string): Observable<Event> {
throw new Error('Method not implemented.');
}

get ga(): Function {
return this._ga;
}
}

0 comments on commit 8bb7dc6

Please sign in to comment.