How to Set Up Environment Variables using JSON files with Rollup and TypeScript
Tags: rollup, litelement, typescript, webdev
Published: 2021-02-02 15:42:21

In previous posts, I explained [How to setup a TypeScript project using Rollup.js](https://labs.thisdot.co/blog/how-to-setup-a-typescript-project-using-rollup-js) from scratch. Also, I covered [How to Serve a Single Page Application(SPA) using Rollup.js and Web Dev Server](https://labs.thisdot.co/blog/how-to-serve-a-single-page-application-spa-using-rollup-js-and-web-dev) step by step. Then, I covered [How to Build a LitElement Application](https://labs.thisdot.co/blog/how-to-build-a-litelement-application-with-rollup-js-and-typescript) with [Rollup.js](https://rollupjs.org/) and TypeScript.

In this article, I'll take the result of those tutorials as a starting point to set up environment variables using JSON files.

## Environment Variables

Reading environment variables is very common in software development. It's a helpful way to cover security concerns and even provides the convenience to set configuration parameters in your application.

For example, in the Node.js world, you may need to set the port number to serve your application. For example:

```js
const app = require('http').createServer((req, res) => res.send('Hello world'));
const PORT = process.env.PORT || 3000;

app.listen(PORT, () => {
  console.log(`Server is ready to listen on port ${PORT}`);
});
```

In the previous code, the app will read the value of `PORT` through `process.env.PORT`. If the value doesn't exist, it will take `3000` as a fallback.

### JSON Modules

In today's JavaScript world, it's possible to read [JSON Modules](https://github.com/whatwg/html/issues/4315) as follows:

```js
import config from '../../config.json'; 
```

Also, recent versions of Node.js enables the use of `--experimental-json-module` [flag](https://nodejs.org/api/esm.html#esm_experimental_json_modules) for the module to work.

Let's explore a practical way to add the same support to the current Single-Page Application which is based on TypeScript, [LitElement](https://lit-element.polymer-project.org/), [Rollup.js](https://rollupjs.org/), and [Web Dev Server](https://modern-web.dev/docs/dev-server/overview/).

## Project Setup
### Base Project
Let's create a clone or [download](https://github.com/luixaviles/typescript-rollup/archive/03-build-litelement.zip) the project seed before adding the new configurations and tools:

```bash
git clone https://github.com/luixaviles/typescript-rollup.git
cd typescript-rollup/
git checkout tags/03-build-litelement -b 04-env-config-json
```

The previous commands will download the project and create a new branch `04-env-config-json` to get started.

### Source Code Files
The previous project already contains a set of files and configurations ready to configure and support JSON Modules. Open it with your favorite code editor and take a look at the project structure:

```text
|- typescript-rollup
    |- src/
        |- math/
            |- math.ts
            |- index.ts
        |- string/
            |- string.ts
            |- index.ts
        |- main.ts
    |- index.html
    |- package.json
    |- rollup.config.js
    |- tsconfig.json
    |- web-dev-server.config.js
```

### Installing Rollup Plugins

Since the Single-page application considers Rollup.js as the module bundler, we'll need to install a couple of node modules first:

```bash
npm install --save-dev @rollup/plugin-json @web/dev-server-rollup
```

* **@rollup/plugin-json** will be in charge to convert `.json` files to ES6 modules.
* **@web/dev-server-rollup** is an adapter for using rollup plugins in [Web Dev Server](https://modern-web.dev/docs/dev-server/overview/), which is used to "serve" the web application in development mode.

The `package.json` file should have the new dependencies listed as follows:

```json
{
  ...
  "devDependencies": {
    ...
    "@rollup/plugin-json": "^4.1.0",
    "@web/dev-server-rollup": "^0.3.2",
    ...
  },
}
```

## Reading the JSON Configuration
Let's add the following `env-config.json` file into the root of the project:

```json
{
  "environment": "production",
  "host": {
    "protocol": "http",
    "hostname": "localhost",
    "port": 8080
  }
}
```

Of course, you can set your variables according to the requirements: access keys, platform, etc.

Since you may have sensitive data in that file, it should not be versioned in any case. It's good practice to add it to the `.gitignore` file:

```text
# Environment variables
env-config.json
```

Also, you can provide a `env-config.json.example` file as an example so that developers, Dev Ops, or any member of the team can create a configuration file from it:

```json
{
  "environment": "development",
  "host": {
    "protocol": "http",
    "hostname": "localhost",
    "port": 8080
  }
}
```

### Creating the TypeScript Model
It's time to define the TypeScript model for our configurations. First, let's create a `src/environment/environment-model.ts`:

```ts
// environment-model.ts
export interface Host {
  protocol: string;
  hostname: string;
  port: number | string;
}

export interface EnvConfig {
  environment: string;
  host: Host;
}
```

Next, create a `src/environment/environment.ts` file with the following content:

```ts
import { EnvConfig } from './environment-model';
import envConfig from '../../env-config.json'; 

export const env = envConfig as EnvConfig;
```

From now on, the configurations will be available in the `env` variable.

### TypeScript Configuration
You may see a couple of compilation errors from TypeScript after adding the `environment.ts` file. To avoid them, it's required to add new compiler options in the `tsconfig.json` file:

```json
{
  "compilerOptions": {
    ...
    "resolveJsonModule": true,
    "allowSyntheticDefaultImports": true
  }
}
```

Why are these changes needed?

* The **resolveJsonModule** flag allows the compiler to include modules with `.json` extension.
* The **allowSyntheticDefaultImports** flag allows default imports from modules with no default export. As the [documentation](https://www.typescriptlang.org/docs/handbook/compiler-options.html) says, it's just _type checking_.

### Reading Configuration Values
Let's read the configuration values to be rendered into an existing web component. Update the `main.ts` file with the following content:

```ts
// main.ts
import { LitElement, html, customElement, css, property } from 'lit-element';
import { env } from './environment/environment';

@customElement('comp-main')
export class CompMain extends LitElement {

    static styles = css`
    :host {
        display: flex;
    }
    `;

    @property({ type: String }) message: string = 'Welcome to LitElement';

    render() {
        return html`
        <div>
            <h1>${this.message}</h1>
            <span>This App uses:</span>
            <ul>
                <li>TypeScript</li>
                <li>Rollup.js</li>
                <li>es-dev-server</li>
            </ul>
            <span>Running environment: ${env.environment}</span>
            <ul>
                <li>Protocol: ${env.host.protocol}</li>
                <li>Hostname: ${env.host.hostname}</li>
                <li>Port: ${env.host.port}</li>
            </ul>
        </div>
        `;
    }
}
```

Just pay attention to the import line: import { env } from './environment/environment'. Then the EnvConfig object (TypeScript model) will be available as the env variable.

### Rollup Configuration
As stated above, the project is already configured and uses [Rollup](https://rollupjs.org/guide/en/) as the module bundler. Let's move forward to allow reading the JSON content for the build.

```js
// rollup.config.js
import merge from 'deepmerge';
import { createSpaConfig } from '@open-wc/building-rollup';
import json from '@rollup/plugin-json';

const baseConfig = createSpaConfig({
  developmentMode: process.env.ROLLUP_WATCH === 'true',
  injectServiceWorker: false
});

export default merge(baseConfig, {
  // any <script type="module"> inside will be bundled by rollup
  input: './index.html',
  plugins: [
    json()
  ]
});
```

For every building process, the new `json()` plugin will be in charge to process the `.json` files. You can call any other Rollup plugin in the `plugins` array.

### Web Dev Server Configuration

If you try to serve the application at this point, you'll find a blank page and some errors in the browser's console:

```text
Failed to load module script: The server responded with a non-JavaScript MIME type of "application/json". 
Strict MIME type checking is enforced for module scripts per HTML spec.
```

![Failed to load module script non-JavaScript MIME type](//images.ctfassets.net/zojzzdop0fzx/2byu1QJLrt45w8vWs5eBjX/2f4b2a91cf7544b206f952089e333003/non-javascript-mime-type-json.png)

That is because we configured Rollup for the build process only. However, for serving the app in a "development mode", we're using Web Dev Server. 

Let's apply a couple of changes in the `web-dev-server.config.js` file to fix it:

```js
// web-dev-server.config.js

const { rollupAdapter } = require('@web/dev-server-rollup');
const json =  require('@rollup/plugin-json');

module.exports = {
    port: 8000,
    nodeResolve: true,
    open: true,
    watch: true,
    appIndex: 'index.html',
    mimeTypes: {
      // serve all json files as js
      '**/*.json': 'js'
    },
    plugins: [rollupAdapter(json())],
  };
```

Let's explain what's happening in that code:

* The `@web/dev-server-rollup` package allows using Rollup plugins in Web Dev Server.
* Instead of using `json()` plugin directly, it needs to be passed through the `rollupAdapter` function first.

When you're done with those changes, run  `npm run start` again to see the final result.

![Running Single-page app with JSON Environment variables](//images.ctfassets.net/zojzzdop0fzx/4GVoe28ud7HhK8m48S8qnY/70107c65879690b997880aac34016aac/running-app-environment-variables-json.png)

## Source Code of the Project

Find the complete project in this GitHub repository: [typescript-rollup](https://github.com/luixaviles/typescript-rollup). Do not forget to give it a star ⭐️  and play around with the code.

Feel free to reach out on [Twitter](https://twitter.com/luixaviles) if you have any questions. Follow me on [GitHub](https://github.com/luixaviles) to see more about my work.

---
*This Dot Labs is a modern web consultancy focused on helping companies realize their digital transformation efforts. For expert architectural guidance, training, or consulting in React, Angular, Vue, Web Components, GraphQL, Node, Bazel, or Polymer, visit [thisdotlabs.com](https://www.thisdotlabs.com).*

*This Dot Media is focused on creating an inclusive and educational web for all.  We keep you up to date with advancements in the modern web through events, podcasts, and free content. To learn, visit [thisdot.co](https://www.thisdot.co).*