diff --git a/CHANGELOG.md b/CHANGELOG.md
index bdee28d1dc..a3ce06485e 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -4,13 +4,22 @@
* Fix `{#each}` context not shadowing outer scope when using `bind:` ([#1565](https://github.com/sveltejs/svelte/issues/1565))
* Fix edge cases in matching selectors against elements ([#1710](https://github.com/sveltejs/svelte/issues/1710))
+* Allow exiting a reactive block early with `break $` ([#2828](https://github.com/sveltejs/svelte/issues/2828))
+* Don't lose `class:` directive classes on an element with `{...spread}` attributes when updating ([#3421](https://github.com/sveltejs/svelte/issues/3421))
+* Check attributes have changed before setting them to avoid image flicker ([#3579](https://github.com/sveltejs/svelte/pull/3579))
+* Fix generating malformed code for `{@debug}` tags with no dependencies ([#3588](https://github.com/sveltejs/svelte/issue/3588))
+* Fix generated code in specific case involving compound ifs and child components ([#3595](https://github.com/sveltejs/svelte/issue/3595))
+* Fix `bind:this` binding to a store ([#3591](https://github.com/sveltejs/svelte/issue/3591))
* Use safer `HTMLElement` check before extending class ([#3608](https://github.com/sveltejs/svelte/issue/3608))
* Add `location` as a known global ([#3619](https://github.com/sveltejs/svelte/pull/3619))
+* Support `{#await}` with `{:catch}` but no `{:then}` ([#3623](https://github.com/sveltejs/svelte/issues/3623))
+* Clean up dead code emitted for ``s ([#3631](https://github.com/sveltejs/svelte/issues/3631))
* Fix tracking of dependencies of compound assignments in reactive statements ([#3634](https://github.com/sveltejs/svelte/issues/3634))
* Flush changes in newly attached block when using `{#await}` ([#3660](https://github.com/sveltejs/svelte/issues/3660))
* Throw exception immediately when calling `createEventDispatcher()` after component instantiation ([#3667](https://github.com/sveltejs/svelte/pull/3667))
* Fix globals shadowing contextual template scope ([#3674](https://github.com/sveltejs/svelte/issues/3674))
* Fix error resulting from trying to set a read-only property when spreading element attributes ([#3681](https://github.com/sveltejs/svelte/issues/3681))
+* Fix handling of boolean attributes in presence of other spread attributes ([#3764](https://github.com/sveltejs/svelte/issues/3764))
## 3.12.1
diff --git a/package-lock.json b/package-lock.json
index 99290966cc..1ca8b7cdb5 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -1,6 +1,6 @@
{
"name": "svelte",
- "version": "3.13.0-alpha.0",
+ "version": "3.13.0-alpha.1",
"lockfileVersion": 1,
"requires": true,
"dependencies": {
@@ -167,9 +167,9 @@
"dev": true
},
"acorn": {
- "version": "7.0.0",
- "resolved": "https://registry.npmjs.org/acorn/-/acorn-7.0.0.tgz",
- "integrity": "sha512-PaF/MduxijYYt7unVGRuds1vBC9bFxbNf+VWqhOClfdgy7RlVkQqt610ig1/yxTgsDIfW1cWDel5EBbOy3jdtQ==",
+ "version": "7.1.0",
+ "resolved": "https://registry.npmjs.org/acorn/-/acorn-7.1.0.tgz",
+ "integrity": "sha512-kL5CuoXA/dgxlBbVrflsflzQ3PAas7RYZB52NOm/6839iVYJgKMJ3cQJD+t2i5+qFa8h3MDpEOJiS64E8JLnSQ==",
"dev": true
},
"acorn-globals": {
@@ -490,14 +490,14 @@
"dev": true
},
"code-red": {
- "version": "0.0.17",
- "resolved": "https://registry.npmjs.org/code-red/-/code-red-0.0.17.tgz",
- "integrity": "sha512-RJJ48sXYOqyd0J4QelF4dRdYb+4DaLV/jHs4mNoxOdLroUGB840cMc9pMtEAbGKjFFzoTKREypFzqphBD8knMg==",
+ "version": "0.0.18",
+ "resolved": "https://registry.npmjs.org/code-red/-/code-red-0.0.18.tgz",
+ "integrity": "sha512-g7W6RwRqBbQTtMaUqrNWDyyl2GK0Uulk/uZPzGdgTXpOGX/LA8bW67EKQLdQgpYfd6APhZVwoX2lrL7mnJOWkA==",
"dev": true,
"requires": {
- "acorn": "^7.0.0",
- "is-reference": "^1.1.3",
- "periscopic": "^1.0.1",
+ "acorn": "^7.1.0",
+ "is-reference": "^1.1.4",
+ "periscopic": "^1.0.2",
"sourcemap-codec": "^1.4.6"
}
},
@@ -1147,14 +1147,6 @@
"acorn": "^7.0.0",
"acorn-jsx": "^5.0.2",
"eslint-visitor-keys": "^1.1.0"
- },
- "dependencies": {
- "acorn": {
- "version": "7.0.0",
- "resolved": "https://registry.npmjs.org/acorn/-/acorn-7.0.0.tgz",
- "integrity": "sha512-PaF/MduxijYYt7unVGRuds1vBC9bFxbNf+VWqhOClfdgy7RlVkQqt610ig1/yxTgsDIfW1cWDel5EBbOy3jdtQ==",
- "dev": true
- }
}
},
"esprima": {
@@ -1768,9 +1760,9 @@
"dev": true
},
"is-reference": {
- "version": "1.1.3",
- "resolved": "https://registry.npmjs.org/is-reference/-/is-reference-1.1.3.tgz",
- "integrity": "sha512-W1iHHv/oyBb2pPxkBxtaewxa1BC58Pn5J0hogyCdefwUIvb6R+TGbAcIa4qPNYLqLhb3EnOgUf2MQkkF76BcKw==",
+ "version": "1.1.4",
+ "resolved": "https://registry.npmjs.org/is-reference/-/is-reference-1.1.4.tgz",
+ "integrity": "sha512-uJA/CDPO3Tao3GTrxYn6AwkM4nUPJiGGYu5+cB8qbC7WGFlrKZbiRo7SFKxUAEpFUfiHofWCXBUNhvYJMh+6zw==",
"dev": true,
"requires": {
"@types/estree": "0.0.39"
@@ -2727,17 +2719,6 @@
"dev": true,
"requires": {
"is-reference": "^1.1.4"
- },
- "dependencies": {
- "is-reference": {
- "version": "1.1.4",
- "resolved": "https://registry.npmjs.org/is-reference/-/is-reference-1.1.4.tgz",
- "integrity": "sha512-uJA/CDPO3Tao3GTrxYn6AwkM4nUPJiGGYu5+cB8qbC7WGFlrKZbiRo7SFKxUAEpFUfiHofWCXBUNhvYJMh+6zw==",
- "dev": true,
- "requires": {
- "@types/estree": "0.0.39"
- }
- }
}
},
"pify": {
@@ -3646,14 +3627,6 @@
"@types/istanbul-lib-coverage": "^2.0.1",
"convert-source-map": "^1.6.0",
"source-map": "^0.7.3"
- },
- "dependencies": {
- "source-map": {
- "version": "0.7.3",
- "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.7.3.tgz",
- "integrity": "sha512-CkCj6giN3S+n9qrYiBTX5gystlENnRW5jZeNLHpe6aue+SrHcG5VYwujhW9s4dY31mEGsxBDrHR6oI69fTXsaQ==",
- "dev": true
- }
}
},
"validate-npm-package-license": {
diff --git a/package.json b/package.json
index c4f1269139..950c44d965 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
{
"name": "svelte",
- "version": "3.13.0-alpha.0",
+ "version": "3.13.0-alpha.1",
"description": "Cybernetically enhanced web apps",
"module": "index.mjs",
"main": "index",
@@ -60,17 +60,17 @@
"@types/node": "^8.10.53",
"@typescript-eslint/eslint-plugin": "^1.13.0",
"@typescript-eslint/parser": "^2.1.0",
- "acorn": "^7.0.0",
+ "acorn": "^7.1.0",
"agadoo": "^1.1.0",
"c8": "^5.0.1",
- "code-red": "0.0.17",
+ "code-red": "0.0.18",
"codecov": "^3.5.0",
"css-tree": "1.0.0-alpha22",
"eslint": "^6.3.0",
"eslint-plugin-import": "^2.18.2",
"eslint-plugin-svelte3": "^2.7.3",
"estree-walker": "^0.8.1",
- "is-reference": "^1.1.3",
+ "is-reference": "^1.1.4",
"jsdom": "^15.1.1",
"kleur": "^3.0.3",
"locate-character": "^2.0.5",
diff --git a/site/content/docs/03-run-time.md b/site/content/docs/03-run-time.md
index 551945eade..19a19c4f41 100644
--- a/site/content/docs/03-run-time.md
+++ b/site/content/docs/03-run-time.md
@@ -184,7 +184,7 @@ dispatch: ((name: string, detail?: any) => void) = createEventDispatcher();
---
-Creates an event dispatcher that can be used to dispatch [component events](docs#Component_events). Event dispatchers are functions that can take two arguments: `name` and `detail`.
+Creates an event dispatcher that can be used to dispatch [component events](docs#on_component_event). Event dispatchers are functions that can take two arguments: `name` and `detail`.
Component events created with `createEventDispatcher` create a [CustomEvent](https://developer.mozilla.org/en-US/docs/Web/API/CustomEvent). These events do not [bubble](https://developer.mozilla.org/en-US/docs/Learn/JavaScript/Building_blocks/Events#Event_bubbling_and_capture) and are not cancellable with `event.preventDefault()`. The `detail` argument corresponds to the [CustomEvent.detail](https://developer.mozilla.org/en-US/docs/Web/API/CustomEvent/detail) property and can contain any type of data.
@@ -1019,8 +1019,12 @@ Unlike client-side components, server-side components don't have a lifespan afte
A server-side component exposes a `render` method that can be called with optional props. It returns an object with `head`, `html`, and `css` properties, where `head` contains the contents of any `` elements encountered.
+You can import a Svelte component directly into Node using [`svelte/register`](docs#svelte_register).
+
```js
-const App = require('./App.svelte');
+require('svelte/register');
+
+const App = require('./App.svelte').default;
const { head, html, css } = App.render({
answer: 42
diff --git a/site/src/routes/_components/WhosUsingSvelte.svelte b/site/src/routes/_components/WhosUsingSvelte.svelte
index fe9a2fcc9d..2a944fe289 100644
--- a/site/src/routes/_components/WhosUsingSvelte.svelte
+++ b/site/src/routes/_components/WhosUsingSvelte.svelte
@@ -55,6 +55,7 @@
+
diff --git a/site/src/server.js b/site/src/server.js
index fb003961bc..c7cf93c3b5 100644
--- a/site/src/server.js
+++ b/site/src/server.js
@@ -15,7 +15,7 @@ const app = polka({
});
if (process.env.PGHOST) {
- app.use(authenticate);
+ app.use(authenticate());
}
app.use(
@@ -34,4 +34,4 @@ app.use(
})
);
-app.listen(PORT);
\ No newline at end of file
+app.listen(PORT);
diff --git a/site/static/organisations/entur.svg b/site/static/organisations/entur.svg
new file mode 100644
index 0000000000..98bb5cc937
--- /dev/null
+++ b/site/static/organisations/entur.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/src/compiler/compile/Component.ts b/src/compiler/compile/Component.ts
index ee24fc700c..b22b351da0 100644
--- a/src/compiler/compile/Component.ts
+++ b/src/compiler/compile/Component.ts
@@ -90,15 +90,6 @@ export default class Component {
file: string;
locate: (c: number) => { line: number; column: number };
- // TODO this does the same as component.locate! remove one or the other
- locator: (
- search: number,
- startIndex?: number
- ) => {
- line: number;
- column: number;
- };
-
stylesheet: Stylesheet;
aliases: Map = new Map();
@@ -140,7 +131,7 @@ export default class Component {
.replace(process.cwd(), '')
.replace(/^[/\\]/, '')
: compile_options.filename);
- this.locate = getLocator(this.source);
+ this.locate = getLocator(this.source, { offsetLine: 1 });
// styles
this.stylesheet = new Stylesheet(
@@ -438,12 +429,8 @@ export default class Component {
return;
}
- if (!this.locator) {
- this.locator = getLocator(this.source, { offsetLine: 1 });
- }
-
- const start = this.locator(pos.start);
- const end = this.locator(pos.end);
+ const start = this.locate(pos.start);
+ const end = this.locate(pos.end);
const frame = get_code_frame(this.source, start.line - 1, start.column);
@@ -456,7 +443,7 @@ export default class Component {
pos: pos.start,
filename: this.compile_options.filename,
toString: () =>
- `${warning.message} (${start.line + 1}:${start.column})\n${frame}`,
+ `${warning.message} (${start.line}:${start.column})\n${frame}`,
});
}
@@ -582,6 +569,7 @@ export default class Component {
this.add_var({
name,
global: true,
+ hoistable: true
});
}
});
@@ -674,6 +662,7 @@ export default class Component {
this.add_var({
name,
global: true,
+ hoistable: true
});
}
});
@@ -772,12 +761,12 @@ export default class Component {
invalidate(name, value?) {
const variable = this.var_lookup.get(name);
- if (variable && (variable.subscribable && variable.reassigned)) {
+ if (variable && (variable.subscribable && (variable.reassigned || variable.export_name))) {
return x`${`$$subscribe_${name}`}($$invalidate('${name}', ${value || name}))`;
}
if (name[0] === '$' && name[1] !== '$') {
- return x`${name.slice(1)}.set(${name})`;
+ return x`${name.slice(1)}.set(${value || name})`;
}
if (
diff --git a/src/compiler/compile/nodes/Attribute.ts b/src/compiler/compile/nodes/Attribute.ts
index c09f9c3074..97d2fd7b2e 100644
--- a/src/compiler/compile/nodes/Attribute.ts
+++ b/src/compiler/compile/nodes/Attribute.ts
@@ -9,7 +9,7 @@ import TemplateScope from './shared/TemplateScope';
import { x } from 'code-red';
export default class Attribute extends Node {
- type: 'Attribute';
+ type: 'Attribute' | 'Spread';
start: number;
end: number;
scope: TemplateScope;
diff --git a/src/compiler/compile/nodes/shared/Expression.ts b/src/compiler/compile/nodes/shared/Expression.ts
index 4a3e96a282..ad4a1bc24d 100644
--- a/src/compiler/compile/nodes/shared/Expression.ts
+++ b/src/compiler/compile/nodes/shared/Expression.ts
@@ -3,7 +3,7 @@ import { walk } from 'estree-walker';
import is_reference from 'is-reference';
import flatten_reference from '../../utils/flatten_reference';
import { create_scopes, Scope, extract_names } from '../../utils/scope';
-import { globals, sanitize } from '../../../utils/names';
+import { sanitize } from '../../../utils/names';
import Wrapper from '../../render_dom/wrappers/shared/Wrapper';
import TemplateScope from './TemplateScope';
import get_object from '../../utils/get_object';
@@ -75,8 +75,6 @@ export default class Expression {
if (scope.has(name)) return;
- if (globals.has(name) && !(component.var_lookup.has(name) || template_scope.names.has(name))) return;
-
if (name[0] === '$' && template_scope.names.has(name.slice(1))) {
component.error(node, {
code: `contextual-store`,
@@ -202,7 +200,6 @@ export default class Expression {
const { name } = flatten_reference(node);
if (scope.has(name)) return;
- if (globals.has(name) && !(component.var_lookup.has(name) || template_scope.names.has(name))) return;
if (function_expression) {
if (template_scope.names.has(name)) {
diff --git a/src/compiler/compile/render_dom/Block.ts b/src/compiler/compile/render_dom/Block.ts
index f73212f3ba..b268954dd0 100644
--- a/src/compiler/compile/render_dom/Block.ts
+++ b/src/compiler/compile/render_dom/Block.ts
@@ -146,11 +146,13 @@ export default class Block {
if (!wrapper.var) continue;
+ let suffix = '';
if (dupes.has(wrapper.var.name)) {
const i = counts.get(wrapper.var.name) || 0;
counts.set(wrapper.var.name, i + 1);
- wrapper.var.name = this.get_unique_name(wrapper.var.name + i).name;
+ suffix = i;
}
+ wrapper.var.name = this.get_unique_name(wrapper.var.name + suffix).name;
}
}
diff --git a/src/compiler/compile/render_dom/index.ts b/src/compiler/compile/render_dom/index.ts
index bd7b99bd8f..72f81cfb05 100644
--- a/src/compiler/compile/render_dom/index.ts
+++ b/src/compiler/compile/render_dom/index.ts
@@ -221,18 +221,15 @@ export default function dom(
}
});
- component.rewrite_props(({ name, reassigned }) => {
+ component.rewrite_props(({ name, reassigned, export_name }) => {
const value = `$${name}`;
+
+ const insert = (reassigned || export_name)
+ ? b`${`$$subscribe_${name}`}()`
+ : b`@component_subscribe($$self, ${name}, #value => $$invalidate('${value}', ${value} = #value))`;
- if (reassigned) {
- return b`${`$$subscribe_${name}`}()`;
- }
-
- const callback = x`$$value => $$invalidate('${value}', ${value} = $$value)`;
-
- let insert = b`@component_subscribe($$self, ${name}, $${callback})`;
if (component.compile_options.dev) {
- insert = b`@validate_store(${name}, '${name}'); ${insert}`;
+ return b`@validate_store(${name}, '${name}'); ${insert}`;
}
return insert;
@@ -304,14 +301,14 @@ export default function dom(
return !variable || variable.hoistable;
})
.map(({ name }) => b`
- ${component.compile_options.dev && `@validate_store(${name.slice(1)}, '${name.slice(1)}');`}
+ ${component.compile_options.dev && b`@validate_store(${name.slice(1)}, '${name.slice(1)}');`}
@component_subscribe($$self, ${name.slice(1)}, $$value => $$invalidate('${name}', ${name} = $$value));
`);
const resubscribable_reactive_store_unsubscribers = reactive_stores
.filter(store => {
const variable = component.var_lookup.get(store.name.slice(1));
- return variable && variable.reassigned;
+ return variable && (variable.reassigned || variable.export_name);
})
.map(({ name }) => b`$$self.$$.on_destroy.push(() => ${`$$unsubscribe_${name.slice(1)}`}());`);
@@ -353,7 +350,7 @@ export default function dom(
const name = $name.slice(1);
const store = component.var_lookup.get(name);
- if (store && store.reassigned) {
+ if (store && (store.reassigned || store.export_name)) {
const unsubscribe = `$$unsubscribe_${name}`;
const subscribe = `$$subscribe_${name}`;
return b`let ${$name}, ${unsubscribe} = @noop, ${subscribe} = () => (${unsubscribe}(), ${unsubscribe} = @subscribe(${name}, $$value => $$invalidate('${$name}', ${$name} = $$value)), ${name})`;
diff --git a/src/compiler/compile/render_dom/wrappers/DebugTag.ts b/src/compiler/compile/render_dom/wrappers/DebugTag.ts
index 87e186f3b4..dc3a2f2857 100644
--- a/src/compiler/compile/render_dom/wrappers/DebugTag.ts
+++ b/src/compiler/compile/render_dom/wrappers/DebugTag.ts
@@ -30,7 +30,6 @@ export default class DebugTagWrapper extends Wrapper {
const { var_lookup } = component;
const start = component.locate(this.node.start + 1);
- start.line += 1;
const end = { line: start.line, column: start.column + 6 };
const loc = { start, end };
diff --git a/src/compiler/compile/render_dom/wrappers/EachBlock.ts b/src/compiler/compile/render_dom/wrappers/EachBlock.ts
index 5c8d193f56..922679c39f 100644
--- a/src/compiler/compile/render_dom/wrappers/EachBlock.ts
+++ b/src/compiler/compile/render_dom/wrappers/EachBlock.ts
@@ -108,7 +108,6 @@ export default class EachBlockWrapper extends Wrapper {
let c = this.node.start + 2;
while (renderer.component.source[c] !== 'e') c += 1;
const start = renderer.component.locate(c);
- start.line += 1;
const end = { line: start.line, column: start.column + 4 };
const length = {
type: 'Identifier',
diff --git a/src/compiler/compile/render_dom/wrappers/Element/Attribute.ts b/src/compiler/compile/render_dom/wrappers/Element/Attribute.ts
index 876be588d6..2cd284108c 100644
--- a/src/compiler/compile/render_dom/wrappers/Element/Attribute.ts
+++ b/src/compiler/compile/render_dom/wrappers/Element/Attribute.ts
@@ -44,9 +44,7 @@ export default class AttributeWrapper {
const element = this.parent;
const name = fix_attribute_casing(this.node.name);
- let metadata = element.node.namespace ? null : attribute_lookup[name];
- if (metadata && metadata.applies_to && !~metadata.applies_to.indexOf(element.node.name))
- metadata = null;
+ const metadata = this.get_metadata();
const is_indirectly_bound_value =
name === 'value' &&
@@ -95,7 +93,7 @@ export default class AttributeWrapper {
const is_select_value_attribute =
name === 'value' && element.node.name === 'select';
- const should_cache = (this.node.should_cache() || is_select_value_attribute);
+ const should_cache = is_select_value_attribute; // TODO is this necessary?
const last = should_cache && block.get_unique_name(
`${element.var.name}_${name.replace(/[^a-zA-Z_$]/g, '_')}_value`
@@ -193,6 +191,13 @@ export default class AttributeWrapper {
}
}
+ get_metadata() {
+ if (this.parent.node.namespace) return null;
+ const metadata = attribute_lookup[fix_attribute_casing(this.node.name)];
+ if (metadata && metadata.applies_to && !metadata.applies_to.includes(this.parent.node.name)) return null;
+ return metadata;
+ }
+
get_class_name_text() {
const scoped_css = this.node.chunks.some((chunk: Text) => chunk.synthetic);
const rendered = this.render_chunks();
diff --git a/src/compiler/compile/render_dom/wrappers/Element/index.ts b/src/compiler/compile/render_dom/wrappers/Element/index.ts
index 3f1b411966..8f54ebf468 100644
--- a/src/compiler/compile/render_dom/wrappers/Element/index.ts
+++ b/src/compiler/compile/render_dom/wrappers/Element/index.ts
@@ -354,7 +354,7 @@ export default class ElementWrapper extends Wrapper {
if (renderer.options.dev) {
const loc = renderer.locate(this.node.start);
block.chunks.hydrate.push(
- b`@add_location(${this.var}, ${renderer.file_var}, ${loc.line}, ${loc.column}, ${this.node.start});`
+ b`@add_location(${this.var}, ${renderer.file_var}, ${loc.line - 1}, ${loc.column}, ${this.node.start});`
);
}
}
@@ -573,8 +573,7 @@ export default class ElementWrapper extends Wrapper {
}
});
- // @ts-ignore todo:
- if (this.node.attributes.find(attr => attr.type === 'Spread')) {
+ if (this.node.attributes.some(attr => attr.is_spread)) {
this.add_spread_attributes(block);
return;
}
@@ -591,21 +590,24 @@ export default class ElementWrapper extends Wrapper {
const initial_props = [];
const updates = [];
- this.node.attributes
- .filter(attr => attr.type === 'Attribute' || attr.type === 'Spread')
+ this.attributes
.forEach(attr => {
- const condition = attr.dependencies.size > 0
- ? changed(Array.from(attr.dependencies))
+ const condition = attr.node.dependencies.size > 0
+ ? changed(Array.from(attr.node.dependencies))
: null;
- if (attr.is_spread) {
- const snippet = attr.expression.manipulate(block);
+ if (attr.node.is_spread) {
+ const snippet = attr.node.expression.manipulate(block);
initial_props.push(snippet);
updates.push(condition ? x`${condition} && ${snippet}` : snippet);
} else {
- const snippet = x`{ ${attr.name}: ${attr.get_value(block)} }`;
+ const metadata = attr.get_metadata();
+ const snippet = x`{ ${
+ (metadata && metadata.property_name) ||
+ fix_attribute_casing(attr.node.name)
+ }: ${attr.node.get_value(block)} }`;
initial_props.push(snippet);
updates.push(condition ? x`${condition} && ${snippet}` : snippet);
@@ -807,6 +809,7 @@ export default class ElementWrapper extends Wrapper {
}
add_classes(block: Block) {
+ const has_spread = this.node.attributes.some(attr => attr.is_spread);
this.node.classes.forEach(class_directive => {
const { expression, name } = class_directive;
let snippet;
@@ -822,7 +825,9 @@ export default class ElementWrapper extends Wrapper {
block.chunks.hydrate.push(updater);
- if ((dependencies && dependencies.size > 0) || this.class_dependencies.length) {
+ if (has_spread) {
+ block.chunks.update.push(updater);
+ } else if ((dependencies && dependencies.size > 0) || this.class_dependencies.length) {
const all_dependencies = this.class_dependencies.concat(...dependencies);
const condition = changed(all_dependencies);
diff --git a/src/compiler/compile/render_dom/wrappers/IfBlock.ts b/src/compiler/compile/render_dom/wrappers/IfBlock.ts
index c54994a36c..dff7851b5a 100644
--- a/src/compiler/compile/render_dom/wrappers/IfBlock.ts
+++ b/src/compiler/compile/render_dom/wrappers/IfBlock.ts
@@ -271,8 +271,8 @@ export default class IfBlockWrapper extends Wrapper {
? b`
${snippet && (
dependencies.length > 0
- ? b`if ((${condition} == null) || ${changed(dependencies)}) ${condition} = !!(${snippet})`
- : b`if (${condition} == null) ${condition} = !!(${snippet})`
+ ? b`if (${condition} == null || ${changed(dependencies)}) ${condition} = !!${snippet}`
+ : b`if (${condition} == null) ${condition} = !!${snippet}`
)}
if (${condition}) return ${block.name};`
: b`return ${block.name};`)}
@@ -388,7 +388,11 @@ export default class IfBlockWrapper extends Wrapper {
function ${select_block_type}(#changed, #ctx) {
${this.branches.map(({ dependencies, condition, snippet }, i) => condition
? b`
- ${snippet && b`if ((${condition} == null) || ${changed(dependencies)}) ${condition} = !!(${snippet})`}
+ ${snippet && (
+ dependencies.length > 0
+ ? b`if (${condition} == null || ${changed(dependencies)}) ${condition} = !!${snippet}`
+ : b`if (${condition} == null) ${condition} = !!${snippet}`
+ )}
if (${condition}) return ${i};`
: b`return ${i};`)}
${!has_else && b`return -1;`}
diff --git a/src/compiler/compile/render_dom/wrappers/Slot.ts b/src/compiler/compile/render_dom/wrappers/Slot.ts
index 1ab1b6abcb..fb90afab5b 100644
--- a/src/compiler/compile/render_dom/wrappers/Slot.ts
+++ b/src/compiler/compile/render_dom/wrappers/Slot.ts
@@ -137,12 +137,12 @@ export default class SlotWrapper extends Wrapper {
block.render_listeners(`_${slot.name}`);
block.event_listeners = listeners;
- if (block.chunks.create) create.push(b`if (!${slot}) { ${block.chunks.create} }`);
- if (block.chunks.claim) claim.push(b`if (!${slot}) { ${block.chunks.claim} }`);
- if (block.chunks.hydrate) hydrate.push(b`if (!${slot}) { ${block.chunks.hydrate} }`);
- if (block.chunks.mount) mount.push(b`if (!${slot}) { ${block.chunks.mount} }`);
- if (block.chunks.update) update.push(b`if (!${slot}) { ${block.chunks.update} }`);
- if (block.chunks.destroy) destroy.push(b`if (!${slot}) { ${block.chunks.destroy} }`);
+ if (block.chunks.create.length) create.push(b`if (!${slot}) { ${block.chunks.create} }`);
+ if (block.chunks.claim.length) claim.push(b`if (!${slot}) { ${block.chunks.claim} }`);
+ if (block.chunks.hydrate.length) hydrate.push(b`if (!${slot}) { ${block.chunks.hydrate} }`);
+ if (block.chunks.mount.length) mount.push(b`if (!${slot}) { ${block.chunks.mount} }`);
+ if (block.chunks.update.length) update.push(b`if (!${slot}) { ${block.chunks.update} }`);
+ if (block.chunks.destroy.length) destroy.push(b`if (!${slot}) { ${block.chunks.destroy} }`);
block.chunks.create = create;
block.chunks.claim = claim;
@@ -155,9 +155,11 @@ export default class SlotWrapper extends Wrapper {
b`if (${slot}) ${slot}.c();`
);
- block.chunks.claim.push(
- b`if (${slot}) ${slot}.l(${parent_nodes});`
- );
+ if (renderer.options.hydratable) {
+ block.chunks.claim.push(
+ b`if (${slot}) ${slot}.l(${parent_nodes});`
+ );
+ }
block.chunks.mount.push(b`
if (${slot}) {
diff --git a/src/compiler/compile/render_dom/wrappers/shared/create_debugging_comment.ts b/src/compiler/compile/render_dom/wrappers/shared/create_debugging_comment.ts
index df1ae9ff8e..e2aa0c0b41 100644
--- a/src/compiler/compile/render_dom/wrappers/shared/create_debugging_comment.ts
+++ b/src/compiler/compile/render_dom/wrappers/shared/create_debugging_comment.ts
@@ -26,7 +26,7 @@ export default function create_debugging_comment(
}
const start = locate(c);
- const loc = `(${start.line + 1}:${start.column})`;
+ const loc = `(${start.line}:${start.column})`;
return `${loc} ${source.slice(c, d)}`.replace(/\s/g, ' ');
}
diff --git a/src/compiler/compile/render_ssr/handlers/DebugTag.ts b/src/compiler/compile/render_ssr/handlers/DebugTag.ts
index 6955d1d1e6..99323a9c4d 100644
--- a/src/compiler/compile/render_ssr/handlers/DebugTag.ts
+++ b/src/compiler/compile/render_ssr/handlers/DebugTag.ts
@@ -12,5 +12,5 @@ export default function(node: DebugTag, renderer: Renderer, options: RenderOptio
${node.expressions.map(e => p`${e.node.name}`)}
}`;
- renderer.add_expression(x`@debug(${filename ? x`"${filename}"` : x`null`}, ${line}, ${column}, ${obj})`);
+ renderer.add_expression(x`@debug(${filename ? x`"${filename}"` : x`null`}, ${line - 1}, ${column}, ${obj})`);
}
diff --git a/src/compiler/compile/render_ssr/handlers/Element.ts b/src/compiler/compile/render_ssr/handlers/Element.ts
index 762152f4d5..1f7c0b7b9f 100644
--- a/src/compiler/compile/render_ssr/handlers/Element.ts
+++ b/src/compiler/compile/render_ssr/handlers/Element.ts
@@ -1,5 +1,4 @@
import { is_void } from '../../../utils/names';
-import Attribute from '../../nodes/Attribute';
import Class from '../../nodes/Class';
import { get_attribute_value, get_class_attribute_value } from './shared/get_attribute_value';
import { get_slot_scope } from './shared/get_slot_scope';
@@ -80,62 +79,61 @@ export default function(node: Element, renderer: Renderer, options: RenderOption
let add_class_attribute = class_expression ? true : false;
- if (node.attributes.find(attr => attr.is_spread)) {
+ if (node.attributes.some(attr => attr.is_spread)) {
// TODO dry this out
const args = [];
node.attributes.forEach(attribute => {
if (attribute.is_spread) {
args.push(attribute.expression.node);
} else {
- if (attribute.name === 'value' && node.name === 'textarea') {
+ const name = attribute.name.toLowerCase();
+ if (name === 'value' && node.name.toLowerCase() === 'textarea') {
node_contents = get_attribute_value(attribute);
} else if (attribute.is_true) {
args.push(x`{ ${attribute.name}: true }`);
} else if (
- boolean_attributes.has(attribute.name) &&
+ boolean_attributes.has(name) &&
attribute.chunks.length === 1 &&
attribute.chunks[0].type !== 'Text'
) {
// a boolean attribute with one non-Text chunk
- args.push(x`{ ${attribute.name}: ${(attribute.chunks[0] as Expression).node} }`);
- } else if (attribute.name === 'class' && class_expression) {
+ args.push(x`{ ${attribute.name}: ${(attribute.chunks[0] as Expression).node} || null }`);
+ } else if (name === 'class' && class_expression) {
// Add class expression
args.push(x`{ ${attribute.name}: [${get_class_attribute_value(attribute)}, ${class_expression}].join(' ').trim() }`);
} else {
- args.push(x`{ ${attribute.name}: ${attribute.name === 'class' ? get_class_attribute_value(attribute) : get_attribute_value(attribute)} }`);
+ args.push(x`{ ${attribute.name}: ${(name === 'class' ? get_class_attribute_value : get_attribute_value)(attribute)} }`);
}
}
});
renderer.add_expression(x`@spread([${args}])`);
} else {
- node.attributes.forEach((attribute: Attribute) => {
- if (attribute.type !== 'Attribute') return;
-
- if (attribute.name === 'value' && node.name === 'textarea') {
+ node.attributes.forEach(attribute => {
+ const name = attribute.name.toLowerCase();
+ if (name === 'value' && node.name.toLowerCase() === 'textarea') {
node_contents = get_attribute_value(attribute);
} else if (attribute.is_true) {
renderer.add_string(` ${attribute.name}`);
} else if (
- boolean_attributes.has(attribute.name) &&
+ boolean_attributes.has(name) &&
attribute.chunks.length === 1 &&
attribute.chunks[0].type !== 'Text'
) {
// a boolean attribute with one non-Text chunk
renderer.add_string(` `);
renderer.add_expression(x`${(attribute.chunks[0] as Expression).node} ? "${attribute.name}" : ""`);
- } else if (attribute.name === 'class' && class_expression) {
+ } else if (name === 'class' && class_expression) {
add_class_attribute = false;
- renderer.add_string(` class="`);
+ renderer.add_string(` ${attribute.name}="`);
renderer.add_expression(x`[${get_class_attribute_value(attribute)}, ${class_expression}].join(' ').trim()`);
renderer.add_string(`"`);
} else if (attribute.chunks.length === 1 && attribute.chunks[0].type !== 'Text') {
- const { name } = attribute;
const snippet = (attribute.chunks[0] as Expression).node;
- renderer.add_expression(x`@add_attribute("${name}", ${snippet}, ${boolean_attributes.has(name) ? 1 : 0})`);
+ renderer.add_expression(x`@add_attribute("${attribute.name}", ${snippet}, ${boolean_attributes.has(name) ? 1 : 0})`);
} else {
renderer.add_string(` ${attribute.name}="`);
- renderer.add_expression(attribute.name === 'class' ? get_class_attribute_value(attribute) : get_attribute_value(attribute));
+ renderer.add_expression((name === 'class' ? get_class_attribute_value : get_attribute_value)(attribute));
renderer.add_string(`"`);
}
});
diff --git a/src/compiler/compile/render_ssr/index.ts b/src/compiler/compile/render_ssr/index.ts
index d193508a89..00157cc256 100644
--- a/src/compiler/compile/render_ssr/index.ts
+++ b/src/compiler/compile/render_ssr/index.ts
@@ -98,6 +98,8 @@ export default function ssr(
: b`
let ${left} = ${right}`;
}
+ } else { // TODO do not add label if it's not referenced
+ statement = b`$: { ${statement} }`;
}
return statement;
diff --git a/src/compiler/parse/acorn.ts b/src/compiler/parse/acorn.ts
index a7d2703956..30ab3c398c 100644
--- a/src/compiler/parse/acorn.ts
+++ b/src/compiler/parse/acorn.ts
@@ -4,13 +4,11 @@ const Parser = acorn.Parser;
export const parse = (source: string) => Parser.parse(source, {
sourceType: 'module',
- // @ts-ignore TODO pending release of fixed types
ecmaVersion: 11,
locations: true
});
export const parse_expression_at = (source: string, index: number) => Parser.parseExpressionAt(source, index, {
- // @ts-ignore TODO pending release of fixed types
ecmaVersion: 11,
locations: true
});
\ No newline at end of file
diff --git a/src/compiler/parse/read/script.ts b/src/compiler/parse/read/script.ts
index efc9306677..0c416be6e8 100644
--- a/src/compiler/parse/read/script.ts
+++ b/src/compiler/parse/read/script.ts
@@ -45,7 +45,7 @@ export default function read_script(parser: Parser, start: number, attributes: N
let ast: Program;
try {
- ast = acorn.parse(source);
+ ast = acorn.parse(source) as any as Program;
} catch (err) {
parser.acorn_error(err);
}
diff --git a/src/compiler/parse/state/mustache.ts b/src/compiler/parse/state/mustache.ts
index 4a1578ada5..140e722b10 100644
--- a/src/compiler/parse/state/mustache.ts
+++ b/src/compiler/parse/state/mustache.ts
@@ -160,57 +160,47 @@ export default function mustache(parser: Parser) {
parser.stack.push(block.else);
}
- } else if (parser.eat(':then')) {
- // TODO DRY out this and the next section
- const pending_block = parser.current();
- if (pending_block.type === 'PendingBlock') {
- pending_block.end = start;
- parser.stack.pop();
- const await_block = parser.current();
+ } else if (parser.match(':then') || parser.match(':catch')) {
+ const block = parser.current();
+ const is_then = parser.eat(':then') || !parser.eat(':catch');
- if (!parser.eat('}')) {
- parser.require_whitespace();
- await_block.value = parser.read_identifier();
- parser.allow_whitespace();
- parser.eat('}', true);
+ if (is_then) {
+ if (block.type !== 'PendingBlock') {
+ parser.error({
+ code: `invalid-then-placement`,
+ message: 'Cannot have an {:then} block outside an {#await ...} block'
+ });
}
-
- const then_block: TemplateNode = {
- start,
- end: null,
- type: 'ThenBlock',
- children: [],
- skip: false
- };
-
- await_block.then = then_block;
- parser.stack.push(then_block);
- }
- } else if (parser.eat(':catch')) {
- const then_block = parser.current();
- if (then_block.type === 'ThenBlock') {
- then_block.end = start;
- parser.stack.pop();
- const await_block = parser.current();
-
- if (!parser.eat('}')) {
- parser.require_whitespace();
- await_block.error = parser.read_identifier();
- parser.allow_whitespace();
- parser.eat('}', true);
+ } else {
+ if (block.type !== 'ThenBlock' && block.type !== 'PendingBlock') {
+ parser.error({
+ code: `invalid-catch-placement`,
+ message: 'Cannot have an {:catch} block outside an {#await ...} block'
+ });
}
+ }
- const catch_block: TemplateNode = {
- start,
- end: null,
- type: 'CatchBlock',
- children: [],
- skip: false
- };
+ block.end = start;
+ parser.stack.pop();
+ const await_block = parser.current();
- await_block.catch = catch_block;
- parser.stack.push(catch_block);
+ if (!parser.eat('}')) {
+ parser.require_whitespace();
+ await_block[is_then ? 'value': 'error'] = parser.read_identifier();
+ parser.allow_whitespace();
+ parser.eat('}', true);
}
+
+ const new_block: TemplateNode = {
+ start,
+ end: null,
+ type: is_then ? 'ThenBlock': 'CatchBlock',
+ children: [],
+ skip: false
+ };
+
+ await_block[is_then ? 'then' : 'catch'] = new_block;
+ parser.stack.push(new_block);
} else if (parser.eat('#')) {
// {#if foo}, {#each foo} or {#await foo}
let type;
diff --git a/src/compiler/parse/state/tag.ts b/src/compiler/parse/state/tag.ts
index 880c866ffe..c7e761afc2 100644
--- a/src/compiler/parse/state/tag.ts
+++ b/src/compiler/parse/state/tag.ts
@@ -81,7 +81,7 @@ export default function tag(parser: Parser) {
parser.current().children.length
) {
parser.error({
- code: `invalid-${name.slice(7)}-content`,
+ code: `invalid-${slug}-content`,
message: `<${name}> cannot have children`
}, parser.current().children[0].start);
}
diff --git a/src/runtime/internal/dom.ts b/src/runtime/internal/dom.ts
index 1c904fe2a3..c60f437863 100644
--- a/src/runtime/internal/dom.ts
+++ b/src/runtime/internal/dom.ts
@@ -86,14 +86,16 @@ export function self(fn) {
export function attr(node: Element, attribute: string, value?: string) {
if (value == null) node.removeAttribute(attribute);
- else node.setAttribute(attribute, value);
+ else if (node.getAttribute(attribute) !== value) node.setAttribute(attribute, value);
}
export function set_attributes(node: Element & ElementCSSInlineStyle, attributes: { [x: string]: string }) {
// @ts-ignore
const descriptors = Object.getOwnPropertyDescriptors(node.__proto__);
for (const key in attributes) {
- if (key === 'style') {
+ if (attributes[key] == null) {
+ node.removeAttribute(key);
+ } else if (key === 'style') {
node.style.cssText = attributes[key];
} else if (descriptors[key] && descriptors[key].set) {
node[key] = attributes[key];
diff --git a/src/runtime/internal/ssr.ts b/src/runtime/internal/ssr.ts
index d8fbf15f0a..208a4637c7 100644
--- a/src/runtime/internal/ssr.ts
+++ b/src/runtime/internal/ssr.ts
@@ -13,7 +13,7 @@ export function spread(args) {
if (invalid_attribute_name_character.test(name)) return;
const value = attributes[name];
- if (value === undefined) return;
+ if (value == null) return;
if (value === true) str += " " + name;
const escaped = String(value)
diff --git a/src/runtime/store/index.ts b/src/runtime/store/index.ts
index 0aff706a1b..64a63d4179 100644
--- a/src/runtime/store/index.ts
+++ b/src/runtime/store/index.ts
@@ -122,16 +122,30 @@ type StoresValues = T extends Readable ? U :
/**
* Derived value store by synchronizing one or more readable stores and
* applying an aggregation function over its input values.
- * @param {Stores} stores input stores
- * @param {function(Stores=, function(*)=):*}fn function callback that aggregates the values
- * @param {*=}initial_value when used asynchronously
+ *
+ * @param stores - input stores
+ * @param fn - function callback that aggregates the values
*/
-export function derived(
+export function derived(
stores: S,
- fn: (values: StoresValues, set: Subscriber) => T | Unsubscriber | void,
- initial_value?: T,
-): Readable {
+ fn: (values: StoresValues) => T
+): Readable;
+/**
+ * Derived value store by synchronizing one or more readable stores and
+ * applying an aggregation function over its input values.
+ *
+ * @param stores - input stores
+ * @param fn - function callback that aggregates the values
+ * @param initial_value - when used asynchronously
+ */
+export function derived(
+ stores: S,
+ fn: (values: StoresValues, set: (value: T) => void) => Unsubscriber | void,
+ initial_value?: T
+): Readable;
+
+export function derived(stores: Stores, fn: Function, initial_value?: T): Readable {
const single = !Array.isArray(stores);
const stores_array: Array> = single
? [stores as Readable]
@@ -141,7 +155,7 @@ export function derived(
return readable(initial_value, (set) => {
let inited = false;
- const values: StoresValues = [] as StoresValues;
+ const values = [];
let pending = 0;
let cleanup = noop;
diff --git a/test/css/index.js b/test/css/index.js
index 61e8a0c6cb..1f90d243be 100644
--- a/test/css/index.js
+++ b/test/css/index.js
@@ -37,7 +37,7 @@ function create(code) {
}
describe('css', () => {
- fs.readdirSync('test/css/samples').forEach(dir => {
+ fs.readdirSync(`${__dirname}/samples`).forEach(dir => {
if (dir[0] === '.') return;
// add .solo to a sample directory name to only run that test
@@ -51,7 +51,7 @@ describe('css', () => {
(solo ? it.only : skip ? it.skip : it)(dir, () => {
const config = try_require(`./samples/${dir}/_config.js`) || {};
const input = fs
- .readFileSync(`test/css/samples/${dir}/input.svelte`, 'utf-8')
+ .readFileSync(`${__dirname}/samples/${dir}/input.svelte`, 'utf-8')
.replace(/\s+$/, '');
const expected_warnings = (config.warnings || []).map(normalize_warning);
@@ -74,10 +74,10 @@ describe('css', () => {
assert.deepEqual(dom_warnings, ssr_warnings);
assert.deepEqual(dom_warnings.map(normalize_warning), expected_warnings);
- fs.writeFileSync(`test/css/samples/${dir}/_actual.css`, dom.css.code);
+ fs.writeFileSync(`${__dirname}/samples/${dir}/_actual.css`, dom.css.code);
const expected = {
- html: read(`test/css/samples/${dir}/expected.html`),
- css: read(`test/css/samples/${dir}/expected.css`)
+ html: read(`${__dirname}/samples/${dir}/expected.html`),
+ css: read(`${__dirname}/samples/${dir}/expected.css`)
};
assert.equal(dom.css.code.replace(/svelte(-ref)?-[a-z0-9]+/g, (m, $1) => $1 ? m : 'svelte-xyz'), expected.css);
@@ -112,7 +112,7 @@ describe('css', () => {
new ClientComponent({ target, props: config.props });
const html = target.innerHTML;
- fs.writeFileSync(`test/css/samples/${dir}/_actual.html`, html);
+ fs.writeFileSync(`${__dirname}/samples/${dir}/_actual.html`, html);
assert.equal(
normalizeHtml(window, html.replace(/svelte(-ref)?-[a-z0-9]+/g, (m, $1) => $1 ? m : 'svelte-xyz')),
diff --git a/test/custom-elements/index.js b/test/custom-elements/index.js
index 7fe47f2ecd..5d2847bfab 100644
--- a/test/custom-elements/index.js
+++ b/test/custom-elements/index.js
@@ -14,7 +14,7 @@ const page = `