diff --git a/.eslintignore b/.eslintignore
index a0ca1e55c847..d1f48d64935c 100644
--- a/.eslintignore
+++ b/.eslintignore
@@ -1,5 +1,6 @@
**/_actual.js
**/expected.js
+**/_output/**.js
test/*/samples/*/output.js
node_modules
diff --git a/.gitignore b/.gitignore
index 053f905294c8..b0e6896dbeba 100644
--- a/.gitignore
+++ b/.gitignore
@@ -23,6 +23,7 @@ node_modules
/test/sourcemaps/samples/*/output.css.map
/yarn-error.log
_actual*.*
+_output
/types
/site/cypress/screenshots/
diff --git a/package-lock.json b/package-lock.json
index 93e762b3a8a4..97a19aeb6e49 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -500,9 +500,9 @@
"dev": true
},
"code-red": {
- "version": "0.0.26",
- "resolved": "https://registry.npmjs.org/code-red/-/code-red-0.0.26.tgz",
- "integrity": "sha512-W4t68vk3xJjmkbuAKfEtaj7E+K82BtV+A4VjBlxHA6gDoSLc+sTB643JdJMSk27vpp5iEqHFuGnHieQGy/GmUQ==",
+ "version": "0.0.27",
+ "resolved": "https://registry.npmjs.org/code-red/-/code-red-0.0.27.tgz",
+ "integrity": "sha512-MSILIi8kkSGag76TW+PRfBP/dN++Ei+uTeiUfqW4Es5teFNrbsAWtyAbPwxKI1vxEsBx64loAfGxS1kVCo1d2g==",
"dev": true,
"requires": {
"acorn": "^7.1.0",
diff --git a/package.json b/package.json
index fb1d7cbad34d..4d115cb778ea 100644
--- a/package.json
+++ b/package.json
@@ -64,7 +64,7 @@
"acorn": "^7.1.0",
"agadoo": "^1.1.0",
"c8": "^5.0.1",
- "code-red": "0.0.26",
+ "code-red": "0.0.27",
"codecov": "^3.5.0",
"css-tree": "1.0.0-alpha22",
"eslint": "^6.3.0",
diff --git a/src/compiler/compile/render_dom/Renderer.ts b/src/compiler/compile/render_dom/Renderer.ts
index 29389ed3c3d8..0b2dc6bfbcec 100644
--- a/src/compiler/compile/render_dom/Renderer.ts
+++ b/src/compiler/compile/render_dom/Renderer.ts
@@ -204,7 +204,6 @@ export default class Renderer {
? x`$$self.$$.dirty`
: x`#dirty`) as Identifier | MemberExpression;
- let bitmask;
const get_bitmask = () => {
const bitmask: BitMasks = [];
names.forEach((name) => {
@@ -228,48 +227,30 @@ export default class Renderer {
return bitmask;
};
- let operator;
- let left;
- let right;
-
return {
- get type() {
- // we make the type a getter, even though it's always
- // a BinaryExpression, because it gives us an opportunity
- // to lazily create the node. TODO would be better if
- // context was determined before rendering, so that
- // this indirection was unnecessary
- if (!bitmask) {
- bitmask = get_bitmask();
-
- if (!bitmask.length) {
- ({ operator, left, right } = x`${dirty} & /*${names.join(', ')}*/ 0` as BinaryExpression);
- } else if (renderer.context_overflow) {
- const expression = 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}`);
-
- ({ operator, left, right } = expression as BinaryExpression);
- } else {
- ({ operator, left, right } = x`${dirty} & /*${names.join(', ')}*/ ${bitmask[0].n}` as BinaryExpression);
- }
+ // Using a ParenthesizedExpression allows us to create
+ // the expression lazily. TODO would be better if
+ // context was determined before rendering, so that
+ // this indirection was unnecessary
+ type: 'ParenthesizedExpression',
+ get expression() {
+ const bitmask = get_bitmask();
+
+ if (!bitmask.length) {
+ return x`${dirty} & /*${names.join(', ')}*/ 0` as BinaryExpression;
}
+ 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 'BinaryExpression';
- },
- get operator() {
- return operator;
- },
- get left() {
- return left;
- },
- get right() {
- return right;
+ return x`${dirty} & /*${names.join(', ')}*/ ${bitmask[0].n}` as BinaryExpression;
}
- } as Expression;
+ } as any;
}
reference(node: string | Identifier | MemberExpression) {
diff --git a/src/compiler/compile/render_dom/wrappers/shared/get_slot_definition.ts b/src/compiler/compile/render_dom/wrappers/shared/get_slot_definition.ts
index 24ca813684b8..2adbd3b1d063 100644
--- a/src/compiler/compile/render_dom/wrappers/shared/get_slot_definition.ts
+++ b/src/compiler/compile/render_dom/wrappers/shared/get_slot_definition.ts
@@ -2,6 +2,7 @@ import Let from '../../../nodes/Let';
import { x, p } from 'code-red';
import Block from '../../Block';
import TemplateScope from '../../../nodes/shared/TemplateScope';
+import { BinaryExpression } from 'estree';
export function get_slot_definition(block: Block, scope: TemplateScope, lets: Let[]) {
if (lets.length === 0) return { block, scope };
@@ -28,21 +29,48 @@ export function get_slot_definition(block: Block, scope: TemplateScope, lets: Le
properties: Array.from(names).map(name => p`${block.renderer.context_lookup.get(name).index}: ${name}`)
};
- const changes = Array.from(names)
- .map(name => {
- const { context_lookup } = block.renderer;
+ const { context_lookup } = block.renderer;
- const literal = {
- type: 'Literal',
- get value() {
+ // i am well aware that this code is gross
+ // TODO make it less gross
+ const changes = {
+ type: 'ParenthesizedExpression',
+ get expression() {
+ if (block.renderer.context_overflow) {
+ const grouped = [];
+
+ Array.from(names).forEach(name => {
const i = context_lookup.get(name).index.value as number;
- return 1 << i;
+ const g = Math.floor(i / 31);
+
+ if (!grouped[g]) grouped[g] = [];
+ grouped[g].push({ name, n: i % 31 });
+ });
+
+ const elements = [];
+
+ for (let g = 0; g < grouped.length; g += 1) {
+ elements[g] = grouped[g]
+ ? grouped[g]
+ .map(({ name, n }) => x`${name} ? ${1 << n} : 0`)
+ .reduce((lhs, rhs) => x`${lhs} | ${rhs}`)
+ : x`0`;
}
- };
- return x`${name} ? ${literal} : 0`;
- })
- .reduce((lhs, rhs) => x`${lhs} | ${rhs}`);
+ return {
+ type: 'ArrayExpression',
+ elements
+ };
+ }
+
+ return Array.from(names)
+ .map(name => {
+ const i = context_lookup.get(name).index.value as number;
+ return x`${name} ? ${1 << i} : 0`;
+ })
+ .reduce((lhs, rhs) => x`${lhs} | ${rhs}`) as BinaryExpression;
+ }
+ };
return {
block,
diff --git a/src/runtime/internal/utils.ts b/src/runtime/internal/utils.ts
index 7e8769cd885e..b844f1dd4c20 100644
--- a/src/runtime/internal/utils.ts
+++ b/src/runtime/internal/utils.ts
@@ -77,9 +77,23 @@ export function get_slot_context(definition, ctx, $$scope, fn) {
}
export function get_slot_changes(definition, $$scope, dirty, fn) {
- return definition[2] && fn
- ? $$scope.dirty | definition[2](fn(dirty))
- : $$scope.dirty;
+ if (definition[2] && fn) {
+ const lets = definition[2](fn(dirty));
+
+ if (typeof $$scope.dirty === 'object') {
+ const merged = [];
+ const len = Math.max($$scope.dirty.length, lets.length);
+ for (let i = 0; i < len; i += 1) {
+ merged[i] = $$scope.dirty[i] | lets[i];
+ }
+
+ return merged;
+ }
+
+ return $$scope.dirty | lets;
+ }
+
+ return $$scope.dirty;
}
export function exclude_internal_props(props) {
diff --git a/test/helpers.js b/test/helpers.js
index ff40ac5f79bc..2a03e0f436f4 100644
--- a/test/helpers.js
+++ b/test/helpers.js
@@ -1,6 +1,7 @@
import * as jsdom from 'jsdom';
import * as assert from 'assert';
import * as glob from 'tiny-glob/sync.js';
+import * as path from 'path';
import * as fs from 'fs';
import * as colors from 'kleur';
@@ -237,3 +238,16 @@ export function useFakeTimers() {
}
};
}
+
+export function mkdirp(dir) {
+ const parent = path.dirname(dir);
+ if (parent === dir) return;
+
+ mkdirp(parent);
+
+ try {
+ fs.mkdirSync(dir);
+ } catch (err) {
+ // do nothing
+ }
+}
\ No newline at end of file
diff --git a/test/runtime/index.js b/test/runtime/index.js
index 60bd70a5d9e2..408fda40f457 100644
--- a/test/runtime/index.js
+++ b/test/runtime/index.js
@@ -3,6 +3,7 @@ import * as path from "path";
import * as fs from "fs";
import { rollup } from 'rollup';
import * as virtual from 'rollup-plugin-virtual';
+import * as glob from 'tiny-glob/sync.js';
import { clear_loops, flush, set_now, set_raf } from "../../internal";
import {
@@ -10,7 +11,8 @@ import {
loadConfig,
loadSvelte,
env,
- setupHtmlEqual
+ setupHtmlEqual,
+ mkdirp
} from "../helpers.js";
let svelte$;
@@ -90,6 +92,33 @@ describe("runtime", () => {
const window = env();
+ glob('**/*.svelte', { cwd }).forEach(file => {
+ if (file[0] === '_') return;
+
+ const dir = `${cwd}/_output/${hydrate ? 'hydratable' : 'normal'}`;
+ const out = `${dir}/${file.replace(/\.svelte$/, '.js')}`;
+
+ if (fs.existsSync(out)) {
+ fs.unlinkSync(out);
+ }
+
+ mkdirp(dir);
+
+ try {
+ const { js } = compile(
+ fs.readFileSync(`${cwd}/${file}`, 'utf-8'),
+ {
+ ...compileOptions,
+ filename: file
+ }
+ );
+
+ fs.writeFileSync(out, js.code);
+ } catch (err) {
+ // do nothing
+ }
+ });
+
return Promise.resolve()
.then(() => {
// hack to support transition tests
@@ -195,7 +224,7 @@ describe("runtime", () => {
} else {
throw err;
}
- }).catch(err => {
+ }).catch(err => {
failed.add(dir);
showOutput(cwd, compileOptions, compile); // eslint-disable-line no-console
throw err;
diff --git a/test/runtime/samples/bitmask-overflow-slot/Echo.svelte b/test/runtime/samples/bitmask-overflow-slot/Echo.svelte
new file mode 100644
index 000000000000..28eaa54060a3
--- /dev/null
+++ b/test/runtime/samples/bitmask-overflow-slot/Echo.svelte
@@ -0,0 +1,5 @@
+
+
+
0
+1
+2
+3
+4
+5
+6
+7
+8
+9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+23
+24
+25
+26
+27
+28
+29
+30
+31
+32
+33
+34
+35
+36
+37
+38
+39
+40
+5:36
+6:37
+38
+0
+ `, + + test({ assert, component, target }) { + component.reads = {}; + + component._0 = 'a'; + component._30 = 'b'; + component._31 = 'c'; + component._32 = 'd'; + component._40 = 'e'; + + component._5 = 'f'; + component._6 = 'g'; + component._36 = 'h'; + component._37 = 'i'; + + assert.htmlEqual(target.innerHTML, ` +a
+1
+2
+3
+4
+f
+g
+7
+8
+9
+10
+11
+12
+13
+14
+15
+16
+17
+18
+19
+20
+21
+22
+23
+24
+25
+26
+27
+28
+29
+b
+c
+d
+33
+34
+35
+h
+i
+38
+39
+e
+f:h
+g:i
+38
+a
+ `); + + assert.deepEqual(component.reads, { + _0: 1, + _5: 3, + _6: 3, + _30: 1, + _31: 1, + _32: 1, + _36: 3, + _37: 3, + _40: 1 + }); + } +}; \ No newline at end of file diff --git a/test/runtime/samples/bitmask-overflow-slot/main.svelte b/test/runtime/samples/bitmask-overflow-slot/main.svelte new file mode 100644 index 000000000000..89e60ce4b944 --- /dev/null +++ b/test/runtime/samples/bitmask-overflow-slot/main.svelte @@ -0,0 +1,107 @@ + + +{read(_0, '_0')}
+{read(_1, '_1')}
+{read(_2, '_2')}
+{read(_3, '_3')}
+{read(_4, '_4')}
+{read(_5, '_5')}
+{read(_6, '_6')}
+{read(_7, '_7')}
+{read(_8, '_8')}
+{read(_9, '_9')}
+{read(_10, '_10')}
+{read(_11, '_11')}
+{read(_12, '_12')}
+{read(_13, '_13')}
+{read(_14, '_14')}
+{read(_15, '_15')}
+{read(_16, '_16')}
+{read(_17, '_17')}
+{read(_18, '_18')}
+{read(_19, '_19')}
+{read(_20, '_20')}
+{read(_21, '_21')}
+{read(_22, '_22')}
+{read(_23, '_23')}
+{read(_24, '_24')}
+{read(_25, '_25')}
+{read(_26, '_26')}
+{read(_27, '_27')}
+{read(_28, '_28')}
+{read(_29, '_29')}
+{read(_30, '_30')}
+{read(_31, '_31')}
+{read(_32, '_32')}
+{read(_33, '_33')}
+{read(_34, '_34')}
+{read(_35, '_35')}
+{read(_36, '_36')}
+{read(_37, '_37')}
+{read(_38, '_38')}
+{read(_39, '_39')}
+{read(_40, '_40')}
+ +{read(_5, '_5') + ':' + read(_36, '_36')}
+{foo}
+{bar}
+ +{dummy}
+