Skip to content

Commit cd21acf

Browse files
authored
Bitmask-based change tracking (#3945)
* start updating tests * start implementing bitmask-based change tracking (#1943) * oops * fix some await block stuff * slots * reactive declarations * component bindings etc * start fixing slots * fix store value invalidations * slot stuff * fixes * fix * fixes * fix some slot stuff * fix some invalidations * fix if blocks * fix a test * destructuring in lets * fix shadowing * fix if block case * all runtime tests passinfg * almost all tests passing * update tests * never hoist writable vars in dev mode, fix debug statements * beef up shadowing test * always use renderer.reference * fix sourcemaps * ugh so close * all tests passing. phase one complete, i guess * add test for component with more than 31 dynamic values * stable sort * stable sort that preserves order * linting * failing test for bitmask overflow * ok i think this is it * lint * rename changed to dirty, for more internal consistency * update tests * use bitwise comparison * add comments... sort of * update tests * moar comments * I don't know what happened to these tests
1 parent 0aceccc commit cd21acf

File tree

112 files changed

+1502
-1083
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

112 files changed

+1502
-1083
lines changed

.eslintrc.js

Lines changed: 1 addition & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -35,12 +35,7 @@ module.exports = {
3535
argsIgnorePattern: '^_'
3636
}
3737
],
38-
'@typescript-eslint/no-object-literal-type-assertion': [
39-
'error',
40-
{
41-
allowAsParameter: true
42-
}
43-
],
38+
'@typescript-eslint/no-object-literal-type-assertion': 'off',
4439
'@typescript-eslint/no-unused-vars': 'off'
4540
},
4641
globals: {

package-lock.json

Lines changed: 3 additions & 3 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -64,7 +64,7 @@
6464
"acorn": "^7.1.0",
6565
"agadoo": "^1.1.0",
6666
"c8": "^5.0.1",
67-
"code-red": "0.0.25",
67+
"code-red": "0.0.26",
6868
"codecov": "^3.5.0",
6969
"css-tree": "1.0.0-alpha22",
7070
"eslint": "^6.3.0",

src/compiler/compile/Component.ts

Lines changed: 4 additions & 64 deletions
Original file line numberDiff line numberDiff line change
@@ -209,7 +209,6 @@ export default class Component {
209209
});
210210

211211
const subscribable_name = name.slice(1);
212-
this.add_reference(subscribable_name);
213212

214213
const variable = this.var_lookup.get(subscribable_name);
215214
if (variable) variable.subscribable = true;
@@ -889,50 +888,6 @@ export default class Component {
889888
return null;
890889
}
891890

892-
invalidate(name, value?) {
893-
const variable = this.var_lookup.get(name);
894-
895-
if (variable && (variable.subscribable && (variable.reassigned || variable.export_name))) {
896-
return x`${`$$subscribe_${name}`}($$invalidate('${name}', ${value || name}))`;
897-
}
898-
899-
if (name[0] === '$' && name[1] !== '$') {
900-
return x`${name.slice(1)}.set(${value || name})`;
901-
}
902-
903-
if (
904-
variable &&
905-
!variable.referenced &&
906-
!variable.is_reactive_dependency &&
907-
!variable.export_name &&
908-
!name.startsWith('$$')
909-
) {
910-
return value || name;
911-
}
912-
913-
if (value) {
914-
return x`$$invalidate('${name}', ${value})`;
915-
}
916-
917-
// if this is a reactive declaration, invalidate dependencies recursively
918-
const deps = new Set([name]);
919-
920-
deps.forEach(name => {
921-
const reactive_declarations = this.reactive_declarations.filter(x =>
922-
x.assignees.has(name)
923-
);
924-
reactive_declarations.forEach(declaration => {
925-
declaration.dependencies.forEach(name => {
926-
deps.add(name);
927-
});
928-
});
929-
});
930-
931-
return Array.from(deps)
932-
.map(n => x`$$invalidate('${n}', ${n})`)
933-
.reduce((lhs, rhs) => x`${lhs}, ${rhs}}`);
934-
}
935-
936891
rewrite_props(get_insert: (variable: Var) => Node[]) {
937892
if (!this.ast.instance) return;
938893

@@ -1054,6 +1009,10 @@ export default class Component {
10541009
if (!d.init) return false;
10551010
if (d.init.type !== 'Literal') return false;
10561011

1012+
// everything except const values can be changed by e.g. svelte devtools
1013+
// which means we can't hoist it
1014+
if (node.kind !== 'const' && this.compile_options.dev) return false;
1015+
10571016
const { name } = d.id as Identifier;
10581017

10591018
const v = this.var_lookup.get(name);
@@ -1325,25 +1284,6 @@ export default class Component {
13251284
});
13261285
}
13271286

