WIP: zero-runtime static extraction prototype#213
Conversation
|
Important Review skippedDraft detected. Please check the settings in the CodeRabbit UI or the ⚙️ Run configurationConfiguration used: Organization UI Review profile: CHILL Plan: Pro Run ID: You can disable this status message by setting the Use the checkbox below for a quick retry:
✨ Finishing Touches🧪 Generate unit tests (beta)
Tip Try Coding Plans. Let us write the prompt for your AI agent so you can ship faster (with fewer bugs). Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
Summary of ChangesHello, I'm Gemini Code Assist1! I'm currently reviewing this pull request and will post my feedback shortly. In the meantime, here's a summary to help you and other reviewers quickly get up to speed! This pull request introduces a foundational prototype for a zero-runtime static CSS extraction architecture within Highlights
🧠 New Feature in Public Preview: You can now enable Memory to help Gemini Code Assist learn from your team's feedback. This makes future code reviews more consistent and personalized to your project's style. Click here to enable Memory in your admin console. Changelog
Ignored Files
Activity
Using Gemini Code AssistThe full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips. Invoking Gemini You can request assistance from Gemini at any point by creating a comment using either
Customization To customize Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a Limitations & Feedback Gemini Code Assist may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counter productive. You can react with 👍 and 👎 on @gemini-code-assist comments. If you're interested in giving your feedback about your experience with Gemini Code Assist for GitHub and other Google products, sign up here. You can also get AI-powered code generation, chat, as well as code reviews directly in the IDE at no cost with the Gemini Code Assist IDE Extension. Footnotes
|
There was a problem hiding this comment.
Code Review
This pull request introduces a comprehensive prototype for zero-runtime static style extraction for antd-style, adding new infrastructure like Babel, Webpack, and Vite plugins for compile-time style collection. The core logic involves statically analyzing createStaticStyles calls, generating stable IDs, and extracting CSS and class name maps into a manifest, with the runtime updated to hydrate and use these pre-compiled styles. A critical prototype pollution vulnerability was identified in the Babel plugin's static evaluator, which could be exploited by malicious source code to affect the build environment. Furthermore, a high-risk dependency on an internal property of the Emotion cache was noted, which is brittle and could break with dependency updates. Specific comments provide suggestions for improving robustness and addressing the identified issues.
| : undefined; | ||
| if (!hash) return undefined; | ||
|
|
||
| const cssText = collector.cache.inserted?.[hash]; |
There was a problem hiding this comment.
Accessing collector.cache.inserted is risky as it relies on an internal, undocumented property of Emotion's cache. This can break with minor updates to Emotion. While this is a prototype, it would be more robust to find a way to retrieve the CSS text using public APIs if possible. This same pattern is also used in collectKeyframesTaggedTemplate.
| ? cls.slice(emotionCache.key.length + 1) | ||
| : undefined; | ||
| if (!hash) return; | ||
| const inserted = (emotionCache.inserted as any)?.[hash]; |
There was a problem hiding this comment.
Accessing emotionCache.inserted relies on an internal implementation detail of Emotion's cache, which is not guaranteed to be stable across versions. This could lead to breakages when Emotion is updated. For a more robust implementation, consider if there's a way to achieve this using Emotion's public APIs.
| for (const property of node.properties) { | ||
| if (!t.isObjectProperty(property)) return undefined; | ||
|
|
||
| const key = | ||
| t.isIdentifier(property.key) || t.isStringLiteral(property.key) | ||
| ? property.key.name || property.key.value | ||
| : undefined; | ||
| if (!key) return undefined; | ||
|
|
||
| const value = resolveStaticExpression(t, property.value, context, callPath, visiting); | ||
| if (value === undefined) return undefined; | ||
|
|
||
| result[key] = value; |
There was a problem hiding this comment.
The resolveStaticExpression function is vulnerable to prototype pollution. When evaluating object expressions from the source code, it uses property keys directly to set values on a fresh object {}. If a malicious source file contains an object with a __proto__ key, it will pollute the Object.prototype of the Babel process. This could potentially lead to Remote Code Execution (RCE) if other parts of the build pipeline are vulnerable to prototype pollution. To fix this, validate that the property key is not __proto__, constructor, or prototype before setting it on the object, or use Object.create(null) for the result object.
| for (const property of node.properties) { | |
| if (!t.isObjectProperty(property)) return undefined; | |
| const key = | |
| t.isIdentifier(property.key) || t.isStringLiteral(property.key) | |
| ? property.key.name || property.key.value | |
| : undefined; | |
| if (!key) return undefined; | |
| const value = resolveStaticExpression(t, property.value, context, callPath, visiting); | |
| if (value === undefined) return undefined; | |
| result[key] = value; | |
| for (const property of node.properties) { | |
| if (!t.isObjectProperty(property)) return undefined; | |
| const key = | |
| t.isIdentifier(property.key) || t.isStringLiteral(property.key) | |
| ? property.key.name || property.key.value | |
| : undefined; | |
| if (!key || key === '__proto__' || key === 'constructor' || key === 'prototype') return undefined; | |
| const value = resolveStaticExpression(t, property.value, context, callPath, visiting); | |
| if (value === undefined) return undefined; | |
| result[key] = value; |
| for (const property of returned.properties) { | ||
| if (!t.isObjectProperty(property)) return undefined; | ||
|
|
||
| const key = | ||
| t.isIdentifier(property.key) || t.isStringLiteral(property.key) | ||
| ? property.key.name || property.key.value | ||
| : undefined; | ||
| if (!key) return undefined; | ||
|
|
||
| const collected = collectStyleValue(t, property.value, context, callPath, collector); | ||
| if (!collected || !collected.className) return undefined; | ||
|
|
||
| styles[key] = collected.className; | ||
| pushCssTexts(collected.cssTexts); | ||
| } |
There was a problem hiding this comment.
The collectStaticChunk function is vulnerable to prototype pollution. When collecting styles from the source code, it uses property keys directly to set values on a fresh object {}. If a malicious source file contains an object with a __proto__ key, it will pollute the Object.prototype of the Babel process. To fix this, validate that the property key is not __proto__, constructor, or prototype before setting it on the object.
| for (const property of returned.properties) { | |
| if (!t.isObjectProperty(property)) return undefined; | |
| const key = | |
| t.isIdentifier(property.key) || t.isStringLiteral(property.key) | |
| ? property.key.name || property.key.value | |
| : undefined; | |
| if (!key) return undefined; | |
| const collected = collectStyleValue(t, property.value, context, callPath, collector); | |
| if (!collected || !collected.className) return undefined; | |
| styles[key] = collected.className; | |
| pushCssTexts(collected.cssTexts); | |
| } | |
| for (const property of returned.properties) { | |
| if (!t.isObjectProperty(property)) return undefined; | |
| const key = | |
| t.isIdentifier(property.key) || t.isStringLiteral(property.key) | |
| ? property.key.name || property.key.value | |
| : undefined; | |
| if (!key || key === '__proto__' || key === 'constructor' || key === 'prototype') return undefined; | |
| const collected = collectStyleValue(t, property.value, context, callPath, collector); | |
| if (!collected || !collected.className) return undefined; | |
| styles[key] = collected.className; | |
| pushCssTexts(collected.cssTexts); | |
| } |
Summary
createStaticStylesstyleIdinjection, compiled chunk collection, manifest hydration, and webpack/vite emit pluginscssVar,responsive,cx,keyframes, local constants, and optional imported-value resolutionValidation
npm run verify:extract-prototypenpm run type-checkExtended ON/OFF comparison (zero-runtime extraction)
Below is the latest comparison from the integrated LobeHub SPA validation path using:
ANTD_STYLE_EXTRACT=1ANTD_STYLE_EXTRACT=01) Functional correctness (ON)
Strict extraction checks pass end-to-end:
%BASE_URL%placeholder leakage in built HTMLhydrateExtractedStyles)Result: pass=18 / warn=0 / fail=0
2) Bundle output (ON vs OFF)
Additional ON-only artifacts:
__antd-style.extract.css: 234,108 B (gzip 28,560 B)__antd-style.extract.manifest.json: 91,453 B (gzip 15,677 B)3) Runtime user-story perf (ON vs OFF)
Logged-in route-transition comparison (same user-story flow, multi-round):
Notes