Angular 9.0 SSR编译成功,但无法在服务器上发布
Angular 9.0 SSR is compiled, but I cannot publish it on the server
我将我的 Angular 8 项目更新为 Angular 9。我 运行 命令“npm 运行 build: ssr && npm 运行 serve: ssr”。它运行完美,在“dist”文件夹中创建了“browser”和“server”文件夹。
我将这些文件上传到我的服务器(Plesk 面板)。我将其设置为使用 node.js.
读取服务器/main.js 文件
失败,因为在“dist”文件夹中找不到“browser / index.html”。我创建了一个名为“dist”的文件夹,并将“browser”文件夹移动到其中。
然后超时了,我的项目没有运行。我运行Node.js在开发模式下看到报错,但是说是超时。
server.ts
import 'zone.js/dist/zone-node';
import { ngExpressEngine } from '@nguniversal/express-engine';
import * as express from 'express';
import { join } from 'path';
import { AppServerModule } from './src/main.server';
import { APP_BASE_HREF } from '@angular/common';
import { existsSync } from 'fs';
const domino = require('domino'); // kb
const fs = require('fs'); // kb
const template = fs.readFileSync('dist/browser/index.html').toString(); // kb
const win = domino.createWindow(template); // kb
global['window'] = win;
global['document'] = win.document;
global["branch"] = null;
global["object"] = win.object;
global['DOMTokenList'] = win.DOMTokenList;
global['Node'] = win.Node;
global['Text'] = win.Text;
global['HTMLElement'] = win.HTMLElement;
global['navigator'] = win.navigator;
// The Express app is exported so that it can be used by serverless Functions.
export function app() {
const server = express();
const distFolder = join(process.cwd(), 'dist/browser');
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,
}));
server.set('view engine', 'html');
server.set('views', distFolder);
// Example Express Rest API endpoints
// app.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() {
const port = process.env.PORT || 4000;
// Start up the Node server
const server = app();
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 || '';
if (moduleFilename === __filename || moduleFilename.includes('iisnode')) {
run();
}
export * from './src/main.server';
主.server.ts
import { enableProdMode } from '@angular/core';
import { environment } from './environments/environment';
if (environment.production) {
enableProdMode();
}
export { AppServerModule } from './app/app.server.module';
export {ngExpressEngine} from '@nguniversal/express-engine';
export { renderModule, renderModuleFactory } from '@angular/platform-server';
tsconfig.app.json
{
"extends": "./tsconfig.json",
"compilerOptions": {
"outDir": "./out-tsc/app",
"types": []
},
"files": [
"src/main.ts",
"src/polyfills.ts"
],
"include": [
"src/**/*.d.ts"
]
}
tsconfig.server.json
{
"extends": "./tsconfig.app.json",
"compilerOptions": {
"module": "commonjs",
"outDir": "./out-tsc/app-server",
"baseUrl": ".",
"types": [
"node"
]
},
"angularCompilerOptions": {
"entryModule": "./src/app/app.server.module#AppServerModule"
},
"files": [
"src/main.server.ts",
"server.ts"
]
}
tsconfig.json
{
"compileOnSave": false,
"compilerOptions": {
"baseUrl": "./",
"outDir": "./dist/out-tsc",
"sourceMap": true,
"declaration": false,
"module": "esnext",
"moduleResolution": "node",
"emitDecoratorMetadata": true,
"experimentalDecorators": true,
"importHelpers": true,
"target": "es5",
"typeRoots": [
"node_modules/@types"
],
"lib": [
"es2018",
"dom"
]
}
}
package.json
{
"name": "kodumunblogu",
"version": "0.0.0",
"scripts": {
"ng": "ng",
"start": "ng serve",
"build": "ng build",
"test": "ng test",
"lint": "ng lint",
"e2e": "ng e2e",
"compile:server_bak": "webpack --config webpack.server.config.js --progress --colors",
"build:ssr_bak": "npm run build:client-and-server-bundles && npm run compile:server",
"serve:ssr_bak": "node dist/server",
"build:client-and-server-bundles_bak": "ng build --prod --aot && ng run kodumunblogu:server:production --bundleDependencies all",
"dev:ssr": "ng run kodumunblogu:serve-ssr",
"serve:ssr": "node dist/server/main.js",
"build:ssr": "ng build --prod && ng run kodumunblogu:server:production",
"prerender": "ng run kodumunblogu:prerender",
"postinstall": "ngcc"
},
"private": true,
"dependencies": {
"@angular/animations": "^9.0.0",
"@angular/cdk": "^9.0.0",
"@angular/common": "~9.0.0",
"@angular/compiler": "~9.0.0",
"@angular/core": "~9.0.0",
"@angular/forms": "~9.0.0",
"@angular/material": "^9.0.0",
"@angular/platform-browser": "~9.0.0",
"@angular/platform-browser-dynamic": "~9.0.0",
"@angular/platform-server": "^9.0.0",
"@angular/router": "~9.0.0",
"@fortawesome/angular-fontawesome": "^0.6.0",
"@fortawesome/fontawesome-svg-core": "^1.2.15",
"@fortawesome/free-brands-svg-icons": "^5.7.2",
"@fortawesome/free-solid-svg-icons": "^5.7.2",
"@fullcalendar/core": "^4.3.1",
"@nguniversal/express-engine": "^9.0.0",
"@ngx-share/button": "^7.1.2",
"@ngx-share/buttons": "^7.1.2",
"@ngx-share/core": "^7.1.2",
"bootstrap": "^4.3.1",
"chart.js": "^2.9.3",
"core-js": "^2.6.5",
"express": "^4.17.1",
"jquery": "^3.3.1",
"primeicons": "^2.0.0",
"primeng": "^9.0.0-rc.4",
"quill": "^1.3.6",
"rxjs": "~6.5.4",
"tslib": "^1.10.0",
"webpack-node-externals": "^1.7.2",
"zone.js": "~0.10.2"
},
"devDependencies": {
"@angular-devkit/build-angular": "~0.900.1",
"@angular/cli": "~9.0.1",
"@angular/compiler-cli": "~9.0.0",
"@angular/language-service": "~9.0.0",
"@nguniversal/builders": "^9.0.0",
"@types/express": "^4.17.0",
"@types/jasmine": "~3.3.10",
"@types/jasminewd2": "~2.0.3",
"@types/node": "^12.11.1",
"codelyzer": "^5.1.2",
"jasmine-core": "~3.3.0",
"jasmine-spec-reporter": "~4.2.1",
"karma": "~4.0.1",
"karma-chrome-launcher": "~2.2.0",
"karma-coverage-istanbul-reporter": "~2.0.5",
"karma-jasmine": "~2.0.1",
"karma-jasmine-html-reporter": "^1.4.0",
"protractor": "~5.4.0",
"ts-loader": "^4.0.0",
"ts-node": "~8.0.3",
"tslint": "~5.14.0",
"typescript": "~3.7.5",
"webpack-cli": "^3.1.0"
}
}
我怀疑 tsconfig.json 文件 baseUrl 是问题所在。您可以将其删除并在本地服务器中尝试一次。
这些是我使用 .net core 3.1 获得的 Angular 9 的步骤,所有这些都使用 Angular Universal 和 NodeIIS。
这可以帮助您入门 https://medium.com/@MarkPieszak/angular-universal-server-side-rendering-deep-dive-dc442a6be7b7。
注意我的最终项目不是 pre-rendered 它是 ssr 意味着某些页面有参数并且会随着时间的推移不断增长所以我不能 pre-render (缓存)某些页面它需要是 ssr .
我在 VS .net 核心中创建了一个 angular 应用程序并添加了通用。然后,在您可以 运行 ng serve 之后,请确保您可以 运行 在您的 angular 9 应用程序上执行以下操作。
- ng 运行 YOURAPPNAME:server:production --bundleDependencies true
- npm 运行 build:ssr
- npm 运行 serve:ssr
请注意确保您的代码适用于 ssr
修复 Angular JS 代码以适用于 SSR
- 更改本地存储的所有代码,例如如果
(isPlatformBrowser(this.platformId)) { }
- Google 全局['window'],
全局 ['document'] 和 app.use(cors()); server.ts 如你所愿
也需要设置这些。
服务器内容
在此处查看用于使 cors 工作的 c# .net 核心的解决方案 https://www.codeproject.com/Questions/5162494/Currently-I-am-working-on-angular-and-web-API-NET
按照上面的步骤删除——在这两行上,我使用发布的向导直接从 VS 到 IIS(它复制根目录中的一个文件夹,其中包含大量 Dll 等,一个包含 dist 的 ClientApp 文件夹和 node_modules) See publish 会为您完成所有艰苦的工作。如果您手动执行此操作,则可以从 debug...\publish 文件夹中复制全部内容。我什至不必摆弄 web.config 文件。发布后,我将域设置为指向 IIS 中的该文件夹。
安装 nodeiis 和 运行 npm 运行 serve:ssr 这将处理所有网站请求并转换为 ssr - 最好使用 pm2 (npm i pm2)
我在VS net core SPA启动中添加了这个
app.UseSpa(spa =>
{
spa.UseProxyToSpaDevelopmentServer("http://localhost:4000");
spa.Options.SourcePath = "ClientApp";
if (env.IsDevelopment())
{
spa.UseAngularCliServer(npmScript: "start");
}
});
在本地测试时我可以 运行 ng serve 并将 localhost 更改为 4200 然后当我 运行 npm 运行 serve:ssr 我可以更改为 4000 你可以刷新您的网站并查看其呈现方式有何不同
备注
这是我的前端代码,展示了我如何设置 resolve 以确保为 seo 正确加载内容 - Angular SSR Refresh/Load Page Pending Api Call
如果遇到预检错误,您可能还需要设置 angular side cors - https://medium.com/@alexxsanya/fix-cors-in-angular-8-using-cli-proxy-configuration-787589f06f94
我希望他们能让事情变得更简单,但在所有这些步骤之后,它可以正常工作并且网站会立即加载。当我使用视图源代码查看页面标题时,它是索引标题,但 Google 网站管理员已阅读所有页面上的正确标题。
备选方案
有一个名为 prerender.io 的付费服务,我打算使用它们每月花费大约 9 欧元,并且实施起来很容易 https://github.com/dingyuliang/prerender-dotnet/wiki/Prerender-Middleware-for-ASP.NET-Core。我将它用于一个 angularJS 项目并且工作正常。
希望这些步骤能帮助别人,因为我花了很长时间才开始工作
-确保在您的“Document Root”中选择浏览器文件夹而不是服务器。
-在您的 server.ts 中删除此条件:
if (moduleFilename === __filename || moduleFilename.includes('iisnode')) {
run();
}
并将其替换为:
run();
然后从 plesk GUI 重新启动您的 node.js 应用程序
我将我的 Angular 8 项目更新为 Angular 9。我 运行 命令“npm 运行 build: ssr && npm 运行 serve: ssr”。它运行完美,在“dist”文件夹中创建了“browser”和“server”文件夹。
我将这些文件上传到我的服务器(Plesk 面板)。我将其设置为使用 node.js.
读取服务器/main.js 文件失败,因为在“dist”文件夹中找不到“browser / index.html”。我创建了一个名为“dist”的文件夹,并将“browser”文件夹移动到其中。
然后超时了,我的项目没有运行。我运行Node.js在开发模式下看到报错,但是说是超时。
server.ts
import 'zone.js/dist/zone-node';
import { ngExpressEngine } from '@nguniversal/express-engine';
import * as express from 'express';
import { join } from 'path';
import { AppServerModule } from './src/main.server';
import { APP_BASE_HREF } from '@angular/common';
import { existsSync } from 'fs';
const domino = require('domino'); // kb
const fs = require('fs'); // kb
const template = fs.readFileSync('dist/browser/index.html').toString(); // kb
const win = domino.createWindow(template); // kb
global['window'] = win;
global['document'] = win.document;
global["branch"] = null;
global["object"] = win.object;
global['DOMTokenList'] = win.DOMTokenList;
global['Node'] = win.Node;
global['Text'] = win.Text;
global['HTMLElement'] = win.HTMLElement;
global['navigator'] = win.navigator;
// The Express app is exported so that it can be used by serverless Functions.
export function app() {
const server = express();
const distFolder = join(process.cwd(), 'dist/browser');
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,
}));
server.set('view engine', 'html');
server.set('views', distFolder);
// Example Express Rest API endpoints
// app.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() {
const port = process.env.PORT || 4000;
// Start up the Node server
const server = app();
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 || '';
if (moduleFilename === __filename || moduleFilename.includes('iisnode')) {
run();
}
export * from './src/main.server';
主.server.ts
import { enableProdMode } from '@angular/core';
import { environment } from './environments/environment';
if (environment.production) {
enableProdMode();
}
export { AppServerModule } from './app/app.server.module';
export {ngExpressEngine} from '@nguniversal/express-engine';
export { renderModule, renderModuleFactory } from '@angular/platform-server';
tsconfig.app.json
{
"extends": "./tsconfig.json",
"compilerOptions": {
"outDir": "./out-tsc/app",
"types": []
},
"files": [
"src/main.ts",
"src/polyfills.ts"
],
"include": [
"src/**/*.d.ts"
]
}
tsconfig.server.json
{
"extends": "./tsconfig.app.json",
"compilerOptions": {
"module": "commonjs",
"outDir": "./out-tsc/app-server",
"baseUrl": ".",
"types": [
"node"
]
},
"angularCompilerOptions": {
"entryModule": "./src/app/app.server.module#AppServerModule"
},
"files": [
"src/main.server.ts",
"server.ts"
]
}
tsconfig.json
{
"compileOnSave": false,
"compilerOptions": {
"baseUrl": "./",
"outDir": "./dist/out-tsc",
"sourceMap": true,
"declaration": false,
"module": "esnext",
"moduleResolution": "node",
"emitDecoratorMetadata": true,
"experimentalDecorators": true,
"importHelpers": true,
"target": "es5",
"typeRoots": [
"node_modules/@types"
],
"lib": [
"es2018",
"dom"
]
}
}
package.json
{
"name": "kodumunblogu",
"version": "0.0.0",
"scripts": {
"ng": "ng",
"start": "ng serve",
"build": "ng build",
"test": "ng test",
"lint": "ng lint",
"e2e": "ng e2e",
"compile:server_bak": "webpack --config webpack.server.config.js --progress --colors",
"build:ssr_bak": "npm run build:client-and-server-bundles && npm run compile:server",
"serve:ssr_bak": "node dist/server",
"build:client-and-server-bundles_bak": "ng build --prod --aot && ng run kodumunblogu:server:production --bundleDependencies all",
"dev:ssr": "ng run kodumunblogu:serve-ssr",
"serve:ssr": "node dist/server/main.js",
"build:ssr": "ng build --prod && ng run kodumunblogu:server:production",
"prerender": "ng run kodumunblogu:prerender",
"postinstall": "ngcc"
},
"private": true,
"dependencies": {
"@angular/animations": "^9.0.0",
"@angular/cdk": "^9.0.0",
"@angular/common": "~9.0.0",
"@angular/compiler": "~9.0.0",
"@angular/core": "~9.0.0",
"@angular/forms": "~9.0.0",
"@angular/material": "^9.0.0",
"@angular/platform-browser": "~9.0.0",
"@angular/platform-browser-dynamic": "~9.0.0",
"@angular/platform-server": "^9.0.0",
"@angular/router": "~9.0.0",
"@fortawesome/angular-fontawesome": "^0.6.0",
"@fortawesome/fontawesome-svg-core": "^1.2.15",
"@fortawesome/free-brands-svg-icons": "^5.7.2",
"@fortawesome/free-solid-svg-icons": "^5.7.2",
"@fullcalendar/core": "^4.3.1",
"@nguniversal/express-engine": "^9.0.0",
"@ngx-share/button": "^7.1.2",
"@ngx-share/buttons": "^7.1.2",
"@ngx-share/core": "^7.1.2",
"bootstrap": "^4.3.1",
"chart.js": "^2.9.3",
"core-js": "^2.6.5",
"express": "^4.17.1",
"jquery": "^3.3.1",
"primeicons": "^2.0.0",
"primeng": "^9.0.0-rc.4",
"quill": "^1.3.6",
"rxjs": "~6.5.4",
"tslib": "^1.10.0",
"webpack-node-externals": "^1.7.2",
"zone.js": "~0.10.2"
},
"devDependencies": {
"@angular-devkit/build-angular": "~0.900.1",
"@angular/cli": "~9.0.1",
"@angular/compiler-cli": "~9.0.0",
"@angular/language-service": "~9.0.0",
"@nguniversal/builders": "^9.0.0",
"@types/express": "^4.17.0",
"@types/jasmine": "~3.3.10",
"@types/jasminewd2": "~2.0.3",
"@types/node": "^12.11.1",
"codelyzer": "^5.1.2",
"jasmine-core": "~3.3.0",
"jasmine-spec-reporter": "~4.2.1",
"karma": "~4.0.1",
"karma-chrome-launcher": "~2.2.0",
"karma-coverage-istanbul-reporter": "~2.0.5",
"karma-jasmine": "~2.0.1",
"karma-jasmine-html-reporter": "^1.4.0",
"protractor": "~5.4.0",
"ts-loader": "^4.0.0",
"ts-node": "~8.0.3",
"tslint": "~5.14.0",
"typescript": "~3.7.5",
"webpack-cli": "^3.1.0"
}
}
我怀疑 tsconfig.json 文件 baseUrl 是问题所在。您可以将其删除并在本地服务器中尝试一次。
这些是我使用 .net core 3.1 获得的 Angular 9 的步骤,所有这些都使用 Angular Universal 和 NodeIIS。
这可以帮助您入门 https://medium.com/@MarkPieszak/angular-universal-server-side-rendering-deep-dive-dc442a6be7b7。
注意我的最终项目不是 pre-rendered 它是 ssr 意味着某些页面有参数并且会随着时间的推移不断增长所以我不能 pre-render (缓存)某些页面它需要是 ssr .
我在 VS .net 核心中创建了一个 angular 应用程序并添加了通用。然后,在您可以 运行 ng serve 之后,请确保您可以 运行 在您的 angular 9 应用程序上执行以下操作。
- ng 运行 YOURAPPNAME:server:production --bundleDependencies true
- npm 运行 build:ssr
- npm 运行 serve:ssr
请注意确保您的代码适用于 ssr
修复 Angular JS 代码以适用于 SSR
- 更改本地存储的所有代码,例如如果 (isPlatformBrowser(this.platformId)) { }
- Google 全局['window'], 全局 ['document'] 和 app.use(cors()); server.ts 如你所愿 也需要设置这些。
服务器内容
在此处查看用于使 cors 工作的 c# .net 核心的解决方案 https://www.codeproject.com/Questions/5162494/Currently-I-am-working-on-angular-and-web-API-NET
按照上面的步骤删除——在这两行上,我使用发布的向导直接从 VS 到 IIS(它复制根目录中的一个文件夹,其中包含大量 Dll 等,一个包含 dist 的 ClientApp 文件夹和 node_modules) See publish 会为您完成所有艰苦的工作。如果您手动执行此操作,则可以从 debug...\publish 文件夹中复制全部内容。我什至不必摆弄 web.config 文件。发布后,我将域设置为指向 IIS 中的该文件夹。
安装 nodeiis 和 运行 npm 运行 serve:ssr 这将处理所有网站请求并转换为 ssr - 最好使用 pm2 (npm i pm2)
我在VS net core SPA启动中添加了这个
app.UseSpa(spa => { spa.UseProxyToSpaDevelopmentServer("http://localhost:4000"); spa.Options.SourcePath = "ClientApp"; if (env.IsDevelopment()) { spa.UseAngularCliServer(npmScript: "start"); } });
在本地测试时我可以 运行 ng serve 并将 localhost 更改为 4200 然后当我 运行 npm 运行 serve:ssr 我可以更改为 4000 你可以刷新您的网站并查看其呈现方式有何不同
备注
这是我的前端代码,展示了我如何设置 resolve 以确保为 seo 正确加载内容 - Angular SSR Refresh/Load Page Pending Api Call
如果遇到预检错误,您可能还需要设置 angular side cors - https://medium.com/@alexxsanya/fix-cors-in-angular-8-using-cli-proxy-configuration-787589f06f94
我希望他们能让事情变得更简单,但在所有这些步骤之后,它可以正常工作并且网站会立即加载。当我使用视图源代码查看页面标题时,它是索引标题,但 Google 网站管理员已阅读所有页面上的正确标题。
备选方案
有一个名为 prerender.io 的付费服务,我打算使用它们每月花费大约 9 欧元,并且实施起来很容易 https://github.com/dingyuliang/prerender-dotnet/wiki/Prerender-Middleware-for-ASP.NET-Core。我将它用于一个 angularJS 项目并且工作正常。
希望这些步骤能帮助别人,因为我花了很长时间才开始工作
-确保在您的“Document Root”中选择浏览器文件夹而不是服务器。
-在您的 server.ts 中删除此条件:
if (moduleFilename === __filename || moduleFilename.includes('iisnode')) {
run();
}
并将其替换为:
run();
然后从 plesk GUI 重新启动您的 node.js 应用程序