diff --git a/src/compile/Component.ts b/src/compile/Component.ts index 5608817302..9b3603ea04 100644 --- a/src/compile/Component.ts +++ b/src/compile/Component.ts @@ -5,7 +5,6 @@ import { walk, childKeys } from 'estree-walker'; import { getLocator } from 'locate-character'; import Stats from '../Stats'; import deindent from '../utils/deindent'; -import CodeBuilder from '../utils/CodeBuilder'; import reservedNames from '../utils/reservedNames'; import namespaces from '../utils/namespaces'; import { removeNode } from '../utils/removeNode'; @@ -17,8 +16,6 @@ import Stylesheet from './css/Stylesheet'; import { test } from '../config'; import Fragment from './nodes/Fragment'; import shared from './shared'; -import { DomTarget } from './dom'; -import { SsrTarget } from './ssr'; import { Node, ShorthandImport, Ast, CompileOptions, CustomElementOptions } from '../interfaces'; import error from '../utils/error'; import getCodeFrame from '../utils/getCodeFrame'; @@ -101,7 +98,6 @@ export default class Component { name: string; options: CompileOptions; fragment: Fragment; - target: DomTarget | SsrTarget; customElement: CustomElementOptions; tag: string; @@ -148,7 +144,6 @@ export default class Component { refs: Set; file: string; - fileVar: string; locate: (c: number) => { line: number, column: number }; stylesheet: Stylesheet; @@ -168,15 +163,13 @@ export default class Component { source: string, name: string, options: CompileOptions, - stats: Stats, - target: DomTarget | SsrTarget + stats: Stats ) { this.stats = stats; this.ast = ast; this.source = source; this.options = options; - this.target = target; this.imports = []; this.shorthandImports = []; @@ -229,8 +222,6 @@ export default class Component { this.aliases = new Map(); this.usedNames = new Set(); - this.fileVar = options.dev && this.getUniqueName('file'); - this.computations = []; this.templateProperties = {}; this.properties = new Map(); diff --git a/src/compile/index.ts b/src/compile/index.ts index 3a513ee731..958376b662 100644 --- a/src/compile/index.ts +++ b/src/compile/index.ts @@ -1,7 +1,7 @@ import { assign } from '../shared'; import Stats from '../Stats'; import parse from '../parse/index'; -import renderDOM, { DomTarget } from './render-dom/index'; +import renderDOM from './render-dom/index'; import renderSSR from './render-ssr/index'; import { CompileOptions, Warning, Ast } from '../interfaces'; import Component from './Component'; @@ -74,10 +74,7 @@ export default function compile(source: string, options: CompileOptions) { source, options.name || 'SvelteComponent', options, - stats, - - // TODO make component generator-agnostic, to allow e.g. WebGL generator - options.generate === 'ssr' ? null : new DomTarget() + stats ); stats.stop('create component'); diff --git a/src/compile/nodes/Attribute.ts b/src/compile/nodes/Attribute.ts index 2ebeb1f79d..94d6a958ee 100644 --- a/src/compile/nodes/Attribute.ts +++ b/src/compile/nodes/Attribute.ts @@ -9,11 +9,6 @@ import Text from './Text'; import Block from '../render-dom/Block'; import Expression from './shared/Expression'; -export interface StyleProp { - key: string; - value: Node[]; -} - export default class Attribute extends Node { type: 'Attribute'; start: number; @@ -108,237 +103,6 @@ export default class Attribute extends Node { : ''; } - render(block: Block) { - const node = this.parent; - const name = fixAttributeCasing(this.name); - - if (name === 'style') { - const styleProps = optimizeStyle(this.chunks); - if (styleProps) { - this.renderStyle(block, styleProps); - return; - } - } - - let metadata = node.namespace ? null : attributeLookup[name]; - if (metadata && metadata.appliesTo && !~metadata.appliesTo.indexOf(node.name)) - metadata = null; - - const isIndirectlyBoundValue = - name === 'value' && - (node.name === 'option' || // TODO check it's actually bound - (node.name === 'input' && - node.bindings.find( - (binding: Binding) => - /checked|group/.test(binding.name) - ))); - - const propertyName = isIndirectlyBoundValue - ? '__value' - : metadata && metadata.propertyName; - - // xlink is a special case... we could maybe extend this to generic - // namespaced attributes but I'm not sure that's applicable in - // HTML5? - const method = /-/.test(node.name) - ? '@setCustomElementData' - : name.slice(0, 6) === 'xlink:' - ? '@setXlinkAttribute' - : '@setAttribute'; - - const isLegacyInputType = this.component.options.legacy && name === 'type' && this.parent.name === 'input'; - - const isDataSet = /^data-/.test(name) && !this.component.options.legacy && !node.namespace; - const camelCaseName = isDataSet ? name.replace('data-', '').replace(/(-\w)/g, function (m) { - return m[1].toUpperCase(); - }) : name; - - if (this.isDynamic) { - let value; - - // TODO some of this code is repeated in Tag.ts — would be good to - // DRY it out if that's possible without introducing crazy indirection - if (this.chunks.length === 1) { - // single {tag} — may be a non-string - value = this.chunks[0].snippet; - } else { - // '{foo} {bar}' — treat as string concatenation - value = - (this.chunks[0].type === 'Text' ? '' : `"" + `) + - this.chunks - .map((chunk: Node) => { - if (chunk.type === 'Text') { - return stringify(chunk.data); - } else { - return chunk.getPrecedence() <= 13 - ? `(${chunk.snippet})` - : chunk.snippet; - } - }) - .join(' + '); - } - - const isSelectValueAttribute = - name === 'value' && node.name === 'select'; - - const shouldCache = this.shouldCache || isSelectValueAttribute; - - const last = shouldCache && block.getUniqueName( - `${node.var}_${name.replace(/[^a-zA-Z_$]/g, '_')}_value` - ); - - if (shouldCache) block.addVariable(last); - - let updater; - const init = shouldCache ? `${last} = ${value}` : value; - - if (isLegacyInputType) { - block.builders.hydrate.addLine( - `@setInputType(${node.var}, ${init});` - ); - updater = `@setInputType(${node.var}, ${shouldCache ? last : value});`; - } else if (isSelectValueAttribute) { - // annoying special case - const isMultipleSelect = node.getStaticAttributeValue('multiple'); - const i = block.getUniqueName('i'); - const option = block.getUniqueName('option'); - - const ifStatement = isMultipleSelect - ? deindent` - ${option}.selected = ~${last}.indexOf(${option}.__value);` - : deindent` - if (${option}.__value === ${last}) { - ${option}.selected = true; - break; - }`; - - updater = deindent` - for (var ${i} = 0; ${i} < ${node.var}.options.length; ${i} += 1) { - var ${option} = ${node.var}.options[${i}]; - - ${ifStatement} - } - `; - - block.builders.mount.addBlock(deindent` - ${last} = ${value}; - ${updater} - `); - } else if (propertyName) { - block.builders.hydrate.addLine( - `${node.var}.${propertyName} = ${init};` - ); - updater = `${node.var}.${propertyName} = ${shouldCache ? last : value};`; - } else if (isDataSet) { - block.builders.hydrate.addLine( - `${node.var}.dataset.${camelCaseName} = ${init};` - ); - updater = `${node.var}.dataset.${camelCaseName} = ${shouldCache ? last : value};`; - } else { - block.builders.hydrate.addLine( - `${method}(${node.var}, "${name}", ${init});` - ); - updater = `${method}(${node.var}, "${name}", ${shouldCache ? last : value});`; - } - - if (this.dependencies.size || isSelectValueAttribute) { - const dependencies = Array.from(this.dependencies); - const changedCheck = ( - (block.hasOutros ? `!#current || ` : '') + - dependencies.map(dependency => `changed.${dependency}`).join(' || ') - ); - - const updateCachedValue = `${last} !== (${last} = ${value})`; - - const condition = shouldCache ? - ( dependencies.length ? `(${changedCheck}) && ${updateCachedValue}` : updateCachedValue ) : - changedCheck; - - block.builders.update.addConditional( - condition, - updater - ); - } - } else { - const value = this.getValue(); - - const statement = ( - isLegacyInputType - ? `@setInputType(${node.var}, ${value});` - : propertyName - ? `${node.var}.${propertyName} = ${value};` - : isDataSet - ? `${node.var}.dataset.${camelCaseName} = ${value};` - : `${method}(${node.var}, "${name}", ${value});` - ); - - block.builders.hydrate.addLine(statement); - - // special case – autofocus. has to be handled in a bit of a weird way - if (this.isTrue && name === 'autofocus') { - block.autofocus = node.var; - } - } - - if (isIndirectlyBoundValue) { - const updateValue = `${node.var}.value = ${node.var}.__value;`; - - block.builders.hydrate.addLine(updateValue); - if (this.isDynamic) block.builders.update.addLine(updateValue); - } - } - - renderStyle( - block: Block, - styleProps: StyleProp[] - ) { - styleProps.forEach((prop: StyleProp) => { - let value; - - if (isDynamic(prop.value)) { - const propDependencies = new Set(); - let shouldCache; - - value = - ((prop.value.length === 1 || prop.value[0].type === 'Text') ? '' : `"" + `) + - prop.value - .map((chunk: Node) => { - if (chunk.type === 'Text') { - return stringify(chunk.data); - } else { - const { dependencies, snippet } = chunk; - - dependencies.forEach(d => { - propDependencies.add(d); - }); - - return chunk.getPrecedence() <= 13 ? `(${snippet})` : snippet; - } - }) - .join(' + '); - - if (propDependencies.size) { - const dependencies = Array.from(propDependencies); - const condition = ( - (block.hasOutros ? `!#current || ` : '') + - dependencies.map(dependency => `changed.${dependency}`).join(' || ') - ); - - block.builders.update.addConditional( - condition, - `@setStyle(${this.parent.var}, "${prop.key}", ${value});` - ); - } - } else { - value = stringify(prop.value[0].data); - } - - block.builders.hydrate.addLine( - `@setStyle(${this.parent.var}, "${prop.key}", ${value});` - ); - }); - } - stringifyForSsr() { return this.chunks .map((chunk: Node) => { @@ -350,353 +114,4 @@ export default class Attribute extends Node { }) .join(''); } -} - -// source: https://developer.mozilla.org/en-US/docs/Web/HTML/Attributes -const attributeLookup = { - accept: { appliesTo: ['form', 'input'] }, - 'accept-charset': { propertyName: 'acceptCharset', appliesTo: ['form'] }, - accesskey: { propertyName: 'accessKey' }, - action: { appliesTo: ['form'] }, - align: { - appliesTo: [ - 'applet', - 'caption', - 'col', - 'colgroup', - 'hr', - 'iframe', - 'img', - 'table', - 'tbody', - 'td', - 'tfoot', - 'th', - 'thead', - 'tr', - ], - }, - allowfullscreen: { propertyName: 'allowFullscreen', appliesTo: ['iframe'] }, - alt: { appliesTo: ['applet', 'area', 'img', 'input'] }, - async: { appliesTo: ['script'] }, - autocomplete: { appliesTo: ['form', 'input'] }, - autofocus: { appliesTo: ['button', 'input', 'keygen', 'select', 'textarea'] }, - autoplay: { appliesTo: ['audio', 'video'] }, - autosave: { appliesTo: ['input'] }, - bgcolor: { - propertyName: 'bgColor', - appliesTo: [ - 'body', - 'col', - 'colgroup', - 'marquee', - 'table', - 'tbody', - 'tfoot', - 'td', - 'th', - 'tr', - ], - }, - border: { appliesTo: ['img', 'object', 'table'] }, - buffered: { appliesTo: ['audio', 'video'] }, - challenge: { appliesTo: ['keygen'] }, - charset: { appliesTo: ['meta', 'script'] }, - checked: { appliesTo: ['command', 'input'] }, - cite: { appliesTo: ['blockquote', 'del', 'ins', 'q'] }, - class: { propertyName: 'className' }, - code: { appliesTo: ['applet'] }, - codebase: { propertyName: 'codeBase', appliesTo: ['applet'] }, - color: { appliesTo: ['basefont', 'font', 'hr'] }, - cols: { appliesTo: ['textarea'] }, - colspan: { propertyName: 'colSpan', appliesTo: ['td', 'th'] }, - content: { appliesTo: ['meta'] }, - contenteditable: { propertyName: 'contentEditable' }, - contextmenu: {}, - controls: { appliesTo: ['audio', 'video'] }, - coords: { appliesTo: ['area'] }, - data: { appliesTo: ['object'] }, - datetime: { propertyName: 'dateTime', appliesTo: ['del', 'ins', 'time'] }, - default: { appliesTo: ['track'] }, - defer: { appliesTo: ['script'] }, - dir: {}, - dirname: { propertyName: 'dirName', appliesTo: ['input', 'textarea'] }, - disabled: { - appliesTo: [ - 'button', - 'command', - 'fieldset', - 'input', - 'keygen', - 'optgroup', - 'option', - 'select', - 'textarea', - ], - }, - download: { appliesTo: ['a', 'area'] }, - draggable: {}, - dropzone: {}, - enctype: { appliesTo: ['form'] }, - for: { propertyName: 'htmlFor', appliesTo: ['label', 'output'] }, - form: { - appliesTo: [ - 'button', - 'fieldset', - 'input', - 'keygen', - 'label', - 'meter', - 'object', - 'output', - 'progress', - 'select', - 'textarea', - ], - }, - formaction: { appliesTo: ['input', 'button'] }, - headers: { appliesTo: ['td', 'th'] }, - height: { - appliesTo: ['canvas', 'embed', 'iframe', 'img', 'input', 'object', 'video'], - }, - hidden: {}, - high: { appliesTo: ['meter'] }, - href: { appliesTo: ['a', 'area', 'base', 'link'] }, - hreflang: { appliesTo: ['a', 'area', 'link'] }, - 'http-equiv': { propertyName: 'httpEquiv', appliesTo: ['meta'] }, - icon: { appliesTo: ['command'] }, - id: {}, - indeterminate: { appliesTo: ['input'] }, - ismap: { propertyName: 'isMap', appliesTo: ['img'] }, - itemprop: {}, - keytype: { appliesTo: ['keygen'] }, - kind: { appliesTo: ['track'] }, - label: { appliesTo: ['track'] }, - lang: {}, - language: { appliesTo: ['script'] }, - loop: { appliesTo: ['audio', 'bgsound', 'marquee', 'video'] }, - low: { appliesTo: ['meter'] }, - manifest: { appliesTo: ['html'] }, - max: { appliesTo: ['input', 'meter', 'progress'] }, - maxlength: { propertyName: 'maxLength', appliesTo: ['input', 'textarea'] }, - media: { appliesTo: ['a', 'area', 'link', 'source', 'style'] }, - method: { appliesTo: ['form'] }, - min: { appliesTo: ['input', 'meter'] }, - multiple: { appliesTo: ['input', 'select'] }, - muted: { appliesTo: ['audio', 'video'] }, - name: { - appliesTo: [ - 'button', - 'form', - 'fieldset', - 'iframe', - 'input', - 'keygen', - 'object', - 'output', - 'select', - 'textarea', - 'map', - 'meta', - 'param', - ], - }, - novalidate: { propertyName: 'noValidate', appliesTo: ['form'] }, - open: { appliesTo: ['details'] }, - optimum: { appliesTo: ['meter'] }, - pattern: { appliesTo: ['input'] }, - ping: { appliesTo: ['a', 'area'] }, - placeholder: { appliesTo: ['input', 'textarea'] }, - poster: { appliesTo: ['video'] }, - preload: { appliesTo: ['audio', 'video'] }, - radiogroup: { appliesTo: ['command'] }, - readonly: { propertyName: 'readOnly', appliesTo: ['input', 'textarea'] }, - rel: { appliesTo: ['a', 'area', 'link'] }, - required: { appliesTo: ['input', 'select', 'textarea'] }, - reversed: { appliesTo: ['ol'] }, - rows: { appliesTo: ['textarea'] }, - rowspan: { propertyName: 'rowSpan', appliesTo: ['td', 'th'] }, - sandbox: { appliesTo: ['iframe'] }, - scope: { appliesTo: ['th'] }, - scoped: { appliesTo: ['style'] }, - seamless: { appliesTo: ['iframe'] }, - selected: { appliesTo: ['option'] }, - shape: { appliesTo: ['a', 'area'] }, - size: { appliesTo: ['input', 'select'] }, - sizes: { appliesTo: ['link', 'img', 'source'] }, - span: { appliesTo: ['col', 'colgroup'] }, - spellcheck: {}, - src: { - appliesTo: [ - 'audio', - 'embed', - 'iframe', - 'img', - 'input', - 'script', - 'source', - 'track', - 'video', - ], - }, - srcdoc: { appliesTo: ['iframe'] }, - srclang: { appliesTo: ['track'] }, - srcset: { appliesTo: ['img'] }, - start: { appliesTo: ['ol'] }, - step: { appliesTo: ['input'] }, - style: { propertyName: 'style.cssText' }, - summary: { appliesTo: ['table'] }, - tabindex: { propertyName: 'tabIndex' }, - target: { appliesTo: ['a', 'area', 'base', 'form'] }, - title: {}, - type: { - appliesTo: [ - 'button', - 'command', - 'embed', - 'object', - 'script', - 'source', - 'style', - 'menu', - ], - }, - usemap: { propertyName: 'useMap', appliesTo: ['img', 'input', 'object'] }, - value: { - appliesTo: [ - 'button', - 'option', - 'input', - 'li', - 'meter', - 'progress', - 'param', - 'select', - 'textarea', - ], - }, - volume: { appliesTo: ['audio', 'video'] }, - width: { - appliesTo: ['canvas', 'embed', 'iframe', 'img', 'input', 'object', 'video'], - }, - wrap: { appliesTo: ['textarea'] }, -}; - -Object.keys(attributeLookup).forEach(name => { - const metadata = attributeLookup[name]; - if (!metadata.propertyName) metadata.propertyName = name; -}); - -function optimizeStyle(value: Node[]) { - let expectingKey = true; - let i = 0; - - const props: { key: string, value: Node[] }[] = []; - let chunks = value.slice(); - - while (chunks.length) { - const chunk = chunks[0]; - - if (chunk.type !== 'Text') return null; - - const keyMatch = /^\s*([\w-]+):\s*/.exec(chunk.data); - if (!keyMatch) return null; - - const key = keyMatch[1]; - - const offset = keyMatch.index + keyMatch[0].length; - const remainingData = chunk.data.slice(offset); - - if (remainingData) { - chunks[0] = { - start: chunk.start + offset, - end: chunk.end, - type: 'Text', - data: remainingData - }; - } else { - chunks.shift(); - } - - const result = getStyleValue(chunks); - if (!result) return null; - - props.push({ key, value: result.value }); - chunks = result.chunks; - } - - return props; -} - -function getStyleValue(chunks: Node[]) { - const value: Node[] = []; - - let inUrl = false; - let quoteMark = null; - let escaped = false; - - while (chunks.length) { - const chunk = chunks.shift(); - - if (chunk.type === 'Text') { - let c = 0; - while (c < chunk.data.length) { - const char = chunk.data[c]; - - if (escaped) { - escaped = false; - } else if (char === '\\') { - escaped = true; - } else if (char === quoteMark) { - quoteMark === null; - } else if (char === '"' || char === "'") { - quoteMark = char; - } else if (char === ')' && inUrl) { - inUrl = false; - } else if (char === 'u' && chunk.data.slice(c, c + 4) === 'url(') { - inUrl = true; - } else if (char === ';' && !inUrl && !quoteMark) { - break; - } - - c += 1; - } - - if (c > 0) { - value.push({ - type: 'Text', - start: chunk.start, - end: chunk.start + c, - data: chunk.data.slice(0, c) - }); - } - - while (/[;\s]/.test(chunk.data[c])) c += 1; - const remainingData = chunk.data.slice(c); - - if (remainingData) { - chunks.unshift({ - start: chunk.start + c, - end: chunk.end, - type: 'Text', - data: remainingData - }); - - break; - } - } - - else { - value.push(chunk); - } - } - - return { - chunks, - value - }; -} - -function isDynamic(value: Node[]) { - return value.length > 1 || value[0].type !== 'Text'; -} +} \ No newline at end of file diff --git a/src/compile/nodes/EachBlock.ts b/src/compile/nodes/EachBlock.ts index e5abfc91ea..ed376fe9fd 100644 --- a/src/compile/nodes/EachBlock.ts +++ b/src/compile/nodes/EachBlock.ts @@ -78,441 +78,4 @@ export default class EachBlock extends Node { ? new ElseBlock(component, this, this.scope, info.else) : null; } - - init( - block: Block, - stripWhitespace: boolean, - nextSibling: Node - ) { - this.cannotUseInnerHTML(); - - this.var = block.getUniqueName(`each`); - this.iterations = block.getUniqueName(`${this.var}_blocks`); - this.get_each_context = this.component.getUniqueName(`get_${this.var}_context`); - - const { dependencies } = this.expression; - block.addDependencies(dependencies); - - this.block = block.child({ - comment: createDebuggingComment(this, this.component), - name: this.component.getUniqueName('create_each_block'), - key: this.key, - - bindings: new Map(block.bindings) - }); - - this.each_block_value = this.component.getUniqueName('each_value'); - - const indexName = this.index || this.component.getUniqueName(`${this.context}_index`); - - this.contexts.forEach(prop => { - this.block.bindings.set(prop.key.name, `ctx.${this.each_block_value}[ctx.${indexName}]${prop.tail}`); - }); - - if (this.index) { - this.block.getUniqueName(this.index); // this prevents name collisions (#1254) - } - - this.contextProps = this.contexts.map(prop => `child_ctx.${prop.key.name} = list[i]${prop.tail};`); - - // TODO only add these if necessary - this.contextProps.push( - `child_ctx.${this.each_block_value} = list;`, - `child_ctx.${indexName} = i;` - ); - - this.component.target.blocks.push(this.block); - this.initChildren(this.block, stripWhitespace, nextSibling); - block.addDependencies(this.block.dependencies); - this.block.hasUpdateMethod = this.block.dependencies.size > 0; - - if (this.else) { - this.else.block = block.child({ - comment: createDebuggingComment(this.else, this.component), - name: this.component.getUniqueName(`${this.block.name}_else`), - }); - - this.component.target.blocks.push(this.else.block); - this.else.initChildren( - this.else.block, - stripWhitespace, - nextSibling - ); - this.else.block.hasUpdateMethod = this.else.block.dependencies.size > 0; - } - - if (this.block.hasOutros || (this.else && this.else.block.hasOutros)) { - block.addOutro(); - } - } - - build( - block: Block, - parentNode: string, - parentNodes: string - ) { - if (this.children.length === 0) return; - - const { component } = this; - - const each = this.var; - - const create_each_block = this.block.name; - const iterations = this.iterations; - - const needsAnchor = this.next ? !this.next.isDomNode() : !parentNode || !this.parent.isDomNode(); - const anchor = needsAnchor - ? block.getUniqueName(`${each}_anchor`) - : (this.next && this.next.var) || 'null'; - - // hack the sourcemap, so that if data is missing the bug - // is easy to find - let c = this.start + 2; - while (component.source[c] !== 'e') c += 1; - component.code.overwrite(c, c + 4, 'length'); - const length = `[✂${c}-${c+4}✂]`; - - const mountOrIntro = (this.block.hasIntroMethod || this.block.hasOutroMethod) ? 'i' : 'm'; - const vars = { - each, - create_each_block, - length, - iterations, - anchor, - mountOrIntro, - }; - - const { snippet } = this.expression; - - block.builders.init.addLine(`var ${this.each_block_value} = ${snippet};`); - - this.component.target.blocks.push(deindent` - function ${this.get_each_context}(ctx, list, i) { - const child_ctx = Object.create(ctx); - ${this.contextProps} - return child_ctx; - } - `); - - if (this.key) { - this.buildKeyed(block, parentNode, parentNodes, snippet, vars); - } else { - this.buildUnkeyed(block, parentNode, parentNodes, snippet, vars); - } - - if (needsAnchor) { - block.addElement( - anchor, - `@createComment()`, - parentNodes && `@createComment()`, - parentNode - ); - } - - if (this.else) { - const each_block_else = component.getUniqueName(`${each}_else`); - const mountOrIntro = (this.else.block.hasIntroMethod || this.else.block.hasOutroMethod) ? 'i' : 'm'; - - block.builders.init.addLine(`var ${each_block_else} = null;`); - - // TODO neaten this up... will end up with an empty line in the block - block.builders.init.addBlock(deindent` - if (!${this.each_block_value}.${length}) { - ${each_block_else} = ${this.else.block.name}(#component, ctx); - ${each_block_else}.c(); - } - `); - - block.builders.mount.addBlock(deindent` - if (${each_block_else}) { - ${each_block_else}.${mountOrIntro}(${parentNode || '#target'}, null); - } - `); - - const initialMountNode = parentNode || `${anchor}.parentNode`; - - if (this.else.block.hasUpdateMethod) { - block.builders.update.addBlock(deindent` - if (!${this.each_block_value}.${length} && ${each_block_else}) { - ${each_block_else}.p(changed, ctx); - } else if (!${this.each_block_value}.${length}) { - ${each_block_else} = ${this.else.block.name}(#component, ctx); - ${each_block_else}.c(); - ${each_block_else}.${mountOrIntro}(${initialMountNode}, ${anchor}); - } else if (${each_block_else}) { - ${each_block_else}.d(1); - ${each_block_else} = null; - } - `); - } else { - block.builders.update.addBlock(deindent` - if (${this.each_block_value}.${length}) { - if (${each_block_else}) { - ${each_block_else}.d(1); - ${each_block_else} = null; - } - } else if (!${each_block_else}) { - ${each_block_else} = ${this.else.block.name}(#component, ctx); - ${each_block_else}.c(); - ${each_block_else}.${mountOrIntro}(${initialMountNode}, ${anchor}); - } - `); - } - - block.builders.destroy.addBlock(deindent` - if (${each_block_else}) ${each_block_else}.d(${parentNode ? '' : 'detach'}); - `); - } - - this.children.forEach((child: Node) => { - child.build(this.block, null, 'nodes'); - }); - - if (this.else) { - this.else.children.forEach((child: Node) => { - child.build(this.else.block, null, 'nodes'); - }); - } - } - - buildKeyed( - block: Block, - parentNode: string, - parentNodes: string, - snippet: string, - { - each, - create_each_block, - length, - anchor, - mountOrIntro, - } - ) { - const get_key = block.getUniqueName('get_key'); - const blocks = block.getUniqueName(`${each}_blocks`); - const lookup = block.getUniqueName(`${each}_lookup`); - - block.addVariable(blocks, '[]'); - block.addVariable(lookup, `@blankObject()`); - - if (this.children[0].isDomNode()) { - this.block.first = this.children[0].var; - } else { - this.block.first = this.block.getUniqueName('first'); - this.block.addElement( - this.block.first, - `@createComment()`, - parentNodes && `@createComment()`, - null - ); - } - - block.builders.init.addBlock(deindent` - const ${get_key} = ctx => ${this.key.snippet}; - - for (var #i = 0; #i < ${this.each_block_value}.${length}; #i += 1) { - let child_ctx = ${this.get_each_context}(ctx, ${this.each_block_value}, #i); - let key = ${get_key}(child_ctx); - ${blocks}[#i] = ${lookup}[key] = ${create_each_block}(#component, key, child_ctx); - } - `); - - const initialMountNode = parentNode || '#target'; - const updateMountNode = this.getUpdateMountNode(anchor); - const anchorNode = parentNode ? 'null' : 'anchor'; - - block.builders.create.addBlock(deindent` - for (#i = 0; #i < ${blocks}.length; #i += 1) ${blocks}[#i].c(); - `); - - if (parentNodes) { - block.builders.claim.addBlock(deindent` - for (#i = 0; #i < ${blocks}.length; #i += 1) ${blocks}[#i].l(${parentNodes}); - `); - } - - block.builders.mount.addBlock(deindent` - for (#i = 0; #i < ${blocks}.length; #i += 1) ${blocks}[#i].${mountOrIntro}(${initialMountNode}, ${anchorNode}); - `); - - const dynamic = this.block.hasUpdateMethod; - - const rects = block.getUniqueName('rects'); - const destroy = this.block.hasAnimation - ? `@fixAndOutroAndDestroyBlock` - : this.block.hasOutros - ? `@outroAndDestroyBlock` - : `@destroyBlock`; - - block.builders.update.addBlock(deindent` - const ${this.each_block_value} = ${snippet}; - - ${this.block.hasOutros && `@groupOutros();`} - ${this.block.hasAnimation && `for (let #i = 0; #i < ${blocks}.length; #i += 1) ${blocks}[#i].r();`} - ${blocks} = @updateKeyedEach(${blocks}, #component, changed, ${get_key}, ${dynamic ? '1' : '0'}, ctx, ${this.each_block_value}, ${lookup}, ${updateMountNode}, ${destroy}, ${create_each_block}, "${mountOrIntro}", ${anchor}, ${this.get_each_context}); - ${this.block.hasAnimation && `for (let #i = 0; #i < ${blocks}.length; #i += 1) ${blocks}[#i].a();`} - `); - - if (this.block.hasOutros && this.component.options.nestedTransitions) { - const countdown = block.getUniqueName('countdown'); - block.builders.outro.addBlock(deindent` - const ${countdown} = @callAfter(#outrocallback, ${blocks}.length); - for (#i = 0; #i < ${blocks}.length; #i += 1) ${blocks}[#i].o(${countdown}); - `); - } - - block.builders.destroy.addBlock(deindent` - for (#i = 0; #i < ${blocks}.length; #i += 1) ${blocks}[#i].d(${parentNode ? '' : 'detach'}); - `); - } - - buildUnkeyed( - block: Block, - parentNode: string, - parentNodes: string, - snippet: string, - { - create_each_block, - length, - iterations, - anchor, - mountOrIntro, - } - ) { - block.builders.init.addBlock(deindent` - var ${iterations} = []; - - for (var #i = 0; #i < ${this.each_block_value}.${length}; #i += 1) { - ${iterations}[#i] = ${create_each_block}(#component, ${this.get_each_context}(ctx, ${this.each_block_value}, #i)); - } - `); - - const initialMountNode = parentNode || '#target'; - const updateMountNode = this.getUpdateMountNode(anchor); - const anchorNode = parentNode ? 'null' : 'anchor'; - - block.builders.create.addBlock(deindent` - for (var #i = 0; #i < ${iterations}.length; #i += 1) { - ${iterations}[#i].c(); - } - `); - - if (parentNodes) { - block.builders.claim.addBlock(deindent` - for (var #i = 0; #i < ${iterations}.length; #i += 1) { - ${iterations}[#i].l(${parentNodes}); - } - `); - } - - block.builders.mount.addBlock(deindent` - for (var #i = 0; #i < ${iterations}.length; #i += 1) { - ${iterations}[#i].${mountOrIntro}(${initialMountNode}, ${anchorNode}); - } - `); - - const allDependencies = new Set(this.block.dependencies); - const { dependencies } = this.expression; - dependencies.forEach((dependency: string) => { - allDependencies.add(dependency); - }); - - const outroBlock = this.block.hasOutros && block.getUniqueName('outroBlock') - if (outroBlock) { - block.builders.init.addBlock(deindent` - function ${outroBlock}(i, detach, fn) { - if (${iterations}[i]) { - ${iterations}[i].o(() => { - if (detach) { - ${iterations}[i].d(detach); - ${iterations}[i] = null; - } - if (fn) fn(); - }); - } - } - `); - } - - // TODO do this for keyed blocks as well - const condition = Array.from(allDependencies) - .map(dependency => `changed.${dependency}`) - .join(' || '); - - if (condition !== '') { - const forLoopBody = this.block.hasUpdateMethod - ? (this.block.hasIntros || this.block.hasOutros) - ? deindent` - if (${iterations}[#i]) { - ${iterations}[#i].p(changed, child_ctx); - } else { - ${iterations}[#i] = ${create_each_block}(#component, child_ctx); - ${iterations}[#i].c(); - } - ${iterations}[#i].i(${updateMountNode}, ${anchor}); - ` - : deindent` - if (${iterations}[#i]) { - ${iterations}[#i].p(changed, child_ctx); - } else { - ${iterations}[#i] = ${create_each_block}(#component, child_ctx); - ${iterations}[#i].c(); - ${iterations}[#i].m(${updateMountNode}, ${anchor}); - } - ` - : deindent` - ${iterations}[#i] = ${create_each_block}(#component, child_ctx); - ${iterations}[#i].c(); - ${iterations}[#i].${mountOrIntro}(${updateMountNode}, ${anchor}); - `; - - const start = this.block.hasUpdateMethod ? '0' : `${iterations}.length`; - - let destroy; - - if (this.block.hasOutros) { - destroy = deindent` - @groupOutros(); - for (; #i < ${iterations}.length; #i += 1) ${outroBlock}(#i, 1); - `; - } else { - destroy = deindent` - for (; #i < ${iterations}.length; #i += 1) { - ${iterations}[#i].d(1); - } - ${iterations}.length = ${this.each_block_value}.${length}; - `; - } - - block.builders.update.addBlock(deindent` - if (${condition}) { - ${this.each_block_value} = ${snippet}; - - for (var #i = ${start}; #i < ${this.each_block_value}.${length}; #i += 1) { - const child_ctx = ${this.get_each_context}(ctx, ${this.each_block_value}, #i); - - ${forLoopBody} - } - - ${destroy} - } - `); - } - - if (outroBlock && this.component.options.nestedTransitions) { - const countdown = block.getUniqueName('countdown'); - block.builders.outro.addBlock(deindent` - ${iterations} = ${iterations}.filter(Boolean); - const ${countdown} = @callAfter(#outrocallback, ${iterations}.length); - for (let #i = 0; #i < ${iterations}.length; #i += 1) ${outroBlock}(#i, 0, ${countdown});` - ); - } - - block.builders.destroy.addBlock(`@destroyEach(${iterations}, detach);`); - } - - remount(name: string) { - // TODO consider keyed blocks - return `for (var #i = 0; #i < ${this.iterations}.length; #i += 1) ${this.iterations}[#i].m(${name}._slotted.default, null);`; - } } diff --git a/src/compile/nodes/Element.ts b/src/compile/nodes/Element.ts index cdf3543a60..5428f7818e 100644 --- a/src/compile/nodes/Element.ts +++ b/src/compile/nodes/Element.ts @@ -113,7 +113,6 @@ export default class Element extends Node { actions: Action[]; bindings: Binding[]; classes: Class[]; - classDependencies: string[]; handlers: EventHandler[]; intro?: Transition; outro?: Transition; @@ -144,7 +143,6 @@ export default class Element extends Node { this.actions = []; this.bindings = []; this.classes = []; - this.classDependencies = []; this.handlers = []; this.intro = null; @@ -614,737 +612,6 @@ export default class Element extends Node { } } - init( - block: Block, - stripWhitespace: boolean, - nextSibling: Node - ) { - if (this.name === 'slot' || this.name === 'option' || this.component.options.dev) { - this.cannotUseInnerHTML(); - } - - this.var = block.getUniqueName( - this.name.replace(/[^a-zA-Z0-9_$]/g, '_') - ); - - this.attributes.forEach(attr => { - if ( - attr.chunks && - attr.chunks.length && - (attr.chunks.length > 1 || attr.chunks[0].type !== 'Text') - ) { - this.parent.cannotUseInnerHTML(); - } - if (attr.dependencies.size) { - block.addDependencies(attr.dependencies); - - // special case —