This commit is contained in:
xiaoxiaojx 2025-09-23 00:56:26 +08:00
parent 29feac18a4
commit 4cccfafaeb
2 changed files with 98 additions and 119 deletions

View File

@ -118,7 +118,6 @@ class DotenvPlugin {
}
/**
* Apply the plugin
* @param {Compiler} compiler the compiler instance
* @returns {void}
*/
@ -129,22 +128,32 @@ class DotenvPlugin {
);
const context = compiler.context;
this.gatherVariables(inputFileSystem, context, (err, variables) => {
if (err) return callback(err);
this.gatherVariables(
inputFileSystem,
context,
(err, variables, fileDependencies) => {
if (err) return callback(err);
const definitions = this.formatDefinitions(variables || {});
const DefinePlugin = compiler.webpack.DefinePlugin;
if (fileDependencies) {
compiler.hooks.compilation.tap(PLUGIN_NAME, (compilation) => {
compilation.fileDependencies.addAll(fileDependencies);
});
}
new DefinePlugin(definitions).apply(compiler);
callback();
});
const definitions = this.formatVariables(variables || {});
const DefinePlugin = compiler.webpack.DefinePlugin;
new DefinePlugin(definitions).apply(compiler);
callback();
}
);
});
}
/**
* @param {InputFileSystem} inputFileSystem the input file system
* @param {string} context the compiler context
* @param {(err: Error | null, variables?: Record<string, string>) => void} callback callback function
* @param {(err: Error | null, variables?: Record<string, string>, fileDependencies?: string[]) => void} callback callback function
* @returns {void}
*/
gatherVariables(inputFileSystem, context, callback) {
@ -159,7 +168,7 @@ class DotenvPlugin {
return callback(new Error("Failed to get environment variables"));
}
const { env, blueprint } = result;
const { env, blueprint, fileDependencies } = result;
try {
for (const key of Object.keys(blueprint)) {
@ -188,7 +197,7 @@ class DotenvPlugin {
}
}
callback(null, vars);
callback(null, vars, fileDependencies);
} catch (error) {
callback(error instanceof Error ? error : new Error(String(error)));
}
@ -212,110 +221,95 @@ class DotenvPlugin {
/**
* @param {InputFileSystem} inputFileSystem the input file system
* @param {string} context the compiler context
* @param {(err: Error | null, result?: {env: Record<string, string>, blueprint: Record<string, string>}) => void} callback callback function
* @param {(err: Error | null, result?: {env: Record<string, string>, blueprint: Record<string, string>, fileDependencies: string[]}) => void} callback callback function
* @returns {void}
*/
getEnvs(inputFileSystem, context, callback) {
const { path, safe } = /** @type {DotenvPluginOptions} */ (this.config);
// First load the main env file and defaults
this.loadFile(
{
file: path || DEFAULT_PATH,
inputFileSystem,
context
},
(err, envContent) => {
if (err) return callback(err);
this.getDefaults(inputFileSystem, context, (err, defaultsContent) => {
if (err) return callback(err);
const env = mergeParse(envContent || "", defaultsContent || "");
let blueprint = env;
if (safe) {
let file = `${path || DEFAULT_PATH}.example`;
if (safe !== true) {
file = safe;
}
this.loadFile(
{
file,
inputFileSystem,
context
},
(err, blueprintContent) => {
if (err) return callback(err);
blueprint = mergeParse(blueprintContent || "");
callback(null, { env, blueprint });
}
);
} else {
callback(null, { env, blueprint });
}
});
}
const { path, safe, defaults } = /** @type {DotenvPluginOptions} */ (
this.config
);
}
/**
* @param {InputFileSystem} inputFileSystem the input file system
* @param {string} context the compiler context
* @param {(err: Error | null, content?: string) => void} callback callback function
* @returns {void}
*/
getDefaults(inputFileSystem, context, callback) {
const { path, defaults } = /** @type {DotenvPluginOptions} */ (this.config);
const loadPromises = [];
/** @type {string[]} */
const loadFiles = [];
const resolvedMainEnvFile = this.resolvePath(
/** @type {string} */ (path),
inputFileSystem,
context
);
loadPromises.push(this.loadFile(inputFileSystem, resolvedMainEnvFile));
loadFiles.push(resolvedMainEnvFile);
if (defaults) {
const defaultsFile =
defaults === true ? `${path || DEFAULT_PATH}.defaults` : defaults;
this.loadFile(
{
file: defaultsFile,
inputFileSystem,
context
},
callback
const resolvedDefaultsFile = this.resolvePath(
/** @type {string} */ (defaultsFile),
inputFileSystem,
context
);
loadPromises.push(this.loadFile(inputFileSystem, resolvedDefaultsFile));
loadFiles.push(resolvedDefaultsFile);
} else {
callback(null, "");
loadPromises.push(Promise.resolve(""));
}
if (safe) {
const safeFile = safe === true ? `${path || DEFAULT_PATH}.example` : safe;
const resolvedSafeFile = this.resolvePath(
/** @type {string} */ (safeFile),
inputFileSystem,
context
);
loadPromises.push(this.loadFile(inputFileSystem, resolvedSafeFile));
loadFiles.push(resolvedSafeFile);
} else {
loadPromises.push(Promise.resolve(""));
}
Promise.all(loadPromises)
.then(([envContent, defaultsContent, safeContent]) => {
const env = mergeParse(envContent || "", defaultsContent || "");
let blueprint = env;
if (safeContent) {
blueprint = mergeParse(safeContent || "");
}
callback(null, { env, blueprint, fileDependencies: loadFiles });
})
.catch((err) => {
callback(err);
});
}
/**
* Load a file with proper path resolution
* @param {object} options options object
* @param {string} options.file the file to load
* @param {InputFileSystem} options.inputFileSystem the input file system
* @param {string} options.context the compiler context for resolving relative paths
* @param {(err: Error | null, content?: string) => void} callback callback function
* @returns {void}
* @param {InputFileSystem} fs the input file system
* @param {string} file the file to load
* @returns {Promise<string>} the content of the file
*/
loadFile({ file, inputFileSystem, context }, callback) {
// Resolve relative paths based on compiler context
const resolvedPath = isAbsolute(file)
? file
: join(inputFileSystem, context, file);
inputFileSystem.readFile(resolvedPath, "utf8", (err, content) => {
if (err) {
// File doesn't exist, return empty string
callback(null, "");
} else {
callback(null, /** @type {string} */ (content));
}
loadFile(fs, file) {
return new Promise((resolve, reject) => {
fs.readFile(file, "utf8", (err, content) => {
if (err) reject(err);
resolve(content || "");
});
});
}
/**
* @param {string} file the file to load
* @param {InputFileSystem} inputFileSystem the input file system
* @param {string} context the compiler context for resolving relative paths
* @returns {string} the resolved path
*/
resolvePath(file, inputFileSystem, context) {
return isAbsolute(file) ? file : join(inputFileSystem, context, file);
}
/**
* @param {Record<string, string>} variables variables object
* @returns {Record<string, string>} formatted data
*/
formatDefinitions(variables) {
formatVariables(variables) {
const { expand, prefix } = /** @type {DotenvPluginOptions} */ (this.config);
const formatted = Object.keys(variables).reduce((obj, key) => {
const v = variables[key];

41
types.d.ts vendored
View File

@ -4451,15 +4451,15 @@ declare class DotenvPlugin {
*/
systemvars?: boolean;
};
/**
* Apply the plugin
*/
apply(compiler: Compiler): void;
gatherVariables(
inputFileSystem: InputFileSystem,
context: string,
callback: (err: null | Error, variables?: Record<string, string>) => void
callback: (
err: null | Error,
variables?: Record<string, string>,
fileDependencies?: string[]
) => void
): void;
initializeVars(): Record<string, string>;
getEnvs(
@ -4470,36 +4470,21 @@ declare class DotenvPlugin {
result?: {
env: Record<string, string>;
blueprint: Record<string, string>;
fileDependencies: string[];
}
) => void
): void;
getDefaults(
inputFileSystem: InputFileSystem,
context: string,
callback: (err: null | Error, content?: string) => void
): void;
/**
* Load a file with proper path resolution
*/
loadFile(
__0: {
/**
* the file to load
*/
file: string;
/**
* the input file system
*/
inputFileSystem: InputFileSystem;
/**
* the compiler context for resolving relative paths
*/
context: string;
},
callback: (err: null | Error, content?: string) => void
): void;
formatDefinitions(variables: Record<string, string>): Record<string, string>;
loadFile(fs: InputFileSystem, file: string): Promise<string>;
resolvePath(
file: string,
inputFileSystem: InputFileSystem,
context: string
): string;
formatVariables(variables: Record<string, string>): Record<string, string>;
}
declare interface DotenvPluginOptions {
/**