Inject Environment Variables at runtime with Webpack Module federation

Viewed 1128

The question about injecting Environment Variables after bundling with Webpack is already asked and answered.

The answer is always based on loading the updated Environment Variables inside the index.html file.

But when using Webpack Module Federation, we are exposing only a component.js file, so how can I bundle the source during the docker build phase, but read the Environment Variables during the serving phase (when running our bundle as a pod inside k8s, for example).

3 Answers

I'm strugling with the same issue... And I can only think of using a convention in the container for handling this problem.

All micro-frontends could potentially expose their environment variables in ./assets/env.template.js (then finally ./assets/env.js) with ${MY_ENVIRONMENT_VARIABLE} as placeholders.

This env.template.js will be an autoexecuting javascript function that will just introduce the environment variable values as global properties of the window (see below the example).

env.template.js

(function (window) {
  window.env = window.env || {};
  window['env']['clientId'] = '${CLIENT_ID}';
  window['env']['baseUrl'] = '${BASE_URL}'
})(this);

When creating the docker image a final asset env.js will be generated that will substitute the environment variables. For example creating the docker nginx image with:

CMD ["/bin/sh", "-c", "envsubst < /usr/share/nginx/html/assets/env.template.js > /usr/share/nginx/html/assets/env.js && exec nginx -g 'daemon off;'"]

The container when loading the remotes could try to dynamically load their env.js files exposed in assets.

The normal angular injected environment would get the values from the window.env the values.

Currently, we are combining the webpack.EnvironmentPlugin with Docker.

In webpack.config.js, we are substituting the Environment Variables with a special string:

plugins: [
    new EnvironmentPlugin({
        MY_CUSTOM_ENV_VAR: '{{MY_CUSTOM_ENV_VAR}}',
        ANOTHER_ENV_VAR: '{{ANOTHER_ENV_VAR}}',
    }),
],

It means, during the build time, if there are no such Environment Variables, the bundle will include the strings '{{MY_CUSTOM_ENV_VAR}}' and '{{MY_CUSTOM_ENV_VAR}}'.

Now, to replace those variables on container starts, we are adding a substitute to the RUN command:

CMD sed -i "s@{{MY_CUSTOM_ENV_VAR}}@${MY_CUSTOM_ENV_VAR}@g" /usr/share/nginx/html/*.bundle*.js \
    && sed -i "s@{{ANOTHER_ENV_VAR}}@${ANOTHER_ENV_VAR}@g" /usr/share/nginx/html/*.bundle*.js \
    && nginx -g "daemon off;"

(It will replace the string {{MY_CUSTOM_ENV_VAR}} with the value of ${MY_CUSTOM_ENV_VAR} on the bundle files: /usr/share/nginx/html/*.bundle*.js (nginx))

In the webpack.config

const webpack = require('webpack');       
require('dotenv').config({ path: './.env' }); 

Then in the plugin modules part add this

new webpack.DefinePlugin({
  "process.env": JSON.stringify(process.env);
}),

This will bring to code all Environment Variables from the machine.

Another improvement is required this way, with this you can create files like .env.development or .env.production

module.exports = (env, args) => {
    require("dotenv").config({ path: `./.env.${args.mode}` });

If you want to pass just the env vars you define in the .env files just change the require to this:

const dotenv = require("dotenv").config({ path: ./.env.${args.mode}` }); 

and the definePlugin to this:

new webpack.DefinePlugin({ "process.env": JSON.stringify(dotenv.parsed), }),
Related