dotenv-webpack
Version:
A simple webpack plugin to support dotenv.
211 lines (177 loc) • 5.94 kB
JavaScript
const dotenv = require('dotenv-defaults')
const fs = require('fs')
// Mostly taken from here: https://github.com/motdotla/dotenv-expand/blob/master/lib/main.js#L4
const interpolate = (env, vars) => {
const matches = env.match(/\$([a-zA-Z0-9_]+)|\${([a-zA-Z0-9_]+)}/g) || []
matches.forEach((match) => {
const key = match.replace(/\$|{|}/g, '')
let variable = vars[key] || ''
variable = interpolate(variable, vars)
env = env.replace(match, variable)
})
return env
}
const isMainThreadElectron = (target) =>
target.startsWith('electron') && target.endsWith('main')
class Dotenv {
/**
* The dotenv-webpack plugin.
* @param {Object} options - The parameters.
* @param {String} [options.path=./.env] - The location of the environment variable.
* @param {Boolean|String} [options.safe=false] - If false ignore safe-mode, if true load `'./.env.example'`, if a string load that file as the sample.
* @param {Boolean} [options.systemvars=false] - If true, load system environment variables.
* @param {Boolean} [options.silent=false] - If true, suppress warnings, if false, display warnings.
* @param {String} [options.prefix=process.env.] - The prefix, used to denote environment variables.
* @returns {webpack.DefinePlugin}
*/
constructor (config = {}) {
this.config = Object.assign({}, {
path: './.env',
prefix: 'process.env.'
}, config)
}
apply (compiler) {
const variables = this.gatherVariables()
const target = compiler.options.target ?? 'web'
const version = (compiler.webpack && compiler.webpack.version) || '4'
const data = this.formatData({
variables,
target,
version
})
const DefinePlugin = (compiler.webpack && compiler.webpack.DefinePlugin) || require('webpack').DefinePlugin
new DefinePlugin(data).apply(compiler)
}
gatherVariables () {
const { safe, allowEmptyValues } = this.config
const vars = this.initializeVars()
const { env, blueprint } = this.getEnvs()
Object.keys(blueprint).forEach(key => {
const value = Object.prototype.hasOwnProperty.call(vars, key) ? vars[key] : env[key]
const isMissing = typeof value === 'undefined' || value === null ||
(!allowEmptyValues && value === '')
if (safe && isMissing) {
throw new Error(`Missing environment variable: ${key}`)
} else {
vars[key] = value
}
})
// add the leftovers
if (safe) {
Object.keys(env).forEach((key) => {
if (!Object.prototype.hasOwnProperty.call(vars, key)) {
vars[key] = env[key]
}
})
}
return vars
}
initializeVars () {
return (this.config.systemvars) ? Object.assign({}, process.env) : {}
}
getEnvs () {
const { path, silent, safe } = this.config
const env = dotenv.parse(this.loadFile({
file: path,
silent
}), this.getDefaults())
let blueprint = env
if (safe) {
let file = `${path}.example`
if (safe !== true) {
file = safe
}
blueprint = dotenv.parse(this.loadFile({
file,
silent
}))
}
return {
env,
blueprint
}
}
getDefaults () {
const { path, silent, defaults } = this.config
if (defaults) {
return this.loadFile({
file: defaults === true ? `${path}.defaults` : defaults,
silent
})
}
return ''
}
formatData ({ variables = {}, target, version }) {
const { expand, prefix } = this.config
const formatted = Object.keys(variables).reduce((obj, key) => {
const v = variables[key]
const vKey = `${prefix}${key}`
let vValue
if (expand) {
if (v.substring(0, 2) === '\\$') {
vValue = v.substring(1)
} else if (v.indexOf('\\$') > 0) {
vValue = v.replace(/\\\$/g, '$')
} else {
vValue = interpolate(v, variables)
}
} else {
vValue = v
}
obj[vKey] = JSON.stringify(vValue)
return obj
}, {})
// We have to stub any remaining `process.env`s due to Webpack 5 not polyfilling it anymore
// https://github.com/mrsteele/dotenv-webpack/issues/240#issuecomment-710231534
// However, if someone targets Node or Electron `process.env` still exists, and should therefore be kept
// https://webpack.js.org/configuration/target
if (this.shouldStub({ target, version })) {
// Results in `"MISSING_ENV_VAR".NAME` which is valid JS
formatted['process.env'] = '"MISSING_ENV_VAR"'
}
return formatted
}
shouldStub ({ target: targetInput, version }) {
if (!version.startsWith('5')) {
return false
}
const targets = Array.isArray(targetInput) ? targetInput : [targetInput]
return targets.every(
target =>
// If configured prefix is 'process.env'
this.config.prefix === 'process.env.' &&
// If we're not configured to never stub
this.config.ignoreStub !== true &&
// And
(
// We are configured to always stub
this.config.ignoreStub === false ||
// Or if we should according to the target
(!target.includes('node') && !isMainThreadElectron(target))
)
)
}
/**
* Load a file.
* @param {String} config.file - The file to load.
* @param {Boolean} config.silent - If true, suppress warnings, if false, display warnings.
* @returns {Object}
*/
loadFile ({ file, silent }) {
try {
return fs.readFileSync(file, 'utf8')
} catch (err) {
this.warn(`Failed to load ${file}.`, silent)
return {}
}
}
/**
* Displays a console message if 'silent' is falsey
* @param {String} msg - The message.
* @param {Boolean} silent - If true, display the message, if false, suppress the message.
*/
warn (msg, silent) {
!silent && console.warn(msg)
}
}
module.exports = Dotenv