README
Heavily inspired by dotenv and dotenv-expand, snackables-next is a simplified version of snackables that handles parsing, caching, and assigning one or many .env
files for NextJS.
Quick Links
- Should I commit my .env files?
- How does snackables-next work and will it override already set or predefined variables?
- Why doesn't the parse method automatically assign Envs?
Installation
By default, snackables-next will be compiled with NextJS and will not be needed as a project dependency. If you wish to use this package outside of Next, then you can manually install it using one of the following package managers:
# with npm
npm install snackables-next
# or with Yarn
yarn add snackables-next
Usage
You must require
/import
the snackables-next package as early as possible and invoke the Config method:
require("snackables-next").config({ paths: ["custom/path/to/.env"] });
// import { config } from 'snackables-next';
// config({ paths: ["custom/path/to/.env"] });
Config Method
By invoking the config method, it will read your .env
files, parse the contents, assign them to process.env
, and return an Object
with parsed
, extracted
and cachedEnvFiles
properties (the cache argument of config
must be set to true for cachedEnvFiles
to be utilized):
const { config } = require("snackables-next");
// import { config } from "snackables-next";
const result = config();
console.log("parsed", result.parsed); // process.env with loaded Envs
console.log("extracted", result.extracted); // extracted Envs within a { KEY: VALUE } object
console.log("cachedEnvFiles", result.cachedEnvFiles); // array of file path and file parsed contents objects: [{ path: "path/to/.env", contents: parsed contents as encoded string }]
Additionally, you can pass options to config
.
Config Argument Options
config accepts a single Object
argument with the following properties:
{
dir?: string,
paths?: string[],
encoding?: BufferEncoding,
cache?: string | boolean,
debug?: string | boolean
}
Config dir
Default: process.cwd()
(root directory)
You may specify a single directory path if your files are located elsewhere.
A single directory path as a string
:
require("snackables-next").config({ dir: "path/to/directory" });
// import { config } from "snackables-next"
// config({ dir: "path/to/directory" });
Config paths
Default: [".env"]
You may specify custom paths if your files are located elsewhere (recommended to use absolute path(s) from your root directory).
A single file path or multiple file paths as an Array
of string
s:
require("snackables-next").config({ paths: ["custom/path/to/.env", "custom/path/to/.env.base"] });
// import { config } from "snackables-next"
// config({ paths: ["custom/path/to/.env", "custom/path/to/.env.base"] });
This can also be combined with the Config dir argument to simplify loading from a custom path:
require("snackables-next").config({ dir: "custom/path/to", paths: [".env", ".env.base"] });
// import { config } from "snackables-next"
// config({ dir: "custom/path/to", paths: [".env", ".env.base"] });
Config encoding
Default: utf-8
You may specify the encoding type of your file containing environment variables.
require("snackables-next").config({ encoding: "latin1" });
// import { config } from "snackables-next"
// config({ encoding: "latin1" });
Config cache
Default: false
You may specify whether or not to temporarily cache .env
files once they're loaded. This is useful if you're importing snackables-next multiple times and attempting to load a file within the same process. If the cache contains the loaded .env
file, it will be skipped. This also works for reloading cached files using parse if the process.env
happens to be reset to a default state.
require("snackables-next").config({ cache: true });
// import { config } from "snackables-next"
// config({ cache: true });
Config debug
Default: false
You may turn on logging to help debug file loading.
require("snackables-next").config({ debug: process.env.DEBUG });
// import { config } from "snackables-next"
// config({ debug: process.env.DEBUG });
Parse Method
If you wish to manually parse Envs from a Buffer or file or cache, then parse will read a string, Buffer, or even CachedEnvFiles and parse/assign their contents.
Parse Argument Options
parse accepts one argument:
src: string | Buffer | CacheEnvFiles,
Parse src
For most use cases, you'll want to pass parse a string
or Buffer
as the first argument which returns an extracted
and parsed keys/values as a single Object
(these will NOT be assigned to process.env
. Why not?).
const { readFileSync } = require("fs");
const { parse } = require("snackables-next");
// import { readFileSync } from "fs";
// import { parse } from "snackables-next";
const config = parse(Buffer.from("BASIC=basic")); // will return an object
console.log(typeof config, config); // object { BASIC : 'basic' }
const results = parse(readFileSync("path/to/.env.file", { encoding: "utf8" })); // will return an object
console.log(typeof results, results); // object { KEY : 'value' }
Parse cache
For edge cases, the parse method also accepts the cachedEnvFiles
array (returned by config) as the first argument if the following requirements are met:
The cache argument is set to true
when the config
method is used and process.env.LOADED_CACHE
is not defined.
If the above requirements are met, parse will reapply cached Envs properties to process.env
and return process.env
.
const { config, parse } = require("snackables-next");
// import { config, parse } from "snackables-next";
// loads ".env.base" and ".env.dev" to process.env and returns an array of cached env objects
// cachedEnvFiles = [{ path: "path/to/.env", contents: parsed contents as encoded string }]
const { cachedEnvFiles } = config({ paths: [".env.base", ".env.dev"], cache: true });
// parses and reapplies cached Envs if the process.env.PROPERTY is undefined
// returns process.env with any reapplied Envs from cache
const reappliedProcessEnv = parse(cachedEnvFiles);
console.log(reappliedProcessEnv)
// this lets snackables-next know not to reload from cache
process.env.LOADED_CACHE = "true";
// since process.env.LOADED_CACHE is defined, cache is skipped and process.env is returned as is
const originalProcessEnv = parse(cachedEnvFiles);
console.log(originalProcessEnv)
Parse Rules
The parsing method currently supports the following rules:
BASIC=basic
becomes{BASIC: 'basic'}
- empty lines are skipped
- lines beginning with
#
are treated as comments - empty values become empty strings (
EMPTY=
becomes{EMPTY: ''}
) - inner quotes are maintained (think JSON) (
JSON={"foo": "bar"}
becomes{JSON:"{\"foo\": \"bar\"}"
) - whitespace is removed from both ends of unquoted values (see more on
trim
) (FOO= some value
becomes{FOO: 'some value'}
) - single and double quoted values are escaped (
SINGLE_QUOTE='quoted'
becomes{SINGLE_QUOTE: "quoted"}
) - single and double quoted values maintain whitespace from both ends (
FOO=" some value "
becomes{FOO: ' some value '}
) - double quoted values expand new lines (
MULTILINE="new\nline"
becomes
{MULTILINE: 'new
line'}
Interpolation
You may want to interpolate ENV values based upon a process.env
value or a key within the .env
file. To interpolate a value, simply define it with $KEY
or ${KEY}
, for example:
Input:
MESSAGE=Hello
INTERP_MESSAGE=$MESSAGE World
INTERP_MESSAGE_BRACKETS=${MESSAGE} World
ENVIRONMENT=$NODE_ENV
Output:
MESSAGE=Hello
INTERP_MESSAGE=Hello World
INTERP_MESSAGE_BRACKETS=Hello World
ENVIRONMENT=development
Interpolation Rules
- Values can be interpolated based upon a
process.env
value:BASIC=$NODE_ENV
||BASIC=${NODE_ENV}
- Values in
process.env
take precedence over interpolated values in.env
files - Interpolated values can't be referenced across multiple
.env
s, instead they must only be referenced within the same file - The
$
character must be escaped when it doesn't refer to another key within the.env
file:\$1234
- Do not use escaped
\$
within a value when it's key is referenced by another key:
Input:
A=\$example
B=$A
Output:
A=$example
B=
Fix:
A=example
B=\$A
Output:
A=example
B=$example
FAQ
.env
files?
Should I commit my No. It's strongly recommended not to commit your .env
files to version control. It should only include environment-specific values such as database passwords or API keys. Your production database should have a different password than your development database.
How does snackables-next work and will it override already set or predefined variables?
By default, snackables-next will look for the .env.*
file(s) defined within the Config paths argument to config
and append them to process.env
.
For example:
require("snackables-next").config({ paths: [".env.base", ".env.dev"] });
// import { config } from "snackables-next"
// config({ paths: [".env.base", ".env.dev"] });
in a local enviroment, .env.base
may have static shared database variables:
DB_HOST=localhost
DB_USER=root
DB_PASS=password
while .env.dev
may have environment specific variables:
DB_PASS=password123
HOST=http://localhost
PORT=3000
snackables-next will parse the files and extract the Envs in the order of how they were defined in paths
. In the example above, the DB_PASS
variable within .env.base
would be overidden by .env.dev
because .env.dev
file was imported last and, as a result, its DB_PASS
will be assigned to process.env
.
Envs that are pre-set or become defined within process.env
WILL NOT be overidden. No exceptions.
Why doesn't the parse method automatically assign Envs?
With the exception of assigning pre-cached Envs (which don't require any Env interpretation/interpolation), parse
can not automatically assign Envs as they're extracted.
Why?
Under the hood, the config
method utilizes the parse
method to extract one or multiple .env
files as it loops over the config's paths argument. The config
method expects parse
to return a single Object
of extracted
Envs that will be accumulated with other files' extracted Envs. The result of these accumulated Envs is then assigned to process.env
once -- this approach has the added benefit of prioritizing Envs without using any additional logic since the last set of extracted Envs automatically override any previous Envs (thanks to Object.assign). While allowing Envs to be assigned multiple times to process.env
doesn't appear to be much different in terms of performance, it requires a bit more additional overhead logic to determine which .env
has priority and whether or not to conditionally apply them (including times when you might want to parse Envs, but not neccesarily assign them).
Contributing Guide
See CONTRIBUTING.md
Updates Log
See UPDATESLOG.md