1.Angular Universal教程
1.1 为项目添加SSR支持
执行下面的命令为项目添加SSR支持:
ng add @nguniversal/express-engine
1.2 在服务端使用绝对URL进行HTTP(数据)请求
在服务端渲染的应用中,HTTP URL 必须是绝对的(比如,https://my-server.com/api/heroes)。这意味着当在服务器上运行时,URL 必须以某种方式转换为绝对 URL,而在浏览器中运行时,它们是相对 URL。
1.3 使用浏览器API
1.3.1 提供服务端访问的浏览器API对象
服务端代码如果使用到了浏览器API,需要在全局变量中提供相应的Mock对象,在server.ts中提供,
// server.ts
const distFolder = join(process.cwd(), 'dist/<your_project_name>/browser');
const template = readFileSync(join(distFolder, 'index.html')).toString();
const win = domino.createWindow(template);
global['window'] = win;
global['document'] = win.document;
global['location'] = win.location;
global['navigator'] = win.navigator;
// 如果引入了其他第三方模块,Mock对象需要在AppServerModule前面声明
import import { AppServerModule } from './src/main.server';
在tsconfig.server.json中更换模块格式,解决打包报错问题
{
"extends": "./tsconfig.app.json",
"compilerOptions": {
// fix 打包问题
"module": "commonjs",
},
}
1.3.2 根据环境选择性执行代码
export class SomeService {
constructor(
@Inject(PLATFORM_ID) private platformId: string,
) {
if(isPlatformBrowser(this.platformId)) {
// 只在浏览器端执行
}
}
}
1.4 组件在初始化时执行异步操作
UI组件在constructor或ngOnInit等生命周期函数中发起异步操作时,需要确保Observable的next回调会被执行,否则有可能会阻塞服务器渲染。
// good example
export class GoodComponent{
constructor(private someService: SomeService,) {
this.getData();
}
getData(): void {
this.someService.getData()
.subscribe(res => {
// do something
})
}
}
// bad example
export class BadComponent {
isLogin = false;
constructor() {
this.getData();
}
getData() {
// isLogin 为false,Observable next回调永远不会执行
timer(10000).pipe(
filter(() => this.isLogin))
.subscribe(() => {
// do something
})
}
}
如果没有国际化的需求,可以跳过下面的内容。
1.5 服务端生成多语种build
执行下面的命令生成多语种build
ng build --localize && ng run <your_project_name>:server --localize
1.5.1 server.ts适配多语种build
需要对server.ts改造如下:
export function app(locale: string): express.Express {
const server = express();
const distFolder = join(process.cwd(), 'dist/<your_project_name>/browser', locale);
const indexHtml = existsSync(join(distFolder, 'index.original.html'))
? 'index.original.html'
: 'index';
// Our Universal express-engine (found @ https://github.com/angular/universal/tree/main/modules/express-engine)
server.engine('html', ngExpressEngine({
bootstrap: AppServerModule,
providers: [
{ provide: LOCALE_ID, useValue: locale },
],
}));
server.set('view engine', 'html');
server.set('views', distFolder);
// Example Express Rest API endpoints
// server.get('/api/**', (req, res) => { });
// Serve static files from /browser
server.get('*.*', express.static(distFolder, {
maxAge: '1y'
}));
// All regular routes use the Universal engine
server.get('*', (req, res) => {
res.render(indexHtml, {
req,
providers: [
{ provide: APP_BASE_HREF, useValue: req.baseUrl },
]
});
});
return server;
}
function run(): void {
const server = app('');
const port = process.env['PORT'] || 4000;
// Start up the Node server
server.listen(port, () => {
console.log(`Node Express server listening on http://localhost:${port}`);
});
}
// Webpack will replace 'require' with '__webpack_require__'
// '__non_webpack_require__' is a proxy to Node 'require'
// The below code is to ensure that the server is run only when not requiring the bundle.
declare const __non_webpack_require__: NodeRequire;
const mainModule = __non_webpack_require__.main;
const moduleFilename = mainModule && mainModule.filename || '';
// only run in dev mode
if ((moduleFilename === __filename && isDev) ||
moduleFilename.includes('iisnode')
) {
run();
}
export * from './src/main.server';
1.5.2 通过express分发多个语种
创建一个proxy-server.js文件,通过express分发不同语种的build,仅在生产环境使用
const express = require("express");
const path = require("path");
// 获取对应语种的build
const getLocalizedServer = (locale) => {
const distFolder = path.join(process.cwd(), `dist/<your_project_name>/server/${locale}`);
const server = require(`${distFolder}/main.js`);
return server.app(locale);
};
function run() {
const appZh = getLocalizedServer("zh");
const appEn = getLocalizedServer("en");
const server = express();
server.use("/zh", appZh);
server.use("", appEn);
server.listen(4200, () => {
console.log(`Node Express server listening on http://localhost:4200`);
});
}
run();