diff --git a/CHANGELOG.md b/CHANGELOG.md index c5fccddcb5..e41ce6f7c4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,12 @@ # Svelte changelog +## 3.16.6 + +* Fix CSS specificity bug when encapsulating styles ([#1277](https://github.com/sveltejs/svelte/issues/1277)) +* Apply directives in the order they're given ([#2446](https://github.com/sveltejs/svelte/issues/2446)) +* Fix destructuring in `let:` directives ([#2751](https://github.com/sveltejs/svelte/issues/2751)) +* Preserve whitespace around ``s in ``s ([#3998](https://github.com/sveltejs/svelte/issues/3998)) + ## 3.16.5 * Better fix for cascading invalidations and fix some regressions ([#4098](https://github.com/sveltejs/svelte/issues/4098), [#4114](https://github.com/sveltejs/svelte/issues/4114), [#4120](https://github.com/sveltejs/svelte/issues/4120)) diff --git a/package-lock.json b/package-lock.json index f3eb858358..5b7af2a23f 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "svelte", - "version": "3.16.5", + "version": "3.16.6", "lockfileVersion": 1, "requires": true, "dependencies": { diff --git a/package.json b/package.json index 36eaf2e323..e64b5248f7 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "svelte", - "version": "3.16.5", + "version": "3.16.6", "description": "Cybernetically enhanced web apps", "module": "index.mjs", "main": "index", diff --git a/site/content/docs/03-run-time.md b/site/content/docs/03-run-time.md index 14093b5264..5bcbe8764c 100644 --- a/site/content/docs/03-run-time.md +++ b/site/content/docs/03-run-time.md @@ -489,7 +489,7 @@ A `spring` store gradually changes to its target value based on its `stiffness` --- -As with [`tweened`](#tweened) stores, `set` and `update` return a Promise that resolves if the spring settles. The `store.stiffness` and `store.damping` properties can be changed while the spring is in motion, and will take immediate effect. +As with [`tweened`](docs#tweened) stores, `set` and `update` return a Promise that resolves if the spring settles. The `store.stiffness` and `store.damping` properties can be changed while the spring is in motion, and will take immediate effect. Both `set` and `update` can take a second argument — an object with `hard` or `soft` properties. `{ hard: true }` sets the target value immediately; `{ soft: n }` preserves existing momentum for `n` seconds before settling. `{ soft: true }` is equivalent to `{ soft: 0.5 }`. diff --git a/site/content/tutorial/12-actions/01-actions/text.md b/site/content/tutorial/12-actions/01-actions/text.md index 38de8f9a65..3706b31e7c 100644 --- a/site/content/tutorial/12-actions/01-actions/text.md +++ b/site/content/tutorial/12-actions/01-actions/text.md @@ -23,7 +23,9 @@ import { pannable } from './pannable.js'; on:panstart={handlePanStart} on:panmove={handlePanMove} on:panend={handlePanEnd} - style="transform: translate({$coords.x}px,{$coords.y}px)" + style="transform: + translate({$coords.x}px,{$coords.y}px) + rotate({$coords.x * 0.2}deg)" > ``` diff --git a/site/src/routes/_components/WhosUsingSvelte.svelte b/site/src/routes/_components/WhosUsingSvelte.svelte index f27f1684e6..3288e79938 100644 --- a/site/src/routes/_components/WhosUsingSvelte.svelte +++ b/site/src/routes/_components/WhosUsingSvelte.svelte @@ -63,6 +63,7 @@ GoDaddy logo Grainger logo HealthTree logo + IOTA logo itslearning logo Jacoux logo Jingmnt logo diff --git a/site/static/organisations/iota.svg b/site/static/organisations/iota.svg new file mode 100644 index 0000000000..1ab864a958 --- /dev/null +++ b/site/static/organisations/iota.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/compiler/compile/Component.ts b/src/compiler/compile/Component.ts index 2c7fb64b67..1a5d39ab28 100644 --- a/src/compiler/compile/Component.ts +++ b/src/compiler/compile/Component.ts @@ -14,7 +14,7 @@ import Stylesheet from './css/Stylesheet'; import { test } from '../config'; import Fragment from './nodes/Fragment'; import internal_exports from './internal_exports'; -import { Ast, CompileOptions, Var, Warning } from '../interfaces'; +import { Ast, CompileOptions, Var, Warning, CssResult } from '../interfaces'; import error from '../utils/error'; import get_code_frame from '../utils/get_code_frame'; import flatten_reference from './utils/flatten_reference'; @@ -226,7 +226,7 @@ export default class Component { return alias; } - generate(result?: Node[]) { + generate(result?: { js: Node[]; css: CssResult }) { let js = null; let css = null; @@ -236,7 +236,7 @@ export default class Component { const banner = `${this.file ? `${this.file} ` : ``}generated by Svelte v${'__VERSION__'}`; - const program: any = { type: 'Program', body: result }; + const program: any = { type: 'Program', body: result.js }; walk(program, { enter: (node, parent, key) => { @@ -310,7 +310,7 @@ export default class Component { css = compile_options.customElement ? { code: null, map: null } - : this.stylesheet.render(compile_options.cssOutputFilename, true); + : result.css; js = print(program, { sourceMapSource: compile_options.filename @@ -752,7 +752,7 @@ export default class Component { if (map.has(node)) { scope = scope.parent; - } + } }, }); diff --git a/src/compiler/compile/css/Selector.ts b/src/compiler/compile/css/Selector.ts index d99af7a110..eecb0cd975 100644 --- a/src/compiler/compile/css/Selector.ts +++ b/src/compiler/compile/css/Selector.ts @@ -63,9 +63,13 @@ export default class Selector { }); } - transform(code: MagicString, attr: string) { + transform(code: MagicString, attr: string, max_amount_class_specificity_increased: number) { + const amount_class_specificity_to_increase = max_amount_class_specificity_increased - this.blocks.filter(block => block.should_encapsulate).length; + attr = attr.repeat(amount_class_specificity_to_increase + 1); + function encapsulate_block(block: Block) { let i = block.selectors.length; + while (i--) { const selector = block.selectors[i]; if (selector.type === 'PseudoElementSelector' || selector.type === 'PseudoClassSelector') { @@ -131,6 +135,16 @@ export default class Selector { } } } + + get_amount_class_specificity_increased() { + let count = 0; + for (const block of this.blocks) { + if (block.should_encapsulate) { + count ++; + } + } + return count; + } } function apply_selector(blocks: Block[], node: Element, stack: Element[], to_encapsulate: any[]): boolean { diff --git a/src/compiler/compile/css/Stylesheet.ts b/src/compiler/compile/css/Stylesheet.ts index 8342c3fa21..998a879687 100644 --- a/src/compiler/compile/css/Stylesheet.ts +++ b/src/compiler/compile/css/Stylesheet.ts @@ -95,12 +95,12 @@ class Rule { code.remove(c, this.node.block.end - 1); } - transform(code: MagicString, id: string, keyframes: Map) { + transform(code: MagicString, id: string, keyframes: Map, max_amount_class_specificity_increased: number) { if (this.parent && this.parent.node.type === 'Atrule' && is_keyframes_node(this.parent.node)) return true; const attr = `.${id}`; - this.selectors.forEach(selector => selector.transform(code, attr)); + this.selectors.forEach(selector => selector.transform(code, attr, max_amount_class_specificity_increased)); this.declarations.forEach(declaration => declaration.transform(code, keyframes)); } @@ -115,6 +115,10 @@ class Rule { if (!selector.used) handler(selector); }); } + + get_max_amount_class_specificity_increased() { + return Math.max(...this.selectors.map(selector => selector.get_amount_class_specificity_increased())); + } } class Declaration { @@ -239,7 +243,7 @@ class Atrule { } } - transform(code: MagicString, id: string, keyframes: Map) { + 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) => { if (type === 'Identifier') { @@ -258,7 +262,7 @@ class Atrule { } this.children.forEach(child => { - child.transform(code, id, keyframes); + child.transform(code, id, keyframes, max_amount_class_specificity_increased); }); } @@ -275,6 +279,10 @@ class Atrule { child.warn_on_unused_selector(handler); }); } + + get_max_amount_class_specificity_increased() { + return Math.max(...this.children.map(rule => rule.get_max_amount_class_specificity_increased())); + } } export default class Stylesheet { @@ -397,8 +405,9 @@ export default class Stylesheet { }); if (should_transform_selectors) { + const max = Math.max(...this.children.map(rule => rule.get_max_amount_class_specificity_increased())); this.children.forEach((child: (Atrule|Rule)) => { - child.transform(code, this.id, this.keyframes); + child.transform(code, this.id, this.keyframes, max); }); } diff --git a/src/compiler/compile/index.ts b/src/compiler/compile/index.ts index 5d29f71e35..12b161aeeb 100644 --- a/src/compiler/compile/index.ts +++ b/src/compiler/compile/index.ts @@ -90,11 +90,11 @@ export default function compile(source: string, options: CompileOptions = {}) { ); stats.stop('create component'); - const js = options.generate === false + const result = options.generate === false ? null : options.generate === 'ssr' ? render_ssr(component, options) : render_dom(component, options); - return component.generate(js); + return component.generate(result); } diff --git a/src/compiler/compile/render_dom/index.ts b/src/compiler/compile/render_dom/index.ts index a607edaf1e..b87fe05ef7 100644 --- a/src/compiler/compile/render_dom/index.ts +++ b/src/compiler/compile/render_dom/index.ts @@ -1,7 +1,7 @@ import { b, x, p } from 'code-red'; import Component from '../Component'; import Renderer from './Renderer'; -import { CompileOptions } from '../../interfaces'; +import { CompileOptions, CssResult } from '../../interfaces'; import { walk } from 'estree-walker'; import { extract_names, Scope } from '../utils/scope'; import { invalidate } from './invalidate'; @@ -11,7 +11,7 @@ import { ClassDeclaration, FunctionExpression, Node, Statement, ObjectExpression export default function dom( component: Component, options: CompileOptions -) { +): { js: Node[]; css: CssResult } { const { name } = component; const renderer = new Renderer(component, options); @@ -530,7 +530,7 @@ export default function dom( body.push(declaration); } - return flatten(body, []); + return { js: flatten(body, []), css }; } function flatten(nodes: any[], target: any[]) { diff --git a/src/compiler/compile/render_dom/wrappers/Element/index.ts b/src/compiler/compile/render_dom/wrappers/Element/index.ts index cd660f202c..0478702c7c 100644 --- a/src/compiler/compile/render_dom/wrappers/Element/index.ts +++ b/src/compiler/compile/render_dom/wrappers/Element/index.ts @@ -377,8 +377,7 @@ export default class ElementWrapper extends Wrapper { } this.add_attributes(block); - this.add_bindings(block); - this.add_event_handlers(block); + this.add_directives_in_order(block); this.add_transitions(block); this.add_animation(block); this.add_actions(block); @@ -436,29 +435,62 @@ export default class ElementWrapper extends Wrapper { return x`@claim_element(${nodes}, "${name}", { ${attributes} }, ${svg})`; } - add_bindings(block: Block) { + add_directives_in_order (block: Block) { + interface BindingGroup { + events: string[]; + bindings: Binding[]; + } + + const bindingGroups = events + .map(event => ({ + events: event.event_names, + bindings: this.bindings + .filter(binding => binding.node.name !== 'this') + .filter(binding => event.filter(this.node, binding.node.name)) + })) + .filter(group => group.bindings.length); + + const this_binding = this.bindings.find(b => b.node.name === 'this'); + + function getOrder (item: EventHandler | BindingGroup | Binding) { + if (item instanceof EventHandler) { + return item.node.start; + } else if (item instanceof Binding) { + return item.node.start; + } else { + return item.bindings[0].node.start; + } + } + + const ordered: Array = [].concat(bindingGroups, this.event_handlers, this_binding).filter(Boolean); + + ordered.sort((a, b) => getOrder(a) - getOrder(b)); + + ordered.forEach(bindingGroupOrEventHandler => { + if (bindingGroupOrEventHandler instanceof EventHandler) { + add_event_handlers(block, this.var, [bindingGroupOrEventHandler]); + } else if (bindingGroupOrEventHandler instanceof Binding) { + this.add_this_binding(block, bindingGroupOrEventHandler); + } else { + this.add_bindings(block, bindingGroupOrEventHandler); + } + }); + } + + add_bindings(block: Block, bindingGroup) { const { renderer } = this; - if (this.bindings.length === 0) return; + if (bindingGroup.bindings.length === 0) return; renderer.component.has_reactive_assignments = true; - const lock = this.bindings.some(binding => binding.needs_lock) ? + const lock = bindingGroup.bindings.some(binding => binding.needs_lock) ? block.get_unique_name(`${this.var.name}_updating`) : null; if (lock) block.add_variable(lock, x`false`); - const groups = events - .map(event => ({ - events: event.event_names, - bindings: this.bindings - .filter(binding => binding.node.name !== 'this') - .filter(binding => event.filter(this.node, binding.node.name)) - })) - .filter(group => group.bindings.length); - - groups.forEach(group => { + [bindingGroup].forEach(group => { const handler = renderer.component.get_unique_name(`${this.var.name}_${group.events.join('_')}_handler`); renderer.add_to_context(handler.name); @@ -586,13 +618,15 @@ export default class ElementWrapper extends Wrapper { if (lock) { block.chunks.update.push(b`${lock} = false;`); } + } - const this_binding = this.bindings.find(b => b.node.name === 'this'); - if (this_binding) { - const binding_callback = bind_this(renderer.component, block, this_binding.node, this.var); + add_this_binding(block: Block, this_binding: Binding) { + const { renderer } = this; + + renderer.component.has_reactive_assignments = true; - block.chunks.mount.push(binding_callback); - } + const binding_callback = bind_this(renderer.component, block, this_binding.node, this.var); + block.chunks.mount.push(binding_callback); } add_attributes(block: Block) { diff --git a/src/compiler/compile/render_dom/wrappers/Text.ts b/src/compiler/compile/render_dom/wrappers/Text.ts index 5247eacdb6..1978cba0d7 100644 --- a/src/compiler/compile/render_dom/wrappers/Text.ts +++ b/src/compiler/compile/render_dom/wrappers/Text.ts @@ -27,6 +27,11 @@ function should_skip(node: Text) { if (parent_element.type === 'Head') return true; if (parent_element.type === 'InlineComponent') return parent_element.children.length === 1 && node === parent_element.children[0]; + // svg namespace exclusions + if (/svg$/.test(parent_element.namespace)) { + if (node.prev && node.prev.type === "Element" && node.prev.name === "tspan") return false; + } + return parent_element.namespace || elements_without_text.has(parent_element.name); } diff --git a/src/compiler/compile/render_dom/wrappers/shared/get_slot_definition.ts b/src/compiler/compile/render_dom/wrappers/shared/get_slot_definition.ts index 2adbd3b1d0..9ab48dd035 100644 --- a/src/compiler/compile/render_dom/wrappers/shared/get_slot_definition.ts +++ b/src/compiler/compile/render_dom/wrappers/shared/get_slot_definition.ts @@ -7,7 +7,7 @@ import { BinaryExpression } from 'estree'; export function get_slot_definition(block: Block, scope: TemplateScope, lets: Let[]) { if (lets.length === 0) return { block, scope }; - const input = { + const context_input = { type: 'ObjectPattern', properties: lets.map(l => ({ type: 'Property', @@ -17,10 +17,41 @@ export function get_slot_definition(block: Block, scope: TemplateScope, lets: Le })) }; + const properties = []; + const value_map = new Map(); + + lets.forEach(l => { + let value; + if (l.names.length > 1) { + // more than one, probably destructuring + const unique_name = block.get_unique_name(l.names.join('_')).name; + value_map.set(l.value, unique_name); + value = { type: 'Identifier', name: unique_name }; + } else { + value = l.value || l.name; + } + properties.push({ + type: 'Property', + kind: 'init', + key: l.name, + value, + }); + }); + + const changes_input = { + type: 'ObjectPattern', + properties, + }; + const names: Set = new Set(); + const names_lookup: Map = new Map(); + lets.forEach(l => { l.names.forEach(name => { names.add(name); + if (value_map.has(l.value)) { + names_lookup.set(name, value_map.get(l.value)); + } }); }); @@ -43,8 +74,10 @@ export function get_slot_definition(block: Block, scope: TemplateScope, lets: Le const i = context_lookup.get(name).index.value as number; const g = Math.floor(i / 31); + const lookup_name = names_lookup.has(name) ? names_lookup.get(name) : name; + if (!grouped[g]) grouped[g] = []; - grouped[g].push({ name, n: i % 31 }); + grouped[g].push({ name: lookup_name, n: i % 31 }); }); const elements = []; @@ -65,8 +98,9 @@ export function get_slot_definition(block: Block, scope: TemplateScope, lets: Le return Array.from(names) .map(name => { + const lookup_name = names_lookup.has(name) ? names_lookup.get(name) : name; const i = context_lookup.get(name).index.value as number; - return x`${name} ? ${1 << i} : 0`; + return x`${lookup_name} ? ${1 << i} : 0`; }) .reduce((lhs, rhs) => x`${lhs} | ${rhs}`) as BinaryExpression; } @@ -75,7 +109,7 @@ export function get_slot_definition(block: Block, scope: TemplateScope, lets: Le return { block, scope, - get_context: x`${input} => ${context}`, - get_changes: x`${input} => ${changes}` + get_context: x`${context_input} => ${context}`, + get_changes: x`${changes_input} => ${changes}` }; } \ No newline at end of file diff --git a/src/compiler/compile/render_ssr/index.ts b/src/compiler/compile/render_ssr/index.ts index 00157cc256..ad22869d32 100644 --- a/src/compiler/compile/render_ssr/index.ts +++ b/src/compiler/compile/render_ssr/index.ts @@ -1,17 +1,17 @@ import { b } from 'code-red'; import Component from '../Component'; -import { CompileOptions } from '../../interfaces'; +import { CompileOptions, CssResult } from '../../interfaces'; import { string_literal } from '../utils/stringify'; import Renderer from './Renderer'; import { INode as TemplateNode } from '../nodes/interfaces'; // TODO import Text from '../nodes/Text'; import { extract_names } from '../utils/scope'; -import { LabeledStatement, Statement, ExpressionStatement, AssignmentExpression } from 'estree'; +import { LabeledStatement, Statement, ExpressionStatement, AssignmentExpression, Node } from 'estree'; export default function ssr( component: Component, options: CompileOptions -) { +): {js: Node[]; css: CssResult} { const renderer = new Renderer({ name: component.name }); @@ -145,7 +145,7 @@ export default function ssr( main ].filter(Boolean); - return b` + const js = b` ${css.code ? b` const #css = { code: "${css.code}", @@ -160,6 +160,8 @@ export default function ssr( ${blocks} }); `; + + return {js, css}; } function trim(nodes: TemplateNode[]) { diff --git a/src/compiler/interfaces.ts b/src/compiler/interfaces.ts index e7362b9313..a5e286462f 100644 --- a/src/compiler/interfaces.ts +++ b/src/compiler/interfaces.ts @@ -1,4 +1,5 @@ import { Node, Program } from "estree"; +import { SourceMap } from 'magic-string'; interface BaseNode { start: number; @@ -160,4 +161,9 @@ export interface Var { hoistable?: boolean; subscribable?: boolean; is_reactive_dependency?: boolean; +} + +export interface CssResult { + code: string; + map: SourceMap; } \ No newline at end of file diff --git a/test/css/samples/preserve-specificity/expected.css b/test/css/samples/preserve-specificity/expected.css new file mode 100644 index 0000000000..1d4f54820f --- /dev/null +++ b/test/css/samples/preserve-specificity/expected.css @@ -0,0 +1 @@ +a.svelte-xyz b c span.svelte-xyz{color:red;font-size:2em;font-family:'Comic Sans MS'}.foo.svelte-xyz.svelte-xyz{color:green} \ No newline at end of file diff --git a/test/css/samples/preserve-specificity/expected.html b/test/css/samples/preserve-specificity/expected.html new file mode 100644 index 0000000000..171d90d362 --- /dev/null +++ b/test/css/samples/preserve-specificity/expected.html @@ -0,0 +1,12 @@ + + + + + Big red Comic Sans + + + Big red Comic Sans + + + + \ No newline at end of file diff --git a/test/css/samples/preserve-specificity/input.svelte b/test/css/samples/preserve-specificity/input.svelte new file mode 100644 index 0000000000..1c0a594145 --- /dev/null +++ b/test/css/samples/preserve-specificity/input.svelte @@ -0,0 +1,24 @@ + + + + + + Big red Comic Sans + + + Big red Comic Sans + + + + + + \ No newline at end of file diff --git a/test/js/samples/collapses-text-around-comments/expected.js b/test/js/samples/collapses-text-around-comments/expected.js index 6fef0f9490..8c0e9d5373 100644 --- a/test/js/samples/collapses-text-around-comments/expected.js +++ b/test/js/samples/collapses-text-around-comments/expected.js @@ -63,4 +63,4 @@ class Component extends SvelteComponent { } } -export default Component; \ No newline at end of file +export default Component; diff --git a/test/js/samples/css-media-query/expected.js b/test/js/samples/css-media-query/expected.js index f477670059..0566c22ddd 100644 --- a/test/js/samples/css-media-query/expected.js +++ b/test/js/samples/css-media-query/expected.js @@ -46,4 +46,4 @@ class Component extends SvelteComponent { } } -export default Component; \ No newline at end of file +export default Component; diff --git a/test/js/samples/media-bindings/expected.js b/test/js/samples/media-bindings/expected.js index bfb1b8911d..b5548a3efe 100644 --- a/test/js/samples/media-bindings/expected.js +++ b/test/js/samples/media-bindings/expected.js @@ -29,26 +29,26 @@ function create_fragment(ctx) { audio_updating = true; } - /*audio_timeupdate_handler*/ ctx[10].call(audio); + /*audio_timeupdate_handler*/ ctx[12].call(audio); } return { c() { audio = element("audio"); + if (/*buffered*/ ctx[0] === void 0) add_render_callback(() => /*audio_progress_handler*/ ctx[10].call(audio)); + if (/*buffered*/ ctx[0] === void 0 || /*seekable*/ ctx[1] === void 0) add_render_callback(() => /*audio_loadedmetadata_handler*/ ctx[11].call(audio)); if (/*played*/ ctx[2] === void 0 || /*currentTime*/ ctx[3] === void 0 || /*ended*/ ctx[9] === void 0) add_render_callback(audio_timeupdate_handler); - if (/*duration*/ ctx[4] === void 0) add_render_callback(() => /*audio_durationchange_handler*/ ctx[11].call(audio)); - if (/*buffered*/ ctx[0] === void 0) add_render_callback(() => /*audio_progress_handler*/ ctx[13].call(audio)); - if (/*buffered*/ ctx[0] === void 0 || /*seekable*/ ctx[1] === void 0) add_render_callback(() => /*audio_loadedmetadata_handler*/ ctx[14].call(audio)); + if (/*duration*/ ctx[4] === void 0) add_render_callback(() => /*audio_durationchange_handler*/ ctx[13].call(audio)); if (/*seeking*/ ctx[8] === void 0) add_render_callback(() => /*audio_seeking_seeked_handler*/ ctx[17].call(audio)); if (/*ended*/ ctx[9] === void 0) add_render_callback(() => /*audio_ended_handler*/ ctx[18].call(audio)); dispose = [ + listen(audio, "progress", /*audio_progress_handler*/ ctx[10]), + listen(audio, "loadedmetadata", /*audio_loadedmetadata_handler*/ ctx[11]), listen(audio, "timeupdate", audio_timeupdate_handler), - listen(audio, "durationchange", /*audio_durationchange_handler*/ ctx[11]), - listen(audio, "play", /*audio_play_pause_handler*/ ctx[12]), - listen(audio, "pause", /*audio_play_pause_handler*/ ctx[12]), - listen(audio, "progress", /*audio_progress_handler*/ ctx[13]), - listen(audio, "loadedmetadata", /*audio_loadedmetadata_handler*/ ctx[14]), + listen(audio, "durationchange", /*audio_durationchange_handler*/ ctx[13]), + listen(audio, "play", /*audio_play_pause_handler*/ ctx[14]), + listen(audio, "pause", /*audio_play_pause_handler*/ ctx[14]), listen(audio, "volumechange", /*audio_volumechange_handler*/ ctx[15]), listen(audio, "ratechange", /*audio_ratechange_handler*/ ctx[16]), listen(audio, "seeking", /*audio_seeking_seeked_handler*/ ctx[17]), @@ -72,6 +72,8 @@ function create_fragment(ctx) { audio.currentTime = /*currentTime*/ ctx[3]; } + audio_updating = false; + if (dirty & /*paused*/ 32 && audio_is_paused !== (audio_is_paused = /*paused*/ ctx[5])) { audio[audio_is_paused ? "pause" : "play"](); } @@ -83,8 +85,6 @@ function create_fragment(ctx) { if (dirty & /*playbackRate*/ 128 && !isNaN(/*playbackRate*/ ctx[7])) { audio.playbackRate = /*playbackRate*/ ctx[7]; } - - audio_updating = false; }, i: noop, o: noop, @@ -107,6 +107,18 @@ function instance($$self, $$props, $$invalidate) { let { seeking } = $$props; let { ended } = $$props; + function audio_progress_handler() { + buffered = time_ranges_to_array(this.buffered); + $$invalidate(0, buffered); + } + + function audio_loadedmetadata_handler() { + buffered = time_ranges_to_array(this.buffered); + seekable = time_ranges_to_array(this.seekable); + $$invalidate(0, buffered); + $$invalidate(1, seekable); + } + function audio_timeupdate_handler() { played = time_ranges_to_array(this.played); currentTime = this.currentTime; @@ -126,18 +138,6 @@ function instance($$self, $$props, $$invalidate) { $$invalidate(5, paused); } - function audio_progress_handler() { - buffered = time_ranges_to_array(this.buffered); - $$invalidate(0, buffered); - } - - function audio_loadedmetadata_handler() { - buffered = time_ranges_to_array(this.buffered); - seekable = time_ranges_to_array(this.seekable); - $$invalidate(0, buffered); - $$invalidate(1, seekable); - } - function audio_volumechange_handler() { volume = this.volume; $$invalidate(6, volume); @@ -182,11 +182,11 @@ function instance($$self, $$props, $$invalidate) { playbackRate, seeking, ended, + audio_progress_handler, + audio_loadedmetadata_handler, audio_timeupdate_handler, audio_durationchange_handler, audio_play_pause_handler, - audio_progress_handler, - audio_loadedmetadata_handler, audio_volumechange_handler, audio_ratechange_handler, audio_seeking_seeked_handler, diff --git a/test/js/samples/video-bindings/expected.js b/test/js/samples/video-bindings/expected.js index e3d48f9922..5b734a70a6 100644 --- a/test/js/samples/video-bindings/expected.js +++ b/test/js/samples/video-bindings/expected.js @@ -17,8 +17,8 @@ import { function create_fragment(ctx) { let video; let video_updating = false; - let video_resize_listener; let video_animationframe; + let video_resize_listener; let dispose; function video_timeupdate_handler() { @@ -29,23 +29,23 @@ function create_fragment(ctx) { video_updating = true; } - /*video_timeupdate_handler*/ ctx[5].call(video); + /*video_timeupdate_handler*/ ctx[4].call(video); } return { c() { video = element("video"); - add_render_callback(() => /*video_elementresize_handler*/ ctx[4].call(video)); - if (/*videoHeight*/ ctx[1] === void 0 || /*videoWidth*/ ctx[2] === void 0) add_render_callback(() => /*video_resize_handler*/ ctx[6].call(video)); + if (/*videoHeight*/ ctx[1] === void 0 || /*videoWidth*/ ctx[2] === void 0) add_render_callback(() => /*video_resize_handler*/ ctx[5].call(video)); + add_render_callback(() => /*video_elementresize_handler*/ ctx[6].call(video)); dispose = [ listen(video, "timeupdate", video_timeupdate_handler), - listen(video, "resize", /*video_resize_handler*/ ctx[6]) + listen(video, "resize", /*video_resize_handler*/ ctx[5]) ]; }, m(target, anchor) { insert(target, video, anchor); - video_resize_listener = add_resize_listener(video, /*video_elementresize_handler*/ ctx[4].bind(video)); + video_resize_listener = add_resize_listener(video, /*video_elementresize_handler*/ ctx[6].bind(video)); }, p(ctx, [dirty]) { if (!video_updating && dirty & /*currentTime*/ 1 && !isNaN(/*currentTime*/ ctx[0])) { @@ -70,11 +70,6 @@ function instance($$self, $$props, $$invalidate) { let { videoWidth } = $$props; let { offsetWidth } = $$props; - function video_elementresize_handler() { - offsetWidth = this.offsetWidth; - $$invalidate(3, offsetWidth); - } - function video_timeupdate_handler() { currentTime = this.currentTime; $$invalidate(0, currentTime); @@ -87,6 +82,11 @@ function instance($$self, $$props, $$invalidate) { $$invalidate(2, videoWidth); } + function video_elementresize_handler() { + offsetWidth = this.offsetWidth; + $$invalidate(3, offsetWidth); + } + $$self.$set = $$props => { if ("currentTime" in $$props) $$invalidate(0, currentTime = $$props.currentTime); if ("videoHeight" in $$props) $$invalidate(1, videoHeight = $$props.videoHeight); @@ -99,9 +99,9 @@ function instance($$self, $$props, $$invalidate) { videoHeight, videoWidth, offsetWidth, - video_elementresize_handler, video_timeupdate_handler, - video_resize_handler + video_resize_handler, + video_elementresize_handler ]; } diff --git a/test/runtime/samples/apply-directives-in-order/_config.js b/test/runtime/samples/apply-directives-in-order/_config.js new file mode 100644 index 0000000000..e5e8980ed1 --- /dev/null +++ b/test/runtime/samples/apply-directives-in-order/_config.js @@ -0,0 +1,37 @@ +export default { + props: { + value: '' + }, + + html: ` + +

