在运行时将环境变量传递到Vue应用程序

51
如何在Vue中访问在运行时传递给容器的环境变量而不是构建期间传递的变量? 堆栈如下: VueCLI 3.0.5 Docker Kubernetes 在stackoverflow和其他地方有建议的解决方案,使用.env文件传递变量(并使用模式),但那是在构建时,并且被烘焙到docker映像中。 我想在运行时将变量传递到Vue中,方法如下: 创建Kubernetes ConfigMap(我做对了) 在运行部署yaml文件时将ConfigMap值传递到K8s pod env变量中(我做对了) 从上面创建的环境变量中读取例如VUE_APP_MyURL,并在我的Vue应用程序中使用该值执行某些操作(我没有做对) 我已经在helloworld.vue中尝试了以下内容:
<template>
<div>{{displayURL}}
  <p>Hello World</p>
</div>
</template>
<script>
export default {  
    data() {
        return {
            displayURL: ""
        }
    },
    mounted() {
        console.log("check 1")
        this.displayURL=process.env.VUE_APP_ENV_MyURL
        console.log(process.env.VUE_APP_ENV_MyURL)
        console.log("check 3")
    }
}
</script>

我在控制台日志里看到了“undefined”,并且在“helloworld”页面上没有显示任何内容。

我还尝试将该值传递到vue.config文件中,然后从那里读取它。但是在console.log中仍然得到相同的“undefined”结果。

<template>
<div>{{displayURL}}
  <p>Hello World</p>
</div>
</template>
<script>
const vueconfig = require('../../vue.config');
export default {  
    data() {
        return {
            displayURL: ""
        }
    },
    mounted() {
        console.log("check 1")
        this.displayURL=vueconfig.VUE_APP_MyURL
        console.log(vueconfig.VUE_APP_MyURL)
        console.log("check 3")
    }
}
</script>

使用以下vue.config的配置:

module.exports = {
    VUE_APP_MyURL: process.env.VUE_APP_ENV_MyURL
}
如果我在vue.config文件中硬编码一个值到VUE_APP_MyURL里,它会成功显示在helloworld页面上。当我查询它时,VUE_APP_ENV_MyURL已经成功地填充了正确的值:kubectl describe pod。但process.env.VUE_APP_MyURL似乎不能成功地检索到该值。不过,顺带一提,我能够成功地使用process.env.VUE_APP_3rdURL将值传递到一个Node.js应用程序中。

1
你是将Vue代码构建为静态HTML/JS,还是在Docker中作为“dev”运行? - Trevor V
1
如果您可以访问包含环境变量的文件,那么您可以使用FileReader API来解析它。顺便说一句,这是非常重要的。 - Simone
感谢@varcorb,我正在将Vue代码构建为静态HTML/JS。 - Johann
根据您所要求的确切内容,Hendrik Malkows的答案可以做到这一点,他详细说明如何添加它非常有用。请考虑将其标记为正确答案!需要明确的是,我有一个k8s configMap,在运行时注入,并允许我在进程环境变量的基础URL之上传递一个基本URL。 - Evan Morrison
7个回答

46
创建一个名为config.js的文件,其中包含您所需的配置信息。稍后我们将使用它来创建一个配置映射,部署到Kubernetes上。将其放置在您的Vue.js项目中,与其他JavaScript文件放在一起。虽然稍后我们会将其从最小化排除,但将其放在那里非常有用,这样IDE工具可以与它一起使用。
const config = (() => {
  return {
    "VUE_APP_ENV_MyURL": "...",
  };
})();

现在请确保您的脚本不会被压缩。为此,请创建一个名为vue.config.js的文件,并将以下内容保存到该文件中,以保留我们的配置文件。

const path = require("path");
module.exports = {
  publicPath: '/',
  configureWebpack: {
    module: {
      rules: [
        {
          test: /config.*config\.js$/,
          use: [
            {
              loader: 'file-loader',
              options: {
                name: 'config.js'
              },
            }
          ]
        }
      ]
    }
  }
}

在您的index.html中,添加一个脚本块以手动加载配置文件。请注意,由于我们刚刚排除了配置文件,因此它不会出现。稍后,我们将从ConfigMap中将其挂载到容器中。在这个例子中,我们假设我们将把它挂载到与我们的HTML文档相同的目录中。

