【源码共读】| configstore 存储

175 阅读2分钟

本文参加了由公众号@若川视野 发起的每周源码共读活动,点击了解详情一起参与。

【若川视野 x 源码共读】第10期 | configstore 存储 点击了解本期详情一起参与

今天阅读的是:configstore

github.com/yeoman/conf…

image-20230129153921404

  • 这个库是用来便捷配置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

image-20230129154726941

来看看测试文件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和嵌套对象的配置。值得我们学习的是文件的读写操作,嵌套对象怎么赋值。