Angular 9通用部署难题



老实说,我觉得这种情况一直在变化,这太烦人了。我在Angular的easlier版本中解决了这个问题:

将Angular Universal部署到Azure

但现在这已经过时了。不再生成server.js,而是必须修改web.config以指向main.js,这听起来是一个改进。我将yaml更新为:

pool:
name: Azure Pipelines
steps:
- task: gittools.gitversion.gitversion-task.GitVersion@5
displayName: GitVersion
- task: NodeTool@0
displayName: 'Use Node 12.x'
inputs:
versionSpec: 12.x
- task: Npm@1
displayName: 'npm install angular cli'
inputs:
command: custom
verbose: false
customCommand: 'install @angular/cli -g'
- task: Npm@1
displayName: 'npm install'
inputs:
verbose: false
- task: Npm@1
displayName: 'npm build'
inputs:
command: custom
verbose: false
customCommand: 'run build:ssr'
- task: CopyFiles@2
displayName: 'Copy dist files to staging'
inputs:
SourceFolder: '$(Build.SourcesDirectory)/dist'
TargetFolder: '$(Build.ArtifactStagingDirectory)/app/dist'
- task: AzureRmWebAppDeployment@4
displayName: 'Azure App Service Deploy: app-name'
inputs:
azureSubscription: 'Pay-As-You-Go (f61dc7cf-0ca2-4982-bbe7-9b6527c2962b)'
WebAppName: r3plica
packageForLinux: '$(Build.ArtifactStagingDirectory)/app'
WebConfigParameters: '-Handler iisnode -NodeStartFile dist/app-name/server/main.js -appType node'

应该是这样,但当然,事情并没有那么简单。现在,如果我运行node-dist/app/server/main.js,我会得到一个错误。它返回的是:

ReferenceError:Blob未在createBase64WorkerFactory中定义(D:\home\site\wwwroot\dist\app name\server\main.js:1418371)

所以我环顾四周,有人建议我安装npm install --save-dev blob-polyfill,然后编辑服务器.ts文件:

import { Blob } from 'blob-polyfill';
global['Blob'] = Blob;

但这似乎并没有起到任何作用。错误仍然存在。有人知道我该做什么吗?


更新

我决定今天再试试。我运行了CCD_ 2并复制了服务器&browser文件夹到本地Web服务器并运行node server/main.js,它抱怨在dist/my-project/browser/index.html上找不到index.html文件,这对我有帮助。因此,我将整个dist文件夹复制到wwwroot并运行node dist/my-project/server/main.js,它就工作了。

所以我更新了我的管道来做同样的事情。我验证了它实际上复制了整个dist文件夹,然后我将web.config复制到根目录。我的web.config文件如下所示:

<configuration>
<system.web>
<customErrors mode="Off" />
</system.web>
<system.webServer>
<staticContent>
<remove fileExtension=".woff2" />
<mimeMap fileExtension=".woff2" mimeType="font/woff2" />
</staticContent>
<handlers>
<!-- Indicates that the server.js file is a node.js site to be handled by the iisnode module -->
<add name="iisnode" path="dist/my-project/server/main.js" verb="*" modules="iisnode"/>
</handlers>
<httpErrors errorMode="Detailed"></httpErrors>
</system.webServer>
</configuration>

但当我试图加载我的网站时,它只会给我一个错误:

HTTP错误403.14-禁止

未为请求的URL配置默认文档,并且服务器上未启用目录浏览。

这真的很烦人。我看了一下https://example.scm.azurewebsites.net然后转到调试控制台,键入node dist/my-project/server/main.js,返回时显示:

Node Express服务器正在侦听http://localhost:4000

所以据我所知,它应该运行良好。有人知道为什么没有吗?

所以,我再次设法解决了这个问题。我花了好几个小时才开始抓狂。我决定建立一个本地网络服务器(iss),并尽我所能。最后,正是这一点救了我,因为iisnode正在记录错误,我可以看到哪里出了问题。

如果我在执行npm run build:ssr并更新webconfig以指向main.js(如<add name="iisnode" path="dist/example-project/server/main.js" verb="*" modules="iisnode"/>)时保持文件夹结构不变,则会出现类似于以下的错误:

错误:无法查找视图"索引";在视图目录"中;C: \inetpub\wwwroot\dist\example project\server\dist\eExample project\browser">

正如您从错误中看到的,它使用了main.js所在位置的相对路径。从这里你可能可以看到我是如何解决我的问题的。

我更新了我的任务以包括一个新的副本,并将main.js复制到根目录,并将我的web.config更新为:

<add name="iisnode" path="main.js" verb="*" modules="iisnode"/>

为了完整起见,这里是我的完整的web.config

<?xml version="1.0" encoding="utf-8"?>
<configuration>
<system.webServer>
<webSocket enabled="false" />
<handlers>
<add name="iisnode" path="main.js" verb="*" modules="iisnode"/>
</handlers>
<rewrite>
<rules>
<rule name="NodeInspector" patternSyntax="ECMAScript" stopProcessing="true">
<match url="^main.js/debug[/]?" />
</rule>
<rule name="StaticContent">
<action type="Rewrite" url="public{REQUEST_URI}"/>
</rule>
<rule name="DynamicContent">
<conditions>
<add input="{REQUEST_FILENAME}" matchType="IsFile" negate="True"/>
</conditions>
<action type="Rewrite" url="main.js"/>
</rule>
<rule name="Angular Routes" stopProcessing="true">
<match url=".*" />
<conditions logicalGrouping="MatchAll">
<add input="{REQUEST_FILENAME}" matchType="IsFile" negate="true" />
<add input="{REQUEST_FILENAME}" matchType="IsDirectory" negate="true" />
</conditions>
<action type="Rewrite" url="/index.html" />
</rule>
</rules>
</rewrite>
<security>
<requestFiltering>
<hiddenSegments>
<remove segment="bin"/>
</hiddenSegments>
</requestFiltering>
</security>
<httpErrors existingResponse="PassThrough" />
</system.webServer>
</configuration>

这是我对azure的xaml:

pool:
name: Azure Pipelines
steps:
- task: gittools.gitversion.gitversion-task.GitVersion@5
displayName: GitVersion
- task: NodeTool@0
displayName: 'Use Node 12.x'
inputs:
versionSpec: 12.x
checkLatest: true
- task: Npm@1
displayName: 'npm install angular cli'
inputs:
command: custom
verbose: false
customCommand: 'install @angular/cli -g'
- task: Npm@1
displayName: 'npm install'
inputs:
verbose: false
- task: Npm@1
displayName: 'npm build'
inputs:
command: custom
verbose: false
customCommand: 'run build:ssr'
- task: CopyFiles@2
displayName: 'Copy dist files to staging'
inputs:
SourceFolder: '$(Build.SourcesDirectory)/dist'
TargetFolder: '$(Build.ArtifactStagingDirectory)/dist'
- task: CopyFiles@2
displayName: 'Copy web.config'
inputs:
SourceFolder: '$(Build.ArtifactStagingDirectory)/dist/example-project/browser'
Contents: web.config
TargetFolder: '$(Build.ArtifactStagingDirectory)'
- task: CopyFiles@2
displayName: 'Copy main.js'
inputs:
SourceFolder: '$(Build.ArtifactStagingDirectory)/dist/example-project/server'
Contents: main.js
TargetFolder: '$(Build.ArtifactStagingDirectory)'
- task: AzureRmWebAppDeployment@4
displayName: 'Azure App Service Deploy: example-project'
inputs:
azureSubscription: 'Your Subscription'
WebAppName: 'example-project'
packageForLinux: '$(Build.ArtifactStagingDirectory)'
enableCustomDeployment: true
RemoveAdditionalFilesFlag: true

