如何在初始化之前以编程方式设置Angular基础路径?

4

背景/目标

  • 我有一个带有多个环境的应用程序,每个环境存储在CDN的子文件夹中。
    • 例如: cdn.com/dev, cdn.com/test
  • 我的目标是加载开发站点(cdn.com/dev/index.html)并从 /dev/ 路径加载相应的资源。

⚠️ℹ️ 我们正在使用一种“构建一次,多次部署”的策略,因此我无法在构建过程或类似过程中传递baseHref。

问题

到目前为止,无论我尝试什么,cdn.com/dev/index.html 都会尝试从根目录 (cdn.com/script.js) 而不是子文件夹 (cdn.com/dev/script.js) 加载脚本。

我尝试了什么

❌ 尝试1: 设置APP_BASE_HREF 并将其用作提供者

我在 index.html 中设置了一个 window 变量:

<script>
    window['base-href'] = window.location.pathname;
    console.log(window['base-href']);
</script>

接着我尝试使用该值通过提供者来传递给app.module.ts中的APP_BASE_HREF

import { APP_BASE_HREF } from '@angular/common';
let baseHref = (window as { [key: string]: any })["base-href"] as string;

if (!baseHref.endsWith("/")) {

  baseHref += "/";

}

// I only updated the providers array below
@NgModule({
  declarations: [
    AppComponent,
    ComingSoonListComponent
  ],
  imports: [
    BrowserModule,
    AppRoutingModule,
    BrowserAnimationsModule
  ],
  providers: [
    {
      provide: APP_BASE_HREF,
      useValue: baseHref
    }
  ],
  bootstrap: [AppComponent]
})

第一次尝试似乎没有起作用。

❌ 尝试二:稍微不太天真的尝试

我从index.html页面中删除了base标签,以防它覆盖了其他东西。

在index.html中,我调用脚本来捕获路径名:

<script>
    window['base-href'] = window.location.pathname;
    console.log(window['base-href']);
  </script>
</

app.module.ts中,我创建了一个工厂方法来捕获窗口值并使用它:
const baseHrefFactory = () => {
  let baseHref = (window as { [key: string]: any })["base-href"] as string;
  if (!baseHref.endsWith("/")) {

    baseHref += "/";

  }
  console.log('TS: using baseHref of:', baseHref);
  return baseHref;
}

// Note I just use the factory method below

@NgModule({
  declarations: [
    AppComponent,
    ComingSoonListComponent,
  ],
  imports: [
    BrowserModule,
    AppRoutingModule,
    BrowserAnimationsModule,
  ],
  providers: [{ provide: APP_BASE_HREF, useFactory: baseHrefFactory }],
  bootstrap: [AppComponent],
})
export class AppModule { }
  • 我可以看到窗口值已被设置(通过控制台日志记录)
  • 似乎其他文件在我们能够以编程方式触发APP_BASE_HREF工厂之前尝试加载,并尝试从根路径而不是我设置的路径进行加载。 在本地运行时,它似乎运行良好,但可能仅因为路径自然为/

❌ 尝试3:直接在HTML中设置base href

<head>标签中的index.html文件中,在创建base标签后:

  <script>
    var baseHref = window.location.pathname;
    if (!baseHref.endsWith("/")) {
      baseHref += "/";
    }

    console.log("setting a baseHref of:", baseHref);
    document.getElementsByTagName("base")[0].href = baseHref;
  </script>
  • ✅ 我添加的 index.html 脚本正在运行(我看到了控制台日志)
  • ✅ 适当的基础路径被选中(/dev/ - 我在控制台日志中看到了它)
  • ✅ 基础标签被正确设置(我可以通过浏览器开发工具检查到这一点)
  • ❌ 浏览器请求的URL不包含 /dev/
    • 实际URL: cdn.com/script.js
    • 期望URL: cdn.com/dev/script.js

❓(尚未尝试)我需要在部署时修改吗?

我是否需要在部署脚本中物理修改 index.html,以使用正确的 <base> 标记中的 href?那将是令人惊讶的,但我想这也是可能的。似乎这肯定是一个答案;只希望它不是唯一的答案。

问题

