Localization Support for my Blog Vue.js: Validation with TypeScript

Vue.js: Environment-Aware Code in TypeScript

Published on Wednesday, February 7, 2018 6:00:00 AM UTC in Programming

Ideally, running your code in production is something that happens completely transparent to your application. Meaning: your app really shouldn't care about the environment it's currently deployed in, and deliberately be as agnostic about that as possible. Sometimes however, there are situations when you need to know this, particularly at development time. Is this legitimate? And if so, how do you get that information? I give you an example and show you how you can easily access this information in Vue.js with TypeScript.

A real-world example

I ran into the situation that a certain single-page application was developed separately from the backend, which really isn't so unusual for larger companies with different teams. In fact, different code bases were used, located in separate repositories. In production however, both parts of this application run in the same context, meaning the backend also is the host of the client application. This allowed very strict security settings in production, for example with regards to CORS policies - since both parts run on the same domain, these can be configured quite restrictive. During development however, those programming the client app only had their personal instance of the API running locally, but in a separate process and context. It would've been quite some hassle to merge both code bases during development to mimic production behavior, and it would've also slowed down everybody because of the extra steps and complexity involved. It was much easier allowing more relaxed CORS settings in development environments, for example, to enable those two parts of the overall system running i.e. on different ports on a dev's machine, talking to each other. There it is, the legitimate example of environmental awareness.

Now, the first attempt you would do to solve this is through dynamic configuration. A modern server-side framework like .NET Core for example has multi-stage configuration fragments that are blended together. You can control any detail in an application by configuration files, environment variables or command line parameters - or a combination of them, and depending on the environment you're running in. In single-page applications it's not quite that easy. They're sent to a user's browser as blob, and we're out of control of all the environment surrounding our app once it is delivered. So we're facing multiple issues here, some of which dangerously close to the typical chicken-egg problem. If you want to go fully dynamic, you similarly have to inject the required environment information somehow from the outside. This means you may have to turn your (likely static!) index page into something that is hosted in a dynamic environment like ASP.NET Core, solely for the purpose so you can inject the required data somewhere in a data attribute or similar. The other way round typically doesn't work: a (static) SPA itself cannot actively ask which environment it's in, because it simply doesn't know where to ask without being told from the outside. So most frameworks decide to statically include the information into your application at build time. At that point you can still make decisions e.g. based on command line parameters easily and control the output. Once built however, there's no simple way to use the same "binary" in different environments, as it would be possible with a truly dynamic configuration of typical server-side frameworks.

Long story short, that's also the way it's done in a typical Vue.js template. Let's see how, and how we can access that information from TypeScript.

How it works in Vue.js

In the WebPack template, you'll most likely work with two different options all the time: npm run dev for the ad-hoc development web server, and npm run build for the production build. The former directly references a "dev" configuration in the script in your package.json, but let's take a look at the production build feature, which points to a file named build.js. If you open it, you'll see the (hard-coded) production setting right at the top of the file:

process.env.NODE_ENV = 'production'

That is basically the switch we are interested in. It signals that we're running in production configuration. The build itself is tailored to production output by other means, for example by including the explicit "prod" configuration for WebPack, and by using the "build" configuration of Vue.js, which basically are the production settings. I won't discuss this in more detail here though.

Let's rather talk about how we get access to this information in our own code? Well, WebPack is really clever. If you carefully look through the two configuration files webpack.dev.conf.js and webpack.prod.conf.js, you'll see that in both files a certain plugin is configured (one pointing to the development value and one to the production value of course):

plugins: [
  new webpack.DefinePlugin({
    'process.env': require('../config/dev.env')
  }),
  //...

The way DefinePlugin works is by string replacement. It basically defines one or more global values that are searched for in your sources and then replaced at compile time. This means if you use process.env.NODE_ENV in your code, WebPack will replace it with the corresponding value configured in its configuration, i.e. either "production" or "development" in this case. Details about this can be found in the official documentation on DefinePlugin. You can confirm this behavior if you look through the compiled output later. The places where the above global is used and for example compared to a fixed string like "production", are often optimized at compile time to directly return the actual boolean result of this comparison (because it's static, this can be done safely). You often won't find these string literals anymore. Sometimes, without full optimization, you can also see funny remains like if ("production" === "development") in the final code, which also confirms this string replacement mechanism.

How to use it in TypeScript

in JavaScript you simply go ahead and use process.env.NODE_ENV anywhere in your code and you're good to go. In TypeScript, this is not so simple, because TypeScript does not know anything about a global process object and refuses to compile. I spent quite some time fiddling with attempts to declare this global the right way. In the end, as so often, the solution is simple:

// this is so you can access the current runtime environment by using process.env.NODE_ENV,
// to enable the webpack "process.env" plugin
declare var process : {
    env: {
        NODE_ENV: string
    }
}

You can nicely package this up into a handy environment helper type, like:

export class EnvironmentHelper {    
    public static get isDevelopment(): boolean {
        return process.env.NODE_ENV === "development";
    }

    public static get isProduction(): boolean {
        return process.env.NODE_ENV === "production";
    }
}

And with that, the above development scenario is easily solved: depending on the environment, you for example either talk to the same origin (production), or a completely unrelated service running on another port of the local machine. Like:

public get baseUrl(): string {
    if (EnvironmentHelper.isDevelopment) {
        return "http://localhost:5000/"
    }

    return "/";
}

What's not so nice is that this code remains in your build even in production (no conditional compilation as with #DEBUG here). For me, this is totally acceptable though. How about you? Would you have set up a separate host for your SPA solely to inject this information into your index page cleanly?

Happy coding! :)

Tags: Deployment · TypeScript · Vue.js