refactor: optimize reactive variable updates and parsing
- Replace `scanDeclareBlocks` with `parseDeclareCsv` in block processor - Add error handling for declare block parsing - Optimize `ReactiveVariableManager` by pre-computing variable keys - Store template text and keys in data attributes for faster updates - Adjust `VariableView` popup width via CSS class
This commit is contained in:
parent
c106b6f79c
commit
ffa023b92a
|
|
@ -7,7 +7,7 @@
|
||||||
|
|
||||||
import { posix } from "path";
|
import { posix } from "path";
|
||||||
import { createHash } from "crypto";
|
import { createHash } from "crypto";
|
||||||
import { scanDeclareBlocks, type VarDeclaration, type TagModifier } from "./declare-parser.js";
|
import { parseDeclareCsv, type VarDeclaration, type TagModifier } from "./declare-parser.js";
|
||||||
import {
|
import {
|
||||||
FENCED_BLOCK_RE,
|
FENCED_BLOCK_RE,
|
||||||
parseBlockAttrs,
|
parseBlockAttrs,
|
||||||
|
|
@ -86,9 +86,13 @@ export function processBlocks(
|
||||||
// ---- Dispatch by role ----
|
// ---- Dispatch by role ----
|
||||||
|
|
||||||
if (attrs.role === "declare") {
|
if (attrs.role === "declare") {
|
||||||
const result = scanDeclareBlocks(_match, fileRelativePath);
|
try {
|
||||||
blocks.declarations.push(...result.variables);
|
const result = parseDeclareCsv(body, fileRelativePath);
|
||||||
blocks.tagModifiers.push(...result.tagModifiers);
|
blocks.declarations.push(...result.variables);
|
||||||
|
blocks.tagModifiers.push(...result.tagModifiers);
|
||||||
|
} catch (e) {
|
||||||
|
console.warn(`[block-processor] ${fileRelativePath}: ${e}`);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (attrs.role === "file") {
|
if (attrs.role === "file") {
|
||||||
|
|
|
||||||
|
|
@ -15,8 +15,11 @@ import { useJournalStream } from "../stores/journalStream";
|
||||||
|
|
||||||
const TEMPLATE_RE = /\{\{(\$[a-zA-Z_][a-zA-Z0-9_]*)\}\}/g;
|
const TEMPLATE_RE = /\{\{(\$[a-zA-Z_][a-zA-Z0-9_]*)\}\}/g;
|
||||||
|
|
||||||
/** Tag name used for marker spans — must stay in sync with the scan pass. */
|
/** Attribute storing the original template text on marker spans. */
|
||||||
const MARKER = "data-reactive-var";
|
const ATTR_TEMPLATE = "data-reactive-var";
|
||||||
|
|
||||||
|
/** Attribute storing pre-computed key list (comma-separated) for fast updates. */
|
||||||
|
const ATTR_KEYS = "data-reactive-keys";
|
||||||
|
|
||||||
export const ReactiveVariableManager: Component = () => {
|
export const ReactiveVariableManager: Component = () => {
|
||||||
const contentDom = useArticleDom();
|
const contentDom = useArticleDom();
|
||||||
|
|
@ -32,7 +35,7 @@ export const ReactiveVariableManager: Component = () => {
|
||||||
|
|
||||||
// Only scan once — after the first scan, the spans exist and we
|
// Only scan once — after the first scan, the spans exist and we
|
||||||
// switch to the reactive update effect below.
|
// switch to the reactive update effect below.
|
||||||
if (dom.querySelector(`[${MARKER}]`)) return;
|
if (dom.querySelector(`[${ATTR_TEMPLATE}]`)) return;
|
||||||
|
|
||||||
scanAndWrap(dom);
|
scanAndWrap(dom);
|
||||||
});
|
});
|
||||||
|
|
@ -49,17 +52,14 @@ export const ReactiveVariableManager: Component = () => {
|
||||||
if (!dom) return;
|
if (!dom) return;
|
||||||
|
|
||||||
const v = values();
|
const v = values();
|
||||||
const spans = dom.querySelectorAll<HTMLElement>(`[${MARKER}]`);
|
const spans = dom.querySelectorAll<HTMLElement>(`[${ATTR_TEMPLATE}]`);
|
||||||
|
|
||||||
for (const span of spans) {
|
for (const span of spans) {
|
||||||
const template = span.getAttribute(MARKER) ?? "";
|
const template = span.getAttribute(ATTR_TEMPLATE) ?? "";
|
||||||
|
const keys = span.getAttribute(ATTR_KEYS)?.split(",") ?? [];
|
||||||
let result = template;
|
let result = template;
|
||||||
TEMPLATE_RE.lastIndex = 0;
|
for (const key of keys) {
|
||||||
let m: RegExpExecArray | null;
|
result = result.replaceAll(`{{${key}}}`, v[key] ?? "");
|
||||||
while ((m = TEMPLATE_RE.exec(template)) !== null) {
|
|
||||||
const key = m[1]; // includes $ prefix
|
|
||||||
const resolved = v[key] ?? "";
|
|
||||||
result = result.replace(`{{${key}}}`, resolved);
|
|
||||||
}
|
}
|
||||||
if (span.textContent !== result) {
|
if (span.textContent !== result) {
|
||||||
span.textContent = result;
|
span.textContent = result;
|
||||||
|
|
@ -90,7 +90,8 @@ function scanAndWrap(root: Element): void {
|
||||||
TEMPLATE_RE.lastIndex = 0;
|
TEMPLATE_RE.lastIndex = 0;
|
||||||
if (!TEMPLATE_RE.test(text)) continue;
|
if (!TEMPLATE_RE.test(text)) continue;
|
||||||
|
|
||||||
// Build replacement HTML: split on {{$key}} and wrap each key span
|
// Build replacement HTML: split on {{$key}}, wrap each match in a span
|
||||||
|
// with pre-computed key for fast updates.
|
||||||
TEMPLATE_RE.lastIndex = 0;
|
TEMPLATE_RE.lastIndex = 0;
|
||||||
const parts: string[] = [];
|
const parts: string[] = [];
|
||||||
let lastIndex = 0;
|
let lastIndex = 0;
|
||||||
|
|
@ -99,8 +100,9 @@ function scanAndWrap(root: Element): void {
|
||||||
if (m.index > lastIndex) {
|
if (m.index > lastIndex) {
|
||||||
parts.push(escapeHtml(text.slice(lastIndex, m.index)));
|
parts.push(escapeHtml(text.slice(lastIndex, m.index)));
|
||||||
}
|
}
|
||||||
|
const key = m[1]; // includes $ prefix
|
||||||
parts.push(
|
parts.push(
|
||||||
`<span ${MARKER}="${escapeAttr(m[0])}">${escapeHtml(m[0])}</span>`,
|
`<span ${ATTR_TEMPLATE}="${escapeAttr(m[0])}" ${ATTR_KEYS}="${escapeAttr(key)}">${escapeHtml(m[0])}</span>`,
|
||||||
);
|
);
|
||||||
lastIndex = TEMPLATE_RE.lastIndex;
|
lastIndex = TEMPLATE_RE.lastIndex;
|
||||||
}
|
}
|
||||||
|
|
@ -123,13 +125,13 @@ function scanAndWrap(root: Element): void {
|
||||||
* Called on cleanup so subsequent remounts get a clean slate.
|
* Called on cleanup so subsequent remounts get a clean slate.
|
||||||
*/
|
*/
|
||||||
function unwrapAll(root: Element): void {
|
function unwrapAll(root: Element): void {
|
||||||
const spans = root.querySelectorAll<HTMLElement>(`[${MARKER}]`);
|
const spans = root.querySelectorAll<HTMLElement>(`[${ATTR_TEMPLATE}]`);
|
||||||
// Process in reverse to avoid DOM mutation issues
|
// Process in reverse to avoid DOM mutation issues
|
||||||
for (let i = spans.length - 1; i >= 0; i--) {
|
for (let i = spans.length - 1; i >= 0; i--) {
|
||||||
const span = spans[i];
|
const span = spans[i];
|
||||||
const parent = span.parentNode;
|
const parent = span.parentNode;
|
||||||
if (!parent) continue;
|
if (!parent) continue;
|
||||||
const text = span.getAttribute(MARKER) ?? span.textContent ?? "";
|
const text = span.getAttribute(ATTR_TEMPLATE) ?? span.textContent ?? "";
|
||||||
span.replaceWith(document.createTextNode(text));
|
span.replaceWith(document.createTextNode(text));
|
||||||
}
|
}
|
||||||
// Normalize to merge adjacent text nodes
|
// Normalize to merge adjacent text nodes
|
||||||
|
|
|
||||||
|
|
@ -90,7 +90,7 @@ const VariableRow: Component<{
|
||||||
|
|
||||||
{/* Hover popup */}
|
{/* Hover popup */}
|
||||||
<Show when={hasPopup() && hovered()}>
|
<Show when={hasPopup() && hovered()}>
|
||||||
<div class="absolute right-0 top-full mt-1 z-50 bg-white border border-gray-200 rounded-md shadow-lg p-2 min-w-[180px] text-xs">
|
<div class="absolute right-0 top-full mt-1 z-50 bg-white border border-gray-200 rounded-md shadow-lg p-2 min-w-45 text-xs">
|
||||||
{/* Declaration expression */}
|
{/* Declaration expression */}
|
||||||
<Show when={declExpr()}>
|
<Show when={declExpr()}>
|
||||||
<div class="mb-1">
|
<div class="mb-1">
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue