diff --git a/CHANGELOG.md b/CHANGELOG.md index 2863bf73eb..05a4b12fa3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,12 @@ # Svelte changelog +## Unreleased + +* Add types to `createEventDispatcher` ([#5211](https://github.com/sveltejs/svelte/issues/5211)) +* In SSR mode, do not automatically declare variables for reactive assignments to member expressions ([#5247](https://github.com/sveltejs/svelte/issues/5247)) +* Include selector in message of `unused-css-selector` warning ([#5252](https://github.com/sveltejs/svelte/issues/5252)) +* Fix using ``s in child `{#await}`/`{#each}` contexts ([#5255](https://github.com/sveltejs/svelte/issues/5255)) + ## 3.24.1 * Prevent duplicate invalidation with certain two-way component bindings ([#3180](https://github.com/sveltejs/svelte/issues/3180), [#5117](https://github.com/sveltejs/svelte/issues/5117), [#5144](https://github.com/sveltejs/svelte/issues/5144)) diff --git a/site/content/docs/02-template-syntax.md b/site/content/docs/02-template-syntax.md index 6d0847eb41..cfc7d7ba7a 100644 --- a/site/content/docs/02-template-syntax.md +++ b/site/content/docs/02-template-syntax.md @@ -544,6 +544,21 @@ Numeric input values are coerced; even though `input.value` is a string as far a ``` +--- + +On `` elements with `type="file"`, you can use `bind:files` to get the [`FileList` of selected files](https://developer.mozilla.org/en-US/docs/Web/API/FileList). + +```sv + + +``` + ##### Binding ` + + + + +{#if files} +

Selected files:

+ {#each Array.from(files) as file} +

{file.name} ({file.size} bytes)

+ {/each} +{/if} diff --git a/site/content/examples/05-bindings/05-file-inputs/meta.json b/site/content/examples/05-bindings/05-file-inputs/meta.json new file mode 100644 index 0000000000..92d0587e87 --- /dev/null +++ b/site/content/examples/05-bindings/05-file-inputs/meta.json @@ -0,0 +1,3 @@ +{ + "title": "File inputs" +} diff --git a/site/content/examples/05-bindings/05-select-bindings/App.svelte b/site/content/examples/05-bindings/06-select-bindings/App.svelte similarity index 100% rename from site/content/examples/05-bindings/05-select-bindings/App.svelte rename to site/content/examples/05-bindings/06-select-bindings/App.svelte diff --git a/site/content/examples/05-bindings/05-select-bindings/meta.json b/site/content/examples/05-bindings/06-select-bindings/meta.json similarity index 100% rename from site/content/examples/05-bindings/05-select-bindings/meta.json rename to site/content/examples/05-bindings/06-select-bindings/meta.json diff --git a/site/content/examples/05-bindings/06-multiple-select-bindings/App.svelte b/site/content/examples/05-bindings/07-multiple-select-bindings/App.svelte similarity index 100% rename from site/content/examples/05-bindings/06-multiple-select-bindings/App.svelte rename to site/content/examples/05-bindings/07-multiple-select-bindings/App.svelte diff --git a/site/content/examples/05-bindings/06-multiple-select-bindings/meta.json b/site/content/examples/05-bindings/07-multiple-select-bindings/meta.json similarity index 100% rename from site/content/examples/05-bindings/06-multiple-select-bindings/meta.json rename to site/content/examples/05-bindings/07-multiple-select-bindings/meta.json diff --git a/site/content/examples/05-bindings/07-each-block-bindings/App.svelte b/site/content/examples/05-bindings/08-each-block-bindings/App.svelte similarity index 100% rename from site/content/examples/05-bindings/07-each-block-bindings/App.svelte rename to site/content/examples/05-bindings/08-each-block-bindings/App.svelte diff --git a/site/content/examples/05-bindings/07-each-block-bindings/meta.json b/site/content/examples/05-bindings/08-each-block-bindings/meta.json similarity index 100% rename from site/content/examples/05-bindings/07-each-block-bindings/meta.json rename to site/content/examples/05-bindings/08-each-block-bindings/meta.json diff --git a/site/content/examples/05-bindings/08-media-elements/App.svelte b/site/content/examples/05-bindings/09-media-elements/App.svelte similarity index 100% rename from site/content/examples/05-bindings/08-media-elements/App.svelte rename to site/content/examples/05-bindings/09-media-elements/App.svelte diff --git a/site/content/examples/05-bindings/08-media-elements/meta.json b/site/content/examples/05-bindings/09-media-elements/meta.json similarity index 100% rename from site/content/examples/05-bindings/08-media-elements/meta.json rename to site/content/examples/05-bindings/09-media-elements/meta.json diff --git a/site/content/examples/05-bindings/09-dimensions/App.svelte b/site/content/examples/05-bindings/10-dimensions/App.svelte similarity index 100% rename from site/content/examples/05-bindings/09-dimensions/App.svelte rename to site/content/examples/05-bindings/10-dimensions/App.svelte diff --git a/site/content/examples/05-bindings/09-dimensions/meta.json b/site/content/examples/05-bindings/10-dimensions/meta.json similarity index 100% rename from site/content/examples/05-bindings/09-dimensions/meta.json rename to site/content/examples/05-bindings/10-dimensions/meta.json diff --git a/site/content/examples/05-bindings/10-bind-this/App.svelte b/site/content/examples/05-bindings/11-bind-this/App.svelte similarity index 100% rename from site/content/examples/05-bindings/10-bind-this/App.svelte rename to site/content/examples/05-bindings/11-bind-this/App.svelte diff --git a/site/content/examples/05-bindings/10-bind-this/meta.json b/site/content/examples/05-bindings/11-bind-this/meta.json similarity index 100% rename from site/content/examples/05-bindings/10-bind-this/meta.json rename to site/content/examples/05-bindings/11-bind-this/meta.json diff --git a/site/content/examples/05-bindings/11-component-bindings/App.svelte b/site/content/examples/05-bindings/12-component-bindings/App.svelte similarity index 100% rename from site/content/examples/05-bindings/11-component-bindings/App.svelte rename to site/content/examples/05-bindings/12-component-bindings/App.svelte diff --git a/site/content/examples/05-bindings/11-component-bindings/Keypad.svelte b/site/content/examples/05-bindings/12-component-bindings/Keypad.svelte similarity index 100% rename from site/content/examples/05-bindings/11-component-bindings/Keypad.svelte rename to site/content/examples/05-bindings/12-component-bindings/Keypad.svelte diff --git a/site/content/examples/05-bindings/11-component-bindings/meta.json b/site/content/examples/05-bindings/12-component-bindings/meta.json similarity index 100% rename from site/content/examples/05-bindings/11-component-bindings/meta.json rename to site/content/examples/05-bindings/12-component-bindings/meta.json diff --git a/site/content/faq/200-are-there-any-video-courses.md b/site/content/faq/200-are-there-any-video-courses.md index 0c2c1f680e..1a70cdfcf5 100644 --- a/site/content/faq/200-are-there-any-video-courses.md +++ b/site/content/faq/200-are-there-any-video-courses.md @@ -2,7 +2,11 @@ question: Are there any video courses? --- -There are no official ones, but here are a couple of third-part ones that we know of. +Rich Harris, the creator of Svelte, taught a course: + +- [Frontend Masters](https://frontendmasters.com/courses/svelte/) + +There are also a couple of third-party courses: - [Egghead](https://egghead.io/playlists/getting-started-with-svelte-3-05a8541a) - [Udemy](https://www.udemy.com/sveltejs-the-complete-guide/) diff --git a/site/package-lock.json b/site/package-lock.json index caeb17a9bc..00da27f0c7 100644 --- a/site/package-lock.json +++ b/site/package-lock.json @@ -1573,9 +1573,9 @@ } }, "clipboard": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/clipboard/-/clipboard-2.0.4.tgz", - "integrity": "sha512-Vw26VSLRpJfBofiVaFb/I8PVfdI1OxKcYShe6fm0sP/DtmiWQNCjhM/okTvdCo0G+lMMm1rMYbk4IK4x1X+kgQ==", + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/clipboard/-/clipboard-2.0.6.tgz", + "integrity": "sha512-g5zbiixBRk/wyKakSwCKd7vQXDjFnAMGHoEyBogG/bw9kTD9GvdAvaoRR1ALcEzt3pVKxZR0pViekPMIS0QyGg==", "optional": true, "requires": { "good-listener": "^1.2.2", @@ -3170,9 +3170,9 @@ "integrity": "sha512-plS7uY0WWiTBwWZs9KM3M88ZxHWKbrbMUDf52CPum6BqAxiLmKROmaTnmhXtv0krQ0l0HRLcFS8JDwOFyPt/OQ==" }, "prismjs": { - "version": "1.17.1", - "resolved": "https://registry.npmjs.org/prismjs/-/prismjs-1.17.1.tgz", - "integrity": "sha512-PrEDJAFdUGbOP6xK/UsfkC5ghJsPJviKgnQOoxaDbBjwc8op68Quupwt1DeAFoG8GImPhiKXAvvsH7wDSLsu1Q==", + "version": "1.21.0", + "resolved": "https://registry.npmjs.org/prismjs/-/prismjs-1.21.0.tgz", + "integrity": "sha512-uGdSIu1nk3kej2iZsLyDoJ7e9bnPzIgY0naW/HdknGj61zScaprVEVGHrPoXqI+M9sP0NDnTK2jpkvmldpuqDw==", "requires": { "clipboard": "^2.0.0" } diff --git a/site/package.json b/site/package.json index fd5be85976..c416fe5bf1 100644 --- a/site/package.json +++ b/site/package.json @@ -25,7 +25,7 @@ "pg": "^7.12.1", "polka": "^1.0.0-next.9", "prism-svelte": "^0.4.3", - "prismjs": "^1.17.1", + "prismjs": "^1.21.0", "sirv": "^1.0.0-next.2", "yootils": "0.0.16" }, diff --git a/site/static/examples/thumbnails/file-inputs.jpg b/site/static/examples/thumbnails/file-inputs.jpg new file mode 100644 index 0000000000..d302633f6b Binary files /dev/null and b/site/static/examples/thumbnails/file-inputs.jpg differ diff --git a/src/compiler/compile/css/Stylesheet.ts b/src/compiler/compile/css/Stylesheet.ts index 246dab0f12..27438947ff 100644 --- a/src/compiler/compile/css/Stylesheet.ts +++ b/src/compiler/compile/css/Stylesheet.ts @@ -435,7 +435,7 @@ export default class Stylesheet { child.warn_on_unused_selector((selector: Selector) => { component.warn(selector.node, { code: `css-unused-selector`, - message: `Unused CSS selector` + message: `Unused CSS selector "${this.source.slice(selector.node.start, selector.node.end)}"` }); }); }); diff --git a/src/compiler/compile/render_dom/wrappers/InlineComponent/index.ts b/src/compiler/compile/render_dom/wrappers/InlineComponent/index.ts index 814f365a84..904cfa3329 100644 --- a/src/compiler/compile/render_dom/wrappers/InlineComponent/index.ts +++ b/src/compiler/compile/render_dom/wrappers/InlineComponent/index.ts @@ -17,6 +17,7 @@ import { Node, Identifier, ObjectExpression } from 'estree'; import EventHandler from '../Element/EventHandler'; import { extract_names } from 'periscopic'; import mark_each_block_bindings from '../shared/mark_each_block_bindings'; +import { string_to_member_expression } from '../../../utils/string_to_member_expression'; export default class InlineComponentWrapper extends Wrapper { var: Identifier; @@ -484,7 +485,7 @@ export default class InlineComponentWrapper extends Wrapper { } else { const expression = this.node.name === 'svelte:self' ? component.name - : this.renderer.reference(this.node.name); + : this.renderer.reference(string_to_member_expression(this.node.name)); block.chunks.init.push(b` ${(this.node.attributes.length > 0 || this.node.bindings.length > 0) && b` diff --git a/src/compiler/compile/render_ssr/index.ts b/src/compiler/compile/render_ssr/index.ts index c87fe3bdd9..a72b8b35ae 100644 --- a/src/compiler/compile/render_ssr/index.ts +++ b/src/compiler/compile/render_ssr/index.ts @@ -5,8 +5,7 @@ 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, Node } from 'estree'; +import { LabeledStatement, Statement, Node } from 'estree'; export default function ssr( component: Component, @@ -72,37 +71,17 @@ export default function ssr( }) : []; + const injected = Array.from(component.injected_reactive_declaration_vars).filter(name => { + const variable = component.var_lookup.get(name); + return variable.injected; + }); + const reactive_declarations = component.reactive_declarations.map(d => { const body: Statement = (d.node as LabeledStatement).body; let statement = b`${body}`; - if (d.declaration) { - const declared = extract_names(d.declaration); - const injected = declared.filter(name => { - return name[0] !== '$' && component.var_lookup.get(name).injected; - }); - - const self_dependencies = injected.filter(name => d.dependencies.has(name)); - - if (injected.length) { - // in some cases we need to do `let foo; [expression]`, in - // others we can do `let [expression]` - const separate = ( - self_dependencies.length > 0 || - declared.length > injected.length - ); - - const { left, right } = (body as ExpressionStatement).expression as AssignmentExpression; - - statement = separate - ? b` - ${injected.map(name => b`let ${name};`)} - ${statement}` - : b` - let ${left} = ${right}`; - } - } else { // TODO do not add label if it's not referenced + if (!d.declaration) { // TODO do not add label if it's not referenced statement = b`$: { ${statement} }`; } @@ -119,6 +98,8 @@ export default function ssr( ${reactive_store_values} + ${injected.map(name => b`let ${name};`)} + ${reactive_declarations} $$rendered = ${literal}; @@ -129,6 +110,8 @@ export default function ssr( : b` ${reactive_store_values} + ${injected.map(name => b`let ${name};`)} + ${reactive_declarations} return ${literal};`; diff --git a/src/compiler/compile/utils/string_to_member_expression.ts b/src/compiler/compile/utils/string_to_member_expression.ts new file mode 100644 index 0000000000..553c1371bd --- /dev/null +++ b/src/compiler/compile/utils/string_to_member_expression.ts @@ -0,0 +1,17 @@ +import { MemberExpression, Identifier } from "estree"; + +export function string_to_member_expression(name: string) { + const parts = name.split("."); + let node: MemberExpression | Identifier = { + type: "Identifier", + name: parts[0], + }; + for (let i = 1; i < parts.length; i++) { + node = { + type: "MemberExpression", + object: node, + property: { type: "Identifier", name: parts[i] }, + } as MemberExpression; + } + return node; +} diff --git a/src/runtime/internal/lifecycle.ts b/src/runtime/internal/lifecycle.ts index 34d52672a2..304e8d6a68 100644 --- a/src/runtime/internal/lifecycle.ts +++ b/src/runtime/internal/lifecycle.ts @@ -27,7 +27,9 @@ export function onDestroy(fn) { get_current_component().$$.on_destroy.push(fn); } -export function createEventDispatcher() { +export function createEventDispatcher< + EventMap extends {} = any +>(): >(type: EventKey, detail?: EventMap[EventKey]) => void { const component = get_current_component(); return (type: string, detail?: any) => { @@ -61,4 +63,4 @@ export function bubble(component, event) { if (callbacks) { callbacks.slice().forEach(fn => fn(event)); } -} \ No newline at end of file +} diff --git a/test/css/samples/empty-class/_config.js b/test/css/samples/empty-class/_config.js index cc766d9478..064de4a564 100644 --- a/test/css/samples/empty-class/_config.js +++ b/test/css/samples/empty-class/_config.js @@ -2,7 +2,7 @@ export default { warnings: [{ filename: "SvelteComponent.svelte", code: `css-unused-selector`, - message: "Unused CSS selector", + message: 'Unused CSS selector ".x"', start: { line: 4, column: 1, diff --git a/test/css/samples/global-with-unused-descendant/_config.js b/test/css/samples/global-with-unused-descendant/_config.js index 3ed2dd2728..69a69ad650 100644 --- a/test/css/samples/global-with-unused-descendant/_config.js +++ b/test/css/samples/global-with-unused-descendant/_config.js @@ -13,7 +13,7 @@ export default { 3: color: red; 4: } `, - message: 'Unused CSS selector', + message: 'Unused CSS selector ":global(.foo) .bar"', pos: 9, start: { character: 9, diff --git a/test/css/samples/omit-scoping-attribute-descendant/_config.js b/test/css/samples/omit-scoping-attribute-descendant/_config.js index a4aaec7c19..25e2a28ebb 100644 --- a/test/css/samples/omit-scoping-attribute-descendant/_config.js +++ b/test/css/samples/omit-scoping-attribute-descendant/_config.js @@ -1,7 +1,7 @@ export default { warnings: [{ code: `css-unused-selector`, - message: 'Unused CSS selector', + message: 'Unused CSS selector "div > p"', start: { line: 8, column: 1, diff --git a/test/css/samples/unused-selector-leading/_config.js b/test/css/samples/unused-selector-leading/_config.js index 20310a5847..2516567f59 100644 --- a/test/css/samples/unused-selector-leading/_config.js +++ b/test/css/samples/unused-selector-leading/_config.js @@ -3,7 +3,7 @@ export default { { filename: "SvelteComponent.svelte", code: `css-unused-selector`, - message: "Unused CSS selector", + message: 'Unused CSS selector ".foo"', start: { line: 4, column: 1, @@ -27,7 +27,7 @@ export default { { filename: "SvelteComponent.svelte", code: `css-unused-selector`, - message: "Unused CSS selector", + message: 'Unused CSS selector ".baz"', start: { line: 4, column: 13, diff --git a/test/css/samples/unused-selector-string-concat/_config.js b/test/css/samples/unused-selector-string-concat/_config.js index 81318fd3ac..ea8f85754e 100644 --- a/test/css/samples/unused-selector-string-concat/_config.js +++ b/test/css/samples/unused-selector-string-concat/_config.js @@ -2,7 +2,7 @@ export default { warnings: [ { code: 'css-unused-selector', - message: 'Unused CSS selector', + message: 'Unused CSS selector ".fooaa"', frame: ` 9: `, - message: 'Unused CSS selector', + message: 'Unused CSS selector ".unused"', pos: 198, start: { character: 198, diff --git a/test/css/samples/unused-selector-ternary-nested/_config.js b/test/css/samples/unused-selector-ternary-nested/_config.js index afee5ac822..936ac639e5 100644 --- a/test/css/samples/unused-selector-ternary-nested/_config.js +++ b/test/css/samples/unused-selector-ternary-nested/_config.js @@ -2,7 +2,7 @@ export default { warnings: [ { code: 'css-unused-selector', - message: 'Unused CSS selector', + message: 'Unused CSS selector ".hover.unused"', frame: ` 13: .thing.active {color: blue;} 14: .hover { color: blue; } @@ -16,7 +16,7 @@ export default { }, { code: 'css-unused-selector', - message: 'Unused CSS selector', + message: 'Unused CSS selector ".unused"', frame: ` 15: .hover.unused { color: blue; } 16: diff --git a/test/css/samples/unused-selector-ternary/_config.js b/test/css/samples/unused-selector-ternary/_config.js index 4ff2713108..4eb12f3fe2 100644 --- a/test/css/samples/unused-selector-ternary/_config.js +++ b/test/css/samples/unused-selector-ternary/_config.js @@ -6,7 +6,7 @@ export default { warnings: [{ filename: "SvelteComponent.svelte", code: `css-unused-selector`, - message: "Unused CSS selector", + message: 'Unused CSS selector ".maybeactive"', start: { line: 16, column: 1, diff --git a/test/css/samples/unused-selector/_config.js b/test/css/samples/unused-selector/_config.js index 49fb3a27e7..790eb9e875 100644 --- a/test/css/samples/unused-selector/_config.js +++ b/test/css/samples/unused-selector/_config.js @@ -2,7 +2,7 @@ export default { warnings: [{ filename: "SvelteComponent.svelte", code: `css-unused-selector`, - message: "Unused CSS selector", + message: 'Unused CSS selector ".bar"', start: { line: 8, column: 1, diff --git a/test/runtime/samples/component-namespace/Tooltip.svelte b/test/runtime/samples/component-namespace/Tooltip.svelte new file mode 100644 index 0000000000..9b44ef57a8 --- /dev/null +++ b/test/runtime/samples/component-namespace/Tooltip.svelte @@ -0,0 +1 @@ +

i am a widget

\ No newline at end of file diff --git a/test/runtime/samples/component-namespace/Widget.svelte b/test/runtime/samples/component-namespace/Widget.svelte new file mode 100644 index 0000000000..b6aa45c6a8 --- /dev/null +++ b/test/runtime/samples/component-namespace/Widget.svelte @@ -0,0 +1,5 @@ + \ No newline at end of file diff --git a/test/runtime/samples/component-namespace/_config.js b/test/runtime/samples/component-namespace/_config.js new file mode 100644 index 0000000000..5b96ac8df0 --- /dev/null +++ b/test/runtime/samples/component-namespace/_config.js @@ -0,0 +1,3 @@ +export default { + html: '

i am a widget

' +}; diff --git a/test/runtime/samples/component-namespace/main.svelte b/test/runtime/samples/component-namespace/main.svelte new file mode 100644 index 0000000000..3f53b2b1c8 --- /dev/null +++ b/test/runtime/samples/component-namespace/main.svelte @@ -0,0 +1,8 @@ + + +{#each widgets as LazyWidget} + +{/each} \ No newline at end of file diff --git a/test/runtime/samples/reactive-value-assign-property/_config.js b/test/runtime/samples/reactive-value-assign-property/_config.js new file mode 100644 index 0000000000..7b6c1d2965 --- /dev/null +++ b/test/runtime/samples/reactive-value-assign-property/_config.js @@ -0,0 +1,5 @@ +export default { + html: ` +

Hello world!

+ `, +}; diff --git a/test/runtime/samples/reactive-value-assign-property/main.svelte b/test/runtime/samples/reactive-value-assign-property/main.svelte new file mode 100644 index 0000000000..58e0fdb03c --- /dev/null +++ b/test/runtime/samples/reactive-value-assign-property/main.svelte @@ -0,0 +1,6 @@ + + +

Hello {user.name}!

\ No newline at end of file