Skip to content

Commit

Permalink
Merge pull request #4 from expatfile/@HofmannZ/feature/new-utilites
Browse files Browse the repository at this point in the history
New utilities
  • Loading branch information
HofmannZ authored May 1, 2023
2 parents 76f7001 + 23c6f30 commit a1eddb0
Show file tree
Hide file tree
Showing 10 changed files with 254 additions and 23 deletions.
102 changes: 88 additions & 14 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
# Next.js Runtime Environment Configuration

![GitHub branch checks state][build-url] [![codecov][cov-img]][cov-url]

Populates your environment at **runtime** rather than **build time**.
# Next.js Runtime Environment Configuration

Populate your environment at **runtime** rather than **build time**.

- Isomorphic - Server and browser compatible (and even in middleware.)
- Static site generation support.
Expand Down Expand Up @@ -49,7 +49,7 @@ containing allow-listed environment variables with a `NEXT_PUBLIC_` prefix.

2. Add the following to the head section of your `pages/_document.js`:

```jsx
```tsx
// pages/_document.tsx
<script src="/__ENV.js" />
```
Expand All @@ -59,21 +59,20 @@ This will load the generated file in the browser.
### Usage 🧑‍💻

In the browser, your variables will now be available at
`window.__ENV.NEXT_PUBLIC_FOO` and on the server at `process.env.NEXT_PUBLIC_FOO`.

#### Helper function 🙌

We have included the `env()` helper function to make retrieving a value easier:
`window.__ENV.NEXT_PUBLIC_FOO` and on the server at
`process.env.NEXT_PUBLIC_FOO`. For example:

```bash
# .env
NEXT_PUBLIC_FOO="foo"
BAR="bar"
NEXT_PUBLIC_BAZ="baz"
```

```jsx
```tsx
// pages/some-page.tsx
type Props = {
bar: string,
bar: string;
};

export default function SomePage({ bar }: Props) {
Expand All @@ -93,13 +92,24 @@ export const getStaticProps: GetStaticProps = async (context) => {
};
```

Becomes...
### Utilities 🛠

```jsx
We have included some utility function to make it even easier to work with
environment variables.

#### `env(key: string): string | undefined`

Returns the value of the environment variable with the given key. If the
environment variable is not found, it returns undefined.

##### Example

```tsx
// pages/some-page.tsx
import { env } from 'next-runtime-env';

type Props = {
bar: string,
bar: string;
};

