Skip to content

Commit

Permalink
subscribe ssr store
Browse files Browse the repository at this point in the history
  • Loading branch information
tanhauhau committed Dec 30, 2020
1 parent 193718f commit bde5f93
Show file tree
Hide file tree
Showing 6 changed files with 130 additions and 116 deletions.
53 changes: 2 additions & 51 deletions src/compiler/compile/render_dom/Renderer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import { x } from 'code-red';
import { Node, Identifier, MemberExpression, Literal, Expression, BinaryExpression } from 'estree';
import flatten_reference from '../utils/flatten_reference';
import { reserved_keywords } from '../utils/reserved_keywords';
import { renderer_invalidate } from './invalidate';

interface ContextMember {
name: string;
Expand Down Expand Up @@ -168,57 +169,7 @@ export default class Renderer {
}

invalidate(name: string, value?, main_execution_context: boolean = false) {
const variable = this.component.var_lookup.get(name);
const member = this.context_lookup.get(name);

if (variable && (variable.subscribable && (variable.reassigned || variable.export_name))) {
return main_execution_context
? x`${`$$subscribe_${name}`}(${value || name})`
: x`${`$$subscribe_${name}`}($$invalidate(${member.index}, ${value || name}))`;
}

if (name[0] === '$' && name[1] !== '$') {
return x`${name.slice(1)}.set(${value || name})`;
}

if (
variable && (
variable.module || (
!variable.referenced &&
!variable.is_reactive_dependency &&
!variable.export_name &&
!name.startsWith('$$')
)
)
) {
return value || name;
}

if (value) {
return x`$$invalidate(${member.index}, ${value})`;
}

// if this is a reactive declaration, invalidate dependencies recursively
const deps = new Set([name]);

deps.forEach(name => {
const reactive_declarations = this.component.reactive_declarations.filter(x =>
x.assignees.has(name)
);
reactive_declarations.forEach(declaration => {
declaration.dependencies.forEach(name => {
deps.add(name);
});
});
});

// TODO ideally globals etc wouldn't be here in the first place
const filtered = Array.from(deps).filter(n => this.context_lookup.has(n));
if (!filtered.length) return null;

return filtered
.map(n => x`$$invalidate(${this.context_lookup.get(n).index}, ${n})`)
.reduce((lhs, rhs) => x`${lhs}, ${rhs}`);
return renderer_invalidate(this, name, value, main_execution_context);
}

dirty(names: string[], is_reactive_declaration = false): Expression {
Expand Down
65 changes: 64 additions & 1 deletion src/compiler/compile/render_dom/invalidate.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ export function invalidate(renderer: Renderer, scope: Scope, node: Node, names:
if (main_execution_context && !variable.subscribable && variable.name[0] !== '$') {
return node;
}
return renderer.invalidate(variable.name, undefined, main_execution_context);
return renderer_invalidate(renderer, variable.name, undefined, main_execution_context);
}

if (!head) {
Expand Down Expand Up @@ -79,3 +79,66 @@ export function invalidate(renderer: Renderer, scope: Scope, node: Node, names:

return invalidate;
}

export function renderer_invalidate(renderer: Renderer, name: string, value?, main_execution_context: boolean = false) {
const variable = renderer.component.var_lookup.get(name);

if (variable && (variable.subscribable && (variable.reassigned || variable.export_name))) {
if (main_execution_context) {
return x`${`$$subscribe_${name}`}(${value || name})`;
} else {
const member = renderer.context_lookup.get(name);
return x`${`$$subscribe_${name}`}($$invalidate(${member.index}, ${value || name}))`;
}
}

if (name[0] === '$' && name[1] !== '$') {
return x`${name.slice(1)}.set(${value || name})`;
}

if (
variable && (
variable.module || (
!variable.referenced &&
!variable.is_reactive_dependency &&
!variable.export_name &&
!name.startsWith('$$')
)
)
) {
return value || name;
}

if (value) {
if (main_execution_context) {
return x`${value}`;
} else {
const member = renderer.context_lookup.get(name);
return x`$$invalidate(${member.index}, ${value})`;
}
}

if (main_execution_context) return;

// if this is a reactive declaration, invalidate dependencies recursively
const deps = new Set([name]);

deps.forEach(name => {
const reactive_declarations = renderer.component.reactive_declarations.filter(x =>
x.assignees.has(name)
);
reactive_declarations.forEach(declaration => {
declaration.dependencies.forEach(name => {
deps.add(name);
});
});
});

// TODO ideally globals etc wouldn't be here in the first place
const filtered = Array.from(deps).filter(n => renderer.context_lookup.has(n));
if (!filtered.length) return null;

return filtered
.map(n => x`$$invalidate(${renderer.context_lookup.get(n).index}, ${n})`)
.reduce((lhs, rhs) => x`${lhs}, ${rhs}`);
}
122 changes: 62 additions & 60 deletions src/compiler/compile/render_ssr/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import Text from '../nodes/Text';
import { LabeledStatement, Statement, Node, Expression } from 'estree';
import { walk } from 'estree-walker';
import { extract_names } from 'periscopic';
import { invalidate } from '../render_dom/invalidate';

export default function ssr(
component: Component,
Expand Down Expand Up @@ -40,19 +41,36 @@ export default function ssr(
const slots = uses_slots ? b`let $$slots = @compute_slots(#slots);` : null;

const reactive_stores = component.vars.filter(variable => variable.name[0] === '$' && variable.name[1] !== '$');
const reactive_store_values = reactive_stores
const reactive_store_subscriptions = reactive_stores
.filter(store => {
const variable = component.var_lookup.get(store.name.slice(1));
return !variable || variable.hoistable;
})
.map(({ name }) => {
const store_name = name.slice(1);
return b`
${component.compile_options.dev && b`@validate_store(${store_name}, '${store_name}');`}
${`$$unsubscribe_${store_name}`} = @subscribe(${store_name}, #value => ${name} = #value)
${store_name}.subscribe($$value => ${name} = $$value);
`
});
const reactive_store_unsubscriptions = reactive_stores.map(
({ name }) => b`${`$$unsubscribe_${name.slice(1)}`}()`
);

const reactive_store_declarations = reactive_stores
.map(({ name }) => {
const store_name = name.slice(1);
const store = component.var_lookup.get(store_name);
if (store && store.hoistable) return null;

const assignment = b`${name} = @get_store_value(${store_name});`;
if (store && store.reassigned) {
const unsubscribe = `$$unsubscribe_${store_name}`;
const subscribe = `$$subscribe_${store_name}`;

return component.compile_options.dev
? b`@validate_store(${store_name}, '${store_name}'); ${assignment}`
: assignment;
})
.filter(Boolean);
return b`let ${name}, ${unsubscribe} = @noop, ${subscribe} = () => (${unsubscribe}(), ${unsubscribe} = @subscribe(${store_name}, $$value => ${name} = $$value), ${store_name})`;
}
return b`let ${name}, ${`$$unsubscribe_${store_name}`};`;
});

// instrument get/set store value
if (component.ast.instance) {
Expand All @@ -72,51 +90,45 @@ export default function ssr(

if (node.type === 'AssignmentExpression' || node.type === 'UpdateExpression') {
const assignee = node.type === 'AssignmentExpression' ? node.left : node.argument;
const names = new Set(extract_names(assignee));

const vars = Array.from(names)
.filter(name => {
const owner = scope.find_owner(name);
return !owner || owner === component.instance_scope;
})
.map(name => component.var_lookup.get(name))
.filter(variable => {
return variable &&
!variable.hoistable &&
!variable.global &&
!variable.module &&
(
variable.subscribable || variable.name[0] === '$'
);
})
.map(variable => {
if (variable.subscribable) {
return x`${'$' + variable.name} = @get_store_value(${variable.name})`;
let names = new Set(extract_names(assignee));
let to_invalidate = new Set<string>();

for (const name of names) {
const variable = component.var_lookup.get(name);
if (variable &&
!variable.hoistable &&
!variable.global &&
!variable.module &&
(
variable.subscribable || variable.name[0] === '$'
)) {
to_invalidate.add(variable.name);
}
if (variable.name[0] === '$') {
return x`${variable.name.slice(1)}.set(${variable.name})`;
}
});

if (vars.length) {
this.replace({
type: 'SequenceExpression',
expressions: [
node,
...vars,
assignee as Expression
]
});
}

if (to_invalidate.size) {
this.replace(
invalidate(
{ component } as any,
scope,
node,
to_invalidate,
true
)
);
}
}
}
});
}

component.rewrite_props(({ name }) => {
component.rewrite_props(({ name, reassigned }) => {
const value = `$${name}`;

let insert = b`${value} = @get_store_value(${name})`;
let insert = reassigned
? b`${`$$subscribe_${name}`}()`
: b`${`$$unsubscribe_${name}`} = @subscribe(${name}, #value => $${value} = #value)`;

if (component.compile_options.dev) {
insert = b`@validate_store(${name}, '${name}'); ${insert}`;
}
Expand Down Expand Up @@ -160,41 +172,31 @@ export default function ssr(
do {
$$settled = true;
${reactive_store_values}
${injected.map(name => b`let ${name};`)}
${reactive_declarations}
$$rendered = ${literal};
} while (!$$settled);
${reactive_store_unsubscriptions}
return $$rendered;
`
: b`
${reactive_store_values}
${injected.map(name => b`let ${name};`)}
${reactive_declarations}
${reactive_store_unsubscriptions}
return ${literal};`;

const blocks = [
rest,
slots,
...reactive_stores.map(({ name }) => {
const store_name = name.slice(1);
const store = component.var_lookup.get(store_name);
if (store && store.hoistable) {
return b`
${component.compile_options.dev && b`@validate_store(${store_name}, '${store_name}');`}
let ${name} = @get_store_value(${store_name});
`;
}
return b`let ${name};`;
}),

...reactive_store_declarations,
...reactive_store_subscriptions,
instance_javascript,
...parent_bindings,
css.code && b`$$result.css.add(#css);`,
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
// destructure to store value
export default {
skip_if_ssr: true, // pending https://github.com/sveltejs/svelte/issues/3582
html: '<h1>2 2 xxx 5 6 9 10 2</h1>',
async test({ assert, target, component }) {
await component.update();
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
// destructure to store
export default {
html: '<h1>2 2 xxx 5 6 9 10 2</h1>',
skip_if_ssr: true,
async test({ assert, target, component }) {
await component.update();
assert.htmlEqual(target.innerHTML, '<h1>11 11 yyy 12 13 14 15 11</h1>');
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,10 @@
// should resubscribe immediately
value = writable({ foo: $value.foo + 2, bar: $value.bar - 2 }); // { foo: 5, bar: 4 }
// should mutate the store value
$value.baz = $value.foo + $value.bar; // { foo: 5, bar: 4, baz: 9 }
// should resubscribe immediately
value = writable({ qux: $value.baz - $value.foo }); // { qux: 4 }
Expand Down

0 comments on commit bde5f93

Please sign in to comment.