Initial commit

This commit is contained in:
2026-05-27 14:28:02 +08:00
commit f74d8990cb
11 changed files with 842 additions and 0 deletions
+23
View File
@@ -0,0 +1,23 @@
# Dependencies
node_modules/
# Build output
dist/
# OS files
.DS_Store
Thumbs.db
# IDE
.vscode/
.idea/
*.swp
*.swo
# Logs
*.log
npm-debug.log*
# Environment
.env
.env.local
+173
View File
@@ -0,0 +1,173 @@
# Windychen Utils 项目开发记录
**日期**: 2026-05-25
**项目**: windychen-untils (v1.0.0)
**类型**: 轻量级 JavaScript 工具库
---
## 一、已完成功能模块
### 1. Object 扩展 (`object.js`)
| 方法 | 功能 | 特性 |
|------|------|------|
| `getPro(path, defaultValue?)` | 安全获取嵌套属性 | 支持点分隔路径、特殊键名(含`.`)、混合路径、默认值回退 |
| `setPro(path, value)` | 设置嵌套路径值 | 自动创建中间对象,支持特殊键名 |
**路径格式示例:**
```javascript
obj.getPro('a.b.c'); // 普通点分隔
obj.getPro("['x.y'].z"); // 特殊键名
obj.getPro("['a.b'].c['d']"); // 混合嵌套
```
**优化点:**
- 提取 `_parsePath()` 公共函数消除重复代码
- 使用正则 `\[['"]([^'"]+)['"]\]|[^.\[\]]+` 统一解析所有 key 格式
- 最终代码量:26 行(原 47 行,精简 45%)
---
### 2. Array 扩展 (`array.js`)
6 个增强方法,均支持三种调用方式:
| 方法 | 对应原生 | 说明 |
|------|---------|------|
| `findPro(a, b?)` | find() | 查找首个匹配项 |
| `filterPro(a, b?)` | filter() | 过滤所有匹配项 |
| `findIndexPro(a, b?)` | findIndex() | 返回索引 |
| `findLastPro(a, b?)` | findLast() | 从末尾查找 |
| **somePro(a, b?)** | some() | 任一满足 |
| **everyPro(a, b?)** | every() | 全部满足 |
**三种调用模式:**
```javascript
// 模式1: 回调函数
arr.findPro(item => item.id > 1);
// 模式2: 单值精确匹配
['aa','bb'].findPro('bb');
// 模式3: key-value 匹配(支持 getPro 嵌套路径)
arr.findPro('user.info.name', 'Alice');
```
**优化点:**
- 提取 `_resolveMatcher()` 统一参数判断逻辑
- 循环生成 6 个 Pro 方法,零重复代码
- key-value 模式接入 `getPro` 支持深层路径访问
- 处理 null/undefined 边界(`item != null && ...`
- 最终代码量:12 行(原 64 行,精简 81%)
---
### 3. Storage 存储 (`storage.js`)
浏览器环境专用:
| 类 | 存储后端 | 特点 |
|----|---------|------|
| `IDBStorage` | IndexedDB | 大容量存储 |
| `ILocalStorage` | localStorage + Map 缓存 | 兼容性好,启动时全量加载 |
**自动选择策略:**
```javascript
window.indexedDB ? IDBStorage : (localStorage ? ILocalStorage : null)
```
**API**
- `storage.set(key, value)` → Promise
- `storage.get(key, initValue)` → Promise
---
## 二、工程化配置
### 构建系统 (Webpack)
**配置文件**: `webpack.config.js`(单文件,多模式)
```bash
npm run build # 全部 (CJS + ESM + UMD)
npm run build:cjs # CommonJS 格式
npm run build:esm # ES Module 格式
npm run build:umd # UMD 浏览器格式
npm run dev # 开发模式
```
**输出产物:**
| 格式 | 文件 | 入口字段 |
|------|------|---------|
| CJS | dist/windychen-utils.cjs.js | main |
| ESM | dist/windychen-utils.esm.js | module |
| UMD | dist/windychen-utils.js | browser |
**演进过程:**
1. 初始创建 3 个独立 webpack 配置文件
2. 发现 `.esm.js` 后缀被 webpack-cli 误识别为 esm 加载器
3. 重命名解决加载异常
4. 发现 `experiments` 应在顶层而非 output 内
5. **最终收敛为单文件 + --env 参数方案**
---
### 项目结构
```
e:\project\utils\
├── index.js # 统一入口,聚合各模块
├── object.js # Object 原型扩展 (getPro/setPro)
├── array.js # Array 原型扩展 (6个Pro方法)
├── storage.js # 浏览器存储封装 (IndexedDB/LS)
├── webpack.config.js # 打包配置 (单文件多模式)
├── package.json # 项目元信息 & 脚本
├── .gitignore # Git 忽略规则
├── test.js # Node/Bun 端测试
├── index.html # 浏览器端测试页面
├── README.md # 项目文档
└── dist/ # 构建产物输出目录
```
---
## 三、测试体系
### Node.js / Bun 测试 (`test.js`)
- 直接 require 源码运行
- 彩色终端输出 (✓/✗)
- 覆盖全部 API 的核心场景
- 失败时 process.exit(1)
- 命令: `npm test`
### 浏览器测试 (`index.html`)
- 引入 dist/windychen-utils.js (UMD)
- 深色主题 UI 展示结果
- 绿色通过 / 红色失败
- 命令: `npm run build:umd && open index.html`
**已修复 Bug**
- array.js 中 `_resolveMatcher` 未处理 item 为 null/undefined 导致的 TypeError
- 修复: `item != null && item.getPro(...)`
---
## 四、关键决策记录
1. **Prototype 扩展方式**: 直接扩展 Object/Array 原型,使用时无需导入,全局可用
2. **路径解析设计**: 正则统一处理普通键和特殊键(含点号),避免分支逻辑
3. **Array Pro 方法设计**: 参数重载(function/string+value/value)覆盖常见使用场景
4. **Storage 自动降级**: IndexedDB → localStorage → null,最大化兼容性
5. **Webpack 单配置**: 通过 --env format 参数控制输出,避免维护多个配置文件
---
## 五、待改进方向
- [ ] 添加单元测试框架(如 Jest/Vitest
- [ ] 支持 TypeScript 类型声明 (.d.ts)
- [ ] Storage 模块添加 delete/clear 方法
- [ ] 考虑添加更多工具方法(日期、字符串、数字等)
- [ ] CI/CD 自动化构建与发布
+101
View File
@@ -0,0 +1,101 @@
# Windychen Utils
轻量级 JavaScript/TypeScript 工具库,提供对象、数组、存储等常用扩展方法。
## 安装
```bash
npm install windychen-untils
```
## 使用
### Node.js / Bun.js
```javascript
require('windychen-untils');
// Object 扩展
const obj = { a: { b: { c: 1 } } };
obj.getPro('a.b.c'); // 1
obj.setPro('x.y.z', 'hello');
// Array 扩展
[{ id: '1' }, { id: '2' }].findPro('id', '2');
```
### 浏览器
```html
<script src="dist/windychen-utils.js"></script>
```
### ES Module
```javascript
import from 'windychen-untils/dist/windychen-utils.esm.js';
```
## API 参考
### Object
| 方法 | 说明 | 示例 |
|------|------|------|
| `getPro(path, default?)` | 安全获取嵌套属性,支持特殊键名 | `obj.getPro('a.b.c')` / `obj.getPro("['x.y'].z")` |
| `setPro(path, value)` | 设置嵌套路径值,自动创建中间对象 | `obj.setPro('foo.bar', 1)` |
**路径格式支持:**
- 普通点分隔:`'a.b.c'`
- 特殊键名:`"['key.with.dot']"`
- 混合:`"['a.b'].c['d.e']"`
### Array
| 方法 | 说明 | 示例 |
|------|------|------|
| `findPro(fn \| key, val?)` | 增强查找 | `arr.findPro('id', '2')` |
| `filterPro(fn \| key, val?)` | 增强过滤 | `arr.filterPro('user.name', 'Alice')` |
| `findIndexPro(fn \| key, val?)` | 增强索引查找 | `arr.findIndexPro('status', 1)` |
| `findLastPro(fn \| key, val?)` | 从末尾查找 | `arr.findLastPro('type', 'b')` |
| **somePro(fn \| key, val?)** | 任一匹配 | `arr.somePro('active', true)` |
| **everyPro(fn \| key, val?)** | 全部匹配 | `arr.everyPro('valid', true)` |
**三种调用方式:**
```javascript
// 回调函数(同原生)
arr.findPro(item => item.id > 1);
// 单值精确匹配
['aa', 'bb'].findPro('bb');
// key-value 匹配(支持嵌套路径)
[{ user: { name: 'Bob' } }].findPro('user.name', 'Bob');
```
### Storage(浏览器环境)
自动选择存储后端:
- 支持 IndexedDB → 使用 `IDBStorage`
- 仅支持 localStorage → 使用 `ILocalStorage`(内存缓存)
```javascript
import storage from 'windychen-untils';
await storage.set('key', { data: 123 });
await storage.get('key', null); // { data: 123 }
```
## 构建
```bash
npm run build # 打包全部 (CJS + ESM + UMD)
npm run build:cjs # CommonJS
npm run build:esm # ES Module
npm run build:umd # UMD (浏览器)
npm test # 运行测试
```
## License
ISC
+46
View File
@@ -0,0 +1,46 @@
/**
* 解析 Pro 方法的匹配器参数
* @param {(Function|string|*)} a - 回调函数、属性键、或单值
* @param {*=} b - 属性值(与 a 配合使用)
* @returns {function(*): boolean} 匹配函数
* @private
*/
const _resolveMatcher = (a, b) => {
if (typeof a === 'function') return a;
if (typeof a === 'string' && b !== undefined) return (item) => item != null && item.getPro(a) === b;
return (item) => item === a;
};
/** @private @type {string[]} 支持增强方法的原始方法列表 */
const _methods = ['find', 'filter', 'findIndex', 'findLast', 'some', 'every'];
_methods.forEach((method) => {
/**
* 增强版数组遍历方法,支持三种调用模式
*
* **模式1 - 回调函数**(同原生方法)
* @example
* arr.findPro(item => item.id > 1)
* arr.filterPro(item => item.active)
*
* **模式2 - 单值精确匹配**
* @example
* ['aa','bb','cc'].findPro('bb')
* [1,2,2,3].filterPro(2)
*
* **模式3 - key-value 匹配**(支持嵌套路径,通过 getPro 访问)
* @example
* [{ user: { name: 'Bob' } }].findPro('user.name', 'Bob')
* arr.filterPro('status', 1)
*
* @name ${method}Pro
* @memberof Array.prototype
* @method
* @param {(Function|string|*)} a - 回调函数、属性键、或单值
* @param {*=} [b] - 属性值(当 a 为 string 时使用)
* @returns {*} 返回结果同对应原生方法
*/
Array.prototype[`${method}Pro`] = function (a, b) {
return this[method](_resolveMatcher(a, b));
};
});
+120
View File
@@ -0,0 +1,120 @@
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8" />
<title>Windychen Utils - 浏览器测试</title>
<style>
body { font-family: monospace; padding: 20px; background: #1a1a2e; color: #eee; }
.pass { color: #4ade80; }
.fail { color: #f87171; }
.group { margin: 16px 0; font-weight: bold; color: #60a5fa; }
h1 { border-bottom: 2px solid #3b82f6; padding-bottom: 10px; }
</style>
</head>
<body>
<h1>Windychen Utils 测试结果</h1>
<div id="output"></div>
<script src="dist/windychen-utils.js"></script>
<script>
const $ = document.getElementById.bind(document);
let passed = 0, failed = 0;
function assert(desc, condition) {
const el = document.createElement('div');
el.className = condition ? 'pass' : 'fail';
el.textContent = `${condition ? '✓' : '✗'} ${desc}`;
$('output').appendChild(el);
condition ? passed++ : failed++;
}
function group(name) {
const el = document.createElement('div');
el.className = 'group';
el.textContent = `\n─── ${name} ───`;
$('output').appendChild(el);
}
// ===== Object.getPro 测试 =====
group('Object.getPro');
const obj = { a: { b: { c: 1 } }, 'x.y': { z: 2 } };
assert("getPro('a.b.c') === 1", obj.getPro('a.b.c') === 1);
assert("getPro('a.b.d', 'default') === 'default'", obj.getPro('a.b.d', 'default') === 'default');
assert("getPro(\"['x.y'].z\") === 2", obj.getPro("['x.y'].z") === 2);
assert("getPro('missing', 42) === 42", obj.getPro('missing', 42) === 42);
// ===== Object.setPro 测试 =====
group('Object.setPro');
const obj2 = {};
obj2.setPro('foo.bar.baz', 'hello');
assert("setPro 嵌套赋值", obj2.foo.bar.baz === 'hello');
obj2.setPro("['a.b'].c", 99);
assert("setPro 特殊键名", obj2['a.b'].c === 99);
// ===== Object.getTypeString 测试 =====
group('Object.getTypeString');
assert("getTypeString('hello') === 'String'", Object.getTypeString('hello') === 'String');
assert("getTypeString(123) === 'Number'", Object.getTypeString(123) === 'Number');
assert("getTypeString([1,2]) === 'Array'", Object.getTypeString([1,2]) === 'Array');
assert("getTypeString(null) === 'Null'", Object.getTypeString(null) === 'Null');
assert("getTypeString(undefined) === 'Undefined'", Object.getTypeString(undefined) === 'Undefined');
assert("getTypeString({}) === 'Object'", Object.getTypeString({}) === 'Object');
assert("getTypeString(true) === 'Boolean'", Object.getTypeString(true) === 'Boolean');
// ===== Array.findPro 测试 =====
group('Array.findPro');
const arr = [
{ user: { name: 'Alice' } },
{ user: { name: 'Bob' } },
null,
{ user: { name: 'Charlie' } },
];
assert("findPro 回调", [1, 2, 3].findPro(t => t > 1) === 2);
assert("findPro key-value", arr.findPro('user.name', 'Bob')?.user?.name === 'Bob');
assert("findPro 单值", ['aa', 'bb', 'cc'].findPro('cc') === 'cc');
// ===== Array.filterPro 测试 =====
group('Array.filterPro');
assert("filterPro key-value", arr.filterPro('user.name', 'Alice').length === 1);
assert("filterPro 单值", [1, 2, 2, 3].filterPro(2).length === 2);
// ===== Array.findIndexPro 测试 =====
group('Array.findIndexPro');
assert("findIndexPro", arr.findIndexPro('user.name', 'Charlie') === 3);
// ===== Array.findLastPro 测试 =====
group('Array.findLastPro');
assert("findLastPro", arr.findLastPro('user.name', 'Bob')?.user?.name === 'Bob');
// ===== Array.somePro / everyPro 测试 =====
group('Array.somePro / everyPro');
assert("somePro", arr.somePro('user.name', 'Alice'));
assert("everyPro", !arr.everyPro('user.name', 'Alice'));
// ===== Storage 测试 =====
group('Storage');
const storage = new window.IStorage();
assert("IStorage 实例存在", storage != null);
(async function () {
await storage.set('test_key', { data: 123, name: 'hello' });
assert("storage.set 执行成功", true);
const val = await storage.get('test_key', null);
assert("storage.get 返回值正确", val && val.data === 123 && val.name === 'hello');
const missing = await storage.get('_not_exist_', 'default');
assert("storage.get 缺失键返回默认值", missing === 'default');
// 汇总
const summary = document.createElement('h2');
summary.style.marginTop = '20px';
summary.innerHTML = `${passed + failed} 项,<span class="pass">${passed} 通过</span><span class="fail">${failed} 失败</span>`;
$('output').appendChild(summary);
})();
</script>
</body>
</html>
+4
View File
@@ -0,0 +1,4 @@
require('./object');
require('./array');
try { require('./storage'); } catch (e) {} // 仅浏览器环境生效
+74
View File
@@ -0,0 +1,74 @@
/**
* 获取值的类型字符串
* @param {*} val - 任意值
* @returns {string} 类型名称,如 'String' 'Number' 'Array' 'Null' 'Undefined'
* @example
* Object.getTypeString('hello') // 'String'
* Object.getTypeString(123) // 'Number'
* Object.getTypeString(null) // 'Null'
*/
Object.getTypeString = function (val) {
return Object.prototype.toString.call(val).replace(/\[object |]/g, '');
};
/**
* 解析路径字符串为键数组
* @param {string} path - 路径字符串,支持点分隔、特殊键名、混合格式
* @returns {string[]} 键数组
* @private
*/
const _parsePath = (path) => {
const keys = [];
const keyRegex = /\[['"]([^'"]+)['"]\]|[^.\[\]]+/g;
let match;
while ((match = keyRegex.exec(path)) !== null) {
keys.push(match[1] || match[0]);
}
return keys;
};
/**
* 安全获取对象嵌套属性
* @param {string} path - 属性路径,支持多种格式:
* - 普通点分隔: 'a.b.c'
* - 特殊键名: "['key.with.dot']"
* - 混合: "['a.b'].c['d.e']"
* @param {*=} defaultValue - 路径不存在或值为 undefined 时返回的默认值
* @returns {*} 找到的属性值或默认值
* @example
* { a: { b: { c: 1 } } }.getPro('a.b.c') // 1
* { a: { b: { c: 1 } } }.getPro('a.x.y', 'default') // 'default'
* { 'x.y': { z: 2 } }.getPro("['x.y'].z") // 2
*/
Object.prototype.getPro = function (path, defaultValue) {
let result = this;
for (const key of _parsePath(path)) {
if (result == null) return defaultValue;
result = result[key];
}
return result !== undefined ? result : defaultValue;
};
/**
* 设置对象嵌套路径值(自动创建中间对象)
* @param {string} path - 属性路径,格式同 getPro
* @param {*} value - 要设置的值
* @returns {void}
* @example
* const obj = {};
* obj.setPro('a.b.c', 1); // obj => { a: { b: { c: 1 } } }
* obj.setPro("['x.y'].z", 2); // obj => { a: {...}, 'x.y': { z: 2 } }
*/
Object.prototype.setPro = function (path, value) {
const keys = _parsePath(path);
let target = this;
for (let i = 0; i < keys.length - 1; i++) {
if (target[keys[i]] == null || typeof target[keys[i]] !== 'object') {
target[keys[i]] = {};
}
target = target[keys[i]];
}
target[keys[keys.length - 1]] = value;
};
+22
View File
@@ -0,0 +1,22 @@
{
"name": "windychen-untils",
"version": "1.0.0",
"main": "dist/windychen-utils.cjs.js",
"module": "dist/windychen-utils.esm.js",
"browser": "dist/windychen-utils.js",
"scripts": {
"build": "webpack --mode production",
"build:cjs": "webpack --mode production --env format=cjs",
"build:esm": "webpack --mode production --env format=esm",
"build:umd": "webpack --mode production --env format=umd",
"dev": "webpack --mode development",
"test": "node test.js"
},
"author": "windychen",
"license": "ISC",
"description": "",
"devDependencies": {
"webpack": "^5.90.0",
"webpack-cli": "^5.1.4"
}
}
+168
View File
@@ -0,0 +1,168 @@
/** @type {Function} 日志输出 */
var log = console.warn;
/**
* 判断值是否为 String 类型
* @param {*} str - 待检测值
* @returns {boolean}
* @private
*/
var isString = function (str) { return Object.getTypeString(str) === 'String'; };
/**
* IndexedDB 存储实现(大容量存储优先选择)
*
* @class IDBStorage
* @example
* const storage = new IDBStorage('myDb', 'myStore', 1);
* await storage.set('key', { data: 123 });
* await storage.get('key', null);
*/
class IDBStorage {
/**
* 创建 IndexedDB 存储实例
* @param {string} [dbName='dbName'] - 数据库名称
* @param {string} [storeName='storeName'] - 对象存储名称
* @param {number} [version=1] - 数据库版本号
*/
constructor(dbName, storeName, version) {
/** @type {number} 数据库版本号 */
this.version = version || 1;
/** @type {string} 数据库名称 */
this.dbName = dbName || 'dbName';
/** @type {string} 对象存储名称 */
this.storeName = storeName || 'storeName';
var request = indexedDB.open(this.dbName, this.version);
/** @type {Promise<IDBDatabase>} 数据库连接 Promise */
this.requestPromise = new Promise(function (resolve, reject) {
request.onupgradeneeded = function (e) {
if (e && e.target && !request.result.objectStoreNames.contains(this.storeName)) {
request.result.createObjectStore(this.storeName, { keyPath: 'key' })
.createIndex('key', 'key', { unique: false });
}
log('[Index][open]-upgrade');
resolve(request.result);
}.bind(this);
request.onsuccess = function () { log('[IndexDB][open]-success'); resolve(request.result); };
request.onerror = function (e) { log('[IndexDB][open]-error', 2); reject(e); };
}.bind(this));
}
/**
* 获取存储值
* @template T
* @param {string} key - 键名
* @param {T} initValue - 默认值(未找到时返回)
* @returns {Promise<T>} 存储的值或默认值
*/
get(key, initValue) {
var _this = this;
return new Promise(async function (resolve) {
var db = await _this.requestPromise;
var transaction = db.transaction(_this.storeName, 'readonly');
var objectStore = transaction.objectStore(_this.storeName);
var req = objectStore.get(key);
req.onsuccess = function () { resolve(req.result ? req.result.value : (initValue || null)); };
req.onerror = function (e) { log('[indexDB][get]-error', 2, e); resolve(initValue || null); };
});
}
/**
* 设置存储值
* @template T
* @param {string} key - 键名
* @param {T} value - 要存储的值
* @returns {Promise<T>} 返回写入的值
*/
set(key, value) {
var _this = this;
return new Promise(async function (resolve) {
var db = await _this.requestPromise;
var transaction = db.transaction(_this.storeName, 'readwrite');
var objectStore = transaction.objectStore(_this.storeName);
var req = objectStore.put({ key: key, value: value });
req.onsuccess = function () { resolve(value); };
req.onerror = function (e) { log('[indexDB][set]-error', 2, e); resolve(value); };
});
}
}
/**
* localStorage 存储实现(兼容性好,带内存缓存)
*
* @class ILocalStorage
* @example
* const storage = new ILocalStorage();
* await storage.set('key', { data: 456 });
* await storage.get('key', null);
*/
class ILocalStorage {
constructor() {
/** @type {Map<string, *>} 内存缓存 */
this.cache = new Map();
this.load();
}
/** 从 localStorage 全量加载到缓存 */
load() {
var _this = this;
Object.entries(localStorage).forEach(function (entry) {
var key = entry[0], value = entry[1];
try {
_this.cache.set(key, JSON.parse(value));
} catch (error) {
log('[localStorage][load]-error', 2, error, key, value);
}
});
}
/**
* 从缓存获取值
* @template T
* @param {string} key - 键名
* @param {T} initValue - 默认值
* @returns {Promise<T>} 缓存的值或默认值
*/
get(key, initValue) {
return new Promise(function (resolve) {
resolve(this.cache.get(key) !== undefined ? this.cache.get(key) : initValue);
}.bind(this));
}
/**
* 写入 localStorage 并更新缓存
* @template T
* @param {string} key - 键名
* @param {T} value - 要存储的值
* @returns {Promise<T>} 返回写入的值
*/
set(key, value) {
return new Promise(function (resolve) {
var _value = isString(value) ? value : '';
try {
_value = JSON.stringify(value);
} catch (e) {
log('[localStorage][set]-error', 2, e);
}
localStorage.setItem(key, _value);
this.cache.set(key, _value);
resolve(value);
}.bind(this));
}
}
// 浏览器环境自动选择存储方式
if (typeof window !== 'undefined') {
/**
* 当前环境可用的存储类(IDBStorage 或 ILocalStorage
* @type {Function|null}
* @global
*/
window.IStorage = window.indexedDB ? IDBStorage : (window.localStorage ? ILocalStorage : null);
if (typeof module !== 'undefined') {
module.exports = new window.IStorage();
}
} else if (typeof module !== 'undefined') {
module.exports = null;
}
+67
View File
@@ -0,0 +1,67 @@
require('./index');
let passed = 0;
let failed = 0;
function assert(desc, condition) {
if (condition) {
console.log(`\x1b[32m ✓ ${desc}\x1b[0m`);
passed++;
} else {
console.log(`\x1b[31m ✗ ${desc}\x1b[0m`);
failed++;
}
}
function group(name) {
console.log(`\n\x1b[36m─── ${name} ───\x1b[0m`);
}
// ===== Object.getPro =====
group('Object.getPro');
const obj = { a: { b: { c: 1 } }, 'x.y': { z: 2 } };
assert("getPro('a.b.c') === 1", obj.getPro('a.b.c') === 1);
assert("getPro('a.b.d', 'default') === 'default'", obj.getPro('a.b.d', 'default') === 'default');
assert("getPro(\"['x.y'].z\") === 2", obj.getPro("['x.y'].z") === 2);
assert("getPro('missing', 42) === 42", obj.getPro('missing', 42) === 42);
// ===== Object.setPro =====
group('Object.setPro');
const obj2 = {};
obj2.setPro('foo.bar.baz', 'hello');
assert("setPro 嵌套赋值", obj2.foo.bar.baz === 'hello');
obj2.setPro("['a.b'].c", 99);
assert("setPro 特殊键名", obj2['a.b'].c === 99);
// ===== Array Pro 方法 =====
const arr = [
{ user: { name: 'Alice' } },
{ user: { name: 'Bob' } },
null,
{ user: { name: 'Charlie' } },
];
group('Array.findPro');
assert("回调 findPro(t=>t>1)", [1, 2, 3].findPro((t) => t > 1) === 2);
assert("key-value findPro('user.name','Bob')", arr.findPro('user.name', 'Bob').user.name === 'Bob');
assert("单值 findPro('cc')", ['aa', 'bb', 'cc'].findPro('cc') === 'cc');
group('Array.filterPro');
assert("key-value filterPro", arr.filterPro('user.name', 'Alice').length === 1);
assert("单值 filterPro(2)", [1, 2, 2, 3].filterPro(2).length === 2);
group('Array.findIndexPro');
assert("findIndexPro", arr.findIndexPro('user.name', 'Charlie') === 3);
group('Array.findLastPro');
assert("findLastPro", arr.findLastPro('user.name', 'Bob').user.name === 'Bob');
group('Array.somePro / everyPro');
assert("somePro", arr.somePro('user.name', 'Alice') === true);
assert("everyPro false", arr.everyPro('user.name', 'Alice') === false);
assert("everyPro true", ['a', 'a'].everyPro('a') === true);
// 汇总
console.log(`\n\x1b[33m共 ${passed + failed} 项,${passed} 通过,${failed} 失败\x1b[0m`);
process.exit(failed > 0 ? 1 : 0);
+44
View File
@@ -0,0 +1,44 @@
const path = require('path');
const configs = {
cjs: {
output: {
path: path.resolve(__dirname, 'dist'),
filename: 'windychen-utils.cjs.js',
libraryTarget: 'commonjs2',
},
},
esm: {
experiments: { outputModule: true },
output: {
path: path.resolve(__dirname, 'dist'),
filename: 'windychen-utils.esm.js',
libraryTarget: 'module',
},
},
umd: {
output: {
path: path.resolve(__dirname, 'dist'),
filename: 'windychen-utils.js',
library: { name: 'WindychenUtils', type: 'umd', export: 'default' },
},
},
};
module.exports = (env = {}) => {
const format = env.format;
if (!format || format === 'all') {
return Object.keys(configs).map((key) => ({
name: key,
entry: './index.js',
...configs[key],
}));
}
if (!configs[format]) {
throw new Error(`不支持的格式: ${format},可选值: cjs, esm, umd`);
}
return { entry: './index.js', ...configs[format] };
};