Pass environment variable into a Vue App at runtime

后端 未结 5 1384
孤街浪徒
孤街浪徒 2020-12-05 17:34

How can I access environment variables in Vue, that are passed to the container at runtime and not during the build?

Stack is as follows:

  • VueCLI 3.0.5
相关标签:
5条回答
  • 2020-12-05 18:16

    I am adding my working solution here, for those who are still having trouble. I do think that @Hendrik M Halkow 's answer is more elegant, though I couldn't manage to solve it using that, simply just because of my lack of expertise in webpack and Vue.I just couldn't figure out where to put the config file and how to refer it.

    My approach is to make use of the environment variables with constants (dummy values) to build it for production, then replace that constants in the image using a custom entrypoint script. The solution goes like this.

    I have encapsulated all configs into one file called 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,
    }
    

    This is used in the project just by looking up the value from config file.

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

    Then I am using standard .env.[profile] files to set environment variables. e.g. the .env.development

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

    Then for production I set string constants as values. e.g. the .env.production

    VUE_APP_API_BASE_URL=VUE_APP_API_BASE_URL
    VUE_APP_CLIENT_ID=VUE_APP_CLIENT_ID
    

    Please not here the value can be any unique string. Just to keep the readability easier, I am just replacing the environment variable name as the value. This will just get compiled and bundled similar to development mode.

    In my Dockerfile, I add an entrypoint that can read those constants and replace it will environment variable values.

    My Dockerfile looks like this (this is pretty standard)

    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;"]
    
    

    Then create a ./docker/entrypoint.sh file as below.

    #!/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;'
    

    This enables me to have runtime configurable image that I can run on many environments. I know it is a bit of a hack. But have seen many people do it this way.

    Hope this helps someone.

    0 讨论(0)
  • 2020-12-05 18:17

    Create a file config.js with your desired configuration. We will use that later to create a config map that we deploy to Kubernetes. Put it into your your Vue.js project where your other JavaScript files are. Although we will exclude it later from minification, it is useful to have it there so that IDE tooling works with it.

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

    Now make sure that your script is excluded from minification. To do that, create a file vue.config.js with the following content that preserves our config file.

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

    In your index.html, add a script block to load the config file manually. Note that the config file won't be there as we just excluded it. Later, we will mount it from a ConfigMap into our container. In this example, we assume that we will mount it into the same directory as our HTML document.

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

    Change your code to use our runtime config:

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

    In Kubernetes, create a config map that uses the content your config file. Of course, you wanna read the content from your config file.

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

    Reference the config map in your deployment. This mounts the config map as a file into your container. The mountPath Already contains our minified index.html. We mount the config file that we referenced before.

    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
    

    Now you can access the config file at <Base URL>/config.js and you should see the exact content that you put into the ConfigMap entry. Your HTML document loads that config map as it loads the rest of your minified Vue.js code. Voila!

    0 讨论(0)
  • 2020-12-05 18:19

    I had the same problem in my current project and found out that it is not possible to access environment variables at runtime at the moment so I end up with the solution of creating .env files or local environment variables that, as you said, are used at the build time.

    0 讨论(0)
  • 2020-12-05 18:21

    I got it to work with the solution proposed by @Hendrik M Halkow.

    But I stored the config.js in the static folder. By doing that, I don't have to care about not minifying the file.

    Then include it like this:

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

    and use this volume mount configuration:

    ...
    volumeMounts:
        - name: config-volume
          mountPath: /usr/share/nginx/html/static/config.js
          subPath: config.js
    
    0 讨论(0)
  • 2020-12-05 18:33

    Create config file

    In public folder: public/config.js

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

    Update index.html

    Update public/index.html to contain following at the end of head:

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

    There is no need to update vue.config.js as we are using the public folder for configuration.

    ESLint

    ESLint would give us error of usage of undefined variable. Therefore we define global variable in .eslintrc.js file:

      globals: {
        config: "readable",
      },
    

    Usage

    Eg. in the store 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 ConfigMap:

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

    Deployment

    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
    
    0 讨论(0)
提交回复
热议问题