diff --git a/CHANGELOG.md b/CHANGELOG.md index 4e24c11d42..af1a97d1f9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,15 @@ # Svelte changelog +## 3.32.3 + +* Fix removal of lone `:host` selectors ([#5982](https://github.com/sveltejs/svelte/issues/5982)) + +## 3.32.2 + +* Fix unnecessary additional invalidation with `` ([#3075](https://github.com/sveltejs/svelte/issues/3075), [#4447](https://github.com/sveltejs/svelte/issues/4447), [#5555](https://github.com/sveltejs/svelte/issues/5555)) +* Fix scoping of selectors with `:global()` and `~` sibling combinators ([#5499](https://github.com/sveltejs/svelte/issues/5499)) +* Fix removal of `:host` selectors as unused when compiling to a custom element ([#5946](https://github.com/sveltejs/svelte/issues/5946)) + ## 3.32.1 * Warn when using `module` variables reactively, and close weird reactivity loophole ([#5847](https://github.com/sveltejs/svelte/pull/5847)) diff --git a/package-lock.json b/package-lock.json index 482acd470a..b4d5b5caff 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "svelte", - "version": "3.32.1", + "version": "3.32.3", "lockfileVersion": 1, "requires": true, "dependencies": { @@ -1051,19 +1051,19 @@ } }, "css-tree": { - "version": "1.0.0-alpha22", - "resolved": "https://registry.npmjs.org/css-tree/-/css-tree-1.0.0-alpha22.tgz", - "integrity": "sha1-M4oAbjMce0+dq3tq9Tns5W/3ivI=", + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/css-tree/-/css-tree-1.1.2.tgz", + "integrity": "sha512-wCoWush5Aeo48GLhfHPbmvZs59Z+M7k5+B1xDnXbdWNcEF423DoFdqSWE0PM5aNk5nI5cp1q7ms36zGApY/sKQ==", "dev": true, "requires": { - "mdn-data": "^1.0.0", - "source-map": "^0.5.3" + "mdn-data": "2.0.14", + "source-map": "^0.6.1" }, "dependencies": { "source-map": { - "version": "0.5.7", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", - "integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=", + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", "dev": true } } @@ -2904,9 +2904,9 @@ } }, "mdn-data": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/mdn-data/-/mdn-data-1.2.0.tgz", - "integrity": "sha512-esDqNvsJB2q5V28+u7NdtdMg6Rmg4khQmAVSjUiX7BY/7haIv0K2yWM43hYp0or+3nvG7+UaTF1JHz31hgU1TA==", + "version": "2.0.14", + "resolved": "https://registry.npmjs.org/mdn-data/-/mdn-data-2.0.14.tgz", + "integrity": "sha512-dn6wd0uw5GsdswPFfsgMp5NSB0/aDe6fK94YJV/AJDYXL6HVLWBsxeq7js7Ad+mU2K9LAlwpk6kN2D5mwCPVow==", "dev": true }, "mem": { diff --git a/package.json b/package.json index 1c88873065..333838e69d 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "svelte", - "version": "3.32.1", + "version": "3.32.3", "description": "Cybernetically enhanced web apps", "module": "index.mjs", "main": "index", @@ -112,7 +112,7 @@ "c8": "^5.0.1", "code-red": "^0.1.4", "codecov": "^3.5.0", - "css-tree": "1.0.0-alpha22", + "css-tree": "^1.1.2", "eslint": "^7.15.0", "eslint-plugin-import": "^2.22.1", "eslint-plugin-svelte3": "^2.7.3", diff --git a/src/compiler/compile/css/Selector.ts b/src/compiler/compile/css/Selector.ts index e910f4333c..6d0e7c5003 100644 --- a/src/compiler/compile/css/Selector.ts +++ b/src/compiler/compile/css/Selector.ts @@ -44,7 +44,10 @@ export default class Selector { } this.local_blocks = this.blocks.slice(0, i); - this.used = this.local_blocks.length === 0; + + const host_only = this.blocks.length === 1 && this.blocks[0].host; + + this.used = this.local_blocks.length === 0 || host_only; } apply(node: Element) { @@ -84,7 +87,7 @@ export default class Selector { while (i--) { const selector = block.selectors[i]; if (selector.type === 'PseudoElementSelector' || selector.type === 'PseudoClassSelector') { - if (selector.name !== 'root') { + if (selector.name !== 'root' && selector.name !== 'host') { if (i === 0) code.prependRight(selector.start, attr); } continue; @@ -162,7 +165,10 @@ function apply_selector(blocks: Block[], node: Element, to_encapsulate: any[]): if (!block) return false; if (!node) { - return block.global && blocks.every(block => block.global); + return ( + (block.global && blocks.every(block => block.global)) || + (block.host && blocks.length === 0) + ); } switch (block_might_apply_to_node(block, node)) { @@ -182,6 +188,11 @@ function apply_selector(blocks: Block[], node: Element, to_encapsulate: any[]): continue; } + if (ancestor_block.host) { + to_encapsulate.push({ node, block }); + return true; + } + let parent = node; while (parent = get_element_parent(parent)) { if (block_might_apply_to_node(ancestor_block, parent) !== BlockAppliesToNode.NotPossible) { @@ -211,6 +222,19 @@ function apply_selector(blocks: Block[], node: Element, to_encapsulate: any[]): } else if (block.combinator.name === '+' || block.combinator.name === '~') { const siblings = get_possible_element_siblings(node, block.combinator.name === '+'); let has_match = false; + + // NOTE: if we have :global(), we couldn't figure out what is selected within `:global` due to the + // css-tree limitation that does not parse the inner selector of :global + // so unless we are sure there will be no sibling to match, we will consider it as matched + const has_global = blocks.some(block => block.global); + if (has_global) { + if (siblings.size === 0 && get_element_parent(node) !== null) { + return false; + } + to_encapsulate.push({ node, block }); + return true; + } + for (const possible_sibling of siblings.keys()) { if (apply_selector(blocks.slice(), possible_sibling, to_encapsulate)) { to_encapsulate.push({ node, block }); @@ -236,6 +260,10 @@ function block_might_apply_to_node(block: Block, node: Element): BlockAppliesToN const selector = block.selectors[i]; const name = typeof selector.name === 'string' && selector.name.replace(/\\(.)/g, '$1'); + if (selector.type === 'PseudoClassSelector' && name === 'host') { + return BlockAppliesToNode.NotPossible; + } + if (selector.type === 'PseudoClassSelector' || selector.type === 'PseudoElementSelector') { continue; } @@ -541,6 +569,7 @@ function loop_child(children: INode[], adjacent_only: boolean) { class Block { global: boolean; + host: boolean; combinator: CssNode; selectors: CssNode[] start: number; @@ -550,6 +579,7 @@ class Block { constructor(combinator: CssNode) { this.combinator = combinator; this.global = false; + this.host = false; this.selectors = []; this.start = null; @@ -562,6 +592,7 @@ class Block { if (this.selectors.length === 0) { this.start = selector.start; this.global = selector.type === 'PseudoClassSelector' && selector.name === 'global'; + this.host = selector.type === 'PseudoClassSelector' && selector.name === 'host'; } this.selectors.push(selector); diff --git a/src/compiler/compile/css/Stylesheet.ts b/src/compiler/compile/css/Stylesheet.ts index b0dab12b19..b730079a89 100644 --- a/src/compiler/compile/css/Stylesheet.ts +++ b/src/compiler/compile/css/Stylesheet.ts @@ -47,7 +47,7 @@ class Rule { constructor(node: CssNode, stylesheet, parent?: Atrule) { this.node = node; this.parent = parent; - this.selectors = node.selector.children.map((node: CssNode) => new Selector(node, stylesheet)); + this.selectors = node.prelude.children.map((node: CssNode) => new Selector(node, stylesheet)); this.declarations = node.block.children.map((node: CssNode) => new Declaration(node)); } @@ -182,11 +182,11 @@ class Atrule { minify(code: MagicString, dev: boolean) { if (this.node.name === 'media') { - const expression_char = code.original[this.node.expression.start]; + const expression_char = code.original[this.node.prelude.start]; let c = this.node.start + (expression_char === '(' ? 6 : 7); - if (this.node.expression.start > c) code.remove(c, this.node.expression.start); + if (this.node.prelude.start > c) code.remove(c, this.node.prelude.start); - this.node.expression.children.forEach((query: CssNode) => { + this.node.prelude.children.forEach((query: CssNode) => { // TODO minify queries c = query.end; }); @@ -194,17 +194,17 @@ class Atrule { code.remove(c, this.node.block.start); } else if (this.node.name === 'supports') { let c = this.node.start + 9; - if (this.node.expression.start - c > 1) code.overwrite(c, this.node.expression.start, ' '); - this.node.expression.children.forEach((query: CssNode) => { + if (this.node.prelude.start - c > 1) code.overwrite(c, this.node.prelude.start, ' '); + this.node.prelude.children.forEach((query: CssNode) => { // TODO minify queries c = query.end; }); code.remove(c, this.node.block.start); } else { let c = this.node.start + this.node.name.length + 1; - if (this.node.expression) { - if (this.node.expression.start - c > 1) code.overwrite(c, this.node.expression.start, ' '); - c = this.node.expression.end; + if (this.node.prelude) { + if (this.node.prelude.start - c > 1) code.overwrite(c, this.node.prelude.start, ' '); + c = this.node.prelude.end; } if (this.node.block && this.node.block.start - c > 0) { code.remove(c, this.node.block.start); @@ -235,7 +235,7 @@ class Atrule { transform(code: MagicString, id: string, keyframes: Map, max_amount_class_specificity_increased: number) { if (is_keyframes_node(this.node)) { - this.node.expression.children.forEach(({ type, name, start, end }: CssNode) => { + this.node.prelude.children.forEach(({ type, name, start, end }: CssNode) => { if (type === 'Identifier') { if (name.startsWith('-global-')) { code.remove(start, start + 8); @@ -317,7 +317,7 @@ export default class Stylesheet { } if (is_keyframes_node(node)) { - node.expression.children.forEach((expression: CssNode) => { + node.prelude.children.forEach((expression: CssNode) => { if (expression.type === 'Identifier' && !expression.name.startsWith('-global-')) { this.keyframes.set(expression.name, `${this.id}-${expression.name}`); } diff --git a/src/compiler/compile/render_dom/wrappers/InlineComponent/index.ts b/src/compiler/compile/render_dom/wrappers/InlineComponent/index.ts index be3d6d146d..835e0b52ce 100644 --- a/src/compiler/compile/render_dom/wrappers/InlineComponent/index.ts +++ b/src/compiler/compile/render_dom/wrappers/InlineComponent/index.ts @@ -347,8 +347,8 @@ export default class InlineComponentWrapper extends Wrapper { } const params = [x`#value`]; + const args = [x`#value`]; if (contextual_dependencies.length > 0) { - const args = []; contextual_dependencies.forEach(name => { params.push({ @@ -361,25 +361,30 @@ export default class InlineComponentWrapper extends Wrapper { }); - block.chunks.init.push(b` - function ${id}(#value) { - ${callee}.call(null, #value, ${args}); - } - `); - block.maintain_context = true; // TODO put this somewhere more logical - } else { - block.chunks.init.push(b` - function ${id}(#value) { - ${callee}.call(null, #value); + } + + block.chunks.init.push(b` + function ${id}(#value) { + ${callee}(${args}); + } + `); + + let invalidate_binding = b` + ${lhs} = #value; + ${renderer.invalidate(dependencies[0])}; + `; + if (binding.expression.node.type === 'MemberExpression') { + invalidate_binding = b` + if ($$self.$$.not_equal(${lhs}, #value)) { + ${invalidate_binding} } - `); + `; } const body = b` function ${id}(${params}) { - ${lhs} = #value; - ${renderer.invalidate(dependencies[0])}; + ${invalidate_binding} } `; diff --git a/src/compiler/parse/read/style.ts b/src/compiler/parse/read/style.ts index 72fd8aeca1..d8f85c0f3b 100644 --- a/src/compiler/parse/read/style.ts +++ b/src/compiler/parse/read/style.ts @@ -14,10 +14,13 @@ export default function read_style(parser: Parser, start: number, attributes: No try { ast = parse(styles, { positions: true, - offset: content_start + offset: content_start, + onParseError(error) { + throw error; + } }); } catch (err) { - if (err.name === 'CssSyntaxError') { + if (err.name === 'SyntaxError') { parser.error({ code: 'css-syntax-error', message: err.message diff --git a/test/css/samples/host/_config.js b/test/css/samples/host/_config.js new file mode 100644 index 0000000000..61539db5bc --- /dev/null +++ b/test/css/samples/host/_config.js @@ -0,0 +1,27 @@ +export default { + warnings: [ + { + code: 'css-unused-selector', + message: 'Unused CSS selector ":host > span"', + pos: 147, + start: { + character: 147, + column: 1, + line: 18 + }, + end: { + character: 159, + column: 13, + line: 18 + }, + frame: ` + 16: } + 17: + 18: :host > span { + ^ + 19: color: red; + 20: } + ` + } + ] +}; diff --git a/test/css/samples/host/expected.css b/test/css/samples/host/expected.css new file mode 100644 index 0000000000..d848082f5e --- /dev/null +++ b/test/css/samples/host/expected.css @@ -0,0 +1 @@ +:host h1.svelte-xyz{color:red}:host>h1.svelte-xyz{color:red}:host>.svelte-xyz{color:red}:host span.svelte-xyz{color:red}:host{color:red} \ No newline at end of file diff --git a/test/css/samples/host/input.svelte b/test/css/samples/host/input.svelte new file mode 100644 index 0000000000..85db1617ef --- /dev/null +++ b/test/css/samples/host/input.svelte @@ -0,0 +1,31 @@ + + +

