feat(bundle): add flat (unlayered) bundle option for page builders - #59
Conversation
Adds a per-bundle 'flat: true' option to bundle.config.json that strips the SLASHED @layer scaffolding before emit. The first consumer is dist/slashed.bricks.css, a Bricks Builder-targeted bundle. Why: page builders that already manage the cascade via @layer (Bricks 2.0+ wraps its element defaults in @layer bricks) treat any unlayered author CSS as automatically winning over their layered rules. Shipping flat CSS to those environments is the simplest integration model and matches the deployment pattern used by Automatic.css and Core Framework. Implementation: - findMatchingBrace() walks {} respecting comments and strings. - stripLayerWrappers() removes top-level @layer ...; declarations and unwraps @layer slashed.X { ... } block wrappers, dedenting inner CSS. - Anchored to start-of-line so commented-out @layer text in file headers cannot accidentally match. - Existing layered bundles unchanged. New artifacts: - dist/slashed.bricks.css and .min.css (file list mirrors optimal). - package.json exports './bricks' entry.
📝 WalkthroughWalkthroughThis PR adds flat CSS bundling support to the SLASHED build system. The bundle script gains layer-stripping utilities to remove ChangesFlat CSS Bundle Support
Estimated code review effort🎯 2 (Simple) | ⏱️ ~12 minutes Possibly related PRs
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
Warning There were issues while running some tools. Please review the errors and either fix the tool's configuration or disable the tool if it's a critical failure. 🔧 ESLint
ESLint skipped: no ESLint configuration detected in root package.json. To enable, add Comment |
There was a problem hiding this comment.
Actionable comments posted: 1
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@scripts/bundle.js`:
- Around line 96-98: The current loop that strips `@layer` wrappers uses a fixed
"for (let guard = 0; guard < 32; guard++)" and may silently stop after 32
iterations leaving remaining headers; change the logic to loop until no header
match is found (e.g., while ((header = /^`@layer`[ \t]+[\w.\s,-]+\{/m.exec(out))
!== null) { ... }) or keep a safety ceiling but throw an error if the ceiling is
reached and a header still matches; update references to the guard variable, the
header regex (/^`@layer`[ \t]+[\w.\s,-]+\{/m), and the out string processing to
ensure all `@layer` wrappers are either removed or a clear exception is raised
instead of silently stopping.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro Plus
Run ID: 0312f0ce-5b9d-467c-91fe-1df413146f61
⛔ Files ignored due to path filters (3)
dist/slashed.bricks.cssis excluded by!**/dist/**dist/slashed.bricks.min.cssis excluded by!**/dist/**dist/slashed.bricks.min.css.mapis excluded by!**/dist/**,!**/*.map
📒 Files selected for processing (3)
bundle.config.jsonpackage.jsonscripts/bundle.js
| for (let guard = 0; guard < 32; guard++) { | ||
| const header = /^@layer[ \t]+[\w.\s,-]+\{/m.exec(out); | ||
| if (!header) break; |
There was a problem hiding this comment.
Avoid silent partial flattening after 32 @layer blocks.
The fixed guard < 32 cap can leave remaining wrappers unprocessed without error, producing a partially layered “flat” bundle. Consider iterating until no match remains (or throw when a safety ceiling is hit and matches still exist).
Proposed fix
- for (let guard = 0; guard < 32; guard++) {
- const header = /^`@layer`[ \t]+[\w.\s,-]+\{/m.exec(out);
- if (!header) break;
+ for (;;) {
+ const header = /^`@layer`[ \t]+[\w.\s,-]+\{/m.exec(out);
+ if (!header) break;
const start = header.index;
const openBrace = start + header[0].length - 1;
const closeBrace = findMatchingBrace(out, openBrace);📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| for (let guard = 0; guard < 32; guard++) { | |
| const header = /^@layer[ \t]+[\w.\s,-]+\{/m.exec(out); | |
| if (!header) break; | |
| for (;;) { | |
| const header = /^`@layer`[ \t]+[\w.\s,-]+\{/m.exec(out); | |
| if (!header) break; | |
| const start = header.index; | |
| const openBrace = start + header[0].length - 1; | |
| const closeBrace = findMatchingBrace(out, openBrace); |
🧰 Tools
🪛 OpenGrep (1.21.0)
[ERROR] 97-97: Dynamic command passed to child_process.exec/execSync. Use child_process.execFile or spawn with an argument array instead.
(coderabbit.command-injection.exec-js)
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@scripts/bundle.js` around lines 96 - 98, The current loop that strips `@layer`
wrappers uses a fixed "for (let guard = 0; guard < 32; guard++)" and may
silently stop after 32 iterations leaving remaining headers; change the logic to
loop until no header match is found (e.g., while ((header = /^`@layer`[
\t]+[\w.\s,-]+\{/m.exec(out)) !== null) { ... }) or keep a safety ceiling but
throw an error if the ceiling is reached and a header still matches; update
references to the guard variable, the header regex (/^`@layer`[
\t]+[\w.\s,-]+\{/m), and the out string processing to ensure all `@layer` wrappers
are either removed or a clear exception is raised instead of silently stopping.
This pull request was created by @kiro-agent on behalf of @jackgranatowski 👻
Comment with /kiro fix to address specific feedback or /kiro all to address everything.
Learn about Kiro autonomous agent
What
Adds a
flat: trueper-bundle option tobundle.config.jsonthat strips the SLASHED@layerscaffolding before emit. First consumer isdist/slashed.bricks.css— a Bricks Builder-targeted bundle that ships unlayered CSS.Why
Page builders that already manage the cascade via
@layer(Bricks 2.0+ wraps its element defaults in@layer bricks) treat any unlayered author CSS as automatically winning over their layered rules. Shipping flat CSS to those environments is the simplest integration model — no pre-declaration file, no concat-plugin footguns, no dependency chain gymnastics.This mirrors the deployment pattern used by Automatic.css (which moved to a "variable-first, BEM-first" unlayered model in v4.x) and Core Framework. Both ship unlayered CSS from a
/wp-content/uploads/...directory and rely on Bricks'@layer bricksdoing the cascade work for them.Implementation
findMatchingBrace(src, openPos)— walks{}respecting CSS comments and quoted strings; returns -1 on imbalance.stripLayerWrappers(content, fileLabel)— runs in two phases:@layer name1, name2, …;declarations (the layer-order block incore/layers.css).@layer slashed.X { … }block, keeping the inner CSS and dedenting two spaces./^@layer\b/m), so commented-out@layertext in file documentation headers cannot accidentally match.Verification
grep -c '@layer' dist/slashed.bricks.min.css→0(none after minification)grep -c '@layer' dist/slashed.optimal.min.css→1(the layer-order declaration, intact)lightningcss.transform()parses the flat bundle with zero warnings.@layer ...;declaration unchanged.Out of scope
bricks_is_builder_main()guard, etc.) — happy to add asdocs/bricks.mdin a follow-up.slashed.bricks.essential.css(smaller, no palette/forms/legacy) variant — easy to add if there's demand.Summary by CodeRabbit
./bricksfrom the package, providing an additional stylesheet distribution option.