diff --git a/.changeset/odd-poems-admire.md b/.changeset/odd-poems-admire.md new file mode 100644 index 000000000000..7536dc0cde3f --- /dev/null +++ b/.changeset/odd-poems-admire.md @@ -0,0 +1,5 @@ +--- +'svelte': patch +--- + +@import CSS at-rule now supports layer() and supports() syntax diff --git a/packages/svelte/src/compiler/compile/css/Stylesheet.js b/packages/svelte/src/compiler/compile/css/Stylesheet.js index d6bf41c13672..4a42f1a8f410 100644 --- a/packages/svelte/src/compiler/compile/css/Stylesheet.js +++ b/packages/svelte/src/compiler/compile/css/Stylesheet.js @@ -207,7 +207,8 @@ class Atrule { this.node.name === 'container' || this.node.name === 'media' || this.node.name === 'supports' || - this.node.name === 'layer' + this.node.name === 'layer' || + this.node.name === 'import' ) { this.children.forEach((child) => { child.apply(node); diff --git a/packages/svelte/src/compiler/parse/read/css-tree-cq/css_tree_parse.js b/packages/svelte/src/compiler/parse/read/css-tree-cq/css_tree_parse.js index bacb4cc33433..be2bcded6a7a 100644 --- a/packages/svelte/src/compiler/parse/read/css-tree-cq/css_tree_parse.js +++ b/packages/svelte/src/compiler/parse/read/css-tree-cq/css_tree_parse.js @@ -1,5 +1,6 @@ // @ts-nocheck import { fork } from 'css-tree'; +import { String, Url, Function, Ident, Semicolon } from 'css-tree/tokenizer'; import * as node from './node/index.js'; @@ -20,6 +21,75 @@ const cqSyntax = fork({ return this.Block(isStyleBlock); } } + }, + // TODO: Wait until css-tree supports layer() or supports() for import and remove this + import: { + parse: { + prelude() { + const children = this.createList(); + + this.skipSC(); + + switch (this.tokenType) { + case String: + children.push(this.String()); + break; + + case Url: + case Function: + children.push(this.Url()); + break; + } + + this.skipSC(); + + if ( + this.tokenType === Function && + this.cmpStr(this.tokenStart, this.tokenEnd, 'layer(') + ) { + children.push( + this.Function(() => { + const children = this.createList(); + this.skipSC(); + children.push(this.LayerName()); + this.skipSC(); + return children; + }, this.scope.AtrulePrelude) + ); + } else if ( + this.tokenType === Ident && + this.cmpStr(this.tokenStart, this.tokenEnd, 'layer') + ) { + children.push(this.Identifier()); + } + + this.skipSC(); + + if ( + this.tokenType === Function && + this.cmpStr(this.tokenStart, this.tokenEnd, 'supports(') + ) { + children.push( + this.Function(() => { + const children = this.createList(); + this.skipSC(); + children.push(this.SupportsCondition()); + this.skipSC(); + return children; + }, this.scope.AtrulePrelude) + ); + } + + this.skipSC(); + + if (this.tokenType !== Semicolon) { + children.push(this.MediaQueryList()); + } + + return children; + }, + block: null + } } }, node diff --git a/packages/svelte/src/compiler/parse/read/css-tree-cq/node/index.js b/packages/svelte/src/compiler/parse/read/css-tree-cq/node/index.js index b00ccba6a72e..5fb168b0d8c0 100644 --- a/packages/svelte/src/compiler/parse/read/css-tree-cq/node/index.js +++ b/packages/svelte/src/compiler/parse/read/css-tree-cq/node/index.js @@ -1,7 +1,9 @@ export * as Comparison from './comparison.js'; export * as ContainerFeatureStyle from './container_feature_style.js'; export * as ContainerQuery from './container_query.js'; +export * as LayerName from './layer_name.js'; export * as MediaQuery from './media_query.js'; export * as QueryFeature from './query_feature.js'; export * as QueryFeatureRange from './query_feature_range.js'; export * as QueryCSSFunction from './query_css_function.js'; +export * as SupportsCondition from './supports_condition.js'; diff --git a/packages/svelte/src/compiler/parse/read/css-tree-cq/node/layer_name.js b/packages/svelte/src/compiler/parse/read/css-tree-cq/node/layer_name.js new file mode 100644 index 000000000000..6aa74f30b8f6 --- /dev/null +++ b/packages/svelte/src/compiler/parse/read/css-tree-cq/node/layer_name.js @@ -0,0 +1,42 @@ +// @ts-nocheck +import { Delim, Ident } from 'css-tree/tokenizer'; + +const FULLSTOP = 0x002e; // U+002E FULL STOP (.) + +export const name = 'LayerName'; +export const structure = { + name: 'Identifier', + sublayer: ['LayerName', null] +}; + +// TODO: Wait for css-tree to support a layer name AST and remove this file +export function parse() { + const start = this.tokenStart; + const name = this.Identifier(); + + const char_idx = this.tokenStart; + const char_code = this.charCodeAt(char_idx); + + let sublayer; + if (char_code === FULLSTOP) { + this.next(); + sublayer = parse.call(this); + } else { + sublayer = null; + } + + return { + type: 'LayerName', + name, + sublayer, + loc: this.getLocation(start, this.tokenStart) + }; +} + +export function generate(node) { + this.token(Ident, node.name); + this.token(Delim, '.'); + if (node.sublayer !== null) { + generate.call(this, node.sublayer); + } +} diff --git a/packages/svelte/src/compiler/parse/read/css-tree-cq/node/supports_condition.js b/packages/svelte/src/compiler/parse/read/css-tree-cq/node/supports_condition.js new file mode 100644 index 000000000000..afbcc954597e --- /dev/null +++ b/packages/svelte/src/compiler/parse/read/css-tree-cq/node/supports_condition.js @@ -0,0 +1,21 @@ +// @ts-nocheck +export const name = 'SupportsCondition'; +export const structure = { + children: [[]] +}; + +// TODO: Wait until css-tree has better @supports condition AST and remove this file +export function parse() { + const start = this.tokenStart; + const support_condition = this.atrule.supports.prelude.call(this); + + return { + type: 'SupportsCondition', + children: support_condition, + loc: this.getLocation(start, this.tokenStart) + }; +} + +export function generate(node) { + this.children(node); +} diff --git a/packages/svelte/test/css/samples/supports-import-layer-supports-media/expected.css b/packages/svelte/test/css/samples/supports-import-layer-supports-media/expected.css new file mode 100644 index 000000000000..b654f6d809f2 --- /dev/null +++ b/packages/svelte/test/css/samples/supports-import-layer-supports-media/expected.css @@ -0,0 +1 @@ +@import "theme.css";@import url(themes.css);@import url("http://themes.css");@import "theme.css" layer;@import url(theme.css) layer;@import url("http://themes.css") layer;@import "theme.css" layer(abcd);@import url(theme.css) layer(abcd);@import url("http://themes.css") layer(abcd);@import "theme.css" layer(abcd.efgh.ijkl);@import url(theme.css) layer(abcd.efgh.ijkl);@import url("http://themes.css") layer(abcd.efgh.ijkl);@import "theme.css" screen and (orientation: portrait);@import url(theme.css) screen and (orientation: portrait);@import url("http://themes.css") screen and (orientation: portrait);@import "theme.css" layer (min-width: 500px);@import url(theme.css) layer (min-width: 500px);@import url("http://themes.css") layer (min-width: 500px);@import "theme.css" layer(abcd) screen and (orientation: portrait);@import url(theme.css) layer(abcd) screen and (orientation: portrait);@import url("http://themes.css") layer(abcd) screen and (orientation: portrait);@import "theme.css" layer(abcd.efgh.ijkl) (min-width: 500px);@import url(theme.css) layer(abcd.efgh.ijkl) (min-width: 500px);@import url("http://themes.css") layer(abcd.efgh.ijkl) (min-width: 500px);@import "theme.css" supports((display: flex));@import url(themes.css) supports((selector(h2 > p)) and (font-tech(color-COLRv1)));@import url("http://themes.css") supports(not (display: grid) and (display: flex));@import "theme.css" layer supports((selector(h2 > p)) and (font-tech(color-COLRv1)));@import url(theme.css) layer supports(not (display: grid) and (display: flex));@import url("http://themes.css") layer supports((display: flex));@import "theme.css" layer(abcd) supports(not (display: grid) and (display: flex));@import url(theme.css) layer(abcd) supports((display: flex));@import url("http://themes.css") layer(abcd) supports((selector(h2 > p)) and (font-tech(color-COLRv1)));@import "theme.css" layer(abcd.efgh.ijkl) supports((display: flex));@import url(theme.css) layer(abcd.efgh.ijkl) supports((selector(h2 > p)) and (font-tech(color-COLRv1)));@import url("http://themes.css") layer(abcd.efgh.ijkl) supports(not (display: grid) and (display: flex));@import "theme.css" supports((selector(h2 > p)) and (font-tech(color-COLRv1))) screen and (orientation: portrait);@import url(theme.css) supports(not (display: grid) and (display: flex)) screen and (orientation: portrait);@import url("http://themes.css") supports((display: flex)) screen and (orientation: portrait);@import "theme.css" layer supports(not (display: grid) and (display: flex) (min-width: 500px));@import url(theme.css) layer supports((display : flex)) (min-width: 500px);@import url("http://themes.css") supports((selector(h2 > p)) and (font-tech(color-COLRv1))) layer (min-width: 500px);@import "theme.css" layer(abcd) supports(not (display: grid) and (display: flex)) screen and (orientation: portrait);@import url(theme.css) layer(abcd) supports((display : flex)) screen and (orientation: portrait);@import url("http://themes.css") layer(abcd) supports((selector(h2 > p)) and (font-tech(color-COLRv1))) screen and (orientation: portrait);@import "theme.css" layer(abcd.efgh.ijkl) supports(not (display: grid) and (display: flex)) (min-width: 500px);@import url(theme.css) layer(abcd.efgh.ijkl) supports((display : flex)) (min-width: 500px);@import url("http://themes.css") layer(abcd.efgh.ijkl) supports((selector(h2 > p)) and (font-tech(color-COLRv1))) (min-width: 500px); \ No newline at end of file diff --git a/packages/svelte/test/css/samples/supports-import-layer-supports-media/input.svelte b/packages/svelte/test/css/samples/supports-import-layer-supports-media/input.svelte new file mode 100644 index 000000000000..b67b9a05d431 --- /dev/null +++ b/packages/svelte/test/css/samples/supports-import-layer-supports-media/input.svelte @@ -0,0 +1,52 @@ + \ No newline at end of file