18 Jul 2019

Expose .env variables to Vue CLI sass

In all programming environments it is important to have a single source of truth, particularly when it comes to constants and environment variables: if you want to change a variable and forgetting to update every instance of a variable can be a source of many mysterious bugs.

However, if you’re using Vue CLI 3 to handle your frontend framework bundling it’s not entirely obvious how you should maintain a single source of truth for your templates, scripts and styling without having one for each.

Vue expects environmental variables to be set in .env files in the root of your project. These are files with a simple key = “value” on every line. For instance:

VUE_APP_MIN_WIDTH_DESKTOP_WIDE = "1600px"
VUE_APP_MIN_WIDTH_DESKTOP = "1300px"

Notice that each key begins with VUE_APP_ which tells the Vue CLI to expose this variable to your javascript. You can then access these environmental variables through process.env without importing anything:

1
2
console.log(process.env.VUE_APP_MIN_WIDTH_DESKTOP_WIDE);
console.log(process.env.VUE_APP_MIN_WIDTH_DESKTOP);

It’s a bit of a mouthful, but it works. If the .env file exists, it’s always included. But if you’re making a development build you can also have a .env.development file too (or instead of) and the Vue CLI will merge both .env files. In production mode the .env.development file will be ignored and instead the CLI will look for .env.production and merge that in instead.

Here’s my .env.development, located in the root folder:

VUE_APP_CDN = "http://localhost/cdn"
VUE_APP_ANOTHER_VAR = "Blah"

and here’s my .env.production:

VUE_APP_CDN = "http://my.real.cdn"
VUE_APP_ANOTHER_VAR = "Bleugh"

This is helpful for me as in development I have static assets in a local folder, but in production they’re uploaded and need to point to a CDN. Of course, static assets are often referenced in sass so how do we expose these variables to our scss files and the sass sections of our .vue files?

If you haven’t already got a vue.config.js file then you’ll need to create one, otherwise merge this in:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
let sav = "";
for (let e in process.env) {
	if (/VUE_APP_/i.test(e)) {
		sav += `$${e}: "${process.env[e]}";`;
	}
}

module.exports = {
	css: {
		loaderOptions: {
			sass: {
				data: sav,
			},
		},
	},
};

So what is going on here? The plan is to create a sass variable for every VUE_APP_ env variable. We’ll put the declaration of all the sass variables into one string and then automagically prepend that string onto the front of every scss file or sass section of a vue component. This won’t bloat the bundle or the sass though - this is because the sass variable declarations never make it into the final CSS.

In the code above we’re iterating through every variable inside process.env, but since this includes lots of junk we don’t want (have you seen what’s in there?) we only add env variables that start VUE_APP_ to sav. We make it fit the form of a sass variable declaration. Then later on inside the css loaderOptions configuration, we set the data variable, the contents of which is prepended onto every scss file and sass section of a vue component.

You can then use it in the vue component like this:

1
2
3
4
5
6
<style lang="scss">
.MyDiv {
    margin: 1em 0 0 0;
    background-image: url($VUE_APP_CDN+"/MyImg.png");
}
</style>

or in your scss files similarly:

1
2
3
4
.MyDiv {
	margin: 1em 0 0 0;
	background-image: url($VUE_APP_CDN+"/MyImg.png");
}

Finally, you may also want to access environmental variables inside the template of your vue component. The simplist way to do that is simply to assign the environmental variable to a property in your vue component:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
<template>
	<div>variable value is {{ myVar }}</div>
</template>

<script>
export default {
	data() {
		return {
			myVar: process.env.VUE_APP_CDN,
		};
	},
};
</script>

If that gets a bit tedious then a Vue Mixin might well be what you’re looking for, but we’ll save that for next time…

comments powered by Disqus