本文参加了由公众号@若川视野 发起的每周源码共读活动,点击了解详情一起参与。
【若川视野 x 源码共读】第10期 | configstore 存储 点击了解本期详情一起参与。
今天阅读的是:configstore
- 这个库是用来便捷配置
config
尝试使用
import Configstore from 'configstore';
const packageJson = JSON.parse(fs.readFileSync('./package.json', 'utf8'));
// Create a Configstore instance.
const config = new Configstore(packageJson.name, {foo: 'bar'});
console.log(config.get('foo'));
//=> 'bar'
config.set('awesome', true);
console.log(config.get('awesome'));
//=> true
// Use dot-notation to access nested properties.
config.set('bar.baz', true);
console.log(config.get('bar'));
//=> {baz: true}
config.delete('awesome');
console.log(config.get('awesome'));
//=> undefined
- 可以看到,我们通过
get,set可以轻松的获取和配置package.json
源码分析
先看package.json,入口文件为index.js
来看看测试文件test.js,具体实现哪些功能点
// get set 功能,支持传Object
test('.set() and .get()')
test('.set() with object and .get()')
// 查找
test('.has()')
// 删除
test('.delete()')
// 清空配置
test('.clear()')
// 获取所有配置,并以Object的形式返回,或者重新替换赋值
test('.all')
// 获取当前配置的长度
test('.size')
// 获取配置文件的路径
test('.path')
// 支持默认配置
test('use default value')
// 支持全局配置
test('support `globalConfigPath` option')
// 支持自定义配置
test('support `configPath` option')
// 检验.all 永远返回Object
test('ensure `.all` is always an object')
// 只有当写入的时候开启文件流
test('the store is NOT created until write')
// 校验子目录也被创建
test('ensure necessary sub-directories are created')
我们来看下源码中是怎么实现的
import path from "path";
import os from "os";
import fs from "graceful-fs";
import { xdgConfig } from "xdg-basedir";
import writeFileAtomic from "write-file-atomic";
import dotProp from "dot-prop";
import uniqueString from "unique-string";
const configDirectory = xdgConfig || path.join(os.tmpdir(), uniqueString());
const permissionError = "You don't have access to this file.";
const mkdirOptions = { mode: 0o0700, recursive: true };
const writeFileOptions = { mode: 0o0600 };
export default class Configstore {
constructor(id, defaults, options = {}) {
// 配置文件前缀
const pathPrefix = options.globalConfigPath
? path.join(id, "config.json")
: path.join("configstore", `${id}.json`);
// 保存当前的配置文件路径
this._path = options.configPath || path.join(configDirectory, pathPrefix);
// 如果有默认值,覆盖原有配置
if (defaults) {
this.all = {
...defaults,
...this.all,
};
}
}
get all() {
try {
// 获取本地配置文件
return JSON.parse(fs.readFileSync(this._path, "utf8"));
} catch (error) {
// 文件不存在则创建,返回空对象
// Create directory if it doesn't exist
if (error.code === "ENOENT") {
return {};
}
// 文件权限不足
// Improve the message of permission errors
if (error.code === "EACCES") {
error.message = `${error.message}\n${permissionError}\n`;
}
// 文件格式错误,清空文件,返回空对象
// Empty the file if it encounters invalid JSON
if (error.name === "SyntaxError") {
writeFileAtomic.sync(this._path, "", writeFileOptions);
return {};
}
throw error;
}
}
set all(value) {
try {
// 确保文件存在
// Make sure the folder exists as it could have been deleted in the meantime
fs.mkdirSync(path.dirname(this._path), mkdirOptions);
writeFileAtomic.sync(
this._path,
JSON.stringify(value, undefined, "\t"),
writeFileOptions
);
} catch (error) {
// Improve the message of permission errors
if (error.code === "EACCES") {
error.message = `${error.message}\n${permissionError}\n`;
}
throw error;
}
}
get size() {
// 返回对象的长度
return Object.keys(this.all || {}).length;
}
get(key) {
// 返回 this.all[key] 的值
return dotProp.get(this.all, key);
}
set(key, value) {
const config = this.all;
if (arguments.length === 1) {
// 如果传值为Object,循环赋值 config[key] = {...:...}
for (const k of Object.keys(key)) {
dotProp.set(config, k, key[k]);
}
} else {
dotProp.set(config, key, value);
}
this.all = config;
}
has(key) {
// 查找
return dotProp.has(this.all, key);
}
delete(key) {
// 删除
const config = this.all;
dotProp.delete(config, key);
this.all = config;
}
clear() {
// 清空
this.all = {};
}
get path() {
// 获取路径
return this._path;
}
}
总结
整个源码很简单,就是获取配置文件,通过get(),set()的方式去配置这个文件,原理实现上,通过使用dot-prop这个库,来实现key-value和嵌套对象的配置。值得我们学习的是文件的读写操作,嵌套对象怎么赋值。