Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to deploy Angular Universal Rendering in Firebase?

I am working on Angular 5 implementing Angular Universal Rendering while in production Angular Universal working on the local machine that I can easily deploy and run. Just following this instruction link it will work just on the local machine https://github.com/angular/angular-cli/wiki/stories-universal-rendering. But when I started deploying the Angular Universal in Firebase it's not working unexpectedly. I followed this linked but there is less information. Please I need some help how to deploy Angular Universal Rendering in Firebase. Appreciate your help Guys! Thank You!

How to deploy angular 4 universal app to firebase

https://www.youtube.com/watch?v=gxCu5TEmxXE

file structure like this:

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.withServerTransition({appId: 'something-unique'})
  ],
  providers: [],
  bootstrap: [AppComponent]
})
export class AppModule { }

src/app/app.server.module.ts:

import { NgModule } from '@angular/core';
import { ServerModule } from '@angular/platform-server';
import { ModuleMapLoaderModule } from '@nguniversal/module-map-ngfactory-loader';

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

@NgModule({
    imports: [
        AppModule,
        ServerModule,
        ModuleMapLoaderModule
    ],
    bootstrap: [AppComponent],
})

export class AppServerModule { }

src/main.server.ts:

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

src/tsconfig.server.json:

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

.angular-cli.json:

{
  "$schema": "./node_modules/@angular/cli/lib/config/schema.json",
  "project": {
    "name": "ng-true-facts"
  },
  "apps": [
    {
      "root": "src",
      "outDir": "dist/browser",
      "assets": [
        "assets",
        "favicon.ico"
      ],
      "index": "index.html",
      "main": "main.ts",
      "polyfills": "polyfills.ts",
      "test": "test.ts",
      "tsconfig": "tsconfig.app.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"
      }
    },
    {
      "platform": "server",
      "root": "src",
      "outDir": "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"
      }
    }
  ],
  "e2e": {
    "protractor": {
      "config": "./protractor.conf.js"
    }
  },
  "lint": [
    {
      "project": "src/tsconfig.app.json",
      "exclude": "**/node_modules/**"
    },
    {
      "project": "src/tsconfig.spec.json",
      "exclude": "**/node_modules/**"
    },
    {
      "project": "e2e/tsconfig.e2e.json",
      "exclude": "**/node_modules/**"
    }
  ],
  "test": {
    "karma": {
      "config": "./karma.conf.js"
    }
  },
  "defaults": {
    "styleExt": "css",
    "component": {}
  }
}

./server.ts

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';

enableProdMode();

const app = express();

const PORT = process.env.PORT || 4000;
const DIST_FOLDER = join(process.cwd(), 'dist');

const template = readFileSync(join(DIST_FOLDER, 'browser', 'index.html')).toString();

const { AppServerModuleNgFactory, LAZY_MODULE_MAP } = require('./dist/server/main.bundle');

const { provideModuleMap } = require('@nguniversal/module-map-ngfactory-loader');

app.engine('html', (_, options, callback) => {
    renderModuleFactory(AppServerModuleNgFactory, {
        document: template,
        url: options.req.url,
        extraProviders: [
            provideModuleMap(LAZY_MODULE_MAP)
        ]
    }).then(html => {
        callback(null, html);
    });
});

app.set('view engine', 'html');
app.set('views', join(DIST_FOLDER, 'browser'));

app.get('*.*', express.static(join(DIST_FOLDER, 'browser')));

app.get('*', (req, res) => {
    res.render(join(DIST_FOLDER, 'browser', 'index.html'), { req });
});

app.listen(PORT, () => {
    console.log(`Node server listening on http://localhost:${PORT}`);
});

./webpack.server.config.js (root project level)

const path = require('path');
const webpack = require('webpack');

module.exports = {
    entry: { server: './server.ts' },
    resolve: { extensions: ['.js', '.ts'] },
    target: 'node',
    externals: [/(node_modules|main\..*\.js)/],
    output: {
        path: path.join(__dirname, 'dist'),
        filename: '[name].js'
    },
    module: {
        rules: [
            { test: /\.ts$/, loader: 'ts-loader' }
        ]
    },
    plugins: [
        new webpack.ContextReplacementPlugin(
            /(.+)?angular(\\|\/)core(.+)?/,
            path.join(__dirname, 'src'),
            {}
        ),
        new webpack.ContextReplacementPlugin(
            /(.+)?express(\\|\/)(.+)?/,
            path.join(__dirname, 'src'),
            {}
        )
    ]
}

/dist/
   /browser/
   /server/

package.json

