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 支持為應(yīng)用程序生成通用集成。這是一個(gè) CommonJS 格式的包, 可以通過 require() 進(jìn)入節(jié)點(diǎn)應(yīng)用程序 , 并與@angular/platform-server 的 api 一起使用以預(yù)渲染應(yīng)用程序。
Example CLI Integration:
CLI 集成例子:
Angular Universal-Starter - Clone the universal-starter for a working example.
Angular 通用啟動(dòng)器 -克隆通用啟動(dòng)器的一個(gè)工作示例。 -
Integrating Angular Universal into existing CLI Applications
將Angular Universal集成到現(xiàn)有的CLI應(yīng)用程序中
This story will show you how to set up Universal bundling for an existing @angular/cli project in 5 steps.
這個(gè)故事將向您展示如何通過5個(gè)步驟為現(xiàn)有的 @angular/cli 項(xiàng)目設(shè)置通用構(gòu)建。
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 到你的項(xiàng)目. 確保您的項(xiàng)目中使用與其他@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構(gòu)建,稍后會(huì)顯示)和@nguniversal/module-map-ngfactory-loader,因?yàn)樗糜谔幚矸?wù)器呈現(xiàn)環(huán)境中的延遲加載。 (通過立即加載塊)
$ npm install --save @angular/platform-server @nguniversal/module-map-ngfactory-loader ts-loader
Step 1: Prepare your App for Universal rendering
步驟一:準(zhǔn)備您的應(yīng)用程序進(jìn)行通用渲染
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 導(dǎo)入添加 .withServerTransition()和一個(gè)應(yīng)用程序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.
接下來,在服務(wù)器上運(yùn)行時(shí),專門為您的應(yīng)用程序創(chuàng)建一個(gè)模塊。建議調(diào)用這個(gè)模塊AppServerModule。
This example places it alongside app.module.ts in a file named app.server.module.ts:
這個(gè)例子將它放在 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導(dǎo)入所有內(nèi)容,然后導(dǎo)入ServerModule。
One important thing to Note: We need
ModuleMapLoaderModuleto help make Lazy-loaded routes possible during Server-side renders with the Angular CLI.
注意一件重要的事情:我們需要使用ModuleMapLoaderModule,以便在使用Angular CLI進(jìn)行服務(wù)器端呈現(xiàn)期間使延遲加載的路由成為可能。
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
步驟二: 創(chuàng)建服務(wù)器 "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:
創(chuàng)建一個(gè)主文件為通用包. 這個(gè)文件只需要導(dǎo)出你的 AppServerModule. 它可以進(jìn)去 src. 這個(gè)例子調(diào)用這個(gè)文件 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".
復(fù)制tsconfig.app.json 到 tsconfig.server.json 并改變它與建立一個(gè) "module" 目標(biāo)為 "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” 添加一個(gè)屬性并將 “entryModule” 設(shè)置為您的AppServerModule,使用包含符號(hào)名稱的散列(#)指定為導(dǎo)入路徑。在這個(gè)例子中,這將是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 中創(chuàng)建一個(gè)目標(biāo)
In angular.json locate the architect property inside your project, and add a new server
target:
在 angular.json 中找到項(xiàng)目中的 architect 屬性,并添加一個(gè)新的 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
構(gòu)建包
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:
完成這些步驟后,您應(yīng)該能夠?yàn)閼?yīng)用程序構(gòu)建一個(gè)服務(wù)器包,使用 --app 標(biāo)志告訴CLI構(gòu)建服務(wù)器包,在.` angular-cli.json 中apps”數(shù)組中引用索引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
設(shè)置Express服務(wù)器來運(yùn)行我們的Universal套件
Now that we have everything set up to -make- the bundles, how we get everything running?
現(xiàn)在我們已經(jīng)把所有東西都設(shè)置成了 - 捆綁包,我們?nèi)绾巫屗械臇|西都運(yùn)行?
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提供了一個(gè)名為 renderModuleFactory() 的方法,我們可以使用它傳遞我們的預(yù)編譯支持AppServerModule,序列化我們的應(yīng)用程序,然后我們將返回結(jié)果給瀏覽器。
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.
你可以做到這一點(diǎn),如果你想要完全的靈活性,或者使用Express引擎和其他一些內(nèi)置的功能,你可以在這里找到 @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.
下面我們可以看到一個(gè)簡(jiǎn)單的Express服務(wù)器的TypeScript實(shí)現(xiàn)來啟動(dòng)一切
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!
注意:這是一個(gè)非常簡(jiǎn)單的Express應(yīng)用程序,只是為了演示。在真實(shí)的生產(chǎn)環(huán)境中,您需要確保您在此處設(shè)置了其他身份驗(yàn)證和安全性功能。這只是為了顯示與通用本身相關(guān)的具體事情。其余的由你決定!
At the ROOT level of your project (where package.json / etc are), created a file named: server.ts
在項(xiàng)目的根級(jí)別(package.json / etc所在的位置)創(chuàng)建一個(gè)名為: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步:設(shè)置一個(gè)webpack配置來處理這個(gè)Node server.ts文件并為您的應(yīng)用程序提供服務(wù)
Now that we have our Node Express server setup, we need to pack it and serve it!
現(xiàn)在我們已經(jīng)安裝了Node Express服務(wù)器,我們需要打包并提供服務(wù)!
Create a file named webpack.server.config.js at the ROOT of your application.
在應(yīng)用程序的ROOT處創(chuàng)建一個(gè)名為 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.
這個(gè)文件基本上接受了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:
現(xiàn)在讓我們看看我們的結(jié)構(gòu)應(yīng)該是什么樣子,如果我們打開我們應(yīng)該看到的 /dist/ 文件夾:
/dist/
/browser/
/server/
To fire up the application, in your terminal enter
要啟動(dòng)應(yīng)用程序,請(qǐng)?jiān)诮K端中輸入
node dist/server.js
:sparkles:
Now lets create a few handy scripts to help us do all of this in the future.
現(xiàn)在讓我們創(chuàng)建一些便利的腳本來幫助我們?cè)谖磥硗瓿伤羞@些。
"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:
在將來,當(dāng)您想通過Universal(本地)查看您的應(yīng)用程序的Production版本時(shí),只需運(yùn)行:
npm run build:ssr && npm run serve:ssr
Enjoy!
欣賞!
Once again to see a working version of everything, check out the universal-starter.
再次看到一切的工作版本,請(qǐng)查看通用啟動(dòng)器。