<script src="<%= BASE_URL %>config.js"></script>

将您的代码更改为使用我们的运行时配置:

this.displayURL = config.VUE_APP_ENV_MyURL || process.env.VUE_APP_ENV_MyURL 

在Kubernetes中创建一个配置映射,使用你的配置文件中的内容。当然,你想要从你的配置文件中读取内容。


apiVersion: v1
kind: ConfigMap
metadata:
  ...
data:
  config.js: |
    var config = (() => {
      return {
        "VUE_APP_ENV_MyURL": "...",
      };
    })();

在部署中引用配置映射。这将配置映射作为文件挂载到容器中。 mountPath 已经包含我们的minified index.html。 我们挂载了之前引用的配置文件。

<code>apiVersion: apps/v1
kind: Deployment
metadata:
  ...
spec:
  ...
  template:
    ...
    spec:
      volumes:
        - name: config-volume
          configMap:
            name: ...
      containers:
        - ...
          volumeMounts:
                - name: config-volume
                  mountPath: /usr/share/nginx/html/config.js
                  subPath: config.js
</code>

现在您可以访问 <Base URL>/config.js 配置文件,并且您应该可以看到您在 ConfigMap 条目中输入的确切内容。HTML文档会在加载其余的压缩Vue.js代码同时加载该配置映射。大功告成!


1
我应该把config.js放在哪里,当我在代码中使用它时,如何导入配置? - NehaM
1
我刚刚添加了一些更详细的解释。这有帮助吗? - Hendrik M Halkow
@HendrikMHalkow,您能否添加一个“树形结构”,展示一下您的项目结构是什么样子的? - Aaron N. Brock
3
你好,如果在一个TypeScript文件中需要引用config对象,这个解决方案是否有效?谢谢。 - Frank Liu
请问您能否添加更详细的说明或项目结构链接,以及如果我需要将相同的标志加载到index.html中怎么办? - Lakshay
我为QuasarVue3制作了一个概要。https://gist.github.com/nkreiger/f5d0ce20a98fb8e0161b4750f3bb4f46 - Noah Kreiger

26
我在这里添加我的工作解决方案,供那些仍然遇到困难的人参考。我认为@Hendrik M Halkow的答案更优雅,尽管我无法使用它来解决问题,只是因为我对webpack和Vue缺乏专业知识,我无法弄清楚在哪里放置配置文件以及如何引用它。
我的方法是利用常量(虚拟值)与环境变量一起构建生产版本,然后使用自定义的入口点脚本替换图像中的常量。解决方案如下。
我已经将所有配置封装到一个名为app.config.js的文件中。
export const clientId = process.env.VUE_APP_CLIENT_ID
export const baseURL = process.env.VUE_APP_API_BASE_URL

export default {
  clientId,
  baseURL,
}

这只是在项目中通过查找配置文件中的值来使用的。

import { baseURL } from '@/app.config';

我使用标准的.env.[profile]文件来设置环境变量。例如:.env.development

VUE_APP_API_BASE_URL=http://localhost:8085/radar-upload
VUE_APP_CLIENT_ID=test-client

对于生产环境,我将字符串常量设置为值。 例如.env.production

VUE_APP_API_BASE_URL=VUE_APP_API_BASE_URL
VUE_APP_CLIENT_ID=VUE_APP_CLIENT_ID
请注意这里的值可以是任何唯一的字符串。为了使可读性更好,我只是用环境变量名替换值。这将与开发模式类似地被编译和捆绑。
在我的 Dockerfile 中,我添加了一个 entrypoint,它可以读取这些常量并用环境变量值替换它。
我的 Dockerfile 看起来像这样(这是相当标准的)。
FROM node:10.16.3-alpine as builder

RUN mkdir /app
WORKDIR /app

COPY package*.json /app/
RUN npm install

COPY . /app/

RUN npm run build --prod

FROM nginx:1.17.3-alpine

# add init script
COPY ./docker/nginx.conf /etc/nginx/nginx.conf

WORKDIR /usr/share/nginx/html

COPY --from=builder /app/dist/ .

COPY ./docker/entrypoint.sh /entrypoint.sh

# expose internal port:80 and run init.sh
EXPOSE 80

ENTRYPOINT ["/entrypoint.sh"]
CMD ["nginx", "-g", "daemon off;"]

