如何在Firebase中部署Angular Universal Rendering?

时间:2018-02-23 16:17:49

标签: angular firebase angular-universal

我正在开发Angular 5,实施Angular Universal Rendering,同时在生产Angular Universal上工作,我可以轻松部署和运行本地机器。只需按照此指令链接,它就可以在本地计算机https://github.com/angular/angular-cli/wiki/stories-universal-rendering上运行。但是当我开始在Firebase中部署Angular Universal时,它并没有意外地工作。我跟着这个链接,但信息较少。我需要一些帮助如何在Firebase中部署Angular Universal Rendering。感谢你的帮助伙计!谢谢!

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"
  }
}

2 个答案:

答案 0 :(得分:1)

搜索了许多文章和视频后,我发现了使用Angular 9的简单方法,只需使用以下命令:

在使用命令添加SSR之后:

ng add @nguniversal/express-engine

您需要添加Firebase软件包:

ng add @angular/fire

在检测到SSR时,系统将询问您是否要添加功能(选择“ y”)。然后选择在Firebase中预添加的项目。

之后,只需使用以下命令进行部署:

ng deploy 

Credits to this article

答案 1 :(得分:0)

我将详细介绍在Firebase中部署Angular 5 Universal的主要10个步骤。您可以在此分步文章中找到更多详细信息:https://blog.angularindepth.com/angular-5-universal-firebase-4c85a7d00862

我们走吧

假设您知道如何在项目中初始化firebase函数,那么您的构建结构可能应该是这样的:

  • dist: Angular(浏览器)应用和静态文件
  • dist / server: Universal App
  • 功能:Express服务器和依赖项

但是,您希望Express服务器从Firebase功能运行,并且需要阅读通用应用程序。此结构不允许它访问该文件夹。

我建议您改用其他结构:

  • dist: Express Server和依赖项
    • 浏览器:Angular(浏览器)应用和静态文件
    • 服务器:通用应用

为此:

1。制作一个新的空dist文件夹。

2。将functions/package.json移至dist/package.json

3。删除functions文件夹

4。更新firebase.json

Firebase现在查看dist文件夹而不是functions文件夹,在任何路线上调用ssr函数,静态资源从dist/browser投放:

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

ssr函数将是您正在导出的快速服务器的函数名称,因此firebase可以使用它。

5。将src/index.html重命名为src/index-1.html

因此,当您调用基本路由时,Firebase无法从静态文件夹中提供<app-root></app-root>空index.html,而不是调用SSR函数。

6。像这样更新您的服务器index.ts

// 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。更新.angular-cli.json

这样,它将适合新的结构:

{
//...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。更新服务器构建位置

确保将服务器文件的输出放在dist文件夹中。

9。将通用依赖项添加到Firebase package.json

更新dist/package.json所以它看起来像这样:

{
//... 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。将其部署到Firebase!

最后,使用firebase deploy

部署到firebase

疑难解答

如果它不起作用或者您缺少一些细节,请查看我在回复开始时链接的文章,因为所有内容都从一开始就深入解释(创建Angular 5 Universal项目)到最后(使用firebase函数部署它。)

玩得开心!