export default function SomePage({ bar }: Props) {
Expand All @@ -119,6 +129,70 @@ export const getStaticProps: GetStaticProps = async (context) => {
};
```

#### `allEnv(): NodeJS.ProcessEnv`

Returns all environment variables as a `NodeJS.ProcessEnv` object regardless of
the platform. This is useful if you want to destructure multiple env vars at
once.

##### Example

```tsx
// pages/some-page.tsx
import { allEnv } from 'next-runtime-env';

type Props = {
bar: string;
};

export default function SomePage({ bar }: Props) {
const { NEXT_PUBLIC_FOO, NEXT_PUBLIC_BAZ } = allEnv();

return (
<div>
{NEXT_PUBLIC_FOO} {NEXT_PUBLIC_BAZ} {bar}
</div>
);
}

export const getStaticProps: GetStaticProps = async (context) => {
const { BAR } = allEnv();

return {
props: {
bar: BAR,
},
};
};
```

#### `makeEnvPublic(key: string | string[]): void`

Makes an environment variable with a given key public. This is useful if you
want to use an environment variable in the browser, but it was was not declared
with a `NEXT_PUBLIC_` prefix.

For ease of use you can also make multiple env vars public at once by passing an
array of keys.

##### Example

```js
// next.config.js
const { configureRuntimeEnv } = require('next-runtime-env/build/configure');
const { makeEnvPublic } = require('next-runtime-env/build/make-env-public');

// Given that `FOO` is declared as a regular env var, not a public one. This
// will make it public and available as `NEXT_PUBLIC_FOO`.
makeEnvPublic('FOO');

// Or you can make multiple env vars public at once.
makeEnvPublic(['BAR', 'BAZ']);

// This will generate the `__ENV.js` file and include `NEXT_PUBLIC_FOO`.
configureRuntimeEnv();
```

### Maintenance 👷

This package is maintained and actively used by [Expatfile.tax][expatfile-site].
Expand Down
52 changes: 52 additions & 0 deletions src/all-env.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
import { allEnv } from './all-env';

let envBackup: NodeJS.ProcessEnv;

beforeAll(() => {
envBackup = process.env;
process.env = {};
});

afterAll(() => {
process.env = envBackup;
});

describe('allEnv()', () => {
afterEach(() => {
delete process.env.FOO;
delete process.env.NEXT_PUBLIC_BAR;
delete process.env.NEXT_PUBLIC_BAZ;

// eslint-disable-next-line @typescript-eslint/no-explicit-any
global.window = undefined as any;
});

it('should return all values from the server', () => {
process.env.FOO = 'foo';
process.env.NEXT_PUBLIC_BAR = 'bar';
process.env.NEXT_PUBLIC_BAZ = 'baz';

expect(allEnv()).toEqual({
FOO: 'foo',
NEXT_PUBLIC_BAR: 'bar',
NEXT_PUBLIC_BAZ: 'baz',
});
});

it('should return all values from the browser', () => {
Object.defineProperty(global, 'window', {
value: {
__ENV: {
NEXT_PUBLIC_BAR: 'bar',
NEXT_PUBLIC_BAZ: 'baz',
},
},
writable: true,
});

expect(allEnv()).toEqual({
NEXT_PUBLIC_BAR: 'bar',
NEXT_PUBLIC_BAZ: 'baz',
});
});
});
14 changes: 14 additions & 0 deletions src/all-env.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import { isBrowser } from './utils/is-browser';

/**
* Reads all safe environment variables from the browser or all environment
* variables from the server.
*/
export function allEnv(): NodeJS.ProcessEnv {
if (isBrowser()) {
// eslint-disable-next-line no-underscore-dangle
return window.__ENV;
}

return process.env;
}
8 changes: 4 additions & 4 deletions src/env.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ describe('env()', () => {
it('should return a value from the server', () => {
process.env.FOO = 'foo';

expect(env('FOO')).toBe('foo');
expect(env('FOO')).toEqual('foo');
});

it('should return a value from the browser', () => {
Expand All @@ -24,11 +24,11 @@ describe('env()', () => {
writable: true,
});

expect(env('NEXT_PUBLIC_FOO')).toBe('foo');
expect(env('NEXT_PUBLIC_FOO')).toEqual('foo');
});

it('should return undefined when variable does not exist on the server', () => {
expect(env('BAM_BAM')).toBe(undefined);
expect(env('BAM_BAM')).toEqual(undefined);
});

it('should return undefined when variable does not exist in the browser', () => {
Expand All @@ -41,6 +41,6 @@ describe('env()', () => {
writable: true,
});

expect(env('BAM_BAM')).toBe(undefined);
expect(env('BAM_BAM')).toEqual(undefined);
});
});
2 changes: 1 addition & 1 deletion src/env.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import { isBrowser } from './utils/is-browser';
* Reads a safe environment variable from the browser or any environment
* variable from the server (process.env).
*/
export function env(key: string) {
export function env(key: string): string | undefined {
if (isBrowser()) {
// eslint-disable-next-line no-underscore-dangle
return window.__ENV[key];
Expand Down
6 changes: 3 additions & 3 deletions src/helpers/write-browser-env.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ describe('writeBrowserEnv()', () => {
writeBrowserEnv({});

expect(infoSpy).toHaveBeenCalledWith(
'next-runtime-env: Writing browser runtime env',
'> [next-runtime-env] Writing browser runtime env',
file
);

Expand All @@ -44,7 +44,7 @@ describe('writeBrowserEnv()', () => {
});

expect(infoSpy).toHaveBeenCalledWith(
'next-runtime-env: Writing browser runtime env',
'> [next-runtime-env] Writing browser runtime env',
file
);

Expand All @@ -61,7 +61,7 @@ describe('writeBrowserEnv()', () => {
});

expect(infoSpy).toHaveBeenCalledWith(
'next-runtime-env: Writing browser runtime env',
'> [next-runtime-env] Writing browser runtime env',
file
);

Expand Down
2 changes: 1 addition & 1 deletion src/helpers/write-browser-env.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ export function writeBrowserEnv(env: NodeJS.ProcessEnv) {
const path = `${base}/public/__ENV.js`;

// eslint-disable-next-line no-console
console.info('next-runtime-env: Writing browser runtime env', path);
console.info('> [next-runtime-env] Writing browser runtime env', path);

const content = `window.__ENV = ${JSON.stringify(env)};`;

Expand Down
1 change: 1 addition & 0 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,4 +7,5 @@ declare global {
}
}

export { allEnv } from './all-env';
export { env } from './env';
56 changes: 56 additions & 0 deletions src/make-env-public.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
import { makeEnvPublic } from './make-env-public';

const warnSpy = jest.spyOn(console, 'warn');

beforeAll(() => {
warnSpy.mockImplementation();
});

afterAll(() => {
warnSpy.mockRestore();
});

describe('makeEnvPublic()', () => {
afterEach(() => {
delete process.env.FOO;
delete process.env.BAR;
delete process.env.BAZ;
delete process.env.NEXT_PUBLIC_FOO;
delete process.env.NEXT_PUBLIC_BAR;
delete process.env.NEXT_PUBLIC_BAZ;
});

it('should prefix an env var with NEXT_PUBLIC_', () => {
process.env.FOO = 'foo';

makeEnvPublic('FOO');

expect(process.env.FOO).toEqual('foo');
expect(process.env.NEXT_PUBLIC_FOO).toEqual('foo');
});

it('should prefix multiple env vars with NEXT_PUBLIC_', () => {
process.env.FOO = 'foo';
process.env.BAR = 'bar';
process.env.BAZ = 'baz';

makeEnvPublic(['FOO', 'BAR', 'BAZ']);

expect(process.env.FOO).toEqual('foo');
expect(process.env.NEXT_PUBLIC_FOO).toEqual('foo');
expect(process.env.BAR).toEqual('bar');
expect(process.env.NEXT_PUBLIC_BAR).toEqual('bar');
expect(process.env.BAZ).toEqual('baz');
expect(process.env.NEXT_PUBLIC_BAZ).toEqual('baz');
});

it('should warn when the env var already starts with NEXT_PUBLIC_', () => {
process.env.NEXT_PUBLIC_FOO = 'foo';

makeEnvPublic('NEXT_PUBLIC_FOO');

expect(warnSpy).toHaveBeenCalledWith(
'> [next-runtime-env] The environment variable "NEXT_PUBLIC_FOO" is already public.'
);
});
});
34 changes: 34 additions & 0 deletions src/make-env-public.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
function prefixKey(key: string) {
// Check if the key already is already public.
if (/^NEXT_PUBLIC_/i.test(key)) {
// eslint-disable-next-line no-console
console.warn(
`> [next-runtime-env] The environment variable "${key}" is already public.`
);
}

const prefixedKey = `NEXT_PUBLIC_${key}`;

process.env[prefixedKey] = process.env[key];
}

/**
* Make a private environment variable public, so that it can be accessed in the
* browser.
*
* Usage:
* ```ts
* // Make a single variable public.
* makeEnvPublic('FOO');
*
* // Make multiple variables public.
* makeEnvPublic(['FOO', 'BAR', 'BAZ']);
* ```
*/
export function makeEnvPublic(key: string | string[]): void {
if (typeof key === 'string') {
prefixKey(key);
} else {
key.forEach(prefixKey);
}
}

0 comments on commit a1eddb0

Please sign in to comment.