Skip to content

Commit

Permalink
sveltejs#2446 - apply directives in order
Browse files Browse the repository at this point in the history
  • Loading branch information
colincasey-sfdc committed May 11, 2019
1 parent 6f491a5 commit 2b2dd8e
Show file tree
Hide file tree
Showing 2 changed files with 69 additions and 205 deletions.
234 changes: 49 additions & 185 deletions src/compile/render-dom/wrappers/Element/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -400,18 +400,32 @@ export default class ElementWrapper extends Wrapper {
}))
.filter(group => group.bindings.length);

const ordered = [...bindingGroups, ...this.node.handlers];
ordered.sort((a, b) => {
const startA = a instanceof EventHandler ? (a as EventHandler).start : a.bindings[0].node.start;
const startB = b instanceof EventHandler ? (b as EventHandler).start : b.bindings[0].node.start;
return startA - startB;
});
const this_binding = this.bindings.find(b => b.node.name === 'this');

function getOrder (item) {
if (item instanceof EventHandler) {
return (item as EventHandler).start;
} else if (item instanceof BindingWrapper) {
return (item as BindingWrapper).node.start
} else {
return item.bindings[0].node.start
}
}

const ordered = [
...bindingGroups,
...this.node.handlers,
...(this_binding ? [this_binding] : [])
];
ordered.sort((a, b) => getOrder(a) - getOrder(b));

ordered.forEach(bindingGroupOrEventHandler => {
if (bindingGroupOrEventHandler instanceof EventHandler) {
add_event_handlers(block, this.var, [bindingGroupOrEventHandler as EventHandler])
} else if (bindingGroupOrEventHandler instanceof BindingWrapper) {
this.add_this_binding(block, bindingGroupOrEventHandler as BindingWrapper);
} else {
this.add_binding(block, bindingGroupOrEventHandler)
this.add_binding(block, bindingGroupOrEventHandler);
}
});
}
Expand Down Expand Up @@ -532,49 +546,11 @@ export default class ElementWrapper extends Wrapper {
if (lock) {
block.builders.update.add_line(`${lock} = false;`);
}

const this_binding = bindingGroup.bindings.find(b => b.node.name === 'this');
if (this_binding) {
const name = renderer.component.get_unique_name(`${this.var}_binding`);

renderer.component.add_var({
name,
internal: true,
referenced: true
});

const { handler, object } = this_binding;

const args = [];
for (const arg of handler.contextual_dependencies) {
args.push(arg);
block.add_variable(arg, `ctx.${arg}`);
}

renderer.component.partly_hoisted.push(deindent`
function ${name}(${['$$node', 'check'].concat(args).join(', ')}) {
${handler.snippet ? `if ($$node || (!$$node && ${handler.snippet} === check)) ` : ''}${handler.mutation}
${renderer.component.invalidate(object)};
}
`);

block.builders.mount.add_line(`@add_binding_callback(() => ctx.${name}(${[this.var, 'null'].concat(args).join(', ')}));`);
block.builders.destroy.add_line(`ctx.${name}(${['null', this.var].concat(args).join(', ')});`);
block.builders.update.add_line(deindent`
if (changed.items) {
ctx.${name}(${['null', this.var].concat(args).join(', ')});
${args.map(a => `${a} = ctx.${a}`).join(', ')};
ctx.${name}(${[this.var, 'null'].concat(args).join(', ')});
}`
);
}
}

