mirror of
https://github.com/heygen-com/hyperframes.git
synced 2026-11-04 13:07:11 +00:00
perf(parsers): ensureHfIds skips the parse when every element already has its id (#7069)
* test(parsers): a stamped document needs no parse to keep its ids * perf(parsers): ensureHfIds skips the parse when every element already has its id * fix(parsers): a fragment checks every tag, a stray body tag included * fix(parsers): keep the parse for a page not opening head then body * refactor(parsers): split the head check out of the page shape check * fix(parsers): find the head end tag without stripping comments
This commit is contained in:
@@ -0,0 +1,66 @@
|
||||
import { beforeEach, describe, expect, it, vi } from "vitest";
|
||||
|
||||
const parses = vi.hoisted(() => ({ count: 0 }));
|
||||
vi.mock("linkedom", async (actual) => {
|
||||
const real = await actual<typeof import("linkedom")>();
|
||||
return {
|
||||
...real,
|
||||
parseHTML: (...args: Parameters<typeof real.parseHTML>) => {
|
||||
parses.count += 1;
|
||||
return real.parseHTML(...args);
|
||||
},
|
||||
};
|
||||
});
|
||||
|
||||
const { ensureHfIds } = await import("./hfIds.js");
|
||||
|
||||
const page = (body: string) =>
|
||||
`<!DOCTYPE html><html><head><meta charset="utf-8"><title>t</title></head><body>${body}</body></html>`;
|
||||
|
||||
describe("ensureHfIds on a document whose elements all carry their ids", () => {
|
||||
beforeEach(() => {
|
||||
parses.count = 0;
|
||||
});
|
||||
|
||||
it("returns it as it is, without a parse: a stamped bundle has nothing to mint", () => {
|
||||
const html = page(
|
||||
`<div data-hf-id="hf-a1b2"><svg data-hf-id="hf-c3d4"><path data-hf-id="hf-e5f6" d="M0 0"/></svg>` +
|
||||
`<script>var a = "<p>";</script></div>`,
|
||||
);
|
||||
expect(ensureHfIds(html)).toBe(html);
|
||||
expect(parses.count).toBe(0);
|
||||
});
|
||||
|
||||
it("parses once when one element lacks its id, or has an empty one", () => {
|
||||
const missing = page(`<div data-hf-id="hf-a1b2"><p>hi</p></div>`);
|
||||
expect(ensureHfIds(missing)).toMatch(/<p data-hf-id="hf-[0-9a-z]+">hi<\/p>/);
|
||||
expect(parses.count).toBe(1);
|
||||
expect(ensureHfIds(page(`<div data-hf-id="">x</div>`))).toMatch(/data-hf-id="hf-[0-9a-z]+"/);
|
||||
expect(parses.count).toBe(2);
|
||||
});
|
||||
|
||||
it("parses a stamped page not opening <head> then <body>: callers re-parsing the output expect both", () => {
|
||||
const headless = `<!DOCTYPE html><html><body><div data-hf-id="hf-a1b2">x</div></body></html>`;
|
||||
expect(ensureHfIds(headless)).toContain("<head></head><body>");
|
||||
const shapes = [
|
||||
`<html><head><title>t</title><body><p data-hf-id="hf-a1b2">x</p></body></html>`,
|
||||
`<html><head><div data-hf-id="hf-c3d4">x</div></head><body><p data-hf-id="hf-a1b2">x</p></body></html>`,
|
||||
`<html><head></head></html>`,
|
||||
`<html><head><title>t</title><!-- </head> --><body><p data-hf-id="hf-a1b2">x</p></body></html>`,
|
||||
];
|
||||
for (const html of shapes) ensureHfIds(html);
|
||||
expect(parses.count).toBe(1 + shapes.length);
|
||||
});
|
||||
|
||||
it("checks a fragment from its first tag, so elements before a stray <body> still get their ids", () => {
|
||||
const minted = ensureHfIds(`<p>y</p><body><div data-hf-id="hf-a1b2">x</div>`);
|
||||
expect(minted).toMatch(/<p data-hf-id="hf-[0-9a-z]+">y<\/p>/);
|
||||
expect(parses.count).toBe(1);
|
||||
});
|
||||
|
||||
it("reads an id only from the tag's own attributes, never from a quoted value", () => {
|
||||
const html = page(`<div title='data-hf-id="hf-zzzz"'>x</div>`);
|
||||
expect(ensureHfIds(html)).toMatch(/<div data-hf-id="hf-[0-9a-z]+" title=/);
|
||||
expect(parses.count).toBe(1);
|
||||
});
|
||||
});
|
||||
@@ -9,7 +9,7 @@
|
||||
* so inserting a non-identical sibling never shifts another element's id.
|
||||
*/
|
||||
import { parseHTML } from "linkedom";
|
||||
import { assignHfIds, getChildElements } from "./hfIdAssignment.js";
|
||||
import { EXCLUDED_TAGS, assignHfIds, getChildElements } from "./hfIdAssignment.js";
|
||||
export {
|
||||
EXCLUDED_TAGS,
|
||||
mintHfId,
|
||||
@@ -18,17 +18,63 @@ export {
|
||||
} from "./hfIdAssignment.js";
|
||||
|
||||
export function ensureHfIds(html: string): string {
|
||||
if (alreadyStamped(html)) return html;
|
||||
const { document, wrapped } = parseFragmentOrDocument(html);
|
||||
if (!document.body) return html;
|
||||
assignHfIds(document.body);
|
||||
return wrapped ? document.body.innerHTML || "" : document.toString();
|
||||
}
|
||||
|
||||
const isFragment = (html: string) =>
|
||||
!/^(?:<!doctype|<html[\s>/])/i.test(html.replace(/^(?:\s|<!--(?:>|->|[\s\S]*?-->))*/, ""));
|
||||
|
||||
function alreadyStamped(html: string): boolean {
|
||||
const tags = startTags(html);
|
||||
if (!tags) return false;
|
||||
const body = isFragment(html) ? -1 : tags.findIndex((tag) => tag.name === "body");
|
||||
if (!isFragment(html) && !opensHeadThenBody(html, tags, body)) return false;
|
||||
return tags
|
||||
.slice(body + 1)
|
||||
.every(
|
||||
(tag) =>
|
||||
!IMPLIED.has(tag.name) &&
|
||||
tag.name !== "table" &&
|
||||
(EXCLUDED_TAGS.has(tag.name) || hasHfId(html, tag)),
|
||||
);
|
||||
}
|
||||
|
||||
const HEAD_CONTENT = new Set(["meta", "link", "base", "title", "style", "script"]);
|
||||
|
||||
// The parse path's document.body read adds any missing <head>/<body>, and callers re-parsing its output expect both.
|
||||
function opensHeadThenBody(html: string, tags: StartTag[], body: number): boolean {
|
||||
const [first, head] = tags;
|
||||
if (body < 0 || first?.name !== "html" || head?.name !== "head") return false;
|
||||
const inHead = tags.slice(2, body);
|
||||
return (
|
||||
inHead.every((tag) => HEAD_CONTENT.has(tag.name)) &&
|
||||
closesHead(html, inHead.at(-1) ?? head, tags[body]!)
|
||||
);
|
||||
}
|
||||
|
||||
function closesHead(html: string, last: StartTag, body: StartTag): boolean {
|
||||
const from = RAW_TEXT.has(last.name) ? rawTextEnd(html, last.name, last.bracket) : last.bracket;
|
||||
const between = html.slice(from, html.lastIndexOf("<", body.nameEnd));
|
||||
const marks = between.matchAll(/<!--[\s\S]*?-->|<\/head\s*>/gi);
|
||||
return from >= 0 && [...marks].some((mark) => !mark[0].startsWith("<!--"));
|
||||
}
|
||||
|
||||
function hasHfId(html: string, tag: StartTag): boolean {
|
||||
const open = html.slice(tag.nameEnd, tag.bracket);
|
||||
const name = /\sdata-hf-id\s*=\s*/i.exec(outsideQuotes(open));
|
||||
if (!name) return false;
|
||||
const value = open.slice(name.index + name[0].length);
|
||||
const quote = value[0] === '"' || value[0] === "'" ? value[0] : "";
|
||||
return quote ? value.length > 1 && value[1] !== quote : /^[^\s>]/.test(value);
|
||||
}
|
||||
|
||||
function parseFragmentOrDocument(html: string) {
|
||||
// Wrap fragments so every body element participates in stable ID assignment.
|
||||
const wrapped = !/^(?:<!doctype|<html[\s>/])/i.test(
|
||||
html.replace(/^(?:\s|<!--(?:>|->|[\s\S]*?-->))*/, ""),
|
||||
);
|
||||
const wrapped = isFragment(html);
|
||||
const { document } = wrapped
|
||||
? parseHTML(`<!DOCTYPE html><html><head></head><body>${html}</body></html>`)
|
||||
: parseHTML(html);
|
||||
|
||||
Reference in New Issue
Block a user