Hello!

+ +
+ World! +
diff --git a/test/css/samples/siblings-combinator-global/_config.js b/test/css/samples/siblings-combinator-global/_config.js new file mode 100644 index 0000000000..342c09ce48 --- /dev/null +++ b/test/css/samples/siblings-combinator-global/_config.js @@ -0,0 +1,50 @@ +export default { + warnings: [ + { + code: 'css-unused-selector', + message: 'Unused CSS selector ":global(input) + span"', + pos: 239, + start: { + character: 239, + column: 2, + line: 9 + }, + end: { + character: 260, + column: 23, + line: 9 + }, + frame: ` + 7: :global(input) ~ p { color: red; } + 8: + 9: :global(input) + span { color: red; } + ^ + 10: :global(input) ~ span { color: red; } + 11: + ` + }, + { + code: 'css-unused-selector', + message: 'Unused CSS selector ":global(input) ~ span"', + pos: 279, + start: { + character: 279, + column: 2, + line: 10 + }, + end: { + character: 300, + column: 23, + line: 10 + }, + frame: ` + 8: + 9: :global(input) + span { color: red; } + 10: :global(input) ~ span { color: red; } + ^ + 11: + 12: + ` + } + ] +}; diff --git a/test/css/samples/siblings-combinator-global/expected.css b/test/css/samples/siblings-combinator-global/expected.css new file mode 100644 index 0000000000..83a4713156 --- /dev/null +++ b/test/css/samples/siblings-combinator-global/expected.css @@ -0,0 +1 @@ +input+div.svelte-xyz{color:red}input~div.svelte-xyz{color:red}input+h1.svelte-xyz{color:red}input~h1.svelte-xyz{color:red}input+p.svelte-xyz{color:red}input~p.svelte-xyz{color:red} \ No newline at end of file diff --git a/test/css/samples/siblings-combinator-global/input.svelte b/test/css/samples/siblings-combinator-global/input.svelte new file mode 100644 index 0000000000..ec06f3c015 --- /dev/null +++ b/test/css/samples/siblings-combinator-global/input.svelte @@ -0,0 +1,21 @@ + + +

