Using .env files in SharePoint Framework development

For a long time we’ve been looking into simplifying our team’s development to deployment workflow in regards to environment-specific application settings.

What can easily be achieved in for example a .Net project, becomes a tedious task when creating a client-side project. After all, your application runs on the user’s client, so defining settings at runtime isn’t possible. This means we have to specify these at build time, injected in our JS bundle.

When working in a team, and having multiple environments to deploy to (dev, test, qa, production, …), keeping these settings out of the code base is imperative.

This is where environment variables can help us. Combined with dotenv and dotenv-expand NPM packages, we can read these variables at build time from the system, and inject them where needed in our code, using process.env.variable.

Let’s walk through the steps to setup .env in your code base.

Create the .env files

In the root of your project, create 2 files:

  • .env – this can contain shared, non-sensitive settings. For example: log severity level, interval settings, …
  • .env.local – this will contain your settings. The settings which you use to run the application. For example: API base url, AAD app ids, …

Important! Make sure to add .env.local to your .gitignore file. This ensures that your settings will not be shared with all other developers. After all they are your settings.

Using environment variables in code

Defining environment variables is one thing, but using them is even easier!

All environment variables will be made available through process.env, which means we can use them like this:

let apiBaseUrl = process.env.SPFX_WEBAPI_BASEURL;

PS: if process.env is not available, make sure to add “node” to the known types in tsconfig.json

{
    ...,
    types: ["node", "webpack-env"],
    ...
}

Include .env variables in the build

In order to be able to use the configuration files in your application, they need to be made available to your bundling task. Since SPFx uses gulp and webpack, we need to append this task to take our .env files into consideration.

First of all, install dotenv and dotenv-expand in your project:

npm install --save-dev dotenv dotenv-expand

Then, create a file process-env.js, which looks like this:

"use strict";

const fs = require("fs");
const path = require("path");

const NODE_ENV = process.env.NODE_ENV || "dev";
const dotEnvPath = path.resolve(process.cwd(), ".env");

const dotenvFiles = [
  `${dotEnvPath}.${NODE_ENV}.local`,
  `${dotEnvPath}.${NODE_ENV}`,
  NODE_ENV !== "test" && `${dotEnvPath}.local`,
  dotEnvPath
].filter(Boolean);

dotenvFiles.forEach(dotenvFile => {
  if (fs.existsSync(dotenvFile)) {
    require("dotenv-expand")(
      require("dotenv").config({
        path: dotenvFile
      })
    );
  }
});

const appDirectory = fs.realpathSync(process.cwd());
process.env.NODE_PATH = (process.env.NODE_PATH || "")
  .split(path.delimiter)
  .filter(folder => folder && !path.isAbsolute(folder))
  .map(folder => path.resolve(appDirectory, folder))
  .join(path.delimiter);

const SPFX_ = /^SPFX_/i;

function getClientEnvironment() {
  const raw = Object.keys(process.env)
    .filter(key => SPFX_.test(key))
    .reduce((env, key) => {
      env[key] = process.env[key];
      return env;
    }, {});

  const stringified = {
    "process.env": Object.keys(raw).reduce((env, key) => {
      env[key] = JSON.stringify(raw[key]);
      return env;
    }, {})
  };

  return { raw, stringified };
}

module.exports = getClientEnvironment;

If you examine the code, you’ll notice a check for the SPFX_ prefix. This will ensure not all environment variables available on our system are injected, but only those we specifically specify.

Next, append gulpfile.js and add this right before the build.initialize() call:

const webpack = require("webpack");
const getClientEnvironment = require("./process-env");

build.configureWebpack.mergeConfig({
  additionalConfiguration: cfg => {
    let pluginDefine = null;
    for (var i = 0; i < cfg.plugins.length; i++) {
      var plugin = cfg.plugins[i];
      if (plugin instanceof webpack.DefinePlugin) {
        pluginDefine = plugin;
      }
    }

    const currentEnv = getClientEnvironment().stringified;

    if (pluginDefine) {
      pluginDefine.definitions = { ...pluginDefine.definitions, ...currentEnv };
    } else {
      cfg.plugins.push(new webpack.DefinePlugin(currentEnv));
    }

    return cfg;
  }
});
Bulk of the code is copied from https://github.com/facebook/create-react-app/blob/master/packages/react-scripts/config/env.js

Conclusion

It only takes a small effort to enable developer- or environment-specific settings in your application.

Using environment variables enable you to pre-define settings on your own development machine, but also in CI / CD pipelines. For example: in Azure DevOps pipelines, all variables you define are automatically added as an environment variable. This makes your deployment setup a whole lot easier.

Code sample

A full implementation can be downloaded from https://github.com/dlw-digitalworkplace/demo-spfx-dotenv

References and inspiration

https://github.com/motdotla/dotenv-expand
https://github.com/motdotla/dotenv
https://create-react-app.dev/

Leave a comment

This site uses Akismet to reduce spam. Learn how your comment data is processed.

Create a website or blog at WordPress.com

Up ↑

Design a site like this with WordPress.com
Get started