Merge remote-tracking branch 'upstream/master' into fast-hydration

pull/4309/head
Avi Marcus 6 years ago
commit 2f75fe906a

@ -1,5 +1,11 @@
# Svelte changelog
## Unreleased
* Fix indirect bindings involving elements with spreads ([#3680](https://github.com/sveltejs/svelte/issues/3680))
* Warn when using `<Foo/>` and `Foo` is dynamic ([#4331](https://github.com/sveltejs/svelte/issues/4331))
* Fix unneeded updating of keyed each blocks ([#4373](https://github.com/sveltejs/svelte/issues/4373))
## 3.18.2
* Fix binding to module-level variables ([#4086](https://github.com/sveltejs/svelte/issues/4086))

@ -18,7 +18,7 @@ You'll be using the *command line*, also known as the terminal. On Windows, you
The command line is a way to interact with your computer (or another computer! but that's a topic for another time) with more power and control than the GUI (graphical user interface) that most people use day-to-day.
Once on the command line, you can navigate the filesystem using `ls` to list the contents of your current directory, and `cd` to change the current directory. For example, if you had a `Development` directory of your projects inside your home directory, you would type
Once on the command line, you can navigate the filesystem using `ls` (`dir` on Windows) to list the contents of your current directory, and `cd` to change the current directory. For example, if you had a `Development` directory of your projects inside your home directory, you would type
```bash
cd Development
@ -34,7 +34,7 @@ cd svelte-projects
A full introduction to the command line is out of the scope of this guide, but here are a few more useful commands:
* `cd ..` — navigates to the parent of the current directory
* `cat my-file.txt` — on Mac/Linux, lists the contents of `my-file.txt`
* `cat my-file.txt` — on Mac/Linux (`type my-file.txt` on Windows), lists the contents of `my-file.txt`
* `open .` (or `start .` on Windows) — opens the current directory in Finder or File Explorer

@ -185,7 +185,7 @@ const ast = svelte.parse(source, { filename: 'App.svelte' });
result: {
code: string,
dependencies: Array<string>
} = svelte.preprocess(
} = await svelte.preprocess(
source: string,
preprocessors: Array<{
markup?: (input: { content: string, filename: string }) => Promise<{
@ -222,7 +222,7 @@ The `markup` function receives the entire component source text, along with the
```js
const svelte = require('svelte/compiler');
const { code } = svelte.preprocess(source, {
const { code } = await svelte.preprocess(source, {
markup: ({ content, filename }) => {
return {
code: content.replace(/foo/g, 'bar')
@ -244,7 +244,7 @@ const svelte = require('svelte/compiler');
const sass = require('node-sass');
const { dirname } = require('path');
const { code, dependencies } = svelte.preprocess(source, {
const { code, dependencies } = await svelte.preprocess(source, {
style: async ({ content, attributes, filename }) => {
// only process <style lang="sass">
if (attributes.lang !== 'sass') return;
@ -277,7 +277,7 @@ Multiple preprocessors can be used together. The output of the first becomes the
```js
const svelte = require('svelte/compiler');
const { code } = svelte.preprocess(source, [
const { code } = await svelte.preprocess(source, [
{
markup: () => {
console.log('this runs first');

@ -412,7 +412,14 @@ export default class EachBlockWrapper extends Wrapper {
? `@outro_and_destroy_block`
: `@destroy_block`;
const all_dependencies = new Set(this.block.dependencies); // TODO should be dynamic deps only
this.node.expression.dynamic_dependencies().forEach((dependency: string) => {
all_dependencies.add(dependency);
});
if (all_dependencies.size) {
block.chunks.update.push(b`
if (${block.renderer.dirty(Array.from(all_dependencies))}) {
const ${this.vars.each_block_value} = ${snippet};
${this.block.has_outros && b`@group_outros();`}
@ -421,7 +428,9 @@ export default class EachBlockWrapper extends Wrapper {
${iterations} = @update_keyed_each(${iterations}, #dirty, ${get_key}, ${dynamic ? 1 : 0}, #ctx, ${this.vars.each_block_value}, ${lookup}, ${update_mount_node}, ${destroy}, ${create_each_block}, ${update_anchor_node}, ${this.vars.get_each_context});
${this.node.has_animation && b`for (let #i = 0; #i < ${view_length}; #i += 1) ${iterations}[#i].a();`}
${this.block.has_outros && b`@check_outros();`}
}
`);
}
if (this.block.has_outros) {
block.chunks.outro.push(b`

@ -39,20 +39,25 @@ export default class AttributeWrapper {
}
}
render(block: Block) {
is_indirectly_bound_value() {
const element = this.parent;
const name = fix_attribute_casing(this.node.name);
const metadata = this.get_metadata();
const is_indirectly_bound_value =
name === 'value' &&
return name === 'value' &&
(element.node.name === 'option' || // TODO check it's actually bound
(element.node.name === 'input' &&
element.node.bindings.find(
element.node.bindings.some(
(binding) =>
/checked|group/.test(binding.name)
)));
}
render(block: Block) {
const element = this.parent;
const name = fix_attribute_casing(this.node.name);
const metadata = this.get_metadata();
const is_indirectly_bound_value = this.is_indirectly_bound_value();
const property_name = is_indirectly_bound_value
? '__value'

@ -675,10 +675,10 @@ export default class ElementWrapper extends Wrapper {
updates.push(condition ? x`${condition} && ${snippet}` : snippet);
} else {
const metadata = attr.get_metadata();
const snippet = x`{ ${
(metadata && metadata.property_name) ||
fix_attribute_casing(attr.node.name)
}: ${attr.get_value(block)} }`;
const name = attr.is_indirectly_bound_value()
? '__value'
: (metadata && metadata.property_name) || fix_attribute_casing(attr.node.name);
const snippet = x`{ ${name}: ${attr.get_value(block)} }`;
initial_props.push(snippet);
updates.push(condition ? x`${condition} && ${snippet}` : snippet);

@ -106,11 +106,28 @@ export default class InlineComponentWrapper extends Wrapper {
block.add_outro();
}
warn_if_reactive() {
const { name } = this.node;
const variable = this.renderer.component.var_lookup.get(name);
if (!variable) {
return;
}
if (variable.reassigned || variable.export_name || variable.is_reactive_dependency) {
this.renderer.component.warn(this.node, {
code: 'reactive-component',
message: `<${name}/> will not be reactive if ${name} changes. Use <svelte:component this={${name}}/> if you want this reactivity.`,
});
}
}
render(
block: Block,
parent_node: Identifier,
parent_nodes: Identifier
) {
this.warn_if_reactive();
const { renderer } = this;
const { component } = renderer;

@ -108,7 +108,7 @@ export function set_attributes(node: Element & ElementCSSInlineStyle, attributes
node.removeAttribute(key);
} else if (key === 'style') {
node.style.cssText = attributes[key];
} else if (descriptors[key] && descriptors[key].set) {
} else if (key === '__value' || descriptors[key] && descriptors[key].set) {
node[key] = attributes[key];
} else {
attr(node, key, attributes[key]);

@ -92,10 +92,12 @@ function create_fragment(ctx) {
insert(target, each_1_anchor, anchor);
},
p(ctx, [dirty]) {
if (dirty & /*things*/ 1) {
const each_value = /*things*/ ctx[0];
for (let i = 0; i < each_blocks.length; i += 1) each_blocks[i].r();
each_blocks = update_keyed_each(each_blocks, dirty, get_key, 1, ctx, each_value, each_1_lookup, each_1_anchor.parentNode, fix_and_destroy_block, create_each_block, each_1_anchor, get_each_context);
for (let i = 0; i < each_blocks.length; i += 1) each_blocks[i].a();
}
},
i: noop,
o: noop,

@ -77,8 +77,10 @@ function create_fragment(ctx) {
insert(target, each_1_anchor, anchor);
},
p(ctx, [dirty]) {
if (dirty & /*things*/ 1) {
const each_value = /*things*/ ctx[0];
each_blocks = update_keyed_each(each_blocks, dirty, get_key, 1, ctx, each_value, each_1_lookup, each_1_anchor.parentNode, destroy_block, create_each_block, each_1_anchor, get_each_context);
}
},
i: noop,
o: noop,

@ -0,0 +1,44 @@
export default {
skip_if_ssr: true,
async test({ assert, component, target, window }) {
const event = new window.MouseEvent('click');
const [radio1, radio2, radio3] = target.querySelectorAll('input[type=radio]');
assert.ok(!radio1.checked);
assert.ok(radio2.checked);
assert.ok(!radio3.checked);
component.radio = 'radio1';
assert.ok(radio1.checked);
assert.ok(!radio2.checked);
assert.ok(!radio3.checked);
await radio3.dispatchEvent(event);
assert.equal(component.radio, 'radio3');
assert.ok(!radio1.checked);
assert.ok(!radio2.checked);
assert.ok(radio3.checked);
const [check1, check2, check3] = target.querySelectorAll('input[type=checkbox]');
assert.ok(!check1.checked);
assert.ok(check2.checked);
assert.ok(!check3.checked);
component.check = ['check1', 'check2'];
assert.ok(check1.checked);
assert.ok(check2.checked);
assert.ok(!check3.checked);
await check3.dispatchEvent(event);
assert.deepEqual(component.check, ['check1', 'check2', 'check3']);
assert.ok(check1.checked);
assert.ok(check2.checked);
assert.ok(check3.checked);
}
};

@ -0,0 +1,12 @@
<script>
export let radio = 'radio2';
export let check = ['check2'];
</script>
<input type='radio' bind:group={radio} value='radio1' {...{}}>
<input type='radio' bind:group={radio} value='radio2' {...{}}>
<input type='radio' bind:group={radio} value='radio3' {...{}}>
<input type='checkbox' bind:group={check} value='check1' {...{}}>
<input type='checkbox' bind:group={check} value='check2' {...{}}>
<input type='checkbox' bind:group={check} value='check3' {...{}}>

@ -4,7 +4,7 @@
export let count;
export let idToValue = Object.create(null);
function ids() {
function ids(count) {
return new Array(count)
.fill(null)
.map((_, i) => ({ id: 'id-' + i}))
@ -15,7 +15,7 @@
<input type='number' bind:value={count}>
<ol>
{#each ids() as object (object.id)}
{#each ids(count) as object (object.id)}
<Nested bind:value={idToValue[object.id]} id={object.id}>
{object.id}: value is {idToValue[object.id]}
</Nested>

@ -0,0 +1,23 @@
export default {
html: `
<button>Click Me</button>
0
<ul></ul>
`,
async test({ assert, component, target, window }) {
const button = target.querySelector("button");
const event = new window.MouseEvent("click");
await button.dispatchEvent(event);
assert.htmlEqual(
target.innerHTML,
`
<button>Click Me</button>
1
<ul></ul>
`
);
}
};

@ -0,0 +1,20 @@
<script>
let num = 0;
let cards = [];
function click() {
// updating cards via push should have no effect to the ul,
// since its being mutated instead of reassigned
cards.push(num++);
}
</script>
<button on:click={click}>
Click Me
</button>
{num}
<ul>
{#each cards as c, i (i)}
<li>{c}</li>
{/each}
</ul>

@ -20,6 +20,7 @@ describe("validate", () => {
const input = fs.readFileSync(`${__dirname}/samples/${dir}/input.svelte`, "utf-8").replace(/\s+$/, "");
const expected_warnings = tryToLoadJson(`${__dirname}/samples/${dir}/warnings.json`) || [];
const expected_errors = tryToLoadJson(`${__dirname}/samples/${dir}/errors.json`);
const options = tryToLoadJson(`${__dirname}/samples/${dir}/options.json`);
let error;
@ -28,7 +29,8 @@ describe("validate", () => {
dev: config.dev,
legacy: config.legacy,
generate: false,
customElement: config.customElement
customElement: config.customElement,
...options,
});
assert.deepEqual(warnings.map(w => ({

@ -0,0 +1,18 @@
<script>
import A from './A.svelte';
import B from './B.svelte';
let Let = A;
function update() {
Let = B;
}
export let ExportLet = B;
$: Reactive = random() ? A : B;
</script>
<Let />
<ExportLet />
<Reactive />
<svelte:component this={Let} />

@ -0,0 +1,47 @@
[
{
"code": "reactive-component",
"message": "<Let/> will not be reactive if Let changes. Use <svelte:component this={Let}/> if you want this reactivity.",
"pos": 190,
"end": {
"character": 197,
"column": 7,
"line": 15
},
"start": {
"character": 190,
"column": 0,
"line": 15
}
},
{
"message": "<ExportLet/> will not be reactive if ExportLet changes. Use <svelte:component this={ExportLet}/> if you want this reactivity.",
"code": "reactive-component",
"pos": 198,
"end": {
"character": 211,
"column": 13,
"line": 16
},
"start": {
"character": 198,
"column": 0,
"line": 16
}
},
{
"message": "<Reactive/> will not be reactive if Reactive changes. Use <svelte:component this={Reactive}/> if you want this reactivity.",
"code": "reactive-component",
"pos": 212,
"end": {
"character": 224,
"column": 12,
"line": 17
},
"start": {
"character": 212,
"column": 0,
"line": 17
}
}
]
Loading…
Cancel
Save