Angular 9:使用 angular i18n 和服务器端渲染(angular 通用)
Angular 9: Using angular i18n along with server side rendering (angular universal)
我正在将 angular 7 应用程序迁移到 angular 9,它使用服务器端呈现(angular 通用)和 angular i18n 2 种语言(法语和英语)。
在旧的 angular 7 流程中,由于我使用的是 AOT,因此我必须为生产环境进行 5 次构建:
- 2 个客户端版本(1 个用于法语,1 个用于英语)
- 2 x 服务器构建(1 个用于法语,1 个用于英语)
- 构建 server.ts
然后,在 server.ts
中,我动态加载了正确的服务器包
旧server.ts
app.engine('html', (_, options: any, callback) => {
const isFR= options.req.url.indexOf('site-fr') >= 0 ;
const {AppServerModuleNgFactory, LAZY_MODULE_MAP} = isFR ? require('./dist/server/fr/main') : require('./dist/server/en/main');
// Our index.html we'll use as our template
const template = readFileSync(join(DIST_FOLDER, 'browser', isFR ? 'fr' : 'en', 'index.html')).toString();
renderModuleFactory(AppServerModuleNgFactory, {
// Our index.html
document: template,
我将应用程序迁移到 angular 9,现在根据我在 documentation 中的了解,只需要一个客户端构建。
You can also provide the --localize option to the ng build command with your existing production configuration. In this case, the CLI builds all locales defined under i18n in the project configuration.
这似乎适用于客户端构建,因为生成了 2 个文件夹(生成了 fr
和 en
)。
然而,没有任何地方提到在 i18n 中使用 SSR。所以我最终得到了一个 server.ts
.
这是我用来构建的脚本和运行项目
angular.json
"serve:ssr": "node dist/myproject/server/main.js",
"build:ssr": "ng build -c production --localize && ng run myproject:server:production"
新server.ts
// The Express app is exported so that it can be used by serverless Functions.
export function app(port) {
const server = express();
const distFolder = join(process.cwd(), 'dist/myproject/browser/fr');
const indexHtml = existsSync(join(distFolder, 'index.original.html')) ? 'index.original.html' : 'index';
// Our Universal express-engine (found @ https://github.com/angular/universal/tree/master/modules/express-engine)
server.engine('html', ngExpressEngine({
bootstrap: AppServerModule,
}));
Dist文件夹结构
- dist
- myproject
- browser
- fr
- index.html
- *.js
- en
- index.html
- *.js
- server
- main.js
注意:我确实看到一个封闭的 github issue 描述了这个问题,但解决方案基本上是回到以前的方式,即每种语言有 2 个构建,[=17= 也有 2 个构建].
肯定还有其他方法吗?
我找到了一个只涉及 2 个构建的解决方案。但是 运行 现在需要 2 个服务器进程实例。
第 1 步:angular.json
确保在 angular.json
中正确定义了语言环境,并在 my-project:server
选项中添加新的 allLocales
目标。
我创建了一个新的 allLocales
目标,因为我不知道如何将 production
目标与 en
和 fr
配置结合起来。这样做的目的是只构建一个服务器,并生成所有语言服务器。
对于前面的包,这在 ng build
本身是可能的(即 ng build --configuration=production,fr,en or ng build --configuration=production --localize
)
angular.json
"projects": {
"my-project": {
"i18n": {
"locales": {
"en": {
"translation": "src/locale/messages.en.xlf",
"baseHref": ""
},
"fr": {
"translation": "src/locale/messages.fr.xlf",
"baseHref": ""
}
}
},
"root": "",
"sourceRoot": "src",
"projectType": "application",
"architect": {
"build": {
"builder": "@angular-devkit/build-angular:browser",
"options": {
//...
},
"configurations": {
"production": {
//...
},
"en": {
"localize": [
"en"
]
},
"fr": {
"localize": [
"fr"
]
}
}
},
"server": {
"builder": "@angular-devkit/build-angular:server",
"options": {
"outputPath": "dist/my-project/server",
"main": "server.ts",
"tsConfig": "tsconfig.server.json"
},
"configurations": {
"production": {
//...
},
"allLocales": {
"outputHashing": "none",
"optimization": false,
"sourceMap": false,
"namedChunks": false,
"extractLicenses": true,
"vendorChunk": false,
"localize": [
"en", "fr"
],
"fileReplacements": [
{
"replace": "src/environments/environment.ts",
"with": "src/environments/environment.prod.ts"
}
]
}
}
},
第 2 步:server.ts
修改server.ts
文件以接受语言参数。每个 运行 生成的服务器包 main.js
的替代品将有自己的端口和语言。
server.ts
//...
export function app(language) { //add language here
const server = express();
const distFolder = join(process.cwd(), 'dist/my-project/browser', language); //Use language here
//...
function run() {
const port = process.env.PORT || 5006;
const language = process.env.LANGUAGE || 'fr';
// Start up the Node server
const server = app(language); //Use the language here
server.listen(port, () => {
console.log(`Node Express server listening on http://localhost:${port} for language ${language}`);
});
第 3 步:修改 package.json
package.json
"build:ssr": "ng build -c production --localize && ng run my-project:server:allLocales"
"serve:ssr-en": "env PORT=5006 LANGUAGE=en node dist/my-project/server/en/main.js",
"serve:ssr-fr": "env PORT=5007 LANGUAGE=fr node dist/my-project/server/fr/main.js",
build:ssr
将为所有语言构建客户端包,然后为所有语言构建服务器包
server:ssr-XX
将为与语言 XX
关联的端口和语言启动 nodejs 服务器
这是结构
- dist
- myproject
- browser
- fr
- index.html
- *.js
- en
- index.html
- *.js
- server
- fr
- main.js
- en
- main.js
第 4 步:反向代理
如果您使用反向代理,请不要忘记将所有请求重定向到正确的 main.js 实例
注意 angular 9 的构建过程现在快得多,因为只有 2 个构建。
这就是我们设法解决 Angular 9(通用和国际化)中的问题的方法:
我们使用旧的 webpack 配置和本地化来构建 ssr。
Build 在单个构建中为每种语言配置浏览器和服务器。
注意 我们在 Angular 8 中有通用和 i18n,因此您可能需要检查 Webpack 服务器配置的文档。
package.json:
- 我们使用了仍在 Angular 8 中的旧 webpack 服务器配置
通用文档。
- 使用本地化标志构建生产环境。
构建很好,但是 docker 内存堆大小有问题,因此增加了节点 space
...
"webpack:server": "webpack --config webpack.server.config.js --progress --colors",
"build:prod": "ng build --configuration=production --localize",
"build:server:prod": "node --max_old_space_size=8048 ./node_modules/@angular/cli/bin/ng run APP-NAME:server:production",
"build:client-and-server-bundles": "npm run build:prod && npm run build:server:prod",
"build:ssr": "npm run build:client-and-server-bundles && npm run webpack:server",
"serve:ssr": "node dist/server.js"
angular.json(在项目设置中声明 i18n 语言环境):
服务器选项:
...
"options": {
"main": "src/main.server.ts",
"tsConfig": "src/tsconfig.server.json",
"localize": ["fi", "en", "sv"]
}
server.ts
在服务器配置中,我们声明了所有包的路由
const routes = [
{path: '/en/*', view: 'en/index', bundle: require('./dist/server/en/main')},
{path: '/sv/*', view: 'sv/index', bundle: require('./dist/server/sv/main')},
{path: '/*', view: 'fi/index', bundle: require('./dist/server/fi/main')}
];
然后是一个 hacky 解决方法:
为每个区域声明 require 方法,使用 AppServerModule、Lazy 模块映射、快速引擎和模块映射提供程序构建服务器配置
const {AppServerModule: AppServerModuleFi, LAZY_MODULE_MAP: LAZY_MODULE_MAP_FI, ngExpressEngine: ngExpressEngineFi, provideModuleMap: provideModuleMapFi} = require('./dist/server/fi/main');
const {AppServerModule: AppServerModuleEn, LAZY_MODULE_MAP: LAZY_MODULE_MAP_EN, ngExpressEngine: ngExpressEngineEn, provideModuleMap: provideModuleMapEn} = require('./dist/server/en/main');
const {AppServerModule: AppServerModuleSv, LAZY_MODULE_MAP: LAZY_MODULE_MAP_SV, ngExpressEngine: ngExpressEngineSv, provideModuleMap: provideModuleMapSv} = require('./dist/server/sv/main');
然后对于每个路由,我们将使用之前声明的服务器配置文件,其中 "dedicated" 需要配置。
英语构建示例:
routes.forEach((route) => {
if (route.path.startsWith('/en')) { // Check against path
// EN routes
app.get(route.path, (req, res) => {
app.engine('html', ngExpressEngineEn({
bootstrap: AppServerModuleEn,
providers: [
provideModuleMapEn(LAZY_MODULE_MAP_EN)
]
}));
app.set('view engine', 'html');
app.set('views', join(DIST_FOLDER, 'browser'));
res.render(route.view, {
req,
res,
engine: ngExpressEngineEn({
bootstrap: AppServerModuleEn,
providers: [provideModuleMapEn(LAZY_MODULE_MAP_EN),
{ req, res }]
})
});
});
我正在将 angular 7 应用程序迁移到 angular 9,它使用服务器端呈现(angular 通用)和 angular i18n 2 种语言(法语和英语)。
在旧的 angular 7 流程中,由于我使用的是 AOT,因此我必须为生产环境进行 5 次构建:
- 2 个客户端版本(1 个用于法语,1 个用于英语)
- 2 x 服务器构建(1 个用于法语,1 个用于英语)
- 构建 server.ts
然后,在 server.ts
中,我动态加载了正确的服务器包
旧server.ts
app.engine('html', (_, options: any, callback) => {
const isFR= options.req.url.indexOf('site-fr') >= 0 ;
const {AppServerModuleNgFactory, LAZY_MODULE_MAP} = isFR ? require('./dist/server/fr/main') : require('./dist/server/en/main');
// Our index.html we'll use as our template
const template = readFileSync(join(DIST_FOLDER, 'browser', isFR ? 'fr' : 'en', 'index.html')).toString();
renderModuleFactory(AppServerModuleNgFactory, {
// Our index.html
document: template,
我将应用程序迁移到 angular 9,现在根据我在 documentation 中的了解,只需要一个客户端构建。
You can also provide the --localize option to the ng build command with your existing production configuration. In this case, the CLI builds all locales defined under i18n in the project configuration.
这似乎适用于客户端构建,因为生成了 2 个文件夹(生成了 fr
和 en
)。
然而,没有任何地方提到在 i18n 中使用 SSR。所以我最终得到了一个 server.ts
.
这是我用来构建的脚本和运行项目
angular.json
"serve:ssr": "node dist/myproject/server/main.js",
"build:ssr": "ng build -c production --localize && ng run myproject:server:production"
新server.ts
// The Express app is exported so that it can be used by serverless Functions.
export function app(port) {
const server = express();
const distFolder = join(process.cwd(), 'dist/myproject/browser/fr');
const indexHtml = existsSync(join(distFolder, 'index.original.html')) ? 'index.original.html' : 'index';
// Our Universal express-engine (found @ https://github.com/angular/universal/tree/master/modules/express-engine)
server.engine('html', ngExpressEngine({
bootstrap: AppServerModule,
}));
Dist文件夹结构
- dist
- myproject
- browser
- fr
- index.html
- *.js
- en
- index.html
- *.js
- server
- main.js
注意:我确实看到一个封闭的 github issue 描述了这个问题,但解决方案基本上是回到以前的方式,即每种语言有 2 个构建,[=17= 也有 2 个构建].
肯定还有其他方法吗?
我找到了一个只涉及 2 个构建的解决方案。但是 运行 现在需要 2 个服务器进程实例。
第 1 步:angular.json
确保在 angular.json
中正确定义了语言环境,并在 my-project:server
选项中添加新的 allLocales
目标。
我创建了一个新的 allLocales
目标,因为我不知道如何将 production
目标与 en
和 fr
配置结合起来。这样做的目的是只构建一个服务器,并生成所有语言服务器。
对于前面的包,这在 ng build
本身是可能的(即 ng build --configuration=production,fr,en or ng build --configuration=production --localize
)
angular.json
"projects": {
"my-project": {
"i18n": {
"locales": {
"en": {
"translation": "src/locale/messages.en.xlf",
"baseHref": ""
},
"fr": {
"translation": "src/locale/messages.fr.xlf",
"baseHref": ""
}
}
},
"root": "",
"sourceRoot": "src",
"projectType": "application",
"architect": {
"build": {
"builder": "@angular-devkit/build-angular:browser",
"options": {
//...
},
"configurations": {
"production": {
//...
},
"en": {
"localize": [
"en"
]
},
"fr": {
"localize": [
"fr"
]
}
}
},
"server": {
"builder": "@angular-devkit/build-angular:server",
"options": {
"outputPath": "dist/my-project/server",
"main": "server.ts",
"tsConfig": "tsconfig.server.json"
},
"configurations": {
"production": {
//...
},
"allLocales": {
"outputHashing": "none",
"optimization": false,
"sourceMap": false,
"namedChunks": false,
"extractLicenses": true,
"vendorChunk": false,
"localize": [
"en", "fr"
],
"fileReplacements": [
{
"replace": "src/environments/environment.ts",
"with": "src/environments/environment.prod.ts"
}
]
}
}
},
第 2 步:server.ts
修改server.ts
文件以接受语言参数。每个 运行 生成的服务器包 main.js
的替代品将有自己的端口和语言。
server.ts
//...
export function app(language) { //add language here
const server = express();
const distFolder = join(process.cwd(), 'dist/my-project/browser', language); //Use language here
//...
function run() {
const port = process.env.PORT || 5006;
const language = process.env.LANGUAGE || 'fr';
// Start up the Node server
const server = app(language); //Use the language here
server.listen(port, () => {
console.log(`Node Express server listening on http://localhost:${port} for language ${language}`);
});
第 3 步:修改 package.json
package.json
"build:ssr": "ng build -c production --localize && ng run my-project:server:allLocales"
"serve:ssr-en": "env PORT=5006 LANGUAGE=en node dist/my-project/server/en/main.js",
"serve:ssr-fr": "env PORT=5007 LANGUAGE=fr node dist/my-project/server/fr/main.js",
build:ssr
将为所有语言构建客户端包,然后为所有语言构建服务器包
server:ssr-XX
将为与语言 XX
这是结构
- dist
- myproject
- browser
- fr
- index.html
- *.js
- en
- index.html
- *.js
- server
- fr
- main.js
- en
- main.js
第 4 步:反向代理
如果您使用反向代理,请不要忘记将所有请求重定向到正确的 main.js 实例
注意 angular 9 的构建过程现在快得多,因为只有 2 个构建。
这就是我们设法解决 Angular 9(通用和国际化)中的问题的方法: 我们使用旧的 webpack 配置和本地化来构建 ssr。 Build 在单个构建中为每种语言配置浏览器和服务器。
注意 我们在 Angular 8 中有通用和 i18n,因此您可能需要检查 Webpack 服务器配置的文档。
package.json:
- 我们使用了仍在 Angular 8 中的旧 webpack 服务器配置 通用文档。
- 使用本地化标志构建生产环境。
构建很好,但是 docker 内存堆大小有问题,因此增加了节点 space
... "webpack:server": "webpack --config webpack.server.config.js --progress --colors", "build:prod": "ng build --configuration=production --localize", "build:server:prod": "node --max_old_space_size=8048 ./node_modules/@angular/cli/bin/ng run APP-NAME:server:production", "build:client-and-server-bundles": "npm run build:prod && npm run build:server:prod", "build:ssr": "npm run build:client-and-server-bundles && npm run webpack:server", "serve:ssr": "node dist/server.js"
angular.json(在项目设置中声明 i18n 语言环境):
服务器选项:
...
"options": {
"main": "src/main.server.ts",
"tsConfig": "src/tsconfig.server.json",
"localize": ["fi", "en", "sv"]
}
server.ts
在服务器配置中,我们声明了所有包的路由
const routes = [
{path: '/en/*', view: 'en/index', bundle: require('./dist/server/en/main')},
{path: '/sv/*', view: 'sv/index', bundle: require('./dist/server/sv/main')},
{path: '/*', view: 'fi/index', bundle: require('./dist/server/fi/main')}
];
然后是一个 hacky 解决方法: 为每个区域声明 require 方法,使用 AppServerModule、Lazy 模块映射、快速引擎和模块映射提供程序构建服务器配置
const {AppServerModule: AppServerModuleFi, LAZY_MODULE_MAP: LAZY_MODULE_MAP_FI, ngExpressEngine: ngExpressEngineFi, provideModuleMap: provideModuleMapFi} = require('./dist/server/fi/main');
const {AppServerModule: AppServerModuleEn, LAZY_MODULE_MAP: LAZY_MODULE_MAP_EN, ngExpressEngine: ngExpressEngineEn, provideModuleMap: provideModuleMapEn} = require('./dist/server/en/main');
const {AppServerModule: AppServerModuleSv, LAZY_MODULE_MAP: LAZY_MODULE_MAP_SV, ngExpressEngine: ngExpressEngineSv, provideModuleMap: provideModuleMapSv} = require('./dist/server/sv/main');
然后对于每个路由,我们将使用之前声明的服务器配置文件,其中 "dedicated" 需要配置。 英语构建示例:
routes.forEach((route) => {
if (route.path.startsWith('/en')) { // Check against path
// EN routes
app.get(route.path, (req, res) => {
app.engine('html', ngExpressEngineEn({
bootstrap: AppServerModuleEn,
providers: [
provideModuleMapEn(LAZY_MODULE_MAP_EN)
]
}));
app.set('view engine', 'html');
app.set('views', join(DIST_FOLDER, 'browser'));
res.render(route.view, {
req,
res,
engine: ngExpressEngineEn({
bootstrap: AppServerModuleEn,
providers: [provideModuleMapEn(LAZY_MODULE_MAP_EN),
{ req, res }]
})
});
});