Compare commits

...

4 Commits

Author SHA1 Message Date
xiaoxiaojx 99738ed20a perf 2025-10-07 02:15:30 +08:00
xiaoxiaojx 891e3a7204 fix lint 2025-10-07 01:57:37 +08:00
xiaoxiaojx c81107ae59 add missingDependencies 2025-10-07 01:46:08 +08:00
xiaoxiaojx ada334b199 add watch test 2025-10-07 01:17:03 +08:00
10 changed files with 87 additions and 11 deletions

View File

@ -6,7 +6,7 @@
"use strict";
const createSchemaValidation = require("./util/create-schema-validation");
const { join } = require("./util/fs");
const { isAbsolute, join } = require("./util/fs");
/** @typedef {import("../declarations/WebpackOptions").DotenvPluginOptions} DotenvPluginOptions */
/** @typedef {import("./Compiler")} Compiler */
@ -260,6 +260,8 @@ class DotenvPlugin {
/** @type {string[] | undefined} */
let fileDependenciesCache;
/** @type {string[] | undefined} */
let missingDependenciesCache;
compiler.hooks.beforeCompile.tapAsync(PLUGIN_NAME, (_params, callback) => {
const inputFileSystem = /** @type {InputFileSystem} */ (
@ -272,7 +274,7 @@ class DotenvPlugin {
inputFileSystem,
mode,
context,
(err, env, fileDependencies) => {
(err, env, fileDependencies, missingDependencies) => {
if (err) return callback(err);
const definitions = envToDefinitions(env || {});
@ -281,6 +283,8 @@ class DotenvPlugin {
definePlugin.definitions = definitions;
// update the file dependencies
fileDependenciesCache = fileDependencies;
// update the missing dependencies
missingDependenciesCache = missingDependencies;
callback();
}
@ -289,6 +293,7 @@ class DotenvPlugin {
compiler.hooks.compilation.tap(PLUGIN_NAME, (compilation) => {
compilation.fileDependencies.addAll(fileDependenciesCache || []);
compilation.missingDependencies.addAll(missingDependenciesCache || []);
});
definePlugin.apply(compiler);
@ -321,7 +326,7 @@ class DotenvPlugin {
* @param {InputFileSystem} fs the input file system
* @param {string | undefined} mode the mode
* @param {string} context the compiler context
* @param {(err: Error | null, env?: Record<string, string>, fileDependencies?: string[]) => void} callback callback function
* @param {(err: Error | null, env?: Record<string, string>, fileDependencies?: string[], missingDependencies?: string[]) => void} callback callback function
* @returns {void}
*/
loadEnv(fs, mode, context, callback) {
@ -336,6 +341,9 @@ class DotenvPlugin {
}
const getDir = () => {
if (typeof rawDir === "string") {
if (isAbsolute(rawDir)) {
return rawDir;
}
return join(fs, context, rawDir);
}
if (rawDir === true) {
@ -350,26 +358,30 @@ class DotenvPlugin {
const envFiles = this.getEnvFilesForMode(fs, dir, mode);
/** @type {string[]} */
const fileDependencies = [];
/** @type {string[]} */
const missingDependencies = [];
// Read all files
const readPromises = envFiles.map((filePath) =>
this.loadFile(fs, filePath).then(
(content) => {
fileDependencies.push(filePath);
return { content, filePath };
return content;
},
() =>
// File doesn't exist, skip it (this is normal)
({ content: "", filePath })
() => {
// File doesn't exist, add to missingDependencies (this is normal)
missingDependencies.push(filePath);
return "";
}
)
);
Promise.all(readPromises)
.then((results) => {
.then((contents) => {
// Parse all files and merge (later files override earlier ones)
// Similar to Vite's implementation
const parsed = /** @type {Record<string, string>} */ ({});
for (const { content } of results) {
for (const content of contents) {
if (!content) continue;
const entries = parse(content);
for (const key in entries) {
@ -400,7 +412,7 @@ class DotenvPlugin {
}
}
callback(null, env, fileDependencies);
callback(null, env, fileDependencies, missingDependencies);
})
.catch((err) => {
callback(err);

View File

@ -0,0 +1,2 @@
WEBPACK_API_URL=https://api0.example.com
WEBPACK_FEATURE_FLAG=false

View File

@ -0,0 +1,5 @@
it("should load env variables from .env file in step 0", function () {
expect(process.env.WEBPACK_API_URL).toBe("https://api0.example.com");
expect(process.env.WEBPACK_FEATURE_FLAG).toBe("false");
expect(process.env.WEBPACK_NEW_VAR).toBeUndefined();
});

View File

@ -0,0 +1,3 @@
WEBPACK_API_URL=https://api1.example.com
WEBPACK_FEATURE_FLAG=false
WEBPACK_NEW_VAR=added-in-step-1

View File

@ -0,0 +1,5 @@
it("should load new variable added to .env file in step 1", function () {
expect(process.env.WEBPACK_API_URL).toBe("https://api1.example.com");
expect(process.env.WEBPACK_FEATURE_FLAG).toBe("false");
expect(process.env.WEBPACK_NEW_VAR).toBe("added-in-step-1");
});

View File

@ -0,0 +1,3 @@
WEBPACK_API_URL=https://api2.example.com
WEBPACK_FEATURE_FLAG=false
WEBPACK_NEW_VAR=added-in-step-1

View File

@ -0,0 +1,2 @@
WEBPACK_FEATURE_FLAG=true
WEBPACK_DEV_ONLY=development-value

View File

@ -0,0 +1,8 @@
it("should override .env values with .env.development in step 2", function () {
expect(process.env.WEBPACK_API_URL).toBe("https://api2.example.com");
// WEBPACK_FEATURE_FLAG should be overridden by .env.development
expect(process.env.WEBPACK_FEATURE_FLAG).toBe("true");
expect(process.env.WEBPACK_NEW_VAR).toBe("added-in-step-1");
// New variable from .env.development
expect(process.env.WEBPACK_DEV_ONLY).toBe("development-value");
});

View File

@ -0,0 +1,35 @@
"use strict";
const path = require("path");
const DotenvPlugin = require("../../../../").DotenvPlugin;
/** @type {(env: Env, options: TestOptions) => import("../../../../").Configuration} */
module.exports = (env, { srcPath, testPath }) => {
const dotenvPlugin = new DotenvPlugin({
prefix: "WEBPACK_",
dir: ""
});
return {
mode: "development",
dotenv: false,
plugins: [
(compiler) => {
let i = 0;
// Update dotenvPlugin.config.dir before each compile
// Use beforeCompile with stage -1 to run before DotenvPlugin
compiler.hooks.beforeCompile.tap(
{
name: "UpdateDotenvDir",
stage: -1
},
() => {
dotenvPlugin.config.dir = path.join(__dirname, String(i));
i++;
}
);
},
dotenvPlugin
]
};
};

3
types.d.ts vendored
View File

@ -4463,7 +4463,8 @@ declare class DotenvPlugin {
callback: (
err: null | Error,
env?: Record<string, string>,
fileDependencies?: string[]
fileDependencies?: string[],
missingDependencies?: string[]
) => void
): void;