basic component bindings working, if a bit messy

pull/1864/head
Rich Harris 7 years ago
parent 349aa0bbac
commit 65680bf445

@ -54,8 +54,6 @@ export default class Component {
event_handlers: Array<{ name: string, body: string }>;
props: string[];
refCallees: Node[];
code: MagicString;
indirectDependencies: Map<string, Set<string>>;
@ -97,7 +95,6 @@ export default class Component {
this.event_handlers = [];
this.refs = new Set();
this.refCallees = [];
this.indirectDependencies = new Map();

@ -12,10 +12,7 @@ export default function dom(
) {
const format = options.format || 'es';
const {
name,
templateProperties
} = component;
const { name } = component;
const renderer = new Renderer(component, options);

@ -192,63 +192,7 @@ export default class InlineComponentWrapper extends Wrapper {
name_updating = block.alias(`${name}_updating`);
block.addVariable(name_updating, '{}');
let hasLocalBindings = false;
let hasStoreBindings = false;
const builder = new CodeBuilder();
this.node.bindings.forEach((binding: Binding) => {
let { name: key } = getObject(binding.expression.node);
let setFromChild;
if (binding.isContextual) {
const computed = isComputed(binding.expression.node);
const tail = binding.expression.node.type === 'MemberExpression' ? getTailSnippet(binding.expression.node) : '';
const { object, property, snippet } = block.bindings.get(key)();
const lhs = binding.expression.node.type === 'MemberExpression'
? binding.expression.snippet
: `${snippet} = childState${quotePropIfNecessary(binding.name)}`;
setFromChild = deindent`
${lhs} = childState${quotePropIfNecessary(binding.name)};
${[...binding.expression.dependencies]
.map((name: string) => {
const isStoreProp = name[0] === '$';
const prop = isStoreProp ? name.slice(1) : name;
const newState = isStoreProp ? 'newStoreState' : 'newState';
if (isStoreProp) hasStoreBindings = true;
else hasLocalBindings = true;
return `${newState}${quotePropIfNecessary(prop)} = ctx${quotePropIfNecessary(name)};`;
})}
`;
}
else {
const isStoreProp = key[0] === '$';
const prop = isStoreProp ? key.slice(1) : key;
const newState = isStoreProp ? 'newStoreState' : 'newState';
if (isStoreProp) hasStoreBindings = true;
else hasLocalBindings = true;
if (binding.expression.node.type === 'MemberExpression') {
setFromChild = deindent`
${binding.expression.snippet} = childState${quotePropIfNecessary(binding.name)};
${newState}${quotePropIfNecessary(prop)} = ctx${quotePropIfNecessary(key)};
`;
}
else {
setFromChild = `${newState}${quotePropIfNecessary(prop)} = childState${quotePropIfNecessary(binding.name)};`;
}
}
statements.push(deindent`
if (${binding.expression.snippet} !== void 0) {
${name_initial_data}${quotePropIfNecessary(binding.name)} = ${binding.expression.snippet};
@ -256,11 +200,6 @@ export default class InlineComponentWrapper extends Wrapper {
}`
);
builder.addConditional(
`!${name_updating}${quotePropIfNecessary(binding.name)} && changed${quotePropIfNecessary(binding.name)}`,
setFromChild
);
updates.push(deindent`
if (!${name_updating}${quotePropIfNecessary(binding.name)} && ${[...binding.expression.dependencies].map((dependency: string) => `changed.${dependency}`).join(' || ')}) {
${name_changes}${quotePropIfNecessary(binding.name)} = ${binding.expression.snippet};
@ -270,27 +209,6 @@ export default class InlineComponentWrapper extends Wrapper {
});
block.maintainContext = true; // TODO put this somewhere more logical
const initialisers = [
hasLocalBindings && 'newState = {}',
hasStoreBindings && 'newStoreState = {}',
].filter(Boolean).join(', ');
// TODO use component.$on('state', ...) instead of _bind
componentInitProperties.push(deindent`
_bind(changed, childState) {
var ${initialisers};
${builder}
${hasLocalBindings && `#component.$set(newState);`}
${name_updating} = {};
}
`);
beforecreate = deindent`
#component.$$root._beforecreate.push(() => {
${name}._bind({ ${this.node.bindings.map(b => `${quoteNameIfNecessary(b.name)}: 1`).join(', ')} }, ${name}.get());
});
`;
}
this.node.handlers.forEach(handler => {
@ -495,6 +413,31 @@ export default class InlineComponentWrapper extends Wrapper {
`);
}
this.node.bindings.forEach(binding => {
const binding_name = component.getUniqueName(`${this.var}_${binding.name}_binding`);
if (binding.expression.contextual_dependencies.size > 0) {
throw new Error(`TODO bindings with contextual dependencies`);
} else {
const lhs = component.source.slice(binding.expression.node.start, binding.expression.node.end);
const deps = Array.from(binding.expression.dependencies);
component.event_handlers.push({
name: binding_name,
body: deindent`
function ${binding_name}(value) {
${lhs} = value;
${deps.map(dep => `$$make_dirty('${dep}');`)}
}
`
});
block.builders.init.addBlock(deindent`
${this.var}.$$bind('${binding.name}', ctx.${binding_name});
`);
}
});
if (component.options.nestedTransitions) {
block.builders.outro.addLine(
`if (${name}) ${name}.$$fragment.o(#outrocallback);`

@ -10,12 +10,16 @@ export class SvelteComponent {
this.$$onupdate = [];
this.$$ondestroy = [];
this.$$bindings = blankObject();
this.$$callbacks = blankObject();
this.$$slotted = options.slots;
set_current_component(this);
const [get_state, inject_props, inject_refs] = this.$$init(
key => this.$$make_dirty(key)
key => {
this.$$make_dirty(key);
if (this.$$bindings[key]) this.$$bindings[key](get_state()[key]);
}
);
this.$$ = { get_state, inject_props, inject_refs };
@ -39,6 +43,10 @@ export class SvelteComponent {
}
}
$destroy() {
this.$$destroy(true);
}
$on(type, callback) {
const callbacks = (this.$$callbacks[type] || (this.$$callbacks[type] = []));
callbacks.push(callback);
@ -49,8 +57,28 @@ export class SvelteComponent {
};
}
$destroy() {
this.$$destroy(true);
$set(values) {
if (this.$$) {
this.$$.inject_props(values);
run_all(this.$$onprops);
for (const key in values) this.$$make_dirty(key);
}
}
$$bind(name, callback) {
this.$$bindings[name] = callback;
callback(this.$$.get_state()[name]);
}
$$destroy(detach) {
if (this.$$) {
this.$$fragment.d(detach);
run_all(this.$$ondestroy);
// TODO null out other refs
this.$$ondestroy = this.$$fragment = this.$$ = null;
}
}
$$make_dirty(key) {
@ -71,29 +99,10 @@ export class SvelteComponent {
this.$$onmount = [];
}
$set(values) {
if (this.$$) {
this.$$.inject_props(values);
run_all(this.$$onprops);
for (const key in values) this.$$make_dirty(key);
}
}
$$update() {
this.$$fragment.p(this.$$dirty, this.$$.get_state());
this.$$.inject_refs(this.$$refs);
run_all(this.$$onupdate);
this.$$dirty = null;
}
$$destroy(detach) {
if (this.$$) {
this.$$fragment.d(detach);
run_all(this.$$ondestroy);
// TODO null out other refs
this.$$ondestroy = this.$$fragment = this.$$ = null;
}
}
}

@ -118,7 +118,7 @@ describe.only("runtime", () => {
try {
SvelteComponent = require(`./samples/${dir}/main.html`);
} catch (err) {
showOutput(cwd, { internal, format: 'cjs', hydratable: hydrate, skipIntroByDefault: compileOptions.skipIntroByDefault, nestedTransitions: compileOptions.nestedTransitions }, compile); // eslint-disable-line no-console
showOutput(cwd, { internal, format: 'cjs', hydratable: hydrate, skipIntroByDefault: compileOptions.skipIntroByDefault, nestedTransitions: compileOptions.nestedTransitions }, svelte.compile); // eslint-disable-line no-console
throw err;
}
@ -187,7 +187,7 @@ describe.only("runtime", () => {
skipIntroByDefault: compileOptions.skipIntroByDefault,
nestedTransitions: compileOptions.nestedTransitions,
dev: compileOptions.dev
}, compile); // eslint-disable-line no-console
}, svelte.compile); // eslint-disable-line no-console
throw err;
}
})
@ -199,7 +199,7 @@ describe.only("runtime", () => {
hydratable: hydrate,
skipIntroByDefault: compileOptions.skipIntroByDefault,
nestedTransitions: compileOptions.nestedTransitions
}, compile);
}, svelte.compile);
}
flush();

@ -6,11 +6,11 @@ export default {
<p>count: 0</p>
`,
test(assert, component, target, window) {
async test(assert, component, target, window) {
const click = new window.MouseEvent('click');
const button = target.querySelector('button');
button.dispatchEvent(click);
await button.dispatchEvent(click);
assert.equal(component.x, 1);
assert.htmlEqual(target.innerHTML, `
@ -18,7 +18,7 @@ export default {
<p>count: 1</p>
`);
button.dispatchEvent(click);
await button.dispatchEvent(click);
assert.equal(component.x, 2);
assert.htmlEqual(target.innerHTML, `

Loading…
Cancel
Save