使用Angular4(AngularUniversal)的服务器端渲染
我正在从事Angular4 webpack项目,我想将其添加到该项目中,以使服务器端渲染成为可能。但大多数教程都使用angular cli。我想将Universal与webpack集成。我尝试了这样做,但运气不佳。请有人帮忙。给定教程中提到的示例使用中提到的示例角资源科。他们最近更新了文档,但尚未提供实施@angular/universal的详细文档。以前是你正在寻找的页面,但它提到了一些问题。可能这就是他们删除它并决定重写它的原因。这只适用于Angular 2。如果您想从头开始,您可以使用它,它具有以下所有功能:使用Angular4(AngularUniversal)的服务器端渲染,angular,webpack,angular-universal,Angular,Webpack,Angular Universal,我正在从事Angular4 webpack项目,我想将其添加到该项目中,以使服务器端渲染成为可能。但大多数教程都使用angular cli。我想将Universal与webpack集成。我尝试了这样做,但运气不佳。请有人帮忙。给定教程中提到的示例使用中提到的示例角资源科。他们最近更新了文档,但尚未提供实施@angular/universal的详细文档。以前是你正在寻找的页面,但它提到了一些问题。可能这就是他们删除它并决定重写它的原因。这只适用于Angular 2。如果您想从头开始,您可以使用它,
- 角度4
- 网页包
- 开发/生产模式
- SCSS编译
- i18n、SEO和TSLint/codelyzer
- 延迟加载、配置、缓存
安装这些软件包:
npm install@angular/{common,compiler,compiler cli,core,forms,http,平台浏览器,平台浏览器动态,平台服务器,路由器,动画}@latesttypescript@latest--保存
npm install express@types/express--保存开发人员
将此添加到您的app.module.ts文件中
import { BrowserModule } from '@angular/platform-browser';
BrowserModule.withServerTransition({
appId: 'my-app-id' // withServerTransition is available only in Angular 4
}),
创建以下文件
src/uni/app.server.ts
import { NgModule } from '@angular/core';
import { APP_BASE_HREF } from '@angular/common';
import { ServerModule } from '@angular/platform-server';
import { AppComponent } from '../app/app';
import { AppModule } from '../app/app.module';
import 'reflect-metadata';
import 'zone.js';
@NgModule({
imports: [
ServerModule,
AppModule
],
bootstrap: [
AppComponent
],
providers: [
{provide: APP_BASE_HREF, useValue: '/'}
]
})
export class AppServerModule {
}
import 'zone.js/dist/zone-node';
import 'zone.js';
import 'reflect-metadata';
import { enableProdMode } from '@angular/core';
import { AppServerModuleNgFactory } from '../../aot/src/uni/app.server.ngfactory';
import * as express from 'express';
import { ngUniversalEngine } from './universal-engine';
enableProdMode();
const server = express();
// set our angular engine as the handler for html files, so it will be used to render them.
server.engine('html', ngUniversalEngine({
bootstrap: [AppServerModuleNgFactory]
}));
// set default view directory
server.set('views', 'src');
// handle requests for routes in the app. ngExpressEngine does the rendering.
server.get(['/', '/dashboard', '/heroes', '/detail/:id'], (req:any, res:any) => {
res.render('index.html', {req});
});
// handle requests for static files
server.get(['/*.js', '/*.css'], (req:any, res:any, next:any) => {
let fileName: string = req.originalUrl;
console.log(fileName);
let root = fileName.startsWith('/node_modules/') ? '.' : 'src';
res.sendFile(fileName, { root: root }, function (err:any) {
if (err) {
next(err);
}
});
});
// start the server
server.listen(3200, () => {
console.log('listening on port 3200...');
});
import * as fs from 'fs';
import { renderModuleFactory } from '@angular/platform-server';
const templateCache = {}; // cache for page templates
const outputCache = {}; // cache for rendered pages
export function ngUniversalEngine(setupOptions: any) {
return function (filePath: string, options: { req: Request }, callback: (err: Error, html: string) => void) {
let url: string = options.req.url;
let html: string = outputCache[url];
if (html) {
// return already-built page for this url
console.log('from cache: ' + url);
callback(null, html);
return;
}
console.log('building: ' + url);
if (!templateCache[filePath]) {
let file = fs.readFileSync(filePath);
templateCache[filePath] = file.toString();
}
// render the page via angular platform-server
let appModuleFactory = setupOptions.bootstrap[0];
renderModuleFactory(appModuleFactory, {
document: templateCache[filePath],
url: url
}).then(str => {
outputCache[url] = str;
callback(null, str);
});
};
}
src/uni/server uni.ts
import { NgModule } from '@angular/core';
import { APP_BASE_HREF } from '@angular/common';
import { ServerModule } from '@angular/platform-server';
import { AppComponent } from '../app/app';
import { AppModule } from '../app/app.module';
import 'reflect-metadata';
import 'zone.js';
@NgModule({
imports: [
ServerModule,
AppModule
],
bootstrap: [
AppComponent
],
providers: [
{provide: APP_BASE_HREF, useValue: '/'}
]
})
export class AppServerModule {
}
import 'zone.js/dist/zone-node';
import 'zone.js';
import 'reflect-metadata';
import { enableProdMode } from '@angular/core';
import { AppServerModuleNgFactory } from '../../aot/src/uni/app.server.ngfactory';
import * as express from 'express';
import { ngUniversalEngine } from './universal-engine';
enableProdMode();
const server = express();
// set our angular engine as the handler for html files, so it will be used to render them.
server.engine('html', ngUniversalEngine({
bootstrap: [AppServerModuleNgFactory]
}));
// set default view directory
server.set('views', 'src');
// handle requests for routes in the app. ngExpressEngine does the rendering.
server.get(['/', '/dashboard', '/heroes', '/detail/:id'], (req:any, res:any) => {
res.render('index.html', {req});
});
// handle requests for static files
server.get(['/*.js', '/*.css'], (req:any, res:any, next:any) => {
let fileName: string = req.originalUrl;
console.log(fileName);
let root = fileName.startsWith('/node_modules/') ? '.' : 'src';
res.sendFile(fileName, { root: root }, function (err:any) {
if (err) {
next(err);
}
});
});
// start the server
server.listen(3200, () => {
console.log('listening on port 3200...');
});
import * as fs from 'fs';
import { renderModuleFactory } from '@angular/platform-server';
const templateCache = {}; // cache for page templates
const outputCache = {}; // cache for rendered pages
export function ngUniversalEngine(setupOptions: any) {
return function (filePath: string, options: { req: Request }, callback: (err: Error, html: string) => void) {
let url: string = options.req.url;
let html: string = outputCache[url];
if (html) {
// return already-built page for this url
console.log('from cache: ' + url);
callback(null, html);
return;
}
console.log('building: ' + url);
if (!templateCache[filePath]) {
let file = fs.readFileSync(filePath);
templateCache[filePath] = file.toString();
}
// render the page via angular platform-server
let appModuleFactory = setupOptions.bootstrap[0];
renderModuleFactory(appModuleFactory, {
document: templateCache[filePath],
url: url
}).then(str => {
outputCache[url] = str;
callback(null, str);
});
};
}
src/uni/universal engine.ts
import { NgModule } from '@angular/core';
import { APP_BASE_HREF } from '@angular/common';
import { ServerModule } from '@angular/platform-server';
import { AppComponent } from '../app/app';
import { AppModule } from '../app/app.module';
import 'reflect-metadata';
import 'zone.js';
@NgModule({
imports: [
ServerModule,
AppModule
],
bootstrap: [
AppComponent
],
providers: [
{provide: APP_BASE_HREF, useValue: '/'}
]
})
export class AppServerModule {
}
import 'zone.js/dist/zone-node';
import 'zone.js';
import 'reflect-metadata';
import { enableProdMode } from '@angular/core';
import { AppServerModuleNgFactory } from '../../aot/src/uni/app.server.ngfactory';
import * as express from 'express';
import { ngUniversalEngine } from './universal-engine';
enableProdMode();
const server = express();
// set our angular engine as the handler for html files, so it will be used to render them.
server.engine('html', ngUniversalEngine({
bootstrap: [AppServerModuleNgFactory]
}));
// set default view directory
server.set('views', 'src');
// handle requests for routes in the app. ngExpressEngine does the rendering.
server.get(['/', '/dashboard', '/heroes', '/detail/:id'], (req:any, res:any) => {
res.render('index.html', {req});
});
// handle requests for static files
server.get(['/*.js', '/*.css'], (req:any, res:any, next:any) => {
let fileName: string = req.originalUrl;
console.log(fileName);
let root = fileName.startsWith('/node_modules/') ? '.' : 'src';
res.sendFile(fileName, { root: root }, function (err:any) {
if (err) {
next(err);
}
});
});
// start the server
server.listen(3200, () => {
console.log('listening on port 3200...');
});
import * as fs from 'fs';
import { renderModuleFactory } from '@angular/platform-server';
const templateCache = {}; // cache for page templates
const outputCache = {}; // cache for rendered pages
export function ngUniversalEngine(setupOptions: any) {
return function (filePath: string, options: { req: Request }, callback: (err: Error, html: string) => void) {
let url: string = options.req.url;
let html: string = outputCache[url];
if (html) {
// return already-built page for this url
console.log('from cache: ' + url);
callback(null, html);
return;
}
console.log('building: ' + url);
if (!templateCache[filePath]) {
let file = fs.readFileSync(filePath);
templateCache[filePath] = file.toString();
}
// render the page via angular platform-server
let appModuleFactory = setupOptions.bootstrap[0];
renderModuleFactory(appModuleFactory, {
document: templateCache[filePath],
url: url
}).then(str => {
outputCache[url] = str;
callback(null, str);
});
};
}
在tsconfig.ts文件中添加以下配置,我假定该文件位于根目录中
{
"compilerOptions": {
"baseUrl": "",
"declaration": false,
"emitDecoratorMetadata": true,
"experimentalDecorators": true,
"lib": ["es2016", "dom"],
"moduleResolution": "node",
"outDir": "./dist/out-tsc",
"sourceMap": true,
"target": "es5",
"module": "commonjs",
"types": ["node"],
"typeRoots": [
"node_modules/@types"
]
},
"files": [
"src/uni/app.server.ts",
"src/uni/server-uni.ts"
],
"angularCompilerOptions": {
"genDir": "aot",
"entryModule": "./src/app/app.module#AppModule",
"skipMetadataEmit": true
},
"exclude": [
"test.ts",
"**/*.spec.ts"
]
}
const ngtools = require('@ngtools/webpack');
const webpack = require('webpack');
const path = require('path');
const ExtractTextWebpackPlugin = require("extract-text-webpack-plugin");
module.exports = {
devtool: 'source-map',
entry: {
main: ['./src/uni/app.server.ts', './src/uni/server-uni.ts']
},
resolve: {
extensions: ['.ts', '.js']
},
target: 'node',
output: {
path: path.join(__dirname, "dist"),
filename: 'server.js'
},
plugins: [
new ngtools.AotPlugin({
tsConfigPath: './tsconfig.json'
})
],
module: {
rules: [
{
test: /\.(scss|html|png|jpe?g|gif|svg|woff|woff2|ttf|eot|ico)$/,
use: 'raw-loader'
},
{ test: /\.ts$/, loader: require.resolve('@ngtools/webpack') },
{
test: /\.(png|jpg|woff|woff2|eot|ttf|svg)(\?v=[0-9]\.[0-9]\.[0-9])?$/,
loader: 'url?limit=512&&name=[path][name].[ext]?[hash]'
},
{ test: /\.scss$/, use: [{
loader: "style-loader" // creates style nodes from JS strings
}, {
loader: "css-loader" // translates CSS into CommonJS
}, {
loader: "sass-loader" // compiles Sass to CSS
}] }
]
}
}
在根目录中播放webpack.config.uni.js
{
"compilerOptions": {
"baseUrl": "",
"declaration": false,
"emitDecoratorMetadata": true,
"experimentalDecorators": true,
"lib": ["es2016", "dom"],
"moduleResolution": "node",
"outDir": "./dist/out-tsc",
"sourceMap": true,
"target": "es5",
"module": "commonjs",
"types": ["node"],
"typeRoots": [
"node_modules/@types"
]
},
"files": [
"src/uni/app.server.ts",
"src/uni/server-uni.ts"
],
"angularCompilerOptions": {
"genDir": "aot",
"entryModule": "./src/app/app.module#AppModule",
"skipMetadataEmit": true
},
"exclude": [
"test.ts",
"**/*.spec.ts"
]
}
const ngtools = require('@ngtools/webpack');
const webpack = require('webpack');
const path = require('path');
const ExtractTextWebpackPlugin = require("extract-text-webpack-plugin");
module.exports = {
devtool: 'source-map',
entry: {
main: ['./src/uni/app.server.ts', './src/uni/server-uni.ts']
},
resolve: {
extensions: ['.ts', '.js']
},
target: 'node',
output: {
path: path.join(__dirname, "dist"),
filename: 'server.js'
},
plugins: [
new ngtools.AotPlugin({
tsConfigPath: './tsconfig.json'
})
],
module: {
rules: [
{
test: /\.(scss|html|png|jpe?g|gif|svg|woff|woff2|ttf|eot|ico)$/,
use: 'raw-loader'
},
{ test: /\.ts$/, loader: require.resolve('@ngtools/webpack') },
{
test: /\.(png|jpg|woff|woff2|eot|ttf|svg)(\?v=[0-9]\.[0-9]\.[0-9])?$/,
loader: 'url?limit=512&&name=[path][name].[ext]?[hash]'
},
{ test: /\.scss$/, use: [{
loader: "style-loader" // creates style nodes from JS strings
}, {
loader: "css-loader" // translates CSS into CommonJS
}, {
loader: "sass-loader" // compiles Sass to CSS
}] }
]
}
}
在您的package.json文件中添加以下脚本:
"ngc-build": "ngc -p ./tsconfig.json", // To generate ngFactory file
"build:uni": "webpack --config webpack.config.uni.js",
"serve:uni": "node dist/server.js",
我们应该记住一些事情:
窗口
、文档
、导航器
、以及其他浏览器类型—服务器上不存在—因此使用它们或使用它们的任何库(例如jQuery)都无法工作。如果您真的需要某些功能,您可以在本文中提供一些选项
角度通用仅用于角度2.x。Angular 4.x您需要使用平台服务器。示例如下:
对于角度2.X.X:
AngularClass使用express/universal的种子项目
对于角度为4.X.X的
使用angular平台服务器
其他例子也很少:
- 逐步学习执行
您可以找到一个关于打开Webpack的服务器端渲染的Angular 4教程
特点:
- 它是建立在角度4
- 它不依赖于角度CLI
- 它建立在网页包的基础上
- 博客提供了分三个阶段的逐步指导:
- 阶段1:在Docker容器上运行服务器端呈现的Hello World页面(为了方便起见,我提供了一个预安装的Docker映像,但说明应该在您自己的环境中运行)
- 阶段2:在主页上创建新的功能链接
- 阶段3(可选):通过RESTfulAPI动态插入WordPress博客文章
最终结果可以在Docker主机上轻松查看,如下所示:
(dockerhost)$ docker run -it -p 8002:8000 oveits/angular_hello_world:centos bash
(container)# git clone https://github.com/oveits/ng-universal-demo
(container)# cd ng-universal-demo
(container)# npm i
(container)# npm run start
我在上面选择了端口8002,因为我已经在端口8000和8001上运行了其他示例;如果docker主机在Virtualbox上运行,则可能需要从Virtualbox主机的8002到Virtualbox VM的8002的端口映射
在浏览器上,导航到。您将看到从WordPressAPI下载的博客文章的内容。使用右键单击->查看源代码,您将看到HTML内容。这表明这是一个服务器端呈现页面
注:与您尝试过的教程一样,本教程基于a,但通过,我发现了一个升级到Angular 4的版本,并且与Webpack的配合更好(有关更多详细信息,请参阅的附录)。服务器端是否使用node.js和.net core?我使用的是nodejs。