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

41
types.d.ts vendored
View File

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