feature: vor und nachteile

This commit is contained in:
Hikyu
2026-05-31 13:54:31 +02:00
parent 552a947a1c
commit e3dad17446
23270 changed files with 3024794 additions and 200 deletions
+220
View File
@@ -0,0 +1,220 @@
<h1 align='center'>packrup</h1>
<p align="center">
<a href='https://github.com/harlan-zw/packrup/actions/workflows/test.yml'>
</a>
<a href="https://www.npmjs.com/package/packrup" target="__blank"><img src="https://img.shields.io/npm/v/packrup?style=flat&colorA=002438&colorB=28CF8D" alt="NPM version"></a>
<a href="https://www.npmjs.com/package/packrup" target="__blank"><img alt="NPM Downloads" src="https://img.shields.io/npm/dm/packrup?flat&colorA=002438&colorB=28CF8D"></a>
<a href="https://github.com/harlan-zw/packrup" target="__blank"><img alt="GitHub stars" src="https://img.shields.io/github/stars/harlan-zw/packrup?flat&colorA=002438&colorB=28CF8D"></a>
</p>
<p align="center">
Simple utils to pack (and unpack) arrays and strings to a flat object.
</p>
<p align="center">
<table>
<tbody>
<td align="center">
<img width="800" height="0" /><br>
<i>Status:</i> Stable</b> <br>
<sup> Please report any issues 🐛</sup><br>
<sub>Made possible by my <a href="https://github.com/sponsors/harlan-zw">Sponsor Program 💖</a><br> Follow me <a href="https://twitter.com/harlan_zw">@harlan_zw</a> 🐦 • Join <a href="https://discord.gg/275MBUBvgP">Discord</a> for help</sub><br>
<img width="800" height="0" />
</td>
</tbody>
</table>
</p>
## Background
The [zhead](https://github.com/harlan-zw/zhead) package provides a flat-object style API for HTML `<meta>` tags,
to make this happen we need to pack and unpack arrays and strings to a flat object.
For example, the following object:
```json
{
"viewport": {
"content": {
"width": "device-width",
"initial-scale": "1"
}
}
}
```
Can be packed to the below (and vice versa):
```html
<meta name="viewport" content="width=device-width, initial-scale=1">
```
For an example see [useSeoMeta](https://github.com/unjs/unhead/blob/main/packages/shared/src/meta.ts).
## Features
- Pack arrays, objects and strings to a flat object
- Handles duplicates with `key`
- Supports nested key selections with `dot.notation`
- 🌳 Composable, tree-shakable and tiny (< 1kb, see [export-size-report](https://github.com/harlan-zw/packrup/blob/main/export-size-report.json))
## Help Wanted
These utils were meant to be fully typed, but I struggled with the implementation. If you want a fun TypeScript challenge
then please open a PR :).
## Installation
```bash
npm install --save-dev packrup
# Using yarn
yarn add --dev packrup
```
## API
### packArray
**Arguments**
- _input_ - `array`
The array to pack
- _options_ - `{ key: string | string[], value: string | string[] }`
The options to use to resolve the key and value.
By default, will choose first 2 keys of an object.
```ts
import { packArray } from 'packrup'
packArray([
{ 'http-equiv': 'content-security-policy', 'content': 'content-src none' }
])
// {
// 'content-security-policy': 'content-src none',
// }
```
### packObject
**Arguments**
- _input_ - `object`
The record to pack.
- _options_ - `{ key: string | string[], value: string | string[] }`
The options to use to resolve the key and value.
By default, will choose first 2 keys of an object.
```ts
import { packObject } from 'packrup'
packObject({
image: {
src: {
'1x': 'https://example.com/image.png',
'2x': 'https://example.com/image@2x.png'
},
alt: 'Example Image'
},
}, {
key: 'image.src.1x',
value: 'image.alt'
})
// {
// "https://example.com/image.png": "Example Image",
// }
```
### packString
```ts
import { packString } from 'packrup'
const head = packString('src="https://example.com/image.jpg" width="800" height="600"')
// {
// "height": "600",
// "src": "https://example.com/image.jpg",
// "width": "800",
// }
```
### unpackToArray
**Arguments**
- _input_ - `array`
The array to pack
- _options_ - `{ key: string | string[], value: string | string[] }`
The options to use to resolve the key and value.
By default, will choose first 2 keys of an object.
```ts
import { unpackToArray } from 'packrup'
unpackToArray({
'content-security-policy': 'content-src none',
}, { key: 'http-equiv', value: 'content' })
```
### unpackToString
**Arguments**
- _input_ - `object`
The record to unpack to a string.
- _options_
```ts
export interface TransformValueOptions {
entrySeparator?: string
keyValueSeparator?: string
wrapValue?: string
resolve?: (ctx: { key: string, value: unknown }) => string | void
}
```
```ts
import { unpackToString } from 'packrup'
unpackToString({
'noindex': true,
'nofollow': true,
'max-snippet': 20,
'maxi-image-preview': 'large',
}, {
resolve({ key, value }) {
if (typeof value === 'boolean')
return `${key}`
},
keyValueSeparator: ':',
entrySeparator: ', ',
})
// "noindex, nofollow, max-snippet:20, maxi-image-preview:large"
```
## Sponsors
<p align="center">
<a href="https://raw.githubusercontent.com/harlan-zw/static/main/sponsors.svg">
<img src='https://raw.githubusercontent.com/harlan-zw/static/main/sponsors.svg'/>
</a>
</p>
## License
MIT License © 2022-PRESENT [Harlan Wilton](https://github.com/harlan-zw)
+115
View File
@@ -0,0 +1,115 @@
'use strict';
function asArray(input) {
return Array.isArray(input) ? input : [input];
}
const InternalKeySymbol = "_$key";
function packObject(input, options) {
const keys = Object.keys(input);
let [k, v] = keys;
options = options || {};
options.key = options.key || k;
options.value = options.value || v;
options.resolveKey = options.resolveKey || ((k2) => k2);
const resolveKey = (index) => {
const arr = asArray(options[index]);
return arr.find((k2) => {
if (typeof k2 === "string" && k2.includes(".")) {
return k2;
}
return k2 && keys.includes(k2);
});
};
const resolveValue = (k2, input2) => {
if (k2.includes(".")) {
const paths = k2.split(".");
let val = input2;
for (const path of paths)
val = val[path];
return val;
}
return input2[k2];
};
k = resolveKey("key") || k;
v = resolveKey("value") || v;
const dedupeKeyPrefix = input.key ? `${InternalKeySymbol}${input.key}-` : "";
let keyValue = resolveValue(k, input);
keyValue = options.resolveKey(keyValue);
return {
[`${dedupeKeyPrefix}${keyValue}`]: resolveValue(v, input)
};
}
function packArray(input, options) {
const packed = {};
for (const i of input) {
const packedObj = packObject(i, options);
const pKey = Object.keys(packedObj)[0];
const isDedupeKey = pKey.startsWith(InternalKeySymbol);
if (!isDedupeKey && packed[pKey]) {
packed[pKey] = Array.isArray(packed[pKey]) ? packed[pKey] : [packed[pKey]];
packed[pKey].push(Object.values(packedObj)[0]);
} else {
packed[isDedupeKey ? pKey.split("-").slice(1).join("-") || pKey : pKey] = packedObj[pKey];
}
}
return packed;
}
function packString(input) {
const output = {};
input.split(" ").forEach(
(item) => {
const val = item.replace(/"/g, "").split("=");
output[val[0]] = val[1];
}
);
return output;
}
function unpackToArray(input, options) {
const unpacked = [];
const kFn = options.resolveKeyData || ((ctx) => ctx.key);
const vFn = options.resolveValueData || ((ctx) => ctx.value);
for (const [k, v] of Object.entries(input)) {
unpacked.push(...(Array.isArray(v) ? v : [v]).map((i) => {
const ctx = { key: k, value: i };
const val = vFn(ctx);
if (typeof val === "object")
return unpackToArray(val, options);
if (Array.isArray(val))
return val;
return {
[typeof options.key === "function" ? options.key(ctx) : options.key]: kFn(ctx),
[typeof options.value === "function" ? options.value(ctx) : options.value]: val
};
}).flat());
}
return unpacked;
}
function unpackToString(value, options) {
return Object.entries(value).map(([key, value2]) => {
if (typeof value2 === "object")
value2 = unpackToString(value2, options);
if (options.resolve) {
const resolved = options.resolve({ key, value: value2 });
if (typeof resolved !== "undefined")
return resolved;
}
if (typeof value2 === "number")
value2 = value2.toString();
if (typeof value2 === "string" && options.wrapValue) {
value2 = value2.replace(new RegExp(options.wrapValue, "g"), `\\${options.wrapValue}`);
value2 = `${options.wrapValue}${value2}${options.wrapValue}`;
}
return `${key}${options.keyValueSeparator || ""}${value2}`;
}).join(options.entrySeparator || "");
}
exports.InternalKeySymbol = InternalKeySymbol;
exports.packArray = packArray;
exports.packObject = packObject;
exports.packString = packString;
exports.unpackToArray = unpackToArray;
exports.unpackToString = unpackToString;
+43
View File
@@ -0,0 +1,43 @@
type Arrayable<T> = T | T[];
interface PackArrayOptions<T extends Record<string, any>[]> extends PackOptions<T[number]> {
key?: Arrayable<keyof T[number] | string>;
value?: Arrayable<keyof T[number] | string>;
}
interface PackOptions<T extends Record<keyof T, any>> {
key?: Arrayable<keyof T | string>;
value?: Arrayable<keyof T | string>;
resolveKey?: (key: keyof T) => string;
}
declare function packArray<T extends Record<string, any>[]>(input: T, options?: PackArrayOptions<T>): Partial<Record<string, any>>;
declare const InternalKeySymbol = "_$key";
declare function packObject<T extends Record<string, any>>(input: T, options?: PackOptions<T>): Partial<T>;
declare function packString<T extends string>(input: T): Record<string, any>;
interface Context {
key: string;
value: any;
}
type ResolveFn = (ctx: Context) => string;
interface UnpackArrayOptions {
key: string | ResolveFn;
value: string | ResolveFn;
resolveKeyData?: ResolveFn;
resolveValueData?: ResolveFn;
}
declare function unpackToArray(input: Record<string, any>, options: UnpackArrayOptions): Record<string, any>[];
interface TransformValueOptions {
entrySeparator?: string;
keyValueSeparator?: string;
wrapValue?: string;
resolve?: (ctx: {
key: string;
value: unknown;
}) => string | void;
}
declare function unpackToString<T extends Record<keyof T, unknown>>(value: T, options: TransformValueOptions): string;
export { InternalKeySymbol, type TransformValueOptions, type UnpackArrayOptions, packArray, packObject, packString, unpackToArray, unpackToString };
+43
View File
@@ -0,0 +1,43 @@
type Arrayable<T> = T | T[];
interface PackArrayOptions<T extends Record<string, any>[]> extends PackOptions<T[number]> {
key?: Arrayable<keyof T[number] | string>;
value?: Arrayable<keyof T[number] | string>;
}
interface PackOptions<T extends Record<keyof T, any>> {
key?: Arrayable<keyof T | string>;
value?: Arrayable<keyof T | string>;
resolveKey?: (key: keyof T) => string;
}
declare function packArray<T extends Record<string, any>[]>(input: T, options?: PackArrayOptions<T>): Partial<Record<string, any>>;
declare const InternalKeySymbol = "_$key";
declare function packObject<T extends Record<string, any>>(input: T, options?: PackOptions<T>): Partial<T>;
declare function packString<T extends string>(input: T): Record<string, any>;
interface Context {
key: string;
value: any;
}
type ResolveFn = (ctx: Context) => string;
interface UnpackArrayOptions {
key: string | ResolveFn;
value: string | ResolveFn;
resolveKeyData?: ResolveFn;
resolveValueData?: ResolveFn;
}
declare function unpackToArray(input: Record<string, any>, options: UnpackArrayOptions): Record<string, any>[];
interface TransformValueOptions {
entrySeparator?: string;
keyValueSeparator?: string;
wrapValue?: string;
resolve?: (ctx: {
key: string;
value: unknown;
}) => string | void;
}
declare function unpackToString<T extends Record<keyof T, unknown>>(value: T, options: TransformValueOptions): string;
export { InternalKeySymbol, type TransformValueOptions, type UnpackArrayOptions, packArray, packObject, packString, unpackToArray, unpackToString };
+43
View File
@@ -0,0 +1,43 @@
type Arrayable<T> = T | T[];
interface PackArrayOptions<T extends Record<string, any>[]> extends PackOptions<T[number]> {
key?: Arrayable<keyof T[number] | string>;
value?: Arrayable<keyof T[number] | string>;
}
interface PackOptions<T extends Record<keyof T, any>> {
key?: Arrayable<keyof T | string>;
value?: Arrayable<keyof T | string>;
resolveKey?: (key: keyof T) => string;
}
declare function packArray<T extends Record<string, any>[]>(input: T, options?: PackArrayOptions<T>): Partial<Record<string, any>>;
declare const InternalKeySymbol = "_$key";
declare function packObject<T extends Record<string, any>>(input: T, options?: PackOptions<T>): Partial<T>;
declare function packString<T extends string>(input: T): Record<string, any>;
interface Context {
key: string;
value: any;
}
type ResolveFn = (ctx: Context) => string;
interface UnpackArrayOptions {
key: string | ResolveFn;
value: string | ResolveFn;
resolveKeyData?: ResolveFn;
resolveValueData?: ResolveFn;
}
declare function unpackToArray(input: Record<string, any>, options: UnpackArrayOptions): Record<string, any>[];
interface TransformValueOptions {
entrySeparator?: string;
keyValueSeparator?: string;
wrapValue?: string;
resolve?: (ctx: {
key: string;
value: unknown;
}) => string | void;
}
declare function unpackToString<T extends Record<keyof T, unknown>>(value: T, options: TransformValueOptions): string;
export { InternalKeySymbol, type TransformValueOptions, type UnpackArrayOptions, packArray, packObject, packString, unpackToArray, unpackToString };
+108
View File
@@ -0,0 +1,108 @@
function asArray(input) {
return Array.isArray(input) ? input : [input];
}
const InternalKeySymbol = "_$key";
function packObject(input, options) {
const keys = Object.keys(input);
let [k, v] = keys;
options = options || {};
options.key = options.key || k;
options.value = options.value || v;
options.resolveKey = options.resolveKey || ((k2) => k2);
const resolveKey = (index) => {
const arr = asArray(options[index]);
return arr.find((k2) => {
if (typeof k2 === "string" && k2.includes(".")) {
return k2;
}
return k2 && keys.includes(k2);
});
};
const resolveValue = (k2, input2) => {
if (k2.includes(".")) {
const paths = k2.split(".");
let val = input2;
for (const path of paths)
val = val[path];
return val;
}
return input2[k2];
};
k = resolveKey("key") || k;
v = resolveKey("value") || v;
const dedupeKeyPrefix = input.key ? `${InternalKeySymbol}${input.key}-` : "";
let keyValue = resolveValue(k, input);
keyValue = options.resolveKey(keyValue);
return {
[`${dedupeKeyPrefix}${keyValue}`]: resolveValue(v, input)
};
}
function packArray(input, options) {
const packed = {};
for (const i of input) {
const packedObj = packObject(i, options);
const pKey = Object.keys(packedObj)[0];
const isDedupeKey = pKey.startsWith(InternalKeySymbol);
if (!isDedupeKey && packed[pKey]) {
packed[pKey] = Array.isArray(packed[pKey]) ? packed[pKey] : [packed[pKey]];
packed[pKey].push(Object.values(packedObj)[0]);
} else {
packed[isDedupeKey ? pKey.split("-").slice(1).join("-") || pKey : pKey] = packedObj[pKey];
}
}
return packed;
}
function packString(input) {
const output = {};
input.split(" ").forEach(
(item) => {
const val = item.replace(/"/g, "").split("=");
output[val[0]] = val[1];
}
);
return output;
}
function unpackToArray(input, options) {
const unpacked = [];
const kFn = options.resolveKeyData || ((ctx) => ctx.key);
const vFn = options.resolveValueData || ((ctx) => ctx.value);
for (const [k, v] of Object.entries(input)) {
unpacked.push(...(Array.isArray(v) ? v : [v]).map((i) => {
const ctx = { key: k, value: i };
const val = vFn(ctx);
if (typeof val === "object")
return unpackToArray(val, options);
if (Array.isArray(val))
return val;
return {
[typeof options.key === "function" ? options.key(ctx) : options.key]: kFn(ctx),
[typeof options.value === "function" ? options.value(ctx) : options.value]: val
};
}).flat());
}
return unpacked;
}
function unpackToString(value, options) {
return Object.entries(value).map(([key, value2]) => {
if (typeof value2 === "object")
value2 = unpackToString(value2, options);
if (options.resolve) {
const resolved = options.resolve({ key, value: value2 });
if (typeof resolved !== "undefined")
return resolved;
}
if (typeof value2 === "number")
value2 = value2.toString();
if (typeof value2 === "string" && options.wrapValue) {
value2 = value2.replace(new RegExp(options.wrapValue, "g"), `\\${options.wrapValue}`);
value2 = `${options.wrapValue}${value2}${options.wrapValue}`;
}
return `${key}${options.keyValueSeparator || ""}${value2}`;
}).join(options.entrySeparator || "");
}
export { InternalKeySymbol, packArray, packObject, packString, unpackToArray, unpackToString };
+58
View File
@@ -0,0 +1,58 @@
{
"name": "packrup",
"type": "module",
"version": "0.1.2",
"packageManager": "pnpm@8.15.4",
"description": "Node Schema.org for Simple and Automated Google Rich Results",
"author": "Harlan Wilton <harlan@harlanzw.com>",
"license": "MIT",
"funding": "https://github.com/sponsors/harlan-zw",
"homepage": "https://github.com/harlan-zw/packrup#readme",
"repository": {
"type": "git",
"url": "git+https://github.com/harlan-zw/packrup.git"
},
"bugs": {
"url": "https://github.com/harlan-zw/packrup/issues"
},
"keywords": [
"pack object",
"pack string",
"pack array"
],
"sideEffects": false,
"exports": {
".": {
"types": "./dist/index.d.ts",
"import": "./dist/index.mjs",
"require": "./dist/index.cjs"
}
},
"main": "dist/index.cjs",
"module": "dist/index.mjs",
"types": "dist/index.d.ts",
"files": [
"dist"
],
"devDependencies": {
"@antfu/eslint-config": "^2.8.0",
"@types/fs-extra": "^11.0.4",
"@vitest/ui": "^1.3.1",
"bumpp": "^9.4.0",
"eslint": "^8.57.0",
"fs-extra": "^11.2.0",
"jsdom": "^24.0.0",
"typescript": "^5.4.2",
"unbuild": "^2.0.0",
"utility-types": "^3.11.0",
"vitest": "^1.3.1"
},
"scripts": {
"build": "unbuild",
"stub": "unbuild --stub",
"test": "vitest",
"export:sizes": "npx export-size . -r",
"release": "pnpm build && bumpp && pnpm -r publish --no-git-checks",
"lint": "eslint . --fix"
}
}