|
import Block from './Block.js'; |
|
import FragmentWrapper from './wrappers/Fragment.js'; |
|
import { x } from 'code-red'; |
|
import flatten_reference from '../utils/flatten_reference.js'; |
|
import { reserved_keywords } from '../utils/reserved_keywords.js'; |
|
import { renderer_invalidate } from './invalidate.js'; |
|
|
|
export default class Renderer { |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
component; |
|
|
|
|
|
options; |
|
|
|
|
|
context = []; |
|
|
|
|
|
initial_context = []; |
|
|
|
|
|
context_lookup = new Map(); |
|
|
|
|
|
context_overflow; |
|
|
|
|
|
blocks = []; |
|
|
|
|
|
readonly = new Set(); |
|
|
|
|
|
meta_bindings = []; |
|
|
|
|
|
binding_groups = new Map(); |
|
|
|
|
|
block; |
|
|
|
|
|
fragment; |
|
|
|
|
|
file_var; |
|
|
|
|
|
|
|
|
|
|
|
|
|
locate; |
|
|
|
|
|
|
|
|
|
|
|
|
|
meta_locate; |
|
|
|
|
|
|
|
|
|
|
|
constructor(component, options) { |
|
this.component = component; |
|
this.options = options; |
|
this.locate = component.locate; |
|
this.meta_locate = component.meta_locate; |
|
this.file_var = options.dev && this.component.get_unique_name('file'); |
|
component.vars |
|
.filter((v) => !v.hoistable || (v.export_name && !v.module)) |
|
.forEach((v) => this.add_to_context(v.name)); |
|
|
|
component.vars.filter((v) => v.subscribable).forEach((v) => this.add_to_context(`$${v.name}`)); |
|
reserved_keywords.forEach((keyword) => { |
|
if (component.var_lookup.has(keyword)) { |
|
this.add_to_context(keyword); |
|
} |
|
}); |
|
if (component.slots.size > 0) { |
|
this.add_to_context('$$scope'); |
|
this.add_to_context('#slots'); |
|
} |
|
|
|
this.block = new Block({ |
|
renderer: this, |
|
name: null, |
|
type: 'component', |
|
key: null, |
|
bindings: new Map(), |
|
dependencies: new Set() |
|
}); |
|
this.block.has_update_method = true; |
|
this.fragment = new FragmentWrapper( |
|
this, |
|
this.block, |
|
component.fragment.children, |
|
null, |
|
true, |
|
null |
|
); |
|
|
|
this.blocks.forEach((block) => { |
|
if (block instanceof Block) { |
|
block.assign_variable_names(); |
|
} |
|
}); |
|
this.block.assign_variable_names(); |
|
this.fragment.render(this.block, null, (x`#nodes`)); |
|
this.context_overflow = this.context.length > 31; |
|
this.context.forEach((member) => { |
|
const { variable } = member; |
|
if (variable) { |
|
member.priority += 2; |
|
if (variable.mutated || variable.reassigned) member.priority += 4; |
|
|
|
|
|
if (variable.is_reactive_dependency && (variable.mutated || variable.reassigned)) |
|
member.priority += 16; |
|
if (variable.export_name) member.priority += 32; |
|
if (variable.referenced) member.priority += 64; |
|
} else if (member.is_non_contextual) { |
|
|
|
|
|
member.priority += 8; |
|
} |
|
if (!member.is_contextual) { |
|
member.priority += 1; |
|
} |
|
}); |
|
this.context.sort( |
|
(a, b) => |
|
b.priority - a.priority || |
|
(a.index.value) - (b.index.value) |
|
); |
|
this.context.forEach((member, i) => (member.index.value = i)); |
|
let i = this.context.length; |
|
while (i--) { |
|
const member = this.context[i]; |
|
if (member.variable) { |
|
if ( |
|
member.variable.referenced || |
|
member.variable.export_name || |
|
(member.variable.is_reactive_dependency && |
|
(member.variable.mutated || member.variable.reassigned)) |
|
) |
|
break; |
|
} else if (member.is_non_contextual) { |
|
break; |
|
} |
|
} |
|
this.initial_context = this.context.slice(0, i + 1); |
|
} |
|
|
|
|
|
|
|
|
|
|
|
add_to_context(name, contextual = false) { |
|
if (!this.context_lookup.has(name)) { |
|
|
|
const member = { |
|
name, |
|
index: { type: 'Literal', value: this.context.length }, |
|
is_contextual: false, |
|
is_non_contextual: false, |
|
variable: null, |
|
priority: 0 |
|
}; |
|
this.context_lookup.set(name, member); |
|
this.context.push(member); |
|
} |
|
const member = this.context_lookup.get(name); |
|
if (contextual) { |
|
member.is_contextual = true; |
|
} else { |
|
member.is_non_contextual = true; |
|
member.variable = this.component.var_lookup.get(name); |
|
} |
|
return member; |
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
invalidate(name, value, main_execution_context = false) { |
|
return renderer_invalidate(this, name, value, main_execution_context); |
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
dirty(names, is_reactive_declaration = false) { |
|
const renderer = this; |
|
const dirty = |
|
( |
|
is_reactive_declaration ? x`$$self.$$.dirty` : x`#dirty` |
|
); |
|
const get_bitmask = () => { |
|
|
|
const bitmask = []; |
|
names.forEach((name) => { |
|
const member = renderer.context_lookup.get(name); |
|
if (!member) return; |
|
if (member.index.value === -1) { |
|
throw new Error('unset index'); |
|
} |
|
const value = (member.index.value); |
|
const i = (value / 31) | 0; |
|
const n = 1 << value % 31; |
|
if (!bitmask[i]) bitmask[i] = { n: 0, names: [] }; |
|
bitmask[i].n |= n; |
|
bitmask[i].names.push(name); |
|
}); |
|
return bitmask; |
|
}; |
|
|
|
return ({ |
|
|
|
|
|
|
|
|
|
type: 'ParenthesizedExpression', |
|
get expression() { |
|
const bitmask = get_bitmask(); |
|
if (!bitmask.length) { |
|
return ( |
|
x`${dirty} & /*${names.join(', ')}*/ 0` |
|
); |
|
} |
|
if (renderer.context_overflow) { |
|
return bitmask |
|
.map((b, i) => ({ b, i })) |
|
.filter(({ b }) => b) |
|
.map(({ b, i }) => x`${dirty}[${i}] & /*${b.names.join(', ')}*/ ${b.n}`) |
|
.reduce((lhs, rhs) => x`${lhs} | ${rhs}`); |
|
} |
|
return ( |
|
x`${dirty} & /*${names.join(', ')}*/ ${bitmask[0].n}` |
|
); |
|
} |
|
}); |
|
} |
|
|
|
|
|
|
|
|
|
get_initial_dirty() { |
|
const _this = this; |
|
|
|
|
|
|
|
const val = (x`-1`); |
|
return { |
|
get type() { |
|
return _this.context_overflow ? 'ArrayExpression' : 'UnaryExpression'; |
|
}, |
|
|
|
get elements() { |
|
const elements = []; |
|
for (let i = 0; i < _this.context.length; i += 31) { |
|
elements.push(val); |
|
} |
|
return elements; |
|
}, |
|
|
|
operator: val.operator, |
|
prefix: val.prefix, |
|
argument: val.argument |
|
}; |
|
} |
|
|
|
|
|
|
|
|
|
|
|
reference(node, ctx = '#ctx') { |
|
if (typeof node === 'string') { |
|
node = { type: 'Identifier', name: node }; |
|
} |
|
const { name, nodes } = flatten_reference(node); |
|
const member = this.context_lookup.get(name); |
|
|
|
if (this.component.var_lookup.get(name)) { |
|
this.component.add_reference(node, name); |
|
} |
|
if (member !== undefined) { |
|
const replacement = ( |
|
x`/*${member.name}*/ ${ctx}[${member.index}]` |
|
); |
|
if (nodes[0].loc) replacement.object.loc = nodes[0].loc; |
|
nodes[0] = replacement; |
|
return nodes.reduce((lhs, rhs) => x`${lhs}.${rhs}`); |
|
} |
|
return node; |
|
} |
|
|
|
|
|
remove_block(block) { |
|
this.blocks.splice(this.blocks.indexOf(block), 1); |
|
} |
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|