Skip to content

Commit 1f84722

Browse files
authored
Merge pull request sveltejs#4078 from sveltejs/bitmask-overflow-slot
Handle slot updates where parent component has many variables
2 parents 691211a + 82ac963 commit 1f84722

File tree

12 files changed

+362
-58
lines changed

12 files changed

+362
-58
lines changed

.eslintignore

+1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
**/_actual.js
22
**/expected.js
3+
**/_output/**.js
34
test/*/samples/*/output.js
45
node_modules
56

.gitignore

+1
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ node_modules
2323
/test/sourcemaps/samples/*/output.css.map
2424
/yarn-error.log
2525
_actual*.*
26+
_output
2627
/types
2728

2829
/site/cypress/screenshots/

package-lock.json

+3-3
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

+1-1
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.26",
67+
"code-red": "0.0.27",
6868
"codecov": "^3.5.0",
6969
"css-tree": "1.0.0-alpha22",
7070
"eslint": "^6.3.0",

src/compiler/compile/render_dom/Renderer.ts

+19-38
Original file line numberDiff line numberDiff line change
@@ -204,7 +204,6 @@ export default class Renderer {
204204
? x`$$self.$$.dirty`
205205
: x`#dirty`) as Identifier | MemberExpression;
206206

207-
let bitmask;
208207
const get_bitmask = () => {
209208
const bitmask: BitMasks = [];
210209
names.forEach((name) => {
@@ -228,48 +227,30 @@ export default class Renderer {
228227
return bitmask;
229228
};
230229

231-
let operator;
232-
let left;
233-
let right;
234-
235230
return {
236-
get type() {
237-
// we make the type a getter, even though it's always
238-
// a BinaryExpression, because it gives us an opportunity
239-
// to lazily create the node. TODO would be better if
240-
// context was determined before rendering, so that
241-
// this indirection was unnecessary
242-
if (!bitmask) {
243-
bitmask = get_bitmask();
244-
245-
if (!bitmask.length) {
246-
({ operator, left, right } = x`${dirty} & /*${names.join(', ')}*/ 0` as BinaryExpression);
247-
} else if (renderer.context_overflow) {
248-
const expression = bitmask
249-
.map((b, i) => ({ b, i }))
250-
.filter(({ b }) => b)
251-
.map(({ b, i }) => x`${dirty}[${i}] & /*${b.names.join(', ')}*/ ${b.n}`)
252-
.reduce((lhs, rhs) => x`${lhs} | ${rhs}`);
253-
254-
({ operator, left, right } = expression as BinaryExpression);
255-
} else {
256-
({ operator, left, right } = x`${dirty} & /*${names.join(', ')}*/ ${bitmask[0].n}` as BinaryExpression);
257-
}
231+
// Using a ParenthesizedExpression allows us to create
232+
// the expression lazily. TODO would be better if
233+
// context was determined before rendering, so that
234+
// this indirection was unnecessary
235+
type: 'ParenthesizedExpression',
236+
get expression() {
237+
const bitmask = get_bitmask();
238+
239+
if (!bitmask.length) {
240+
return x`${dirty} & /*${names.join(', ')}*/ 0` as BinaryExpression;
258241
}
259242

243+
if (renderer.context_overflow) {
244+
return bitmask
245+
.map((b, i) => ({ b, i }))
246+
.filter(({ b }) => b)
247+
.map(({ b, i }) => x`${dirty}[${i}] & /*${b.names.join(', ')}*/ ${b.n}`)
248+
.reduce((lhs, rhs) => x`${lhs} | ${rhs}`);
249+
}
260250

261-
return 'BinaryExpression';
262-
},
263-
get operator() {
264-
return operator;
265-
},
266-
get left() {
267-
return left;
268-
},
269-
get right() {
270-
return right;
251+
return x`${dirty} & /*${names.join(', ')}*/ ${bitmask[0].n}` as BinaryExpression;
271252
}
272-
} as Expression;
253+
} as any;
273254
}
274255

275256
reference(node: string | Identifier | MemberExpression) {

src/compiler/compile/render_dom/wrappers/shared/get_slot_definition.ts

+39-11
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import Let from '../../../nodes/Let';
22
import { x, p } from 'code-red';
33
import Block from '../../Block';
44
import TemplateScope from '../../../nodes/shared/TemplateScope';
5+
import { BinaryExpression } from 'estree';
56

67
export function get_slot_definition(block: Block, scope: TemplateScope, lets: Let[]) {
78
if (lets.length === 0) return { block, scope };
@@ -28,21 +29,48 @@ export function get_slot_definition(block: Block, scope: TemplateScope, lets: Le
2829
properties: Array.from(names).map(name => p`${block.renderer.context_lookup.get(name).index}: ${name}`)
2930
};
3031

31-
const changes = Array.from(names)
32-
.map(name => {
33-
const { context_lookup } = block.renderer;
32+
const { context_lookup } = block.renderer;
3433

35-
const literal = {
36-
type: 'Literal',
37-
get value() {
34+
// i am well aware that this code is gross
35+
// TODO make it less gross
36+
const changes = {
37+
type: 'ParenthesizedExpression',
38+
get expression() {
39+
if (block.renderer.context_overflow) {
40+
const grouped = [];
41+
42+
Array.from(names).forEach(name => {
3843
const i = context_lookup.get(name).index.value as number;
39-
return 1 << i;
44+
const g = Math.floor(i / 31);
45+
46+
if (!grouped[g]) grouped[g] = [];
47+
grouped[g].push({ name, n: i % 31 });
48+
});
49+
50+
const elements = [];
51+
52+
for (let g = 0; g < grouped.length; g += 1) {
53+
elements[g] = grouped[g]
54+
? grouped[g]
55+
.map(({ name, n }) => x`${name} ? ${1 << n} : 0`)
56+
.reduce((lhs, rhs) => x`${lhs} | ${rhs}`)
57+
: x`0`;
4058
}
41-
};
4259

43-
return x`${name} ? ${literal} : 0`;
44-
})
45-
.reduce((lhs, rhs) => x`${lhs} | ${rhs}`);
60+
return {
61+
type: 'ArrayExpression',
62+
elements
63+
};
64+
}
65+
66+
return Array.from(names)
67+
.map(name => {
68+
const i = context_lookup.get(name).index.value as number;
69+
return x`${name} ? ${1 << i} : 0`;
70+
})
71+
.reduce((lhs, rhs) => x`${lhs} | ${rhs}`) as BinaryExpression;
72+
}
73+
};
4674

4775
return {
4876
block,

src/runtime/internal/utils.ts

+17-3
Original file line numberDiff line numberDiff line change
@@ -77,9 +77,23 @@ export function get_slot_context(definition, ctx, $$scope, fn) {
7777
}
7878

7979
export function get_slot_changes(definition, $$scope, dirty, fn) {
80-
return definition[2] && fn
81-
? $$scope.dirty | definition[2](fn(dirty))
82-
: $$scope.dirty;
80+
if (definition[2] && fn) {
81+
const lets = definition[2](fn(dirty));
82+
83+
if (typeof $$scope.dirty === 'object') {
84+
const merged = [];
85+
const len = Math.max($$scope.dirty.length, lets.length);
86+
for (let i = 0; i < len; i += 1) {
87+
merged[i] = $$scope.dirty[i] | lets[i];
88+
}
89+
90+
return merged;
91+
}
92+
93+
return $$scope.dirty | lets;
94+
}
95+
96+
return $$scope.dirty;
8397
}
8498

8599
export function exclude_internal_props(props) {

test/helpers.js

+14
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import * as jsdom from 'jsdom';
22
import * as assert from 'assert';
33
import * as glob from 'tiny-glob/sync.js';
4+
import * as path from 'path';
45
import * as fs from 'fs';
56
import * as colors from 'kleur';
67

@@ -237,3 +238,16 @@ export function useFakeTimers() {
237238
}
238239
};
239240
}
241+
242+
export function mkdirp(dir) {
243+
const parent = path.dirname(dir);
244+
if (parent === dir) return;
245+
246+
mkdirp(parent);
247+
248+
try {
249+
fs.mkdirSync(dir);
250+
} catch (err) {
251+
// do nothing
252+
}
253+
}

test/runtime/index.js

+31-2
Original file line numberDiff line numberDiff line change
@@ -3,14 +3,16 @@ import * as path from "path";
33
import * as fs from "fs";
44
import { rollup } from 'rollup';
55
import * as virtual from 'rollup-plugin-virtual';
6+
import * as glob from 'tiny-glob/sync.js';
67
import { clear_loops, flush, set_now, set_raf } from "../../internal";
78

89
import {
910
showOutput,
1011
loadConfig,
1112
loadSvelte,
1213
env,
13-
setupHtmlEqual
14+
setupHtmlEqual,
15+
mkdirp
1416
} from "../helpers.js";
1517

1618
let svelte$;
@@ -90,6 +92,33 @@ describe("runtime", () => {
9092

9193
const window = env();
9294

95+
glob('**/*.svelte', { cwd }).forEach(file => {
96+
if (file[0] === '_') return;
97+
98+
const dir = `${cwd}/_output/${hydrate ? 'hydratable' : 'normal'}`;
99+
const out = `${dir}/${file.replace(/\.svelte$/, '.js')}`;
100+
101+
if (fs.existsSync(out)) {
102+
fs.unlinkSync(out);
103+
}
104+
105+
mkdirp(dir);
106+
107+
try {
108+
const { js } = compile(
109+
fs.readFileSync(`${cwd}/${file}`, 'utf-8'),
110+
{
111+
...compileOptions,
112+
filename: file
113+
}
114+
);
115+
116+
fs.writeFileSync(out, js.code);
117+
} catch (err) {
118+
// do nothing
119+
}
120+
});
121+
93122
return Promise.resolve()
94123
.then(() => {
95124
// hack to support transition tests
@@ -195,7 +224,7 @@ describe("runtime", () => {
195224
} else {
196225
throw err;
197226
}
198-
}).catch(err => {
227+
}).catch(err => {
199228
failed.add(dir);
200229
showOutput(cwd, compileOptions, compile); // eslint-disable-line no-console
201230
throw err;
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
<script>
2+
export let dummy;
3+
</script>
4+
5+
<slot dummy={dummy}></slot>

0 commit comments

Comments
 (0)