{
  "name": "ng-true-facts",
  "version": "0.0.0",
  "license": "MIT",
  "scripts": {
    "ng": "ng",
    "start": "ng serve",
    "build": "ng build --prod",
    "build:ssr": "npm run build:client-and-server-bundles && npm run webpack:server",
    "serve:ssr": "node dist/server.js",
    "build:client-and-server-bundles": "ng build --prod && ng build --prod --app 1 --output-hashing=false",
    "webpack:server": "webpack --config webpack.server.config.js --progress --colors",
    "test": "ng test",
    "lint": "ng lint",
    "e2e": "ng e2e"
  },
  "private": true,
  "dependencies": {
    "@angular/animations": "^5.2.0",
    "@angular/common": "^5.2.0",
    "@angular/compiler": "^5.2.0",
    "@angular/core": "^5.2.0",
    "@angular/forms": "^5.2.0",
    "@angular/http": "^5.2.0",
    "@angular/platform-browser": "^5.2.0",
    "@angular/platform-browser-dynamic": "^5.2.0",
    "@angular/router": "^5.2.0",
    "@nguniversal/module-map-ngfactory-loader": "^5.0.0-beta.5",
    "core-js": "^2.4.1",
    "firebase-functions": "^0.8.1",
    "rxjs": "^5.5.6",
    "ts-loader": "^3.5.0",
    "zone.js": "^0.8.19"
  },
  "devDependencies": {
    "@angular/cli": "~1.7.0",
    "@angular/compiler-cli": "^5.2.0",
    "@angular/language-service": "^5.2.0",
    "@angular/platform-server": "^5.2.6",
    "@types/jasmine": "~2.8.3",
    "@types/jasminewd2": "~2.0.2",
    "@types/node": "~6.0.60",
    "codelyzer": "^4.0.1",
    "jasmine-core": "~2.8.0",
    "jasmine-spec-reporter": "~4.2.1",
    "karma": "~2.0.0",
    "karma-chrome-launcher": "~2.2.0",
    "karma-coverage-istanbul-reporter": "^1.2.1",
    "karma-jasmine": "~1.1.0",
    "karma-jasmine-html-reporter": "^0.2.2",
    "protractor": "~5.1.2",
    "ts-node": "~4.1.0",
    "tslint": "~5.9.1",
    "typescript": "~2.5.3"
  }
}
like image 793
Fred Avatar asked Feb 23 '18 16:02

Fred


2 Answers

After searching many articles and videos, I found a simple way with Angular 9 using a few commands, as following:

After adding SSR with command:

ng add @nguniversal/express-engine

You need to add the Firebase package:

ng add @angular/fire

You will be asked, as the SSR is detected, whether you want to add functions (choose yes 'y'). Then choose project pre-added in Firebase.

After that, just deploy using:

ng deploy 

Credits to this article

like image 109
Mohamed Magdi Avatar answered Oct 12 '22 06:10

Mohamed Magdi


I'm gonna detail the main 10 steps to achieve deploying Angular 5 Universal in Firebase. You can find more details in this step-by-step article: https://blog.angularindepth.com/angular-5-universal-firebase-4c85a7d00862

Let's go

Assuming you know how to init firebase functions in your project, your build structure should probably look like that:

  • dist: Angular (browser) App and static files
  • dist/server: Universal App
  • functions: Express server and dependencies

However, you want the Express server to run from Firebase functions, and it needs to read the Universal App. This structure does not allow it to access that folder.

I suggest you to use this other structure instead:

  • dist: Express Server and dependencies
    • browser: Angular (browser) App and static files
    • server: Universal App

In order to do so:

1. make a new empty dist folder.

2. Move functions/package.json to dist/package.json

3. Remove functions folder

4. Update firebase.json

Firebase now looks at the dist folder instead of the functions one, a ssr function is called on any route, and the static assets are served from dist/browser:

{
  "hosting": {
    "public": "dist/browser",
    "ignore": [
      "firebase.json",
      "**/.*",
      "**/node_modules/**"
    ],
    "rewrites": [
      {
        "source": "**",
        "function": "ssr"
      }
    ]
  },
  "functions": {
    "source": "dist"
  }
}

The ssr function will be the name of the function with your express server you are exporting so firebase can use it.

5. Rename src/index.html to src/index-1.html

So when you call the base route, Firebase cannot serve the <app-root></app-root> empty index.html from static folders instead of calling the SSR function.

6. Update your server index.ts like this:

// These are important and needed before anything else
import  'zone.js/dist/zone-node';
import  'reflect-metadata';

import { enableProdMode } from  '@angular/core';
import  *  as  express  from  'express';
import { join } from  'path';
  
// NOTE: leave this as require() since this file is built Dynamically from webpack
const { AppServerModuleNgFactory, LAZY_MODULE_MAP } =  require('./server/main.bundle');

