我知道这个问题已经以许多不同的形式提出。但是,我已经阅读了有关该问题的所有帖子和文章,并且无法解决我们面临的先决条件所带来的问题。因此,我想先描述我们的问题并给出我们的动力,然后再描述我们已经尝试或正在使用的不同方法,即使它们不能令人满意。
我们的设置可谓有些复杂。我们的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建成并根据这个应用程序部署在环境中特定变量被替换。
这种方法除了会产生一些问题之外,其效果很好:
ng build
当然,这总是运行命令。为了防止容器每次在容器容器中运行ng build
先前RUN
指令中的命令来缓存它们时就构建ES模块。由于这两个问题,我们决定将逻辑从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时,该应用程序显然会尝试从'/'(根)加载所有内容。
但是,这也意味着它试图获取又名角脚本main.js
,vendor.js
,runtime.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-href
and和deploy-url
之后。
ng build --prod --base-href=http://recognisable-host-name/ --deploy-url=http://recognisable-host-name/
结果runtime*.js
和index.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}"
}
这照顾了我参加的所有要点,即:
我希望它对其他人有帮助,特别是因为当我研究此主题时,我发现了数十篇文章,但没有一篇符合所有标准,或者需要许多复杂,效率低下或难以维护的自定义代码。
编辑:
如果有人感兴趣,我会设置一个演示项目,在其中可以测试此解决方案:https :
//gitlab.com/satanik-angular/base-path-problem
本文收集自互联网,转载请注明来源。
如有侵权,请联系[email protected] 删除。
我来说两句