-
-
Notifications
You must be signed in to change notification settings - Fork 4.6k
feat: container
query support via css-tree
extension
#8275
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
Changes from all commits
Commits
Show all changes
8 commits
Select commit
Hold shift + click to select a range
c2df6d5
Added container query support via css-tree extension. Closes #6969.
typhonrt e762949
Merge branch 'master' into master
typhonrt c180aa4
Merge branch 'sveltejs:master' into master
typhonrt 39fb408
Merge branch 'sveltejs:master' into master
typhonrt 94c22e6
Adds single value CSS function support to container query parsing.
typhonrt f493e56
Merge branch 'sveltejs:master' into master
typhonrt 15fa8ad
Merge branch 'sveltejs:master' into master
typhonrt 8f5fd3d
Code style / formatting.
typhonrt File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,43 @@ | ||
// @ts-nocheck | ||
// Note: Must import from the `css-tree` browser bundled distribution due to `createRequire` usage if importing from | ||
// `css-tree` Node module directly. This allows the production build of Svelte to work correctly. | ||
import { fork } from '../../../../../node_modules/css-tree/dist/csstree.esm.js'; | ||
|
||
import * as Comparison from './node/comparison'; | ||
import * as ContainerFeature from './node/container_feature'; | ||
import * as ContainerFeatureRange from './node/container_feature_range'; | ||
import * as ContainerFeatureStyle from './node/container_feature_style'; | ||
import * as ContainerQuery from './node/container_query'; | ||
import * as QueryCSSFunction from './node/query_css_function'; | ||
|
||
/** | ||
* Extends `css-tree` for container query support by forking and adding new nodes and at-rule support for `@container`. | ||
* | ||
* The new nodes are located in `./node`. | ||
*/ | ||
const cqSyntax = fork({ | ||
atrule: { // extend or override at-rule dictionary | ||
container: { | ||
parse: { | ||
prelude() { | ||
return this.createSingleNodeList( | ||
this.ContainerQuery() | ||
); | ||
}, | ||
block(isStyleBlock = false) { | ||
return this.Block(isStyleBlock); | ||
} | ||
} | ||
} | ||
}, | ||
node: { // extend node types | ||
Comparison, | ||
ContainerFeature, | ||
ContainerFeatureRange, | ||
ContainerFeatureStyle, | ||
ContainerQuery, | ||
QueryCSSFunction | ||
} | ||
}); | ||
|
||
export const parse = cqSyntax.parse; |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,48 @@ | ||
// @ts-nocheck | ||
import { Delim } from 'css-tree/tokenizer'; | ||
|
||
export const name = 'Comparison'; | ||
export const structure = { | ||
value: String | ||
}; | ||
|
||
export function parse() { | ||
const start = this.tokenStart; | ||
|
||
const char1 = this.consume(Delim); | ||
|
||
// The first character in the comparison operator must match '<', '=', or '>'. | ||
if (char1 !== '<' && char1 !== '>' && char1 !== '=') { | ||
this.error('Malformed comparison operator'); | ||
} | ||
|
||
let char2; | ||
|
||
if (this.tokenType === Delim) { | ||
char2 = this.consume(Delim); | ||
|
||
// The second character in the comparison operator must match '='. | ||
if (char2 !== '=') { | ||
this.error('Malformed comparison operator'); | ||
} | ||
} | ||
|
||
// If the next token is also 'Delim' then it is malformed. | ||
if (this.tokenType === Delim) { | ||
this.error('Malformed comparison operator'); | ||
} | ||
|
||
const value = char2 ? `${char1}${char2}` : char1; | ||
|
||
return { | ||
type: 'Comparison', | ||
loc: this.getLocation(start, this.tokenStart), | ||
value | ||
}; | ||
} | ||
|
||
export function generate(node) { | ||
for (let index = 0; index < node.value.length; index++) { | ||
this.token(Delim, node.value.charAt(index)); | ||
} | ||
} |
82 changes: 82 additions & 0 deletions
82
src/compiler/parse/read/css-tree-cq/node/container_feature.ts
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,82 @@ | ||
// @ts-nocheck | ||
import { | ||
Ident, | ||
Number, | ||
Dimension, | ||
Function, | ||
LeftParenthesis, | ||
RightParenthesis, | ||
Colon, | ||
Delim | ||
} from 'css-tree/tokenizer'; | ||
|
||
export const name = 'ContainerFeature'; | ||
export const structure = { | ||
name: String, | ||
value: ['Identifier', 'Number', 'Dimension', 'QueryCSSFunction', 'Ratio', null] | ||
}; | ||
|
||
export function parse() { | ||
const start = this.tokenStart; | ||
let value = null; | ||
|
||
this.eat(LeftParenthesis); | ||
this.skipSC(); | ||
|
||
const name = this.consume(Ident); | ||
this.skipSC(); | ||
|
||
if (this.tokenType !== RightParenthesis) { | ||
this.eat(Colon); | ||
this.skipSC(); | ||
|
||
switch (this.tokenType) { | ||
case Number: | ||
if (this.lookupNonWSType(1) === Delim) { | ||
value = this.Ratio(); | ||
} else { | ||
value = this.Number(); | ||
} | ||
break; | ||
|
||
case Dimension: | ||
value = this.Dimension(); | ||
break; | ||
|
||
case Function: | ||
value = this.QueryCSSFunction(); | ||
break; | ||
|
||
case Ident: | ||
value = this.Identifier(); | ||
break; | ||
|
||
default: | ||
this.error('Number, dimension, ratio, function, or identifier is expected'); | ||
break; | ||
} | ||
|
||
this.skipSC(); | ||
} | ||
|
||
this.eat(RightParenthesis); | ||
|
||
return { | ||
type: 'ContainerFeature', | ||
loc: this.getLocation(start, this.tokenStart), | ||
name, | ||
value | ||
}; | ||
} | ||
|
||
export function generate(node) { | ||
this.token(LeftParenthesis, '('); | ||
this.token(Ident, node.name); | ||
|
||
if (node.value !== null) { | ||
this.token(Colon, ':'); | ||
this.node(node.value); | ||
} | ||
|
||
this.token(RightParenthesis, ')'); | ||
} |
86 changes: 86 additions & 0 deletions
86
src/compiler/parse/read/css-tree-cq/node/container_feature_range.ts
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,86 @@ | ||
// @ts-nocheck | ||
import { | ||
Ident, | ||
Number, | ||
Delim, | ||
Dimension, | ||
Function, | ||
LeftParenthesis, | ||
RightParenthesis, | ||
WhiteSpace | ||
} from 'css-tree/tokenizer'; | ||
|
||
export const name = 'ContainerFeatureRange'; | ||
export const structure = { | ||
name: String, | ||
value: ['Identifier', 'Number', 'Comparison', 'Dimension', 'QueryCSSFunction', 'Ratio', null] | ||
}; | ||
|
||
function lookup_non_WS_type_and_value(offset, type, referenceStr) { | ||
let current_type; | ||
|
||
do { | ||
current_type = this.lookupType(offset++); | ||
if (current_type !== WhiteSpace) { | ||
break; | ||
} | ||
} while (current_type !== 0); // NULL -> 0 | ||
|
||
return current_type === type ? this.lookupValue(offset - 1, referenceStr) : false; | ||
} | ||
|
||
export function parse() { | ||
const children = this.createList(); | ||
let child = null; | ||
|
||
this.eat(LeftParenthesis); | ||
this.skipSC(); | ||
|
||
while (!this.eof && this.tokenType !== RightParenthesis) { | ||
switch (this.tokenType) { | ||
case Number: | ||
if (lookup_non_WS_type_and_value.call(this, 1, Delim, '/')) { | ||
child = this.Ratio(); | ||
} else { | ||
child = this.Number(); | ||
} | ||
break; | ||
|
||
case Delim: | ||
child = this.Comparison(); | ||
break; | ||
|
||
case Dimension: | ||
child = this.Dimension(); | ||
break; | ||
|
||
case Function: | ||
child = this.QueryCSSFunction(); | ||
break; | ||
|
||
case Ident: | ||
child = this.Identifier(); | ||
break; | ||
|
||
default: | ||
this.error('Number, dimension, comparison, ratio, function, or identifier is expected'); | ||
break; | ||
} | ||
|
||
children.push(child); | ||
|
||
this.skipSC(); | ||
} | ||
|
||
this.eat(RightParenthesis); | ||
|
||
return { | ||
type: 'ContainerFeatureRange', | ||
loc: this.getLocationFromList(children), | ||
children | ||
}; | ||
} | ||
|
||
export function generate(node) { | ||
this.children(node); | ||
} |
85 changes: 85 additions & 0 deletions
85
src/compiler/parse/read/css-tree-cq/node/container_feature_style.ts
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,85 @@ | ||
// @ts-nocheck | ||
import { | ||
Function, | ||
Ident, | ||
Number, | ||
Dimension, | ||
RightParenthesis, | ||
Colon, | ||
Delim | ||
} from 'css-tree/tokenizer'; | ||
|
||
export const name = 'ContainerFeatureStyle'; | ||
export const structure = { | ||
name: String, | ||
value: ['Function', 'Identifier', 'Number', 'Dimension', 'QueryCSSFunction', 'Ratio', null] | ||
}; | ||
|
||
export function parse() { | ||
const start = this.tokenStart; | ||
let value = null; | ||
|
||
const function_name = this.consumeFunctionName(); | ||
if (function_name !== 'style') { | ||
this.error('Unknown container style query identifier; "style" is expected'); | ||
} | ||
|
||
this.skipSC(); | ||
|
||
const name = this.consume(Ident); | ||
this.skipSC(); | ||
|
||
if (this.tokenType !== RightParenthesis) { | ||
this.eat(Colon); | ||
this.skipSC(); | ||
|
||
switch (this.tokenType) { | ||
case Number: | ||
if (this.lookupNonWSType(1) === Delim) { | ||
value = this.Ratio(); | ||
} else { | ||
value = this.Number(); | ||
} | ||
break; | ||
|
||
case Dimension: | ||
value = this.Dimension(); | ||
break; | ||
|
||
case Function: | ||
value = this.QueryCSSFunction(); | ||
break; | ||
|
||
case Ident: | ||
value = this.Identifier(); | ||
break; | ||
|
||
default: | ||
this.error('Number, dimension, ratio, function or identifier is expected'); | ||
break; | ||
} | ||
|
||
this.skipSC(); | ||
} | ||
|
||
this.eat(RightParenthesis); | ||
|
||
return { | ||
type: 'ContainerFeatureStyle', | ||
loc: this.getLocation(start, this.tokenStart), | ||
name, | ||
value | ||
}; | ||
} | ||
|
||
export function generate(node) { | ||
this.token(Function, 'style('); | ||
this.token(Ident, node.name); | ||
|
||
if (node.value !== null) { | ||
this.token(Colon, ':'); | ||
this.node(node.value); | ||
} | ||
|
||
this.token(RightParenthesis, ')'); | ||
} |
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Could you expand on that
createRequire
usage thing? I'm not sure what this is about.Uh oh!
There was an error while loading. Please reload this page.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Just repeating info I included in the post below:
So, the Node version of
css-tree
when includingfork
has a path that invokescreateRequire
indata.js
:https://github.com/csstree/csstree/blob/master/lib/data.js#L4-L7
The ESM bundled / browser version of
css-tree
has all of those resources in the ESM bundle thus doesn't depend on Node specific functionality to import a local JSON file.When building Svelte for production (not local testing) it causes an issue to use the Node version of
css-tree
when accessingfork
for extension as those JSON files can not be referenced by the production / zero-dependency build of Svelte as the bundling / distribution process doesn't include the internalcss-tree
JSON files referenced. When not producing a production buildcss-tree
and a couple of other Node dependencies are not bundled / marked external thus it works.https://github.com/sveltejs/svelte/blob/master/rollup.config.js#L144-L146
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
For Rollup this sounds ok. Just to double check: Once we go unbundled ESM then can't do this anymore like this.
css-tree
will be a regular dependency which means we have to use the public imports. But that would be ok because the JSON file will be available then withincss-tree
(because it's installed as a regular dependency), correct?Uh oh!
There was an error while loading. Please reload this page.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Presumably when going unbundled ESM the dependencies previously bundled that are marked external in the local test build will be moved to
package.json
dependencies
; likely:acorn
,css-tree
, andmagic-string
. In this case it will certainly be possible to directly reference the Node import path IEimport { fork } from 'css-tree';
Outside of course SvelteKit taking the unbundled ESM approach, some day I'd be glad to learn more about the impetus for native ESM vs TS. I too have always been a native ESM proponent and all my Svelte UI framework / library work is native ESM. I also work on tooling to be able to generate bundled TS declarations from ESM source + JSDoc. Not saying that is applicable in Svelte's future, but a curious conversation overall for another day.