+ `, + + ssrHtml: ` + +

+ `, + + async test({ assert, component, target, window }) { + const input = target.querySelector('input'); + + const event = new window.Event('input'); + input.value = 'h'; + await input.dispatchEvent(event); + + assert.equal(input.value, 'H'); + assert.htmlEqual(target.innerHTML, ` + +

H

+ `); + + input.value = 'he'; + await input.dispatchEvent(event); + assert.equal(input.value, 'HE'); + assert.htmlEqual(target.innerHTML, ` + +

HE

+ `); + }, +}; diff --git a/test/runtime/samples/apply-directives-in-order/main.svelte b/test/runtime/samples/apply-directives-in-order/main.svelte new file mode 100644 index 0000000000..be652c7b79 --- /dev/null +++ b/test/runtime/samples/apply-directives-in-order/main.svelte @@ -0,0 +1,10 @@ + + + +

{value}

diff --git a/test/runtime/samples/component-slot-let-destructured-2/Nested.svelte b/test/runtime/samples/component-slot-let-destructured-2/Nested.svelte new file mode 100644 index 0000000000..5dfe32bc7f --- /dev/null +++ b/test/runtime/samples/component-slot-let-destructured-2/Nested.svelte @@ -0,0 +1,5 @@ + + + diff --git a/test/runtime/samples/component-slot-let-destructured-2/_config.js b/test/runtime/samples/component-slot-let-destructured-2/_config.js new file mode 100644 index 0000000000..38b04b7b5e --- /dev/null +++ b/test/runtime/samples/component-slot-let-destructured-2/_config.js @@ -0,0 +1,68 @@ +export default { + html: ` +
+ hello world 0 hello + +
+
+ hello world 0 hello + +
+
+ hello world 0 hello + +
+ `, + async test({ assert, component, target, window }) { + const [button1, button2, button3] = target.querySelectorAll('button'); + const event = new window.MouseEvent('click'); + + await button1.dispatchEvent(event); + assert.htmlEqual(target.innerHTML, ` +
+ hello world 1 hello + +
+
+ hello world 0 hello + +
+
+ hello world 0 hello + +
+ `); + + await button2.dispatchEvent(event); + assert.htmlEqual(target.innerHTML, ` +
+ hello world 1 hello + +
+
+ hello world 1 hello + +
+
+ hello world 0 hello + +
+ `); + + await button3.dispatchEvent(event); + assert.htmlEqual(target.innerHTML, ` +
+ hello world 1 hello + +
+
+ hello world 1 hello + +
+
+ hello world 1 hello + +
+ `); + } +}; diff --git a/test/runtime/samples/component-slot-let-destructured-2/main.svelte b/test/runtime/samples/component-slot-let-destructured-2/main.svelte new file mode 100644 index 0000000000..215a1390d9 --- /dev/null +++ b/test/runtime/samples/component-slot-let-destructured-2/main.svelte @@ -0,0 +1,28 @@ + + +
+ + {pair[0]} {pair[1]} {c} {foo} + + + +
+ +
+ + {a} {b} {d} {foo} + + + +
+ +
+ + {a} {b} {e} {foo} + + + +
\ No newline at end of file diff --git a/test/runtime/samples/svg-tspan-preserve-space/_config.js b/test/runtime/samples/svg-tspan-preserve-space/_config.js new file mode 100644 index 0000000000..283af0a2b0 --- /dev/null +++ b/test/runtime/samples/svg-tspan-preserve-space/_config.js @@ -0,0 +1,3 @@ +export default { + html: `foo barfoo bar`, +}; diff --git a/test/runtime/samples/svg-tspan-preserve-space/main.svelte b/test/runtime/samples/svg-tspan-preserve-space/main.svelte new file mode 100644 index 0000000000..df43d575a9 --- /dev/null +++ b/test/runtime/samples/svg-tspan-preserve-space/main.svelte @@ -0,0 +1 @@ +foo {"bar"}foo bar \ No newline at end of file diff --git a/test/server-side-rendering/index.js b/test/server-side-rendering/index.js index 768917e833..a56a4ddaed 100644 --- a/test/server-side-rendering/index.js +++ b/test/server-side-rendering/index.js @@ -1,13 +1,16 @@ import * as assert from "assert"; import * as fs from "fs"; import * as path from "path"; +import * as glob from 'tiny-glob/sync.js'; import { showOutput, loadConfig, + loadSvelte, setupHtmlEqual, tryToLoadJson, - shouldUpdateExpected + shouldUpdateExpected, + mkdirp } from "../helpers.js"; function tryToReadFile(file) { @@ -20,6 +23,7 @@ function tryToReadFile(file) { } const sveltePath = process.cwd().split('\\').join('/'); +let compile = null; describe("ssr", () => { before(() => { @@ -28,6 +32,8 @@ describe("ssr", () => { sveltePath }); + compile = loadSvelte(true).compile; + return setupHtmlEqual(); }); @@ -142,6 +148,33 @@ describe("ssr", () => { require("../../register")(compileOptions); + glob('**/*.svelte', { cwd }).forEach(file => { + if (file[0] === '_') return; + + const dir = `${cwd}/_output/ssr`; + const out = `${dir}/${file.replace(/\.svelte$/, '.js')}`; + + if (fs.existsSync(out)) { + fs.unlinkSync(out); + } + + mkdirp(dir); + + try { + const { js } = compile( + fs.readFileSync(`${cwd}/${file}`, 'utf-8'), + { + ...compileOptions, + filename: file + } + ); + + fs.writeFileSync(out, js.code); + } catch (err) { + // do nothing + } + }); + try { if (config.before_test) config.before_test(); diff --git a/test/validator/index.js b/test/validator/index.js index 9f991df4f2..8bf0400d7c 100644 --- a/test/validator/index.js +++ b/test/validator/index.js @@ -27,7 +27,8 @@ describe("validate", () => { const { warnings } = svelte.compile(input, { dev: config.dev, legacy: config.legacy, - generate: false + generate: false, + customElement: config.customElement }); assert.deepEqual(warnings.map(w => ({ diff --git a/test/validator/samples/tag-custom-element-options-missing/input.svelte b/test/validator/samples/tag-custom-element-options-missing/input.svelte new file mode 100644 index 0000000000..f5f5d74270 --- /dev/null +++ b/test/validator/samples/tag-custom-element-options-missing/input.svelte @@ -0,0 +1 @@ + diff --git a/test/validator/samples/tag-custom-element-options-missing/warnings.json b/test/validator/samples/tag-custom-element-options-missing/warnings.json new file mode 100644 index 0000000000..185c6c3179 --- /dev/null +++ b/test/validator/samples/tag-custom-element-options-missing/warnings.json @@ -0,0 +1,15 @@ +[{ + "code": "missing-custom-element-compile-options", + "message": "The 'tag' option is used when generating a custom element. Did you forget the 'customElement: true' compile option?", + "start": { + "line": 1, + "column": 16, + "character": 16 + }, + "end": { + "line": 1, + "column": 36, + "character": 36 + }, + "pos": 16 +}] diff --git a/test/validator/samples/tag-custom-element-options-true/_config.js b/test/validator/samples/tag-custom-element-options-true/_config.js new file mode 100644 index 0000000000..ec3c350d46 --- /dev/null +++ b/test/validator/samples/tag-custom-element-options-true/_config.js @@ -0,0 +1,3 @@ +export default { + customElement: true +}; diff --git a/test/validator/samples/tag-custom-element-options-true/input.svelte b/test/validator/samples/tag-custom-element-options-true/input.svelte new file mode 100644 index 0000000000..f5f5d74270 --- /dev/null +++ b/test/validator/samples/tag-custom-element-options-true/input.svelte @@ -0,0 +1 @@ +