get some SSR tests passing

pull/1839/head
Rich Harris 7 years ago
parent f56aab6fe4
commit a23bf00667

@ -481,10 +481,6 @@ export default class Component {
this.userVars.add(specifier.local.name); this.userVars.add(specifier.local.name);
this.declarations.push(specifier.local.name); // TODO we don't really want this, but it's convenient for now this.declarations.push(specifier.local.name); // TODO we don't really want this, but it's convenient for now
}); });
if (this.options.sveltePath && node.source.value === 'svelte') {
code.overwrite(node.source.start, node.source.end, this.options.sveltePath);
}
} }
}); });
} }

@ -75,7 +75,7 @@ export default class Attribute extends Node {
if (this.chunks.length === 1) { if (this.chunks.length === 1) {
return this.chunks[0].type === 'Text' return this.chunks[0].type === 'Text'
? stringify(this.chunks[0].data) ? stringify(this.chunks[0].data)
: this.chunks[0].snippet; : this.chunks[0].render();
} }
return (this.chunks[0].type === 'Text' ? '' : `"" + `) + return (this.chunks[0].type === 'Text' ? '' : `"" + `) +
@ -84,7 +84,7 @@ export default class Attribute extends Node {
if (chunk.type === 'Text') { if (chunk.type === 'Text') {
return stringify(chunk.data); return stringify(chunk.data);
} else { } else {
return chunk.getPrecedence() <= 13 ? `(${chunk.snippet})` : chunk.snippet; return chunk.getPrecedence() <= 13 ? `(${chunk.render()})` : chunk.render();
} }
}) })
.join(' + '); .join(' + ');

@ -7,7 +7,7 @@ export default class EventHandler extends Node {
name: string; name: string;
modifiers: Set<string>; modifiers: Set<string>;
expression: Expression; expression: Expression;
callee: any; // TODO handler_name: string;
usesComponent: boolean; usesComponent: boolean;
usesContext: boolean; usesContext: boolean;
@ -25,9 +25,7 @@ export default class EventHandler extends Node {
this.modifiers = new Set(info.modifiers); this.modifiers = new Set(info.modifiers);
if (info.expression) { if (info.expression) {
this.expression = new Expression(component, this, template_scope, info.expression, true); this.expression = new Expression(component, this, template_scope, info.expression);
this.snippet = this.expression.snippet;
this.usesContext = this.expression.usesContext; this.usesContext = this.expression.usesContext;
} else { } else {
component.init_uses_self = true; component.init_uses_self = true;
@ -41,10 +39,19 @@ export default class EventHandler extends Node {
} }
`); `);
this.snippet = `ctx.${name}`; this.handler_name = name;
Object.defineProperty(this, 'snippet', {
get: () => {
throw new Error('here');
}
});
} }
}
// TODO figure out what to do about custom events render() {
// this.isCustomEvent = component.events.has(this.name); return this.expression
? this.expression.render()
: `ctx.${this.handler_name}`;
} }
} }