然后按照以下内容创建一个 ./docker/entrypoint.sh 文件。

#!/bin/sh

ROOT_DIR=/usr/share/nginx/html

# Replace env vars in JavaScript files
echo "Replacing env constants in JS"
for file in $ROOT_DIR/js/app.*.js* $ROOT_DIR/index.html $ROOT_DIR/precache-manifest*.js;
do
  echo "Processing $file ...";

  sed -i 's|VUE_APP_API_BASE_URL|'${VUE_APP_API_BASE_URL}'|g' $file 
  sed -i 's|VUE_APP_CLIENT_ID|'${VUE_APP_CLIENT_ID}'|g' $file

done

echo "Starting Nginx"
nginx -g 'daemon off;'

这使我能够拥有在许多环境中运行的运行时可配置图像。我知道这有点像是一个 hack,但我见过很多人都是这样做的。

希望这对某些人有所帮助。


2
嗨@NehaM,我刚刚为我的解决方案添加了更详细的说明。希望它能帮到你。我建议避免将入口点脚本化,因为它与distroless镜像不兼容。看一下https://github.com/SDA-SE/nginx——一个小于6MB的NGINX镜像。如果你无法避免脚本化,请在最后一个命令之前放置一个`exec`,这样shell进程将被你的nginx进程替换。 - Hendrik M Halkow
感谢您的建议和改进答案。我仍在使用我想出的解决方案。我已经对代码进行了小的改进。 - NehaM
1
我认为这个是最优雅的。我只希望它更具有动态性,这样我们就不需要重复太多变量,但我不确定在保持可读性的同时是否可以做到这一点。 - Pierre de LESPINAY
1
变量名应与键名不同,否则它将无法工作。 在.env.production中,应为VUE_APP_API_BASE_URL=VUE_APP_API_BASE_URL_value - Amir Choubani
这是Gravitee.io前端应用程序使用的最佳实践,请访问https://github.com/gravitee-io/gravitee-docker/blob/master/images/portal-ui/files/entrypoint.sh。 - David KELLER
优秀而优雅的解决方案! - undefined

10

创建配置文件

在public文件夹中: public/config.js

const config = (() => {
  return {
    "VUE_CONFIG_APP_API": "...",
  };
})();

更新 index.html

请在 public/index.html 的 head 标签末尾添加以下内容:

  <!-- docker configurable variables -->
  <script src="<%= BASE_URL %>config.js"></script>

由于我们使用公共文件夹进行配置,因此无需更新vue.config.js。

ESLint

ESLint会给我们提供未定义变量的错误提示。因此,我们在.eslintrc.js文件中定义全局变量:

  globals: {
    config: "readable",
  },

使用方法

例如,在存储 src/store/user.js 中。

