Edited at

Cloud Functions for FirebaseでAngular Universal

More than 1 year has passed since last update.

名前: ちきさん

所属: オプト

GitHub/Twitter/Qiita: @ovrmrw


今回のGitHubリポジトリ

https://github.com/ovrmrw/angular-universal-with-firebase-hosting


参考にしたもの

https://github.com/angular/angular-cli/blob/master/docs/documentation/stories/universal-rendering.md

https://medium.com/@cdeniz/angular-universal-on-firebase-dynamic-hosting-4fdd034af3db

https://medium.com/@cyrilletuzi/angular-server-side-rendering-in-node-with-express-universal-engine-dce21933ddce



Angular Universalやってみたい :blush:



Expressが必要... :pensive:



Firebase好きだし、Firebase HostingでAngular Universalやりたい :blush:



Firebase HostingにはExpressアプリをホスティングできない... :pensive:



Cloud Functions for Firebaseでワンチャンあるかも...! :smiling_imp:


Angular CLI 1.3.0-rc.3以上とFirebase CLIをインストールする

$ npm install -g @angular/cli@next firebase-tools


ng new で新しいプロジェクトを作成する。

$ ng new angular-universal-with-firebase-hosting


Firebaseプロジェクトにする。

$ cd angular-universal-with-firebase-hosting

$ firebase init

※ HostingとFunctionsは必須。その他は下記のように設定する。

Do you want to install dependencies with npm now? (Y/n): N

What do you want to use as your public directory? (public): public
Configure as a single-page app (rewrite all urls to /index.html): Y


@angular/platform-serverをインストールする。

$ npm install --save-dev @angular/platform-server


src/app/app.module.ts を編集する。


src/app/app.module.ts

import { BrowserModule } from '@angular/platform-browser';

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

import { AppComponent } from './app.component';

@NgModule({
declarations: [
AppComponent
],
imports: [
// BrowserModule
BrowserModule.withServerTransition({ appId: 'my-app' }) // ←ここ
],
providers: [],
bootstrap: [AppComponent]
})
export class AppModule { }



src/app/app.server.module.ts を追加する。


src/app/app.server.module.ts

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

import { ServerModule } from '@angular/platform-server';

import { AppModule } from './app.module';
import { AppComponent } from './app.component';

@NgModule({
imports: [
AppModule, // ←ここにAppModuleを入れる。
ServerModule,
],
bootstrap: [AppComponent],
})
export class AppServerModule { }



src/main.server.ts を追加する。


src/main.server.ts

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

export { AppServerModule } from './app/app.server.module';

enableProdMode();



src/tsconfig.server.json を追加する。


src/tsconfig.server.json

{

"extends": "./tsconfig.app.json",
"compilerOptions": {
"module": "commonjs"
},
"exclude": [
"test.ts",
"**/*.spec.ts"
],
"angularCompilerOptions": {
"entryModule": "app/app.server.module#AppServerModule"
}
}


angular-cli.json を編集する。


.angular-cli.json

{

"$schema": "./node_modules/@angular/cli/lib/config/schema.json",
"project": {
"name": "angular-universal-with-firebase-hosting"
},
"apps": [
{
.......
},
{
"platform": "server",
"root": "src",
"outDir": "functions/dist-server",
"assets": [
"assets",
"favicon.ico"
],
"index": "index.html",
"main": "main.server.ts",
"test": "test.ts",
"tsconfig": "tsconfig.server.json",
"testTsconfig": "tsconfig.spec.json",
"prefix": "app",
"styles": [
"styles.css"
],
"scripts": [],
"environmentSource": "environments/environment.ts",
"environments": {
"dev": "environments/environment.ts",
"prod": "environments/environment.prod.ts"
}
}
],
.......
}


src/index.html を functions/dist-serverディレクトリ にコピーする。

$ cp src/index.html functions/dist-server


functions/package.json の dependencies を編集する。この後 functionsディレクトリに移動して npm install する。


functions/package.json



{
"name": "functions",
"description": "Cloud Functions for Firebase",
"dependencies": {
"firebase-admin": "~4.2.1",
"firebase-functions": "^0.5.7",
"@angular/animations": "^4.0.0",
"@angular/common": "^4.0.0",
"@angular/compiler": "^4.0.0",
"@angular/core": "^4.0.0",
"@angular/forms": "^4.0.0",
"@angular/http": "^4.0.0",
"@angular/platform-browser": "^4.0.0",
"@angular/platform-browser-dynamic": "^4.0.0",
"@angular/platform-server": "^4.0.0",
"@angular/router": "^4.0.0",
"core-js": "^2.4.1",
"express": "^4.15.3",
"rxjs": "^5.4.2",
"zone.js": "^0.8.16"
}
"private": true
}


functions/index.js を編集する。


functions/index.js

require('zone.js/dist/zone-node');

const functions = require('firebase-functions');
const express = require('express');
const path = require('path')

const renderModuleFactory = require('@angular/platform-server').renderModuleFactory;

const AppServerModuleNgFactory = require('./dist-server/main.bundle').AppServerModuleNgFactory;

const index = require('fs').readFileSync(path.resolve(__dirname, './dist-server/index.html'), 'utf8');

const app = express();

app.get('/', (req, res) => {
renderModuleFactory(AppServerModuleNgFactory, { document: index, url: '/' })
.then(html => {
res.send(html);
})
.catch(err => {
console.log(err)
});
});

exports.ssr = functions.https.onRequest(app);



firebase.json を編集する。


firebase.json

{

"hosting": {
"public": "public",
"rewrites": [
{
"source": "**",
"function": "ssr"
}
]
}
}


publicフォルダを追加し、空の hoge.css というファイルを配置する。

(ファイルが何もないとそのフォルダをFirebase Hostingにデプロイできないため)


package.json の scripts を編集する。


package.json

{

"scripts": {
.....,
"build": "ng build --prod --app 1 --output-hashing=none",
"copy": "cp src/index.html functions/dist-server",
"deploy": "npm run build && npm run copy && firebase deploy",
.....
}
}


デプロイする。

$ npm run deploy


動作確認

https://easy-ng-universal.firebaseapp.com/



なにかいろいろもんだいはありそうだけど、くわしいことはよくわからない。



Thanks :raised_hands_tone1: