pull/1746/head
Rich Harris 7 years ago
parent dfa3d7fa74
commit 69804ecaf8

@ -16,29 +16,4 @@ export default class Fragment extends Node {
this.scope = scope; this.scope = scope;
this.children = mapChildren(component, this, scope, info.children); this.children = mapChildren(component, this, scope, info.children);
} }
init() {
this.block = new Block({
component: this.component,
name: '@create_main_fragment',
key: null,
bindings: new Map(),
dependencies: new Set(),
});
this.component.target.blocks.push(this.block);
this.initChildren(this.block, true, null);
this.block.hasUpdateMethod = true;
}
build() {
this.init();
this.children.forEach(child => {
child.build(this.block, null, 'nodes');
});
}
} }

@ -1,27 +1,3 @@
import Node from './shared/Node';
import Tag from './shared/Tag'; import Tag from './shared/Tag';
import Block from '../render-dom/Block';
export default class MustacheTag extends Tag { export default class MustacheTag extends Tag {}
build(
block: Block,
parentNode: string,
parentNodes: string
) {
const { init } = this.renameThisMethod(
block,
value => `@setData(${this.var}, ${value});`
);
block.addElement(
this.var,
`@createText(${init})`,
parentNodes && `@claimText(${parentNodes}, ${init})`,
parentNode
);
}
remount(name: string) {
return `@append(${name}._slotted.default, ${this.var});`;
}
}

@ -1,96 +1,3 @@
import deindent from '../../utils/deindent';
import Node from './shared/Node';
import Tag from './shared/Tag'; import Tag from './shared/Tag';
import Block from '../render-dom/Block';
export default class RawMustacheTag extends Tag { export default class RawMustacheTag extends Tag {}
build(
block: Block,
parentNode: string,
parentNodes: string
) {
const name = this.var;
const needsAnchorBefore = this.prev ? this.prev.type !== 'Element' : !parentNode;
const needsAnchorAfter = this.next ? this.next.type !== 'Element' : !parentNode;
const anchorBefore = needsAnchorBefore
? block.getUniqueName(`${name}_before`)
: (this.prev && this.prev.var) || 'null';
const anchorAfter = needsAnchorAfter
? block.getUniqueName(`${name}_after`)
: (this.next && this.next.var) || 'null';
let detach: string;
let insert: (content: string) => string;
let useInnerHTML = false;
if (anchorBefore === 'null' && anchorAfter === 'null') {
useInnerHTML = true;
detach = `${parentNode}.innerHTML = '';`;
insert = content => `${parentNode}.innerHTML = ${content};`;
} else if (anchorBefore === 'null') {
detach = `@detachBefore(${anchorAfter});`;
insert = content => `${anchorAfter}.insertAdjacentHTML("beforebegin", ${content});`;
} else if (anchorAfter === 'null') {
detach = `@detachAfter(${anchorBefore});`;
insert = content => `${anchorBefore}.insertAdjacentHTML("afterend", ${content});`;
} else {
detach = `@detachBetween(${anchorBefore}, ${anchorAfter});`;
insert = content => `${anchorBefore}.insertAdjacentHTML("afterend", ${content});`;
}
const { init } = this.renameThisMethod(
block,
content => deindent`
${!useInnerHTML && detach}
${insert(content)}
`
);
// we would have used comments here, but the `insertAdjacentHTML` api only
// exists for `Element`s.
if (needsAnchorBefore) {
block.addElement(
anchorBefore,
`@createElement('noscript')`,
parentNodes && `@createElement('noscript')`,
parentNode,
true
);
}
function addAnchorAfter() {
block.addElement(
anchorAfter,
`@createElement('noscript')`,
parentNodes && `@createElement('noscript')`,
parentNode
);
}
if (needsAnchorAfter && anchorBefore === 'null') {
// anchorAfter needs to be in the DOM before we
// insert the HTML...
addAnchorAfter();
}
block.builders.mount.addLine(insert(init));
if (!parentNode) {
block.builders.destroy.addConditional('detach', needsAnchorBefore
? `${detach}\n@detachNode(${anchorBefore});`
: detach);
}
if (needsAnchorAfter && anchorBefore !== 'null') {
// ...otherwise it should go afterwards
addAnchorAfter();
}
}
remount(name: string) {
return `@append(${name}._slotted.default, ${this.var});`;
}
}

@ -1,15 +1,6 @@
import deindent from '../../utils/deindent';
import isValidIdentifier from '../../utils/isValidIdentifier';
import reservedNames from '../../utils/reservedNames';
import Node from './shared/Node'; import Node from './shared/Node';
import Element from './Element'; import Element from './Element';
import Attribute from './Attribute'; import Attribute from './Attribute';
import Block from '../render-dom/Block';
import { quotePropIfNecessary } from '../../utils/quoteIfNecessary';
function sanitize(name) {
return name.replace(/[^a-zA-Z]+/g, '_').replace(/^_/, '').replace(/_$/, '');
}
export default class Slot extends Element { export default class Slot extends Element {
type: 'Element'; type: 'Element';

@ -1,12 +1,12 @@
import CodeBuilder from '../../utils/CodeBuilder'; import CodeBuilder from '../../utils/CodeBuilder';
import deindent from '../../utils/deindent'; import deindent from '../../utils/deindent';
import { escape } from '../../utils/stringify'; import { escape } from '../../utils/stringify';
import Component from '../Component'; import Renderer from './Renderer';
export interface BlockOptions { export interface BlockOptions {
parent?: Block; parent?: Block;
name: string; name: string;
component?: Component; renderer?: Renderer;
comment?: string; comment?: string;
key?: string; key?: string;
bindings?: Map<string, string>; bindings?: Map<string, string>;
@ -15,7 +15,7 @@ export interface BlockOptions {
export default class Block { export default class Block {
parent?: Block; parent?: Block;
component: Component; renderer: Renderer;
name: string; name: string;
comment?: string; comment?: string;
@ -60,7 +60,7 @@ export default class Block {
constructor(options: BlockOptions) { constructor(options: BlockOptions) {
this.parent = options.parent; this.parent = options.parent;
this.component = options.component; this.renderer = options.renderer;
this.name = options.name; this.name = options.name;
this.comment = options.comment; this.comment = options.comment;
@ -94,7 +94,7 @@ export default class Block {
this.hasOutroMethod = false; this.hasOutroMethod = false;
this.outros = 0; this.outros = 0;
this.getUniqueName = this.component.getUniqueNameMaker(); this.getUniqueName = this.renderer.component.getUniqueNameMaker();
this.variables = new Map(); this.variables = new Map();
this.aliases = new Map() this.aliases = new Map()
@ -110,6 +110,7 @@ export default class Block {
const dupes = new Set(); const dupes = new Set();
this.wrappers.forEach(wrapper => { this.wrappers.forEach(wrapper => {
if (!wrapper.var) return;
if (wrapper.parent && wrapper.parent.canUseInnerHTML) return; if (wrapper.parent && wrapper.parent.canUseInnerHTML) return;
if (seen.has(wrapper.var)) { if (seen.has(wrapper.var)) {
@ -122,6 +123,8 @@ export default class Block {
const counts = new Map(); const counts = new Map();
this.wrappers.forEach(wrapper => { this.wrappers.forEach(wrapper => {
if (!wrapper.var) return;
if (dupes.has(wrapper.var)) { if (dupes.has(wrapper.var)) {
const i = counts.get(wrapper.var) || 0; const i = counts.get(wrapper.var) || 0;
wrapper.var = this.getUniqueName(wrapper.var + i); wrapper.var = this.getUniqueName(wrapper.var + i);
@ -159,11 +162,11 @@ export default class Block {
} }
addIntro() { addIntro() {
this.hasIntros = this.hasIntroMethod = this.component.target.hasIntroTransitions = true; this.hasIntros = this.hasIntroMethod = this.renderer.hasIntroTransitions = true;
} }
addOutro() { addOutro() {
this.hasOutros = this.hasOutroMethod = this.component.target.hasOutroTransitions = true; this.hasOutros = this.hasOutroMethod = this.renderer.hasOutroTransitions = true;
this.outros += 1; this.outros += 1;
} }
@ -198,7 +201,7 @@ export default class Block {
} }
toString() { toString() {
const { dev } = this.component.options; const { dev } = this.renderer.options;
if (this.hasIntroMethod || this.hasOutroMethod) { if (this.hasIntroMethod || this.hasOutroMethod) {
this.addVariable('#current'); this.addVariable('#current');
@ -233,7 +236,7 @@ export default class Block {
properties.addBlock(`c: @noop,`); properties.addBlock(`c: @noop,`);
} else { } else {
const hydrate = !this.builders.hydrate.isEmpty() && ( const hydrate = !this.builders.hydrate.isEmpty() && (
this.component.options.hydratable this.renderer.options.hydratable
? `this.h()` ? `this.h()`
: this.builders.hydrate : this.builders.hydrate
); );
@ -246,7 +249,7 @@ export default class Block {
`); `);
} }
if (this.component.options.hydratable) { if (this.renderer.options.hydratable) {
if (this.builders.claim.isEmpty() && this.builders.hydrate.isEmpty()) { if (this.builders.claim.isEmpty() && this.builders.hydrate.isEmpty()) {
properties.addBlock(`l: @noop,`); properties.addBlock(`l: @noop,`);
} else { } else {
@ -259,7 +262,7 @@ export default class Block {
} }
} }
if (this.component.options.hydratable && !this.builders.hydrate.isEmpty()) { if (this.renderer.options.hydratable && !this.builders.hydrate.isEmpty()) {
properties.addBlock(deindent` properties.addBlock(deindent`
${dev ? 'h: function hydrate' : 'h'}() { ${dev ? 'h: function hydrate' : 'h'}() {
${this.builders.hydrate} ${this.builders.hydrate}

@ -26,6 +26,7 @@ export default class Renderer {
constructor(component: Component, options: CompileOptions) { constructor(component: Component, options: CompileOptions) {
this.component = component; this.component = component;
this.options = options; this.options = options;
this.locate = component.locate; // TODO messy
this.readonly = new Set(); this.readonly = new Set();
this.slots = new Set(); this.slots = new Set();
@ -40,7 +41,7 @@ export default class Renderer {
// main block // main block
this.block = new Block({ this.block = new Block({
component, renderer: this,
name: '@create_main_fragment', name: '@create_main_fragment',
key: null, key: null,

@ -1,8 +1,10 @@
import Attribute from '../../../nodes/Attribute'; import Attribute from '../../../nodes/Attribute';
import Block from '../../Block'; import Block from '../../Block';
import fixAttributeCasing from '../../../../utils/fixAttributeCasing'; import fixAttributeCasing from '../../../../utils/fixAttributeCasing';
import ElementWrapper from '.'; import ElementWrapper from './index';
import { stringify } from '../../../../utils/stringify'; import { stringify } from '../../../../utils/stringify';
import Wrapper from '../shared/wrapper';
import deindent from '../../../../utils/deindent';
export default class AttributeWrapper { export default class AttributeWrapper {
node: Attribute; node: Attribute;
@ -14,6 +16,20 @@ export default class AttributeWrapper {
if (node.dependencies.size > 0) { if (node.dependencies.size > 0) {
parent.cannotUseInnerHTML(); parent.cannotUseInnerHTML();
// special case — <option value={foo}> — see below
if (this.parent.node.name === 'option' && node.name === 'value') {
let select: ElementWrapper = this.parent;
while (select && (select.node.type !== 'Element' || select.node.name !== 'select')) select = select.parent;
if (select && select.selectBindingDependencies) {
select.selectBindingDependencies.forEach(prop => {
this.node.dependencies.forEach((dependency: string) => {
this.parent.renderer.component.indirectDependencies.get(prop).add(dependency);
});
});
}
}
} }
} }

@ -0,0 +1,56 @@
import Binding from '../../../../nodes/Binding';
import Element from '../../../../nodes/Element';
import ElementWrapper from '..';
import Block from '../../../Block';
import Renderer from '../../../Renderer';
import flattenReference from '../../../../../utils/flattenReference';
import { Node } from '../../../../../interfaces';
import BindingWrapper from './Binding';
export default class SelectBinding extends BindingWrapper {
element: ElementWrapper;
node: Binding;
static filter(
node: Element,
binding_lookup: Record<string, Binding>,
type: string
) {
return node.name === 'select' && binding_lookup.value;
}
constructor(
element: ElementWrapper,
binding_lookup: Record<string, Binding>
) {
super(element, binding_lookup.value);
this.events = ['change'];
element.renderer.hasComplexBindings = true;
// TODO does this also apply to e.g. `<input type='checkbox' bind:group='foo'>`?
const dependencies = this.binding.value.dependencies;
this.element.selectBindingDependencies = dependencies;
dependencies.forEach((prop: string) => {
element.renderer.component.indirectDependencies.set(prop, new Set());
});
}
fromDom() {
return this.element.node.getStaticAttributeValue('multiple') === true ?
`@selectMultipleValue(${this.element.var})` :
`@selectValue(${this.element.var})`;
}
toDom() {
return this.element.getStaticAttributeValue('multiple') === true ?
`@selectOptions(${this.element.var}, ${this.binding.value.snippet});` :
`@selectOption(${this.element.var}, ${this.binding.value.snippet});`;
}
render(block: Block) {
super.render(block);
}
}

@ -17,10 +17,12 @@ import StyleAttributeWrapper from './StyleAttribute';
import { dimensions } from '../../../../utils/patterns'; import { dimensions } from '../../../../utils/patterns';
import InputTextBinding from './Binding/InputTextBinding'; import InputTextBinding from './Binding/InputTextBinding';
import InputRadioGroupBinding from './Binding/InputRadioGroupBinding'; import InputRadioGroupBinding from './Binding/InputRadioGroupBinding';
import SelectBinding from './Binding/SelectBinding';
const bindings = [ const bindings = [
InputTextBinding, InputTextBinding,
InputRadioGroupBinding InputRadioGroupBinding,
SelectBinding
]; ];
const events = [ const events = [
@ -98,6 +100,8 @@ export default class ElementWrapper extends Wrapper {
classDependencies: string[]; classDependencies: string[];
initialUpdate: string; initialUpdate: string;
selectBindingDependencies?: Set<string>;
var: string; var: string;
constructor( constructor(
@ -296,9 +300,9 @@ export default class ElementWrapper extends Wrapper {
} }
if (renderer.options.dev) { if (renderer.options.dev) {
const loc = renderer.locate(this.start); const loc = renderer.locate(this.node.start);
block.builders.hydrate.addLine( block.builders.hydrate.addLine(
`@addLoc(${this.var}, ${renderer.fileVar}, ${loc.line}, ${loc.column}, ${this.start});` `@addLoc(${this.var}, ${renderer.fileVar}, ${loc.line}, ${loc.column}, ${this.node.start});`
); );
} }
} }
@ -597,7 +601,7 @@ export default class ElementWrapper extends Wrapper {
`; `;
if (handler.shouldHoist) { if (handler.shouldHoist) {
component.target.blocks.push(handlerFunction); renderer.blocks.push(handlerFunction);
} else { } else {
block.builders.init.addBlock(handlerFunction); block.builders.init.addBlock(handlerFunction);
} }

@ -5,6 +5,7 @@ import Element from './Element';
import IfBlock from './IfBlock'; import IfBlock from './IfBlock';
import InlineComponent from './InlineComponent'; import InlineComponent from './InlineComponent';
import MustacheTag from './MustacheTag'; import MustacheTag from './MustacheTag';
import RawMustacheTag from './RawMustacheTag';
import Slot from './Slot'; import Slot from './Slot';
import Text from './Text'; import Text from './Text';
import Window from './Window'; import Window from './Window';
@ -22,6 +23,7 @@ const wrappers = {
IfBlock, IfBlock,
InlineComponent, InlineComponent,
MustacheTag, MustacheTag,
RawMustacheTag,
Slot, Slot,
Text, Text,
Window Window
@ -86,6 +88,8 @@ export default class FragmentWrapper {
} }
const wrapper = new TextWrapper(renderer, block, parent, child); const wrapper = new TextWrapper(renderer, block, parent, child);
if (wrapper.skip) continue;
this.nodes.unshift(wrapper); this.nodes.unshift(wrapper);
link(lastChild, lastChild = wrapper); link(lastChild, lastChild = wrapper);

@ -378,7 +378,7 @@ export default class InlineComponentWrapper extends Wrapper {
});`} });`}
${name}._fragment.c(); ${name}._fragment.c();
${this.fragment.nodes.map(child => child.remount(name))} ${this.fragment && this.fragment.nodes.map(child => child.remount(name))}
${name}._mount(${updateMountNode}, ${anchor}); ${name}._mount(${updateMountNode}, ${anchor});
${this.node.handlers.map(handler => deindent` ${this.node.handlers.map(handler => deindent`

@ -22,8 +22,4 @@ export default class MustacheTagWrapper extends Tag {
parentNode parentNode
); );
} }
remount(name: string) {
return `@append(${name}._slotted.default, ${this.var});`;
}
} }

@ -0,0 +1,101 @@
import Renderer from '../Renderer';
import Block from '../Block';
import Node from '../../nodes/shared/Node';
import Tag from './shared/Tag';
import Wrapper from './shared/wrapper';
import deindent from '../../../utils/deindent';
export default class RawMustacheTagWrapper extends Tag {
constructor(
renderer: Renderer,
block: Block,
parent: Wrapper,
node: Node
) {
super(renderer, block, parent, node);
this.cannotUseInnerHTML();
}
render(block: Block, parentNode: string, parentNodes: string) {
const name = this.var;
// TODO use isDomNode instead of type === 'Element'?
const needsAnchorBefore = this.prev ? this.prev.node.type !== 'Element' : !parentNode;
const needsAnchorAfter = this.next ? this.next.node.type !== 'Element' : !parentNode;
const anchorBefore = needsAnchorBefore
? block.getUniqueName(`${name}_before`)
: (this.prev && this.prev.var) || 'null';
const anchorAfter = needsAnchorAfter
? block.getUniqueName(`${name}_after`)
: (this.next && this.next.var) || 'null';
let detach: string;
let insert: (content: string) => string;
let useInnerHTML = false;
if (anchorBefore === 'null' && anchorAfter === 'null') {
useInnerHTML = true;
detach = `${parentNode}.innerHTML = '';`;
insert = content => `${parentNode}.innerHTML = ${content};`;
} else if (anchorBefore === 'null') {
detach = `@detachBefore(${anchorAfter});`;
insert = content => `${anchorAfter}.insertAdjacentHTML("beforebegin", ${content});`;
} else if (anchorAfter === 'null') {
detach = `@detachAfter(${anchorBefore});`;
insert = content => `${anchorBefore}.insertAdjacentHTML("afterend", ${content});`;
} else {
detach = `@detachBetween(${anchorBefore}, ${anchorAfter});`;
insert = content => `${anchorBefore}.insertAdjacentHTML("afterend", ${content});`;
}
const { init } = this.renameThisMethod(
block,
content => deindent`
${!useInnerHTML && detach}
${insert(content)}
`
);
// we would have used comments here, but the `insertAdjacentHTML` api only
// exists for `Element`s.
if (needsAnchorBefore) {
block.addElement(
anchorBefore,
`@createElement('noscript')`,
parentNodes && `@createElement('noscript')`,
parentNode,
true
);
}
function addAnchorAfter() {
block.addElement(
anchorAfter,
`@createElement('noscript')`,
parentNodes && `@createElement('noscript')`,
parentNode
);
}
if (needsAnchorAfter && anchorBefore === 'null') {
// anchorAfter needs to be in the DOM before we
// insert the HTML...
addAnchorAfter();
}
block.builders.mount.addLine(insert(init));
if (!parentNode) {
block.builders.destroy.addConditional('detach', needsAnchorBefore
? `${detach}\n@detachNode(${anchorBefore});`
: detach);
}
if (needsAnchorAfter && anchorBefore !== 'null') {
// ...otherwise it should go afterwards
addAnchorAfter();
}
}
}

@ -1,6 +1,5 @@
import Renderer from '../../Renderer'; import Renderer from '../../Renderer';
import Node from '../../../nodes/shared/Node'; import Node from '../../../nodes/shared/Node';
import { CompileOptions } from '../../../../interfaces';
import Block from '../../Block'; import Block from '../../Block';
export default class Wrapper { export default class Wrapper {
@ -20,10 +19,19 @@ export default class Wrapper {
parent: Wrapper, parent: Wrapper,
node: Node node: Node
) { ) {
this.renderer = renderer;
this.parent = parent;
this.node = node; this.node = node;
// make these non-enumerable so that they can be logged sensibly
// (TODO in dev only?)
Object.defineProperties(this, {
renderer: {
value: renderer
},
parent: {
value: parent
}
});
this.canUseInnerHTML = !renderer.options.hydratable; this.canUseInnerHTML = !renderer.options.hydratable;
block.wrappers.push(this); block.wrappers.push(this);

Loading…
Cancel
Save