- 本文参加了由公众号@若川视野 发起的每周源码共读活动, 点击了解详情一起参与。
- 这是源码共读的第22期,链接:www.yuque.com/ruochuan12/…。
本次源码分析的内容是 dotenv, 它的作用是将项目中 .env 环境文件里定义的变量"键=值对", 放入到 process.env 中, 这样在项目里就可以使用了.
process 的官方解释:
process 对象是一个全局变量,提供了有关当前 Node.js 进程的信息并对其进行控制。 作为全局变量,它始终可供 Node.js 应用程序使用,无需使用 require()。 它也可以使用 require() 显式地访问。
process.env的解释:
process.env 属性会返回包含用户环境的对象
vue-cli 中就有相应的配置
除了通过环境文件.env的方式添加环境变量, 我们在执行 npm 命令的时候 可以携带一些参数, 比如 npm run serve --type=dev, 打印 process.env 对象后能发现在其中添加了如下对象
npm_config_type: 'dev
dotenv 的源码
// 引入 fs/path/os 三个node模块
const fs = require('fs')
const path = require('path')
const os = require('os')
// 定义了一个正则表达式,
const LINE = /(?:^|^)\s*(?:export\s+)?([\w.-]+)(?:\s*=\s*?|:\s+?)(\s*'(?:\'|[^'])*'|\s*"(?:\"|[^"])*"|\s*`(?:\`|[^`])*`|[^#\r\n]+)?\s*(?:#.*)?(?:$|$)/mg
// Parser src into an Object, 将src解析成一个Object
function parse (src) {
const obj = {}
// Convert buffer to string, 将buffer转换成string, src 一般是通过例如fs.readFileSync('tests/.env', { encoding: 'utf8' }) 等方式读取的数据
let lines = src.toString()
// Convert line breaks to same format, 统一换行方式
lines = lines.replace(/\r\n?/mg, '\n')
let match
// exec() 方法在一个指定字符串中执行一个搜索匹配。返回一个结果数组或 null
while ((match = LINE.exec(lines)) != null) {
const key = match[1]
// Default undefined or null to empty string
let value = (match[2] || '')
// Remove whitespace
value = value.trim()
// Check if double quoted
const maybeQuote = value[0]
// Remove surrounding quotes
value = value.replace(/^(['"`])([\s\S]*)\1$/mg, '$2')
// Expand newlines if double quoted
if (maybeQuote === '"') {
value = value.replace(/\n/g, '\n')
value = value.replace(/\r/g, '\r')
}
// Add to object
obj[key] = value
}
return obj
}
// 封装了一个log方法
function _log (message) {
console.log(`[dotenv][DEBUG] ${message}`)
}
function _resolveHome (envPath) {
// os.homedir() 用于获取当前用户的主目录路径。
return envPath[0] === '~' ? path.join(os.homedir(), envPath.slice(1)) : envPath
}
// Populates process.env from .env file
function config (options) {
// process.cwd()方法是流程模块的内置应用程序编程接口,用于获取node.js流程的当前工作目录。默认获取 .env 文件的路径
let dotenvPath = path.resolve(process.cwd(), '.env')
let encoding = 'utf8'
const debug = Boolean(options && options.debug)
const override = Boolean(options && options.override)
// 使用配置里的环境文件路径和编码
if (options) {
if (options.path != null) {
dotenvPath = _resolveHome(options.path)
}
if (options.encoding != null) {
encoding = options.encoding
}
}
try {
// Specifying an encoding returns a string instead of a buffer, 读取环境文件并解析
// fs.readFileSync 同步读取文件内容
const parsed = DotenvModule.parse(fs.readFileSync(dotenvPath, { encoding }))
// 遍历解析后对象
Object.keys(parsed).forEach(function (key) {
// 如果 process.env 上没有key 则赋值
if (!Object.prototype.hasOwnProperty.call(process.env, key)) {
process.env[key] = parsed[key]
} else {
// 如果需要覆盖再覆盖
if (override === true) {
process.env[key] = parsed[key]
}
// debug 模式下输出每个属性的赋值情况 是不是被覆盖的
if (debug) {
if (override === true) {
_log(`"${key}" is already defined in `process.env` and WAS overwritten`)
} else {
_log(`"${key}" is already defined in `process.env` and was NOT overwritten`)
}
}
}
})
return { parsed }
} catch (e) {
if (debug) {
_log(`Failed to load ${dotenvPath} ${e.message}`)
}
return { error: e }
}
}
const DotenvModule = {
config,
parse
}
module.exports.config = DotenvModule.config
module.exports.parse = DotenvModule.parse
module.exports = DotenvModule
综上 本次源码的难度偏简单, 比较费劲儿的是理解这里面用到的正则表达式
比如 LINE 过于复杂 还没理解