-
-
Notifications
You must be signed in to change notification settings - Fork 601
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
Closed
Closed
Changes from 72 commits
Commits
Show all changes
76 commits
Select commit
Hold shift + click to select a range
abe80f0
feat: handle env files
f5bcec5
chore: undo package json changes
011627e
refactor: undo changes to webpack cli
bb735ec
feat: add dotenv webpack plugin package
5a1b4db
refactor: remove cache
95ca34e
fix: default mode should be production
08d16ff
refactor: provide env files as default value
e3a23ce
Merge branch 'master' into feature/env-files
e2cf250
feat: add webpack cli runner code
ca5a782
Merge branch 'master' into feature/env-files
fd6d2ba
fix: add env plugin in build config
d544597
fix: add webpack as dev dep for dotenv webpack plugin
b83ccaf
fix: incorrect path being used in dotenv
a4388d7
test: add a simple test
ab00786
fix: check if webpack is installed before adding dotenv plugin
4e039ba
test: add test for value override
3790945
test: add test for the case when variable does not start with webpack_
c3d97d7
refactor: simplify test code
1aef466
test: add missing assertion
bd6c4a6
chore: debug failing smoketests
74ad4c8
feat: add cli option
afcbf8c
Merge branch 'webpack:master' into feature/env-files
burhanuday f203e8b
Merge branch 'master' into feature/env-files
9286cb5
refactor: redo applying the plugin
8689a2e
test: update snapshots
ca6fc8d
test: update tests
75ebe62
Merge branch 'feature/env-files' of github.com:burhanuday/webpack-cli…
4388c69
test: update snapshots
7ab13cf
fix: undo package json changes
bf8cb58
feat: add .example env file
ff7246f
chore: move dotenv webpack plugin to peerdeps
9d7e1db
feat: rename cli flag
8a45c8b
test: update snapshots
7401474
Merge branch 'master' into feature/env-files
f4b8835
test: add more test cases
9ee072d
test: add test case for mode local files
f7f79b8
fix: update types
88d3660
test: add test case for multiple configs
0158dca
fix: do validation of passed config
f8a6142
test: add more test cases
a165f38
fix: use correct logger name
06399c5
feat: add types and jsdoc
a0534f5
Merge branch 'master' into feature/env-files
7f8c4c7
Merge branch 'master' into feature/env-files
e00469c
chore: bump
d89d73c
feat: add schema utils package
0c477b0
feat: use schema utils package to validate input
8911ba7
test: update tests with new validation errors
00fb0b8
refactor: avoid using object assign
cdeb09f
perf: use webpack fs for better perf
07dbcd5
feat: add env files to build dependencies
99a7163
refactor: extract file exists into a function
03d285c
fix: use push instead of unshift for perf
ac666c4
Merge branch 'master' into feature/env-files
7de5892
feat: make env var prefix configurable
07ac04e
test: add tests for env var prefix
a3e81fa
test: update tests
8e8b78c
Merge branch 'master' into feature/env-files
c191cb2
feat: update priority order to make it same as next
67d8aed
test: add test case to check priority order of variables
a29fc1b
feat: reduce fs call
ca7a264
test: add test case to validate files
8a98a05
refactor: filter variables together
03211fe
feat: check if plugin is installed before adding
1cb9309
chore: update snapshots
26d136f
docs: add documentation for plugin
cdd9bde
docs: update cli docs
8acb20c
docs: update schema
1388d5e
feat: throw error when an empty value is passed
81edc4c
feat: update types
55b64a1
test: add tests for allowEmptyValues
c4ec39b
docs: add docs for allowemptyvalues option
f0e32aa
feat: fail the build on errors
6681233
feat: use webpack error to throw
8d92b67
Merge branch 'master' into feature/env-files
ccf341e
chore: remove author field from package file
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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, | ||
}); | ||
``` |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,22 @@ | ||
{ | ||
"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", | ||
"author": "Burhanuddin Udaipurwala (burhanuday)", | ||
evenstensberg marked this conversation as resolved.
Show resolved
Hide resolved
|
||
"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" | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,191 @@ | ||
const dotenv = require("dotenv"); | ||
const dotenvExpand = require("dotenv-expand"); | ||
const { DefinePlugin } = 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 */ | ||
|
||
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, | ||
}; | ||
} | ||
|
||
/** | ||
* 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)) { | ||
const logger = compiler.getInfrastructureLogger("DotenvWebpackPlugin"); | ||
logger.error(`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; | ||
} | ||
|
||
/** | ||
* 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); | ||
}); | ||
|
||
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) { | ||
const logger = compiler.getInfrastructureLogger("DotenvWebpackPlugin"); | ||
logger.error( | ||
`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; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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" | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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; | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
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*