Skip to content

Commit

Permalink
css: change ast format for @import conditions
Browse files Browse the repository at this point in the history
  • Loading branch information
evanw committed Aug 7, 2023
1 parent d770d53 commit 6892b1b
Show file tree
Hide file tree
Showing 4 changed files with 100 additions and 32 deletions.
16 changes: 15 additions & 1 deletion internal/css_ast/css_ast.go
Original file line number Diff line number Diff line change
Expand Up @@ -432,8 +432,22 @@ func (r *RAtCharset) Hash() (uint32, bool) {
return hash, true
}

type ImportConditions struct {
Layers []Token
Supports []Token
Media []Token
}

func (conditions *ImportConditions) CloneWithImportRecords(importRecordsIn []ast.ImportRecord, importRecordsOut []ast.ImportRecord) (*ImportConditions, []ast.ImportRecord) {
result := ImportConditions{}
result.Layers, importRecordsOut = CloneTokensWithImportRecords(conditions.Layers, importRecordsIn, nil, importRecordsOut)
result.Supports, importRecordsOut = CloneTokensWithImportRecords(conditions.Supports, importRecordsIn, nil, importRecordsOut)
result.Media, importRecordsOut = CloneTokensWithImportRecords(conditions.Media, importRecordsIn, nil, importRecordsOut)
return &result, importRecordsOut
}

type RAtImport struct {
ImportConditions []Token
ImportConditions *ImportConditions
ImportRecordIndex uint32
}