export const actions = {
  async LoadUsers({ dispatch }) {
    return await dispatch(
      "axios/get",
      {
        url: config.VUE_CONFIG_APP_API + "User/List",
      },
      { root: true }
    );
  },
...

K8S配置映射:

apiVersion: v1
kind: ConfigMap
metadata:
  name: fe-config
  namespace: ...
data:
  config.js: |
    var config = (() => {
      return {
        "VUE_CONFIG_APP_API": "...",
      };
    })();

部署
apiVersion: apps/v1
kind: Deployment
metadata:
  ...
spec:
  ...
  template:
    ...
    spec:
      volumes:
        - name: config-volume
          configMap:
            name: fe-config
      containers:
        - ...
          volumeMounts:
                - name: config-volume
                  mountPath: /usr/share/nginx/html/config.js
                  subPath: config.js

为什么不在config.js中定义“const config = {xxx:“…”}”?谢谢您的回复。 - Xingxing Qiao
因为配置是硬编码的,您无法在运行时修改它,例如在 Kubernetes 中... 您必须在构建后加载它。 - Scholtz
谢谢您的回复!我的问题不是很清楚。我的意思是,为什么要将对象包装在匿名函数中并返回它,而不是只将对象分配给“config”? - Xingxing Qiao
对于在多个具有不同 API 端点的服务器上部署,这将是最好的简单解决方案,无需为每个服务器重新构建,只需更改一行即可。 - Samnad Sainulabdeen

6

我在当前项目中遇到了同样的问题,发现目前无法在运行时访问环境变量,因此我最终采用创建.env文件或本地环境变量的解决方案,正如你所说,这些变量是在构建时使用的。


2
本帖的作者解释说,试图将变量传递到静态html.js代码中是毫无意义的。我终于明白了!https://www.reddit.com/r/vuejs/comments/8sbosc/how_to_access_environment_variables_passed_to_a/ - Johann
构建时间而非运行时是违反发布工程原则的... 无论如何,create-react-app 是 React 最好的选择... 从这个角度来看。 - Abdennour TOUMI

3
如果您正在使用VueJs3 + Vite3 + TypeScript,您可以这样做:
创建app.config.ts(不是JS)
export const clientId = import.meta.env.VITE_CLIENT_ID
export const baseURL = import.meta.env.VITE_API_BASE_URL

export default {
  clientId,
  baseURL,
}

替换 assets 子目录中的值:(改进的 shell 脚本)

#!/bin/sh
# @see https://dev59.com/2GMl5IYBdhLWcg3wuI3p
ROOT_DIR=/usr/share/nginx/html
          
# Replace env vars in JavaScript files
echo "Replacing env constants in JS"

keys="VITE_CLIENT_ID
VITE_API_BASE_URL"

for file in $ROOT_DIR/assets/index*.js* ;
do
  echo "Processing $file ...";
  for key in $keys
  do
    value=$(eval echo \$$key)
    echo "replace $key by $value"
    sed -i 's#'"$key"'#'"$value"'#g' $file
  done
done

echo "Starting Nginx"
nginx -g 'daemon off;'

在 Dockerfile 中不要忘记 "RUN chmod u+x"。
# build
FROM node:lts-alpine as build-stage
WORKDIR /app
COPY package*.json ./
RUN npm install
COPY . .
RUN npm run build

# production
FROM nginx:stable-alpine as production-stage
COPY --from=build-stage /app/dist /usr/share/nginx/html
COPY ./docker/nginx.conf /etc/nginx/conf.d/default.conf
COPY ./docker/entrypoint.sh /entrypoint.sh

COPY ./docker/entrypoint.sh /usr/local/bin/
RUN chmod u+x /usr/local/bin/entrypoint.sh

EXPOSE 80

ENTRYPOINT ["/usr/local/bin/entrypoint.sh"]
CMD ["nginx", "-g", "daemon off;"]

然后您就可以在项目中的任何TypeScript文件中导入它了。

import { baseURL } from '@/app.config';

@see NehaM回答的更新:在运行时将环境变量传递到Vue应用程序中


index.js是什么?对我来说和index.html一样吗? - Euridice01
这是构建的文件,例如,VueJs+Vite将在资产构建目录中构建:About.3ebd8e42.css、About.a0fa1f22.js、index.a144206e.css和... index.47dd29d7.js。 - David KELLER

2
我最近创建了一组插件,以优雅地解决这个问题:
  • 没有全局命名空间污染。
  • 不需要import语句。
  • 几乎零配置。
  • 支持Vue CLI(也适用于Webpack、Rollup和Vite、CSR、SSR和SSG以及单元测试工具。由Unplugin和Babel提供支持)。

您可以按照以下方式访问环境变量(深受Vite启发):

// src/index.js
console.log(`Hello, ${import.meta.env.HELLO}.`);

在生产过程中,它将被临时替换为占位符:
// dist/index.js
console.log(`Hello, ${"__import_meta_env_placeholder__".HELLO}.`);

最后,您可以使用内置脚本将占位符替换为系统中的真实环境变量(例如,在k8s pod中读取环境变量):

// dist/index.js
console.log(`Hello, ${{ HELLO: "import-meta-env" }.HELLO}.`);
// > Hello, import-meta-env.

您可以在https://iendeavor.github.io/import-meta-env/上查看更多信息。
此外,还有一个Docker设置示例和一个Vue CLI设置示例
希望这能帮助需要的人。

1

我用@Hendrik M Halkow提出的解决方案使它工作了。

但是我将config.js存储在静态文件夹中。这样做,我就不必担心文件未被压缩。

然后像这样包含它:

<script src="<%= BASE_URL %>static/config.js"></script>

使用此卷挂载配置:
...
volumeMounts:
    - name: config-volume
      mountPath: /usr/share/nginx/html/static/config.js
      subPath: config.js

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