如何在运行时修改基础路径,以便 Angular 从正确的 URL 下载资源,包括 CDN 端点的子路径?我能否在运行时执行此操作,还是需要在部署时以编程方式设置?


你是如何提供index.html的服务的?使用ASP.NET?PHP?你是否愿意使用服务器端渲染来提供基本的index.html服务? - Boluc Papuccuoglu
@BolucPapuccuoglu 很好的问题。在这种情况下,应用程序正在部署到静态存储中;没有服务器端呈现能力。如果这意味着我需要在部署过程中更新一些内容,那没关系;只是似乎我应该能够在不这样做的情况下实现我的目标。 - SeanKilleen
1
我现在面临完全相同的问题,以下 hack 方法可行,即设置 <base href="${SOME_ENV_VAR}"> 然后在我的 dockerfile 中将 index.html 复制到 index.html.template,并作为提供应用程序的一部分运行 envsubst 来替换环境变量为正确的路径。 - A.Rashad
2个回答

1

你可以将base href设置为相对路径吗?

ng build --base-href ./

这将相对于您的索引文件加载文件。

嗨,马修!正如我在最初的问题中所指出的那样,我处于“一次构建,多次部署”的情况下。构建在提交到主分支时运行。然后需要将这些构件部署到开发、测试、QA和生产环境中。因此,在构建时无法设置基本 href。 - SeanKilleen
或许我误解了。您是在建议设置运行--base-href ./,这样就可以允许相对基础href,无论它部署到哪里,并且将根据index.html页面进行处理? - SeanKilleen
1
没错,那正是我想表达的意思。 - Mathew Berg
--base-href ./ 传递给构建除了设置 base 标签的 href 属性之外,还有其他作用吗?我明确将基础标签的 href 属性设置为“./”,但仍然看到资源错误地尝试从根路径加载的问题。 - SeanKilleen
我已经尝试更新构建命令并修改基础标签本身,但都没有成功。例如在index.html中的main.js等资源仍然从CDN的根目录加载。 - SeanKilleen

0

据我所知,修改基础 href 的唯一方法是:

  • ❌ 使用 angular.json 文件 -- 但这只适用于构建时,因此不适用于构建一次,部署多个场景
  • ❌ 使用命令行,例如 ng build --base-href="./dev" -- 但同样,这只适用于构建时,并且不适用于所有在该构建之后部署的环境。
  • ❌ 使用 index.html<base> 标签的内容。但同样,在部署到多个地方时,这无法满足我的需求。

因此,就我所能判断的情况而言,对于我来说,在此情况下的解决方案将是让部署脚本修改 index.html,以确保其中的 base 标签与它被部署到的环境相匹配。

我编写了一个微小的 PowerShell 脚本,可以在部署过程中使用,如果对其他人方便的话:

<#
.SYNOPSIS
    Modifies an HTML file to set the base href with the appropriate CDN endpoint.
.EXAMPLE
    # Can use relative path to file
    .\TransformBaseHref.ps1 -PathToHtmlFile ..\code\webapp\dist\webapp\index.html -CDNPath "EnvironmentName"
.EXAMPLE
    # Can use full path to file
    .\TransformBaseHref.ps1 -PathToHtmlFile C:\path\to\index.html -CDNPath "EnvironmentName"
.EXAMPLE
    # When index.html is in Current directory
    .\TransformBaseHref.ps1 -PathToHtmlFile index.html -CDNPath "EnvironmentName"
#>
Param (
    [Parameter(Mandatory = $true, HelpMessage = "The path to the html file to be updated.")]
    [string] $PathToHtmlFile, 
    [Parameter(Mandatory = $true, HelpMessage = "The name of the CDN subfolder, e.g. 'develop' for the development environment")]
    [string] $CDNPath
)

((Get-Content $PathToHtmlFile).Replace("<base href=""/"">", "<base href=""/$CDNPath/"">")) | Set-Content $PathToHtmlFile

我已向Angular团队提交了一个功能请求,以寻找创建更好体验的方法:https://github.com/angular/angular/issues/45174如果我错过了什么重要的东西,并且这是可能的,请告诉我。 - SeanKilleen
接受了自己的答案,因为在 https://github.com/angular/angular-cli/issues/22758 上的 Angular 响应证实了它。 - SeanKilleen

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接