【问题标题】:Angular 9 universal deployment woesAngular 9 通用部署问题
【发布时间】:2020-08-17 19:53:11
【问题描述】:

老实说,我觉得这很烦人,以至于这种情况一直在变化。 我在这里用更简单的 Angular 版本解决了这个问题:

Deploying Angular Universal to Azure

但现在这已经过时了。 不再生成 server.js,而是您必须修改您的 web.config 以指向 ma​​in.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-name/server/main.js,我会收到错误消息。它返回这个:

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

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

import { Blob } from 'blob-polyfill';

global['Blob'] = Blob;

但这似乎没有做任何事情。错误仍然存​​在。 有谁知道我必须做什么?


更新

我决定今天再试一次。 我运行 npm build:ssr 并将 server & browser 文件夹复制到本地网络服务器并运行 node server/main.js 并抱怨它在 dist/my- 找不到 index.html 文件project/browser/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

所以据我所知,它应该可以正常工作。 有谁知道为什么不是?

【问题讨论】:

  • 嗨,r3plica。几天以来我遇到了完全相同的问题......你能取得任何进展或给我任何关于如何部署我们的应用程序的提示吗? (烦人是一个非常礼貌的词,顺便说一句)......

标签: angular azure angular-universal


【解决方案1】:

所以,我再次设法解决了这个问题。我倾注了几个小时,它开始让我发疯。我决定建立一个本地 Web 服务器 (iss) 并尽我所能。最后,正是这一点救了我,因为 iisnode 正在记录错误,我可以看到问题所在。

如果我在您执行 npm run build:ssr 并更新 webconfig 以像 &lt;add name="iisnode" path="dist/example-project/server/main.js" verb="*" modules="iisnode"/&gt; 一样指向 ma​​in.js 时保持文件夹结构不变,您会收到类似以下的错误:

错误:无法在视图目录“C:\inetpub\wwwroot\dist\example-project\server\dist\example-project\browser”中查找视图“index”

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

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

&lt;add name="iisnode" path="main.js" verb="*" modules="iisnode"/&gt;.

为了完整起见,这是我的完整 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>

这是我的天蓝色 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

【讨论】:

  • 我遇到了这个问题,并在过去几天卡住了。你能分享任何想法吗?什么是你的 azure app 服务类型,它是基于 Windows 的 ASP.Net Stack 4.7 还是其他的东西。
  • stackoverflow.com/questions/63932339/… 我已经问过问题了。你能回复我吗。
  • 我做了同样的事情,但这不适用于延迟加载的模块(我收到错误 D:\home\site\wwwroot\main.js - D:\Program Files\iisnode\interceptor .js 错误:找不到模块 './21.js') @r3plica 你有这样的问题吗?
  • 最近我做到了。我更改了“复制 main.js”任务以包含所有脚本(将内容更改为 ** 而不是 main.js)。如果您查看 dist/{app-name}/server 您会注意到所有延迟加载的模块都有 js 文件
【解决方案2】:

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

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

    -Handler iisnode -NodeStartFile dist/server/main.js -appType 节点

  • [server.ts] - 考虑到这一点,还要考虑根据您的运行时环境设置浏览器路径,这样如果您在生产环境中,它应该是 ../browser

  • [server.ts] - server.ts 中的订单事项。 如果您遇到浏览器 API 问题,这是因为“import { AppServerModule } from './ma​​in.server';” 必须放置在 多米诺声明之后。

这是一个 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 次部署来解决这些问题。

最后的话:如果您在 Angular 8 迁移之后来到这里,我很乐意为您提供帮助并给您很好的提示,但老实说,请按照指南并仔细阅读文档。此外,如果使用 Azure DevOps 管道,则应考虑使用 npm 缓存。我们的 as 很大,现在我们在每个构建过程中节省了超过 12 分钟(这是一个巨大的时间,不是吗?)请随时与我联系。

胡安

【讨论】:

  • 对于 Angular 的每个版本都必须重做一些事情真的很烦人吗:/ 我会试一试(我觉得部署应该很容易,并且在以后的版本中更容易) ,而不是更难......)
  • 好吧,一旦我了解了一些事情,它就会变得更容易。例如,我的应用程序使用 i18n,我必须构建 n 次并部署 n 个版本,每个支持的语言环境一个,现在我们的构建时间减少了很多......我鼓励 Angular 开发团队考虑大多数项目使用 SSR 还需要使用浏览器相关的 API,以便必须审查必须放在全局声明之后的 import 语句...
猜你喜欢
  • 1970-01-01
  • 2017-08-23
  • 2020-09-11
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2020-11-06
  • 2020-06-21
相关资源
最近更新 更多