Skip to content

Commit

Permalink
css: implement bare :global and :local
Browse files Browse the repository at this point in the history
  • Loading branch information
evanw committed Jul 18, 2023
1 parent 5c23bee commit fd1ddfa
Show file tree
Hide file tree
Showing 5 changed files with 262 additions and 44 deletions.
45 changes: 45 additions & 0 deletions internal/bundler_tests/bundler_css_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -344,6 +344,10 @@ func TestImportCSSFromJSLocalVsGlobal(t *testing.T) {
div:global(+ .GLOBAL_A):hover { color: #019 }
div:local(+ .local_a):hover { color: #01A }
:global.GLOBAL:local.local { color: #01B }
:global .GLOBAL :local .local { color: #01C }
:global { .GLOBAL { :local { .local { color: #01D } } } }
`

css_suite.expectBundled(t, bundled{
Expand Down Expand Up @@ -375,6 +379,47 @@ func TestImportCSSFromJSLocalVsGlobal(t *testing.T) {
})
}

func TestImportCSSFromJSLowerBareLocalAndGlobal(t *testing.T) {
css := `
.before { color: #000 }
:local { .button { color: #000 } }
.after { color: #000 }
.before { color: #001 }
:global { .button { color: #001 } }
.after { color: #001 }
div { :local { .button { color: #002 } } }
div { :global { .button { color: #003 } } }
:local(:global) { color: #004 }
:global(:local) { color: #005 }
:local(:global) { .button { color: #006 } }
:global(:local) { .button { color: #007 } }
`

css_suite.expectBundled(t, bundled{
files: map[string]string{
"/entry.js": `
import styles from "./styles.css"
console.log(styles)
`,
"/styles.css": css,
},
entryPaths: []string{"/entry.js"},
options: config.Options{
Mode: config.ModeBundle,
AbsOutputDir: "/out",
ExtensionToLoader: map[string]config.Loader{
".js": config.LoaderJS,
".css": config.LoaderLocalCSS,
},
UnsupportedCSSFeatures: compat.Nesting,
},
})
}

func TestImportCSSFromJSWriteToStdout(t *testing.T) {
css_suite.expectBundled(t, bundled{
files: map[string]string{
Expand Down
97 changes: 97 additions & 0 deletions internal/bundler_tests/snapshots/snapshots_css.txt
Original file line number Diff line number Diff line change
Expand Up @@ -735,6 +735,21 @@ div:global(+ .GLOBAL_A):hover {
div:local(+ .local_a):hover {
color: #01A;
}
:global.GLOBAL:local.local {
color: #01B;
}
:global .GLOBAL :local .local {
color: #01C;
}
:global {
.GLOBAL {
:local {
.local {
color: #01D;
}
}
}
}

/* LOCAL.global-css */
.top_level {
Expand Down Expand Up @@ -820,6 +835,21 @@ div + .GLOBAL_A:hover {
div + .LOCAL_local_a:hover {
color: #01A;
}
.GLOBAL.LOCAL_local {
color: #01B;
}
.GLOBAL .LOCAL_local {
color: #01C;
}
& {
.GLOBAL {
& {
.LOCAL_local {
color: #01D;
}
}
}
}

/* LOCAL.local-css */
.LOCAL_top_level {
Expand Down Expand Up @@ -905,6 +935,73 @@ div + .GLOBAL_A:hover {
div + .LOCAL_local_a2:hover {
color: #01A;
}
.GLOBAL.LOCAL_local2 {
color: #01B;
}
.GLOBAL .LOCAL_local2 {
color: #01C;
}
& {
.GLOBAL {
& {
.LOCAL_local2 {
color: #01D;
}
}
}
}

================================================================================
TestImportCSSFromJSLowerBareLocalAndGlobal
---------- /out/entry.js ----------
// styles.css
var styles_default = {
before: "styles_before",
button: "styles_button",
after: "styles_after"
};

// entry.js
console.log(styles_default);

---------- /out/entry.css ----------
/* styles.css */
.styles_before {
color: #000;
}
:scope .styles_button {
color: #000;
}
.styles_after {
color: #000;
}
.styles_before {
color: #001;
}
:scope .button {
color: #001;
}
.styles_after {
color: #001;
}
div .styles_button {
color: #002;
}
div .button {
color: #003;
}
:scope {
color: #004;
}
:scope {
color: #005;
}
:scope .styles_button {
color: #006;
}
:scope .styles_button {
color: #007;
}

================================================================================
TestImportGlobalCSSFromJS
Expand Down
17 changes: 17 additions & 0 deletions internal/css_ast/css_ast.go
Original file line number Diff line number Diff line change
Expand Up @@ -745,6 +745,23 @@ func (sel CompoundSelector) IsSingleAmpersand() bool {
return sel.HasNestingSelector() && sel.Combinator.Byte == 0 && sel.TypeSelector == nil && len(sel.SubclassSelectors) == 0
}

func (sel CompoundSelector) IsInvalidBecauseEmpty() bool {
return !sel.HasNestingSelector() && sel.TypeSelector == nil && len(sel.SubclassSelectors) == 0
}

func (sel CompoundSelector) FirstLoc() logger.Loc {
var firstLoc ast.Index32
if sel.TypeSelector != nil {
firstLoc = ast.MakeIndex32(uint32(sel.TypeSelector.FirstLoc().Start))
} else if len(sel.SubclassSelectors) > 0 {
firstLoc = ast.MakeIndex32(uint32(sel.SubclassSelectors[0].Loc.Start))
}
if firstLoc.IsValid() && (!sel.NestingSelectorLoc.IsValid() || firstLoc.GetIndex() < sel.NestingSelectorLoc.GetIndex()) {
return logger.Loc{Start: int32(firstLoc.GetIndex())}
}
return logger.Loc{Start: int32(sel.NestingSelectorLoc.GetIndex())}
}

func (sel CompoundSelector) Clone() CompoundSelector {
clone := sel

Expand Down
7 changes: 7 additions & 0 deletions internal/css_parser/css_parser.go
Original file line number Diff line number Diff line change
Expand Up @@ -1835,6 +1835,11 @@ func mangleNumber(t string) (string, bool) {
}

func (p *parser) parseSelectorRuleFrom(preludeStart int, isTopLevel bool, opts parseSelectorOpts) css_ast.Rule {
// Save and restore the local symbol state in case there are any bare
// ":global" or ":local" annotations. The effect of these should be scoped
// to within the selector rule.
local := p.makeLocalSymbols

// Try parsing the prelude as a selector list
if list, ok := p.parseSelectorList(opts); ok {
canInlineNoOpNesting := true
Expand Down Expand Up @@ -1863,9 +1868,11 @@ func (p *parser) parseSelectorRuleFrom(preludeStart int, isTopLevel bool, opts p
if p.expectWithMatchingLoc(css_lexer.TCloseBrace, matchingLoc) {
selector.CloseBraceLoc = closeBraceLoc
}
p.makeLocalSymbols = local
return css_ast.Rule{Loc: p.tokens[preludeStart].Range.Loc, Data: &selector}
}
}
p.makeLocalSymbols = local

// Otherwise, parse a generic qualified rule
return p.parseQualifiedRuleFrom(preludeStart, parseQualifiedRuleOpts{
Expand Down
Loading

0 comments on commit fd1ddfa

Please sign in to comment.