@ -20,7 +20,7 @@ export default class InlineComponent extends Node {
constructor(component: Component, parent, scope, info) { constructor(component: Component, parent, scope, info) {
super(component, parent, scope, info); super(component, parent, scope, info);
if (info.name !== 'svelte:component') { if (info.name !== 'svelte:component' && info.name !== 'svelte:self') {
component.warn_if_undefined(info, scope); component.warn_if_undefined(info, scope);
component.expectedProperties.add(info.name); component.expectedProperties.add(info.name);
} }

@ -2,13 +2,14 @@ import Component from '../../Component';
import { walk } from 'estree-walker'; import { walk } from 'estree-walker';
import isReference from 'is-reference'; import isReference from 'is-reference';
import flattenReference from '../../../utils/flattenReference'; import flattenReference from '../../../utils/flattenReference';
import { createScopes } from '../../../utils/annotateWithScopes'; import { createScopes, Scope } from '../../../utils/annotateWithScopes';
import { Node } from '../../../interfaces'; import { Node } from '../../../interfaces';
import addToSet from '../../../utils/addToSet'; import addToSet from '../../../utils/addToSet';
import globalWhitelist from '../../../utils/globalWhitelist'; import globalWhitelist from '../../../utils/globalWhitelist';
import deindent from '../../../utils/deindent'; import deindent from '../../../utils/deindent';
import Wrapper from '../../render-dom/wrappers/shared/Wrapper'; import Wrapper from '../../render-dom/wrappers/shared/Wrapper';
import sanitize from '../../../utils/sanitize'; import sanitize from '../../../utils/sanitize';
import TemplateScope from './TemplateScope';
const binaryOperators: Record<string, number> = { const binaryOperators: Record<string, number> = {
'**': 15, '**': 15,
@ -60,27 +61,43 @@ const precedence: Record<string, (node?: Node) => number> = {
export default class Expression { export default class Expression {
component: Component; component: Component;
owner: Wrapper;
node: any; node: any;
snippet: string; snippet: string;
references: Set<string>; references: Set<string>;
dependencies: Set<string>; dependencies: Set<string>;
contextual_dependencies: Set<string>; contextual_dependencies: Set<string>;
template_scope: TemplateScope;
scope: Scope;
scope_map: WeakMap<Node, Scope>;
is_synthetic: boolean;
declarations: string[] = []; declarations: string[] = [];
usesContext = false; usesContext = false;
usesEvent = false; usesEvent = false;
constructor(component: Component, owner: Wrapper, scope, info, isEventHandler?: boolean) { rendered: string;
constructor(component: Component, owner: Wrapper, template_scope: TemplateScope, info) {
// TODO revert to direct property access in prod? // TODO revert to direct property access in prod?
Object.defineProperties(this, { Object.defineProperties(this, {
component: { component: {
value: component value: component
},
// TODO remove this, is just for debugging
snippet: {
get: () => {
throw new Error(`cannot access expression.snippet, use expression.render() instead`)
}
} }
}); });
this.node = info; this.node = info;
this.template_scope = template_scope;
this.snippet = `[✂${info.start}-${info.end}✂]`; this.owner = owner;
this.is_synthetic = owner.isSynthetic;
const expression_dependencies = new Set(); const expression_dependencies = new Set();
const expression_contextual_dependencies = new Set(); const expression_contextual_dependencies = new Set();
@ -88,48 +105,34 @@ export default class Expression {
let dependencies = expression_dependencies; let dependencies = expression_dependencies;
let contextual_dependencies = expression_contextual_dependencies; let contextual_dependencies = expression_contextual_dependencies;
const { declarations } = this; let { map, scope } = createScopes(info);
const { code } = component; this.scope = scope;
this.scope_map = map;
let { map, scope: currentScope } = createScopes(info);
const expression = this; const expression = this;
const isSynthetic = owner.isSynthetic;
let function_expression;
let pending_assignments = new Set();
// discover dependencies, but don't change the code yet
walk(info, { walk(info, {
enter(node: any, parent: any, key: string) { enter(node: any, parent: any, key: string) {
// don't manipulate shorthand props twice // don't manipulate shorthand props twice
if (key === 'value' && parent.shorthand) return; if (key === 'value' && parent.shorthand) return;
code.addSourcemapLocation(node.start);
code.addSourcemapLocation(node.end);
if (map.has(node)) { if (map.has(node)) {
currentScope = map.get(node); scope = map.get(node);
} }
if (isReference(node, parent)) { if (isReference(node, parent)) {
const { name, nodes } = flattenReference(node); const { name } = flattenReference(node);
if (currentScope.has(name)) return; if (scope.has(name)) return;
if (globalWhitelist.has(name) && component.declarations.indexOf(name) === -1) return; if (globalWhitelist.has(name) && component.declarations.indexOf(name) === -1) return;
if (!isSynthetic && !function_expression) { if (template_scope.names.has(name)) {
// <option> value attribute could be synthetic — avoid double editing
code.prependRight(node.start, key === 'key' && parent.shorthand
? `${name}: ctx.`
: 'ctx.');
}
if (scope.names.has(name)) {
expression.usesContext = true; expression.usesContext = true;
contextual_dependencies.add(name); contextual_dependencies.add(name);
scope.dependenciesForName.get(name).forEach(dependency => { template_scope.dependenciesForName.get(name).forEach(dependency => {
dependencies.add(dependency); dependencies.add(dependency);
}); });
} else { } else {
@ -137,6 +140,93 @@ export default class Expression {
component.expectedProperties.add(name); component.expectedProperties.add(name);
} }
this.skip();
}
if (node.type === 'CallExpression') {
// TODO remove this? rely on reactive declarations?
if (node.callee.type === 'Identifier') {
const dependencies_for_invocation = component.findDependenciesForFunctionCall(node.callee.name);
if (dependencies_for_invocation) {
addToSet(dependencies, dependencies_for_invocation);
} else {
dependencies.add('$$BAIL$$');
}
} else {
dependencies.add('$$BAIL$$');
}
}
}
});
this.dependencies = dependencies;
this.contextual_dependencies = contextual_dependencies;
}
getPrecedence() {
return this.node.type in precedence ? precedence[this.node.type](this.node) : 0;
}
render() {
if (this.rendered) return this.rendered;
// <option> value attribute could be synthetic — avoid double editing
// TODO move this logic into Element?
if (this.is_synthetic) return;
const {
component,
declarations,
scope_map: map,
template_scope,
owner
} = this;
let scope = this.scope;
const { code } = component;
let function_expression;
let pending_assignments = new Set();
let dependencies: Set<string>;
let contextual_dependencies: Set<string>;
// rewrite code as appropriate
walk(this.node, {
enter(node: any, parent: any, key: string) {
// don't manipulate shorthand props twice
if (key === 'value' && parent.shorthand) return;
code.addSourcemapLocation(node.start);
code.addSourcemapLocation(node.end);
if (map.has(node)) {
scope = map.get(node);
}
if (isReference(node, parent)) {
const { name, nodes } = flattenReference(node);
if (scope.has(name)) return;
if (globalWhitelist.has(name) && component.declarations.indexOf(name) === -1) return;
if (function_expression) {
if (template_scope.names.has(name)) {
contextual_dependencies.add(name);
template_scope.dependenciesForName.get(name).forEach(dependency => {
dependencies.add(dependency);
});
} else {
dependencies.add(name);
component.expectedProperties.add(name);
}
} else {
code.prependRight(node.start, key === 'key' && parent.shorthand
? `${name}: ctx.`
: 'ctx.');
}
if (node.type === 'MemberExpression') { if (node.type === 'MemberExpression') {
nodes.forEach(node => { nodes.forEach(node => {
code.addSourcemapLocation(node.start); code.addSourcemapLocation(node.start);
@ -165,24 +255,11 @@ export default class Expression {
dependencies = new Set(); dependencies = new Set();
contextual_dependencies = new Set(); contextual_dependencies = new Set();
} }
if (node.type === 'CallExpression') {
if (node.callee.type === 'Identifier') {
const dependencies_for_invocation = component.findDependenciesForFunctionCall(node.callee.name);
if (dependencies_for_invocation) {
addToSet(dependencies, dependencies_for_invocation);
} else {
dependencies.add('$$BAIL$$');
}
} else {
dependencies.add('$$BAIL$$');
}
}
} }
}, },
leave(node: Node) { leave(node: Node) {
if (map.has(node)) currentScope = currentScope.parent; if (map.has(node)) scope = scope.parent;
if (node === function_expression) { if (node === function_expression) {
if (pending_assignments.size > 0) { if (pending_assignments.size > 0) {
@ -259,8 +336,8 @@ export default class Expression {
} }
function_expression = null; function_expression = null;
dependencies = expression_dependencies; dependencies = null;
contextual_dependencies = expression_contextual_dependencies; contextual_dependencies = null;
} }
if (/Statement/.test(node.type)) { if (/Statement/.test(node.type)) {
@ -284,12 +361,7 @@ export default class Expression {
} }
}); });
this.dependencies = dependencies; return this.rendered = `[✂${this.node.start}-${this.node.end}✂]`;
this.contextual_dependencies = contextual_dependencies;
}
getPrecedence() {
return this.node.type in precedence ? precedence[this.node.type](this.node) : 0;
} }
} }

@ -125,7 +125,7 @@ export default class AwaitBlockWrapper extends Wrapper {
const anchor = this.getOrCreateAnchor(block, parentNode, parentNodes); const anchor = this.getOrCreateAnchor(block, parentNode, parentNodes);
const updateMountNode = this.getUpdateMountNode(anchor); const updateMountNode = this.getUpdateMountNode(anchor);
const { snippet } = this.node.expression; const snippet = this.node.expression.render();
const info = block.getUniqueName(`info`); const info = block.getUniqueName(`info`);
const promise = block.getUniqueName(`promise`); const promise = block.getUniqueName(`promise`);

@ -8,12 +8,14 @@ export default class DocumentWrapper extends Wrapper {
render(block: Block, parentNode: string, parentNodes: string) { render(block: Block, parentNode: string, parentNodes: string) {
this.node.handlers.forEach(handler => { this.node.handlers.forEach(handler => {
const snippet = handler.render();
block.builders.init.addBlock(deindent` block.builders.init.addBlock(deindent`
document.addEventListener("${handler.name}", ${handler.snippet}); document.addEventListener("${handler.name}", ${snippet});
`); `);
block.builders.destroy.addBlock(deindent` block.builders.destroy.addBlock(deindent`
document.removeEventListener("${handler.name}", ${handler.snippet}); document.removeEventListener("${handler.name}", ${snippet});
`); `);
}); });
} }

@ -177,7 +177,7 @@ export default class EachBlockWrapper extends Wrapper {
if (this.hasBinding) this.contextProps.push(`child_ctx.${this.vars.each_block_value} = list;`); if (this.hasBinding) this.contextProps.push(`child_ctx.${this.vars.each_block_value} = list;`);
if (this.hasBinding || this.node.index) this.contextProps.push(`child_ctx.${this.indexName} = i;`); if (this.hasBinding || this.node.index) this.contextProps.push(`child_ctx.${this.indexName} = i;`);
const { snippet } = this.node.expression; const snippet = this.node.expression.render();
block.builders.init.addLine(`var ${this.vars.each_block_value} = ${snippet};`); block.builders.init.addLine(`var ${this.vars.each_block_value} = ${snippet};`);
@ -299,7 +299,7 @@ export default class EachBlockWrapper extends Wrapper {
} }
block.builders.init.addBlock(deindent` block.builders.init.addBlock(deindent`
const ${get_key} = ctx => ${this.node.key.snippet}; const ${get_key} = ctx => ${this.node.key.render()};
for (var #i = 0; #i < ${this.vars.each_block_value}.${length}; #i += 1) { for (var #i = 0; #i < ${this.vars.each_block_value}.${length}; #i += 1) {
let child_ctx = ${this.vars.get_each_context}(ctx, ${this.vars.each_block_value}, #i); let child_ctx = ${this.vars.get_each_context}(ctx, ${this.vars.each_block_value}, #i);

@ -78,7 +78,7 @@ export default class AttributeWrapper {
// DRY it out if that's possible without introducing crazy indirection // DRY it out if that's possible without introducing crazy indirection
if (this.node.chunks.length === 1) { if (this.node.chunks.length === 1) {
// single {tag} — may be a non-string // single {tag} — may be a non-string
value = this.node.chunks[0].snippet; value = this.node.chunks[0].render();
} else { } else {
// '{foo} {bar}' — treat as string concatenation // '{foo} {bar}' — treat as string concatenation
value = value =
@ -89,8 +89,8 @@ export default class AttributeWrapper {
return stringify(chunk.data); return stringify(chunk.data);
} else { } else {
return chunk.getPrecedence() <= 13 return chunk.getPrecedence() <= 13
? `(${chunk.snippet})` ? `(${chunk.render()})`
: chunk.snippet; : chunk.render();
} }
}) })
.join(' + '); .join(' + ');
@ -217,7 +217,7 @@ export default class AttributeWrapper {
return `="${value.map(chunk => { return `="${value.map(chunk => {
return chunk.type === 'Text' return chunk.type === 'Text'
? chunk.data.replace(/"/g, '\\"') ? chunk.data.replace(/"/g, '\\"')
: `\${${chunk.snippet}}` : `\${${chunk.render()}}`
})}"`; })}"`;
} }
} }

@ -75,7 +75,7 @@ export default class BindingWrapper {
const { name } = getObject(this.node.expression.node); const { name } = getObject(this.node.expression.node);
const { snippet } = this.node.expression; const snippet = this.node.expression.render();
// TODO unfortunate code is necessary because we need to use `ctx` // TODO unfortunate code is necessary because we need to use `ctx`
// inside the fragment, but not inside the <script> // inside the fragment, but not inside the <script>

@ -32,7 +32,8 @@ export default class StyleAttributeWrapper extends AttributeWrapper {
if (chunk.type === 'Text') { if (chunk.type === 'Text') {
return stringify(chunk.data); return stringify(chunk.data);
} else { } else {
const { dependencies, snippet } = chunk; const { dependencies } = chunk;
const snippet = chunk.render();
dependencies.forEach(d => { dependencies.forEach(d => {
propDependencies.add(d); propDependencies.add(d);

@ -283,10 +283,6 @@ export default class ElementWrapper extends Wrapper {
}); });
} }
let hasHoistedEventHandlerOrBinding = (
//(this.hasAncestor('EachBlock') && this.bindings.length > 0) ||
this.node.handlers.some(handler => handler.shouldHoist)
);
const eventHandlerOrBindingUsesComponent = ( const eventHandlerOrBindingUsesComponent = (
this.bindings.length > 0 || this.bindings.length > 0 ||
this.node.handlers.some(handler => handler.usesComponent) this.node.handlers.some(handler => handler.usesComponent)
@ -298,30 +294,8 @@ export default class ElementWrapper extends Wrapper {
this.node.actions.some(action => action.usesContext) this.node.actions.some(action => action.usesContext)
); );
if (hasHoistedEventHandlerOrBinding) { if (eventHandlerOrBindingUsesContext) {
const initialProps: string[] = []; block.maintainContext = true;
const updates: string[] = [];
if (eventHandlerOrBindingUsesComponent) {
const component = block.alias('component');
initialProps.push(component === 'component' ? 'component' : `component: ${component}`);
}
if (eventHandlerOrBindingUsesContext) {
initialProps.push(`ctx`);
block.builders.update.addLine(`${node}._svelte.ctx = ctx;`);
block.maintainContext = true;
}
if (initialProps.length) {
block.builders.hydrate.addBlock(deindent`
${node}._svelte = { ${initialProps.join(', ')} };
`);
}
} else {
if (eventHandlerOrBindingUsesContext) {
block.maintainContext = true;
}
} }
this.addBindings(block); this.addBindings(block);
@ -595,7 +569,8 @@ export default class ElementWrapper extends Wrapper {
: null; : null;
if (attr.isSpread) { if (attr.isSpread) {
const { snippet, dependencies } = attr.expression; const { dependencies } = attr.expression;
const snippet = attr.expression.render();
initialProps.push(snippet); initialProps.push(snippet);
@ -659,7 +634,7 @@ export default class ElementWrapper extends Wrapper {
if (intro === outro) { if (intro === outro) {
const name = block.getUniqueName(`${this.var}_transition`); const name = block.getUniqueName(`${this.var}_transition`);
const snippet = intro.expression const snippet = intro.expression
? intro.expression.snippet ? intro.expression.render()
: '{}'; : '{}';
block.addVariable(name); block.addVariable(name);
@ -691,7 +666,7 @@ export default class ElementWrapper extends Wrapper {
if (intro) { if (intro) {
block.addVariable(introName); block.addVariable(introName);
const snippet = intro.expression const snippet = intro.expression
? intro.expression.snippet ? intro.expression.render()
: '{}'; : '{}';
const fn = `ctx.${intro.name}`; // TODO add built-in transitions? const fn = `ctx.${intro.name}`; // TODO add built-in transitions?
@ -714,7 +689,7 @@ export default class ElementWrapper extends Wrapper {
if (outro) { if (outro) {
block.addVariable(outroName); block.addVariable(outroName);
const snippet = outro.expression const snippet = outro.expression
? outro.expression.snippet ? outro.expression.render()
: '{}'; : '{}';
const fn = `ctx.${outro.name}`; const fn = `ctx.${outro.name}`;
@ -753,7 +728,7 @@ export default class ElementWrapper extends Wrapper {
if (${animation}) ${animation}.stop(); if (${animation}) ${animation}.stop();
`); `);
const params = this.node.animation.expression ? this.node.animation.expression.snippet : '{}'; const params = this.node.animation.expression ? this.node.animation.expression.render() : '{}';
block.builders.animate.addBlock(deindent` block.builders.animate.addBlock(deindent`
if (${animation}) ${animation}.stop(); if (${animation}) ${animation}.stop();
${animation} = @wrapAnimation(${this.var}, ${rect}, ctx.${this.node.animation.name}, ${params}); ${animation} = @wrapAnimation(${this.var}, ${rect}, ctx.${this.node.animation.name}, ${params});
@ -769,7 +744,7 @@ export default class ElementWrapper extends Wrapper {
const { expression, name } = classDir; const { expression, name } = classDir;
let snippet, dependencies; let snippet, dependencies;
if (expression) { if (expression) {
snippet = expression.snippet; snippet = expression.render();
dependencies = expression.dependencies; dependencies = expression.dependencies;
} else { } else {
snippet = `${quotePropIfNecessary(name)}`; snippet = `${quotePropIfNecessary(name)}`;

@ -32,7 +32,7 @@ class IfBlockBranch extends Wrapper {
) { ) {
super(renderer, block, parent, node); super(renderer, block, parent, node);
this.condition = (<IfBlock>node).expression && (<IfBlock>node).expression.snippet; this.condition = (<IfBlock>node).expression && (<IfBlock>node).expression.render();
this.block = block.child({ this.block = block.child({
comment: createDebuggingComment(node, parent.renderer.component), comment: createDebuggingComment(node, parent.renderer.component),

@ -146,7 +146,7 @@ export default class InlineComponentWrapper extends Wrapper {
: null; : null;
if (attr.isSpread) { if (attr.isSpread) {
const value = attr.expression.snippet; const value = attr.expression.render();
initialProps.push(value); initialProps.push(value);
changes.push(condition ? `${condition} && ${value}` : value); changes.push(condition ? `${condition} && ${value}` : value);
@ -199,15 +199,17 @@ export default class InlineComponentWrapper extends Wrapper {
const updating = block.getUniqueName(`updating_${binding.name}`); const updating = block.getUniqueName(`updating_${binding.name}`);
block.addVariable(updating); block.addVariable(updating);
const snippet = binding.expression.render();
statements.push(deindent` statements.push(deindent`
if (${binding.expression.snippet} !== void 0) { if (${snippet} !== void 0) {
${name_initial_data}${quotePropIfNecessary(binding.name)} = ${binding.expression.snippet}; ${name_initial_data}${quotePropIfNecessary(binding.name)} = ${snippet};
}` }`
); );
updates.push(deindent` updates.push(deindent`
if (!${updating} && ${[...binding.expression.dependencies].map((dependency: string) => `changed.${dependency}`).join(' || ')}) { if (!${updating} && ${[...binding.expression.dependencies].map((dependency: string) => `changed.${dependency}`).join(' || ')}) {
${name_changes}${quotePropIfNecessary(binding.name)} = ${binding.expression.snippet}; ${name_changes}${quotePropIfNecessary(binding.name)} = ${snippet};
} }
`); `);
@ -267,14 +269,14 @@ export default class InlineComponentWrapper extends Wrapper {
}); });
} }
return `${name}.$on("${handler.name}", ${handler.snippet});`; return `${name}.$on("${handler.name}", ${handler.render()});`;
}); });
if (this.node.name === 'svelte:component') { if (this.node.name === 'svelte:component') {
const switch_value = block.getUniqueName('switch_value'); const switch_value = block.getUniqueName('switch_value');
const switch_props = block.getUniqueName('switch_props'); const switch_props = block.getUniqueName('switch_props');
const { snippet } = this.node.expression; const snippet = this.node.expression.render();
block.builders.init.addBlock(deindent` block.builders.init.addBlock(deindent`
var ${switch_value} = ${snippet}; var ${switch_value} = ${snippet};

@ -47,7 +47,8 @@ export default class TitleWrapper extends Wrapper {
if (chunk.type === 'Text') { if (chunk.type === 'Text') {
return stringify(chunk.data); return stringify(chunk.data);
} else { } else {
const { dependencies, snippet } = chunk.expression; const { dependencies } = chunk.expression;
const snippet = chunk.expression.render();
dependencies.forEach(d => { dependencies.forEach(d => {
allDependencies.add(d); allDependencies.add(d);

@ -1,7 +1,6 @@
import Wrapper from './Wrapper'; import Wrapper from './Wrapper';
import Renderer from '../../Renderer'; import Renderer from '../../Renderer';
import Block from '../../Block'; import Block from '../../Block';
import Node from '../../../nodes/shared/Node';
import MustacheTag from '../../../nodes/MustacheTag'; import MustacheTag from '../../../nodes/MustacheTag';
import RawMustacheTag from '../../../nodes/RawMustacheTag'; import RawMustacheTag from '../../../nodes/RawMustacheTag';
@ -19,7 +18,8 @@ export default class Tag extends Wrapper {
block: Block, block: Block,
update: ((value: string) => string) update: ((value: string) => string)
) { ) {
const { snippet, dependencies } = this.node.expression; const { dependencies } = this.node.expression;
const snippet = this.node.expression.render();
const value = this.node.shouldCache && block.getUniqueName(`${this.var}_value`); const value = this.node.shouldCache && block.getUniqueName(`${this.var}_value`);
const content = this.node.shouldCache ? value : snippet; const content = this.node.shouldCache ? value : snippet;

@ -11,7 +11,7 @@ export default function addActions(
const { expression } = action; const { expression } = action;
let snippet, dependencies; let snippet, dependencies;
if (expression) { if (expression) {
snippet = expression.snippet; snippet = expression.render();
dependencies = expression.dependencies; dependencies = expression.dependencies;
expression.declarations.forEach(declaration => { expression.declarations.forEach(declaration => {

@ -12,25 +12,28 @@ export default function addEventHandlers(
if (handler.modifiers.has('stopPropagation')) modifiers.push('event.stopPropagation();'); if (handler.modifiers.has('stopPropagation')) modifiers.push('event.stopPropagation();');
const opts = ['passive', 'once', 'capture'].filter(mod => handler.modifiers.has(mod)); const opts = ['passive', 'once', 'capture'].filter(mod => handler.modifiers.has(mod));
const snippet = handler.render();
if (opts.length) { if (opts.length) {
const optString = (opts.length === 1 && opts[0] === 'capture') const optString = (opts.length === 1 && opts[0] === 'capture')
? 'true' ? 'true'
: `{ ${opts.map(opt => `${opt}: true`).join(', ')} }`; : `{ ${opts.map(opt => `${opt}: true`).join(', ')} }`;
block.builders.hydrate.addLine( block.builders.hydrate.addLine(
`@addListener(${target}, "${handler.name}", ${handler.snippet}, ${optString});` `@addListener(${target}, "${handler.name}", ${snippet}, ${optString});`
); );
block.builders.destroy.addLine( block.builders.destroy.addLine(
`@removeListener(${target}, "${handler.name}", ${handler.snippet}, ${optString});` `@removeListener(${target}, "${handler.name}", ${snippet}, ${optString});`
); );
} else { } else {
block.builders.hydrate.addLine( block.builders.hydrate.addLine(
`@addListener(${target}, "${handler.name}", ${handler.snippet});` `@addListener(${target}, "${handler.name}", ${snippet});`
); );
block.builders.destroy.addLine( block.builders.destroy.addLine(
`@removeListener(${target}, "${handler.name}", ${handler.snippet});` `@removeListener(${target}, "${handler.name}", ${snippet});`
); );
} }

@ -2,8 +2,6 @@ import Renderer from '../Renderer';
import { CompileOptions } from '../../../interfaces'; import { CompileOptions } from '../../../interfaces';
export default function(node, renderer: Renderer, options: CompileOptions) { export default function(node, renderer: Renderer, options: CompileOptions) {
const { snippet } = node.expression;
renderer.append('${(function(__value) { if(@isPromise(__value)) return `'); renderer.append('${(function(__value) { if(@isPromise(__value)) return `');
renderer.render(node.pending.children, options); renderer.render(node.pending.children, options);
@ -12,5 +10,6 @@ export default function(node, renderer: Renderer, options: CompileOptions) {
renderer.render(node.then.children, options); renderer.render(node.then.children, options);
const snippet = node.expression.render();
renderer.append(`\`;}(Object.assign({}, ctx, { ${node.value}: __value }));}(${snippet})) }`); renderer.append(`\`;}(Object.assign({}, ctx, { ${node.value}: __value }));}(${snippet})) }`);
} }

@ -1,5 +1,5 @@
export default function(node, renderer, options) { export default function(node, renderer, options) {
const { snippet } = node.expression; const snippet = node.expression.render();
const props = node.contexts.map(prop => `${prop.key.name}: item${prop.tail}`); const props = node.contexts.map(prop => `${prop.key.name}: item${prop.tail}`);

@ -60,7 +60,7 @@ export default function(node, renderer, options) {
const classExpr = node.classes.map((classDir: Class) => { const classExpr = node.classes.map((classDir: Class) => {
const { expression, name } = classDir; const { expression, name } = classDir;
const snippet = expression ? expression.snippet : `ctx${quotePropIfNecessary(name)}`; const snippet = expression ? expression.render() : `ctx${quotePropIfNecessary(name)}`;
return `${snippet} ? "${name}" : ""`; return `${snippet} ? "${name}" : ""`;
}).join(', '); }).join(', ');
@ -71,7 +71,7 @@ export default function(node, renderer, options) {
const args = []; const args = [];
node.attributes.forEach(attribute => { node.attributes.forEach(attribute => {
if (attribute.isSpread) { if (attribute.isSpread) {
args.push(attribute.expression.snippet); args.push(attribute.expression.render());
} else { } else {
if (attribute.name === 'value' && node.name === 'textarea') { if (attribute.name === 'value' && node.name === 'textarea') {
textareaContents = stringifyAttribute(attribute); textareaContents = stringifyAttribute(attribute);
@ -83,7 +83,7 @@ export default function(node, renderer, options) {
attribute.chunks[0].type !== 'Text' attribute.chunks[0].type !== 'Text'
) { ) {
// a boolean attribute with one non-Text chunk // a boolean attribute with one non-Text chunk
args.push(`{ ${quoteNameIfNecessary(attribute.name)}: ${attribute.chunks[0].snippet} }`); args.push(`{ ${quoteNameIfNecessary(attribute.name)}: ${attribute.chunks[0].render()} }`);
} else { } else {
args.push(`{ ${quoteNameIfNecessary(attribute.name)}: \`${stringifyAttribute(attribute)}\` }`); args.push(`{ ${quoteNameIfNecessary(attribute.name)}: \`${stringifyAttribute(attribute)}\` }`);
} }
@ -105,13 +105,13 @@ export default function(node, renderer, options) {
attribute.chunks[0].type !== 'Text' attribute.chunks[0].type !== 'Text'
) { ) {
// a boolean attribute with one non-Text chunk // a boolean attribute with one non-Text chunk
openingTag += '${' + attribute.chunks[0].snippet + ' ? " ' + attribute.name + '" : "" }'; openingTag += '${' + attribute.chunks[0].render() + ' ? " ' + attribute.name + '" : "" }';
} else if (attribute.name === 'class' && classExpr) { } else if (attribute.name === 'class' && classExpr) {
addClassAttribute = false; addClassAttribute = false;
openingTag += ` class="\${[\`${stringifyAttribute(attribute)}\`, ${classExpr}].join(' ').trim() }"`; openingTag += ` class="\${[\`${stringifyAttribute(attribute)}\`, ${classExpr}].join(' ').trim() }"`;
} else if (attribute.chunks.length === 1 && attribute.chunks[0].type !== 'Text') { } else if (attribute.chunks.length === 1 && attribute.chunks[0].type !== 'Text') {
const { name } = attribute; const { name } = attribute;
const { snippet } = attribute.chunks[0]; const snippet = attribute.chunks[0].render();
openingTag += '${(v => v == null ? "" : ` ' + name + '="${@escape(' + snippet + ')}"`)(' + snippet + ')}'; openingTag += '${(v => v == null ? "" : ` ' + name + '="${@escape(' + snippet + ')}"`)(' + snippet + ')}';
} else { } else {
@ -121,11 +121,12 @@ export default function(node, renderer, options) {
} }
node.bindings.forEach(binding => { node.bindings.forEach(binding => {
const { name, expression: { snippet } } = binding; const { name, expression } = binding;
if (name === 'group') { if (name === 'group') {
// TODO server-render group bindings // TODO server-render group bindings
} else { } else {
const snippet = expression.render();
openingTag += ' ${(v => v ? ("' + name + '" + (v === true ? "" : "=" + JSON.stringify(v))) : "")(' + snippet + ')}'; openingTag += ' ${(v => v ? ("' + name + '" + (v === true ? "" : "=" + JSON.stringify(v))) : "")(' + snippet + ')}';
} }
}); });

@ -1,3 +1,3 @@
export default function(node, renderer, options) { export default function(node, renderer, options) {
renderer.append('${' + node.expression.snippet + '}'); renderer.append('${' + node.expression.render() + '}');
} }

@ -1,5 +1,5 @@
export default function(node, renderer, options) { export default function(node, renderer, options) {
const { snippet } = node.expression; const snippet = node.expression.render();
renderer.append('${ ' + snippet + ' ? `'); renderer.append('${ ' + snippet + ' ? `');

@ -12,7 +12,7 @@ export default function(node, renderer, options) {
return escapeTemplate(escape(chunk.data)); return escapeTemplate(escape(chunk.data));
} }
return '${@escape( ' + chunk.snippet + ')}'; return '${@escape( ' + chunk.render() + ')}';
} }
const bindingProps = node.bindings.map(binding => { const bindingProps = node.bindings.map(binding => {
@ -34,7 +34,7 @@ export default function(node, renderer, options) {
return stringify(chunk.data); return stringify(chunk.data);
} }
return chunk.snippet; return chunk.render();
} }
return '`' + attribute.chunks.map(stringifyAttribute).join('') + '`'; return '`' + attribute.chunks.map(stringifyAttribute).join('') + '`';
@ -47,7 +47,7 @@ export default function(node, renderer, options) {
node.attributes node.attributes
.map(attribute => { .map(attribute => {
if (attribute.isSpread) { if (attribute.isSpread) {
return attribute.expression.snippet; return attribute.expression.render();
} else { } else {
return `{ ${quoteNameIfNecessary(attribute.name)}: ${getAttributeValue(attribute)} }`; return `{ ${quoteNameIfNecessary(attribute.name)}: ${getAttributeValue(attribute)} }`;
} }
@ -64,7 +64,7 @@ export default function(node, renderer, options) {
node.name === 'svelte:self' node.name === 'svelte:self'
? node.component.name ? node.component.name
: node.name === 'svelte:component' : node.name === 'svelte:component'
? `((${node.expression.snippet}) || @missingComponent)` ? `((${node.expression.render()}) || @missingComponent)`
: `ctx.${node.name}` : `ctx.${node.name}`
); );
@ -75,7 +75,7 @@ export default function(node, renderer, options) {
while (parent = parent.parent) { while (parent = parent.parent) {
if (parent.type === 'IfBlock') { if (parent.type === 'IfBlock') {
// TODO handle contextual bindings... // TODO handle contextual bindings...
conditions.push(`(${parent.expression.snippet})`); conditions.push(`(${parent.expression.render()})`);
} }
} }

@ -1,9 +1,11 @@
export default function(node, renderer, options) { export default function(node, renderer, options) {
const snippet = node.expression.render();
renderer.append( renderer.append(
node.parent && node.parent &&
node.parent.type === 'Element' && node.parent.type === 'Element' &&
node.parent.name === 'style' node.parent.name === 'style'
? '${' + node.expression.snippet + '}' ? '${' + snippet + '}'
: '${@escape(' + node.expression.snippet + ')}' : '${@escape(' + snippet + ')}'
); );
} }

@ -191,7 +191,7 @@ export function showOutput(cwd, options = {}, compile = svelte.compile) {
); );
console.log( // eslint-disable-line no-console console.log( // eslint-disable-line no-console
`\n>> ${colors.cyan().bold(file)}\n${addLineNumbers(js.code)}\n<< ${colors.cyan().bold(file)}` `\n>> ${colors.cyan.bold(file)}\n${addLineNumbers(js.code)}\n<< ${colors.cyan.bold(file)}`
); );
}); });
} }

@ -25,14 +25,17 @@ function getName(filename) {
return base[0].toUpperCase() + base.slice(1); return base[0].toUpperCase() + base.slice(1);
} }
describe("runtime", () => { const sveltePath = process.cwd();
const internal = path.join(sveltePath, 'internal.js');
describe.only("runtime", () => {
before(() => { before(() => {
svelte = loadSvelte(false); svelte = loadSvelte(false);
svelte$ = loadSvelte(true); svelte$ = loadSvelte(true);
require.extensions[".html"] = function(module, filename) { require.extensions[".html"] = function(module, filename) {
const options = Object.assign( const options = Object.assign(
{ filename, name: getName(filename), format: 'cjs' }, { filename, name: getName(filename), format: 'cjs', sveltePath },
compileOptions compileOptions
); );
@ -48,7 +51,7 @@ describe("runtime", () => {
const failed = new Set(); const failed = new Set();
function runTest(dir, internal, hydrate) { function runTest(dir, hydrate) {
if (dir[0] === ".") return; if (dir[0] === ".") return;
const { flush } = require(internal); const { flush } = require(internal);
@ -73,7 +76,7 @@ describe("runtime", () => {
global.document.title = ''; global.document.title = '';
compileOptions = config.compileOptions || {}; compileOptions = config.compileOptions || {};
compileOptions.shared = internal; compileOptions.sveltePath = sveltePath;
compileOptions.hydratable = hydrate; compileOptions.hydratable = hydrate;
compileOptions.immutable = config.immutable; compileOptions.immutable = config.immutable;
@ -211,10 +214,9 @@ describe("runtime", () => {
}); });
} }
const internal = path.resolve("internal.js");
fs.readdirSync("test/runtime/samples").forEach(dir => { fs.readdirSync("test/runtime/samples").forEach(dir => {
runTest(dir, internal, false); runTest(dir, false);
runTest(dir, internal, true); runTest(dir, true);
}); });
async function create_component(src = '<div></div>') { async function create_component(src = '<div></div>') {

@ -118,7 +118,7 @@ describe.only("ssr", () => {
try { try {
const Component = require(`../runtime/samples/${dir}/main.html`).default; const Component = require(`../runtime/samples/${dir}/main.html`).default;
const { html } = Component.render(config.data, { const { html } = Component.render(config.props, {
store: (config.store !== true) && config.store store: (config.store !== true) && config.store
}); });

Loading…
Cancel
Save