add_bindings(block: Block) {
add_this_binding(block: Block, this_binding: BindingWrapper) {
const { renderer } = this;

if (this.bindings.length === 0) return;

renderer.component.has_reactive_assignments = true;

const lock = this.bindings.some(binding => binding.needs_lock) ?
Expand All @@ -583,154 +559,42 @@ export default class ElementWrapper extends Wrapper {

if (lock) block.add_variable(lock, 'false');

const groups = events
.map(event => ({
events: event.event_names,
bindings: this.bindings
.filter(binding => binding.node.name !== 'this')
.filter(binding => event.filter(this.node, binding.node.name))
}))
.filter(group => group.bindings.length);

groups.forEach(group => {
const handler = renderer.component.get_unique_name(`${this.var}_${group.events.join('_')}_handler`);

renderer.component.add_var({
name: handler,
internal: true,
referenced: true
});

// TODO figure out how to handle locks
const needs_lock = group.bindings.some(binding => binding.needs_lock);

const dependencies = new Set();
const contextual_dependencies = new Set();

group.bindings.forEach(binding => {
// TODO this is a mess
add_to_set(dependencies, binding.get_dependencies());
add_to_set(contextual_dependencies, binding.node.expression.contextual_dependencies);
add_to_set(contextual_dependencies, binding.handler.contextual_dependencies);

binding.render(block, lock);
});

// media bindings — awkward special case. The native timeupdate events
// fire too infrequently, so we need to take matters into our
// own hands
let animation_frame;
if (group.events[0] === 'timeupdate') {
animation_frame = block.get_unique_name(`${this.var}_animationframe`);
block.add_variable(animation_frame);
}

const has_local_function = contextual_dependencies.size > 0 || needs_lock || animation_frame;

let callee;

// TODO dry this out — similar code for event handlers and component bindings
if (has_local_function) {
// need to create a block-local function that calls an instance-level function
block.builders.init.add_block(deindent`
function ${handler}() {
${animation_frame && deindent`
cancelAnimationFrame(${animation_frame});
if (!${this.var}.paused) ${animation_frame} = requestAnimationFrame(${handler});`}
${needs_lock && `${lock} = true;`}
ctx.${handler}.call(${this.var}${contextual_dependencies.size > 0 ? ', ctx' : ''});
}
`);

callee = handler;
} else {
callee = `ctx.${handler}`;
}

this.renderer.component.partly_hoisted.push(deindent`
function ${handler}(${contextual_dependencies.size > 0 ? `{ ${Array.from(contextual_dependencies).join(', ')} }` : ``}) {
${group.bindings.map(b => b.handler.mutation)}
${Array.from(dependencies).filter(dep => dep[0] !== '$').map(dep => `${this.renderer.component.invalidate(dep)};`)}
}
`);

group.events.forEach(name => {
if (name === 'resize') {
// special case
const resize_listener = block.get_unique_name(`${this.var}_resize_listener`);
block.add_variable(resize_listener);

block.builders.mount.add_line(
`${resize_listener} = @add_resize_listener(${this.var}, ${callee}.bind(${this.var}));`
);

block.builders.destroy.add_line(
`${resize_listener}.cancel();`
);
} else {
block.event_listeners.push(
`@listen(${this.var}, "${name}", ${callee})`
);
}
});

const some_initial_state_is_undefined = group.bindings
.map(binding => `${binding.snippet} === void 0`)
.join(' || ');

if (this.node.name === 'select' || group.bindings.find(binding => binding.node.name === 'indeterminate' || binding.is_readonly_media_attribute())) {
const callback = has_local_function ? handler : `() => ${callee}.call(${this.var})`;
block.builders.hydrate.add_line(
`if (${some_initial_state_is_undefined}) @add_render_callback(${callback});`
);
}

if (group.events[0] === 'resize') {
block.builders.hydrate.add_line(
`@add_render_callback(() => ${callee}.call(${this.var}));`
);
}
});

if (lock) {
block.builders.update.add_line(`${lock} = false;`);
}

const this_binding = this.bindings.find(b => b.node.name === 'this');
if (this_binding) {
const name = renderer.component.get_unique_name(`${this.var}_binding`);
const name = renderer.component.get_unique_name(`${this.var}_binding`);

renderer.component.add_var({
name,
internal: true,
referenced: true
});
renderer.component.add_var({
name,
internal: true,
referenced: true
});

const { handler, object } = this_binding;
const { handler, object } = this_binding;

const args = [];
for (const arg of handler.contextual_dependencies) {
args.push(arg);
block.add_variable(arg, `ctx.${arg}`);
}
const args = [];
for (const arg of handler.contextual_dependencies) {
args.push(arg);
block.add_variable(arg, `ctx.${arg}`);
}

renderer.component.partly_hoisted.push(deindent`
function ${name}(${['$$node', 'check'].concat(args).join(', ')}) {
${handler.snippet ? `if ($$node || (!$$node && ${handler.snippet} === check)) ` : ''}${handler.mutation}
${renderer.component.invalidate(object)};
}
`);
renderer.component.partly_hoisted.push(deindent`
function ${name}(${['$$node', 'check'].concat(args).join(', ')}) {
${handler.snippet ? `if ($$node || (!$$node && ${handler.snippet} === check)) ` : ''}${handler.mutation}
${renderer.component.invalidate(object)};
}
`);

block.builders.mount.add_line(`@add_binding_callback(() => ctx.${name}(${[this.var, 'null'].concat(args).join(', ')}));`);
block.builders.destroy.add_line(`ctx.${name}(${['null', this.var].concat(args).join(', ')});`);
block.builders.update.add_line(deindent`
if (changed.items) {
ctx.${name}(${['null', this.var].concat(args).join(', ')});
${args.map(a => `${a} = ctx.${a}`).join(', ')};
ctx.${name}(${[this.var, 'null'].concat(args).join(', ')});
}`
);
}
block.builders.mount.add_line(`@add_binding_callback(() => ctx.${name}(${[this.var, 'null'].concat(args).join(', ')}));`);
block.builders.destroy.add_line(`ctx.${name}(${['null', this.var].concat(args).join(', ')});`);
block.builders.update.add_line(deindent`
if (changed.items) {
ctx.${name}(${['null', this.var].concat(args).join(', ')});
${args.map(a => `${a} = ctx.${a}`).join(', ')};
ctx.${name}(${[this.var, 'null'].concat(args).join(', ')});
}`
);
}

add_attributes(block: Block) {
Expand Down
40 changes: 20 additions & 20 deletions test/js/samples/media-bindings/expected.js
Original file line number Diff line number Diff line change
Expand Up @@ -26,18 +26,18 @@ function create_fragment(ctx) {
return {
c() {
audio = element("audio");
if (ctx.played === void 0 || ctx.currentTime === void 0) add_render_callback(audio_timeupdate_handler);
if (ctx.duration === void 0) add_render_callback(() => ctx.audio_durationchange_handler.call(audio));
if (ctx.buffered === void 0) add_render_callback(() => ctx.audio_progress_handler.call(audio));
if (ctx.buffered === void 0 || ctx.seekable === void 0) add_render_callback(() => ctx.audio_loadedmetadata_handler.call(audio));
if (ctx.played === void 0 || ctx.currentTime === void 0) add_render_callback(audio_timeupdate_handler);
if (ctx.duration === void 0) add_render_callback(() => ctx.audio_durationchange_handler.call(audio));

dispose = [
listen(audio, "progress", ctx.audio_progress_handler),
listen(audio, "loadedmetadata", ctx.audio_loadedmetadata_handler),
listen(audio, "timeupdate", audio_timeupdate_handler),
listen(audio, "durationchange", ctx.audio_durationchange_handler),
listen(audio, "play", ctx.audio_play_pause_handler),
listen(audio, "pause", ctx.audio_play_pause_handler),
listen(audio, "progress", ctx.audio_progress_handler),
listen(audio, "loadedmetadata", ctx.audio_loadedmetadata_handler),
listen(audio, "volumechange", ctx.audio_volumechange_handler),
listen(audio, "ratechange", ctx.audio_ratechange_handler)
];
Expand All @@ -53,10 +53,10 @@ function create_fragment(ctx) {

p(changed, ctx) {
if (!audio_updating && changed.currentTime && !isNaN(ctx.currentTime)) audio.currentTime = ctx.currentTime;
audio_updating = false;
if (changed.paused && audio_is_paused !== (audio_is_paused = ctx.paused)) audio[audio_is_paused ? "pause" : "play"]();
if (changed.volume && !isNaN(ctx.volume)) audio.volume = ctx.volume;
if (changed.playbackRate && !isNaN(ctx.playbackRate)) audio.playbackRate = ctx.playbackRate;
audio_updating = false;
},

i: noop,
Expand All @@ -75,6 +75,18 @@ function create_fragment(ctx) {
function instance($$self, $$props, $$invalidate) {
let { buffered, seekable, played, currentTime, duration, paused, volume, playbackRate } = $$props;

function audio_progress_handler() {
buffered = time_ranges_to_array(this.buffered);
$$invalidate('buffered', buffered);
}

function audio_loadedmetadata_handler() {
buffered = time_ranges_to_array(this.buffered);
seekable = time_ranges_to_array(this.seekable);
$$invalidate('buffered', buffered);
$$invalidate('seekable', seekable);
}

function audio_timeupdate_handler() {
played = time_ranges_to_array(this.played);
currentTime = this.currentTime;
Expand All @@ -92,18 +104,6 @@ function instance($$self, $$props, $$invalidate) {
$$invalidate('paused', paused);
}

function audio_progress_handler() {
buffered = time_ranges_to_array(this.buffered);
$$invalidate('buffered', buffered);
}

function audio_loadedmetadata_handler() {
buffered = time_ranges_to_array(this.buffered);
seekable = time_ranges_to_array(this.seekable);
$$invalidate('buffered', buffered);
$$invalidate('seekable', seekable);
}

function audio_volumechange_handler() {
volume = this.volume;
$$invalidate('volume', volume);
Expand Down Expand Up @@ -134,11 +134,11 @@ function instance($$self, $$props, $$invalidate) {
paused,
volume,
playbackRate,
audio_progress_handler,
audio_loadedmetadata_handler,
audio_timeupdate_handler,
audio_durationchange_handler,
audio_play_pause_handler,
audio_progress_handler,
audio_loadedmetadata_handler,
audio_volumechange_handler,
audio_ratechange_handler
};
Expand All @@ -151,4 +151,4 @@ class Component extends SvelteComponent {
}
}

export default Component;
export default Component;

0 comments on commit 2b2dd8e

Please sign in to comment.