From 135f6263956c794704c358ce3c0a484482b35821 Mon Sep 17 00:00:00 2001 From: Rich Harris Date: Mon, 12 Jun 2017 21:49:38 -0400 Subject: [PATCH 01/12] include ast in svelte.compile return value (#632) --- src/generators/Generator.ts | 4 ++++ src/utils/clone.ts | 18 ++++++++++++++++++ test/parser/index.js | 35 ++++++++++++++++++++++------------- 3 files changed, 44 insertions(+), 13 deletions(-) create mode 100644 src/utils/clone.ts diff --git a/src/generators/Generator.ts b/src/generators/Generator.ts index 084f1e7d90..4dd423be09 100644 --- a/src/generators/Generator.ts +++ b/src/generators/Generator.ts @@ -10,6 +10,7 @@ import getIntro from './shared/utils/getIntro'; import getOutro from './shared/utils/getOutro'; import processCss from './shared/processCss'; import annotateWithScopes from '../utils/annotateWithScopes'; +import clone from '../utils/clone'; import DomBlock from './dom/Block'; import SsrBlock from './server-side-rendering/Block'; import { Node, Parsed, CompileOptions } from '../interfaces'; @@ -48,6 +49,8 @@ export default class Generator { name: string, options: CompileOptions ) { + this.ast = clone(parsed); + this.parsed = parsed; this.source = source; this.name = name; @@ -330,6 +333,7 @@ export default class Generator { addString('\n\n' + getOutro(format, name, options, this.imports)); return { + ast: this.ast, code: compiled.toString(), map: compiled.generateMap({ includeContent: true, diff --git a/src/utils/clone.ts b/src/utils/clone.ts new file mode 100644 index 0000000000..523568e434 --- /dev/null +++ b/src/utils/clone.ts @@ -0,0 +1,18 @@ +import { Node } from '../interfaces'; + +export default function clone(node: Node) { + const cloned = {}; + + for (const key in node) { + const value = node[key]; + if (Array.isArray(value)) { + cloned[key] = value.map(clone); + } else if (value && typeof value === 'object') { + cloned[key] = clone(value); + } else { + cloned[key] = value; + } + } + + return cloned; +} \ No newline at end of file diff --git a/test/parser/index.js b/test/parser/index.js index 35f687376a..6bdd1b6376 100644 --- a/test/parser/index.js +++ b/test/parser/index.js @@ -1,10 +1,10 @@ -import assert from "assert"; -import * as fs from "fs"; -import { svelte } from "../helpers.js"; +import assert from 'assert'; +import fs from 'fs'; +import { svelte } from '../helpers.js'; -describe("parse", () => { - fs.readdirSync("test/parser/samples").forEach(dir => { - if (dir[0] === ".") return; +describe('parse', () => { + fs.readdirSync('test/parser/samples').forEach(dir => { + if (dir[0] === '.') return; // add .solo to a sample directory name to only run that test const solo = /\.solo$/.test(dir); @@ -17,14 +17,14 @@ describe("parse", () => { (solo ? it.only : it)(dir, () => { const input = fs - .readFileSync(`test/parser/samples/${dir}/input.html`, "utf-8") - .replace(/\s+$/, ""); + .readFileSync(`test/parser/samples/${dir}/input.html`, 'utf-8') + .replace(/\s+$/, ''); try { const actual = svelte.parse(input); fs.writeFileSync( `test/parser/samples/${dir}/_actual.json`, - JSON.stringify(actual, null, "\t") + JSON.stringify(actual, null, '\t') ); const expected = require(`./samples/${dir}/output.json`); @@ -32,7 +32,7 @@ describe("parse", () => { assert.deepEqual(actual.css, expected.css); assert.deepEqual(actual.js, expected.js); } catch (err) { - if (err.name !== "ParseError") throw err; + if (err.name !== 'ParseError') throw err; try { const expected = require(`./samples/${dir}/error.json`); @@ -41,13 +41,13 @@ describe("parse", () => { assert.deepEqual(err.loc, expected.loc); assert.equal(err.pos, expected.pos); } catch (err2) { - throw err2.code === "MODULE_NOT_FOUND" ? err : err2; + throw err2.code === 'MODULE_NOT_FOUND' ? err : err2; } } }); }); - it("handles errors with options.onerror", () => { + it('handles errors with options.onerror', () => { let errored = false; svelte.compile(`

unclosed`, { @@ -60,9 +60,18 @@ describe("parse", () => { assert.ok(errored); }); - it("throws without options.onerror", () => { + it('throws without options.onerror', () => { assert.throws(() => { svelte.compile(`

unclosed`); }, /

was left open/); }); + + it('includes AST in svelte.compile output', () => { + const dir = fs.readdirSync('test/parser/samples')[0]; + const source = fs.readFileSync(`test/parser/samples/${dir}/input.html`, 'utf-8'); + + const { ast } = svelte.compile(source); + const parsed = svelte.parse(source); + assert.deepEqual(ast, parsed); + }); }); From d3d026a510a3b702d6113e04ce540b3beaf8ac00 Mon Sep 17 00:00:00 2001 From: Rich Harris Date: Wed, 14 Jun 2017 17:44:13 -0400 Subject: [PATCH 02/12] sync state with view, if special case if (type === 'radio') { diff --git a/test/runtime/samples/binding-select-initial-value-undefined/_config.js b/test/runtime/samples/binding-select-initial-value-undefined/_config.js new file mode 100644 index 0000000000..5328bf4727 --- /dev/null +++ b/test/runtime/samples/binding-select-initial-value-undefined/_config.js @@ -0,0 +1,26 @@ +export default { + solo: true, + show: true, + + html: ` +

selected: a

+ + + +

selected: a

+ `, + + test ( assert, component, target ) { + const select = target.querySelector( 'select' ); + const options = [ ...target.querySelectorAll( 'option' ) ]; + + assert.equal( select.value, 'a' ); + assert.ok( options[0].selected ); + + component.destroy(); + } +}; diff --git a/test/runtime/samples/binding-select-initial-value-undefined/main.html b/test/runtime/samples/binding-select-initial-value-undefined/main.html new file mode 100644 index 0000000000..ea15402240 --- /dev/null +++ b/test/runtime/samples/binding-select-initial-value-undefined/main.html @@ -0,0 +1,9 @@ +

selected: {{selected}}

+ + + +

selected: {{selected}}

\ No newline at end of file From 4b7b0503af1482af5234e1d3080350fac34d02b9 Mon Sep 17 00:00:00 2001 From: Rich Harris Date: Wed, 14 Jun 2017 17:45:25 -0400 Subject: [PATCH 03/12] skip failing SSR test for now --- .../samples/binding-select-initial-value-undefined/_config.js | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/test/runtime/samples/binding-select-initial-value-undefined/_config.js b/test/runtime/samples/binding-select-initial-value-undefined/_config.js index 5328bf4727..d5944ae815 100644 --- a/test/runtime/samples/binding-select-initial-value-undefined/_config.js +++ b/test/runtime/samples/binding-select-initial-value-undefined/_config.js @@ -1,6 +1,5 @@ export default { - solo: true, - show: true, + 'skip-ssr': true, // TODO would be nice to fix this in SSR as well html: `

selected: a

From 9a70ca78745588e385d18b907a270479c20ce605 Mon Sep 17 00:00:00 2001 From: Rich Harris Date: Thu, 15 Jun 2017 13:44:29 -0400 Subject: [PATCH 04/12] mark indirect dependencies of + // + // + // + // + // ...we need to know that `foo` depends on `bar` and `baz`, + // so that if `foo.qux` changes, we know that we need to + // mark `bar` and `baz` as dirty too + if (node.name === 'select') { + const value = node.attributes.find((attribute: Node) => attribute.name === 'value'); + if (value) { + // TODO does this also apply to e.g. ``? + const dependencies = block.findDependencies(value.value); + state.selectBindingDependencies = dependencies; + dependencies.forEach((prop: string) => { + generator.indirectDependencies.set(prop, new Set()); + }); + } else { + state.selectBindingDependencies = null; + } + } + const isComponent = generator.components.has(node.name) || node.name === ':Self'; @@ -239,27 +293,6 @@ const preprocessors = { }); } - node.attributes.forEach((attribute: Node) => { - if (attribute.type === 'Attribute' && attribute.value !== true) { - attribute.value.forEach((chunk: Node) => { - if (chunk.type !== 'Text') { - const dependencies = block.findDependencies(chunk.expression); - block.addDependencies(dependencies); - } - }); - } else if (attribute.type === 'Binding') { - const dependencies = block.findDependencies(attribute.value); - block.addDependencies(dependencies); - } else if (attribute.type === 'Transition') { - if (attribute.intro) - generator.hasIntroTransitions = block.hasIntroMethod = true; - if (attribute.outro) { - generator.hasOutroTransitions = block.hasOutroMethod = true; - block.outros += 1; - } - } - }); - if (node.children.length) { if (isComponent) { const name = block.getUniqueName( diff --git a/src/generators/dom/visitors/shared/binding/getSetter.ts b/src/generators/dom/visitors/shared/binding/getSetter.ts index 7ce77b4489..679d9005f6 100644 --- a/src/generators/dom/visitors/shared/binding/getSetter.ts +++ b/src/generators/dom/visitors/shared/binding/getSetter.ts @@ -25,7 +25,10 @@ export default function getSetter({ ${computed && `var state = ${block.component}.get();`} list[index]${tail} = ${value}; - ${block.component}._set({ ${prop}: ${block.component}.get( '${prop}' ) }); + ${computed ? + `${block.component}._set({ ${dependencies.map((prop: string) => `${prop}: state.${prop}`).join(', ')} });` : + `${block.component}._set({ ${dependencies.map((prop: string) => `${prop}: ${block.component}.get( '${prop}' )`).join(', ')} });` + } `; } @@ -35,7 +38,7 @@ export default function getSetter({ return deindent` var state = ${block.component}.get(); ${snippet} = ${value}; - ${block.component}._set({ ${name}: state.${name} }); + ${block.component}._set({ ${dependencies.map((prop: string) => `${prop}: state.${prop}`).join(', ')} }); `; } diff --git a/test/runtime/samples/binding-indirect/_config.js b/test/runtime/samples/binding-indirect/_config.js new file mode 100644 index 0000000000..9fdfa23a25 --- /dev/null +++ b/test/runtime/samples/binding-indirect/_config.js @@ -0,0 +1,91 @@ +const tasks = [ + { description: 'put your left leg in', done: false }, + { description: 'your left leg out', done: false }, + { description: 'in, out, in, out', done: false }, + { description: 'shake it all about', done: false } +]; + +export default { + 'skip-ssr': true, + allowES2015: true, + + data: { + tasks, + selected: tasks[0] + }, + + html: ` + + + + +

Pending tasks

+

put your left leg in

+

your left leg out

+

in, out, in, out

+

shake it all about

+ `, + + test(assert, component, target, window) { + const input = target.querySelector('input'); + const select = target.querySelector('select'); + const options = target.querySelectorAll('option'); + + const change = new window.Event('change'); + + input.checked = true; + input.dispatchEvent(change); + + assert.ok(component.get('tasks')[0].done); + assert.htmlEqual(target.innerHTML, ` + + + + +

Pending tasks

+

your left leg out

+

in, out, in, out

+

shake it all about

+ `); + + options[1].selected = true; + select.dispatchEvent(change); + assert.equal(component.get('selected'), tasks[1]); + assert.ok(!input.checked); + + input.checked = true; + input.dispatchEvent(change); + + assert.ok(component.get('tasks')[1].done); + assert.htmlEqual(target.innerHTML, ` + + + + +

Pending tasks

+

in, out, in, out

+

shake it all about

+ `); + } +}; \ No newline at end of file diff --git a/test/runtime/samples/binding-indirect/main.html b/test/runtime/samples/binding-indirect/main.html new file mode 100644 index 0000000000..e1ab12c82f --- /dev/null +++ b/test/runtime/samples/binding-indirect/main.html @@ -0,0 +1,14 @@ + + + + +

Pending tasks

+{{#each tasks.filter(t => !t.done) as task}} +

{{task.description}}

+{{/each}} \ No newline at end of file From cae565bece941aee415d8542207a5ca206421c60 Mon Sep 17 00:00:00 2001 From: Rich Harris Date: Thu, 15 Jun 2017 13:57:28 -0400 Subject: [PATCH 05/12] -> v1.22.4 --- CHANGELOG.md | 6 ++++++ README.md | 2 +- package.json | 2 +- 3 files changed, 8 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index aef7d40aab..ed3818af12 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,11 @@ # Svelte changelog +## 1.23.0 + +* Include `ast` in `svelte.compile` return value ([#632](https://github.com/sveltejs/svelte/issues/632)) +* Set initial value of `` bindings (i.e. the dependencies of their `