@stll/html 0.0.1-placeholder.0 → 0.1.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +23 -0
- package/dist/bounded.d.ts +7 -0
- package/dist/bounded.js +58 -0
- package/dist/email.d.ts +8 -0
- package/dist/email.js +15 -0
- package/dist/load.d.ts +5 -0
- package/dist/load.js +53 -0
- package/dist/nodes.d.ts +10 -0
- package/dist/nodes.js +11 -0
- package/dist/selection-DiMoYiIl.d.ts +5 -0
- package/package.json +74 -1
package/README.md
ADDED
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
# @stll/html
|
|
2
|
+
|
|
3
|
+
String HTML and XML loading for tree-based consumers. HTML uses parse5 with
|
|
4
|
+
its htmlparser2 tree adapter; XML uses `cheerio/slim` directly. This keeps
|
|
5
|
+
Cheerio's byte decoders and HTTP client out of the server bundle.
|
|
6
|
+
|
|
7
|
+
Import `load` and the Cheerio types from `@stll/html`, and DOM node types and predicates from `@stll/html/nodes`.
|
|
8
|
+
Document mode is the default. Pass `false` as the third argument for fragments.
|
|
9
|
+
The loader preserves parse5 serialization and context-aware parsing of inserted
|
|
10
|
+
HTML through the per-document Cheerio hooks. XML options retain slim's parser
|
|
11
|
+
and serializer.
|
|
12
|
+
|
|
13
|
+
Fixture equivalence goldens live beside the API's HTML fixtures test in
|
|
14
|
+
`packages/html/src/loader-equivalence.test.ts`. They contain SHA-256 hashes
|
|
15
|
+
of the original Cheerio 1.2.0 HTML, XML, text, and post-insertion HTML output,
|
|
16
|
+
plus input hashes, across every enrolled input and parser mode.
|
|
17
|
+
|
|
18
|
+
Apache-2.0.
|
|
19
|
+
|
|
20
|
+
`@stll/html/email` owns the shared scripting-disabled parse and serialization mode
|
|
21
|
+
for email previews. `@stll/html/bounded` limits allocations during initial HTML
|
|
22
|
+
loading, leaving later document edits outside that budget. `htmlTreeAdapter` is
|
|
23
|
+
available for adapter instrumentation. Parser dependencies are declared only here.
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import { n as CheerioAPI } from "./selection-DiMoYiIl.js";
|
|
2
|
+
type AllocationBudget = {
|
|
3
|
+
maxDomAllocations: number;
|
|
4
|
+
reject: () => never;
|
|
5
|
+
};
|
|
6
|
+
export declare const loadWithAllocationBudget: (html: string, { maxDomAllocations, reject }: AllocationBudget) => import("cheerio/slim").CheerioAPI;
|
|
7
|
+
export type { CheerioAPI };
|
package/dist/bounded.js
ADDED
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import { load } from "./load.js";
|
|
2
|
+
import { isText } from "domhandler";
|
|
3
|
+
import { adapter } from "parse5-htmlparser2-tree-adapter";
|
|
4
|
+
// src/bounded.ts
|
|
5
|
+
const loadWithAllocationBudget = (html, { maxDomAllocations, reject }) => {
|
|
6
|
+
let allocations = 0;
|
|
7
|
+
let parsingPublisherMarkup = true;
|
|
8
|
+
const charge = (count) => {
|
|
9
|
+
if (!parsingPublisherMarkup) return;
|
|
10
|
+
allocations += count;
|
|
11
|
+
if (allocations > maxDomAllocations) reject();
|
|
12
|
+
};
|
|
13
|
+
const treeAdapter = {
|
|
14
|
+
...adapter,
|
|
15
|
+
createDocument: () => {
|
|
16
|
+
charge(1);
|
|
17
|
+
return adapter.createDocument();
|
|
18
|
+
},
|
|
19
|
+
createDocumentFragment: () => {
|
|
20
|
+
charge(1);
|
|
21
|
+
return adapter.createDocumentFragment();
|
|
22
|
+
},
|
|
23
|
+
createElement: (...args) => {
|
|
24
|
+
charge(1 + args[2].length);
|
|
25
|
+
return adapter.createElement(...args);
|
|
26
|
+
},
|
|
27
|
+
createCommentNode: (...args) => {
|
|
28
|
+
charge(1);
|
|
29
|
+
return adapter.createCommentNode(...args);
|
|
30
|
+
},
|
|
31
|
+
createTextNode: (...args) => {
|
|
32
|
+
charge(1);
|
|
33
|
+
return adapter.createTextNode(...args);
|
|
34
|
+
},
|
|
35
|
+
insertText: (parent, text) => {
|
|
36
|
+
const last = parent.children.at(-1);
|
|
37
|
+
if (last === void 0 || !isText(last)) charge(1);
|
|
38
|
+
return adapter.insertText(parent, text);
|
|
39
|
+
},
|
|
40
|
+
insertTextBefore: (parent, text, reference) => {
|
|
41
|
+
const previous = reference.prev;
|
|
42
|
+
if (previous === null || !isText(previous)) charge(1);
|
|
43
|
+
return adapter.insertTextBefore(parent, text, reference);
|
|
44
|
+
},
|
|
45
|
+
setDocumentType: (...args) => {
|
|
46
|
+
charge(1);
|
|
47
|
+
return adapter.setDocumentType(...args);
|
|
48
|
+
},
|
|
49
|
+
adoptAttributes: (...args) => {
|
|
50
|
+
charge(args[1].length);
|
|
51
|
+
return adapter.adoptAttributes(...args);
|
|
52
|
+
}
|
|
53
|
+
};
|
|
54
|
+
const document = load(html, { treeAdapter });
|
|
55
|
+
parsingPublisherMarkup = false;
|
|
56
|
+
return document;
|
|
57
|
+
};
|
|
58
|
+
export { loadWithAllocationBudget };
|
package/dist/email.d.ts
ADDED
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import { AnyNode } from "./nodes.js";
|
|
2
|
+
export declare const EMAIL_PREVIEW_DOM_OPTIONS: Readonly<{
|
|
3
|
+
scriptingEnabled: false;
|
|
4
|
+
treeAdapter: import("parse5").TreeAdapter<import("parse5-htmlparser2-tree-adapter").Htmlparser2TreeAdapterMap>;
|
|
5
|
+
}>;
|
|
6
|
+
export declare const loadEmailDocument: (html: string) => import("cheerio/slim").CheerioAPI;
|
|
7
|
+
export declare const serializeEmailNode: (node: AnyNode) => string;
|
|
8
|
+
export declare const serializeEmailPreviewMarkup: ($: ReturnType<typeof loadEmailDocument>) => string;
|
package/dist/email.js
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { load } from "./load.js";
|
|
2
|
+
import { serializeOuter } from "parse5";
|
|
3
|
+
import { adapter } from "parse5-htmlparser2-tree-adapter";
|
|
4
|
+
// src/email.ts
|
|
5
|
+
const EMAIL_PREVIEW_DOM_OPTIONS = Object.freeze({
|
|
6
|
+
scriptingEnabled: false,
|
|
7
|
+
treeAdapter: adapter
|
|
8
|
+
});
|
|
9
|
+
const loadEmailDocument = (html) => load(html, EMAIL_PREVIEW_DOM_OPTIONS);
|
|
10
|
+
const serializeEmailNode = (node) => serializeOuter(node, EMAIL_PREVIEW_DOM_OPTIONS);
|
|
11
|
+
const serializeEmailPreviewMarkup = ($) => {
|
|
12
|
+
const html = $("html").get(0);
|
|
13
|
+
return html ? serializeEmailNode(html) : "";
|
|
14
|
+
};
|
|
15
|
+
export { EMAIL_PREVIEW_DOM_OPTIONS, loadEmailDocument, serializeEmailNode, serializeEmailPreviewMarkup };
|
package/dist/load.d.ts
ADDED
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
import { n as CheerioAPI, r as CheerioOptions, t as Cheerio } from "./selection-DiMoYiIl.js";
|
|
2
|
+
export declare const htmlTreeAdapter: import("parse5").TreeAdapter<import("parse5-htmlparser2-tree-adapter").Htmlparser2TreeAdapterMap>;
|
|
3
|
+
/** Parse strings without loading Cheerio's byte decoders or HTTP client. */
|
|
4
|
+
export declare const load: (content: string, options?: CheerioOptions | null, document?: boolean) => CheerioAPI;
|
|
5
|
+
export type { Cheerio, CheerioAPI, CheerioOptions };
|
package/dist/load.js
ADDED
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import { panic } from "better-result";
|
|
2
|
+
import { load as load$1 } from "cheerio/slim";
|
|
3
|
+
import { isDocument } from "domhandler";
|
|
4
|
+
import { parse, parseFragment, serializeOuter } from "parse5";
|
|
5
|
+
import { adapter } from "parse5-htmlparser2-tree-adapter";
|
|
6
|
+
// src/load.ts
|
|
7
|
+
const htmlTreeAdapter = adapter;
|
|
8
|
+
/** Parse strings without loading Cheerio's byte decoders or HTTP client. */
|
|
9
|
+
const load = (content, options, document = true) => {
|
|
10
|
+
if (usesXmlParser(options)) return load$1(content, options, document);
|
|
11
|
+
const parseOptions = {
|
|
12
|
+
...options,
|
|
13
|
+
treeAdapter: options?.treeAdapter ?? adapter,
|
|
14
|
+
scriptingEnabled: options?.scriptingEnabled !== false
|
|
15
|
+
};
|
|
16
|
+
const root = document ? parse(content, parseOptions) : parseFragment(content, parseOptions);
|
|
17
|
+
const $ = load$1(root, options, document);
|
|
18
|
+
const methods = Object.getOwnPropertyDescriptors($.fn);
|
|
19
|
+
const slimParse = methods._parse.value ?? panic("Cheerio must own its parse hook");
|
|
20
|
+
const slimRender = methods._render.value ?? panic("Cheerio must own its render hook");
|
|
21
|
+
const slimMake = methods._make.value ?? panic("Cheerio must own its selection hook");
|
|
22
|
+
$.fn._parse = function(markup, opts, isDoc, context) {
|
|
23
|
+
if (typeof markup !== "string" || opts._useHtmlParser2) return slimParse.call(this, markup, opts, isDoc, context);
|
|
24
|
+
const parserOptions = {
|
|
25
|
+
...opts,
|
|
26
|
+
treeAdapter: opts.treeAdapter ?? adapter,
|
|
27
|
+
scriptingEnabled: opts.scriptingEnabled !== false
|
|
28
|
+
};
|
|
29
|
+
return isDoc ? parse(markup, parserOptions) : parseFragment(context, markup, parserOptions);
|
|
30
|
+
};
|
|
31
|
+
$.fn._render = function(dom) {
|
|
32
|
+
if (this.options._useHtmlParser2) return slimRender.call(this, dom);
|
|
33
|
+
return ("length" in dom ? Array.from(dom) : [dom]).flatMap((node) => isDocument(node) ? node.children : [node]).map((node) => serializeOuter(node, { treeAdapter: adapter })).join("");
|
|
34
|
+
};
|
|
35
|
+
$.fn._make = new Proxy(slimMake, { apply(target, receiver, [selector, context]) {
|
|
36
|
+
return target.call(receiver, isMarkup(selector) ? parseFragment(selector, parseOptions).children : selector, isMarkup(context) ? parseFragment(context, parseOptions) : context);
|
|
37
|
+
} });
|
|
38
|
+
return new Proxy($, { apply(target, _thisArg, [selector, context, scope, opts]) {
|
|
39
|
+
if (usesXmlParser(opts)) return target(selector, context, scope, opts);
|
|
40
|
+
const parserOptions = {
|
|
41
|
+
...parseOptions,
|
|
42
|
+
...opts
|
|
43
|
+
};
|
|
44
|
+
return target(isMarkup(selector) ? parseFragment(selector, parserOptions).children : selector, isMarkup(context) ? parseFragment(context, parserOptions) : context, typeof scope === "string" ? parseFragment(scope, parserOptions) : scope, opts);
|
|
45
|
+
} });
|
|
46
|
+
};
|
|
47
|
+
const isMarkup = (value) => {
|
|
48
|
+
if (typeof value !== "string") return false;
|
|
49
|
+
const start = value.indexOf("<");
|
|
50
|
+
return start !== -1 && /[a-z!]/iu.test(value.charAt(start + 1)) && value.includes(">", start + 2);
|
|
51
|
+
};
|
|
52
|
+
const usesXmlParser = (options) => options?.xml === true || typeof options?.xml === "object" || options?.xmlMode === true;
|
|
53
|
+
export { htmlTreeAdapter, load };
|
package/dist/nodes.d.ts
ADDED
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { AnyNode, Element, Element as Element$1, ParentNode, Text, Text as Text$1, cloneNode as cloneNode$1, hasChildren as hasChildren$1, isCDATA as isCDATA$1, isComment as isComment$1, isTag as isTag$1, isText as isText$1 } from "domhandler";
|
|
2
|
+
export declare const createElement: (...args: ConstructorParameters<typeof Element$1>) => Element$1;
|
|
3
|
+
export declare const createText: (data: string) => Text$1;
|
|
4
|
+
export declare const cloneNode: typeof cloneNode$1;
|
|
5
|
+
export declare const hasChildren: typeof hasChildren$1;
|
|
6
|
+
export declare const isCDATA: typeof isCDATA$1;
|
|
7
|
+
export declare const isComment: typeof isComment$1;
|
|
8
|
+
export declare const isTag: typeof isTag$1;
|
|
9
|
+
export declare const isText: typeof isText$1;
|
|
10
|
+
export type { AnyNode, Element, ParentNode, Text };
|
package/dist/nodes.js
ADDED
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { Element, Text, cloneNode as cloneNode$1, hasChildren as hasChildren$1, isCDATA as isCDATA$1, isComment as isComment$1, isTag as isTag$1, isText as isText$1 } from "domhandler";
|
|
2
|
+
// src/nodes.ts
|
|
3
|
+
const createElement = (...args) => new Element(...args);
|
|
4
|
+
const createText = (data) => new Text(data);
|
|
5
|
+
const cloneNode = cloneNode$1;
|
|
6
|
+
const hasChildren = hasChildren$1;
|
|
7
|
+
const isCDATA = isCDATA$1;
|
|
8
|
+
const isComment = isComment$1;
|
|
9
|
+
const isTag = isTag$1;
|
|
10
|
+
const isText = isText$1;
|
|
11
|
+
export { cloneNode, createElement, createText, hasChildren, isCDATA, isComment, isTag, isText };
|
package/package.json
CHANGED
|
@@ -1 +1,74 @@
|
|
|
1
|
-
{
|
|
1
|
+
{
|
|
2
|
+
"name": "@stll/html",
|
|
3
|
+
"version": "0.1.1",
|
|
4
|
+
"description": "String-only HTML parsing with Cheerio traversal",
|
|
5
|
+
"keywords": [
|
|
6
|
+
"cheerio",
|
|
7
|
+
"html",
|
|
8
|
+
"parse5",
|
|
9
|
+
"parser",
|
|
10
|
+
"xml"
|
|
11
|
+
],
|
|
12
|
+
"homepage": "https://github.com/stella/stella/tree/main/packages/html",
|
|
13
|
+
"bugs": {
|
|
14
|
+
"url": "https://github.com/stella/stella/issues"
|
|
15
|
+
},
|
|
16
|
+
"license": "Apache-2.0",
|
|
17
|
+
"repository": {
|
|
18
|
+
"type": "git",
|
|
19
|
+
"url": "https://github.com/stella/stella.git",
|
|
20
|
+
"directory": "packages/html"
|
|
21
|
+
},
|
|
22
|
+
"files": [
|
|
23
|
+
"dist",
|
|
24
|
+
"README.md"
|
|
25
|
+
],
|
|
26
|
+
"type": "module",
|
|
27
|
+
"sideEffects": false,
|
|
28
|
+
"exports": {
|
|
29
|
+
".": {
|
|
30
|
+
"types": "./dist/load.d.ts",
|
|
31
|
+
"import": "./dist/load.js"
|
|
32
|
+
},
|
|
33
|
+
"./nodes": {
|
|
34
|
+
"types": "./dist/nodes.d.ts",
|
|
35
|
+
"import": "./dist/nodes.js"
|
|
36
|
+
},
|
|
37
|
+
"./bounded": {
|
|
38
|
+
"types": "./dist/bounded.d.ts",
|
|
39
|
+
"import": "./dist/bounded.js"
|
|
40
|
+
},
|
|
41
|
+
"./email": {
|
|
42
|
+
"types": "./dist/email.d.ts",
|
|
43
|
+
"import": "./dist/email.js"
|
|
44
|
+
}
|
|
45
|
+
},
|
|
46
|
+
"publishConfig": {
|
|
47
|
+
"access": "public"
|
|
48
|
+
},
|
|
49
|
+
"scripts": {
|
|
50
|
+
"clean": "git clean -xdf dist .cache .turbo node_modules",
|
|
51
|
+
"build": "tsdown",
|
|
52
|
+
"pack:dry-run": "bun pm pack --dry-run",
|
|
53
|
+
"test": "bun test src",
|
|
54
|
+
"typecheck": "bun check --no-pretty --all --project=tsconfig.json",
|
|
55
|
+
"lint": "cd ../.. && bun --bun oxlint -c oxlint.config.ts --report-unused-disable-directives-severity=error --type-aware --type-check packages/html",
|
|
56
|
+
"lint:fix": "cd ../.. && bun --bun oxlint -c oxlint.config.ts --type-aware --fix packages/html",
|
|
57
|
+
"prepack": "bun run build"
|
|
58
|
+
},
|
|
59
|
+
"devDependencies": {
|
|
60
|
+
"@stll/sha256": "0.0.0",
|
|
61
|
+
"@stll/typescript-config": "0.0.0",
|
|
62
|
+
"bun-types": "1.4.4",
|
|
63
|
+
"tsdown": "0.23.4"
|
|
64
|
+
},
|
|
65
|
+
"dependencies": {
|
|
66
|
+
"better-result": "3.0.1",
|
|
67
|
+
"cheerio": "^1.2.0",
|
|
68
|
+
"domhandler": "^5.0.3",
|
|
69
|
+
"parse5": "7.3.0",
|
|
70
|
+
"parse5-htmlparser2-tree-adapter": "7.1.0"
|
|
71
|
+
},
|
|
72
|
+
"main": "./dist/load.js",
|
|
73
|
+
"types": "./dist/load.d.ts"
|
|
74
|
+
}
|