yeoman/configstrore 源码解析

·  阅读 416

前言

yeoman/configstore 是 yeoman 中用于配置存储的一个工具,非常实用和强大。核心思想就是通过既定文件命名规则,在当前系统中创建文件,用于存储用户的配置数据。再通过文件读写,实现配置的修改和保存。

使用

  1. 新建一个 Node 项目,安装依赖。

    npm install configstore
    
  2. 设置 package.json 中的 typemodule

  3. 新建 test_configstore.js 并执行。test_configstore.js 内容如下:

    import Configstore from "configstore";
    import fs from "fs";
    
    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"));
    
  4. 运行 test_configstore.js 文件即可。

依赖分析

先了解下 configstore 所依赖的 npm 包的主要作用。

Node 内置模块,用于操作文件和目录。

Node 内置模块,提供操作系统相关的方法和属性。

一个升级版的 fs 模块,兼容不同平台和环境。在文件读写这一块,优于原生 fs 模块。

在 Linux 平台下,获取用户的配置文件路径。例如: '/usr/share' - 用于存放共享数据的文件目录等等。

基于 fs.writeFile 的扩展模块,用于文件写入,并支持设置文件的 uid/gid。

uid 代表用户对文件的操作权限,gid 代表用户所属组对文件的操作权限。

支持多层级嵌套对象,通过 '.' 方式访问及操作对象属性。

随机生成一个 32 位长度字符串。

前置变量

/**
 * 1. configDirectory: 存储当前操作系统的临时文件路径。Linux 通过 xdgConfig 包获取。
 *    其他操作系统通过 os.tmpdir() 获取,后面再拼接一个 32 位的字符串。
 * 	  eg: 'C:\\Users\\userName\\AppData\\Local\\Tempb4de2a49c8ffa3fbee04446f045483b2'
 * 2. permissionError: 文件权限提示语
 * 3. mkdirOptions:文件夹创建配置,mode 参数代表的是 linux 系统的目录权限, recursive 表示递归创建
 * 		mode: 0o0700:代表所有者具有读、写及可执行权限
 * 4. writeFileOptions: 写文件配置。
 */
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 };

核心代码

/**
 * 5. Configstore 类声明,使用 ES modules 语法进行导出
 */
export default class Configstore {
	/**
	 * 6. 构造函数,用于创建 Configstore 实例
	 * @param {*} id 用于 configstore 存储文件命名
	 * @param {*} defaults 默认存储数据,比如一个对象
	 * @param {*} options 存储配置
	 */
	constructor(id, defaults, options = {}) {
		/**
		 * 7. pathPrefix: 存储文件路径名称。
		 * 	  若用户设置了 globalConfigPath,则名称为: id/config.json。
		 * 	  否则为:configstore/id.json
		 */
		const pathPrefix = options.globalConfigPath
			? path.join(id, "config.json")
			: path.join("configstore", `${id}.json`);

		/**
		 * 8. _path:存储文件在操作系统中的完整路径。
		 * 	 options.configPath:用户自定义的完整路径。
		 *   或为 系统临时存储路径/存储文件路径。
		 */
		this._path = options.configPath || path.join(configDirectory, pathPrefix);

		/**
		 * 9. _all: 存储当前 configstore 实例的存储数据,是一个对象格式。
		 */
		if (defaults) {
			this.all = {
				...defaults,
				...this.all,
			};
		}
	}

	/**
	 * 10. 以 utf-8 编码格式读取存储对象的文件
	 */
	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;
		}
	}

	/**
	 * 11. 存储数据写入,等同于覆盖写入
	 */
	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;
		}
	}

	/**
	 * 12. 获取存储数据(JSON 对象)属性数量
	 */
	get size() {
		return Object.keys(this.all || {}).length;
	}

	/**
	 * 13. 按 key 名称去获取存储数据(本质上就是对象属性访问,额外支持了嵌套对象的属性访问)
	 * @param {*} key
	 * @returns
	 */
	get(key) {
		return dotProp.get(this.all, key);
	}

	/**
	 * 14. 按照 key-value 形式给数据对象属性赋值
	 * @param {*} key
	 * @param {*} value
	 */
	set(key, value) {
		const config = this.all;

		if (arguments.length === 1) {
			for (const k of Object.keys(key)) {
				dotProp.set(config, k, key[k]);
			}
		} else {
			dotProp.set(config, key, value);
		}

		this.all = config;
	}

	/**
	 * 15. 判断数据中是否存在某个属性
	 * @param {*} key
	 * @returns
	 */
	has(key) {
		return dotProp.has(this.all, key);
	}

	/**
	 * 16. 删除指定 key 的属性值
	 * @param {*} key
	 */
	delete(key) {
		const config = this.all;
		dotProp.delete(config, key);
		this.all = config;
	}

	/**
	 * 17. 清空当前存储数据
	 */
	clear() {
		this.all = {};
	}

	/**
	 * 18. 获取当前数据存储文件的完整路径
	 */
	get path() {
		return this._path;
	}

总结

在开发 js 库的时候,涉及文件操作的部分需要考虑到不同系统及平台等环境的情况。configstore 的做法就是一个很好的思路,值得借鉴。

分类:
前端
标签:
分类:
前端
标签:
收藏成功!
已添加到「」, 点击更改