对于任何面临这些问题的人,我刚刚解决了它,这是我们的解决方案,但事实很少:

  • [Web.config]节点上下文,我指的是进程工作目录,在iisnode中不同,PWD是目标文件路径,这意味着如果你的main.js在dist/server/main.js中,那么相对于浏览器的路径将不是dist/browser/,而是/浏览器/
  • 考虑一下,在部署过程中,您必须根据这个新结构生成Web.config

    -处理程序iisnode-NodeStartFiledist/server/main.js-appType节点

  • [server.ts]-考虑到这一点,还应考虑根据您的运行时环境设置浏览器路径,以便在生产中使用/浏览器

  • [server.ts]-服务器.ts中的订单问题。如果您面对浏览器API问题,是因为"从'./main.server'导入{AppServerModule};">必须放置在多米诺骨牌声明之后。

这是一个在server.ts上的工作示例,该服务器还根据带有区域设置字符串的url请求使用i18n重定向(现在我也解决了这个i18n问题,我可以告诉你值得阅读文档)。

/***************************************************************************************************
* Load `$localize` onto the global scope - used if i18n tags appear in Angular templates.
*/
import { APP_BASE_HREF } from '@angular/common';
import '@angular/localize/init';
import { ngExpressEngine } from '@nguniversal/express-engine';
import * as express from 'express';
import { existsSync } from 'fs';
import { join } from 'path';
import 'zone.js/dist/zone-node';
import { environment } from './environments/environment';
// THIS FIX MOST OF THE COMMON ISSUES WITH SSR:
// FIRST SET THE BROWSER PATH ACCORDING TO RUNTIME ENVIRONMENT
let browserPath;
if (environment.production) {
browserPath = '../browser';
} else {
browserPath = 'dist/browser';
}
const enDistFolder = join(process.cwd(), browserPath + '/en');
// Emulate browser APIs
const domino = require('domino');
const fs = require('fs');
const templateA = fs.readFileSync(join(enDistFolder, 'index.html')).toString();
const win = domino.createWindow(templateA);
console.log('win');
win.Object = Object;
console.log('Object');
win.Math = Math;
console.log('Math');
global['window'] = win;
global['document'] = win.document;
global['Event'] = win.Event;
console.log('declared Global Vars....');
/****************************************************/   
/** NOTE THIS: I need to avoid sorting this line */
// USE CTRL+P -> SAVE WITHOUT FORMATTING
import { AppServerModule } from './main.server';
/****************************************************/
// The Express app is exported so that it can be used by serverless Functions.
export function app() {
const server = express();
const indexHtml = existsSync(join(browserPath, 'index.original.html')) ? 'index.original.html' : 'index.html';
// 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', browserPath);
// Example Express Rest API endpoints
// server.get('/api/**', (req, res) => { });
// Serve static files from /browser
server.get('*.*', express.static(browserPath, {
maxAge: '1y'
}));
server.use('/robots.txt', express.static('/en/robots.txt'));
server.use('/ads.txt', express.static('/en/ads.txt'));
// THE ORIGINAL Universal Requests handler
// // // All regular routes use the Universal engine
// // server.get('*', (req, res) => {
// //   res.render(indexHtml, { req, providers: [{ provide: APP_BASE_HREF, useValue: req.baseUrl }] });
// // });
// OUR i18n REQUESTS HANDLER
// All regular routes use the Universal engine
server.get('*', (req, res) => {
// this is for i18n
const supportedLocales = ['en', 'es'];
const defaultLocale = 'es';
const matches = req.url.match(/^/([a-z]{2}(?:-[A-Z]{2})?)//);
// check if the requested url has a correct format '/locale' and matches any of the supportedLocales
const locale = (matches && supportedLocales.indexOf(matches[1]) !== -1) ? matches[1] : defaultLocale;
res.render(`${locale}/index.html`, { req });
});
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 './main.server';

我仍然需要在这个代码和我们的应用程序中做一些工作(SSR和oauth问题,另一个有趣的话题),但我想分享它,因为我们花了将近20次部署来解决这些问题。

最后一句话:如果你在经历了一次棱角分明的8迁移后来到这里,我很乐意帮助你,并给你很好的提示,但老实说,请遵循指南并仔细阅读文档。此外,如果你正在使用Azure DevOps管道,你应该考虑使用npm缓存。我们的as很大,现在每个构建过程都节省了12分钟以上(这是一个巨大的时间,不是吗?)请随时与我联系。

Juan

最新更新