Angular 通用集成

Angular Universal Integration

Angular 通用集成

The Angular CLI supports generation of a Universal build for your application. This is a CommonJS-formatted bundle which can be require()'d into a Node application (for example, an Express server) and used with @angular/platform-server's APIs to prerender your application.
Angular CLI 支持为应用程序生成通用集成。这是一个 CommonJS 格式的包, 可以通过 require() 进入节点应用程序 , 并与@angular/platform-server 的 api 一起使用以预渲染应用程序。


Example CLI Integration:

CLI 集成例子:

Angular Universal-Starter - Clone the universal-starter for a working example.
Angular 通用启动器 -克隆通用启动器的一个工作示例。 -


Integrating Angular Universal into existing CLI Applications

将Angular Universal集成到现有的CLI应用程序中

This story will show you how to set up Universal bundling for an existing @angular/cli project in 5 steps.
这个故事将向您展示如何通过5个步骤为现有的 @angular/cli 项目设置通用构建。


Install Dependencies

安装依赖

Install @angular/platform-server into your project. Make sure you use the same version as the other @angular packages in your project.
安装 @angular/platform-server 到你的项目. 确保您的项目中使用与其他@angular软件包相同的版本。

You'll also need ts-loader (for your webpack build we'll show later) and @nguniversal/module-map-ngfactory-loader, as it's used to handle lazy-loading in the context of a server-render. (by loading the chunks right away)
您还需要ts-loader(用于您的webpack构建,稍后会显示)和 @nguniversal/module-map-ngfactory-loader,因为它用于处理服务器呈现环境中的延迟加载。 (通过立即加载块)

$ npm install --save @angular/platform-server @nguniversal/module-map-ngfactory-loader ts-loader

Step 1: Prepare your App for Universal rendering

步骤一:准备您的应用程序进行通用渲染

The first thing you need to do is make your AppModule compatible with Universal by adding .withServerTransition() and an application ID to your BrowserModule import:
您需要做的第一件事是通过向您的 BrowserModule 导入添加 .withServerTransition()和一个应用程序ID,使您的 AppModule 与通用兼容:

src/app/app.module.ts:

@NgModule({
  bootstrap: [AppComponent],
  imports: [
    // Add .withServerTransition() to support Universal rendering.
    // The application ID can be any identifier which is unique on
    // the page.
    BrowserModule.withServerTransition({appId: 'my-app'}),
    ...
  ],

})
export class AppModule {}

Next, create a module specifically for your application when running on the server. It's recommended to call this module AppServerModule.
接下来,在服务器上运行时,专门为您的应用程序创建一个模块。建议调用这个模块AppServerModule

This example places it alongside app.module.ts in a file named app.server.module.ts:
这个例子将它放在 app.module.ts 名为的文件 app.server.module.ts旁边:

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

You can see here we're simply Importing everything from AppModule, followed by ServerModule.
您可以在这里看到我们只是从AppModule导入所有内容,然后导入ServerModule。

One important thing to Note: We need ModuleMapLoaderModule to help make Lazy-loaded routes possible during Server-side renders with the Angular CLI.
注意一件重要的事情:我们需要使用ModuleMapLoaderModule,以便在使用Angular CLI进行服务器端呈现期间使延迟加载的路由成为可能。

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: [
    // The AppServerModule should import your AppModule followed
    // by the ServerModule from @angular/platform-server.
    AppModule,
    ServerModule,
    ModuleMapLoaderModule // <-- *Important* to have lazy-loaded routes work
  ],
  // Since the bootstrapped component is not inherited from your
  // imported AppModule, it needs to be repeated here.
  bootstrap: [AppComponent],
})
export class AppServerModule {}

Step 2: Create a server "main" file and tsconfig to build it

步骤二: 创建服务器 "main" 文件并 tsconfig 以生成它

Create a main file for your Universal bundle. This file only needs to export your AppServerModule. It can go in src. This example calls this file main.server.ts:
创建一个主文件为通用包. 这个文件只需要导出你的 AppServerModule. 它可以进去 src. 这个例子调用这个文件 main.server.ts:

src/main.server.ts:

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

Copy tsconfig.app.json to tsconfig.server.json and change it to build with a "module" target of "commonjs".
复制tsconfig.app.jsontsconfig.server.json 并改变它与建立一个 "module" 目标为 "commonjs".

Add a section for "angularCompilerOptions" and set "entryModule" to your AppServerModule, specified as a path to the import with a hash (#) containing the symbol name. In this example, this would be app/app.server.module#AppServerModule.
“angularCompilerOptions” 添加一个属性并将 “entryModule” 设置为您的AppServerModule,使用包含符号名称的散列(#)指定为导入路径。在这个例子中,这将是app/app.server.module#AppServerModule

src/tsconfig.server.json:

{
  "extends": "../tsconfig.json",
  "compilerOptions": {
    "outDir": "../out-tsc/app",
    "baseUrl": "./",
    // Set the module format to "commonjs":
    "module": "commonjs",
    "types": []
  },
  "exclude": [
    "test.ts",
    "**/*.spec.ts"
  ],
  // Add "angularCompilerOptions" with the AppServerModule you wrote
  // set as the "entryModule".
  "angularCompilerOptions": {
    "entryModule": "app/app.server.module#AppServerModule"
  }
}

Step 3: Create a new target in angular.json

步骤三: 在 angular.json 中创建一个目标

In angular.json locate the architect property inside your project, and add a new server
target:
angular.json 中找到项目中的 architect 属性,并添加一个新的 server 属性:

"architect": {
  "build": { ... }
  "server": {
    "builder": "@angular-devkit/build-angular:server",
    "options": {
      "outputPath": "dist/your-project-name-server",
      "main": "src/main.server.ts",
      "tsConfig": "src/tsconfig.server.json"
    }
  }
}

Building the bundle

构建包

With these steps complete, you should be able to build a server bundle for your application, using the --app flag to tell the CLI to build the server bundle, referencing its index of 1 in the "apps" array in .angular-cli.json:
完成这些步骤后,您应该能够为应用程序构建一个服务器包,使用 --app 标志告诉CLI构建服务器包,在.` angular-cli.json 中apps”数组中引用索引1以.json:

# This builds your project using the server target, and places the output
# in dist/your-project-name-server/
$ ng run your-project-name:server

Date: 2017-07-24T22:42:09.739Z
Hash: 9cac7d8e9434007fd8da
Time: 4933ms
chunk {0} main.js (main) 9.49 kB [entry] [rendered]
chunk {1} styles.css (styles) 0 bytes [entry] [rendered]

Step 4: Setting up an Express Server to run our Universal bundles

设置Express服务器来运行我们的Universal套件

Now that we have everything set up to -make- the bundles, how we get everything running?
现在我们已经把所有东西都设置成了 - 捆绑包,我们如何让所有的东西都运行?

PlatformServer offers a method called renderModuleFactory() that we can use to pass in our AoT'd AppServerModule, to serialize our application, and then we'll be returning that result to the Browser.
PlatformServer提供了一个名为 renderModuleFactory() 的方法,我们可以使用它传递我们的预编译支持AppServerModule,序列化我们的应用程序,然后我们将返回结果给浏览器。

app.engine('html', (_, options, callback) => {
  renderModuleFactory(AppServerModuleNgFactory, {
    // Our index.html
    document: template,
    url: options.req.url,
    // DI so that we can get lazy-loading to work differently (since we need it to just instantly render it)
    extraProviders: [
      provideModuleMap(LAZY_MODULE_MAP)
    ]
  }).then(html => {
    callback(null, html);
  });
});

You could do this, if you want complete flexibility, or use an express-engine with a few other built in features from @nguniversal/express-engine found here.
你可以做到这一点,如果你想要完全的灵活性,或者使用Express引擎和其他一些内置的功能,你可以在这里找到 @nguniversal/express-engine

// It's used as easily as
import { ngExpressEngine } from '@nguniversal/express-engine';

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

Below we can see a TypeScript implementation of a -very- simple Express server to fire everything up.
下面我们可以看到一个简单的Express服务器的TypeScript实现来启动一切

Note: This is a very bare bones Express application, and is just for demonstrations sake. In a real production environment, you'd want to make sure you have other authentication and security things setup here as well. This is just meant just to show the specific things needed that are relevant to Universal itself. The rest is up to you!
注意:这是一个非常简单的Express应用程序,只是为了演示。在真实的生产环境中,您需要确保您在此处设置了其他身份验证和安全性功能。这只是为了显示与通用本身相关的具体事情。其余的由你决定!

At the ROOT level of your project (where package.json / etc are), created a file named: server.ts
在项目的根级别(package.json / etc所在的位置)创建一个名为:server.ts 的文件

./server.ts (root project level)

// These are important and needed before anything else
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';

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

// Express server
const app = express();

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

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

// * NOTE :: leave this as require() since this file is built Dynamically from webpack
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, {
    // Our index.html
    document: template,
    url: options.req.url,
    // DI so that we can get lazy-loading to work differently (since we need it to just instantly render it)
    extraProviders: [
      provideModuleMap(LAZY_MODULE_MAP)
    ]
  }).then(html => {
    callback(null, html);
  });
});

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

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

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

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

Step 5: Setup a webpack config to handle this Node server.ts file and serve your application!

第5步:设置一个webpack配置来处理这个Node server.ts文件并为您的应用程序提供服务

Now that we have our Node Express server setup, we need to pack it and serve it!
现在我们已经安装了Node Express服务器,我们需要打包并提供服务!

Create a file named webpack.server.config.js at the ROOT of your application.
在应用程序的ROOT处创建一个名为 webpack.server.config.js 的文件。

This file basically takes that server.ts file, and takes it and compiles it and every dependency it has into dist/server.js.
这个文件基本上接受了server.ts文件,并将其编译到 dist/server.js 中。

./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',
  // this makes sure we include node_modules and other 3rd party libraries
  externals: [/(node_modules|main\..*\.js)/],
  output: {
    path: path.join(__dirname, 'dist'),
    filename: '[name].js'
  },
  module: {
    rules: [
      { test: /\.ts$/, loader: 'ts-loader' }
    ]
  },
  plugins: [
    // Temporary Fix for issue: https://github.com/angular/angular/issues/11580
    // for "WARNING Critical dependency: the request of a dependency is an expression"
    new webpack.ContextReplacementPlugin(
      /(.+)?angular(\\|\/)core(.+)?/,
      path.join(__dirname, 'src'), // location of your src
      {} // a map of your routes
    ),
    new webpack.ContextReplacementPlugin(
      /(.+)?express(\\|\/)(.+)?/,
      path.join(__dirname, 'src'),
      {}
    )
  ]
}

Almost there!
差不多了!

Now let's see what our resulting structure should look like, if we open up our /dist/ folder we should see:
现在让我们看看我们的结构应该是什么样子,如果我们打开我们应该看到的 /dist/ 文件夹:

/dist/
   /browser/
   /server/

To fire up the application, in your terminal enter
要启动应用程序,请在终端中输入

node dist/server.js

:sparkles:

Now lets create a few handy scripts to help us do all of this in the future.
现在让我们创建一些便利的脚本来帮助我们在未来完成所有这些。

"scripts": {

  // These will be your common scripts
  "build:ssr": "npm run build:client-and-server-bundles && npm run webpack:server",
  "serve:ssr": "node dist/server.js",

  // Helpers for the above scripts
  "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"
}

In the future when you want to see a Production build of your app with Universal (locally), you can simply run:
在将来,当您想通过Universal(本地)查看您的应用程序的Production版本时,只需运行:

npm run build:ssr && npm run serve:ssr

Enjoy!
欣赏!

Once again to see a working version of everything, check out the universal-starter.
再次看到一切的工作版本,请查看通用启动器

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 205,033评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 87,725评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,473评论 0 338
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,846评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,848评论 5 368
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,691评论 1 282
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,053评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,700评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 42,856评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,676评论 2 323
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,787评论 1 333
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,430评论 4 321
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,034评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,990评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,218评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,174评论 2 352
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,526评论 2 343

推荐阅读更多精彩内容