1328-
qualify(name) {
1329-
if (name === `$$props`) return x`#ctx.$$props`;
1330-
1331-
let [head, ...tail] = name.split('.');
1332-
1333-
const variable = this.var_lookup.get(head);
1334-
1335-
if (variable) {
1336-
this.add_reference(name); // TODO we can probably remove most other occurrences of this
1337-
1338-
if (!variable.hoistable) {
1339-
tail.unshift(head);
1340-
head = '#ctx';
1341-
}
1342-
}
1343-
1344-
return [head, ...tail].reduce((lhs, rhs) => x`${lhs}.${rhs}`);
1345-
}
1346-
13471287
warn_if_undefined(name: string, node, template_scope: TemplateScope) {
13481288
if (name[0] === '$') {
13491289
if (name === '$' || name[1] === '$' && name !== '$$props') {

src/compiler/compile/nodes/Action.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ export default class Action extends Node {
1414
component.warn_if_undefined(info.name, info, scope);
1515

1616
this.name = info.name;
17-
component.qualify(info.name);
17+
component.add_reference(info.name.split('.')[0]);
1818

1919
this.expression = info.expression
2020
? new Expression(component, this, scope, info.expression)

src/compiler/compile/nodes/Animation.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ export default class Animation extends Node {
1313
component.warn_if_undefined(info.name, info, scope);
1414

1515
this.name = info.name;
16-
component.qualify(info.name);
16+
component.add_reference(info.name.split('.')[0]);
1717

1818
if (parent.animation) {
1919
component.error(this, {

src/compiler/compile/nodes/Transition.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ export default class Transition extends Node {
1515
component.warn_if_undefined(info.name, info, scope);
1616

1717
this.name = info.name;
18-
component.qualify(info.name);
18+
component.add_reference(info.name.split('.')[0]);
1919

2020
this.directive = info.intro && info.outro ? 'transition' : info.intro ? 'in' : 'out';
2121
this.is_local = info.modifiers.includes('local');

src/compiler/compile/nodes/shared/Expression.ts

Lines changed: 21 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -9,9 +9,9 @@ import TemplateScope from './TemplateScope';
99
import get_object from '../../utils/get_object';
1010
import Block from '../../render_dom/Block';
1111
import is_dynamic from '../../render_dom/wrappers/shared/is_dynamic';
12-
import { x, b, p } from 'code-red';
13-
import { invalidate } from '../../utils/invalidate';
14-
import { Node, FunctionExpression } from 'estree';
12+
import { b } from 'code-red';
13+
import { invalidate } from '../../render_dom/invalidate';
14+
import { Node, FunctionExpression, Identifier } from 'estree';
1515
import { TemplateNode } from '../../../interfaces';
1616

1717
type Owner = Wrapper | TemplateNode;
@@ -213,7 +213,8 @@ export default class Expression {
213213
component.add_reference(name); // TODO is this redundant/misplaced?
214214
}
215215
} else if (is_contextual(component, template_scope, name)) {
216-
this.replace(x`#ctx.${node}`);
216+
const reference = block.renderer.reference(node);
217+
this.replace(reference);
217218
}
218219

219220
this.skip();
@@ -260,42 +261,38 @@ export default class Expression {
260261
// function can be hoisted inside the component init
261262
component.partly_hoisted.push(declaration);
262263

263-
this.replace(x`#ctx.${id}` as any);
264-
265-
component.add_var({
266-
name: id.name,
267-
internal: true,
268-
referenced: true
269-
});
264+
block.renderer.add_to_context(id.name);
265+
this.replace(block.renderer.reference(id));
270266
}
271267

272268
else {
273269
// we need a combo block/init recipe
274-
(node as FunctionExpression).params.unshift({
275-
type: 'ObjectPattern',
276-
properties: Array.from(contextual_dependencies).map(name => p`${name}` as any)
277-
});
270+
const deps = Array.from(contextual_dependencies);
271+
272+
(node as FunctionExpression).params = [
273+
...deps.map(name => ({ type: 'Identifier', name } as Identifier)),
274+
...(node as FunctionExpression).params
275+
];
276+
277+
const context_args = deps.map(name => block.renderer.reference(name));
278278

279279
component.partly_hoisted.push(declaration);
280280

281-
this.replace(id as any);
281+
block.renderer.add_to_context(id.name);
282+
const callee = block.renderer.reference(id);
282283

283-
component.add_var({
284-
name: id.name,
285-
internal: true,
286-
referenced: true
287-
});
284+
this.replace(id as any);
288285

289286
if ((node as FunctionExpression).params.length > 0) {
290287
declarations.push(b`
291288
function ${id}(...args) {
292-
return #ctx.${id}(#ctx, ...args);
289+
return ${callee}(${context_args}, ...args);
293290
}
294291
`);
295292
} else {
296293
declarations.push(b`
297294
function ${id}() {
298-
return #ctx.${id}(#ctx);
295+
return ${callee}(${context_args});
299296
}
300297
`);
301298
}
@@ -329,7 +326,7 @@ export default class Expression {
329326
}
330327
});
331328

332-
this.replace(invalidate(component, scope, node, traced));
329+
this.replace(invalidate(block.renderer, scope, node, traced));
333330
}
334331
}
335332
});

src/compiler/compile/render_dom/Block.ts

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import Renderer from './Renderer';
22
import Wrapper from './wrappers/shared/Wrapper';
33
import { b, x } from 'code-red';
4-
import { Node, Identifier } from 'estree';
4+
import { Node, Identifier, ArrayPattern } from 'estree';
55
import { is_head } from './wrappers/shared/is_head';
66

77
export interface BlockOptions {
@@ -34,7 +34,7 @@ export default class Block {
3434
key: Identifier;
3535
first: Identifier;
3636

37-
dependencies: Set<string>;
37+
dependencies: Set<string> = new Set();
3838

3939
bindings: Map<string, {
4040
object: Identifier;
@@ -90,8 +90,6 @@ export default class Block {
9090
this.key = options.key;
9191
this.first = null;
9292

93-
this.dependencies = new Set();
94-
9593
this.bindings = options.bindings;
9694

9795
this.chunks = {
@@ -302,7 +300,13 @@ export default class Block {
302300
properties.update = noop;
303301
} else {
304302
const ctx = this.maintain_context ? x`#new_ctx` : x`#ctx`;
305-
properties.update = x`function #update(#changed, ${ctx}) {
303+
304+
let dirty: Identifier | ArrayPattern = { type: 'Identifier', name: '#dirty' };
305+
if (!this.renderer.context_overflow && !this.parent) {
306+
dirty = { type: 'ArrayPattern', elements: [dirty] };
307+
}
308+
309+
properties.update = x`function #update(${ctx}, ${dirty}) {
306310
${this.maintain_context && b`#ctx = ${ctx};`}
307311
${this.chunks.update}
308312
}`;

0 commit comments

Comments
 (0)