// NgUniversalTools: Express Engine and moduleMap for lazy loading
import { ngExpressEngine } from  '@nguniversal/express-engine';
import { provideModuleMap } from  '@nguniversal/module-map-ngfactory-loader';

//firebase cloud functions
import * as firebaseFunctions from 'firebase-functions';


// Faster server renders w/ Prod mode (dev mode never needed)
enableProdMode();

//check if Firebase functions is enabled or not
const DISABLE_FIREBASE = process.env.DISABLE_FIREBASE || false;

// Express server
const  app  =  express();
const  PORT  =  process.env.PORT  ||  4000;
const DIST_FOLDER = join(process.cwd(), DISABLE_FIREBASE ? 'dist' : './');

app.engine('html', ngExpressEngine({
bootstrap:  AppServerModuleNgFactory,
providers: [
    provideModuleMap(LAZY_MODULE_MAP)
]
}));

app.set('view engine', 'html');
app.set('views', join(DIST_FOLDER, 'browser'));

/* TODO: implement data requests securely
// app.get('/api/*', (req, res) => {
// res.status(404).send('data requests are not supported');
// });
*/

// All regular routes use the Universal engine
app.get('*', (req, res) => {
    res.render(join(DIST_FOLDER, 'browser', 'index-1.html'), {req});
});

if(DISABLE_FIREBASE){
    // Server static files from express in case there's no firebase hosting
    app.get('*.*', express.static(join(DIST_FOLDER, 'browser')));

    // Start up the Node server if not using firebase cloud functions
    app.listen(PORT, () => {
        console.log(`Node server listening on http://localhost:${PORT}`);
    });
}

//server side rendering using frebase cloud functions
export let ssr = DISABLE_FIREBASE ? null : firebaseFunctions.https.onRequest(app);

7. Update .angular-cli.json

This way, it will fit in the new structure:

{
//...some stuff...

"apps": [
    {
      "root": "src",
      "outDir": "dist/browser",
      "assets": [
        "assets",
        "favicon.ico"
      ],
      "index": "index-1.html",
      "main": "main.ts",
      "polyfills": "polyfills.ts",
      "test": "test.ts",
      "tsconfig": "tsconfig.app.json",
      "testTsconfig": "tsconfig.spec.json",
      "prefix": "app",
      "styles": [
        "styles.sass"
      ],
      "scripts": [],
      "environmentSource": "environments/environment.ts",
      "environments": {
        "dev": "environments/environment.ts",
        "prod": "environments/environment.prod.ts"
      }
    },
    {
      "root": "src",
      "outDir": "dist/server",
      "assets": [
        "assets",
        "favicon.ico"
      ],
      "index": "index-1.html",
      "main": "main.server.ts",
      "test": "test.ts",
      "tsconfig": "tsconfig.server.json",
      "testTsconfig": "tsconfig.spec.json",
      "prefix": "app",
      "styles": [
        "styles.sass"
      ],
      "scripts": [],
      "environmentSource": "environments/environment.ts",
      "environments": {
        "dev": "environments/environment.ts",
        "prod": "environments/environment.prod.ts"
      },
      "platform": "server"
    }
  ],

 //...more stuff...
}

8. Update server build location

Make sure that the output of the server file is placed in the dist folder.

9. Add Universal dependencies to Firebase package.json

Update dist/package.json so it looks like this:

{
//... some stuff...
"dependencies": {
      "@angular/animations": "^5.2.6",
      "@angular/common": "^5.2.6",
      "@angular/compiler": "^5.2.6",
      "@angular/core": "^5.2.6",
      "@angular/forms": "^5.2.6",
      "@angular/http": "^5.2.6",
      "@angular/platform-browser": "^5.2.6",
      "@angular/platform-browser-dynamic": "^5.2.6",
      "@angular/platform-server": "^5.2.6",
      "@angular/router": "^5.2.6",
      "@nguniversal/express-engine": "^5.0.0-beta.6",
      "@nguniversal/module-map-ngfactory-loader": "^5.0.0-beta.6",
      "express": "^4.16.2",
      "firebase-admin": "~5.9.0",
      "firebase-functions": "^0.8.1",
      "rxjs": "^5.5.6",
      "zone.js": "^0.8.20"
    },
//... more stuff...
}

10. Deploy it to Firebase!

Finally, deploy to firebase with firebase deploy

Troubleshooting

If it is not working or you are missing some detail, please, review the article I linked at the beginning of the response, as all the stuff is deeply explained from the beginning (creating Angular 5 Universal project) to the end (deploy it using firebase functions).

Have fun!

like image 37
Enrique Oriol Avatar answered Oct 12 '22 07:10

Enrique Oriol