Skip to content

Commit

Permalink
css: always inline bare :local and :global
Browse files Browse the repository at this point in the history
  • Loading branch information
evanw committed Jul 27, 2023
1 parent e5a7a35 commit 8143c28
Show file tree
Hide file tree
Showing 6 changed files with 58 additions and 17 deletions.
12 changes: 6 additions & 6 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -24,21 +24,21 @@
With the `local-css` loader enabled, that CSS will be turned into something like this (with the local name mapping exposed to JS):

```css
@keyframes stdin_stdin_pulse {
@keyframes stdin_pulse {
from, to {
opacity: 1;
}
50% {
opacity: 0.5;
}
}
@counter-style stdin_stdin_moon {
@counter-style stdin_moon {
system: cyclic;
symbols: 🌕 🌖 🌗 🌘 🌑 🌒 🌓 🌔;
}
ul {
animation: 2s ease-in-out infinite stdin_stdin_pulse;
list-style: inside stdin_stdin_moon;
animation: 2s ease-in-out infinite stdin_pulse;
list-style: inside stdin_moon;
}
```

Expand All @@ -47,15 +47,15 @@
```css
div {
/* All symbols are global inside this scope
* (i.e. "hide" and "moon" are global below) */
* (i.e. "pulse" and "moon" are global below) */
:global {
animation: 2s ease-in-out infinite pulse;
list-style: inside moon;
}
}
```

If you want to use `@keyframes` or `@counter-style` with a global name, make sure it's defined in a file that uses the `css` or `global-css` loader instead of the `local-css` loader. For example, you can configure `--loader:.module.css=local-css` so that the `local-css` loader only applies to `*.module.css` files.
If you want to use `@keyframes` or `@counter-style` with a global name, make sure it's in a file that uses the `css` or `global-css` loader instead of the `local-css` loader. For example, you can configure `--loader:.module.css=local-css` so that the `local-css` loader only applies to `*.module.css` files.

* Support strings as keyframe animation names in CSS ([#2555](https://github.com/evanw/esbuild/issues/2555))

Expand Down
15 changes: 14 additions & 1 deletion internal/bundler_tests/bundler_css_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -349,7 +349,20 @@ func TestImportCSSFromJSLocalVsGlobal(t *testing.T) {
:global.GLOBAL:local.local { color: #01B }
:global .GLOBAL :local .local { color: #01C }
:global { .GLOBAL { :local { .local { color: #01D } } } }
:global {
.GLOBAL {
before: outer;
:local {
before: inner;
.local {
color: #01D;
}
after: inner;
}
after: outer;
}
}
`

css_suite.expectBundled(t, bundled{
Expand Down
24 changes: 16 additions & 8 deletions internal/bundler_tests/snapshots/snapshots_css.txt
Original file line number Diff line number Diff line change
Expand Up @@ -1016,11 +1016,15 @@ div:local(+ .local_a):hover {
}
:global {
.GLOBAL {
before: outer;
:local {
before: inner;
.local {
color: #01D;
}
after: inner;
}
after: outer;
}
}

Expand Down Expand Up @@ -1116,11 +1120,13 @@ div + .LOCAL_local_a:hover {
}
& {
.GLOBAL {
& {
.LOCAL_local {
color: #01D;
}
before: outer;
before: inner;
.LOCAL_local {
color: #01D;
}
after: inner;
after: outer;
}
}

Expand Down Expand Up @@ -1216,11 +1222,13 @@ div + .LOCAL_local_a2:hover {
}
& {
.GLOBAL {
& {
.LOCAL_local2 {
color: #01D;
}
before: outer;
before: inner;
.LOCAL_local2 {
color: #01D;
}
after: inner;
after: outer;
}
}

Expand Down
3 changes: 3 additions & 0 deletions internal/css_ast/css_ast.go
Original file line number Diff line number Diff line change
Expand Up @@ -777,6 +777,9 @@ type CompoundSelector struct {
SubclassSelectors []SubclassSelector
NestingSelectorLoc ast.Index32 // "&"
Combinator Combinator // Optional, may be 0

// If this is true, this is a "&" that was generated by a bare ":local" or ":global"
WasEmptyFromLocalOrGlobal bool
}

func (sel *CompoundSelector) HasNestingSelector() bool {
Expand Down
18 changes: 17 additions & 1 deletion internal/css_parser/css_parser.go
Original file line number Diff line number Diff line change
Expand Up @@ -529,8 +529,24 @@ func (p *parser) parseListOfDeclarations(opts listOfDeclarationsOpts) (list []cs
css_lexer.TDelimGreaterThan,
css_lexer.TDelimTilde:
p.nestingIsPresent = true
list = append(list, p.parseSelectorRule(false, parseSelectorOpts{isDeclarationContext: true}))
foundNesting = true
rule := p.parseSelectorRule(false, parseSelectorOpts{isDeclarationContext: true})

// If this rule was a single ":global" or ":local", inline it here. This
// is handled differently than a bare "&" with normal CSS nesting because
// that would be inlined at the end of the parent rule's body instead,
// which is probably unexpected (e.g. it would trip people up when trying
// to write rules in a specific order).
if sel, ok := rule.Data.(*css_ast.RSelector); ok && len(sel.Selectors) == 1 {
if first := sel.Selectors[0]; len(first.Selectors) == 1 {
if first := first.Selectors[0]; first.WasEmptyFromLocalOrGlobal && first.IsSingleAmpersand() {
list = append(list, sel.Rules...)
continue
}
}
}

list = append(list, rule)

default:
list = append(list, p.parseDeclaration())
Expand Down
3 changes: 2 additions & 1 deletion internal/css_parser/css_parser_selector.go
Original file line number Diff line number Diff line change
Expand Up @@ -208,7 +208,8 @@ func (p *parser) flattenLocalAndGlobalSelectors(list []css_ast.ComplexSelector,
if len(selectors) == 0 {
// Treat a bare ":global" or ":local" as a bare "&" nesting selector
selectors = append(selectors, css_ast.CompoundSelector{
NestingSelectorLoc: ast.MakeIndex32(uint32(sel.Selectors[0].FirstLoc().Start)),
NestingSelectorLoc: ast.MakeIndex32(uint32(sel.Selectors[0].FirstLoc().Start)),
WasEmptyFromLocalOrGlobal: true,
})

// Make sure we report that nesting is present so that it can be lowered
Expand Down

0 comments on commit 8143c28

Please sign in to comment.