Expand Down
36 changes: 30 additions & 6 deletions internal/css_parser/css_parser.go
Original file line number Diff line number Diff line change
Expand Up @@ -1141,6 +1141,7 @@ abortRuleParser:
kind = atRuleEmpty
p.eat(css_lexer.TWhitespace)
if path, r, ok := p.expectURLOrString(); ok {
var conditions css_ast.ImportConditions
importConditionsStart := p.index
for {
if kind := p.current().Kind; kind == css_lexer.TSemicolon || kind == css_lexer.TOpenBrace ||
Expand All @@ -1152,16 +1153,39 @@ abortRuleParser:
if p.current().Kind == css_lexer.TOpenBrace {
break // Avoid parsing an invalid "@import" rule
}
importConditions := p.convertTokens(p.tokens[importConditionsStart:p.index])
conditions.Media = p.convertTokens(p.tokens[importConditionsStart:p.index])
kind := ast.ImportAt

// Insert or remove whitespace before the first token
if len(importConditions) > 0 {
var importConditions *css_ast.ImportConditions
if len(conditions.Media) > 0 {
kind = ast.ImportAtConditional
if p.options.minifyWhitespace {
importConditions[0].Whitespace &= ^css_ast.WhitespaceBefore
} else {
importConditions[0].Whitespace |= css_ast.WhitespaceBefore
importConditions = &conditions

// Handle "layer()"
if t := conditions.Media[0]; (t.Kind == css_lexer.TIdent || t.Kind == css_lexer.TFunction) && t.Text == "layer" {
conditions.Layers = conditions.Media[:1]
conditions.Media = conditions.Media[1:]
}

// Handle "supports()"
if len(conditions.Media) > 0 {
if t := conditions.Media[0]; t.Kind == css_lexer.TFunction && t.Text == "supports" {
conditions.Supports = conditions.Media[:1]
conditions.Media = conditions.Media[1:]
}
}

// Remove leading and trailing whitespace
if len(conditions.Layers) > 0 {
conditions.Layers[0].Whitespace &= ^(css_ast.WhitespaceBefore | css_ast.WhitespaceAfter)
}
if len(conditions.Supports) > 0 {
conditions.Supports[0].Whitespace &= ^(css_ast.WhitespaceBefore | css_ast.WhitespaceAfter)
}
if n := len(conditions.Media); n > 0 {
conditions.Media[0].Whitespace &= ^css_ast.WhitespaceBefore
conditions.Media[n-1].Whitespace &= ^css_ast.WhitespaceAfter
}
}

Expand Down
24 changes: 23 additions & 1 deletion internal/css_printer/css_printer.go
Original file line number Diff line number Diff line change
Expand Up @@ -170,7 +170,29 @@ func (p *printer) printRule(rule css_ast.Rule, indent int32, omitTrailingSemicol
}
p.printQuoted(record.Path.Text, flags)
p.recordImportPathForMetafile(r.ImportRecordIndex)
p.printTokens(r.ImportConditions, printTokensOpts{})
if conditions := r.ImportConditions; conditions != nil {
space := !p.options.MinifyWhitespace
if len(conditions.Layers) > 0 {
if space {
p.print(" ")
}
p.printTokens(conditions.Layers, printTokensOpts{})
space = true
}
if len(conditions.Supports) > 0 {
if space {
p.print(" ")
}
p.printTokens(conditions.Supports, printTokensOpts{})
space = true
}
if len(conditions.Media) > 0 {
if space {
p.print(" ")
}
p.printTokens(conditions.Media, printTokensOpts{})
}
}
p.print(";")

case *css_ast.RAtKeyframes:
Expand Down
56 changes: 32 additions & 24 deletions internal/linker/linker.go
Original file line number Diff line number Diff line change
Expand Up @@ -194,7 +194,7 @@ type chunkReprCSS struct {

type externalImportCSS struct {
path logger.Path
conditions []css_ast.Token
conditions *css_ast.ImportConditions
conditionImportRecords []ast.ImportRecord
}

Expand Down Expand Up @@ -3337,8 +3337,7 @@ func (c *linkerContext) findImportedCSSFilesInJSOrder(entryPoint uint32) (order
// traversal order is B D C A.
func (c *linkerContext) findImportedFilesInCSSOrder(entryPoints []uint32) (externalOrder []externalImportCSS, internalOrder []uint32) {
type externalImportsCSS struct {
conditions [][]css_ast.Token
unconditional bool
conditions []css_ast.ImportConditions
}

visited := make(map[uint32]bool)
Expand Down Expand Up @@ -3376,32 +3375,40 @@ func (c *linkerContext) findImportedFilesInCSSOrder(entryPoints []uint32) (exter
// Record external dependencies
external := externals[record.Path]

// Check for an unconditional import. An unconditional import
// should always mask all conditional imports that are overridden
// by the unconditional import.
if external.unconditional {
continue
var before css_ast.ImportConditions
if conditions := atImport.ImportConditions; conditions != nil {
before = *conditions
}

if len(atImport.ImportConditions) == 0 {
external.unconditional = true
} else {
// Check for a conditional import. A conditional import does not
// mask an earlier unconditional import because re-evaluating a
// CSS file can have observable results.
for _, tokens := range external.conditions {
if css_ast.TokensEqualIgnoringWhitespace(tokens, atImport.ImportConditions) {
// Skip this rule if a later rule masks it
for _, after := range external.conditions {
if css_ast.TokensEqualIgnoringWhitespace(before.Layers, after.Layers) {
if len(after.Supports) == 0 && len(after.Media) == 0 {
// If the later one doesn't have any conditions, only keep
// the later one. The later one will mask the effects of the
// earlier one regardless of whether the earlier one has any
// conditions or not. Only do this if the layers are equal.
continue outer
}

// If the import conditions are exactly equal, then only keep
// the later one. The earlier one will have no effect.
if css_ast.TokensEqualIgnoringWhitespace(before.Supports, after.Supports) &&
css_ast.TokensEqualIgnoringWhitespace(before.Media, after.Media) {
continue outer
}
}
external.conditions = append(external.conditions, atImport.ImportConditions)
}

// Clone any import records associated with the condition tokens
conditions, conditionImportRecords := css_ast.CloneTokensWithImportRecords(
atImport.ImportConditions, repr.AST.ImportRecords, nil, nil)

external.conditions = append(external.conditions, before)
externals[record.Path] = external

var conditions *css_ast.ImportConditions
var conditionImportRecords []ast.ImportRecord
if atImport.ImportConditions != nil {
conditions, conditionImportRecords = atImport.ImportConditions.CloneWithImportRecords(repr.AST.ImportRecords, conditionImportRecords)
}

externalOrder = append(externalOrder, externalImportCSS{
path: record.Path,
conditions: conditions,
Expand Down Expand Up @@ -5712,9 +5719,10 @@ func (c *linkerContext) generateChunkCSS(chunkIndex int, chunkWaitGroup *sync.Wa
// Insert all external "@import" rules at the front. In CSS, all "@import"
// rules must come first or the browser will just ignore them.
for _, external := range chunkRepr.externalImportsInOrder {
var conditions []css_ast.Token
conditions, tree.ImportRecords = css_ast.CloneTokensWithImportRecords(
external.conditions, external.conditionImportRecords, conditions, tree.ImportRecords)
var conditions *css_ast.ImportConditions
if external.conditions != nil {
conditions, tree.ImportRecords = external.conditions.CloneWithImportRecords(external.conditionImportRecords, tree.ImportRecords)
}
tree.Rules = append(tree.Rules, css_ast.Rule{Data: &css_ast.RAtImport{
ImportRecordIndex: uint32(len(tree.ImportRecords)),
ImportConditions: conditions,
Expand Down

0 comments on commit 6892b1b

Please sign in to comment.