From eb6fb66f192effbae4227e61787bef0b6bdcc213 Mon Sep 17 00:00:00 2001 From: Conduitry Date: Thu, 3 Feb 2022 12:59:55 -0500 Subject: [PATCH 01/57] -> v3.46.4 --- CHANGELOG.md | 9 +++++---- package-lock.json | 4 ++-- package.json | 2 +- 3 files changed, 8 insertions(+), 7 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index f079922149..1dd186cfcb 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,12 +1,13 @@ # Svelte changelog -## Unreleased +## 3.46.4 * Avoid `maximum call stack size exceeded` errors on large components ([#4694](https://github.com/sveltejs/svelte/issues/4694)) -* Keep space in `
` tags or when `preserveWhitespace` is `true`([#6437](https://github.com/sveltejs/svelte/issues/6437), [#4731](https://github.com/sveltejs/svelte/issues/4731))
-* Better error message when trying to use style directives on inline components ([#7177](https://github.com/sveltejs/svelte/issues/7177))
+* Preserve leading space with `preserveWhitespace: true` ([#4731](https://github.com/sveltejs/svelte/issues/4731))
+* Preserve leading space in `
` tags ([#6437](https://github.com/sveltejs/svelte/issues/6437))
+* Improve error message when trying to use `style:` directives on inline components ([#7177](https://github.com/sveltejs/svelte/issues/7177))
 * Add `FormData` as a known global ([#7199](https://github.com/sveltejs/svelte/pull/7199))
-* Mark `css`/`instance`/`module` Ast properties as optional ([#7204](https://github.com/sveltejs/svelte/pull/7204))
+* Mark `css`/`instance`/`module` AST properties as optional in types ([#7204](https://github.com/sveltejs/svelte/pull/7204))
 
 ## 3.46.3
 
diff --git a/package-lock.json b/package-lock.json
index 7681615287..e9b672d689 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -1,11 +1,11 @@
 {
   "name": "svelte",
-  "version": "3.46.3",
+  "version": "3.46.4",
   "lockfileVersion": 2,
   "requires": true,
   "packages": {
     "": {
-      "version": "3.46.3",
+      "version": "3.46.4",
       "license": "MIT",
       "devDependencies": {
         "@ampproject/remapping": "^0.3.0",
diff --git a/package.json b/package.json
index e20d33f81d..2ca79c5fa8 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
 {
   "name": "svelte",
-  "version": "3.46.3",
+  "version": "3.46.4",
   "description": "Cybernetically enhanced web apps",
   "module": "index.mjs",
   "main": "index",

From a4a1c3c27293fe4993e81eaf24cf6fa030a9aacb Mon Sep 17 00:00:00 2001
From: Geoff Rich <4992896+geoffrich@users.noreply.github.com>
Date: Wed, 9 Feb 2022 09:12:07 -0800
Subject: [PATCH 02/57] [docs] set language on FAQ code blocks (#7237)

---
 site/content/faq/450-how-do-i-document-my-components.md | 2 +-
 site/content/faq/500-what-about-typescript-support.md   | 4 ++--
 2 files changed, 3 insertions(+), 3 deletions(-)

diff --git a/site/content/faq/450-how-do-i-document-my-components.md b/site/content/faq/450-how-do-i-document-my-components.md
index 78177abd54..4574d02961 100644
--- a/site/content/faq/450-how-do-i-document-my-components.md
+++ b/site/content/faq/450-how-do-i-document-my-components.md
@@ -4,7 +4,7 @@ question: How do I document my components?
 
 In editors which use the Svelte Language Server you can document Components, functions and exports using specially formatted comments.
 
-````svelte
+````sv
 
+
 
-{$q}
+{$q2}
diff --git a/test/validator/samples/unreferenced-variables/warnings.json b/test/validator/samples/unreferenced-variables/warnings.json
index dfac58ebdb..4097ba023b 100644
--- a/test/validator/samples/unreferenced-variables/warnings.json
+++ b/test/validator/samples/unreferenced-variables/warnings.json
@@ -2,76 +2,76 @@
 	{
 		"code": "unused-export-let",
 		"end": {
-			"character": 103,
-			"column": 12,
-			"line": 8
+			"character": 519,
+			"column": 13,
+			"line": 31
 		},
-		"message": "Component has unused export property 'd'. If it is for external reference only, please consider using `export const d`",
-		"pos": 102,
+		"message": "Component has unused export property 'd2'. If it is for external reference only, please consider using `export const d2`",
+		"pos": 517,
 		"start": {
-			"character": 102,
+			"character": 517,
 			"column": 11,
-			"line": 8
+			"line": 31
 		}
 	},
 	{
 		"code": "unused-export-let",
 		"end": {
-			"character": 106,
-			"column": 15,
-			"line": 8
+			"character": 523,
+			"column": 17,
+			"line": 31
 		},
-		"message": "Component has unused export property 'e'. If it is for external reference only, please consider using `export const e`",
-		"pos": 105,
+		"message": "Component has unused export property 'e2'. If it is for external reference only, please consider using `export const e2`",
+		"pos": 521,
 		"start": {
-			"character": 105,
-			"column": 14,
-			"line": 8
+			"character": 521,
+			"column": 15,
+			"line": 31
 		}
 	},
 	{
 		"code": "unused-export-let",
 		"end": {
-			"character": 130,
-			"column": 18,
-			"line": 9
+			"character": 549,
+			"column": 19,
+			"line": 32
 		},
-		"message": "Component has unused export property 'g'. If it is for external reference only, please consider using `export const g`",
-		"pos": 125,
+		"message": "Component has unused export property 'g2'. If it is for external reference only, please consider using `export const g2`",
+		"pos": 543,
 		"start": {
-			"character": 125,
+			"character": 543,
 			"column": 13,
-			"line": 9
+			"line": 32
 		}
 	},
 	{
 		"code": "unused-export-let",
 		"end": {
-			"character": 150,
-			"column": 18,
-			"line": 10
+			"character": 570,
+			"column": 19,
+			"line": 33
 		},
-		"message": "Component has unused export property 'h'. If it is for external reference only, please consider using `export const h`",
-		"pos": 145,
+		"message": "Component has unused export property 'h2'. If it is for external reference only, please consider using `export const h2`",
+		"pos": 564,
 		"start": {
-			"character": 145,
+			"character": 564,
 			"column": 13,
-			"line": 10
+			"line": 33
 		}
 	},
 	{
 		"code": "unused-export-let",
 		"end": {
-			"character": 199,
-			"column": 25,
-			"line": 12
+			"character": 621,
+			"column": 26,
+			"line": 35
 		},
-		"message": "Component has unused export property 'j'. If it is for external reference only, please consider using `export const j`",
-		"pos": 187,
+		"message": "Component has unused export property 'j2'. If it is for external reference only, please consider using `export const j2`",
+		"pos": 608,
 		"start": {
-			"character": 187,
+			"character": 608,
 			"column": 13,
-			"line": 12
+			"line": 35
 		}
 	}
 ]

From 2c629bcc0c9eec64ad55d1359e75815c6e12cf14 Mon Sep 17 00:00:00 2001
From: Ben McCann <322311+benmccann@users.noreply.github.com>
Date: Wed, 2 Mar 2022 16:57:26 -0800
Subject: [PATCH 17/57] Update CHANGELOG.md

---
 CHANGELOG.md | 1 +
 1 file changed, 1 insertion(+)

diff --git a/CHANGELOG.md b/CHANGELOG.md
index 5df6ab56f3..cd8f5933bf 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -4,6 +4,7 @@
 
 - Add `aria-description` to the list of allowed ARIA attributes ([#7301](https://github.com/sveltejs/svelte/issues/7301))
 - Add TypeScript interfaces for typing actions ([#6538](https://github.com/sveltejs/svelte/issues/6538))
+- Do not generate `unused-export-let` warning inside `"
->
\ No newline at end of file
+	foo="">\"
+	bar="">\"
+>
diff --git a/test/server-side-rendering/samples/attribute-escaped-quotes/main.svelte b/test/server-side-rendering/samples/attribute-escaped-quotes/main.svelte
index ed34f4d129..aeed0f20d1 100644
--- a/test/server-side-rendering/samples/attribute-escaped-quotes/main.svelte
+++ b/test/server-side-rendering/samples/attribute-escaped-quotes/main.svelte
@@ -1,5 +1,6 @@
 
 
-
\ No newline at end of file +
From 1632bca34e4803d6b0e0b0abd652ab5968181860 Mon Sep 17 00:00:00 2001 From: Simon H <5968653+dummdidumm@users.noreply.github.com> Date: Thu, 3 Mar 2022 12:24:04 +0100 Subject: [PATCH 21/57] Update changelog --- CHANGELOG.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 4f0304be85..ff085942d7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,7 +5,8 @@ * Add `aria-description` to the list of allowed ARIA attributes ([#7301](https://github.com/sveltejs/svelte/issues/7301)) * Add TypeScript interfaces for typing actions ([#6538](https://github.com/sveltejs/svelte/issues/6538)) * Do not generate `unused-export-let` warning inside ` ``` -...we can then import it from `App.svelte`... +...we can then import it in `App.svelte`... ```html -

+

+

+

diff --git a/test/runtime/samples/inline-style/_config.js b/test/runtime/samples/inline-style/_config.js index 3e984d4c69..192659346c 100644 --- a/test/runtime/samples/inline-style/_config.js +++ b/test/runtime/samples/inline-style/_config.js @@ -4,9 +4,9 @@ export default { `, test({ assert, component, target, window }) { - const p = target.querySelector('div'); + const div = target.querySelector('div'); - const styles = window.getComputedStyle(p); + const styles = window.getComputedStyle(div); assert.equal(styles.color, 'red'); } }; From 428023ea58fc373630ffa48bd6e49bbe454008f5 Mon Sep 17 00:00:00 2001 From: Conduitry Date: Wed, 30 Mar 2022 10:50:54 -0400 Subject: [PATCH 26/57] update changelog --- CHANGELOG.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index ff085942d7..93b077a331 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,11 +2,12 @@ ## Unreleased -* Add `aria-description` to the list of allowed ARIA attributes ([#7301](https://github.com/sveltejs/svelte/issues/7301)) * Add TypeScript interfaces for typing actions ([#6538](https://github.com/sveltejs/svelte/issues/6538)) * Do not generate `unused-export-let` warning inside ` diff --git a/test/runtime/samples/key-block-component-slot/_config.js b/test/runtime/samples/key-block-component-slot/_config.js new file mode 100644 index 0000000000..8fbbeaec64 --- /dev/null +++ b/test/runtime/samples/key-block-component-slot/_config.js @@ -0,0 +1,18 @@ +const logs = []; + +export default { + html: '', + props: { + logs + }, + async test({ assert, component, target, raf }) { + assert.deepEqual(logs, ['mount']); + + const button = target.querySelector('button'); + + const click = new window.MouseEvent('click'); + await button.dispatchEvent(click); + + assert.deepEqual(logs, ['mount', 'unmount', 'mount']); + } +}; diff --git a/test/runtime/samples/key-block-component-slot/main.svelte b/test/runtime/samples/key-block-component-slot/main.svelte new file mode 100644 index 0000000000..77fde32d9c --- /dev/null +++ b/test/runtime/samples/key-block-component-slot/main.svelte @@ -0,0 +1,17 @@ + + + + {#key reset} + + {/key} + + + \ No newline at end of file From 54197c5a1f02b01291e6ee062fb51d377bcd99a6 Mon Sep 17 00:00:00 2001 From: Tan Li Hau Date: Fri, 8 Apr 2022 22:26:32 +0800 Subject: [PATCH 43/57] Update CHANGELOG.md --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 46b871cfb4..da5eb22c40 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,7 @@ ## Unreleased * Add `Symbol` as a known global ([#7419](https://github.com/sveltejs/svelte/pull/7419)) +* Fix `{#key}` block not reactive when the key variable is not being used ([#7422](https://github.com/sveltejs/svelte/pull/7422)) ## 3.46.6 From e0d93254fd9db02cf7fd1b9f35238bdcdbe04a81 Mon Sep 17 00:00:00 2001 From: Yuichiro Yamashita Date: Fri, 8 Apr 2022 23:54:55 +0900 Subject: [PATCH 44/57] [feature] Dynamic elements implementation (#6898) Closes #2324 Co-authored-by: Alfred Ringstad Co-authored-by: Simon Holthausen Co-authored-by: tanhauhau --- site/content/docs/02-template-syntax.md | 22 +++ .../03-svelte-element/app-a/App.svelte | 18 ++ .../03-svelte-element/app-b/App.svelte | 12 ++ .../03-svelte-element/text.md | 23 +++ .../app-a/App.svelte | 0 .../app-b/App.svelte | 0 .../text.md | 0 .../app-a/App.svelte | 0 .../app-b/App.svelte | 0 .../text.md | 0 .../app-a/App.svelte | 0 .../app-b/App.svelte | 0 .../text.md | 0 .../app-a/App.svelte | 0 .../app-b/App.svelte | 0 .../text.md | 0 .../app-a/App.svelte | 0 .../app-a/Todo.svelte | 0 .../app-a/flash.js | 0 .../app-b/App.svelte | 0 .../app-b/Todo.svelte | 0 .../app-b/flash.js | 0 .../text.md | 0 .../app-a/App.svelte | 0 .../app-a/Box.svelte | 0 .../app-b/App.svelte | 0 .../app-b/Box.svelte | 0 .../text.md | 0 src/compiler/compile/compiler_errors.ts | 4 + src/compiler/compile/nodes/Element.ts | 18 ++ src/compiler/compile/render_dom/Block.ts | 9 + .../render_dom/wrappers/Element/index.ts | 168 ++++++++++++++++-- .../compile/render_ssr/handlers/Element.ts | 25 ++- src/compiler/parse/errors.ts | 8 + src/compiler/parse/state/tag.ts | 23 ++- src/runtime/internal/dev.ts | 6 + test/css/samples/dynamic-element/_config.js | 21 +++ test/css/samples/dynamic-element/expected.css | 1 + .../css/samples/dynamic-element/expected.html | 1 + test/css/samples/dynamic-element/input.svelte | 10 ++ test/js/samples/debug-ssr-foo/expected.js | 2 +- .../dynamic-element-string/input.svelte | 2 + .../dynamic-element-string/output.json | 50 ++++++ .../dynamic-element-variable/input.svelte | 2 + .../dynamic-element-variable/output.json | 80 +++++++++ .../error-svelte-selfdestructive/error.json | 2 +- .../dynamic-element-action-update/_config.js | 45 +++++ .../dynamic-element-action-update/main.svelte | 14 ++ .../dynamic-element-animation-2/_config.js | 105 +++++++++++ .../dynamic-element-animation-2/main.svelte | 26 +++ .../dynamic-element-animation/_config.js | 62 +++++++ .../dynamic-element-animation/main.svelte | 18 ++ .../dynamic-element-attribute/_config.js | 17 ++ .../dynamic-element-attribute/main.svelte | 5 + .../_config.js | 3 + .../main.svelte | 6 + .../dynamic-element-binding-this/_config.js | 8 + .../dynamic-element-binding-this/main.svelte | 6 + .../dynamic-element-change-tag/_config.js | 17 ++ .../dynamic-element-change-tag/main.svelte | 5 + .../dynamic-element-empty-tag/_config.js | 3 + .../dynamic-element-empty-tag/main.svelte | 5 + .../dynamic-element-event-handler1/_config.js | 21 +++ .../main.svelte | 6 + .../dynamic-element-event-handler2/_config.js | 23 +++ .../main.svelte | 6 + .../dynamic-element-expression/_config.js | 3 + .../dynamic-element-expression/main.svelte | 1 + .../dynamic-element-invalid-this/_config.js | 9 + .../dynamic-element-invalid-this/main.svelte | 5 + .../dynamic-element-null-tag/_config.js | 3 + .../dynamic-element-null-tag/main.svelte | 5 + .../dynamic-element-pass-props/_config.js | 16 ++ .../dynamic-element-pass-props/main.svelte | 6 + .../samples/dynamic-element-slot/Foo.svelte | 7 + .../samples/dynamic-element-slot/_config.js | 29 +++ .../samples/dynamic-element-slot/main.svelte | 10 ++ .../samples/dynamic-element-store/_config.js | 3 + .../samples/dynamic-element-store/main.svelte | 6 + .../samples/dynamic-element-string/_config.js | 3 + .../dynamic-element-string/main.svelte | 1 + .../_config.js | 21 +++ .../main.svelte | 5 + .../dynamic-element-transition/_config.js | 17 ++ .../dynamic-element-transition/main.svelte | 17 ++ .../dynamic-element-undefined-tag/_config.js | 19 ++ .../dynamic-element-undefined-tag/main.svelte | 5 + .../dynamic-element-variable/_config.js | 20 +++ .../dynamic-element-variable/main.svelte | 6 + .../dynamic-element-string/_expected.html | 1 + .../dynamic-element-string/main.svelte | 1 + .../dynamic-element-variable/_expected.html | 2 + .../dynamic-element-variable/main.svelte | 7 + .../dynamic-element-invalid-tag/errors.json | 17 ++ .../dynamic-element-invalid-tag/input.svelte | 3 + .../dynamic-element-missing-tag/errors.json | 15 ++ .../dynamic-element-missing-tag/input.svelte | 3 + .../samples/dynamic-element-this/errors.json | 15 ++ .../samples/dynamic-element-this/input.svelte | 3 + 99 files changed, 1170 insertions(+), 22 deletions(-) create mode 100644 site/content/tutorial/16-special-elements/03-svelte-element/app-a/App.svelte create mode 100644 site/content/tutorial/16-special-elements/03-svelte-element/app-b/App.svelte create mode 100644 site/content/tutorial/16-special-elements/03-svelte-element/text.md rename site/content/tutorial/16-special-elements/{03-svelte-window => 04-svelte-window}/app-a/App.svelte (100%) rename site/content/tutorial/16-special-elements/{03-svelte-window => 04-svelte-window}/app-b/App.svelte (100%) rename site/content/tutorial/16-special-elements/{03-svelte-window => 04-svelte-window}/text.md (100%) rename site/content/tutorial/16-special-elements/{04-svelte-window-bindings => 05-svelte-window-bindings}/app-a/App.svelte (100%) rename site/content/tutorial/16-special-elements/{04-svelte-window-bindings => 05-svelte-window-bindings}/app-b/App.svelte (100%) rename site/content/tutorial/16-special-elements/{04-svelte-window-bindings => 05-svelte-window-bindings}/text.md (100%) rename site/content/tutorial/16-special-elements/{05-svelte-body => 06-svelte-body}/app-a/App.svelte (100%) rename site/content/tutorial/16-special-elements/{05-svelte-body => 06-svelte-body}/app-b/App.svelte (100%) rename site/content/tutorial/16-special-elements/{05-svelte-body => 06-svelte-body}/text.md (100%) rename site/content/tutorial/16-special-elements/{06-svelte-head => 07-svelte-head}/app-a/App.svelte (100%) rename site/content/tutorial/16-special-elements/{06-svelte-head => 07-svelte-head}/app-b/App.svelte (100%) rename site/content/tutorial/16-special-elements/{06-svelte-head => 07-svelte-head}/text.md (100%) rename site/content/tutorial/16-special-elements/{07-svelte-options => 08-svelte-options}/app-a/App.svelte (100%) rename site/content/tutorial/16-special-elements/{07-svelte-options => 08-svelte-options}/app-a/Todo.svelte (100%) rename site/content/tutorial/16-special-elements/{07-svelte-options => 08-svelte-options}/app-a/flash.js (100%) rename site/content/tutorial/16-special-elements/{07-svelte-options => 08-svelte-options}/app-b/App.svelte (100%) rename site/content/tutorial/16-special-elements/{07-svelte-options => 08-svelte-options}/app-b/Todo.svelte (100%) rename site/content/tutorial/16-special-elements/{07-svelte-options => 08-svelte-options}/app-b/flash.js (100%) rename site/content/tutorial/16-special-elements/{07-svelte-options => 08-svelte-options}/text.md (100%) rename site/content/tutorial/16-special-elements/{08-svelte-fragment => 09-svelte-fragment}/app-a/App.svelte (100%) rename site/content/tutorial/16-special-elements/{08-svelte-fragment => 09-svelte-fragment}/app-a/Box.svelte (100%) rename site/content/tutorial/16-special-elements/{08-svelte-fragment => 09-svelte-fragment}/app-b/App.svelte (100%) rename site/content/tutorial/16-special-elements/{08-svelte-fragment => 09-svelte-fragment}/app-b/Box.svelte (100%) rename site/content/tutorial/16-special-elements/{08-svelte-fragment => 09-svelte-fragment}/text.md (100%) create mode 100644 test/css/samples/dynamic-element/_config.js create mode 100644 test/css/samples/dynamic-element/expected.css create mode 100644 test/css/samples/dynamic-element/expected.html create mode 100644 test/css/samples/dynamic-element/input.svelte create mode 100644 test/parser/samples/dynamic-element-string/input.svelte create mode 100644 test/parser/samples/dynamic-element-string/output.json create mode 100644 test/parser/samples/dynamic-element-variable/input.svelte create mode 100644 test/parser/samples/dynamic-element-variable/output.json create mode 100644 test/runtime/samples/dynamic-element-action-update/_config.js create mode 100644 test/runtime/samples/dynamic-element-action-update/main.svelte create mode 100644 test/runtime/samples/dynamic-element-animation-2/_config.js create mode 100644 test/runtime/samples/dynamic-element-animation-2/main.svelte create mode 100644 test/runtime/samples/dynamic-element-animation/_config.js create mode 100644 test/runtime/samples/dynamic-element-animation/main.svelte create mode 100644 test/runtime/samples/dynamic-element-attribute/_config.js create mode 100644 test/runtime/samples/dynamic-element-attribute/main.svelte create mode 100644 test/runtime/samples/dynamic-element-binding-invalid/_config.js create mode 100644 test/runtime/samples/dynamic-element-binding-invalid/main.svelte create mode 100644 test/runtime/samples/dynamic-element-binding-this/_config.js create mode 100644 test/runtime/samples/dynamic-element-binding-this/main.svelte create mode 100644 test/runtime/samples/dynamic-element-change-tag/_config.js create mode 100644 test/runtime/samples/dynamic-element-change-tag/main.svelte create mode 100644 test/runtime/samples/dynamic-element-empty-tag/_config.js create mode 100644 test/runtime/samples/dynamic-element-empty-tag/main.svelte create mode 100644 test/runtime/samples/dynamic-element-event-handler1/_config.js create mode 100644 test/runtime/samples/dynamic-element-event-handler1/main.svelte create mode 100644 test/runtime/samples/dynamic-element-event-handler2/_config.js create mode 100644 test/runtime/samples/dynamic-element-event-handler2/main.svelte create mode 100644 test/runtime/samples/dynamic-element-expression/_config.js create mode 100644 test/runtime/samples/dynamic-element-expression/main.svelte create mode 100644 test/runtime/samples/dynamic-element-invalid-this/_config.js create mode 100644 test/runtime/samples/dynamic-element-invalid-this/main.svelte create mode 100644 test/runtime/samples/dynamic-element-null-tag/_config.js create mode 100644 test/runtime/samples/dynamic-element-null-tag/main.svelte create mode 100644 test/runtime/samples/dynamic-element-pass-props/_config.js create mode 100644 test/runtime/samples/dynamic-element-pass-props/main.svelte create mode 100644 test/runtime/samples/dynamic-element-slot/Foo.svelte create mode 100644 test/runtime/samples/dynamic-element-slot/_config.js create mode 100644 test/runtime/samples/dynamic-element-slot/main.svelte create mode 100644 test/runtime/samples/dynamic-element-store/_config.js create mode 100644 test/runtime/samples/dynamic-element-store/main.svelte create mode 100644 test/runtime/samples/dynamic-element-string/_config.js create mode 100644 test/runtime/samples/dynamic-element-string/main.svelte create mode 100644 test/runtime/samples/dynamic-element-template-literals/_config.js create mode 100644 test/runtime/samples/dynamic-element-template-literals/main.svelte create mode 100644 test/runtime/samples/dynamic-element-transition/_config.js create mode 100644 test/runtime/samples/dynamic-element-transition/main.svelte create mode 100644 test/runtime/samples/dynamic-element-undefined-tag/_config.js create mode 100644 test/runtime/samples/dynamic-element-undefined-tag/main.svelte create mode 100644 test/runtime/samples/dynamic-element-variable/_config.js create mode 100644 test/runtime/samples/dynamic-element-variable/main.svelte create mode 100644 test/server-side-rendering/samples/dynamic-element-string/_expected.html create mode 100644 test/server-side-rendering/samples/dynamic-element-string/main.svelte create mode 100644 test/server-side-rendering/samples/dynamic-element-variable/_expected.html create mode 100644 test/server-side-rendering/samples/dynamic-element-variable/main.svelte create mode 100644 test/validator/samples/dynamic-element-invalid-tag/errors.json create mode 100644 test/validator/samples/dynamic-element-invalid-tag/input.svelte create mode 100644 test/validator/samples/dynamic-element-missing-tag/errors.json create mode 100644 test/validator/samples/dynamic-element-missing-tag/input.svelte create mode 100644 test/validator/samples/dynamic-element-this/errors.json create mode 100644 test/validator/samples/dynamic-element-this/input.svelte diff --git a/site/content/docs/02-template-syntax.md b/site/content/docs/02-template-syntax.md index e42ef23b71..00070e563c 100644 --- a/site/content/docs/02-template-syntax.md +++ b/site/content/docs/02-template-syntax.md @@ -1627,6 +1627,28 @@ If `this` is falsy, no component is rendered. ``` +### `` + +```sv + +``` + +--- + +The `` element lets you render an element of a dynamically specified type. This is useful for example when rich text content from a CMS. If the tag is changed, the children will be preserved unless there's a transition attached to the element. Any properties and event listeners present will be applied to the element. + +The only supported binding is `bind:this`, since the element type specific bindings that Svelte does at build time (e.g. `bind:value` for input elements) does not work with a dynamic tag type. + +If `this` has a nullish value, a warning will be logged in development mode. + +```sv + + +Foo +``` ### `` diff --git a/site/content/tutorial/16-special-elements/03-svelte-element/app-a/App.svelte b/site/content/tutorial/16-special-elements/03-svelte-element/app-a/App.svelte new file mode 100644 index 0000000000..0718efd949 --- /dev/null +++ b/site/content/tutorial/16-special-elements/03-svelte-element/app-a/App.svelte @@ -0,0 +1,18 @@ + + + + +{#if selected === 'h1'} +

I'm a h1 tag

+{:else if selected === 'h3'} +

I'm a h3 tag

+{:else if selected === 'p'} +

I'm a p tag

+{/if} diff --git a/site/content/tutorial/16-special-elements/03-svelte-element/app-b/App.svelte b/site/content/tutorial/16-special-elements/03-svelte-element/app-b/App.svelte new file mode 100644 index 0000000000..068f7b3c4f --- /dev/null +++ b/site/content/tutorial/16-special-elements/03-svelte-element/app-b/App.svelte @@ -0,0 +1,12 @@ + + + + +I'm a {selected} tag diff --git a/site/content/tutorial/16-special-elements/03-svelte-element/text.md b/site/content/tutorial/16-special-elements/03-svelte-element/text.md new file mode 100644 index 0000000000..ace344db42 --- /dev/null +++ b/site/content/tutorial/16-special-elements/03-svelte-element/text.md @@ -0,0 +1,23 @@ +--- +title: +--- + +Sometimes we don't know in advance what kind of DOM element to render. `` comes in handy here. Instead of a sequence of `if` blocks... + +```html +{#if selected === 'h1'} +

I'm a h1 tag

+{:else if selected === 'h3'} +

I'm a h3 tag

+{:else if selected === 'p'} +

I'm a p tag

+{/if} +``` + +...we can have a single dynamic component: + +```html +I'm a {selected} tag +``` + +The `this` value can be any string, or a falsy value — if it's falsy, no element is rendered. \ No newline at end of file diff --git a/site/content/tutorial/16-special-elements/03-svelte-window/app-a/App.svelte b/site/content/tutorial/16-special-elements/04-svelte-window/app-a/App.svelte similarity index 100% rename from site/content/tutorial/16-special-elements/03-svelte-window/app-a/App.svelte rename to site/content/tutorial/16-special-elements/04-svelte-window/app-a/App.svelte diff --git a/site/content/tutorial/16-special-elements/03-svelte-window/app-b/App.svelte b/site/content/tutorial/16-special-elements/04-svelte-window/app-b/App.svelte similarity index 100% rename from site/content/tutorial/16-special-elements/03-svelte-window/app-b/App.svelte rename to site/content/tutorial/16-special-elements/04-svelte-window/app-b/App.svelte diff --git a/site/content/tutorial/16-special-elements/03-svelte-window/text.md b/site/content/tutorial/16-special-elements/04-svelte-window/text.md similarity index 100% rename from site/content/tutorial/16-special-elements/03-svelte-window/text.md rename to site/content/tutorial/16-special-elements/04-svelte-window/text.md diff --git a/site/content/tutorial/16-special-elements/04-svelte-window-bindings/app-a/App.svelte b/site/content/tutorial/16-special-elements/05-svelte-window-bindings/app-a/App.svelte similarity index 100% rename from site/content/tutorial/16-special-elements/04-svelte-window-bindings/app-a/App.svelte rename to site/content/tutorial/16-special-elements/05-svelte-window-bindings/app-a/App.svelte diff --git a/site/content/tutorial/16-special-elements/04-svelte-window-bindings/app-b/App.svelte b/site/content/tutorial/16-special-elements/05-svelte-window-bindings/app-b/App.svelte similarity index 100% rename from site/content/tutorial/16-special-elements/04-svelte-window-bindings/app-b/App.svelte rename to site/content/tutorial/16-special-elements/05-svelte-window-bindings/app-b/App.svelte diff --git a/site/content/tutorial/16-special-elements/04-svelte-window-bindings/text.md b/site/content/tutorial/16-special-elements/05-svelte-window-bindings/text.md similarity index 100% rename from site/content/tutorial/16-special-elements/04-svelte-window-bindings/text.md rename to site/content/tutorial/16-special-elements/05-svelte-window-bindings/text.md diff --git a/site/content/tutorial/16-special-elements/05-svelte-body/app-a/App.svelte b/site/content/tutorial/16-special-elements/06-svelte-body/app-a/App.svelte similarity index 100% rename from site/content/tutorial/16-special-elements/05-svelte-body/app-a/App.svelte rename to site/content/tutorial/16-special-elements/06-svelte-body/app-a/App.svelte diff --git a/site/content/tutorial/16-special-elements/05-svelte-body/app-b/App.svelte b/site/content/tutorial/16-special-elements/06-svelte-body/app-b/App.svelte similarity index 100% rename from site/content/tutorial/16-special-elements/05-svelte-body/app-b/App.svelte rename to site/content/tutorial/16-special-elements/06-svelte-body/app-b/App.svelte diff --git a/site/content/tutorial/16-special-elements/05-svelte-body/text.md b/site/content/tutorial/16-special-elements/06-svelte-body/text.md similarity index 100% rename from site/content/tutorial/16-special-elements/05-svelte-body/text.md rename to site/content/tutorial/16-special-elements/06-svelte-body/text.md diff --git a/site/content/tutorial/16-special-elements/06-svelte-head/app-a/App.svelte b/site/content/tutorial/16-special-elements/07-svelte-head/app-a/App.svelte similarity index 100% rename from site/content/tutorial/16-special-elements/06-svelte-head/app-a/App.svelte rename to site/content/tutorial/16-special-elements/07-svelte-head/app-a/App.svelte diff --git a/site/content/tutorial/16-special-elements/06-svelte-head/app-b/App.svelte b/site/content/tutorial/16-special-elements/07-svelte-head/app-b/App.svelte similarity index 100% rename from site/content/tutorial/16-special-elements/06-svelte-head/app-b/App.svelte rename to site/content/tutorial/16-special-elements/07-svelte-head/app-b/App.svelte diff --git a/site/content/tutorial/16-special-elements/06-svelte-head/text.md b/site/content/tutorial/16-special-elements/07-svelte-head/text.md similarity index 100% rename from site/content/tutorial/16-special-elements/06-svelte-head/text.md rename to site/content/tutorial/16-special-elements/07-svelte-head/text.md diff --git a/site/content/tutorial/16-special-elements/07-svelte-options/app-a/App.svelte b/site/content/tutorial/16-special-elements/08-svelte-options/app-a/App.svelte similarity index 100% rename from site/content/tutorial/16-special-elements/07-svelte-options/app-a/App.svelte rename to site/content/tutorial/16-special-elements/08-svelte-options/app-a/App.svelte diff --git a/site/content/tutorial/16-special-elements/07-svelte-options/app-a/Todo.svelte b/site/content/tutorial/16-special-elements/08-svelte-options/app-a/Todo.svelte similarity index 100% rename from site/content/tutorial/16-special-elements/07-svelte-options/app-a/Todo.svelte rename to site/content/tutorial/16-special-elements/08-svelte-options/app-a/Todo.svelte diff --git a/site/content/tutorial/16-special-elements/07-svelte-options/app-a/flash.js b/site/content/tutorial/16-special-elements/08-svelte-options/app-a/flash.js similarity index 100% rename from site/content/tutorial/16-special-elements/07-svelte-options/app-a/flash.js rename to site/content/tutorial/16-special-elements/08-svelte-options/app-a/flash.js diff --git a/site/content/tutorial/16-special-elements/07-svelte-options/app-b/App.svelte b/site/content/tutorial/16-special-elements/08-svelte-options/app-b/App.svelte similarity index 100% rename from site/content/tutorial/16-special-elements/07-svelte-options/app-b/App.svelte rename to site/content/tutorial/16-special-elements/08-svelte-options/app-b/App.svelte diff --git a/site/content/tutorial/16-special-elements/07-svelte-options/app-b/Todo.svelte b/site/content/tutorial/16-special-elements/08-svelte-options/app-b/Todo.svelte similarity index 100% rename from site/content/tutorial/16-special-elements/07-svelte-options/app-b/Todo.svelte rename to site/content/tutorial/16-special-elements/08-svelte-options/app-b/Todo.svelte diff --git a/site/content/tutorial/16-special-elements/07-svelte-options/app-b/flash.js b/site/content/tutorial/16-special-elements/08-svelte-options/app-b/flash.js similarity index 100% rename from site/content/tutorial/16-special-elements/07-svelte-options/app-b/flash.js rename to site/content/tutorial/16-special-elements/08-svelte-options/app-b/flash.js diff --git a/site/content/tutorial/16-special-elements/07-svelte-options/text.md b/site/content/tutorial/16-special-elements/08-svelte-options/text.md similarity index 100% rename from site/content/tutorial/16-special-elements/07-svelte-options/text.md rename to site/content/tutorial/16-special-elements/08-svelte-options/text.md diff --git a/site/content/tutorial/16-special-elements/08-svelte-fragment/app-a/App.svelte b/site/content/tutorial/16-special-elements/09-svelte-fragment/app-a/App.svelte similarity index 100% rename from site/content/tutorial/16-special-elements/08-svelte-fragment/app-a/App.svelte rename to site/content/tutorial/16-special-elements/09-svelte-fragment/app-a/App.svelte diff --git a/site/content/tutorial/16-special-elements/08-svelte-fragment/app-a/Box.svelte b/site/content/tutorial/16-special-elements/09-svelte-fragment/app-a/Box.svelte similarity index 100% rename from site/content/tutorial/16-special-elements/08-svelte-fragment/app-a/Box.svelte rename to site/content/tutorial/16-special-elements/09-svelte-fragment/app-a/Box.svelte diff --git a/site/content/tutorial/16-special-elements/08-svelte-fragment/app-b/App.svelte b/site/content/tutorial/16-special-elements/09-svelte-fragment/app-b/App.svelte similarity index 100% rename from site/content/tutorial/16-special-elements/08-svelte-fragment/app-b/App.svelte rename to site/content/tutorial/16-special-elements/09-svelte-fragment/app-b/App.svelte diff --git a/site/content/tutorial/16-special-elements/08-svelte-fragment/app-b/Box.svelte b/site/content/tutorial/16-special-elements/09-svelte-fragment/app-b/Box.svelte similarity index 100% rename from site/content/tutorial/16-special-elements/08-svelte-fragment/app-b/Box.svelte rename to site/content/tutorial/16-special-elements/09-svelte-fragment/app-b/Box.svelte diff --git a/site/content/tutorial/16-special-elements/08-svelte-fragment/text.md b/site/content/tutorial/16-special-elements/09-svelte-fragment/text.md similarity index 100% rename from site/content/tutorial/16-special-elements/08-svelte-fragment/text.md rename to site/content/tutorial/16-special-elements/09-svelte-fragment/text.md diff --git a/src/compiler/compile/compiler_errors.ts b/src/compiler/compile/compiler_errors.ts index df832373fc..a04780e375 100644 --- a/src/compiler/compile/compiler_errors.ts +++ b/src/compiler/compile/compiler_errors.ts @@ -246,6 +246,10 @@ export default { code: 'invalid-animation', message: 'An element that uses the animate directive must be the sole child of a keyed each block' }, + invalid_animation_dynamic_element: { + code: 'invalid-animation', + message: ' cannot have a animate directive' + }, invalid_directive_value: { code: 'invalid-directive-value', message: 'Can only bind to an identifier (e.g. `foo`) or a member expression (e.g. `foo.bar` or `foo[baz]`)' diff --git a/src/compiler/compile/nodes/Element.ts b/src/compiler/compile/nodes/Element.ts index 78d6c9188f..00a9b6b22f 100644 --- a/src/compiler/compile/nodes/Element.ts +++ b/src/compiler/compile/nodes/Element.ts @@ -18,6 +18,9 @@ import Let from './Let'; import TemplateScope from './shared/TemplateScope'; import { INode } from './interfaces'; import Component from '../Component'; +import Expression from './shared/Expression'; +import { string_literal } from '../utils/stringify'; +import { Literal } from 'estree'; import compiler_warnings from '../compiler_warnings'; import compiler_errors from '../compiler_errors'; @@ -190,11 +193,26 @@ export default class Element extends Node { children: INode[]; namespace: string; needs_manual_style_scoping: boolean; + tag_expr: Expression; + + get is_dynamic_element() { + return this.name === 'svelte:element'; + } constructor(component: Component, parent: Node, scope: TemplateScope, info: any) { super(component, parent, scope, info); this.name = info.name; + if (info.name === 'svelte:element') { + if (typeof info.tag !== 'string') { + this.tag_expr = new Expression(component, this, scope, info.tag); + } else { + this.tag_expr = new Expression(component, this, scope, string_literal(info.tag) as Literal); + } + } else { + this.tag_expr = new Expression(component, this, scope, string_literal(this.name) as Literal); + } + this.namespace = get_namespace(parent as Element, this, component.namespace); if (this.namespace !== namespaces.foreign) { diff --git a/src/compiler/compile/render_dom/Block.ts b/src/compiler/compile/render_dom/Block.ts index cad7e22170..34c4774804 100644 --- a/src/compiler/compile/render_dom/Block.ts +++ b/src/compiler/compile/render_dom/Block.ts @@ -48,6 +48,7 @@ export default class Block { hydrate: Array; mount: Array; measure: Array; + restore_measurements: Array; fix: Array; animate: Array; intro: Array; @@ -96,6 +97,7 @@ export default class Block { hydrate: [], mount: [], measure: [], + restore_measurements: [], fix: [], animate: [], intro: [], @@ -326,6 +328,12 @@ export default class Block { ${this.chunks.measure} }`; + if (this.chunks.restore_measurements.length) { + properties.restore_measurements = x`function #restore_measurements(#measurement) { + ${this.chunks.restore_measurements} + }`; + } + properties.fix = x`function #fix() { ${this.chunks.fix} }`; @@ -379,6 +387,7 @@ export default class Block { m: ${properties.mount}, p: ${properties.update}, r: ${properties.measure}, + s: ${properties.restore_measurements}, f: ${properties.fix}, a: ${properties.animate}, i: ${properties.intro}, diff --git a/src/compiler/compile/render_dom/wrappers/Element/index.ts b/src/compiler/compile/render_dom/wrappers/Element/index.ts index 989e7cb82b..b66c8938fc 100644 --- a/src/compiler/compile/render_dom/wrappers/Element/index.ts +++ b/src/compiler/compile/render_dom/wrappers/Element/index.ts @@ -26,6 +26,7 @@ import Action from '../../../nodes/Action'; import MustacheTagWrapper from '../MustacheTag'; import RawMustacheTagWrapper from '../RawMustacheTag'; import is_dynamic from '../shared/is_dynamic'; +import create_debugging_comment from '../shared/create_debugging_comment'; import { push_array } from '../../../../utils/push_array'; interface BindingGroup { @@ -134,6 +135,8 @@ const events = [ } ]; +const CHILD_DYNAMIC_ELEMENT_BLOCK = 'child_dynamic_element'; + export default class ElementWrapper extends Wrapper { node: Element; fragment: FragmentWrapper; @@ -147,6 +150,9 @@ export default class ElementWrapper extends Wrapper { var: any; void: boolean; + child_dynamic_element_block?: Block = null; + child_dynamic_element?: ElementWrapper = null; + constructor( renderer: Renderer, block: Block, @@ -156,6 +162,24 @@ export default class ElementWrapper extends Wrapper { next_sibling: Wrapper ) { super(renderer, block, parent, node); + + if (node.is_dynamic_element && block.type !== CHILD_DYNAMIC_ELEMENT_BLOCK) { + this.child_dynamic_element_block = block.child({ + comment: create_debugging_comment(node, renderer.component), + name: renderer.component.get_unique_name('create_dynamic_element'), + type: CHILD_DYNAMIC_ELEMENT_BLOCK + }); + renderer.blocks.push(this.child_dynamic_element_block); + this.child_dynamic_element = new ElementWrapper( + renderer, + this.child_dynamic_element_block, + parent, + node, + strip_whitespace, + next_sibling + ); + } + this.var = { type: 'Identifier', name: node.name.replace(/[^a-zA-Z0-9_$]/g, '_') @@ -199,6 +223,8 @@ export default class ElementWrapper extends Wrapper { block.add_animation(); } + block.add_dependencies(node.tag_expr.dependencies); + // add directive and handler dependencies [node.animation, node.outro, ...node.actions, ...node.classes, ...node.styles].forEach(directive => { if (directive && directive.expression) { @@ -221,6 +247,7 @@ export default class ElementWrapper extends Wrapper { node.handlers.length > 0 || node.styles.length > 0 || this.node.name === 'option' || + node.tag_expr.dynamic_dependencies().length || renderer.options.dev ) { this.parent.cannot_use_innerhtml(); // need to use add_location @@ -232,6 +259,110 @@ export default class ElementWrapper extends Wrapper { } render(block: Block, parent_node: Identifier, parent_nodes: Identifier) { + if (this.child_dynamic_element) { + this.render_dynamic_element(block, parent_node, parent_nodes); + } else { + this.render_element(block, parent_node, parent_nodes); + } + } + + render_dynamic_element(block: Block, parent_node: Identifier, parent_nodes: Identifier) { + this.child_dynamic_element.render( + this.child_dynamic_element_block, + null, + (x`#nodes` as unknown) as Identifier + ); + + const previous_tag = block.get_unique_name('previous_tag'); + const tag = this.node.tag_expr.manipulate(block); + block.add_variable(previous_tag, tag); + + block.chunks.init.push(b` + ${this.renderer.options.dev && b`@validate_dynamic_element(${tag});`} + let ${this.var} = ${tag} && ${this.child_dynamic_element_block.name}(#ctx); + `); + + block.chunks.create.push(b` + if (${this.var}) ${this.var}.c(); + `); + + if (this.renderer.options.hydratable) { + block.chunks.claim.push(b` + if (${this.var}) ${this.var}.l(${parent_nodes}); + `); + } + + block.chunks.mount.push(b` + if (${this.var}) ${this.var}.m(${parent_node || '#target'}, ${parent_node ? 'null' : '#anchor'}); + `); + + const anchor = this.get_or_create_anchor(block, parent_node, parent_nodes); + const has_transitions = !!(this.node.intro || this.node.outro); + const not_equal = this.renderer.component.component_options.immutable ? x`@not_equal` : x`@safe_not_equal`; + + block.chunks.update.push(b` + if (${tag}) { + if (!${previous_tag}) { + ${this.var} = ${this.child_dynamic_element_block.name}(#ctx); + ${this.var}.c(); + ${has_transitions && b`@transition_in(${this.var})`} + ${this.var}.m(${this.get_update_mount_node(anchor)}, ${anchor}); + } else if (${not_equal}(${previous_tag}, ${tag})) { + ${this.var}.d(1); + ${this.renderer.options.dev && b`@validate_dynamic_element(${tag});`} + ${this.var} = ${this.child_dynamic_element_block.name}(#ctx); + ${this.var}.c(); + ${this.var}.m(${this.get_update_mount_node(anchor)}, ${anchor}); + } else { + ${this.var}.p(#ctx, #dirty); + } + } else if (${previous_tag}) { + ${ + has_transitions + ? b` + @group_outros(); + @transition_out(${this.var}, 1, 1, () => { + ${this.var} = null; + }); + @check_outros(); + ` + : b` + ${this.var}.d(1); + ${this.var} = null; + ` + } + } + ${previous_tag} = ${tag}; + `); + + if (this.child_dynamic_element_block.has_intros) { + block.chunks.intro.push(b`@transition_in(${this.var});`); + } + + if (this.child_dynamic_element_block.has_outros) { + block.chunks.outro.push(b`@transition_out(${this.var});`); + } + + block.chunks.destroy.push(b`if (${this.var}) ${this.var}.d(detaching)`); + + if (this.node.animation) { + const measurements = block.get_unique_name('measurements'); + block.add_variable(measurements); + block.chunks.measure.push(b`${measurements} = ${this.var}.r()`); + block.chunks.fix.push(b`${this.var}.f();`); + block.chunks.animate.push(b` + ${this.var}.s(${measurements}); + ${this.var}.a() + `); + } + } + + is_dom_node() { + return super.is_dom_node() && !this.child_dynamic_element; + } + + render_element(block: Block, parent_node: Identifier, parent_nodes: Identifier) { + const { renderer } = this; if (this.node.name === 'noscript') return; @@ -249,7 +380,7 @@ export default class ElementWrapper extends Wrapper { if (renderer.options.hydratable) { if (parent_nodes) { block.chunks.claim.push(b` - ${node} = ${this.get_claim_statement(parent_nodes)}; + ${node} = ${this.get_claim_statement(block, parent_nodes)}; `); if (!this.void && this.node.children.length > 0) { @@ -357,6 +488,8 @@ export default class ElementWrapper extends Wrapper { b`@add_location(${this.var}, ${renderer.file_var}, ${loc.line - 1}, ${loc.column}, ${this.node.start});` ); } + + block.renderer.dirty(this.node.tag_expr.dynamic_dependencies()); } can_use_textcontent() { @@ -364,7 +497,7 @@ export default class ElementWrapper extends Wrapper { } get_render_statement(block: Block) { - const { name, namespace } = this.node; + const { name, namespace, tag_expr } = this.node; if (namespace === namespaces.svg) { return x`@svg_element("${name}")`; @@ -379,22 +512,32 @@ export default class ElementWrapper extends Wrapper { return x`@element_is("${name}", ${is.render_chunks(block).reduce((lhs, rhs) => x`${lhs} + ${rhs}`)})`; } - return x`@element("${name}")`; + const reference = tag_expr.manipulate(block); + return x`@element(${reference})`; } - get_claim_statement(nodes: Identifier) { + get_claim_statement(block: Block, nodes: Identifier) { const attributes = this.attributes .filter((attr) => !(attr instanceof SpreadAttributeWrapper) && !attr.property_name) .map((attr) => p`${(attr as StyleAttributeWrapper | AttributeWrapper).name}: true`); - const name = this.node.namespace - ? this.node.name - : this.node.name.toUpperCase(); + let reference; + if (this.node.tag_expr.node.type === 'Literal') { + if (this.node.namespace) { + reference = `"${this.node.tag_expr.node.value}"`; + } else { + reference = `"${(this.node.tag_expr.node.value as String || '').toUpperCase()}"`; + } + } else if (this.node.namespace) { + reference = x`${this.node.tag_expr.manipulate(block)}`; + } else { + reference = x`(${this.node.tag_expr.manipulate(block)} || 'null').toUpperCase()`; + } if (this.node.namespace === namespaces.svg) { - return x`@claim_svg_element(${nodes}, "${name}", { ${attributes} })`; + return x`@claim_svg_element(${nodes}, ${reference}, { ${attributes} })`; } else { - return x`@claim_element(${nodes}, "${name}", { ${attributes} })`; + return x`@claim_element(${nodes}, ${reference}, { ${attributes} })`; } } @@ -847,6 +990,11 @@ export default class ElementWrapper extends Wrapper { ${rect} = ${this.var}.getBoundingClientRect(); `); + if (block.type === CHILD_DYNAMIC_ELEMENT_BLOCK) { + block.chunks.measure.push(b`return ${rect}`); + block.chunks.restore_measurements.push(b`${rect} = #measurement;`); + } + block.chunks.fix.push(b` @fix_position(${this.var}); ${stop_animation}(); @@ -940,7 +1088,7 @@ export default class ElementWrapper extends Wrapper { if (should_cache) { block.chunks.update.push(b` if (${block.renderer.dirty(dependencies)} && (${cached_snippet} !== (${cached_snippet} = ${snippet}))) { - ${updater} + ${updater} } `); } else { diff --git a/src/compiler/compile/render_ssr/handlers/Element.ts b/src/compiler/compile/render_ssr/handlers/Element.ts index dd096ae79b..7c4c5ba765 100644 --- a/src/compiler/compile/render_ssr/handlers/Element.ts +++ b/src/compiler/compile/render_ssr/handlers/Element.ts @@ -8,8 +8,9 @@ import Expression from '../../nodes/shared/Expression'; import remove_whitespace_children from './utils/remove_whitespace_children'; import fix_attribute_casing from '../../render_dom/wrappers/Element/fix_attribute_casing'; import { namespaces } from '../../../utils/namespaces'; +import { Expression as ESExpression } from 'estree'; -export default function(node: Element, renderer: Renderer, options: RenderOptions) { +export default function (node: Element, renderer: Renderer, options: RenderOptions) { const children = remove_whitespace_children(node.children, node.next); @@ -22,7 +23,8 @@ export default function(node: Element, renderer: Renderer, options: RenderOption node.attributes.some((attribute) => attribute.name === 'contenteditable') ); - renderer.add_string(`<${node.name}`); + renderer.add_string('<'); + add_tag_name(); const class_expression_list = node.classes.map(class_directive => { const { expression, name } = class_directive; @@ -167,14 +169,25 @@ export default function(node: Element, renderer: Renderer, options: RenderOption renderer.add_expression(node_contents); } - if (!is_void(node.name)) { - renderer.add_string(``); - } + add_close_tag(); } else { renderer.render(children, options); + add_close_tag(); + } + function add_close_tag() { if (!is_void(node.name)) { - renderer.add_string(``); + renderer.add_string(''); + } + } + + function add_tag_name() { + if (node.tag_expr.node.type === 'Literal') { + renderer.add_string(node.tag_expr.node.value as string); + } else { + renderer.add_expression(node.tag_expr.node as ESExpression); } } } diff --git a/src/compiler/parse/errors.ts b/src/compiler/parse/errors.ts index ef1f72a8be..63bd5b0919 100644 --- a/src/compiler/parse/errors.ts +++ b/src/compiler/parse/errors.ts @@ -99,6 +99,10 @@ export default { code: `invalid-${slug}-content`, message: `<${name}> cannot have children` }), + invalid_element_definition: { + code: 'invalid-element-definition', + message: 'Invalid element definition' + }, invalid_element_placement: (slug: string, name: string) => ({ code: `invalid-${slug}-placement`, message: `<${name}> tags cannot be inside elements or blocks` @@ -161,6 +165,10 @@ export default { code: 'missing-attribute-value', message: 'Expected value for the attribute' }, + missing_element_definition: { + code: 'missing-element-definition', + message: ' must have a \'this\' attribute' + }, unclosed_script: { code: 'unclosed-script', message: ' + +tag is {tag}. diff --git a/test/runtime/samples/dynamic-element-animation-2/_config.js b/test/runtime/samples/dynamic-element-animation-2/_config.js new file mode 100644 index 0000000000..27b28bb349 --- /dev/null +++ b/test/runtime/samples/dynamic-element-animation-2/_config.js @@ -0,0 +1,105 @@ +let originalDivGetBoundingClientRect; +let originalSpanGetBoundingClientRect; +let originalParagraphGetBoundingClientRect; + +export default { + skip_if_ssr: true, + props: { + things: [ + { id: 1, name: 'a' }, + { id: 2, name: 'b' }, + { id: 3, name: 'c' }, + { id: 4, name: 'd' }, + { id: 5, name: 'e' } + ], + tag: 'div' + }, + + html: ` +
a
+
b
+
c
+
d
+
e
+ `, + + before_test() { + originalDivGetBoundingClientRect = + window.HTMLDivElement.prototype.getBoundingClientRect; + originalSpanGetBoundingClientRect = + window.HTMLSpanElement.prototype.getBoundingClientRect; + originalParagraphGetBoundingClientRect = + window.HTMLParagraphElement.prototype.getBoundingClientRect; + + window.HTMLDivElement.prototype.getBoundingClientRect = + fakeGetBoundingClientRect; + window.HTMLSpanElement.prototype.getBoundingClientRect = + fakeGetBoundingClientRect; + window.HTMLParagraphElement.prototype.getBoundingClientRect = + fakeGetBoundingClientRect; + + function fakeGetBoundingClientRect() { + const index = [...this.parentNode.children].indexOf(this); + const top = index * 30; + + return { + left: 0, + right: 100, + top, + bottom: top + 20 + }; + } + }, + after_test() { + window.HTMLDivElement.prototype.getBoundingClientRect = + originalDivGetBoundingClientRect; + window.HTMLSpanElement.prototype.getBoundingClientRect = + originalSpanGetBoundingClientRect; + window.HTMLParagraphElement.prototype.getBoundingClientRect = + originalParagraphGetBoundingClientRect; + }, + + async test({ assert, component, target, raf }) { + // switch tag and things at the same time + await component.update('p', [ + { id: 5, name: 'e' }, + { id: 2, name: 'b' }, + { id: 3, name: 'c' }, + { id: 4, name: 'd' }, + { id: 1, name: 'a' } + ]); + + const ps = document.querySelectorAll('p'); + assert.equal(ps[0].dy, 120); + assert.equal(ps[4].dy, -120); + + raf.tick(50); + assert.equal(ps[0].dy, 60); + assert.equal(ps[4].dy, -60); + + raf.tick(100); + assert.equal(ps[0].dy, 0); + assert.equal(ps[4].dy, 0); + + await component.update('span', [ + { id: 1, name: 'a' }, + { id: 2, name: 'b' }, + { id: 3, name: 'c' }, + { id: 4, name: 'd' }, + { id: 5, name: 'e' } + ]); + + const spans = document.querySelectorAll('span'); + + assert.equal(spans[0].dy, 120); + assert.equal(spans[4].dy, -120); + + raf.tick(150); + assert.equal(spans[0].dy, 60); + assert.equal(spans[4].dy, -60); + + raf.tick(200); + assert.equal(spans[0].dy, 0); + assert.equal(spans[4].dy, 0); + } +}; diff --git a/test/runtime/samples/dynamic-element-animation-2/main.svelte b/test/runtime/samples/dynamic-element-animation-2/main.svelte new file mode 100644 index 0000000000..f655a40af9 --- /dev/null +++ b/test/runtime/samples/dynamic-element-animation-2/main.svelte @@ -0,0 +1,26 @@ + + +{#each things as thing (thing.id)} + {thing.name} +{/each} \ No newline at end of file diff --git a/test/runtime/samples/dynamic-element-animation/_config.js b/test/runtime/samples/dynamic-element-animation/_config.js new file mode 100644 index 0000000000..e3c57a868e --- /dev/null +++ b/test/runtime/samples/dynamic-element-animation/_config.js @@ -0,0 +1,62 @@ +export default { + props: { + things: [ + { id: 1, name: 'a' }, + { id: 2, name: 'b' }, + { id: 3, name: 'c' }, + { id: 4, name: 'd' }, + { id: 5, name: 'e' } + ], + tag: 'div' + }, + + html: ` +
a
+
b
+
c
+
d
+
e
+ `, + + test({ assert, component, target, raf }) { + component.tag = 'p'; + assert.equal(target.querySelectorAll('p').length, 5); + + component.tag = 'div'; + let divs = target.querySelectorAll('div'); + divs.forEach(div => { + div.getBoundingClientRect = function() { + const index = [...this.parentNode.children].indexOf(this); + const top = index * 30; + + return { + left: 0, + right: 100, + top, + bottom: top + 20 + }; + }; + }); + + component.things = [ + { id: 5, name: 'e' }, + { id: 2, name: 'b' }, + { id: 3, name: 'c' }, + { id: 4, name: 'd' }, + { id: 1, name: 'a' } + ]; + + divs = target.querySelectorAll('div'); + assert.ok(~divs[0].style.animation.indexOf('__svelte')); + assert.equal(divs[1].style.animation, ''); + assert.equal(divs[2].style.animation, ''); + assert.equal(divs[3].style.animation, ''); + assert.ok(~divs[4].style.animation.indexOf('__svelte')); + + raf.tick(100); + assert.deepEqual([ + divs[0].style.animation, + divs[4].style.animation + ], ['', '']); + } +}; diff --git a/test/runtime/samples/dynamic-element-animation/main.svelte b/test/runtime/samples/dynamic-element-animation/main.svelte new file mode 100644 index 0000000000..596d12c77a --- /dev/null +++ b/test/runtime/samples/dynamic-element-animation/main.svelte @@ -0,0 +1,18 @@ + + +{#each things as thing (thing.id)} + {thing.name} +{/each} \ No newline at end of file diff --git a/test/runtime/samples/dynamic-element-attribute/_config.js b/test/runtime/samples/dynamic-element-attribute/_config.js new file mode 100644 index 0000000000..6e7c340437 --- /dev/null +++ b/test/runtime/samples/dynamic-element-attribute/_config.js @@ -0,0 +1,17 @@ +export default { + props: { + tag: 'div' + }, + html: '
Foo
', + + test({ assert, component, target }) { + component.tag = 'h1'; + + assert.htmlEqual( + target.innerHTML, + ` +

Foo

+ ` + ); + } +}; diff --git a/test/runtime/samples/dynamic-element-attribute/main.svelte b/test/runtime/samples/dynamic-element-attribute/main.svelte new file mode 100644 index 0000000000..2498e06de9 --- /dev/null +++ b/test/runtime/samples/dynamic-element-attribute/main.svelte @@ -0,0 +1,5 @@ + + +Foo diff --git a/test/runtime/samples/dynamic-element-binding-invalid/_config.js b/test/runtime/samples/dynamic-element-binding-invalid/_config.js new file mode 100644 index 0000000000..14c6d775dc --- /dev/null +++ b/test/runtime/samples/dynamic-element-binding-invalid/_config.js @@ -0,0 +1,3 @@ +export default { + error: "'value' is not a valid binding on elements" +}; diff --git a/test/runtime/samples/dynamic-element-binding-invalid/main.svelte b/test/runtime/samples/dynamic-element-binding-invalid/main.svelte new file mode 100644 index 0000000000..45f8f96061 --- /dev/null +++ b/test/runtime/samples/dynamic-element-binding-invalid/main.svelte @@ -0,0 +1,6 @@ + + + diff --git a/test/runtime/samples/dynamic-element-binding-this/_config.js b/test/runtime/samples/dynamic-element-binding-this/_config.js new file mode 100644 index 0000000000..e0722c9375 --- /dev/null +++ b/test/runtime/samples/dynamic-element-binding-this/_config.js @@ -0,0 +1,8 @@ +export default { + html: '
', + + test({ assert, component, target }) { + const div = target.querySelector('div'); + assert.equal(div, component.foo); + } +}; diff --git a/test/runtime/samples/dynamic-element-binding-this/main.svelte b/test/runtime/samples/dynamic-element-binding-this/main.svelte new file mode 100644 index 0000000000..75e8b02ce1 --- /dev/null +++ b/test/runtime/samples/dynamic-element-binding-this/main.svelte @@ -0,0 +1,6 @@ + + + diff --git a/test/runtime/samples/dynamic-element-change-tag/_config.js b/test/runtime/samples/dynamic-element-change-tag/_config.js new file mode 100644 index 0000000000..9e4bf6fd32 --- /dev/null +++ b/test/runtime/samples/dynamic-element-change-tag/_config.js @@ -0,0 +1,17 @@ +export default { + props: { + tag: 'div' + }, + html: '
Foo
', + + test({ assert, component, target }) { + component.tag = 'h1'; + + assert.htmlEqual( + target.innerHTML, + ` +

Foo

+ ` + ); + } +}; diff --git a/test/runtime/samples/dynamic-element-change-tag/main.svelte b/test/runtime/samples/dynamic-element-change-tag/main.svelte new file mode 100644 index 0000000000..a9c4d5c00c --- /dev/null +++ b/test/runtime/samples/dynamic-element-change-tag/main.svelte @@ -0,0 +1,5 @@ + + +Foo \ No newline at end of file diff --git a/test/runtime/samples/dynamic-element-empty-tag/_config.js b/test/runtime/samples/dynamic-element-empty-tag/_config.js new file mode 100644 index 0000000000..22dc25b41e --- /dev/null +++ b/test/runtime/samples/dynamic-element-empty-tag/_config.js @@ -0,0 +1,3 @@ +export default { + html: '' +}; diff --git a/test/runtime/samples/dynamic-element-empty-tag/main.svelte b/test/runtime/samples/dynamic-element-empty-tag/main.svelte new file mode 100644 index 0000000000..e3889ce0f5 --- /dev/null +++ b/test/runtime/samples/dynamic-element-empty-tag/main.svelte @@ -0,0 +1,5 @@ + + +Foo diff --git a/test/runtime/samples/dynamic-element-event-handler1/_config.js b/test/runtime/samples/dynamic-element-event-handler1/_config.js new file mode 100644 index 0000000000..03b8f7879d --- /dev/null +++ b/test/runtime/samples/dynamic-element-event-handler1/_config.js @@ -0,0 +1,21 @@ +let clicked = false; +function handler() { + clicked = true; +} + +export default { + props: { + handler + }, + html: '', + + test({ assert, target }) { + assert.equal(clicked, false); + + const button = target.querySelector('button'); + const click = new window.MouseEvent('click'); + button.dispatchEvent(click); + + assert.equal(clicked, true); + } +}; diff --git a/test/runtime/samples/dynamic-element-event-handler1/main.svelte b/test/runtime/samples/dynamic-element-event-handler1/main.svelte new file mode 100644 index 0000000000..7a7fef9c22 --- /dev/null +++ b/test/runtime/samples/dynamic-element-event-handler1/main.svelte @@ -0,0 +1,6 @@ + + +Foo \ No newline at end of file diff --git a/test/runtime/samples/dynamic-element-event-handler2/_config.js b/test/runtime/samples/dynamic-element-event-handler2/_config.js new file mode 100644 index 0000000000..22cbe735a7 --- /dev/null +++ b/test/runtime/samples/dynamic-element-event-handler2/_config.js @@ -0,0 +1,23 @@ +let clicked = false; +function handler() { + clicked = true; +} + +export default { + props: { + tag: 'div', + handler + }, + html: '
Foo
', + + test({ assert, component, target }) { + assert.equal(clicked, false); + + component.tag = 'button'; + const button = target.querySelector('button'); + const click = new window.MouseEvent('click'); + button.dispatchEvent(click); + + assert.equal(clicked, true); + } +}; diff --git a/test/runtime/samples/dynamic-element-event-handler2/main.svelte b/test/runtime/samples/dynamic-element-event-handler2/main.svelte new file mode 100644 index 0000000000..f2534c1f62 --- /dev/null +++ b/test/runtime/samples/dynamic-element-event-handler2/main.svelte @@ -0,0 +1,6 @@ + + +Foo diff --git a/test/runtime/samples/dynamic-element-expression/_config.js b/test/runtime/samples/dynamic-element-expression/_config.js new file mode 100644 index 0000000000..acad91c901 --- /dev/null +++ b/test/runtime/samples/dynamic-element-expression/_config.js @@ -0,0 +1,3 @@ +export default { + html: '
Foo
' +}; diff --git a/test/runtime/samples/dynamic-element-expression/main.svelte b/test/runtime/samples/dynamic-element-expression/main.svelte new file mode 100644 index 0000000000..7ec11e4ef6 --- /dev/null +++ b/test/runtime/samples/dynamic-element-expression/main.svelte @@ -0,0 +1 @@ +Foo \ No newline at end of file diff --git a/test/runtime/samples/dynamic-element-invalid-this/_config.js b/test/runtime/samples/dynamic-element-invalid-this/_config.js new file mode 100644 index 0000000000..3aaa554991 --- /dev/null +++ b/test/runtime/samples/dynamic-element-invalid-this/_config.js @@ -0,0 +1,9 @@ +export default { + compileOptions: { + dev: true + }, + props: { + tag: 123 + }, + error: ' expects "this" attribute to be a string.' +}; diff --git a/test/runtime/samples/dynamic-element-invalid-this/main.svelte b/test/runtime/samples/dynamic-element-invalid-this/main.svelte new file mode 100644 index 0000000000..bcc2c293bb --- /dev/null +++ b/test/runtime/samples/dynamic-element-invalid-this/main.svelte @@ -0,0 +1,5 @@ + + + diff --git a/test/runtime/samples/dynamic-element-null-tag/_config.js b/test/runtime/samples/dynamic-element-null-tag/_config.js new file mode 100644 index 0000000000..22dc25b41e --- /dev/null +++ b/test/runtime/samples/dynamic-element-null-tag/_config.js @@ -0,0 +1,3 @@ +export default { + html: '' +}; diff --git a/test/runtime/samples/dynamic-element-null-tag/main.svelte b/test/runtime/samples/dynamic-element-null-tag/main.svelte new file mode 100644 index 0000000000..58dc96ff2a --- /dev/null +++ b/test/runtime/samples/dynamic-element-null-tag/main.svelte @@ -0,0 +1,5 @@ + + +Foo diff --git a/test/runtime/samples/dynamic-element-pass-props/_config.js b/test/runtime/samples/dynamic-element-pass-props/_config.js new file mode 100644 index 0000000000..35f8b7abdf --- /dev/null +++ b/test/runtime/samples/dynamic-element-pass-props/_config.js @@ -0,0 +1,16 @@ +let clicked = false; + +export default { + props: { + tag: 'div', + onClick: () => clicked = true + }, + html: '
Foo
', + + async test({ assert, target, window }) { + const div = target.querySelector('div'); + await div.dispatchEvent(new window.MouseEvent('click')); + + assert.equal(clicked, true); + } +}; diff --git a/test/runtime/samples/dynamic-element-pass-props/main.svelte b/test/runtime/samples/dynamic-element-pass-props/main.svelte new file mode 100644 index 0000000000..6a54a93f27 --- /dev/null +++ b/test/runtime/samples/dynamic-element-pass-props/main.svelte @@ -0,0 +1,6 @@ + + +Foo \ No newline at end of file diff --git a/test/runtime/samples/dynamic-element-slot/Foo.svelte b/test/runtime/samples/dynamic-element-slot/Foo.svelte new file mode 100644 index 0000000000..51dda4b7e3 --- /dev/null +++ b/test/runtime/samples/dynamic-element-slot/Foo.svelte @@ -0,0 +1,7 @@ +

Foo

+
+ +
+
+ +
diff --git a/test/runtime/samples/dynamic-element-slot/_config.js b/test/runtime/samples/dynamic-element-slot/_config.js new file mode 100644 index 0000000000..aa9da522a3 --- /dev/null +++ b/test/runtime/samples/dynamic-element-slot/_config.js @@ -0,0 +1,29 @@ +export default { + props: { + x: true + }, + + html: ` +

Foo

+
+

This is default slot

+
+
+

This is other slot

+
+ `, + + test({ assert, component, target }) { + component.tag = 'h2'; + + assert.htmlEqual(target.innerHTML, ` +

Foo

+
+

This is default slot

+
+
+

This is other slot

+
+ `); + } +}; diff --git a/test/runtime/samples/dynamic-element-slot/main.svelte b/test/runtime/samples/dynamic-element-slot/main.svelte new file mode 100644 index 0000000000..4b1cd81969 --- /dev/null +++ b/test/runtime/samples/dynamic-element-slot/main.svelte @@ -0,0 +1,10 @@ + + + + This is default slot + This is other slot + + diff --git a/test/runtime/samples/dynamic-element-store/_config.js b/test/runtime/samples/dynamic-element-store/_config.js new file mode 100644 index 0000000000..ded19eef79 --- /dev/null +++ b/test/runtime/samples/dynamic-element-store/_config.js @@ -0,0 +1,3 @@ +export default { + html: '
' +}; diff --git a/test/runtime/samples/dynamic-element-store/main.svelte b/test/runtime/samples/dynamic-element-store/main.svelte new file mode 100644 index 0000000000..84a577ecee --- /dev/null +++ b/test/runtime/samples/dynamic-element-store/main.svelte @@ -0,0 +1,6 @@ + + + \ No newline at end of file diff --git a/test/runtime/samples/dynamic-element-string/_config.js b/test/runtime/samples/dynamic-element-string/_config.js new file mode 100644 index 0000000000..acad91c901 --- /dev/null +++ b/test/runtime/samples/dynamic-element-string/_config.js @@ -0,0 +1,3 @@ +export default { + html: '
Foo
' +}; diff --git a/test/runtime/samples/dynamic-element-string/main.svelte b/test/runtime/samples/dynamic-element-string/main.svelte new file mode 100644 index 0000000000..62d65d5f20 --- /dev/null +++ b/test/runtime/samples/dynamic-element-string/main.svelte @@ -0,0 +1 @@ +Foo \ No newline at end of file diff --git a/test/runtime/samples/dynamic-element-template-literals/_config.js b/test/runtime/samples/dynamic-element-template-literals/_config.js new file mode 100644 index 0000000000..d37164b4d8 --- /dev/null +++ b/test/runtime/samples/dynamic-element-template-literals/_config.js @@ -0,0 +1,21 @@ +export default { + props: { + size: 1 + }, + html: '

This is h1 tag

', + + test({ assert, component, target }) { + const h1 = target.firstChild; + component.size = 2; + + assert.htmlEqual( + target.innerHTML, + ` +

This is h2 tag

+ ` + ); + + const h2 = target.firstChild; + assert.notEqual(h1, h2); + } +}; diff --git a/test/runtime/samples/dynamic-element-template-literals/main.svelte b/test/runtime/samples/dynamic-element-template-literals/main.svelte new file mode 100644 index 0000000000..84b63dcca7 --- /dev/null +++ b/test/runtime/samples/dynamic-element-template-literals/main.svelte @@ -0,0 +1,5 @@ + + +This is h{size} tag diff --git a/test/runtime/samples/dynamic-element-transition/_config.js b/test/runtime/samples/dynamic-element-transition/_config.js new file mode 100644 index 0000000000..cb8474afc1 --- /dev/null +++ b/test/runtime/samples/dynamic-element-transition/_config.js @@ -0,0 +1,17 @@ +export default { + test({ assert, component, target, raf }) { + component.visible = true; + const h1 = target.querySelector('h1'); + assert.equal(h1.style.animation, '__svelte_3809512021_0 100ms linear 0ms 1 both'); + + raf.tick(150); + component.tag = 'h2'; + const h2 = target.querySelector('h2'); + assert.equal(h1.style.animation, ''); + assert.equal(h2.style.animation, ''); + + raf.tick(50); + component.visible = false; + assert.equal(h2.style.animation, '__svelte_3750847757_0 100ms linear 0ms 1 both'); + } +}; diff --git a/test/runtime/samples/dynamic-element-transition/main.svelte b/test/runtime/samples/dynamic-element-transition/main.svelte new file mode 100644 index 0000000000..b8c0eff0bd --- /dev/null +++ b/test/runtime/samples/dynamic-element-transition/main.svelte @@ -0,0 +1,17 @@ + + +{#if visible} + +{/if} diff --git a/test/runtime/samples/dynamic-element-undefined-tag/_config.js b/test/runtime/samples/dynamic-element-undefined-tag/_config.js new file mode 100644 index 0000000000..d0bd665d3d --- /dev/null +++ b/test/runtime/samples/dynamic-element-undefined-tag/_config.js @@ -0,0 +1,19 @@ +export default { + html: '', + test({ component, target, assert }) { + component.tag = 'h1'; + assert.htmlEqual(target.innerHTML, '

Foo

'); + + component.tag = null; + assert.htmlEqual(target.innerHTML, ''); + + component.tag = 'div'; + assert.htmlEqual(target.innerHTML, '
Foo
'); + + component.tag = false; + assert.htmlEqual(target.innerHTML, ''); + + component.tag = 'span'; + assert.htmlEqual(target.innerHTML, 'Foo'); + } +}; diff --git a/test/runtime/samples/dynamic-element-undefined-tag/main.svelte b/test/runtime/samples/dynamic-element-undefined-tag/main.svelte new file mode 100644 index 0000000000..6aca93ca13 --- /dev/null +++ b/test/runtime/samples/dynamic-element-undefined-tag/main.svelte @@ -0,0 +1,5 @@ + + +Foo diff --git a/test/runtime/samples/dynamic-element-variable/_config.js b/test/runtime/samples/dynamic-element-variable/_config.js new file mode 100644 index 0000000000..20e0fa9418 --- /dev/null +++ b/test/runtime/samples/dynamic-element-variable/_config.js @@ -0,0 +1,20 @@ +export default { + props: { + tag: 'div', + text: 'Foo' + }, + html: '
Foo
', + + test({ assert, component, target }) { + const div = target.firstChild; + component.tag = 'nav'; + component.text = 'Bar'; + + assert.htmlEqual(target.innerHTML, ` + + `); + + const h1 = target.firstChild; + assert.notEqual(div, h1); + } +}; diff --git a/test/runtime/samples/dynamic-element-variable/main.svelte b/test/runtime/samples/dynamic-element-variable/main.svelte new file mode 100644 index 0000000000..d60953bba5 --- /dev/null +++ b/test/runtime/samples/dynamic-element-variable/main.svelte @@ -0,0 +1,6 @@ + + +{text} \ No newline at end of file diff --git a/test/server-side-rendering/samples/dynamic-element-string/_expected.html b/test/server-side-rendering/samples/dynamic-element-string/_expected.html new file mode 100644 index 0000000000..cb98432e14 --- /dev/null +++ b/test/server-side-rendering/samples/dynamic-element-string/_expected.html @@ -0,0 +1 @@ +
Foo
diff --git a/test/server-side-rendering/samples/dynamic-element-string/main.svelte b/test/server-side-rendering/samples/dynamic-element-string/main.svelte new file mode 100644 index 0000000000..62d65d5f20 --- /dev/null +++ b/test/server-side-rendering/samples/dynamic-element-string/main.svelte @@ -0,0 +1 @@ +Foo \ No newline at end of file diff --git a/test/server-side-rendering/samples/dynamic-element-variable/_expected.html b/test/server-side-rendering/samples/dynamic-element-variable/_expected.html new file mode 100644 index 0000000000..3ae445f54c --- /dev/null +++ b/test/server-side-rendering/samples/dynamic-element-variable/_expected.html @@ -0,0 +1,2 @@ +

Foo

+
Bar
\ No newline at end of file diff --git a/test/server-side-rendering/samples/dynamic-element-variable/main.svelte b/test/server-side-rendering/samples/dynamic-element-variable/main.svelte new file mode 100644 index 0000000000..9aaba18bf1 --- /dev/null +++ b/test/server-side-rendering/samples/dynamic-element-variable/main.svelte @@ -0,0 +1,7 @@ + + +Foo +Bar \ No newline at end of file diff --git a/test/validator/samples/dynamic-element-invalid-tag/errors.json b/test/validator/samples/dynamic-element-invalid-tag/errors.json new file mode 100644 index 0000000000..c8d3c52490 --- /dev/null +++ b/test/validator/samples/dynamic-element-invalid-tag/errors.json @@ -0,0 +1,17 @@ +[ + { + "message": "Invalid element definition", + "code": "invalid-element-definition", + "start": { + "line": 2, + "column": 17, + "character": 23 + }, + "end": { + "line": 2, + "column": 17, + "character": 23 + }, + "pos": 23 + } +] diff --git a/test/validator/samples/dynamic-element-invalid-tag/input.svelte b/test/validator/samples/dynamic-element-invalid-tag/input.svelte new file mode 100644 index 0000000000..bc6b8be822 --- /dev/null +++ b/test/validator/samples/dynamic-element-invalid-tag/input.svelte @@ -0,0 +1,3 @@ +
+ foo +
diff --git a/test/validator/samples/dynamic-element-missing-tag/errors.json b/test/validator/samples/dynamic-element-missing-tag/errors.json new file mode 100644 index 0000000000..8243deeb80 --- /dev/null +++ b/test/validator/samples/dynamic-element-missing-tag/errors.json @@ -0,0 +1,15 @@ +[{ + "code": "missing-element-definition", + "message": " must have a 'this' attribute", + "start": { + "line": 2, + "column": 1, + "character": 7 + }, + "end": { + "line": 2, + "column": 1, + "character": 7 + }, + "pos": 7 +}] diff --git a/test/validator/samples/dynamic-element-missing-tag/input.svelte b/test/validator/samples/dynamic-element-missing-tag/input.svelte new file mode 100644 index 0000000000..4b645d25a1 --- /dev/null +++ b/test/validator/samples/dynamic-element-missing-tag/input.svelte @@ -0,0 +1,3 @@ +
+ foo +
diff --git a/test/validator/samples/dynamic-element-this/errors.json b/test/validator/samples/dynamic-element-this/errors.json new file mode 100644 index 0000000000..7d61c20ee2 --- /dev/null +++ b/test/validator/samples/dynamic-element-this/errors.json @@ -0,0 +1,15 @@ +[{ + "code": "unexpected-reserved-word", + "message": "'this' is a reserved word in JavaScript and cannot be used here", + "start": { + "line": 2, + "column": 18, + "character": 24 + }, + "end": { + "line": 2, + "column": 18, + "character": 24 + }, + "pos": 24 + }] diff --git a/test/validator/samples/dynamic-element-this/input.svelte b/test/validator/samples/dynamic-element-this/input.svelte new file mode 100644 index 0000000000..1a5079b924 --- /dev/null +++ b/test/validator/samples/dynamic-element-this/input.svelte @@ -0,0 +1,3 @@ +
+ foo +
From f6fd8e1ec835601634c4f8e0d4278dbc7a36ec9e Mon Sep 17 00:00:00 2001 From: Simon H <5968653+dummdidumm@users.noreply.github.com> Date: Fri, 8 Apr 2022 16:59:03 +0200 Subject: [PATCH 45/57] update changelog --- CHANGELOG.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index da5eb22c40..af883f9697 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,9 +1,10 @@ # Svelte changelog -## Unreleased +## 3.47.0 (Unreleased) * Add `Symbol` as a known global ([#7419](https://github.com/sveltejs/svelte/pull/7419)) * Fix `{#key}` block not reactive when the key variable is not being used ([#7422](https://github.com/sveltejs/svelte/pull/7422)) +* Add support for dynamic elements through `` ([#2324](https://github.com/sveltejs/svelte/issues/2324)) ## 3.46.6 From eca1a652fb3939be477a194fe2aeaefec5cae207 Mon Sep 17 00:00:00 2001 From: Yuichiro Yamashita Date: Sat, 9 Apr 2022 02:30:49 +0900 Subject: [PATCH 46/57] [fix] better context checks for identifiers in const tags (#7222) Fixes #7423 Fixes #7431 Fixes #7206 Fixes #7431 Fixes #7221 Co-authored-by: tanhauhau --- .../compile/nodes/shared/Expression.ts | 10 ++++- .../samples/const-tag-each-const/_config.js | 29 +++++++++++++++ .../samples/const-tag-each-const/main.svelte | 26 +++++++++++++ .../_config.js | 29 +++++++++++++++ .../main.svelte | 19 ++++++++++ .../_config.js | 32 ++++++++++++++++ .../main.svelte | 33 +++++++++++++++++ .../_config.js | 29 +++++++++++++++ .../main.svelte | 25 +++++++++++++ .../const-tag-each-function/_config.js | 29 +++++++++++++++ .../const-tag-each-function/main.svelte | 26 +++++++++++++ .../samples/const-tag-shadow-2/_config.js | 37 +++++++++++++++++++ .../samples/const-tag-shadow-2/main.svelte | 15 ++++++++ 13 files changed, 337 insertions(+), 2 deletions(-) create mode 100644 test/runtime/samples/const-tag-each-const/_config.js create mode 100644 test/runtime/samples/const-tag-each-const/main.svelte create mode 100644 test/runtime/samples/const-tag-each-duplicated-variable1/_config.js create mode 100644 test/runtime/samples/const-tag-each-duplicated-variable1/main.svelte create mode 100644 test/runtime/samples/const-tag-each-duplicated-variable2/_config.js create mode 100644 test/runtime/samples/const-tag-each-duplicated-variable2/main.svelte create mode 100644 test/runtime/samples/const-tag-each-duplicated-variable3/_config.js create mode 100644 test/runtime/samples/const-tag-each-duplicated-variable3/main.svelte create mode 100644 test/runtime/samples/const-tag-each-function/_config.js create mode 100644 test/runtime/samples/const-tag-each-function/main.svelte create mode 100644 test/runtime/samples/const-tag-shadow-2/_config.js create mode 100644 test/runtime/samples/const-tag-shadow-2/main.svelte diff --git a/src/compiler/compile/nodes/shared/Expression.ts b/src/compiler/compile/nodes/shared/Expression.ts index 98fb2f1e3a..751b739564 100644 --- a/src/compiler/compile/nodes/shared/Expression.ts +++ b/src/compiler/compile/nodes/shared/Expression.ts @@ -254,11 +254,17 @@ export default class Expression { const declaration = b`const ${id} = ${node}`; if (owner.type === 'ConstTag') { + let child_scope = scope; walk(node, { - enter(node: Node) { - if (node.type === 'Identifier') { + enter(node: Node, parent: any) { + if (map.has(node)) child_scope = map.get(node); + if (node.type === 'Identifier' && is_reference(node, parent)) { + if (child_scope.has(node.name)) return; this.replace(block.renderer.reference(node, ctx)); } + }, + leave(node: Node) { + if (map.has(node)) child_scope = child_scope.parent; } }); } else if (dependencies.size === 0 && contextual_dependencies.size === 0) { diff --git a/test/runtime/samples/const-tag-each-const/_config.js b/test/runtime/samples/const-tag-each-const/_config.js new file mode 100644 index 0000000000..cde4226a25 --- /dev/null +++ b/test/runtime/samples/const-tag-each-const/_config.js @@ -0,0 +1,29 @@ +export default { + html: ` +

0

+

bar: 1,2,3,1,1,2,3,2, num: 1

+

bar: 0,2,4,1,0,2,4,2, num: 2

+ `, + async test({ component, target, assert }) { + assert.htmlEqual( + target.innerHTML, + ` +

0

+

bar: 1,2,3,1,1,2,3,2, num: 1

+

bar: 0,2,4,1,0,2,4,2, num: 2

+ ` + ); + + component.nums = [1, 2, 3]; + + assert.htmlEqual( + target.innerHTML, + ` +

0

+

bar: 1,2,3,1,1,2,3,2,1,2,3,3, num: 1

+

bar: 0,2,4,1,0,2,4,2,0,2,4,3, num: 2

+

bar: -100,0,100,1,-100,0,100,2,-100,0,100,3, num: 3

+ ` + ); + } +}; diff --git a/test/runtime/samples/const-tag-each-const/main.svelte b/test/runtime/samples/const-tag-each-const/main.svelte new file mode 100644 index 0000000000..182ea3cfc6 --- /dev/null +++ b/test/runtime/samples/const-tag-each-const/main.svelte @@ -0,0 +1,26 @@ + + +

{foo}

+{#each nums as num, index} + {@const bar = nums.map((num) => { + const func = (foos, num) => { + return [...foos.map((foo) => foo), num]; + } + return func(foos[index].nums, num); + })} +

bar: {bar}, num: {num}

+{/each} diff --git a/test/runtime/samples/const-tag-each-duplicated-variable1/_config.js b/test/runtime/samples/const-tag-each-duplicated-variable1/_config.js new file mode 100644 index 0000000000..1f3e5856f2 --- /dev/null +++ b/test/runtime/samples/const-tag-each-duplicated-variable1/_config.js @@ -0,0 +1,29 @@ +export default { + html: ` +

bar: 1,2,3,0,2,4,-100,0,100, num: 1

+

bar: 1,2,3,0,2,4,-100,0,100, num: 2

+

bar: 1,2,3,0,2,4,-100,0,100, num: 3

+ `, + async test({ component, target, assert }) { + assert.htmlEqual( + target.innerHTML, + ` +

bar: 1,2,3,0,2,4,-100,0,100, num: 1

+

bar: 1,2,3,0,2,4,-100,0,100, num: 2

+

bar: 1,2,3,0,2,4,-100,0,100, num: 3

+ ` + ); + + component.nums = [1, 2, 3, 4]; + + assert.htmlEqual( + target.innerHTML, + ` +

bar: 1,2,3,0,2,4,-100,0,100, num: 1

+

bar: 1,2,3,0,2,4,-100,0,100, num: 2

+

bar: 1,2,3,0,2,4,-100,0,100, num: 3

+

bar: 1,2,3,0,2,4,-100,0,100, num: 4

+ ` + ); + } +}; diff --git a/test/runtime/samples/const-tag-each-duplicated-variable1/main.svelte b/test/runtime/samples/const-tag-each-duplicated-variable1/main.svelte new file mode 100644 index 0000000000..00c53dd4ff --- /dev/null +++ b/test/runtime/samples/const-tag-each-duplicated-variable1/main.svelte @@ -0,0 +1,19 @@ + + +{#each nums as num} + {@const bar = foos.map((foos) => foos.nums)} +

bar: {bar}, num: {num}

+{/each} diff --git a/test/runtime/samples/const-tag-each-duplicated-variable2/_config.js b/test/runtime/samples/const-tag-each-duplicated-variable2/_config.js new file mode 100644 index 0000000000..16f48218f1 --- /dev/null +++ b/test/runtime/samples/const-tag-each-duplicated-variable2/_config.js @@ -0,0 +1,32 @@ +export default { + html: ` +

foo: dummy-foo, num: dummy-num

+

bar: 1,2,3,2,, num: 1

+

bar: 1,2,3,2,, num: 2

+

bar: 1,2,3,2,, num: 3

+ `, + async test({ component, target, assert }) { + assert.htmlEqual( + target.innerHTML, + ` +

foo: dummy-foo, num: dummy-num

+

bar: 1,2,3,2,, num: 1

+

bar: 1,2,3,2,, num: 2

+

bar: 1,2,3,2,, num: 3

+ ` + ); + + component.nums = [1, 2, 3, 4]; + + assert.htmlEqual( + target.innerHTML, + ` +

foo: dummy-foo, num: dummy-num

+

bar: 1,2,3,2,4,, num: 1

+

bar: 1,2,3,2,4,, num: 2

+

bar: 1,2,3,2,4,, num: 3

+

bar: 1,2,3,2,4,, num: 4

+ ` + ); + } +}; diff --git a/test/runtime/samples/const-tag-each-duplicated-variable2/main.svelte b/test/runtime/samples/const-tag-each-duplicated-variable2/main.svelte new file mode 100644 index 0000000000..1f7afd92fd --- /dev/null +++ b/test/runtime/samples/const-tag-each-duplicated-variable2/main.svelte @@ -0,0 +1,33 @@ + + +

foo: {foo}, num: {num}

+{#each nums as num} + {@const bar = foos.map((foo) => + foo.nums.filter((num) => { + if (Object.keys($$slots).length) { + return false; + } else if (Object.keys(foo).length) { + return nums.includes(num) || default_nums.includes(num); + } else { + return false; + } + }) || num + )} +

bar: {bar}, num: {num}

+{/each} diff --git a/test/runtime/samples/const-tag-each-duplicated-variable3/_config.js b/test/runtime/samples/const-tag-each-duplicated-variable3/_config.js new file mode 100644 index 0000000000..cde4226a25 --- /dev/null +++ b/test/runtime/samples/const-tag-each-duplicated-variable3/_config.js @@ -0,0 +1,29 @@ +export default { + html: ` +

0

+

bar: 1,2,3,1,1,2,3,2, num: 1

+

bar: 0,2,4,1,0,2,4,2, num: 2

+ `, + async test({ component, target, assert }) { + assert.htmlEqual( + target.innerHTML, + ` +

0

+

bar: 1,2,3,1,1,2,3,2, num: 1

+

bar: 0,2,4,1,0,2,4,2, num: 2

+ ` + ); + + component.nums = [1, 2, 3]; + + assert.htmlEqual( + target.innerHTML, + ` +

0

+

bar: 1,2,3,1,1,2,3,2,1,2,3,3, num: 1

+

bar: 0,2,4,1,0,2,4,2,0,2,4,3, num: 2

+

bar: -100,0,100,1,-100,0,100,2,-100,0,100,3, num: 3

+ ` + ); + } +}; diff --git a/test/runtime/samples/const-tag-each-duplicated-variable3/main.svelte b/test/runtime/samples/const-tag-each-duplicated-variable3/main.svelte new file mode 100644 index 0000000000..f559627d23 --- /dev/null +++ b/test/runtime/samples/const-tag-each-duplicated-variable3/main.svelte @@ -0,0 +1,25 @@ + + +

{foo}

+{#each nums as num, index} + {@const bar = nums.map((num) => { + return (function (foos, num) { + return [...foos.map((foo) => foo), num]; + })(foos[index].nums, num); + })} +

bar: {bar}, num: {num}

+{/each} diff --git a/test/runtime/samples/const-tag-each-function/_config.js b/test/runtime/samples/const-tag-each-function/_config.js new file mode 100644 index 0000000000..cde4226a25 --- /dev/null +++ b/test/runtime/samples/const-tag-each-function/_config.js @@ -0,0 +1,29 @@ +export default { + html: ` +

0

+

bar: 1,2,3,1,1,2,3,2, num: 1

+

bar: 0,2,4,1,0,2,4,2, num: 2

+ `, + async test({ component, target, assert }) { + assert.htmlEqual( + target.innerHTML, + ` +

0

+

bar: 1,2,3,1,1,2,3,2, num: 1

+

bar: 0,2,4,1,0,2,4,2, num: 2

+ ` + ); + + component.nums = [1, 2, 3]; + + assert.htmlEqual( + target.innerHTML, + ` +

0

+

bar: 1,2,3,1,1,2,3,2,1,2,3,3, num: 1

+

bar: 0,2,4,1,0,2,4,2,0,2,4,3, num: 2

+

bar: -100,0,100,1,-100,0,100,2,-100,0,100,3, num: 3

+ ` + ); + } +}; diff --git a/test/runtime/samples/const-tag-each-function/main.svelte b/test/runtime/samples/const-tag-each-function/main.svelte new file mode 100644 index 0000000000..2e027c6620 --- /dev/null +++ b/test/runtime/samples/const-tag-each-function/main.svelte @@ -0,0 +1,26 @@ + + +

{foo}

+{#each nums as num, index} + {@const bar = nums.map((num) => { + function func(foos, num) { + return [...foos.map((foo) => foo), num]; + } + return func(foos[index].nums, num); + })} +

bar: {bar}, num: {num}

+{/each} diff --git a/test/runtime/samples/const-tag-shadow-2/_config.js b/test/runtime/samples/const-tag-shadow-2/_config.js new file mode 100644 index 0000000000..9ad4aed48c --- /dev/null +++ b/test/runtime/samples/const-tag-shadow-2/_config.js @@ -0,0 +1,37 @@ +export default { + html: ` +

1

+

3,6,9

+

2

+

3,6,9

+

3

+

3,6,9

+ `, + test({ component, target, assert }) { + component.baz = 5; + assert.htmlEqual( + target.innerHTML, + ` +

1

+

5,10,15

+

2

+

5,10,15

+

3

+

5,10,15

+ ` + ); + + component.array = [3, 4, 5]; + assert.htmlEqual( + target.innerHTML, + ` +

3

+

15,20,25

+

4

+

15,20,25

+

5

+

15,20,25

+ ` + ); + } +}; diff --git a/test/runtime/samples/const-tag-shadow-2/main.svelte b/test/runtime/samples/const-tag-shadow-2/main.svelte new file mode 100644 index 0000000000..c3bcb2f605 --- /dev/null +++ b/test/runtime/samples/const-tag-shadow-2/main.svelte @@ -0,0 +1,15 @@ + + +{#each array as item} +

{foo(item)}

+ {@const bar = array.map((item) => { + const bar = baz; + const foo = (item) => item * bar; + return foo(item); + })} +

{bar}

+{/each} From cd3bb35342492918f48e7aeecedfd9397d173f98 Mon Sep 17 00:00:00 2001 From: Simon H <5968653+dummdidumm@users.noreply.github.com> Date: Fri, 8 Apr 2022 19:32:35 +0200 Subject: [PATCH 47/57] update changelog --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index af883f9697..b343675fbe 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,7 @@ * Add `Symbol` as a known global ([#7419](https://github.com/sveltejs/svelte/pull/7419)) * Fix `{#key}` block not reactive when the key variable is not being used ([#7422](https://github.com/sveltejs/svelte/pull/7422)) * Add support for dynamic elements through `` ([#2324](https://github.com/sveltejs/svelte/issues/2324)) +* Better context checks for identifiers in `{@const ..}` tags ([#7222](https://github.com/sveltejs/svelte/pull/7222)) ## 3.46.6 From 467ba0a920d9b9902a2059085bac2662c6813b9a Mon Sep 17 00:00:00 2001 From: Conduitry Date: Fri, 8 Apr 2022 14:24:31 -0400 Subject: [PATCH 48/57] -> v3.47.0 --- CHANGELOG.md | 8 ++++---- package-lock.json | 4 ++-- package.json | 2 +- 3 files changed, 7 insertions(+), 7 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index b343675fbe..64b25a217d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,11 +1,11 @@ # Svelte changelog -## 3.47.0 (Unreleased) +## 3.47.0 -* Add `Symbol` as a known global ([#7419](https://github.com/sveltejs/svelte/pull/7419)) -* Fix `{#key}` block not reactive when the key variable is not being used ([#7422](https://github.com/sveltejs/svelte/pull/7422)) * Add support for dynamic elements through `` ([#2324](https://github.com/sveltejs/svelte/issues/2324)) -* Better context checks for identifiers in `{@const ..}` tags ([#7222](https://github.com/sveltejs/svelte/pull/7222)) +* Miscellaneous variable context fixes in `{@const}` ([#7222](https://github.com/sveltejs/svelte/pull/7222)) +* Fix `{#key}` block not being reactive when the key variable is not otherwise used ([#7408](https://github.com/sveltejs/svelte/issues/7408)) +* Add `Symbol` as a known global ([#7418](https://github.com/sveltejs/svelte/issues/7418)) ## 3.46.6 diff --git a/package-lock.json b/package-lock.json index 5f9fdfc5de..f42a96a32c 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,11 +1,11 @@ { "name": "svelte", - "version": "3.46.6", + "version": "3.47.0", "lockfileVersion": 2, "requires": true, "packages": { "": { - "version": "3.46.6", + "version": "3.47.0", "license": "MIT", "devDependencies": { "@ampproject/remapping": "^0.3.0", diff --git a/package.json b/package.json index 82dc1e84c7..2237ef40f1 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "svelte", - "version": "3.46.6", + "version": "3.47.0", "description": "Cybernetically enhanced web apps", "module": "index.mjs", "main": "index", From 707455fa8b29acd092c8b44382a89c347326b646 Mon Sep 17 00:00:00 2001 From: Kazuma Oe Date: Mon, 11 Apr 2022 00:50:15 +0900 Subject: [PATCH 49/57] [docs] add `` example (#7439) --- .../17-special-elements/02-svelte-element/App.svelte | 12 ++++++++++++ .../17-special-elements/02-svelte-element/meta.json | 3 +++ .../App.svelte | 0 .../{02-svelte-window => 03-svelte-window}/meta.json | 0 .../App.svelte | 0 .../meta.json | 0 .../{04-svelte-body => 05-svelte-body}/App.svelte | 0 .../{04-svelte-body => 05-svelte-body}/meta.json | 0 .../{05-svelte-head => 06-svelte-head}/App.svelte | 0 .../{05-svelte-head => 06-svelte-head}/meta.json | 0 10 files changed, 15 insertions(+) create mode 100644 site/content/examples/17-special-elements/02-svelte-element/App.svelte create mode 100644 site/content/examples/17-special-elements/02-svelte-element/meta.json rename site/content/examples/17-special-elements/{02-svelte-window => 03-svelte-window}/App.svelte (100%) rename site/content/examples/17-special-elements/{02-svelte-window => 03-svelte-window}/meta.json (100%) rename site/content/examples/17-special-elements/{03-svelte-window-bindings => 04-svelte-window-bindings}/App.svelte (100%) rename site/content/examples/17-special-elements/{03-svelte-window-bindings => 04-svelte-window-bindings}/meta.json (100%) rename site/content/examples/17-special-elements/{04-svelte-body => 05-svelte-body}/App.svelte (100%) rename site/content/examples/17-special-elements/{04-svelte-body => 05-svelte-body}/meta.json (100%) rename site/content/examples/17-special-elements/{05-svelte-head => 06-svelte-head}/App.svelte (100%) rename site/content/examples/17-special-elements/{05-svelte-head => 06-svelte-head}/meta.json (100%) diff --git a/site/content/examples/17-special-elements/02-svelte-element/App.svelte b/site/content/examples/17-special-elements/02-svelte-element/App.svelte new file mode 100644 index 0000000000..068f7b3c4f --- /dev/null +++ b/site/content/examples/17-special-elements/02-svelte-element/App.svelte @@ -0,0 +1,12 @@ + + + + +I'm a {selected} tag diff --git a/site/content/examples/17-special-elements/02-svelte-element/meta.json b/site/content/examples/17-special-elements/02-svelte-element/meta.json new file mode 100644 index 0000000000..4ff15b716f --- /dev/null +++ b/site/content/examples/17-special-elements/02-svelte-element/meta.json @@ -0,0 +1,3 @@ +{ + "title": "" +} \ No newline at end of file diff --git a/site/content/examples/17-special-elements/02-svelte-window/App.svelte b/site/content/examples/17-special-elements/03-svelte-window/App.svelte similarity index 100% rename from site/content/examples/17-special-elements/02-svelte-window/App.svelte rename to site/content/examples/17-special-elements/03-svelte-window/App.svelte diff --git a/site/content/examples/17-special-elements/02-svelte-window/meta.json b/site/content/examples/17-special-elements/03-svelte-window/meta.json similarity index 100% rename from site/content/examples/17-special-elements/02-svelte-window/meta.json rename to site/content/examples/17-special-elements/03-svelte-window/meta.json diff --git a/site/content/examples/17-special-elements/03-svelte-window-bindings/App.svelte b/site/content/examples/17-special-elements/04-svelte-window-bindings/App.svelte similarity index 100% rename from site/content/examples/17-special-elements/03-svelte-window-bindings/App.svelte rename to site/content/examples/17-special-elements/04-svelte-window-bindings/App.svelte diff --git a/site/content/examples/17-special-elements/03-svelte-window-bindings/meta.json b/site/content/examples/17-special-elements/04-svelte-window-bindings/meta.json similarity index 100% rename from site/content/examples/17-special-elements/03-svelte-window-bindings/meta.json rename to site/content/examples/17-special-elements/04-svelte-window-bindings/meta.json diff --git a/site/content/examples/17-special-elements/04-svelte-body/App.svelte b/site/content/examples/17-special-elements/05-svelte-body/App.svelte similarity index 100% rename from site/content/examples/17-special-elements/04-svelte-body/App.svelte rename to site/content/examples/17-special-elements/05-svelte-body/App.svelte diff --git a/site/content/examples/17-special-elements/04-svelte-body/meta.json b/site/content/examples/17-special-elements/05-svelte-body/meta.json similarity index 100% rename from site/content/examples/17-special-elements/04-svelte-body/meta.json rename to site/content/examples/17-special-elements/05-svelte-body/meta.json diff --git a/site/content/examples/17-special-elements/05-svelte-head/App.svelte b/site/content/examples/17-special-elements/06-svelte-head/App.svelte similarity index 100% rename from site/content/examples/17-special-elements/05-svelte-head/App.svelte rename to site/content/examples/17-special-elements/06-svelte-head/App.svelte diff --git a/site/content/examples/17-special-elements/05-svelte-head/meta.json b/site/content/examples/17-special-elements/06-svelte-head/meta.json similarity index 100% rename from site/content/examples/17-special-elements/05-svelte-head/meta.json rename to site/content/examples/17-special-elements/06-svelte-head/meta.json From afd3f4e5a9c279555db977bc9839ef662c9d5a0a Mon Sep 17 00:00:00 2001 From: gtmnayan <50981692+gtm-nayan@users.noreply.github.com> Date: Mon, 11 Apr 2022 06:35:28 +0545 Subject: [PATCH 50/57] [feat] Make setContext return the value that was passed in (#7432) * return value from setContext * update docs * Add test * eof new line * pacify the linter * const and tabs --- site/content/docs/03-run-time.md | 2 +- src/runtime/internal/lifecycle.ts | 3 ++- test/runtime/samples/context-setcontext-return/_config.js | 5 +++++ test/runtime/samples/context-setcontext-return/main.svelte | 7 +++++++ 4 files changed, 15 insertions(+), 2 deletions(-) create mode 100644 test/runtime/samples/context-setcontext-return/_config.js create mode 100644 test/runtime/samples/context-setcontext-return/main.svelte diff --git a/site/content/docs/03-run-time.md b/site/content/docs/03-run-time.md index 636a98834d..a0d3f4148c 100644 --- a/site/content/docs/03-run-time.md +++ b/site/content/docs/03-run-time.md @@ -148,7 +148,7 @@ setContext(key: any, context: any) --- -Associates an arbitrary `context` object with the current component and the specified `key`. The context is then available to children of the component (including slotted content) with `getContext`. +Associates an arbitrary `context` object with the current component and the specified `key` and returns that object. The context is then available to children of the component (including slotted content) with `getContext`. Like lifecycle functions, this must be called during component initialisation. diff --git a/src/runtime/internal/lifecycle.ts b/src/runtime/internal/lifecycle.ts index bb3df3d295..3b3c2f5f71 100644 --- a/src/runtime/internal/lifecycle.ts +++ b/src/runtime/internal/lifecycle.ts @@ -46,8 +46,9 @@ export function createEventDispatcher< }; } -export function setContext(key, context: T) { +export function setContext(key, context: T): T { get_current_component().$$.context.set(key, context); + return context; } export function getContext(key): T { diff --git a/test/runtime/samples/context-setcontext-return/_config.js b/test/runtime/samples/context-setcontext-return/_config.js new file mode 100644 index 0000000000..28a78f1359 --- /dev/null +++ b/test/runtime/samples/context-setcontext-return/_config.js @@ -0,0 +1,5 @@ +export default { + html: ` +
true
+ ` +}; diff --git a/test/runtime/samples/context-setcontext-return/main.svelte b/test/runtime/samples/context-setcontext-return/main.svelte new file mode 100644 index 0000000000..87153ee846 --- /dev/null +++ b/test/runtime/samples/context-setcontext-return/main.svelte @@ -0,0 +1,7 @@ + + +
{a === b}
From c36f1c066ef2dbfd1a414d358151a09d0fe7ae4c Mon Sep 17 00:00:00 2001 From: Tan Li Hau Date: Mon, 11 Apr 2022 08:51:37 +0800 Subject: [PATCH 51/57] Update CHANGELOG.md --- CHANGELOG.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 64b25a217d..1fa6ec5796 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,9 @@ # Svelte changelog +## Unreleased + +* Return the context object in `setContext` [#7427](https://github.com/sveltejs/svelte/issues/7427) + ## 3.47.0 * Add support for dynamic elements through `` ([#2324](https://github.com/sveltejs/svelte/issues/2324)) From 1e0c10bebcf57b9eca6bb60fe213853c792322df Mon Sep 17 00:00:00 2001 From: josh-brainbox <71662743+josh-brainbox@users.noreply.github.com> Date: Mon, 11 Apr 2022 03:36:17 -0400 Subject: [PATCH 52/57] [docs] fix tutorial/keyed-each-blocks (#7218) --- site/content/tutorial/04-logic/05-keyed-each-blocks/text.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/site/content/tutorial/04-logic/05-keyed-each-blocks/text.md b/site/content/tutorial/04-logic/05-keyed-each-blocks/text.md index b990dead95..76d8322fb0 100644 --- a/site/content/tutorial/04-logic/05-keyed-each-blocks/text.md +++ b/site/content/tutorial/04-logic/05-keyed-each-blocks/text.md @@ -4,7 +4,7 @@ title: Keyed each blocks By default, when you modify the value of an `each` block, it will add and remove items at the *end* of the block, and update any values that have changed. That might not be what you want. -It's easier to show why than to explain. Click the 'Remove first thing' button a few times, and notice what happens: It removes the first `` component, but the *last* DOM node. Then it updates the `name` value in the remaining DOM nodes, but not the emoji. +It's easier to show why than to explain. Click the 'Remove first thing' button a few times, and notice what happens: it does not remove the first `` component, but rather the *last* DOM node. Then it updates the `name` value in the remaining DOM nodes, but not the emoji. Instead, we'd like to remove only the first `` component and its DOM node, and leave the others unaffected. From e50be669cc73063eb9f66d0c2c4b7d3b61d0488d Mon Sep 17 00:00:00 2001 From: Tan Li Hau Date: Tue, 12 Apr 2022 16:38:08 +0800 Subject: [PATCH 53/57] fix const declared without let: binding get ignored (#7434) --- src/compiler/compile/nodes/SlotTemplate.ts | 5 +--- .../Component.svelte | 2 ++ .../_config.js | 15 ++++++++++++ .../main.svelte | 24 +++++++++++++++++++ 4 files changed, 42 insertions(+), 4 deletions(-) create mode 100644 test/runtime/samples/const-tag-component-without-let/Component.svelte create mode 100644 test/runtime/samples/const-tag-component-without-let/_config.js create mode 100644 test/runtime/samples/const-tag-component-without-let/main.svelte diff --git a/src/compiler/compile/nodes/SlotTemplate.ts b/src/compiler/compile/nodes/SlotTemplate.ts index cd18859603..90299c8e39 100644 --- a/src/compiler/compile/nodes/SlotTemplate.ts +++ b/src/compiler/compile/nodes/SlotTemplate.ts @@ -27,10 +27,7 @@ export default class SlotTemplate extends Node { this.validate_slot_template_placement(); - const has_let = info.attributes.some((node) => node.type === 'Let'); - if (has_let) { - scope = scope.child(); - } + scope = scope.child(); info.attributes.forEach((node) => { switch (node.type) { diff --git a/test/runtime/samples/const-tag-component-without-let/Component.svelte b/test/runtime/samples/const-tag-component-without-let/Component.svelte new file mode 100644 index 0000000000..e9e76f19b9 --- /dev/null +++ b/test/runtime/samples/const-tag-component-without-let/Component.svelte @@ -0,0 +1,2 @@ + + diff --git a/test/runtime/samples/const-tag-component-without-let/_config.js b/test/runtime/samples/const-tag-component-without-let/_config.js new file mode 100644 index 0000000000..047e034ce9 --- /dev/null +++ b/test/runtime/samples/const-tag-component-without-let/_config.js @@ -0,0 +1,15 @@ +export default { + html: ` +
static dynamic
+
static dynamic
+
static dynamic
+ `, + async test({ component, target, assert }) { + component.props = 'xxx'; + assert.htmlEqual(target.innerHTML, ` +
static xxx
+
static xxx
+
static xxx
+ `); + } +}; diff --git a/test/runtime/samples/const-tag-component-without-let/main.svelte b/test/runtime/samples/const-tag-component-without-let/main.svelte new file mode 100644 index 0000000000..9bee015c98 --- /dev/null +++ b/test/runtime/samples/const-tag-component-without-let/main.svelte @@ -0,0 +1,24 @@ + + + + + {@const foo = "static"} + {@const bar = props} +
{foo} {bar}
+
+ + + {@const foo = "static"} + {@const bar = props} +
{foo} {bar}
+
+
+ + + {@const foo = "static"} + {@const bar = props} +
{foo} {bar}
+
From fc19537995c8d2563345a4570e65ec6f08eb872d Mon Sep 17 00:00:00 2001 From: Tan Li Hau Date: Tue, 12 Apr 2022 16:40:34 +0800 Subject: [PATCH 54/57] Update CHANGELOG.md --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 1fa6ec5796..2a7b5ee0a2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,7 @@ ## Unreleased * Return the context object in `setContext` [#7427](https://github.com/sveltejs/svelte/issues/7427) +* Fix `{@const}` tag not working inside Component when there's no `let:` [#7189](https://github.com/sveltejs/svelte/issues/7189) ## 3.47.0 From d9687da63202633845e60e0538b3e075c5636ff8 Mon Sep 17 00:00:00 2001 From: Tan Li Hau Date: Tue, 12 Apr 2022 16:48:09 +0800 Subject: [PATCH 55/57] each animate allow having comment siblings (#7435) Co-authored-by: GHOST Co-authored-by: GHOST --- src/compiler/compile/nodes/EachBlock.ts | 5 ++++- .../samples/animation-comment-siblings/errors.json | 1 + .../samples/animation-comment-siblings/input.svelte | 9 +++++++++ 3 files changed, 14 insertions(+), 1 deletion(-) create mode 100644 test/validator/samples/animation-comment-siblings/errors.json create mode 100644 test/validator/samples/animation-comment-siblings/input.svelte diff --git a/src/compiler/compile/nodes/EachBlock.ts b/src/compiler/compile/nodes/EachBlock.ts index bb78845dd9..bea6fb7910 100644 --- a/src/compiler/compile/nodes/EachBlock.ts +++ b/src/compiler/compile/nodes/EachBlock.ts @@ -63,7 +63,7 @@ export default class EachBlock extends AbstractBlock { ([this.const_tags, this.children] = get_const_tags(info.children, component, this, this)); if (this.has_animation) { - this.children = this.children.filter(child => !isEmptyNode(child)); + this.children = this.children.filter(child => !isEmptyNode(child) && !isCommentNode(child)); if (this.children.length !== 1) { const child = this.children.find(child => !!(child as Element).animation); @@ -83,3 +83,6 @@ export default class EachBlock extends AbstractBlock { function isEmptyNode(node: INode) { return node.type === 'Text' && node.data.trim() === ''; } +function isCommentNode(node: INode) { + return node.type === 'Comment'; +} diff --git a/test/validator/samples/animation-comment-siblings/errors.json b/test/validator/samples/animation-comment-siblings/errors.json new file mode 100644 index 0000000000..fe51488c70 --- /dev/null +++ b/test/validator/samples/animation-comment-siblings/errors.json @@ -0,0 +1 @@ +[] diff --git a/test/validator/samples/animation-comment-siblings/input.svelte b/test/validator/samples/animation-comment-siblings/input.svelte new file mode 100644 index 0000000000..1c8170950b --- /dev/null +++ b/test/validator/samples/animation-comment-siblings/input.svelte @@ -0,0 +1,9 @@ + + +{#each things as thing (thing)} + +
+{/each} \ No newline at end of file From f100d96898b4845ebef666c8c887ae6add3b0952 Mon Sep 17 00:00:00 2001 From: Tan Li Hau Date: Tue, 12 Apr 2022 16:49:59 +0800 Subject: [PATCH 56/57] Update CHANGELOG.md --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 2a7b5ee0a2..947173fd4d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,7 @@ * Return the context object in `setContext` [#7427](https://github.com/sveltejs/svelte/issues/7427) * Fix `{@const}` tag not working inside Component when there's no `let:` [#7189](https://github.com/sveltejs/svelte/issues/7189) +* Ignore comments in `{#each}` blocks when containing elements with `animate:` ([#3999](https://github.com/sveltejs/svelte/issues/3999)) ## 3.47.0 From c371c3fd3c70f35ef5b7e9b65d3539f6ef205767 Mon Sep 17 00:00:00 2001 From: tarunama Date: Tue, 12 Apr 2022 17:57:11 +0900 Subject: [PATCH 57/57] execute `npm audit fix` (#7421) --- package-lock.json | 34 +++++++++++++++++----------------- 1 file changed, 17 insertions(+), 17 deletions(-) diff --git a/package-lock.json b/package-lock.json index f42a96a32c..0b5a130992 100644 --- a/package-lock.json +++ b/package-lock.json @@ -2989,9 +2989,9 @@ "dev": true }, "node_modules/json-schema": { - "version": "0.2.3", - "resolved": "https://registry.npmjs.org/json-schema/-/json-schema-0.2.3.tgz", - "integrity": "sha1-tIDIkuWaLwWVTOcnvT8qTogvnhM=", + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/json-schema/-/json-schema-0.4.0.tgz", + "integrity": "sha512-es94M3nTIfsEPisRafak+HDLfHXnKBhV3vU5eqPcS3flIWqcxJWgXHXiey3YrpaNsanY5ei1VoYEbOzijuq9BA==", "dev": true }, "node_modules/json-schema-traverse": { @@ -3025,18 +3025,18 @@ } }, "node_modules/jsprim": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/jsprim/-/jsprim-1.4.1.tgz", - "integrity": "sha1-MT5mvB5cwG5Di8G3SZwuXFastqI=", + "version": "1.4.2", + "resolved": "https://registry.npmjs.org/jsprim/-/jsprim-1.4.2.tgz", + "integrity": "sha512-P2bSOMAc/ciLz6DzgjVlGJP9+BrJWu5UDGK70C2iweC5QBIeFf0ZXRvGjEj2uYgrY2MkAAhsSWHDWlFtEroZWw==", "dev": true, - "engines": [ - "node >=0.6.0" - ], "dependencies": { "assert-plus": "1.0.0", "extsprintf": "1.3.0", - "json-schema": "0.2.3", + "json-schema": "0.4.0", "verror": "1.10.0" + }, + "engines": { + "node": ">=0.6.0" } }, "node_modules/kleur": { @@ -7426,9 +7426,9 @@ "dev": true }, "json-schema": { - "version": "0.2.3", - "resolved": "https://registry.npmjs.org/json-schema/-/json-schema-0.2.3.tgz", - "integrity": "sha1-tIDIkuWaLwWVTOcnvT8qTogvnhM=", + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/json-schema/-/json-schema-0.4.0.tgz", + "integrity": "sha512-es94M3nTIfsEPisRafak+HDLfHXnKBhV3vU5eqPcS3flIWqcxJWgXHXiey3YrpaNsanY5ei1VoYEbOzijuq9BA==", "dev": true }, "json-schema-traverse": { @@ -7459,14 +7459,14 @@ } }, "jsprim": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/jsprim/-/jsprim-1.4.1.tgz", - "integrity": "sha1-MT5mvB5cwG5Di8G3SZwuXFastqI=", + "version": "1.4.2", + "resolved": "https://registry.npmjs.org/jsprim/-/jsprim-1.4.2.tgz", + "integrity": "sha512-P2bSOMAc/ciLz6DzgjVlGJP9+BrJWu5UDGK70C2iweC5QBIeFf0ZXRvGjEj2uYgrY2MkAAhsSWHDWlFtEroZWw==", "dev": true, "requires": { "assert-plus": "1.0.0", "extsprintf": "1.3.0", - "json-schema": "0.2.3", + "json-schema": "0.4.0", "verror": "1.10.0" } },