-
-
Notifications
You must be signed in to change notification settings - Fork 636
feat: dot-env plugin #4113
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
Closed
Closed
feat: dot-env plugin #4113
Changes from all commits
Commits
Show all changes
43 commits
Select commit
Hold shift + click to select a range
93a45c8
test
info-arnav 80d7233
Merge branch 'master' of https://github.com/info-arnav/webpack-cli
info-arnav 4355ebc
add: add boilerplate script
info-arnav 5704492
add: react boilerplate
info-arnav 1591b8d
add: react boilerplate
info-arnav 78c59c8
feat: dot env plugin
info-arnav e3c4bb4
dot-env
info-arnav 14062c5
feat: dot-env support
info-arnav 41385a0
feat: dot-env plugin
info-arnav 7c1811f
feat: dot-env plugin
info-arnav 06bc78d
Merge branch 'master' into dot-env
info-arnav ef9e163
fix: changed the directory and removed silent
info-arnav 132d757
Merge branch 'dot-env' of https://github.com/info-arnav/webpack-cli i…
info-arnav ad0f842
fix: yarn.lock conflict
info-arnav 8ee2144
feat: multiple prefix
info-arnav 9fd7dca
feat: multiple prefix
info-arnav 98499d3
feat: added comment for future aspect
info-arnav f4f53b8
fix: removed loadconfig from webpack-cli
info-arnav e471b42
feat: added the --dot-env arg
info-arnav 1bb6371
fix :changed the dotenv-webpack-plugin directory
info-arnav e60dffb
fix: added test -> positive
info-arnav 75ec38f
fix: added webpack logger
info-arnav 4c53402
fix: added tests
info-arnav 9d2cae1
fix: used Map for cache
info-arnav 7ed4652
fix: private changed with # and cache removed
info-arnav efeff89
fix: cache implimented properly
info-arnav 7491651
fix: type target string
info-arnav 6269163
fix: reolved issues
info-arnav 67d08db
Merge branch 'master' into dot-env
info-arnav 5ae5ffa
fix: reolved all the issues, tests pending
info-arnav 143344b
fix: renamed config to options
info-arnav f214c62
feat: added safe method
info-arnav da567e9
fix: replaced for-each with for..of
info-arnav 903104b
fix: resolved all issues except hook
info-arnav 61fc823
fix: removed async, and made small changes:
info-arnav b3049cf
fix: compilation is defined
info-arnav f45788a
fix: corrected types
info-arnav 7c4e8c8
test: added tests for safe, expand and empty modes
info-arnav 5a1e693
fix: resolved small changes
info-arnav 969181e
Merge branch 'master' into dot-env
info-arnav a51a1fc
fix: removed compilation hook
info-arnav 397d73a
Merge branch 'dot-env' of https://github.com/info-arnav/webpack-cli i…
info-arnav 78eac39
adding a compilation hook inside the initialize hook
info-arnav 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 hidden or 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
Empty file.
Empty file.
This file contains hidden or 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 hidden or 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 hidden or 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
220 changes: 220 additions & 0 deletions
220
packages/webpack-cli/src/plugins/dotenv-webpack-plugin.ts
This file contains hidden or 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,220 @@ | ||
import dotenv from "dotenv"; | ||
import { Compiler, DefinePlugin, cache } from "webpack"; | ||
|
||
interface EnvVariables { | ||
[key: string]: string; | ||
} | ||
interface DotenvConfig { | ||
paths?: string[]; | ||
prefixes?: string[]; | ||
systemvars?: boolean; | ||
allowEmptyValues?: boolean; | ||
info-arnav marked this conversation as resolved.
Show resolved
Hide resolved
|
||
expand?: boolean; | ||
info-arnav marked this conversation as resolved.
Show resolved
Hide resolved
|
||
ignoreStub?: boolean; | ||
safe?: boolean; | ||
} | ||
|
||
const interpolate = (env: string, vars: EnvVariables): string => { | ||
const matches = env.match(/\$([a-zA-Z0-9_]+)|\${([a-zA-Z0-9_]+)}/g) || []; | ||
matches.forEach((match) => { | ||
env = env.replace(match, interpolate(vars[match.replace(/\$|{|}/g, "")] || "", vars)); | ||
}); | ||
return env; | ||
}; | ||
|
||
const isMainThreadElectron = (target: string | undefined): boolean => | ||
!!target && target.startsWith("electron") && target.endsWith("main"); | ||
|
||
export class Dotenv { | ||
#options: DotenvConfig; | ||
#inputFileSystem!: any; | ||
#compiler!: Compiler; | ||
#logger: any; | ||
#cache!: any; | ||
constructor(config: DotenvConfig) { | ||
this.#options = { | ||
prefixes: ["process.env.", "import.meta.env."], | ||
allowEmptyValues: true, | ||
expand: true, | ||
safe: true, | ||
paths: process.env.NODE_ENV | ||
? [ | ||
".env", | ||
...(process.env.NODE_ENV === "test" ? [] : [".env.local"]), | ||
`.env.[mode]`, | ||
`.env.[mode].local`, | ||
] | ||
: [], | ||
...config, | ||
}; | ||
} | ||
|
||
public apply(compiler: Compiler): void { | ||
this.#inputFileSystem = compiler.inputFileSystem; | ||
this.#logger = compiler.getInfrastructureLogger("dotenv-webpack-plugin"); | ||
this.#compiler = compiler; | ||
compiler.hooks.initialize.tap("dotenv-webpack-plugin", () => { | ||
this.#execute(); | ||
// Not sure if this part is is a correct approach | ||
compiler.hooks.compilation.tap("dotenv-webpack-plugin", async (compilation) => { | ||
this.#cache = await compilation.getCache("dotenv-webpack-plugin-cache"); | ||
if (this.#cache) { | ||
new DefinePlugin(this.#cache).apply(compiler); | ||
} else { | ||
this.#execute(); | ||
} | ||
}); | ||
}); | ||
} | ||
|
||
#execute() { | ||
const variables = this.#gatherVariables(); | ||
const target: string = | ||
typeof this.#compiler.options.target == "string" ? this.#compiler.options.target : ""; | ||
const data = this.#formatData({ | ||
variables, | ||
target: target, | ||
}); | ||
new DefinePlugin(data).apply(this.#compiler); | ||
} | ||
|
||
#gatherVariables(compilation?: any): EnvVariables { | ||
const { allowEmptyValues, safe } = this.#options; | ||
const vars: EnvVariables = this.#initializeVars(); | ||
|
||
const { env, blueprint } = this.#getEnvs(compilation); | ||
|
||
Object.keys(blueprint).forEach((key) => { | ||
const value = Object.prototype.hasOwnProperty.call(vars, key) ? vars[key] : env[key]; | ||
|
||
if ( | ||
(typeof value === "undefined" || value === null || (!allowEmptyValues && value === "")) && | ||
safe | ||
) { | ||
compilation?.errors.push(new Error(`Missing environment variable: ${key}`)); | ||
} else { | ||
vars[key] = value; | ||
} | ||
if (safe) { | ||
Object.keys(env).forEach((key) => { | ||
if (!Object.prototype.hasOwnProperty.call(vars, key)) { | ||
vars[key] = env[key]; | ||
} | ||
}); | ||
} | ||
}); | ||
|
||
return vars; | ||
} | ||
|
||
#initializeVars(): EnvVariables { | ||
return this.#options.systemvars | ||
? Object.fromEntries(Object.entries(process.env).map(([key, value]) => [key, value ?? ""])) | ||
: {}; | ||
} | ||
|
||
#getEnvs(compilation?: any): { env: EnvVariables; blueprint: EnvVariables } { | ||
const { paths, safe } = this.#options; | ||
|
||
const env: EnvVariables = {}; | ||
let blueprint: EnvVariables = {}; | ||
|
||
for (const path of paths || []) { | ||
const fileContent = this.#loadFile( | ||
path.replace("[mode]", `${process.env.NODE_ENV || this.#compiler.options.mode}`), | ||
compilation, | ||
); | ||
Object.assign(env, dotenv.parse(fileContent)); | ||
} | ||
blueprint = env; | ||
if (safe) { | ||
for (const path of paths || []) { | ||
const exampleContent = this.#loadFile( | ||
`${path.replace( | ||
"[mode]", | ||
`${process.env.NODE_ENV || this.#compiler.options.mode}`, | ||
)}.example`, | ||
); | ||
blueprint = { ...blueprint, ...dotenv.parse(exampleContent) }; | ||
} | ||
} | ||
return { env, blueprint }; | ||
} | ||
|
||
#formatData({ | ||
variables = {}, | ||
target, | ||
}: { | ||
variables: EnvVariables; | ||
target: string; | ||
}): Record<string, string> { | ||
const { expand, prefixes } = this.#options; | ||
|
||
const preprocessedVariables: EnvVariables = Object.keys(variables).reduce( | ||
(obj: EnvVariables, key: string) => { | ||
let value = variables[key]; | ||
if (expand) { | ||
if (value.startsWith("\\$")) { | ||
value = value.substring(1); | ||
} else if (value.includes("\\$")) { | ||
value = value.replace(/\\\$/g, "$"); | ||
} else { | ||
value = interpolate(value, variables); | ||
} | ||
} | ||
obj[key] = JSON.stringify(value); | ||
return obj; | ||
}, | ||
{}, | ||
); | ||
|
||
const formatted: Record<string, string> = {}; | ||
if (prefixes) { | ||
prefixes.forEach((prefix) => { | ||
Object.entries(preprocessedVariables).forEach(([key, value]) => { | ||
formatted[`${prefix}${key}`] = value; | ||
}); | ||
}); | ||
} | ||
|
||
const shouldStubEnv = | ||
prefixes?.includes("process.env.") && this.#shouldStub({ target, prefix: "process.env." }); | ||
info-arnav marked this conversation as resolved.
Show resolved
Hide resolved
|
||
if (shouldStubEnv) { | ||
formatted["process.env"] = '"MISSING_ENV_VAR"'; | ||
} | ||
|
||
return formatted; | ||
} | ||
|
||
#shouldStub({ | ||
target: targetInput, | ||
prefix, | ||
}: { | ||
target: string | string[] | undefined; | ||
prefix: string; | ||
}): boolean { | ||
const targets: string[] = Array.isArray(targetInput) ? targetInput : [targetInput || ""]; | ||
|
||
return targets.every( | ||
(target) => | ||
prefix === "process.env." && | ||
this.#options.ignoreStub !== true && | ||
(this.#options.ignoreStub === false || | ||
(!target.includes("node") && !isMainThreadElectron(target))), | ||
); | ||
} | ||
|
||
#loadFile(filePath: string, compilation?: any): Buffer | string { | ||
try { | ||
const fileContent = this.#inputFileSystem.readFileSync(filePath); | ||
compilation?.buildDependencies.add(filePath); | ||
return fileContent; | ||
} catch (err: any) { | ||
compilation?.missingDependencies.add(filePath); | ||
this.#logger.log(`Unable to upload ${filePath} file due:\n ${err.toString()}`); | ||
return "{}"; | ||
info-arnav marked this conversation as resolved.
Show resolved
Hide resolved
|
||
} | ||
} | ||
} | ||
|
||
module.exports = Dotenv; |
This file contains hidden or 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 hidden or 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
Empty file.
Empty file.
This file contains hidden or 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,50 @@ | ||
"use strict"; | ||
|
||
const { run } = require("../utils/test-utils"); | ||
const path = require("path"); | ||
const fs = require("fs"); | ||
|
||
describe("dotenv", () => { | ||
const testDirectory = path.join(__dirname, "test-dot-env"); | ||
const outputFile = path.join(testDirectory, "output.js"); | ||
|
||
beforeAll(async () => { | ||
if (!fs.existsSync(testDirectory)) { | ||
fs.mkdirSync(testDirectory); | ||
} | ||
await fs.promises.writeFile( | ||
path.join(testDirectory, "webpack.config.js"), | ||
` | ||
const path = require('path'); | ||
module.exports = { | ||
entry: './index.js', | ||
output: { | ||
path: path.resolve(__dirname), | ||
filename: 'output.js' | ||
}, | ||
}; | ||
`, | ||
); | ||
await fs.promises.writeFile( | ||
path.join(testDirectory, "index.js"), | ||
"module.exports = import.meta.env.TEST_VARIABLE;", | ||
); | ||
await fs.promises.writeFile(path.join(testDirectory, ".env"), "TEST_VARIABLE="); | ||
}); | ||
|
||
afterAll(() => { | ||
fs.unlinkSync(path.join(testDirectory, "webpack.config.js")); | ||
fs.unlinkSync(path.join(testDirectory, "index.js")); | ||
fs.unlinkSync(path.join(testDirectory, ".env")); | ||
if (fs.existsSync(outputFile)) { | ||
fs.unlinkSync(outputFile); | ||
} | ||
fs.rmdirSync(testDirectory); | ||
}); | ||
|
||
it("should refer to the example file", async () => { | ||
await run(testDirectory, ["--dot-env"]); | ||
const output = fs.readFileSync(outputFile, "utf-8"); | ||
expect(output).toContain('exports=""'); | ||
}); | ||
}); |
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.
Uh oh!
There was an error while loading. Please reload this page.