-
-
Notifications
You must be signed in to change notification settings - Fork 609
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
feat: built in env files support #3759
Changes from all commits
abe80f0
f5bcec5
011627e
bb735ec
5a1b4db
95ca34e
08d16ff
e3a23ce
e2cf250
ca5a782
fd6d2ba
d544597
b83ccaf
a4388d7
ab00786
4e039ba
3790945
c3d97d7
1aef466
bd6c4a6
74ad4c8
afcbf8c
f203e8b
9286cb5
8689a2e
ca6fc8d
75ebe62
4388c69
7ab13cf
bf8cb58
ff7246f
9d7e1db
8a45c8b
7401474
f4b8835
9ee072d
f7f79b8
88d3660
0158dca
f8a6142
a165f38
06399c5
a0534f5
7f8c4c7
e00469c
d89d73c
0c477b0
8911ba7
00fb0b8
cdeb09f
07dbcd5
99a7163
03d285c
ac666c4
7de5892
07ac04e
a3e81fa
8e8b78c
c191cb2
67d8aed
a29fc1b
ca7a264
8a98a05
03211fe
1cb9309
26d136f
cdd9bde
8acb20c
1388d5e
81edc4c
55b64a1
c4ec39b
f0e32aa
6681233
8d92b67
ccf341e
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,91 @@ | ||
# Dotenv Webpack Plugin | ||
|
||
`dotenv-webpack-plugin` is a webpack plugin that enables consumers to load environment variables from dotenv files. This plugin simplifies the process of managing environment-specific configurations in your webpack projects. | ||
|
||
## Features | ||
|
||
- Loads environment variables from dotenv files | ||
- Provides a convenient way to manage environment-specific configurations | ||
- Fully configurable via an options API | ||
|
||
## Installation | ||
|
||
Install `dotenv-webpack-plugin` using npm: | ||
|
||
```bash | ||
npm install dotenv-webpack-plugin --save-dev | ||
``` | ||
|
||
or using yarn: | ||
|
||
```bash | ||
yarn add dotenv-webpack-plugin --dev | ||
``` | ||
|
||
or using pnpm: | ||
|
||
```bash | ||
pnpm add dotenv-webpack-plugin --save-dev | ||
``` | ||
|
||
## Usage | ||
|
||
To use `dotenv-webpack-plugin`, follow these steps: | ||
|
||
1. Create a `.env` file in the root directory of your project. Add each environment variable on a new lines in the form of `PUBLIC_NAME=VALUE`. By default only variables that are prefixed with `PUBLIC_` will be exposed to webpack. The prefix can be changed by passing the `envVarPrefix` option to the plugin. | ||
|
||
1. Import `dotenv-webpack-plugin` in your webpack configuration file: | ||
|
||
```javascript | ||
const DotenvWebpackPlugin = require("dotenv-webpack-plugin"); | ||
``` | ||
|
||
1. Add an instance of DotenvWebpackPlugin to your webpack plugins: | ||
|
||
```javascript | ||
module.exports = { | ||
// Your webpack configuration options... | ||
plugins: [new DotenvWebpackPlugin()], | ||
}; | ||
``` | ||
|
||
## Configuration Options | ||
|
||
DotenvWebpackPlugin accepts the following configuration options: | ||
|
||
1. `envFiles`: An array of dotenv files to load. By default, DotenvWebpackPlugin will look for the following files in the root directory of your project: | ||
|
||
- `.env.[mode].local` | ||
- `.env.local` | ||
- `.env.[mode]` | ||
- `.env` | ||
- `.env.example` | ||
|
||
The `[mode]` placeholder will be replaced with the current webpack mode. For example, if the current webpack mode is `development`, DotenvWebpackPlugin will look for the following files: | ||
|
||
- `.env.development.local` | ||
- `.env.local` | ||
- `.env.development` | ||
- `.env` | ||
- `.env.example` | ||
|
||
If the same variable is defined in multiple files, the value from the file with the highest precedence will be used. The precedence order is same as the order of files listed above. | ||
|
||
While passing an array of dotenv files, the path towards the right of the array will have the highest precedence. For example, if you pass `["./.env", "./.env.local"]`, the value from `.env.local` will be used if the same variable is defined in both files. | ||
|
||
1. `envVarPrefix`: The prefix to use when loading environment variables. By default, DotenvWebpackPlugin will look for variables prefixed with `PUBLIC_`. | ||
|
||
1. `prefixes`: An array of prefixes to prepend to the names of environment variables. By default, DotenvWebpackPlugin will prepend `process.env.` and `import.meta.env.` to the names of environment variables. | ||
|
||
1. `allowEmptyValues`: A boolean value indicating whether to allow empty values. By default this value is set to `false`, DotenvWebpackPlugin will throw an error if an environment variable is defined without a value. | ||
|
||
You can pass these options when creating an instance of DotenvWebpackPlugin: | ||
|
||
```javascript | ||
new DotenvWebpackPlugin({ | ||
envFiles: ["./.env", "./.env.local"], | ||
prefixes: ["process.env.", "import.meta.env."], | ||
envVarPrefix: "PUBLIC_", | ||
allowEmptyValues: false, | ||
}); | ||
``` |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,21 @@ | ||
{ | ||
"name": "dotenv-webpack-plugin", | ||
"version": "1.0.0", | ||
"description": "A webpack plugin to support env files", | ||
"main": "src/index.js", | ||
"types": "src/types.d.ts", | ||
"repository": "https://github.com/webpack/webpack-cli", | ||
"license": "MIT", | ||
"private": true, | ||
"dependencies": { | ||
"dotenv": "^16.0.3", | ||
"dotenv-expand": "^10.0.0", | ||
"schema-utils": "^4.0.1" | ||
}, | ||
"peerDependencies": { | ||
"webpack": "5.x.x" | ||
}, | ||
"devDependencies": { | ||
"webpack": "^5.81.0" | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,212 @@ | ||
const dotenv = require("dotenv"); | ||
const dotenvExpand = require("dotenv-expand"); | ||
const { DefinePlugin, WebpackError } = require("webpack"); | ||
const { validate } = require("schema-utils"); | ||
const schema = require("./options.json"); | ||
|
||
/** @typedef {import("./types").Config} Config */ | ||
/** @typedef {import("schema-utils/declarations/validate").Schema} Schema */ | ||
/** @typedef {import("webpack").Compiler} Compiler */ | ||
/** @typedef {import("webpack").Compilation} Compilation */ | ||
|
||
class DotenvWebpackPlugin { | ||
/** | ||
* Dotenv Webpack Plugin | ||
* @param {Config} options - Configuration options | ||
*/ | ||
constructor(options = {}) { | ||
validate(/** @type {Schema} */ (schema), options || {}, { | ||
name: "DotenvWebpackPlugin", | ||
baseDataPath: "options", | ||
}); | ||
|
||
const currentDirectory = process.cwd(); | ||
|
||
this.defaultFileList = [ | ||
`${currentDirectory}/.env.example`, // loaded in all cases | ||
`${currentDirectory}/.env`, // loaded in all cases | ||
`${currentDirectory}/.env.[mode]`, // only loaded in specified mode | ||
`${currentDirectory}/.env.local`, // loaded in all cases, ignored by git | ||
`${currentDirectory}/.env.[mode].local`, // only loaded in specified mode, ignored by git | ||
]; | ||
|
||
const { | ||
// priority is in ascending order | ||
// files at the end of the array have higher priority | ||
envFiles = this.defaultFileList, | ||
prefixes = ["process.env.", "import.meta.env."], | ||
envVarPrefix = "PUBLIC_", | ||
alexander-akait marked this conversation as resolved.
Show resolved
Hide resolved
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Let's allow it to be |
||
allowEmptyValues = false, | ||
} = options; | ||
|
||
this.options = { | ||
envFiles, | ||
prefixes, | ||
envVarPrefix, | ||
allowEmptyValues, | ||
}; | ||
|
||
this.errors = []; | ||
} | ||
|
||
/** | ||
* Default file list and the options file list are updated with the | ||
* value of the mode if [mode] placeholder is used | ||
* @param {String} mode - Webpack mode | ||
*/ | ||
updateFileListWithMode(mode) { | ||
this.options.envFiles = this.options.envFiles.map((environmentFile) => | ||
environmentFile.replace(/\[mode\]/g, mode), | ||
); | ||
this.defaultFileList = this.defaultFileList.map((environmentFile) => | ||
environmentFile.replace(/\[mode\]/g, mode), | ||
); | ||
} | ||
|
||
/** | ||
* Read file from path and parse it | ||
* @param {Compiler} compiler - Webpack compiler | ||
* @param {string} environmentFile - Path to environment file | ||
*/ | ||
readFile(compiler, environmentFile) { | ||
return new Promise((resolve, reject) => { | ||
const fs = compiler.inputFileSystem; | ||
|
||
fs.readFile(environmentFile, (err, environmentFileContents) => { | ||
if (err) { | ||
if (!this.defaultFileList.includes(environmentFile)) { | ||
this.collectError(`Could not read ${environmentFile}`); | ||
return reject(err); | ||
} else { | ||
return resolve(); | ||
} | ||
} | ||
|
||
const parsedEnvVariables = dotenv.parse(environmentFileContents); | ||
|
||
resolve(parsedEnvVariables); | ||
}); | ||
alexander-akait marked this conversation as resolved.
Show resolved
Hide resolved
|
||
}); | ||
} | ||
|
||
filterVariables(envVariables) { | ||
const filteredEnvVariables = {}; | ||
|
||
for (const [key, value] of Object.entries(envVariables)) { | ||
// only add variables starting with the provided prefix | ||
if (key.startsWith(this.options.envVarPrefix)) { | ||
filteredEnvVariables[key] = value; | ||
} | ||
} | ||
|
||
return filteredEnvVariables; | ||
} | ||
|
||
assignPrefixes(envVariables) { | ||
const prefixedEnvVariables = {}; | ||
|
||
for (const [key, value] of Object.entries(envVariables)) { | ||
for (let index = 0; index < this.options.prefixes.length; index++) { | ||
const prefix = this.options.prefixes[index]; | ||
prefixedEnvVariables[`${prefix}${key}`] = value; | ||
} | ||
} | ||
|
||
return prefixedEnvVariables; | ||
} | ||
|
||
/** | ||
* Throw collected errors to fail the build | ||
* @param {Compilation} compilation - Webpack compilation | ||
*/ | ||
throwErrors(compilation) { | ||
const errors = this.errors.map((error) => { | ||
const webpackError = new WebpackError(error); | ||
webpackError.name = "DotenvWebpackPluginError"; | ||
return webpackError; | ||
}); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Also we need this logic https://github.com/mrsteele/dotenv-webpack/blob/master/src/index.js#L62 There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. from what i understand, that plugin implements a "blueprint" feature. We have not implemented it. Although i dont find it entirely necessary, I am open to suggestions on why this is required There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Because it allows to catch problems on startup |
||
compilation.errors.push(...errors); | ||
} | ||
|
||
collectError(errorMessage) { | ||
this.errors.push(errorMessage); | ||
} | ||
|
||
/** | ||
* Get list of empty values | ||
* @param {Object} envVariables - Environment variables | ||
* @returns {Array} - List of empty values | ||
*/ | ||
getEmptyValues(envVariables) { | ||
const emptyValues = []; | ||
|
||
for (const [key, value] of Object.entries(envVariables)) { | ||
if (value === "") { | ||
emptyValues.push(key); | ||
} | ||
} | ||
|
||
return emptyValues; | ||
} | ||
|
||
/** | ||
* Webpack apply hook | ||
* @param {Compiler} compiler - Webpack compiler | ||
* @returns {void} | ||
*/ | ||
apply(compiler) { | ||
const mode = compiler.options.mode || "production"; | ||
this.updateFileListWithMode(mode); | ||
|
||
compiler.hooks.beforeRun.tapPromise("DotenvWebpackPlugin", (compiler) => { | ||
compiler.hooks.compilation.tap("DotenvWebpackPlugin", (compilation) => { | ||
compilation.buildDependencies.addAll(this.options.envFiles); | ||
if (this.errors.length > 0) { | ||
this.throwErrors(compilation); | ||
} | ||
}); | ||
|
||
return Promise.all( | ||
this.options.envFiles.map((environmentFile) => this.readFile(compiler, environmentFile)), | ||
) | ||
.then((valuesList) => { | ||
const envVariables = {}; | ||
|
||
valuesList.forEach((values) => { | ||
if (values) { | ||
Object.entries(values).forEach(([key, value]) => { | ||
envVariables[key] = value; | ||
}); | ||
} | ||
}); | ||
|
||
const filteredEnvVariables = this.filterVariables(envVariables); | ||
|
||
const emptyValues = this.getEmptyValues(filteredEnvVariables); | ||
|
||
if (!this.options.allowEmptyValues && emptyValues.length > 0) { | ||
this.collectError( | ||
`Environment variables cannot have an empty value. The following variables are empty: ${emptyValues.join( | ||
", ", | ||
)}`, | ||
); | ||
return; | ||
evenstensberg marked this conversation as resolved.
Show resolved
Hide resolved
|
||
} | ||
|
||
const prefixedEnvVariables = this.assignPrefixes(filteredEnvVariables); | ||
|
||
// expand environment vars | ||
const expandedEnvVariables = dotenvExpand.expand({ | ||
parsed: prefixedEnvVariables, | ||
// don't write to process.env | ||
ignoreProcessEnv: true, | ||
}).parsed; | ||
|
||
new DefinePlugin(expandedEnvVariables).apply(compiler); | ||
}) | ||
.catch(() => {}); | ||
}); | ||
} | ||
} | ||
|
||
module.exports = DotenvWebpackPlugin; |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,30 @@ | ||
{ | ||
"definitions": {}, | ||
"title": "DotenvWebpackPlugin", | ||
"type": "object", | ||
"additionalProperties": false, | ||
"properties": { | ||
"envFiles": { | ||
"description": "The paths to the .env files to load", | ||
"type": "array", | ||
"items": { | ||
"type": "string" | ||
} | ||
}, | ||
"prefixes": { | ||
"description": "The prefixes to prepend to the environment variables", | ||
"type": "array", | ||
"items": { | ||
"type": "string" | ||
} | ||
}, | ||
"envVarPrefix": { | ||
"description": "The prefix to filter environment variables by", | ||
"type": "string" | ||
}, | ||
"allowEmptyValues": { | ||
"description": "Whether to allow empty values for environment variables", | ||
"type": "boolean" | ||
} | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,6 @@ | ||
export type Config = { | ||
envFiles?: string[]; | ||
prefixes?: string[]; | ||
envVarPrefix?: string; | ||
allowEmptyValues?: boolean; | ||
}; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
test/**/**/dist*