Hello!

+ +
+ World! +
+ +{#each [] as _} +

+{/each} \ No newline at end of file diff --git a/test/parser/samples/css/output.json b/test/parser/samples/css/output.json index 676b11f6a9..a39f718cf1 100644 --- a/test/parser/samples/css/output.json +++ b/test/parser/samples/css/output.json @@ -37,7 +37,7 @@ "children": [ { "type": "Rule", - "selector": { + "prelude": { "type": "SelectorList", "children": [ { @@ -74,7 +74,7 @@ "end": 43 } ], - "start": 39, + "start": 40, "end": 43 }, "start": 33, diff --git a/test/runtime/samples/component-binding-reactive-property-no-extra-call/Component.svelte b/test/runtime/samples/component-binding-reactive-property-no-extra-call/Component.svelte new file mode 100644 index 0000000000..f180f10cd8 --- /dev/null +++ b/test/runtime/samples/component-binding-reactive-property-no-extra-call/Component.svelte @@ -0,0 +1,6 @@ + + +{value}{value2} diff --git a/test/runtime/samples/component-binding-reactive-property-no-extra-call/_config.js b/test/runtime/samples/component-binding-reactive-property-no-extra-call/_config.js new file mode 100644 index 0000000000..7027c1a4e4 --- /dev/null +++ b/test/runtime/samples/component-binding-reactive-property-no-extra-call/_config.js @@ -0,0 +1,5 @@ +export default { + async test({ assert, component }) { + assert.equal(component.object_updates, component.primitive_updates); + } +}; diff --git a/test/runtime/samples/component-binding-reactive-property-no-extra-call/main.svelte b/test/runtime/samples/component-binding-reactive-property-no-extra-call/main.svelte new file mode 100644 index 0000000000..9d52ed675f --- /dev/null +++ b/test/runtime/samples/component-binding-reactive-property-no-extra-call/main.svelte @@ -0,0 +1,13 @@ + + +