Angular 2+-动态更改应用程序的基本路径

撒旦

介绍

我知道这个问题已经以许多不同的形式提出。但是,我已经阅读了有关该问题的所有帖子和文章,并且无法解决我们面临的先决条件所带来的问题。因此,我想先描述我们的问题并给出我们的动力,然后再描述我们已经尝试或正在使​​用的不同方法,即使它们不能令人满意。

问题/动机

我们的设置可谓有些复杂。我们的Angular应用程序已部署在多个Kubernetes集群上,并通过不同的路径提供服务。

这些集群本身位于代理之后,Kubernetes本身添加了一个名为Ingress的代理。因此,对我们来说,常见的设置如下所示:

本地

http://本地主机:4200 /

本地集群

https://kubernetes.local/angular-app

发展历程

https://ourcompany.com/proxy-dev/kubernetes/angular-app

分期

https://ourcompany.com/proxy-staging/kubernetes/angular-app

顾客

https://kubernetes.customer.com/angular-app

始终为应用程序提供不同的基本路径。因为这是Kubernetes,所以我们的应用程序已与Docker容器一起部署。

传统上,人们会ng build先编译Angular应用,然后再使用Dockerfile,如下所示:

FROM nginx:1.17.10-alpine

EXPOSE 80

COPY conf/nginx.conf /etc/nginx/conf.d/default.conf
RUN rm -rf /usr/share/nginx/html/*
COPY dist/ /usr/share/nginx/html/

要告诉CLI如何设置base-href以及从何处提供资产,将使用Angular CLI的--base-href--deploy-url选项。

不幸的是,这意味着我们需要为每个要在其中部署应用程序的环境构建一个Docker容器。

此外,在此预构建过程中,我们必须注意设置其他环境变量,例如在environments[.prod].ts每个部署环境中。

当前解决方案

我们当前的解决方案包括在部署期间构建应用程序。通过使用另一个Docker容器(即所谓的initContainer)来工作,该容器在nginx容器启动之前运行。

Dockerfile如下所示:

FROM node:13.10.1-alpine

# set working directory
WORKDIR /app

# add `/app/node_modules/.bin` to $PATH
ENV PATH /app/node_modules/.bin:$PATH

# install and cache app dependencies
COPY package*.json ./

RUN npm install

COPY / /app/

ENV PROTOCOL http
ENV HOST localhost
ENV CONTEXT_PATH /
ENV PORT 8080
ENV ENDPOINT localhost/endpoint
VOLUME /app/dist

# prebuild ES5 modules
RUN ng build --prod --output-path=build
CMD \
  sed -i -E "s@(endpoint: ['|\"])[^'\"]+@\1${ENDPOINT}@g" src/environments/environment.prod.ts \
  && \
  ng build \
  --prod \
  --base-href=${PROTOCOL}://${HOST}:${PORT}${CONTEXT_PATH} \
  --deploy-url=${PROTOCOL}://${HOST}:${PORT}${CONTEXT_PATH} \
  --output-path=build \
  && \
  rm -rf /app/dist/* && \
  mv -v build/* /app/dist/

将这一容器放入一个Kubernetes部署作为initContainer允许该应用与正确的基础HREF,部署-URL建成并根据这个应用程序部署在环境中特定变量被替换。

这种方法除了会产生一些问题之外,其效果很好:

  1. 一次部署需要几分钟
    每次重新部署应用程序时,initContainer都需要运行。ng build当然,这总是运行命令。为了防止容器每次在容器容器中运行ng build先前RUN指令中的命令来缓存它们时就构建ES模块
    尽管如此,用于差异加载的建筑物和Terser等仍在运行,这需要几分钟的时间才能完成。
    如果有水平吊舱自动缩放,那么额外的吊舱将永远可用。
  2. 容器包含代码。这更多是一项策略,但是我们公司不建议在部署时交付代码。至少不是非混淆形式。

由于这两个问题,我们决定将逻辑从Kubernetes / Docker直接移到应用程序本身。

计划的解决方案

经过一番研究,我们偶然发现了APP_BASE_HREFInjectionToken。因此,我们尝试遵循Web上的不同指南,以根据应用程序所部署的环境动态地设置此设置。首先要做的是:

  1. 添加一个名为文件config.json/src/assets/config.json在内容的基础路径(和其他变量)
{
   "basePath": "/angular-app",
   "endpoint": "https://kubernetes.local/endpoint"
}
  1. 我们向该main.ts文件中添加了代码,以window.location.pathname通过父历史记录以递归方式探查当前文件,以查找config.json
export type AppConfig = {
  basePath: string;
  endpoint: string;
};

export const APP_CONFIG = new InjectionToken<AppConfig>('APP_CONFIG');

export async function fetchConfig(): Promise<AppConfig> {
  const pathName = window.location.pathname.split('/');

  for (const index of pathName.slice().reverse().keys()) {

    const path = pathName.slice(0, pathName.length - index).join('/');
    const url = stripSlashes(`${window.location.origin}/${path}/assets/config.json`);
    const promise = fetch(url);
    const [response, error] = await handle(promise) as [Response, any];

    if (!error && response.ok && response.headers.get('content-type') === 'application/json') {
      return await response.json();
    }
  }
  return null;
}

fetchConfig().then((config: AppConfig) => {
  platformBrowserDynamic([{ provide: APP_CONFIG, useValue: config }])
    .bootstrapModule(AppModule)
    .catch(err => console.error('An unexpected error occured: ', err));
});
  1. 里面app.module.tsAPP_BASE_HREF与初始化APP_CONFIG
@NgModule({
  providers: [
  {
    provide: APP_BASE_HREF,
    useFactory: (config: AppConfig) => {
      return config.basePath;
    },
    deps: [APP_CONFIG]
  }
  ]
})
export class AppModule { }

重要提示:我们使用此方法而不是使用,APP_INITIALIZER因为尝试时,的提供者APP_BASE_HREF总是在的提供者之前运行APP_INITIALIZER,因此APP_BASE_HREF始终是未定义的。

不幸的是,这仅在本地有效,而在代理被代理后无效。我们在这里观察到的问题是,当最初由Web服务器提供应用程序而未指定base-href和deploy-url时,该应用程序显然会尝试从'/'(根)加载所有内容。

但是,这也意味着它试图获取又名角脚本main.jsvendor.jsruntime.js从那里,所以没有的我们的代码是实际运行和所有其他资产。

为了解决这个问题,我们稍微修改了代码。

无需让服务器进行角度探测,config.json我们将代码直接放在index.html并内联了。
这样,我们可以找到基本路径,并将html中的所有链接替换为带前缀的链接,以至少加载脚本和其他资产。如下所示:

<body>
  <app-root></app-root>
  <script>
    function addScriptTag(d, src) {
      const script = d.createElement('script');
      script.type = 'text/javascript';
      script.onload = function(){
        // remote script has loaded
      };
      script.src = src;
      d.getElementsByTagName('body')[0].appendChild(script);
    }

    const pathName = window.location.pathname.split('/');

    const promises = [];

    for (const index of pathName.slice().reverse().keys()) {

      const path = pathName.slice(0, pathName.length - index).join('/');
      const url = `${window.location.origin}/${path}/assets/config.json`;
      const stripped = url.replace(/([^:]\/)\/+/gi, '$1')
      promises.push(fetch(stripped));
    }

    Promise.all(promises).then(result => {
      const response = result.find(response => response.ok && response.headers.get('content-type').includes('application/json'));
      if (response) {
        response.json().then(json => {
          document.querySelector('base').setAttribute('href', json.basePath);
          for (const node of document.querySelectorAll('script[src]')) {
            addScriptTag(document, `${json.basePath}/${node.getAttribute('src')}`);
            node.remove();
            window['app-config'] = json;
          }
        });
      }
    });
  </script>
</body>

此外,我们必须对APP_BASE_HREF提供者内部的代码进行如下修改:

useFactory: () => {
  const config: AppConfig = (window as {[key: string]: any})['app-config'];
  return config.basePath;
},
deps: []

现在发生的事情是,它加载页面,用前缀的URL替换源URL,加载脚本,加载应用程序并设置APP_BASE_HREF

路由似乎可以工作,但是所有其他逻辑(如加载语言文件,降价文件和其他资产)都不再起作用。

我认为该--base-href选项实际上设置了,APP_BASE_HREF但是--deploy-url我找不到什么选项。
大多数文章和帖子都指出,只需指定base-href即可,资产也可以正常工作,但事实并非如此。

考虑到所有这些,然后我的问题将是如何设计一个Angular应用程序,使其绝对能够设置其base-href和deploy-url,以便所有角度功能(如路由,translate,import()等)都像我一样工作是通过Angular CLI设置的?

我不确定我是否提供了足够的信息来完全理解我们的问题和期望,但是如果没有的话,我会尽可能提供。

撒旦

花了一些时间,但我们最终设法找到一个足够简单的解决方案,以涵盖我们在问题中提出的所有观点。

该解决方案与原始方法非常接近,该方法仅是脱机构建Angular应用程序,然后将内容复制到nginx容器中,并与之前在init容器中使用的构建过程混合在一起。

然后,我们的解决方案如下所示。我们首先离线构建应用程序,然后注入一些可识别的字符串,后面base-hrefand和deploy-url之后。

ng build --prod --base-href=http://recognisable-host-name/ --deploy-url=http://recognisable-host-name/

结果runtime*.jsindex.html文件将在代码内包含这些内容,并作为加载资产的基础。

然后在第二阶段中,将标准Angular应用程序的Dockerfile改编为

FROM nginx:1.17.10-alpine

EXPOSE 80

COPY conf/nginx.conf /etc/nginx/conf.d/default.conf
RUN rm -rf /usr/share/nginx/html/*
COPY dist/ /usr/share/nginx/html/

到这样的新版本

FROM nginx:1.17.10-alpine

EXPOSE 80

COPY conf/nginx.conf /etc/nginx/conf.d/default.conf
RUN rm -rf /usr/share/nginx/html/*
COPY dist/ /usr/share/nginx/html/

ENV CONTEXT_PATH http://localhost:80
ENV ENDPOINT http://localhost/endpoint
ENV VARIABLE foobar

CMD \
  mainFiles=$(ls /usr/share/nginx/html/main*.js) \
  && \
  for f in ${mainFiles}; do \
    envsubst '${ENDPOINT},${VARIABLE}' < "$f" > "${f}.tmp" && mv "${f}.tmp" "$f"; \
  done \
  && \
  runtimeFiles=$(grep -lr "recognisable-host-name" /usr/share/nginx/html) \
  && \
  for f in ${runtimeFiles}; do sed -i -E "s@http://recognisable-host-name/?@${CONTEXT_PATH}@g" "$f"; done \
  && \
  nginx -g 'daemon off;'

首先,我们在此处传递我们要进行的替换。首先,CONTEXT_PATH是完整的基本路径,它将替换http://recognisable-host-name我们之前注入字符串,方法是首先找到包含该字符串的所有文件,然后用sed命令替换它

也可以通过利用envsubst和替换文件中所有这些变量的提及来替换其他特定于环境的变量main*.js因此,environments.prod.ts准备如下:

export const environment = {
    "endpoint": "${ENDPOINT}",
    "variable": "${VARIABLE}"
}

这照顾了我参加的所有要点,即:

  1. 不共享原始代码
  2. 快速部署
  3. 容器可以在任何代理之后提供服务(例如Nginx-Ingress / Kubernetes)
  4. 可以注入环境变量

我希望它对其他人有帮助,特别是因为当我研究此主题时,我发现了数十篇文章,但没有一篇符合所有标准,或者需要许多复杂,效率低下或难以维护的自定义代码。

编辑:

如果有人感兴趣,我会设置一个演示项目,在其中可以测试此解决方案:https :
//gitlab.com/satanik-angular/base-path-problem

本文收集自互联网,转载请注明来源。

如有侵权,请联系[email protected] 删除。

编辑于
0

我来说两句

0条评论
登录后参与评论

相关文章

来自分类Dev

无法在Angular 2应用程序中更改lite-server的基本文件夹

来自分类Dev

Angular 2 - 在特定日期更改应用程序背景

来自分类Dev

怎么可能将Angular 2应用程序放置在除根路径之外的任何位置?

来自分类Dev

github页面上Angular2应用程序中的路径问题

来自分类Dev

关于发布Angular 2应用程序

来自分类Dev

Angular 2应用程序的范围

来自分类Dev

Ping Angular 2 应用程序

来自分类Dev

如何运行angular 2应用程序?

来自分类Dev

Angular 2动态更改发布请求中的基本URL

来自分类Dev

Angular 2.x更改头部的<title>(在我的应用程序外部)

来自分类Dev

首次加载应用程序时的Angular2更改检测

来自分类Dev

如何在angular 2中使用“ SystemJsNgModuleLoader”将动态模块导入到应用程序?

来自分类Dev

Angular2:使指令在整个应用程序中可用

来自分类Dev

呈现应用程序之前,Angular 2加载数据

来自分类Dev

将Angular 2应用程序部署到Azure

来自分类Dev

如何异步引导Angular 2应用程序

来自分类Dev

Angular 2应用程序是否默认阻止cookie的存储?

来自分类Dev

Angular2应用程序在单击路线时冻结

来自分类Dev

为什么我的angular 2应用程序不引导?

来自分类Dev

无法在Angular 2应用程序中提交HTML表单

来自分类Dev

如何在PHP应用程序中使用Angular 2?

来自分类Dev

Angular 2跨应用程序(根组件)通信

来自分类Dev

在Angular 2 +流星应用程序中包含Google字体

来自分类Dev

是否可以嵌套Angular2应用程序

来自分类Dev

Angular2未在cordova应用程序中加载

来自分类Dev

如何调试Angular 2 Typescript应用程序

来自分类Dev

@在Angular2应用程序外注入

来自分类Dev

在angular2应用程序中使用Vaadin网格

来自分类Dev

在表格应用程序中隐藏表格angular 2

Related 相关文章

  1. 1

    无法在Angular 2应用程序中更改lite-server的基本文件夹

  2. 2

    Angular 2 - 在特定日期更改应用程序背景

  3. 3

    怎么可能将Angular 2应用程序放置在除根路径之外的任何位置?

  4. 4

    github页面上Angular2应用程序中的路径问题

  5. 5

    关于发布Angular 2应用程序

  6. 6

    Angular 2应用程序的范围

  7. 7

    Ping Angular 2 应用程序

  8. 8

    如何运行angular 2应用程序?

  9. 9

    Angular 2动态更改发布请求中的基本URL

  10. 10

    Angular 2.x更改头部的<title>(在我的应用程序外部)

  11. 11

    首次加载应用程序时的Angular2更改检测

  12. 12

    如何在angular 2中使用“ SystemJsNgModuleLoader”将动态模块导入到应用程序?

  13. 13

    Angular2:使指令在整个应用程序中可用

  14. 14

    呈现应用程序之前,Angular 2加载数据

  15. 15

    将Angular 2应用程序部署到Azure

  16. 16

    如何异步引导Angular 2应用程序

  17. 17

    Angular 2应用程序是否默认阻止cookie的存储?

  18. 18

    Angular2应用程序在单击路线时冻结

  19. 19

    为什么我的angular 2应用程序不引导?

  20. 20

    无法在Angular 2应用程序中提交HTML表单

  21. 21

    如何在PHP应用程序中使用Angular 2?

  22. 22

    Angular 2跨应用程序(根组件)通信

  23. 23

    在Angular 2 +流星应用程序中包含Google字体

  24. 24

    是否可以嵌套Angular2应用程序

  25. 25

    Angular2未在cordova应用程序中加载

  26. 26

    如何调试Angular 2 Typescript应用程序

  27. 27

    @在Angular2应用程序外注入

  28. 28

    在angular2应用程序中使用Vaadin网格

  29. 29

    在表格应用程序中隐藏表格angular 2

热门标签

归档