lint is up to 40x slower than 0.8.106 on a long linked stylesheet: the canvas-zoom rule strips comments once per matched rule, and lint now reads stylesheets linked from sub-compositions
维护者通常 1 天内回复
已经有一个关联 PR 被合并了。
- #5712 来自 @rudycelekli —— 已合并
评估
- 难度
- 2/5
- 预计耗时
- 1-3 小时
- 新手友好度
- 75/100
- Issue 类型
- 缺陷
- 描述清晰度
- 描述清楚
- 活跃度
- 活跃
- 技术栈
- css, typescript
- 领域
- performance, tooling
调研方向
慢路径是 packages/lint/src/rules/composition.ts 中的 stylesheetCanvasZoomHits,其中的 while 循环在每次匹配时都调用 stripCssComments(style.content)(约第 364 行)。先阅读 collectHeavyOverlayHooks(第 203-206 行),它已经对注释只剥离一次,然后将同样的模式应用到循环中。完成标准是:复现用例的 lint 耗时大致随规则数量线性增长,且 lint 的输出保持不变。
由索引模型根据 Issue 内容生成。
描述
Describe the bug
lint takes 10 to 60 s a project in 0.8.158 where 0.8.106 took 0.5 to 1.6 s on the same projects and machine (one project measured alternately: 11.5 and 12.5 s on 0.8.158 against 2.2 and 1.7 s on 0.8.106). On a quiet machine, two projects measured alternately on 0.8.152 took 3.1 and 3.2 s against 0.8.106's 0.5 and 0.5 s, and 7.1 and 7.4 s against 0.6 and 0.5 s. check runs the lint too, so every check pays it, and a check split into parts pays it in every part.
A CPU profile of one 14.2 s run: 6.5 s in stripCssComments and 4.6 s in stylesheetCanvasZoomHits (chunk-7F3B5YYT.js of the package's dist, around line 6470). The canvas-zoom rule's loop is
while ((match = ruleWithBody.exec(stripCssComments(style.content))) !== null)
so it strips the whole stylesheet's comments again for every rule it matches, and the time grows with the square of the stylesheet's length. Our stylesheet is about 2,750 lines with some 800 rules, which is where it shows.
The loop itself is older: 0.8.106 is as slow on the same 3,000 rules when they sit in an inline <style> (4.4 to 5.2 s on 0.8.106, 4.2 to 4.8 s on 0.8.158). What changed is which stylesheets lint reads. A <link rel="stylesheet"> inside a sub-composition's <template> is now also resolved from the project root (packages/lint/src/project.ts, lines 108-113 at v0.8.162), where 0.8.106 looked only beside the sub-composition's file and skipped it. That matches how the runtime mounts the template, but it sends every such stylesheet through the quadratic loop, once for each composition that links it (line 246). Our sub-compositions each link the one shared stylesheet.
Measured on 0.8.158 (0.8.152 behaves the same). The code is unchanged in v0.8.162: packages/lint/src/rules/composition.ts, line 364 still calls stripCssComments(style.content) inside the while.
Minimal reproduction
A blank project with one sub-composition that links a long stylesheet (3,000 simple rules with a comment each):
npx hyperframes init repro --non-interactive --example blank
cd repro
mkdir compositions
# write index.html and compositions/scene.html as below, then:
node -e "require('fs').writeFileSync('long.css', Array.from({ length: 3000 }, (_, i) => '/* rule ' + i + ' */\n.r' + i + ' { color: #f4f4f5; }\n').join(''))"
index.html (the blank template plus the #scene clip, with the example comment removed from the script):
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=1920, height=1080" />
<script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/gsap.min.js"></script>
<style>
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
html,
body {
margin: 0;
width: 1920px;
height: 1080px;
overflow: hidden;
background: #0a0a0a;
}
#root {
width: 100%;
height: 100%;
display: flex;
align-items: center;
justify-content: center;
font-family: Inter, ui-sans-serif, system-ui, sans-serif;
}
#title {
color: #f4f4f5;
font-size: 72px;
font-weight: 600;
letter-spacing: -0.03em;
}
</style>
</head>
<body>
<div
id="root"
data-composition-id="main"
data-start="0"
data-duration="10"
data-width="1920"
data-height="1080"
>
<h1 id="title" class="clip" data-start="0" data-duration="10" data-track-index="0">Title</h1>
<div
id="scene"
data-composition-id="scene"
data-composition-src="compositions/scene.html"
data-start="0"
data-duration="10"
data-track-index="1"
data-width="1920"
data-height="1080"
></div>
</div>
<script>
const tl = gsap.timeline({ paused: true });
window.__timelines["main"] = tl;
tl.seek(0);
</script>
</body>
</html>
compositions/scene.html:
<!doctype html>
<html>
<head>
<meta charset="UTF-8" />
</head>
<body>
<template>
<link rel="stylesheet" href="long.css" />
<div id="root" data-composition-id="scene" data-width="1920" data-height="1080"></div>
<script>
window.__timelines["scene"] = gsap.timeline({ paused: true });
</script>
</template>
</body>
</html>
Steps to reproduce
- Set up the reproduction above.
- In
repro, timenpx [email protected] lintandnpx [email protected] lint, alternately, a few times each (both report0 errors, 0 warnings). - Change
3000in thenode -eline to1000or6000, regeneratelong.cssand time again: on 0.8.158 the time grows with the square of the rule count.
Expected behavior
lint about as fast as 0.8.106 on the same project (0.4 to 0.5 s here), its time growing linearly with the stylesheet's length.
Actual behavior
Wall-clock times of lint on the reproduction, versions run alternately (the installed CLI started with node, so no npx resolution is included):
rules in long.css |
0.8.106 | 0.8.152 | 0.8.158 |
|---|---|---|---|
| 1,000 | 0.47, 0.48 s | 0.95, 0.83 s | |
| 3,000 | 0.44, 0.42, 0.44 s | 5.3, 5.1, 4.8 s | 4.4, 5.4, 6.7 s |
| 6,000 | 0.45, 0.48 s | 21.4, 17.3 s |
A CPU profile of 0.8.158 on the 3,000-rule reproduction: of 3.6 s, 1.4 s in stripCssComments, 1.3 s in stylesheetCanvasZoomHits and 0.6 s in garbage collection.
Environment
OS: Windows 11 (build 26200)
Node.js: v24.14.0
HyperFrames: 0.8.158 and 0.8.152 against 0.8.106 (code unchanged at v0.8.162)
Chrome: Chrome Headless Shell 152.0.7977.30
Additional context
Request: strip the comments once, before the loop (and run the regular expression over that one string), as collectHeavyOverlayHooks in the same file already does (composition.ts, lines 203-206 at v0.8.162).
- 主要语言
- TypeScript
- 星标
- 54.1k
- 派生
- 4.9k
- 平均合并
- 15 小时 19 分钟
- 30 天内合并 PR
- 769
环境准备
这个项目没有提供开发容器、Dockerfile 或贡献指南,环境需要你自己搭建:先看它的 README,通用步骤见我们的新手贡献指南。
从这里开始
- 先读完整个 Issue,再读项目的贡献指南。
- 在 Issue 下留言说明你要接手 —— 这能避免两个人做同样的事。
- Fork 仓库,在一个分支上完成修改。
- 提交 Pull Request,并在描述里引用这个 Issue 编号。
heygen-com/hyperframes 的其他 Issue
-
难度 2/5 1-3 小时 新手友好度 78/100
heygen-com/hyperframes#6481 · 1 条评论 ·
维护者通常 1 天内回复
-
Timeline: show a clip's full name when its label is cut off可能已有人在做 @DanielMcSheehy 于 9 天前认领。 未关闭desktop-candidate difficulty/easy enhancement good first issue help wanted triage/ready
难度 2/5 1-3 小时 新手友好度 85/100
heygen-com/hyperframes#5582 · 2 条评论 ·
维护者通常 1 天内回复
-
Studio: press ? to open the keyboard shortcuts panel可能已有人在做 @B-Payal 于 13 天前认领。 未关闭desktop-candidate difficulty/easy enhancement good first issue help wanted triage/ready
难度 2/5 1-3 小时 新手友好度 82/100
heygen-com/hyperframes#5580 · 1 条评论 ·
维护者通常 1 天内回复
-
lint: add tests for three caption rules that have none可能已有人在做 关联的 PR 仍在进行中或已合并。 未关闭difficulty/easy enhancement help wanted triage/ready
难度 2/5 1-3 小时 新手友好度 85/100
heygen-com/hyperframes#5388 · 1 条评论 ·
维护者通常 1 天内回复
-
难度 4/5 3-5 天 新手友好度 52/100
heygen-com/hyperframes#6556 ·
维护者通常 1 天内回复
查看 heygen-com/hyperframes 的全部 Issue
相似的 Issue
-
难度 2/5 1-3 小时 新手友好度 75/100
维护者通常 1 天内回复
-
难度 1/5 1-3 小时 新手友好度 84/100
-
难度 2/5 1-3 小时 新手友好度 83/100
keptinventory/kept#336 ·
维护者通常 1 天内回复
-
good first issue
难度 2/5 1-3 小时 新手友好度 84/100
-
hemi-earn
难度 2/5 1-3 小时 新手友好度 72/100
vetro-protocol/vetro-monorepo#1013 ·
维护者通常 1 天内回复