Repository navigation
Conversation
|
Status
|
563f2fc to
6813963
Compare
|
The base of this PR ( This branch still holds the old commits of #46040 and of #45604, so the diff of this PR shows them. To show only this PR again: git fetch origin robobun/1ca36eeb/css-import-partition
git rebase --onto origin/robobun/1ca36eeb/css-import-partition 563f2fcf45Two things changed in the base:
|
d3e0ea3 to
8224754
Compare
8224754 to
cdb5c5f
Compare
|
@alii three questions decide the shape of this draft. The second one is also open on #46040.
Reach, from the body: an After #47882 merges, I will rebase this branch and add the one |
…entry after its external @import rules A CSS chunk printed the @namespace rules of a file where that file prints. After the rules of another file, or inside the block of a conditional @import, a browser ignores the rule and drops each rule that uses its prefix. The bundler parse now keeps each @namespace rule in StyleSheet.namespaces, out of the rule list. The order builder adds one entry after the external @import rules of the chunk, and that entry prints the rules of the files once. One style sheet has one namespace scope. A rule is in place when bun's parser takes it where its file prints. A rule in place is in force as written. Another rule only adds a prefix that is free, so a default namespace is never put in force where it was not.
cdb5c5f to
2f7aa51
Compare
Problem
@namespaceis in 115 of 96,778 npm.cssfiles.@namespacerule after another file's rules, or inside a conditional@importblock. A browser ignores it there and drops each rule with its prefix. A secondbun buildfails:error: @namespace rules must come before any other rules except @charset, @import, and @layer.prepareCssAstsForChunk.rs:360).Fix
@namespacerules out ofrules, inStyleSheet.namespaces. One new order entry prints them once, after the external@importrules (add_namespaces).test/bundler/esbuild/css.test.ts,bundler_html.test.ts,test/bake/dev/css.test.ts. Alsotest/bundler/css/.Background
@namespaceis valid only between the@importrules and the first other rule. A chunk is one style sheet.@layerstatements ahead of@import.Downsides
@import ... all.StyleSheet+24 bytes,.text+5,888 bytes, +187 instructions for a 16-entry chunk with no@namespace.Notes
State. Draft. It waits for three answers from a maintainer (below) and for #47882, which changes the same two functions. Rebased on 2026-11-06 onto main b1305dd. The diff is the same, and the suites under Checks pass again on a debug build.
Reach and demand.
@namespace): 5,857 ship CSS, 96,778.cssfiles. 115 files in 24 packages have an@namespacerule. 38 declare only a default namespace, 37 declare a prefix that no selector uses, and 40 use a declared prefix. Examples:@readium/css(3,991 downloads a week),unreset-css(21,774),scss-reset.@namespacerule. I do not have the corpus of css: consolidate the open CSS parser fixes #47025 itself. css: consolidate the open CSS parser fixes #47025 counts "[xl|href$=".pdf"]lost its namespace prefix (css: keep the namespace prefix on attribute selectors that have an operator #45159). 2 real stylesheets".What breaks. Release builds of main 9365e0c and of this PR,
bun build --minify.entry.cssimportsa.css(.a{color:blue}), then declaressvgand hassvg|a{color:red}.a{color:#00f}@namespace svg "…";svg|a{color:red}@namespace svg "…";.a{color:#00f}svg|a{color:red}entry.cssis@import "./icons.css" screen; .e{color:green}@media screen{@namespace svg "…";svg|a{color:red}}.e{color:green}@namespace svg "…";@media screen{svg|a{color:red}}.e{color:green}entry.jsimportsbase.css, thenicons.css(@namespace xl "urn:example:xlink",a[href^="#"],a[xl|href^="#"]).icon{fill:red}@namespace xl "…";a[href^=\#],a[xl|href^="#"]{color:green}@namespace xl "…";.icon{fill:red}a[href^=\#],a[xl|href^="#"]{color:green}bun buildof main's output fails for rows 1 and 3, and logswarn: invalid @ rule encountered: '@namespace'for row 2.A real package.
@readium/css2.0.5 (ReadiumCSS-before.css,-default.css,-after.css, each with 4@namespacerules) and a two-ruleapp.css, in Chrome 153 with 171 elements (HTML, inline SVG, MathML).app.cssfirst: Chrome keeps 0 of the 12 rules in main's chunk and 3 in this PR's chunk (the prefixesepub,m,svg). Elements whose computed style differs from the four separate sheets: 42 on main, 30 with this PR. The 30 come from the default namespace, which is not in place and is left out.app.csslast: Chrome keeps 4 rules on main and with this PR, and both differ from the separate sheets on the same 33 elements. Main's second build fails, and this PR's passes.The rule.
@namespacerule inStyleSheet.namespaces.StyleSheet::parse(no bundle) keeps it inrules.Namespacesentry after the last external@import, or after the leading@layerstatement entries when the chunk has no external import.@importrules,@layerstatements ahead of each@import, unknown at-rule statements and other@namespacerules print before it.top_level_afterwalks the order with the states ofTopLevelRuleParserfor this.Cases that stay wrong: one chunk has one namespace scope. These are the same on main and with this PR. I did not open a tracking issue. This text can be one.
a.cssis@namespace p "urn:a"; p|x{…},b.cssis@namespace p "urn:b"; p|y{…}. The chunk keepsurn:a, sop|ymatches elements ofurn:a..e{…}of a later file then matches only elements of that namespace.The dev server serves one sheet for each root, so a page can differ between
bun ./index.htmlandbun build. Rows ofnamespaceCasespin each case, and the fieldreboundof a row lists the files whose rules change namespace.Changes against what a browser reads from main's output.
@import "x.css" all. With a minify flag main prints no@media allblock, so the rule is valid there. The import still has a condition, so this PR leaves the rule out (6 of the 62 valid test builds, rowDefaultOfMediaAllImport).@namespaceat@custom-media, at an unknown at-rule with a block, and at@-moz-document. A browser skips a rule that it does not know. So a default namespace after only such rules is in force in a browser on main, and this PR leaves it out (rowsDefaultAfterCustomMedia,DefaultAfterUnknownAtRuleBlock,DefaultAfterMozDocument). The parser is the one fixed reference, and engines differ on which rules they skip.Questions for a maintainer. #46040 asks the second one for
@import.rulesin the parser (this PR, css: keep bundled @import rules in StyleSheet.imports, out of the printable rule list #46040), or strip them in the linker (the@layerstatements of css: print @layer statements before @import and keep the layers of duplicate imports #47797, the@importof css: fix two layer ordering bugs left after #47797 #47882)?Other PRs.
@importthat a file prints again behind an unknown at-rule statement. Until it merges,@foo bar; @import "https://example.com/e.css"; @namespace svg url(…); svg|a{…}prints that@importafter the@namespacerule. A browser reads the same rules as on main, and a secondbun buildfails where it passes on main. After css: fix two layer ordering bugs left after #47797 #47882 this PR needs a rebase and one arm (CssImportOrderKind::Namespaces(_) => false) in its newmatch.@importstrip of prepare.with { type: "css" }as a module that exports a CSSStyleSheet in browser builds #41959 prints aparse_bundlersheet with no chunk.to_csshas a debug assertion for a sheet that still holdsnamespaces, and prepare takes the list out of its copy of a file.<link media>is dropped) is separate. A sheet that a page links withmedia="print"counts as a file with no condition here.What the references do. esbuild 0.21.5 (run) warns
"@namespace" rules are not supported [unsupported-@namespace]and prints the rule in place. esbuild 0.28.2 (source,internal/css_parser/css_parser.go:1649-1666) declines two rewrites: a default namespace into a prefix, and a rename of a prefix that two files use. lightningcss 1.0.0-alpha.72 (source,src/bundler.rs:713-815) leaves the rule in the rules of its file. css-namespaces-3 says that a misplaced rule "must be ignored".Checks. Release builds of main 9365e0c and of this PR.
namespaceCases,--minify-whitespace: a second build has no@namespaceerror or warning for 17 rows on main and for 44 with this PR.--minify,--minify-whitespaceand--minify-syntax(72 builds). 62 builds are valid on main: 16 are byte-identical, 40 change bytes only (Chrome 153 reads the same namespaces and the same number of rules), and 6 lose the default namespace behind@import ... all. The bytes change because the rules print above the/* file */comment, because a@layeror unknown at-rule statement of the file now follows them, or because one prefix had two rules.@namespace(one entry that imports the 15 sheets oftest/js/bun/css/files): 4 of 4 flag sets are byte-identical.add_namespaces,top_level_afterand the prepare step (for examplerpositiontoposition, or a@layerstatement that does not end the place): 16 fail at least one row, and the one that keeps the list in the copy of a file trips the debug assertion.test/bundler/esbuild/css.test.ts(150 pass),test/bundler/bundler_html.test.ts(30),test/bake/dev/css.test.ts(19),test/bundler/css/(217),test/js/bun/css/css.test.ts(2,018),test/regression/issue/20546.test.ts(2),test/regression/issue/28914.test.ts(5).cargo clippy -p bun_css -p bun_bundler -- -D warningsis clean.Measured. The same two release builds.
size -A:.text110,626,985 to 110,632,873 bytes (+5,888),.rodata26,772,460 both. The stripped binary grows by 4,096 bytes.test/bundler/compile-aot-size.test.ts): abun build --compile --aothello world maps 68,113,459 bytes on the base and 68,125,747 with this PR (+12,288). With--trim: 25,986,099 and 25,994,291 (+8,192). The base numbers are from the CI build of ci: leave the SSH server out of the Linux images #47981, which has the source of main b1305dd. The limit of that test for--trimis 25,880,000, so the test fails on both.sizeof(gdb, debug builds):StyleSheet520 to 544 bytes,BundlerAtRuleParser96 to 120,CssImportOrderKind56 both,CssImportOrder104 both.NamespaceRuleis 56 bytes.@namespace, from aqemu-x86_64trace, 3 of 3 runs equal: order builder 4,347 to 4,452, itsvisit78,101 both, prepare 1,927 to 1,977, print 4,119 to 4,151. That is 187 more, about 12 for each entry.mi_mallocis 112,860 in each run.css/CSSAtNamespaceOfTwoEntryPoints,html/SharedCSSWithNamespace).perf,valgrind,bloatyandstraceare not in the container.Self-review, not done.
Earlier designs of this PR. The first design rewrote selectors to keep a scope for each file. It broke
composesand used 572 MB in place of 62 MB on a 150-entry build. The second design kept the rules in their files and modelled what each entry prints, on the branch of #46040. #47797 and #47025 changed two of the three printer facts that it copied. This design has no model of the printer: it walks the order with the states of bun's parser.[human-review] gate passed · iteration 3 · 10 files touched
fails on main (without fix)
passes on PR (with fix)
diff hotspot
gate history · 2 passed · 0 rejected · iteration 3
evidence per changed file