Merge branch 'master' into customStyleTag

pull/5639/head
Ivan Hofer 5 years ago
commit a474470a93

@ -1,5 +1,15 @@
# Svelte changelog # Svelte changelog
## 3.30.0
* Add a typed `SvelteComponent` interface ([#5431](https://github.com/sveltejs/svelte/pull/5431))
* Support spread into `<slot>` props ([#5456](https://github.com/sveltejs/svelte/issues/5456))
* Fix setting reactive dependencies which don't appear in the template to `undefined` ([#5538](https://github.com/sveltejs/svelte/issues/5538))
* Support preprocessor sourcemaps during compilation ([#5584](https://github.com/sveltejs/svelte/pull/5584))
* Fix ordering of elements when using `{#if}` inside `{#key}` ([#5680](https://github.com/sveltejs/svelte/issues/5680))
* Add `hasContext` lifecycle function ([#5690](https://github.com/sveltejs/svelte/pull/5690))
* Fix missing `walk` types in `svelte/compiler` ([#5696](https://github.com/sveltejs/svelte/pull/5696))
## 3.29.7 ## 3.29.7
* Include `./register` in exports map ([#5670](https://github.com/sveltejs/svelte/issues/5670)) * Include `./register` in exports map ([#5670](https://github.com/sveltejs/svelte/issues/5670))

2
package-lock.json generated

@ -1,6 +1,6 @@
{ {
"name": "svelte", "name": "svelte",
"version": "3.29.7", "version": "3.30.0",
"lockfileVersion": 1, "lockfileVersion": 1,
"requires": true, "requires": true,
"dependencies": { "dependencies": {

@ -1,6 +1,6 @@
{ {
"name": "svelte", "name": "svelte",
"version": "3.29.7", "version": "3.30.0",
"description": "Cybernetically enhanced web apps", "description": "Cybernetically enhanced web apps",
"module": "index.mjs", "module": "index.mjs",
"main": "index", "main": "index",

@ -20,7 +20,7 @@ const ts_plugin = is_publish
const external = id => id.startsWith('svelte/'); const external = id => id.startsWith('svelte/');
fs.writeFileSync(`./compiler.d.ts`, `export { compile, parse, preprocess, VERSION } from './types/compiler/index';`); fs.writeFileSync(`./compiler.d.ts`, `export { compile, parse, preprocess, walk, VERSION } from './types/compiler/index';`);
export default [ export default [
/* runtime */ /* runtime */

@ -178,6 +178,26 @@ Retrieves the context that belongs to the closest parent component with the spec
</script> </script>
``` ```
#### `hasContext`
```js
hasContext: boolean = hasContext(key: any)
```
---
Checks whether a given `key` has been set in the context of a parent component. Must be called during component initialisation.
```sv
<script>
import { hasContext } from 'svelte';
if (hasContext('answer')) {
// do something
}
</script>
```
#### `createEventDispatcher` #### `createEventDispatcher`
```js ```js

@ -15,7 +15,7 @@ export default class Slot extends Element {
super(component, parent, scope, info); super(component, parent, scope, info);
info.attributes.forEach(attr => { info.attributes.forEach(attr => {
if (attr.type !== 'Attribute') { if (attr.type !== 'Attribute' && attr.type !== 'Spread') {
component.error(attr, { component.error(attr, {
code: 'invalid-slot-directive', code: 'invalid-slot-directive',
message: '<slot> cannot have directives' message: '<slot> cannot have directives'

@ -111,8 +111,9 @@ export default class Renderer {
// these determine whether variable is included in initial context // these determine whether variable is included in initial context
// array, so must have the highest priority // array, so must have the highest priority
if (variable.export_name) member.priority += 16; if (variable.is_reactive_dependency && (variable.mutated || variable.reassigned)) member.priority += 16;
if (variable.referenced) member.priority += 32; if (variable.export_name) member.priority += 32;
if (variable.referenced) member.priority += 64;
} else if (member.is_non_contextual) { } else if (member.is_non_contextual) {
// determine whether variable is included in initial context // determine whether variable is included in initial context
// array, so must have the highest priority // array, so must have the highest priority
@ -131,7 +132,7 @@ export default class Renderer {
while (i--) { while (i--) {
const member = this.context[i]; const member = this.context[i];
if (member.variable) { if (member.variable) {
if (member.variable.referenced || member.variable.export_name) break; if (member.variable.referenced || member.variable.export_name || (member.variable.is_reactive_dependency && (member.variable.mutated || member.variable.reassigned))) break;
} else if (member.is_non_contextual) { } else if (member.is_non_contextual) {
break; break;
} }
@ -220,7 +221,7 @@ export default class Renderer {
.reduce((lhs, rhs) => x`${lhs}, ${rhs}`); .reduce((lhs, rhs) => x`${lhs}, ${rhs}`);
} }
dirty(names, is_reactive_declaration = false): Expression { dirty(names: string[], is_reactive_declaration = false): Expression {
const renderer = this; const renderer = this;
const dirty = (is_reactive_declaration const dirty = (is_reactive_declaration

@ -670,7 +670,7 @@ export default class ElementWrapper extends Wrapper {
// handle edge cases for elements // handle edge cases for elements
if (this.node.name === 'select') { if (this.node.name === 'select') {
const dependencies = new Set(); const dependencies = new Set<string>();
for (const attr of this.attributes) { for (const attr of this.attributes) {
for (const dep of attr.node.dependencies) { for (const dep of attr.node.dependencies) {
dependencies.add(dep); dependencies.add(dep);

@ -44,7 +44,7 @@ export default class KeyBlockWrapper extends Wrapper {
renderer, renderer,
this.block, this.block,
node.children, node.children,
parent, this,
strip_whitespace, strip_whitespace,
next_sibling next_sibling
); );

@ -8,7 +8,6 @@ import { sanitize } from '../../../utils/names';
import add_to_set from '../../utils/add_to_set'; import add_to_set from '../../utils/add_to_set';
import get_slot_data from '../../utils/get_slot_data'; import get_slot_data from '../../utils/get_slot_data';
import { is_reserved_keyword } from '../../utils/reserved_keywords'; import { is_reserved_keyword } from '../../utils/reserved_keywords';
import Expression from '../../nodes/shared/Expression';
import is_dynamic from './shared/is_dynamic'; import is_dynamic from './shared/is_dynamic';
import { Identifier, ObjectExpression } from 'estree'; import { Identifier, ObjectExpression } from 'estree';
import create_debugging_comment from './shared/create_debugging_comment'; import create_debugging_comment from './shared/create_debugging_comment';
@ -82,6 +81,7 @@ export default class SlotWrapper extends Wrapper {
} }
let get_slot_changes_fn; let get_slot_changes_fn;
let get_slot_spread_changes_fn;
let get_slot_context_fn; let get_slot_context_fn;
if (this.node.values.size > 0) { if (this.node.values.size > 0) {
@ -90,25 +90,17 @@ export default class SlotWrapper extends Wrapper {
const changes = x`{}` as ObjectExpression; const changes = x`{}` as ObjectExpression;
const dependencies = new Set(); const spread_dynamic_dependencies = new Set<string>();
this.node.values.forEach(attribute => { this.node.values.forEach(attribute => {
attribute.chunks.forEach(chunk => { if (attribute.type === 'Spread') {
if ((chunk as Expression).dependencies) { add_to_set(spread_dynamic_dependencies, Array.from(attribute.dependencies).filter((name) => this.is_dependency_dynamic(name)));
add_to_set(dependencies, (chunk as Expression).contextual_dependencies); } else {
const dynamic_dependencies = Array.from(attribute.dependencies).filter((name) => this.is_dependency_dynamic(name));
// add_to_set(dependencies, (chunk as Expression).dependencies);
(chunk as Expression).dependencies.forEach(name => {
const variable = renderer.component.var_lookup.get(name);
if (variable && !variable.hoistable) dependencies.add(name);
});
}
});
const dynamic_dependencies = Array.from(attribute.dependencies).filter((name) => this.is_dependency_dynamic(name));
if (dynamic_dependencies.length > 0) { if (dynamic_dependencies.length > 0) {
changes.properties.push(p`${attribute.name}: ${renderer.dirty(dynamic_dependencies)}`); changes.properties.push(p`${attribute.name}: ${renderer.dirty(dynamic_dependencies)}`);
}
} }
}); });
@ -116,6 +108,13 @@ export default class SlotWrapper extends Wrapper {
const ${get_slot_changes_fn} = #dirty => ${changes}; const ${get_slot_changes_fn} = #dirty => ${changes};
const ${get_slot_context_fn} = #ctx => ${get_slot_data(this.node.values, block)}; const ${get_slot_context_fn} = #ctx => ${get_slot_data(this.node.values, block)};
`); `);
if (spread_dynamic_dependencies.size) {
get_slot_spread_changes_fn = renderer.component.get_unique_name(`get_${sanitize(slot_name)}_slot_spread_changes`);
renderer.blocks.push(b`
const ${get_slot_spread_changes_fn} = #dirty => ${renderer.dirty(Array.from(spread_dynamic_dependencies))} > 0 ? -1 : 0;
`);
}
} else { } else {
get_slot_changes_fn = 'null'; get_slot_changes_fn = 'null';
get_slot_context_fn = 'null'; get_slot_context_fn = 'null';
@ -170,7 +169,11 @@ export default class SlotWrapper extends Wrapper {
? Array.from(this.fallback.dependencies).filter((name) => this.is_dependency_dynamic(name)) ? Array.from(this.fallback.dependencies).filter((name) => this.is_dependency_dynamic(name))
: []; : [];
const slot_update = b` const slot_update = get_slot_spread_changes_fn ? b`
if (${slot}.p && ${renderer.dirty(dynamic_dependencies)}) {
@update_slot_spread(${slot}, ${slot_definition}, #ctx, ${renderer.reference('$$scope')}, #dirty, ${get_slot_changes_fn}, ${get_slot_spread_changes_fn}, ${get_slot_context_fn});
}
`: b`
if (${slot}.p && ${renderer.dirty(dynamic_dependencies)}) { if (${slot}.p && ${renderer.dirty(dynamic_dependencies)}) {
@update_slot(${slot}, ${slot_definition}, #ctx, ${renderer.reference('$$scope')}, #dirty, ${get_slot_changes_fn}, ${get_slot_context_fn}); @update_slot(${slot}, ${slot_definition}, #ctx, ${renderer.reference('$$scope')}, #dirty, ${get_slot_changes_fn}, ${get_slot_context_fn});
} }

@ -9,6 +9,14 @@ export default function get_slot_data(values: Map<string, Attribute>, block: Blo
properties: Array.from(values.values()) properties: Array.from(values.values())
.filter(attribute => attribute.name !== 'name') .filter(attribute => attribute.name !== 'name')
.map(attribute => { .map(attribute => {
if (attribute.is_spread) {
const argument = get_spread_value(block, attribute);
return {
type: 'SpreadElement',
argument
};
}
const value = get_value(block, attribute); const value = get_value(block, attribute);
return p`${attribute.name}: ${value}`; return p`${attribute.name}: ${value}`;
}) })
@ -29,3 +37,7 @@ function get_value(block: Block, attribute: Attribute) {
return value; return value;
} }
function get_spread_value(block: Block, attribute: Attribute) {
return block ? attribute.expression.manipulate(block) : attribute.expression.node;
}

@ -213,16 +213,19 @@ if (typeof HTMLElement === 'function') {
}; };
} }
export class SvelteComponent { export class SvelteComponent<
Props extends Record<string, any> = any,
Events extends Record<string, any> = any
> {
$$: T$$; $$: T$$;
$$set?: ($$props: any) => void; $$set?: ($$props: Partial<Props>) => void;
$destroy() { $destroy() {
destroy_component(this, 1); destroy_component(this, 1);
this.$destroy = noop; this.$destroy = noop;
} }
$on(type, callback) { $on<K extends Extract<keyof Events, string>>(type: K, callback: (e: Events[K]) => void) {
const callbacks = (this.$$.callbacks[type] || (this.$$.callbacks[type] = [])); const callbacks = (this.$$.callbacks[type] || (this.$$.callbacks[type] = []));
callbacks.push(callback); callbacks.push(callback);
@ -232,7 +235,7 @@ export class SvelteComponent {
}; };
} }
$set($$props) { $set($$props: Partial<Props>) {
if (this.$$set && !is_empty($$props)) { if (this.$$set && !is_empty($$props)) {
this.$$.skip_bound = true; this.$$.skip_bound = true;
this.$$set($$props); this.$$set($$props);

@ -97,15 +97,44 @@ export function validate_slots(name, slot, keys) {
} }
} }
type Props = Record<string, any>; export interface SvelteComponentDev<
export interface SvelteComponentDev { Props extends Record<string, any> = any,
$set(props?: Props): void; Events extends Record<string, any> = any,
$on<T = any>(event: string, callback: (event: CustomEvent<T>) => void): () => void; Slots extends Record<string, any> = any
> {
$set(props?: Partial<Props>): void;
$on<K extends Extract<keyof Events, string>>(type: K, callback: (e: Events[K]) => void): () => void;
$destroy(): void; $destroy(): void;
[accessor: string]: any; [accessor: string]: any;
} }
export class SvelteComponentDev extends SvelteComponent { export class SvelteComponentDev<
Props extends Record<string, any> = any,
Events extends Record<string, any> = any,
Slots extends Record<string, any> = any
> extends SvelteComponent<Props, Events> {
/**
* @private
* For type checking capabilities only.
* Does not exist at runtime.
* ### DO NOT USE!
*/
$$prop_def: Props;
/**
* @private
* For type checking capabilities only.
* Does not exist at runtime.
* ### DO NOT USE!
*/
$$events_def: Events;
/**
* @private
* For type checking capabilities only.
* Does not exist at runtime.
* ### DO NOT USE!
*/
$$slot_def: Slots;
constructor(options: { constructor(options: {
target: Element; target: Element;
anchor?: Element; anchor?: Element;
@ -113,7 +142,7 @@ export class SvelteComponentDev extends SvelteComponent {
hydrate?: boolean; hydrate?: boolean;
intro?: boolean; intro?: boolean;
$$inline?: boolean; $$inline?: boolean;
}) { }) {
if (!options || (!options.target && !options.$$inline)) { if (!options || (!options.target && !options.$$inline)) {
throw new Error("'target' is a required option"); throw new Error("'target' is a required option");
} }

@ -54,6 +54,10 @@ export function getContext<T>(key): T {
return get_current_component().$$.context.get(key); return get_current_component().$$.context.get(key);
} }
export function hasContext(key): boolean {
return get_current_component().$$.context.has(key);
}
// TODO figure out if we still want to support // TODO figure out if we still want to support
// shorthand events, or if we want to implement // shorthand events, or if we want to implement
// a real bubbling mechanism // a real bubbling mechanism

@ -48,7 +48,7 @@ export function transition_in(block, local?: 0 | 1) {
} }
} }
export function transition_out(block, local: 0 | 1, detach: 0 | 1, callback) { export function transition_out(block, local: 0 | 1, detach?: 0 | 1, callback?) {
if (block && block.o) { if (block && block.o) {
if (outroing.has(block)) return; if (outroing.has(block)) return;
outroing.add(block); outroing.add(block);

@ -117,6 +117,14 @@ export function update_slot(slot, slot_definition, ctx, $$scope, dirty, get_slot
} }
} }
export function update_slot_spread(slot, slot_definition, ctx, $$scope, dirty, get_slot_changes_fn, get_slot_spread_changes_fn, get_slot_context_fn) {
const slot_changes = get_slot_spread_changes_fn(dirty) | get_slot_changes(slot_definition, $$scope, dirty, get_slot_changes_fn);
if (slot_changes) {
const slot_context = get_slot_context(slot_definition, ctx, $$scope, get_slot_context_fn);
slot.p(slot_context, slot_changes);
}
}
export function exclude_internal_props(props) { export function exclude_internal_props(props) {
const result = {}; const result = {};
for (const k in props) if (k[0] !== '$') result[k] = props[k]; for (const k in props) if (k[0] !== '$') result[k] = props[k];

@ -62,7 +62,7 @@ function instance($$self, $$props, $$invalidate) {
} }
}; };
return [x]; return [x, y];
} }
class Component extends SvelteComponent { class Component extends SvelteComponent {

@ -12,15 +12,15 @@ function instance($$self, $$props, $$invalidate) {
$$self.$$.update = () => { $$self.$$.update = () => {
if ($$self.$$.dirty & /*x*/ 1) { if ($$self.$$.dirty & /*x*/ 1) {
$: $$invalidate(2, b = x); $: $$invalidate(1, b = x);
} }
if ($$self.$$.dirty & /*b*/ 4) { if ($$self.$$.dirty & /*b*/ 2) {
$: a = b; $: a = b;
} }
}; };
return [x]; return [x, b];
} }
class Component extends SvelteComponent { class Component extends SvelteComponent {

@ -64,7 +64,7 @@ function instance($$self, $$props, $$invalidate) {
}; };
$: x = a * 2; $: x = a * 2;
return [y]; return [y, b];
} }
class Component extends SvelteComponent { class Component extends SvelteComponent {

@ -0,0 +1,7 @@
<script>
export let obj;
export let c;
export let d;
</script>
<slot {c} {...obj} {d} />

@ -0,0 +1,55 @@
export default {
props: {
obj: { a: 1, b: 42 },
c: 5,
d: 10
},
html: `
<p>1</p>
<p>42</p>
<p>5</p>
<p>10</p>
`,
test({ assert, target, component }) {
component.obj = { a: 2, b: 50, c: 30 };
assert.htmlEqual(target.innerHTML, `
<p>2</p>
<p>50</p>
<p>30</p>
<p>10</p>
`);
component.c = 22;
assert.htmlEqual(target.innerHTML, `
<p>2</p>
<p>50</p>
<p>30</p>
<p>10</p>
`);
component.d = 44;
assert.htmlEqual(target.innerHTML, `
<p>2</p>
<p>50</p>
<p>30</p>
<p>44</p>
`);
component.obj = { a: 9, b: 12 };
assert.htmlEqual(target.innerHTML, `
<p>9</p>
<p>12</p>
<p>22</p>
<p>44</p>
`);
component.c = 88;
assert.htmlEqual(target.innerHTML, `
<p>9</p>
<p>12</p>
<p>88</p>
<p>44</p>
`);
}
};

@ -0,0 +1,14 @@
<script>
import Nested from './Nested.svelte';
export let obj;
export let c;
export let d;
</script>
<Nested {obj} {c} {d} let:a let:b let:c let:d>
<p>{a}</p>
<p>{b}</p>
<p>{c}</p>
<p>{d}</p>
</Nested>

@ -0,0 +1,21 @@
export default {
html: `
<section>
<div>Second</div>
</section>
<button>Click</button>
`,
async test({ assert, component, target, window }) {
const button = target.querySelector('button');
await button.dispatchEvent(new window.Event('click'));
assert.htmlEqual(target.innerHTML, `
<section>
<div>First</div>
<div>Second</div>
</section>
<button>Click</button>
`);
}
};

@ -0,0 +1,17 @@
<script>
let slide = 0;
let num = false;
const changeNum = () => num = !num;
</script>
<section>
{#key slide}
{#if num}
<div>First</div>
{/if}
{/key}
<div>Second</div>
</section>
<button on:click={changeNum}>Click</button>

@ -0,0 +1,26 @@
export default {
html: `
<p>42</p>
<p>42</p>
`,
async test({ assert, component, target }) {
await component.updateStore(undefined);
assert.htmlEqual(target.innerHTML, '<p>undefined</p><p>42</p>');
await component.updateStore(33);
assert.htmlEqual(target.innerHTML, '<p>33</p><p>42</p>');
await component.updateStore(undefined);
assert.htmlEqual(target.innerHTML, '<p>undefined</p><p>42</p>');
await component.updateVar(undefined);
assert.htmlEqual(target.innerHTML, '<p>undefined</p><p>undefined</p>');
await component.updateVar(33);
assert.htmlEqual(target.innerHTML, '<p>undefined</p><p>33</p>');
await component.updateVar(undefined);
assert.htmlEqual(target.innerHTML, '<p>undefined</p><p>undefined</p>');
}
};

@ -0,0 +1,19 @@
<script>
import { writable } from 'svelte/store';
let store = writable(42);
let variable = 42;
let value;
let value2;
$: value = $store;
$: value2 = variable;
export function updateStore(value) {
store.set(value);
}
export function updateVar(value) {
variable = value;
}
</script>
<p>{ value }</p>
<p>{ value2 }</p>

@ -25,6 +25,7 @@
//"strict": true, //"strict": true,
"noImplicitThis": true, "noImplicitThis": true,
"noUnusedLocals": true, "noUnusedLocals": true,
"noUnusedParameters": true "noUnusedParameters": true,
"typeRoots": ["./node_modules/@types"]
} }
} }

Loading…
Cancel
Save