gpt4 book ai didi

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

转载 作者:行者123 更新时间:2023-12-02 11:42:15 24 4
gpt4 key购买 nike

介绍
我知道这个问题已经以许多不同的形式提出。但是,我已经阅读了有关该问题的所有帖子和文章,并且无法解决我们面临的先决条件所带来的问题。因此,我想先描述我们的问题并给出我们的动力,然后再描述我们已经尝试或正在使​​用的不同方法,即使它们不能令人满意。
问题/动机
我们的设置可谓有些复杂。我们的Angular应用程序已部署在多个Kubernetes集群上,并通过不同的路径提供服务。
这些集群本身位于代理之后,Kubernetes本身添加了一个名为Ingress的代理。因此,对我们来说,常见的设置如下所示:
本地
http:// localhost: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以及从哪里提供 Assets ,可以使用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/
将该容器作为initContainer集成到Kubernetes部署中,可以使用正确的base-href,deploying-url构建该应用程序,并根据部署该应用程序的环境替换特定的变量。
这种方法除了会产生一些问题之外,其效果很好:
  • 一次部署需要几分钟
    每次重新部署应用程序时,initContainer都需要运行。当然,这总是运行ng build命令。为了防止它在每次容器已经在前面的ng build指令中运行RUN命令来缓存它们时构建ES模块。
    尽管如此,用于差异加载的建筑物和Terser等仍在运行,这需要几分钟的时间才能完成。
    如果存在水平 pods 自动缩放,那么将需要永久使用其他 pods 。
  • 容器包含代码。这更多是一项策略,但是我们公司不建议在部署时交付代码。至少不是非混淆形式。

  • 由于这两个问题,我们决定将逻辑从Kubernetes / Docker直接移到应用程序本身。
    计划的解决方案
    经过一些研究,我们偶然发现了 APP_BASE_HREF InjectionToken。因此,我们尝试遵循Web上的不同指南,以根据应用程序所部署的环境动态地设置此设置。首先要做的是:
  • config.json中添加一个名为/src/assets/config.json的文件,其内容(以及其他变量)的基本路径为
  • {
    "basePath": "/angular-app",
    "endpoint": "https://kubernetes.local/endpoint"
    }
  • 我们已将代码添加到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));
    });
  • app.module.ts内部,使用APP_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时,该应用程序显然会尝试从'/'(根)加载所有内容。
    但这也意味着它试图从那里获取 Angular 脚本akat, main.jsvendor.jsruntime.js以及所有其他 Assets ,因此实际上我们的代码都没有运行。
    为了解决这个问题,我们稍微修改了代码。
    代替让 Angular 探测服务器来查询 config.json,我们将代码直接放在 index.html内并内联。
    这样,我们可以找到基本路径,并将html中的所有链接替换为带前缀的链接,以至少加载脚本和其他 Assets 。如下所示:
    <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
    路由似乎可以工作,但是所有其他逻辑(如加载语言文件, Markdown 文件和其他 Assets )都不再起作用。
    我认为 --base-href选项实际上设置了 APP_BASE_HREF,但是我找不到 --deploy-url选项是什么。
    大多数文章和帖子都指出,只需指定base-href就足够了, Assets 也可以正常工作,但事实并非如此。

    考虑到所有这些,然后我的问题将是如何设计一个Angular应用程序,使其绝对能够设置其base-href和deploy-url,以便所有 Angular 功能(如路由,translate,import()等)都像我一样工作是通过Angular CLI设置的?
    我不确定我是否提供了足够的信息来完全理解我们的问题和期望,但是如果没有,我会尽可能提供。

    最佳答案

    花了一些时间,但我们最终设法找到一个足够简单的解决方案,以涵盖我们在问题中提出的所有观点。
    该解决方案与原始方法非常接近,该方法仅是脱机构建Angular应用程序,然后将内容复制到nginx容器中,并与先前在init容器中使用的构建过程混合在一起。
    然后,我们的解决方案如下所示。我们首先离线构建应用程序,然后注入(inject)一些可识别的字符串,其中base-hrefdeploy-url应该在以后。

    ng build --prod --base-href=http://recognisable-host-name/ --deploy-url=http://recognisable-host-name/
    生成的 runtime*.jsindex.html文件将在代码内部包含这些内容,并作为加载 Assets 的基础。
    然后在第二阶段中,将标准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是完整的基本路径,它将替换我们之前注入(inject)的 http://recognisable-host-name字符串,方法是首先找到包含该字符串的所有文件,然后将其替换为 sed命令。
    也可以通过使用 envsubst并替换 main*.js文件中对这些变量的所有提及来替换特定于环境的其他变量。因此, environments.prod.ts的准备如下:
    export const environment = {
    "endpoint": "${ENDPOINT}",
    "variable": "${VARIABLE}"
    }
    这照顾了我参加的所有要点,即:
  • 不共享原始代码
  • 快速部署
  • 容器可以放在任何代理(例如Nginx-Ingress / Kubernetes)之后
  • 环境变量可以注入(inject)

  • 我希望它对其他人有帮助,特别是因为当我研究此主题时,我发现了数十篇文章,但没有一篇符合所有标准,或者需要许多复杂,效率低下或难以维护的自定义代码。
    编辑:
    如果有人感兴趣,我会设置一个演示项目,可以测试此解决方案:

    https://gitlab.com/satanik-angular/base-path-problem

    关于angular - Angular 2+-动态更改应用程序的基本路径,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/63299094/

    24 4 0
    Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
    广告合作:1813099741@qq.com 6ren.com