diff --git a/src/compiler/compile/Component.ts b/src/compiler/compile/Component.ts index ce54f5f92b..addc00b3eb 100644 --- a/src/compiler/compile/Component.ts +++ b/src/compiler/compile/Component.ts @@ -16,7 +16,7 @@ import Stylesheet from './css/Stylesheet'; import { test } from '../config'; import Fragment from './nodes/Fragment'; import internal_exports from './internal_exports'; -import { Node, Ast, CompileOptions, Var, Warning } from '../interfaces'; +import { Node, Ast, CompileOptions, Var, Warning, Identifier } from '../interfaces'; import error from '../utils/error'; import get_code_frame from '../utils/get_code_frame'; import flatten_reference from './utils/flatten_reference'; @@ -90,7 +90,7 @@ export default class Component { ast: Ast; source: string; code: MagicString; - name: string; + name: Identifier; compile_options: CompileOptions; fragment: Fragment; module_scope: Scope; @@ -99,7 +99,7 @@ export default class Component { component_options: ComponentOptions; namespace: string; - tag: string; + tag: Identifier; accessors: boolean; vars: Var[] = []; @@ -122,8 +122,8 @@ export default class Component { reactive_declaration_nodes: Set = new Set(); has_reactive_assignments = false; injected_reactive_declaration_vars: Set = new Set(); - helpers: Map = new Map(); - globals: Map = new Map(); + helpers: Map = new Map(); + globals: Map = new Map(); indirect_dependencies: Map> = new Map(); @@ -141,7 +141,7 @@ export default class Component { stylesheet: Stylesheet; - aliases: Map = new Map(); + aliases: Map = new Map(); used_names: Set = new Set(); globally_used_names: Set = new Set(); @@ -156,7 +156,7 @@ export default class Component { stats: Stats, warnings: Warning[] ) { - this.name = name; + this.name = { type: 'Identifier', name }; this.stats = stats; this.warnings = warnings; @@ -205,7 +205,10 @@ export default class Component { message: `No custom element 'tag' option was specified. To automatically register a custom element, specify a name with a hyphen in it, e.g. . To hide this warning, use `, }); } - this.tag = this.component_options.tag || compile_options.tag; + this.tag = { + type: 'Identifier', + name: this.component_options.tag || compile_options.tag + }; } else { this.tag = this.name; } @@ -335,7 +338,7 @@ export default class Component { const referenced_globals = Array.from( this.globals, - ([name, alias]) => name !== alias && { name, alias } + ([name, alias]) => name !== alias.name && { name, alias } ).filter(Boolean); if (referenced_globals.length) { this.helper('globals'); @@ -348,7 +351,7 @@ export default class Component { const module = create_module( printed.code, format, - name, + name.name, banner, compile_options.sveltePath, imported_helpers, @@ -437,7 +440,7 @@ export default class Component { }; } - get_unique_name(name: string) { + get_unique_name(name: string): Identifier { if (test) name = `${name}$`; let alias = name; for ( @@ -449,7 +452,7 @@ export default class Component { alias = `${name}_${i++}` ); this.used_names.add(alias); - return alias; + return { type: 'Identifier', name: alias }; } get_unique_name_maker() { @@ -463,7 +466,7 @@ export default class Component { internal_exports.forEach(add); this.var_lookup.forEach((_value, key) => add(key)); - return (name: string) => { + return (name: string): Identifier => { if (test) name = `${name}$`; let alias = name; for ( @@ -473,7 +476,11 @@ export default class Component { ); local_used_names.add(alias); this.globally_used_names.add(alias); - return alias; + + return { + type: 'Identifier', + name: alias + }; }; } diff --git a/src/compiler/compile/create_module.ts b/src/compiler/compile/create_module.ts index 79cb33549c..83ba38c510 100644 --- a/src/compiler/compile/create_module.ts +++ b/src/compiler/compile/create_module.ts @@ -1,6 +1,6 @@ import deindent from './utils/deindent'; import list from '../utils/list'; -import { ModuleFormat, Node } from '../interfaces'; +import { ModuleFormat, Node, Identifier } from '../interfaces'; import { stringify_props } from './utils/stringify_props'; const wrappers = { esm, cjs }; @@ -16,8 +16,8 @@ export default function create_module( name: string, banner: string, sveltePath = 'svelte', - helpers: Array<{ name: string; alias: string }>, - globals: Array<{ name: string; alias: string }>, + helpers: Array<{ name: string; alias: Identifier }>, + globals: Array<{ name: string; alias: Identifier }>, imports: Node[], module_exports: Export[], source: string @@ -45,14 +45,14 @@ function esm( banner: string, sveltePath: string, internal_path: string, - helpers: Array<{ name: string; alias: string }>, - globals: Array<{ name: string; alias: string }>, + helpers: Array<{ name: string; alias: Identifier }>, + globals: Array<{ name: string; alias: Identifier }>, imports: Node[], module_exports: Export[], source: string ) { const internal_imports = helpers.length > 0 && ( - `import ${stringify_props(helpers.map(h => h.name === h.alias ? h.name : `${h.name} as ${h.alias}`).sort())} from ${JSON.stringify(internal_path)};` + `import ${stringify_props(helpers.map(h => h.name === h.alias.name ? h.name : `${h.name} as ${h.alias}`).sort())} from ${JSON.stringify(internal_path)};` ); const internal_globals = globals.length > 0 && ( `const ${stringify_props(globals.map(g => `${g.name}: ${g.alias}`).sort())} = ${helpers.find(({ name }) => name === 'globals').alias};` @@ -90,12 +90,12 @@ function cjs( banner: string, sveltePath: string, internal_path: string, - helpers: Array<{ name: string; alias: string }>, - globals: Array<{ name: string; alias: string }>, + helpers: Array<{ name: string; alias: Identifier }>, + globals: Array<{ name: string; alias: Identifier }>, imports: Node[], module_exports: Export[] ) { - const declarations = helpers.map(h => `${h.alias === h.name ? h.name : `${h.name}: ${h.alias}`}`).sort(); + const declarations = helpers.map(h => `${h.alias.name === h.name ? h.name : `${h.name}: ${h.alias}`}`).sort(); const internal_imports = helpers.length > 0 && ( `const ${stringify_props(declarations)} = require(${JSON.stringify(internal_path)});\n` diff --git a/src/compiler/compile/nodes/EventHandler.ts b/src/compiler/compile/nodes/EventHandler.ts index 0c65e463ba..428ee678bf 100644 --- a/src/compiler/compile/nodes/EventHandler.ts +++ b/src/compiler/compile/nodes/EventHandler.ts @@ -4,13 +4,14 @@ import Component from '../Component'; import deindent from '../utils/deindent'; import Block from '../render_dom/Block'; import { sanitize } from '../../utils/names'; +import { Identifier } from '../../interfaces'; export default class EventHandler extends Node { type: 'EventHandler'; name: string; modifiers: Set; expression: Expression; - handler_name: string; + handler_name: Identifier; uses_context = false; can_make_passive = false; @@ -42,21 +43,21 @@ export default class EventHandler extends Node { } } } else { - const name = component.get_unique_name(`${sanitize(this.name)}_handler`); + const id = component.get_unique_name(`${sanitize(this.name)}_handler`); component.add_var({ - name, + name: id.name, internal: true, referenced: true }); component.partly_hoisted.push(deindent` - function ${name}(event) { + function ${id}(event) { @bubble($$self, event); } `); - this.handler_name = name; + this.handler_name = id; } } diff --git a/src/compiler/compile/nodes/shared/Expression.ts b/src/compiler/compile/nodes/shared/Expression.ts index 7974e0e241..ffb0f4ea3a 100644 --- a/src/compiler/compile/nodes/shared/Expression.ts +++ b/src/compiler/compile/nodes/shared/Expression.ts @@ -307,7 +307,7 @@ export default class Expression { if (map.has(node)) scope = scope.parent; if (node === function_expression) { - const name = component.get_unique_name( + const id = component.get_unique_name( sanitize(get_function_name(node, owner)) ); @@ -325,16 +325,16 @@ export default class Expression { const body = code.slice(node.body.start, node.body.end).trim(); const fn = node.type === 'FunctionExpression' - ? `${node.async ? 'async ' : ''}function${node.generator ? '*' : ''} ${name}(${args.join(', ')}) ${body}` - : `const ${name} = ${node.async ? 'async ' : ''}(${args.join(', ')}) => ${body};`; + ? `${node.async ? 'async ' : ''}function${node.generator ? '*' : ''} ${id}(${args.join(', ')}) ${body}` + : `const ${id} = ${node.async ? 'async ' : ''}(${args.join(', ')}) => ${body};`; if (dependencies.size === 0 && contextual_dependencies.size === 0) { // we can hoist this out of the component completely component.fully_hoisted.push(fn); - code.overwrite(node.start, node.end, name); + code.overwrite(node.start, node.end, id.name); component.add_var({ - name, + name: id.name, internal: true, hoistable: true, referenced: true @@ -344,10 +344,10 @@ export default class Expression { else if (contextual_dependencies.size === 0) { // function can be hoisted inside the component init component.partly_hoisted.push(fn); - code.overwrite(node.start, node.end, `ctx.${name}`); + code.overwrite(node.start, node.end, `ctx.${id}`); component.add_var({ - name, + name: id.name, internal: true, referenced: true }); @@ -356,17 +356,17 @@ export default class Expression { else { // we need a combo block/init recipe component.partly_hoisted.push(fn); - code.overwrite(node.start, node.end, name); + code.overwrite(node.start, node.end, id.name); component.add_var({ - name, + name: id.name, internal: true, referenced: true }); declarations.push(b` - function ${name}(${original_params ? '...args' : ''}) { - return ctx.${name}(ctx${original_params ? ', ...args' : ''}); + function ${id}(${original_params ? '...args' : ''}) { + return ctx.${id}(ctx${original_params ? ', ...args' : ''}); } `); } diff --git a/src/compiler/compile/render_dom/Block.ts b/src/compiler/compile/render_dom/Block.ts index f073060b45..a94743b21e 100644 --- a/src/compiler/compile/render_dom/Block.ts +++ b/src/compiler/compile/render_dom/Block.ts @@ -2,34 +2,46 @@ import Renderer from './Renderer'; import Wrapper from './wrappers/shared/Wrapper'; import { escape } from '../utils/stringify'; import { b, x } from 'code-red'; -import { Node } from '../../interfaces'; +import { Node, Identifier } from '../../interfaces'; export interface BlockOptions { parent?: Block; - name: string; + name: Identifier; type: string; renderer?: Renderer; comment?: string; - key?: string; - bindings?: Map; + key?: Identifier; + bindings?: Map; dependencies?: Set; } export default class Block { parent?: Block; renderer: Renderer; - name: string; + name: Identifier; type: string; comment?: string; wrappers: Wrapper[]; - key: string; - first: string; + key: Identifier; + first: Identifier; dependencies: Set; - bindings: Map; + bindings: Map; chunks: { init: Node[]; @@ -56,9 +68,9 @@ export default class Block { has_outro_method: boolean; outros: number; - aliases: Map; + aliases: Map; variables: Map; - get_unique_name: (name: string) => string; + get_unique_name: (name: string) => Identifier; has_update_method = false; autofocus: string; @@ -108,6 +120,7 @@ export default class Block { } assign_variable_names() { + // TODO reimplement for #3539 const seen = new Set(); const dupes = new Set(); @@ -139,7 +152,7 @@ export default class Block { counts.set(wrapper.var, i + 1); wrapper.var = this.get_unique_name(wrapper.var + i); } else { - wrapper.var = this.get_unique_name(wrapper.var); + wrapper.var = this.get_unique_name(wrapper.var.name); } } } @@ -154,8 +167,8 @@ export default class Block { add_element( name: string, - render_statement: string, - claim_statement: string, + render_statement: Node, + claim_statement: Node, parent_node: string, no_detach?: boolean ) { @@ -192,7 +205,7 @@ export default class Block { add_variable(name: string, init?: string) { if (name[0] === '#') { - name = this.alias(name.slice(1)); + name = this.alias(name.slice(1)).name; } if (this.variables.has(name) && this.variables.get(name) !== init) { @@ -281,7 +294,7 @@ export default class Block { properties.mount = noop; } else { properties.mount = x`function mount(#target, anchor) { - ${this.chunks.mount} + //${this.chunks.mount} }`; } @@ -338,7 +351,7 @@ export default class Block { }`; } - const return_value = x`{ + const return_value: any = x`{ key: ${properties.key}, first: ${properties.first}, c: ${properties.create}, @@ -354,13 +367,17 @@ export default class Block { d: ${properties.destroy} }`; + return_value.properties = return_value.properties.filter(prop => prop.value); + /* eslint-disable @typescript-eslint/indent,indent */ return b` - ${Array.from(this.variables.entries()).map(([init, id]) => { + ${Array.from(this.variables.entries()).map(([id, init]) => { const id_node = { type: 'Identifier', name: id }; const init_node = { type: 'Identifier', name: init }; - return b`let ${id_node} = ${init_node}`; + return init + ? b`let ${id_node} = ${init_node}` + : b`let ${id_node}`; })} ${this.chunks.init} diff --git a/src/compiler/compile/render_dom/Renderer.ts b/src/compiler/compile/render_dom/Renderer.ts index f0cf905df6..90a76a68b7 100644 --- a/src/compiler/compile/render_dom/Renderer.ts +++ b/src/compiler/compile/render_dom/Renderer.ts @@ -1,5 +1,5 @@ import Block from './Block'; -import { CompileOptions, Node } from '../../interfaces'; +import { CompileOptions, Node, Identifier } from '../../interfaces'; import Component from '../Component'; import FragmentWrapper from './wrappers/Fragment'; @@ -15,7 +15,7 @@ export default class Renderer { block: Block; fragment: FragmentWrapper; - file_var: string; + file_var: Identifier; locate: (c: number) => { line: number; column: number }; constructor(component: Component, options: CompileOptions) { diff --git a/src/compiler/compile/render_dom/wrappers/AwaitBlock.ts b/src/compiler/compile/render_dom/wrappers/AwaitBlock.ts index a5baca066e..6845756414 100644 --- a/src/compiler/compile/render_dom/wrappers/AwaitBlock.ts +++ b/src/compiler/compile/render_dom/wrappers/AwaitBlock.ts @@ -8,6 +8,7 @@ import FragmentWrapper from './Fragment'; import PendingBlock from '../../nodes/PendingBlock'; import ThenBlock from '../../nodes/ThenBlock'; import CatchBlock from '../../nodes/CatchBlock'; +import { Identifier } from '../../../interfaces'; class AwaitBlockBranch extends Wrapper { node: PendingBlock | ThenBlock | CatchBlock; @@ -54,7 +55,7 @@ export default class AwaitBlockWrapper extends Wrapper { then: AwaitBlockBranch; catch: AwaitBlockBranch; - var = 'await_block'; + var: Identifier = { type: 'Identifier', name: 'await_block' }; constructor( renderer: Renderer, @@ -131,7 +132,7 @@ export default class AwaitBlockWrapper extends Wrapper { const info = block.get_unique_name(`info`); const promise = block.get_unique_name(`promise`); - block.add_variable(promise); + block.add_variable(promise.name); block.maintain_context = true; diff --git a/src/compiler/compile/render_dom/wrappers/EachBlock.ts b/src/compiler/compile/render_dom/wrappers/EachBlock.ts index 8d97b65ab8..a98d4a43d1 100644 --- a/src/compiler/compile/render_dom/wrappers/EachBlock.ts +++ b/src/compiler/compile/render_dom/wrappers/EachBlock.ts @@ -4,9 +4,10 @@ import Wrapper from './shared/Wrapper'; import create_debugging_comment from './shared/create_debugging_comment'; import EachBlock from '../../nodes/EachBlock'; import FragmentWrapper from './Fragment'; -import { b } from 'code-red'; +import { b, x } from 'code-red'; import ElseBlock from '../../nodes/ElseBlock'; import { attach_head } from '../../utils/tail'; +import { Identifier } from '../../../interfaces'; export class ElseBlockWrapper extends Wrapper { node: ElseBlock; @@ -51,10 +52,10 @@ export default class EachBlockWrapper extends Wrapper { fragment: FragmentWrapper; else?: ElseBlockWrapper; vars: { - create_each_block: string; - each_block_value: string; - get_each_context: string; - iterations: string; + create_each_block: Identifier; + each_block_value: Identifier; + get_each_context: Identifier; + iterations: Identifier; fixed_length: number; data_length: string; view_length: string; @@ -62,9 +63,9 @@ export default class EachBlockWrapper extends Wrapper { } context_props: string[]; - index_name: string; + index_name: Identifier; - var = 'each'; + var: Identifier = { type: 'Identifier', name: 'each' }; constructor( renderer: Renderer, @@ -93,7 +94,9 @@ export default class EachBlockWrapper extends Wrapper { // TODO this seems messy this.block.has_animation = this.node.has_animation; - this.index_name = this.node.index || renderer.component.get_unique_name(`${this.node.context}_index`); + this.index_name = this.node.index + ? { type: 'Identifier', name: this.node.index } + : renderer.component.get_unique_name(`${this.node.context}_index`); const fixed_length = node.expression.node.type === 'ArrayExpression' && @@ -198,12 +201,12 @@ export default class EachBlockWrapper extends Wrapper { } `); - const initial_anchor_node = parent_node ? 'null' : 'anchor'; - const initial_mount_node = parent_node || '#target'; + const initial_anchor_node: Identifier = { type: 'Identifier', name: parent_node ? 'null' : 'anchor' }; + const initial_mount_node: Identifier = { type: 'Identifier', name: parent_node || '#target' }; const update_anchor_node = needs_anchor ? block.get_unique_name(`${this.var}_anchor`) - : (this.next && this.next.var) || 'null'; - const update_mount_node = this.get_update_mount_node(update_anchor_node); + : (this.next && this.next.var) || { type: 'Identifier', name: 'null' }; + const update_mount_node: Identifier = this.get_update_mount_node((update_anchor_node as Identifier)); const args = { block, @@ -232,9 +235,9 @@ export default class EachBlockWrapper extends Wrapper { if (needs_anchor) { block.add_element( - update_anchor_node, - `@empty()`, - parent_nodes && `@empty()`, + (update_anchor_node as Identifier).name, + x`@empty()`, + parent_nodes && x`@empty()`, parent_node ); } @@ -312,10 +315,10 @@ export default class EachBlockWrapper extends Wrapper { parent_node: string; parent_nodes: string; snippet: string; - initial_anchor_node: string; - initial_mount_node: string; - update_anchor_node: string; - update_mount_node: string; + initial_anchor_node: Identifier; + initial_mount_node: Identifier; + update_anchor_node: Identifier; + update_mount_node: Identifier; }) { const { create_each_block, @@ -327,17 +330,17 @@ export default class EachBlockWrapper extends Wrapper { const get_key = block.get_unique_name('get_key'); const lookup = block.get_unique_name(`${this.var}_lookup`); - block.add_variable(iterations, '[]'); - block.add_variable(lookup, `new @_Map()`); + block.add_variable(iterations.name, '[]'); + block.add_variable(lookup.name, `new @_Map()`); if (this.fragment.nodes[0].is_dom_node()) { this.block.first = this.fragment.nodes[0].var; } else { this.block.first = this.block.get_unique_name('first'); this.block.add_element( - this.block.first, - `@empty()`, - parent_nodes && `@empty()`, + this.block.first.name, + x`@empty()`, + parent_nodes && x`@empty()`, null ); } @@ -421,10 +424,10 @@ export default class EachBlockWrapper extends Wrapper { block: Block; parent_nodes: string; snippet: string; - initial_anchor_node: string; - initial_mount_node: string; - update_anchor_node: string; - update_mount_node: string; + initial_anchor_node: Identifier; + initial_mount_node: Identifier; + update_anchor_node: Identifier; + update_mount_node: Identifier; }) { const { create_each_block, diff --git a/src/compiler/compile/render_dom/wrappers/Element/Attribute.ts b/src/compiler/compile/render_dom/wrappers/Element/Attribute.ts index 457e84f152..ac3532a308 100644 --- a/src/compiler/compile/render_dom/wrappers/Element/Attribute.ts +++ b/src/compiler/compile/render_dom/wrappers/Element/Attribute.ts @@ -99,7 +99,7 @@ export default class AttributeWrapper { `${element.var}_${name.replace(/[^a-zA-Z_$]/g, '_')}_value` ); - if (should_cache) block.add_variable(last); + if (should_cache) block.add_variable(last.name); let updater; const init = should_cache ? `${last} = ${value}` : value; diff --git a/src/compiler/compile/render_dom/wrappers/Element/Binding.ts b/src/compiler/compile/render_dom/wrappers/Element/Binding.ts index fe2f7ceb1c..d32e59e9e8 100644 --- a/src/compiler/compile/render_dom/wrappers/Element/Binding.ts +++ b/src/compiler/compile/render_dom/wrappers/Element/Binding.ts @@ -152,7 +152,7 @@ export default class BindingWrapper { { // this is necessary to prevent audio restarting by itself const last = block.get_unique_name(`${parent.var}_is_paused`); - block.add_variable(last, 'true'); + block.add_variable(last.name, 'true'); update_conditions.push(`${last} !== (${last} = ${this.snippet})`); update_dom = b`${parent.var}[${last} ? "pause" : "play"]();`; @@ -272,7 +272,7 @@ function get_event_handler( mutation: store ? mutate_store(store, value, tail) : `${snippet}${tail} = ${value};`, - contextual_dependencies: new Set([object, property]) + contextual_dependencies: new Set([object.name, property.name]) }; } diff --git a/src/compiler/compile/render_dom/wrappers/Element/index.ts b/src/compiler/compile/render_dom/wrappers/Element/index.ts index d5e63fd1da..c481f03113 100644 --- a/src/compiler/compile/render_dom/wrappers/Element/index.ts +++ b/src/compiler/compile/render_dom/wrappers/Element/index.ts @@ -4,7 +4,7 @@ import Wrapper from '../shared/Wrapper'; import Block from '../../Block'; import { is_void, quote_prop_if_necessary, quote_name_if_necessary, sanitize } from '../../../../utils/names'; import FragmentWrapper from '../Fragment'; -import { stringify, escape_html, escape } from '../../../utils/stringify'; +import { escape_html, escape, string_literal } from '../../../utils/stringify'; import TextWrapper from '../Text'; import fix_attribute_casing from './fix_attribute_casing'; import { b, x } from 'code-red'; @@ -114,7 +114,7 @@ export default class ElementWrapper extends Wrapper { slot_block: Block; select_binding_dependencies?: Set; - var: string; + var: any; constructor( renderer: Renderer, @@ -125,7 +125,10 @@ export default class ElementWrapper extends Wrapper { next_sibling: Wrapper ) { super(renderer, block, parent, node); - this.var = node.name.replace(/[^a-zA-Z0-9_$]/g, '_'); + this.var = { + type: 'Identifier', + name: node.name.replace(/[^a-zA-Z0-9_$]/g, '_') + }; this.class_dependencies = []; @@ -288,7 +291,7 @@ export default class ElementWrapper extends Wrapper { if (this.fragment.nodes.length === 1 && this.fragment.nodes[0].node.type === 'Text') { block.chunks.create.push( // @ts-ignore todo: should it be this.fragment.nodes[0].node.data instead? - b`${node}.textContent = ${stringify(this.fragment.nodes[0].data)};` + b`${node}.textContent = ${string_literal(this.fragment.nodes[0].data)};` ); } else { const inner_html = escape( @@ -306,7 +309,7 @@ export default class ElementWrapper extends Wrapper { child.render( block, this.node.name === 'template' ? `${node}.content` : node, - nodes + nodes.name ); }); } @@ -377,19 +380,19 @@ export default class ElementWrapper extends Wrapper { const { name, namespace } = this.node; if (namespace === 'http://www.w3.org/2000/svg') { - return `@svg_element("${name}")`; + return x`@svg_element("${name}")`; } if (namespace) { - return `@_document.createElementNS("${namespace}", "${name}")`; + return x`@_document.createElementNS("${namespace}", "${name}")`; } const is = this.attributes.find(attr => attr.node.name === 'is'); if (is) { - return `@element_is("${name}", ${is.render_chunks().join(' + ')});`; + return x`@element_is("${name}", ${is.render_chunks().join(' + ')});`; } - return `@element("${name}")`; + return x`@element("${name}")`; } get_claim_statement(nodes: string) { @@ -402,9 +405,9 @@ export default class ElementWrapper extends Wrapper { ? this.node.name : this.node.name.toUpperCase(); - return `@claim_element(${nodes}, "${name}", ${attributes - ? `{ ${attributes} }` - : `{}`}, ${this.node.namespace === namespaces.svg ? true : false})`; + return x`@claim_element(${nodes}, "${name}", ${attributes + ? x`{ ${attributes} }` + : x`{}`}, ${this.node.namespace === namespaces.svg ? true : false})`; } add_bindings(block: Block) { @@ -418,7 +421,7 @@ export default class ElementWrapper extends Wrapper { block.get_unique_name(`${this.var}_updating`) : null; - if (lock) block.add_variable(lock, 'false'); + if (lock) block.add_variable(lock.name, 'false'); const groups = events .map(event => ({ @@ -433,7 +436,7 @@ export default class ElementWrapper extends Wrapper { const handler = renderer.component.get_unique_name(`${this.var}_${group.events.join('_')}_handler`); renderer.component.add_var({ - name: handler, + name: handler.name, internal: true, referenced: true }); @@ -450,7 +453,7 @@ export default class ElementWrapper extends Wrapper { add_to_set(contextual_dependencies, binding.node.expression.contextual_dependencies); add_to_set(contextual_dependencies, binding.handler.contextual_dependencies); - binding.render(block, lock); + binding.render(block, lock.name); }); // media bindings — awkward special case. The native timeupdate events @@ -505,7 +508,7 @@ export default class ElementWrapper extends Wrapper { if (name === 'resize') { // special case const resize_listener = block.get_unique_name(`${this.var}_resize_listener`); - block.add_variable(resize_listener); + block.add_variable(resize_listener.name); block.chunks.mount.push( b`${resize_listener} = @add_resize_listener(${this.var}, ${callee}.bind(${this.var}));` @@ -654,7 +657,7 @@ export default class ElementWrapper extends Wrapper { ? intro.expression.render(block) : '{}'; - block.add_variable(name); + block.add_variable(name.name); const fn = component.qualify(intro.name); @@ -695,7 +698,7 @@ export default class ElementWrapper extends Wrapper { const outro_name = outro && block.get_unique_name(`${this.var}_outro`); if (intro) { - block.add_variable(intro_name); + block.add_variable(intro_name.name); const snippet = intro.expression ? intro.expression.render(block) : '{}'; @@ -737,7 +740,7 @@ export default class ElementWrapper extends Wrapper { } if (outro) { - block.add_variable(outro_name); + block.add_variable(outro_name.name); const snippet = outro.expression ? outro.expression.render(block) : '{}'; @@ -780,8 +783,8 @@ export default class ElementWrapper extends Wrapper { const rect = block.get_unique_name('rect'); const stop_animation = block.get_unique_name('stop_animation'); - block.add_variable(rect); - block.add_variable(stop_animation, '@noop'); + block.add_variable(rect.name); + block.add_variable(stop_animation.name, '@noop'); block.chunks.measure.push(b` ${rect} = ${this.var}.getBoundingClientRect(); diff --git a/src/compiler/compile/render_dom/wrappers/IfBlock.ts b/src/compiler/compile/render_dom/wrappers/IfBlock.ts index 6752315f0b..311d8f7773 100644 --- a/src/compiler/compile/render_dom/wrappers/IfBlock.ts +++ b/src/compiler/compile/render_dom/wrappers/IfBlock.ts @@ -6,8 +6,10 @@ import IfBlock from '../../nodes/IfBlock'; import create_debugging_comment from './shared/create_debugging_comment'; import ElseBlock from '../../nodes/ElseBlock'; import FragmentWrapper from './Fragment'; -import { b } from 'code-red'; +import { b, x } from 'code-red'; import { walk } from 'estree-walker'; +import { Identifier } from '../../../interfaces'; +import Node from '../../nodes/shared/Node'; function is_else_if(node: ElseBlock) { return ( @@ -19,8 +21,8 @@ class IfBlockBranch extends Wrapper { block: Block; fragment: FragmentWrapper; dependencies?: string[]; - condition?: string; - snippet?: string; + condition?: any; + snippet?: Node; is_dynamic: boolean; var = null; @@ -54,9 +56,9 @@ class IfBlockBranch extends Wrapper { if (should_cache) { this.condition = block.get_unique_name(`show_if`); - this.snippet = expression.render(block); + this.snippet = expression.node; } else { - this.condition = expression.render(block); + this.condition = expression.node; } } @@ -79,7 +81,7 @@ export default class IfBlockWrapper extends Wrapper { branches: IfBlockBranch[]; needs_update = false; - var = 'if_block'; + var: Identifier = { type: 'Identifier', name: 'if_block' }; constructor( renderer: Renderer, @@ -224,9 +226,9 @@ export default class IfBlockWrapper extends Wrapper { if (needs_anchor) { block.add_element( - anchor, - `@empty()`, - parent_nodes && `@empty()`, + (anchor as Identifier).name, + x`@empty()`, + parent_nodes && x`@empty()`, parent_node ); } @@ -340,7 +342,7 @@ export default class IfBlockWrapper extends Wrapper { ? '' : `if (~${current_block_type_index}) `; - block.add_variable(current_block_type_index); + block.add_variable(current_block_type_index.name); block.add_variable(name); /* eslint-disable @typescript-eslint/indent,indent */ @@ -470,7 +472,7 @@ export default class IfBlockWrapper extends Wrapper { ) { const branch = this.branches[0]; - if (branch.snippet) block.add_variable(branch.condition, branch.snippet); + if (branch.snippet) block.add_variable(branch.condition, '' + branch.snippet); block.chunks.init.push(b` var ${name} = (${branch.condition}) && ${branch.block.name}(ctx); diff --git a/src/compiler/compile/render_dom/wrappers/InlineComponent/index.ts b/src/compiler/compile/render_dom/wrappers/InlineComponent/index.ts index f65aece1f4..f58f53b0fb 100644 --- a/src/compiler/compile/render_dom/wrappers/InlineComponent/index.ts +++ b/src/compiler/compile/render_dom/wrappers/InlineComponent/index.ts @@ -6,7 +6,7 @@ import FragmentWrapper from '../Fragment'; import { quote_name_if_necessary, quote_prop_if_necessary, sanitize } from '../../../../utils/names'; import { stringify_props } from '../../../utils/stringify_props'; import add_to_set from '../../../utils/add_to_set'; -import { b } from 'code-red'; +import { b, x } from 'code-red'; import Attribute from '../../../nodes/Attribute'; import get_object from '../../../utils/get_object'; import create_debugging_comment from '../shared/create_debugging_comment'; @@ -15,9 +15,10 @@ import EachBlock from '../../../nodes/EachBlock'; import TemplateScope from '../../../nodes/shared/TemplateScope'; import is_dynamic from '../shared/is_dynamic'; import bind_this from '../shared/bind_this'; +import { Identifier } from '../../../../interfaces'; export default class InlineComponentWrapper extends Wrapper { - var: string; + var: Identifier; slots: Map = new Map(); node: InlineComponent; fragment: FragmentWrapper; @@ -61,11 +62,14 @@ export default class InlineComponentWrapper extends Wrapper { } }); - this.var = ( - this.node.name === 'svelte:self' ? renderer.component.name : - this.node.name === 'svelte:component' ? 'switch_instance' : - sanitize(this.node.name) - ).toLowerCase(); + this.var = { + type: 'Identifier', + name: ( + this.node.name === 'svelte:self' ? renderer.component.name.name : + this.node.name === 'svelte:component' ? 'switch_instance' : + sanitize(this.node.name) + ).toLowerCase() + }; if (this.node.children.length) { const default_slot = block.child({ @@ -253,19 +257,19 @@ export default class InlineComponentWrapper extends Wrapper { component.has_reactive_assignments = true; if (binding.name === 'this') { - return bind_this(component, block, binding, this.var); + return bind_this(component, block, binding, this.var.name); } - const name = component.get_unique_name(`${this.var}_${binding.name}_binding`); + const id = component.get_unique_name(`${this.var}_${binding.name}_binding`); component.add_var({ - name, + name: id.name, internal: true, referenced: true }); const updating = block.get_unique_name(`updating_${binding.name}`); - block.add_variable(updating); + block.add_variable(updating.name); const snippet = binding.expression.render(block); @@ -292,13 +296,13 @@ export default class InlineComponentWrapper extends Wrapper { const { name } = binding.expression.node; const { object, property, snippet } = block.bindings.get(name); lhs = snippet; - contextual_dependencies.push(object, property); + contextual_dependencies.push(object.name, property.name); } const value = block.get_unique_name('value'); - const args = [value]; + const args: any[] = [value]; if (contextual_dependencies.length > 0) { - args.push(`{ ${contextual_dependencies.join(', ')} }`); + args.push(x`{ ${contextual_dependencies.join(', ')} }`); block.chunks.init.push(b` function ${name}(${value}) { @@ -311,7 +315,7 @@ export default class InlineComponentWrapper extends Wrapper { block.maintain_context = true; // TODO put this somewhere more logical } else { block.chunks.init.push(b` - function ${name}(${value}) { + function ${id}(${value}) { ctx.${name}.call(null, ${value}); ${updating} = true; @add_flush_callback(() => ${updating} = false); @@ -320,7 +324,7 @@ export default class InlineComponentWrapper extends Wrapper { } const body = b` - function ${name}(${args.join(', ')}) { + function ${id}(${args.join(', ')}) { ${lhs} = ${value}; ${component.invalidate(dependencies[0])}; } @@ -328,7 +332,7 @@ export default class InlineComponentWrapper extends Wrapper { component.partly_hoisted.push(body); - return `@binding_callbacks.push(() => @bind(${this.var}, '${binding.name}', ${name}));`; + return `@binding_callbacks.push(() => @bind(${this.var}, '${binding.name}', ${id}));`; }); const munged_handlers = this.node.handlers.map(handler => { diff --git a/src/compiler/compile/render_dom/wrappers/MustacheTag.ts b/src/compiler/compile/render_dom/wrappers/MustacheTag.ts index 71faf91cb2..434fcb85dc 100644 --- a/src/compiler/compile/render_dom/wrappers/MustacheTag.ts +++ b/src/compiler/compile/render_dom/wrappers/MustacheTag.ts @@ -4,9 +4,11 @@ import Tag from './shared/Tag'; import Wrapper from './shared/Wrapper'; import MustacheTag from '../../nodes/MustacheTag'; import RawMustacheTag from '../../nodes/RawMustacheTag'; +import { Identifier } from '../../../interfaces'; +import { x } from 'code-red'; export default class MustacheTagWrapper extends Tag { - var = 't'; + var: Identifier = { type: 'Identifier', name: 't' }; constructor(renderer: Renderer, block: Block, parent: Wrapper, node: MustacheTag | RawMustacheTag) { super(renderer, block, parent, node); @@ -20,9 +22,9 @@ export default class MustacheTagWrapper extends Tag { ); block.add_element( - this.var, - `@text(${init})`, - parent_nodes && `@claim_text(${parent_nodes}, ${init})`, + this.var.name, + x`@text(${init})`, + parent_nodes && x`@claim_text(${parent_nodes}, ${init})`, parent_node ); } diff --git a/src/compiler/compile/render_dom/wrappers/RawMustacheTag.ts b/src/compiler/compile/render_dom/wrappers/RawMustacheTag.ts index e29946aa61..d4a7251832 100644 --- a/src/compiler/compile/render_dom/wrappers/RawMustacheTag.ts +++ b/src/compiler/compile/render_dom/wrappers/RawMustacheTag.ts @@ -1,13 +1,14 @@ -import { b } from 'code-red'; +import { b, x } from 'code-red'; import Renderer from '../Renderer'; import Block from '../Block'; import Tag from './shared/Tag'; import Wrapper from './shared/Wrapper'; import MustacheTag from '../../nodes/MustacheTag'; import RawMustacheTag from '../../nodes/RawMustacheTag'; +import { Identifier } from '../../../interfaces'; export default class RawMustacheTagWrapper extends Tag { - var = 'raw'; + var: Identifier = { type: 'Identifier', name: 'raw' }; constructor( renderer: Renderer, @@ -41,7 +42,7 @@ export default class RawMustacheTagWrapper extends Tag { const html_tag = block.get_unique_name('html_tag'); const html_anchor = needs_anchor && block.get_unique_name('html_anchor'); - block.add_variable(html_tag); + block.add_variable(html_tag.name); const { init } = this.rename_this_method( block, @@ -54,7 +55,7 @@ export default class RawMustacheTagWrapper extends Tag { block.chunks.mount.push(b`${html_tag}.m(${parent_node || '#target'}${parent_node ? '' : ', anchor'});`); if (needs_anchor) { - block.add_element(html_anchor, '@empty()', '@empty()', parent_node); + block.add_element(html_anchor.name, x`@empty()`, x`@empty()`, parent_node); } if (!parent_node || in_head) { diff --git a/src/compiler/compile/render_dom/wrappers/Slot.ts b/src/compiler/compile/render_dom/wrappers/Slot.ts index b98e37e7d7..6bd1b24e07 100644 --- a/src/compiler/compile/render_dom/wrappers/Slot.ts +++ b/src/compiler/compile/render_dom/wrappers/Slot.ts @@ -10,12 +10,13 @@ import get_slot_data from '../../utils/get_slot_data'; import { stringify_props } from '../../utils/stringify_props'; import Expression from '../../nodes/shared/Expression'; import is_dynamic from './shared/is_dynamic'; +import { Identifier } from '../../../interfaces'; export default class SlotWrapper extends Wrapper { node: Slot; fragment: FragmentWrapper; - var = 'slot'; + var: Identifier = { type: 'Identifier', name: 'slot' }; dependencies: Set = new Set(['$$scope']); constructor( diff --git a/src/compiler/compile/render_dom/wrappers/Text.ts b/src/compiler/compile/render_dom/wrappers/Text.ts index 704d5aad77..747fe87fd9 100644 --- a/src/compiler/compile/render_dom/wrappers/Text.ts +++ b/src/compiler/compile/render_dom/wrappers/Text.ts @@ -3,6 +3,8 @@ import Block from '../Block'; import Text from '../../nodes/Text'; import Wrapper from './shared/Wrapper'; import { stringify } from '../../utils/stringify'; +import { Identifier } from '../../../interfaces'; +import { x } from 'code-red'; // Whitespace inside one of these elements will not result in // a whitespace node being created in any circumstances. (This @@ -33,7 +35,7 @@ export default class TextWrapper extends Wrapper { node: Text; data: string; skip: boolean; - var: string; + var: Identifier; constructor( renderer: Renderer, @@ -46,7 +48,7 @@ export default class TextWrapper extends Wrapper { this.skip = should_skip(this.node); this.data = data; - this.var = this.skip ? null : 't'; + this.var = (this.skip ? null : x`t`) as unknown as Identifier; } use_space() { @@ -69,9 +71,9 @@ export default class TextWrapper extends Wrapper { const use_space = this.use_space(); block.add_element( - this.var, - use_space ? `@space()` : `@text(${stringify(this.data)})`, - parent_nodes && (use_space ? `@claim_space(${parent_nodes})` : `@claim_text(${parent_nodes}, ${stringify(this.data)})`), + this.var.name, + use_space ? x`@space()` : x`@text(${stringify(this.data)})`, + parent_nodes && (use_space ? x`@claim_space(${parent_nodes})` : x`@claim_text(${parent_nodes}, ${stringify(this.data)})`), parent_node ); } diff --git a/src/compiler/compile/render_dom/wrappers/Title.ts b/src/compiler/compile/render_dom/wrappers/Title.ts index 0ccd85b872..7d9c2d40a1 100644 --- a/src/compiler/compile/render_dom/wrappers/Title.ts +++ b/src/compiler/compile/render_dom/wrappers/Title.ts @@ -64,7 +64,7 @@ export default class TitleWrapper extends Wrapper { `title_value` ); - if (this.node.should_cache) block.add_variable(last); + if (this.node.should_cache) block.add_variable(last.name); const init = this.node.should_cache ? `${last} = ${value}` : value; diff --git a/src/compiler/compile/render_dom/wrappers/Window.ts b/src/compiler/compile/render_dom/wrappers/Window.ts index 84706f7d05..a2a84254ef 100644 --- a/src/compiler/compile/render_dom/wrappers/Window.ts +++ b/src/compiler/compile/render_dom/wrappers/Window.ts @@ -73,14 +73,14 @@ export default class WindowWrapper extends Wrapper { const scrolling_timeout = block.get_unique_name(`scrolling_timeout`); Object.keys(events).forEach(event => { - const handler_name = block.get_unique_name(`onwindow${event}`); + const id = block.get_unique_name(`onwindow${event}`); const props = events[event]; if (event === 'scroll') { // TODO other bidirectional bindings... - block.add_variable(scrolling, 'false'); - block.add_variable(clear_scrolling, `() => { ${scrolling} = false }`); - block.add_variable(scrolling_timeout); + block.add_variable(scrolling.name, 'false'); + block.add_variable(clear_scrolling.name, `() => { ${scrolling} = false }`); + block.add_variable(scrolling_timeout.name); const condition = [ bindings.scrollX && `"${bindings.scrollX}" in this._state`, @@ -103,7 +103,7 @@ export default class WindowWrapper extends Wrapper { ${scrolling} = true; @_clearTimeout(${scrolling_timeout}); ${scrolling_timeout} = @_setTimeout(${clear_scrolling}, 100); - ctx.${handler_name}(); + ctx.${id}(); }) `); } else { @@ -114,24 +114,24 @@ export default class WindowWrapper extends Wrapper { }); block.event_listeners.push(b` - @listen(@_window, "${event}", ctx.${handler_name}) + @listen(@_window, "${event}", ctx.${id}) `); } component.add_var({ - name: handler_name, + name: id.name, internal: true, referenced: true }); component.partly_hoisted.push(b` - function ${handler_name}() { + function ${id}() { ${props.map(prop => `${prop.name} = @_window.${prop.value}; $$invalidate('${prop.name}', ${prop.name});`)} } `); block.chunks.init.push(b` - @add_render_callback(ctx.${handler_name}); + @add_render_callback(ctx.${id}); `); component.has_reactive_assignments = true; @@ -159,28 +159,28 @@ export default class WindowWrapper extends Wrapper { // another special case. (I'm starting to think these are all special cases.) if (bindings.online) { - const handler_name = block.get_unique_name(`onlinestatuschanged`); + const id = block.get_unique_name(`onlinestatuschanged`); const name = bindings.online; component.add_var({ - name: handler_name, + name: id.name, internal: true, referenced: true }); component.partly_hoisted.push(b` - function ${handler_name}() { + function ${id}() { ${name} = @_navigator.onLine; $$invalidate('${name}', ${name}); } `); block.chunks.init.push(b` - @add_render_callback(ctx.${handler_name}); + @add_render_callback(ctx.${id}); `); block.event_listeners.push( - `@listen(@_window, "online", ctx.${handler_name})`, - `@listen(@_window, "offline", ctx.${handler_name})` + `@listen(@_window, "online", ctx.${id})`, + `@listen(@_window, "offline", ctx.${id})` ); component.has_reactive_assignments = true; diff --git a/src/compiler/compile/render_dom/wrappers/shared/Tag.ts b/src/compiler/compile/render_dom/wrappers/shared/Tag.ts index d57f0184f4..e05e1cd6c8 100644 --- a/src/compiler/compile/render_dom/wrappers/shared/Tag.ts +++ b/src/compiler/compile/render_dom/wrappers/shared/Tag.ts @@ -20,12 +20,12 @@ export default class Tag extends Wrapper { update: ((value: string) => string) ) { const dependencies = this.node.expression.dynamic_dependencies(); - const snippet = this.node.expression.render(block); + const snippet = this.node.expression.node; const value = this.node.should_cache && block.get_unique_name(`${this.var}_value`); const content = this.node.should_cache ? value : snippet; - if (this.node.should_cache) block.add_variable(value, `${snippet} + ""`); + if (this.node.should_cache) block.add_variable(value.name, `${snippet} + ""`); if (dependencies.length > 0) { const changed_check = ( diff --git a/src/compiler/compile/render_dom/wrappers/shared/Wrapper.ts b/src/compiler/compile/render_dom/wrappers/shared/Wrapper.ts index 60c4a4187b..57e997721c 100644 --- a/src/compiler/compile/render_dom/wrappers/shared/Wrapper.ts +++ b/src/compiler/compile/render_dom/wrappers/shared/Wrapper.ts @@ -1,6 +1,8 @@ import Renderer from '../../Renderer'; import Block from '../../Block'; import { INode } from '../../../nodes/interfaces'; +import { Identifier } from '../../../../interfaces'; +import { x } from 'code-red'; export default class Wrapper { renderer: Renderer; @@ -10,7 +12,7 @@ export default class Wrapper { prev: Wrapper | null; next: Wrapper | null; - var: string; + var: Identifier; can_use_innerhtml: boolean; constructor( @@ -48,13 +50,13 @@ export default class Wrapper { const needs_anchor = this.next ? !this.next.is_dom_node() : !parent_node || !this.parent.is_dom_node(); const anchor = needs_anchor ? block.get_unique_name(`${this.var}_anchor`) - : (this.next && this.next.var) || 'null'; + : (this.next && this.next.var) || { type: 'Identifier', name: 'null' }; if (needs_anchor) { block.add_element( - anchor, - `@empty()`, - parent_nodes && `@empty()`, + anchor.name, + x`@empty()`, + parent_nodes && x`@empty()`, parent_node ); } @@ -62,10 +64,10 @@ export default class Wrapper { return anchor; } - get_update_mount_node(anchor: string) { - return (this.parent && this.parent.is_dom_node()) + get_update_mount_node(anchor: Identifier): Identifier { + return ((this.parent && this.parent.is_dom_node()) ? this.parent.var - : `${anchor}.parentNode`; + : x`${anchor}.parentNode`) as Identifier; } is_dom_node() { diff --git a/src/compiler/compile/render_dom/wrappers/shared/add_actions.ts b/src/compiler/compile/render_dom/wrappers/shared/add_actions.ts index fdc74e2f3b..bbcbadf87f 100644 --- a/src/compiler/compile/render_dom/wrappers/shared/add_actions.ts +++ b/src/compiler/compile/render_dom/wrappers/shared/add_actions.ts @@ -19,30 +19,30 @@ export default function add_actions( dependencies = expression.dynamic_dependencies(); } - const name = block.get_unique_name( + const id = block.get_unique_name( `${action.name.replace(/[^a-zA-Z0-9_$]/g, '_')}_action` ); - block.add_variable(name); + block.add_variable(id.name); const fn = component.qualify(action.name); block.chunks.mount.push( - b`${name} = ${fn}.call(null, ${target}${snippet ? `, ${snippet}` : ''}) || {};` + b`${id} = ${fn}.call(null, ${target}${snippet ? `, ${snippet}` : ''}) || {};` ); if (dependencies && dependencies.length > 0) { - let conditional = `typeof ${name}.update === 'function' && `; + let conditional = `typeof ${id}.update === 'function' && `; const deps = dependencies.map(dependency => `changed.${dependency}`).join(' || '); conditional += dependencies.length > 1 ? `(${deps})` : deps; block.chunks.update.push( - b`if (${conditional}) ${name}.update.call(null, ${snippet});` + b`if (${conditional}) ${id}.update.call(null, ${snippet});` ); } block.chunks.destroy.push( - b`if (${name} && typeof ${name}.destroy === 'function') ${name}.destroy();` + b`if (${id} && typeof ${id}.destroy === 'function') ${id}.destroy();` ); }); } diff --git a/src/compiler/compile/render_dom/wrappers/shared/bind_this.ts b/src/compiler/compile/render_dom/wrappers/shared/bind_this.ts index 0896dc3991..b4e3d25e7d 100644 --- a/src/compiler/compile/render_dom/wrappers/shared/bind_this.ts +++ b/src/compiler/compile/render_dom/wrappers/shared/bind_this.ts @@ -8,7 +8,7 @@ export default function bind_this(component: Component, block: Block, binding: B const fn = component.get_unique_name(`${variable}_binding`); component.add_var({ - name: fn, + name: fn.name, internal: true, referenced: true }); diff --git a/src/compiler/compile/utils/stringify.ts b/src/compiler/compile/utils/stringify.ts index cd478b90ff..650a49051a 100644 --- a/src/compiler/compile/utils/stringify.ts +++ b/src/compiler/compile/utils/stringify.ts @@ -2,6 +2,13 @@ export function stringify(data: string, options = {}) { return JSON.stringify(escape(data, options)); } +export function string_literal(data: string) { + return { + type: 'Literal', + value: data + }; +} + export function escape(data: string, { only_escape_at_symbol = false } = {}) { return data.replace(only_escape_at_symbol ? /@+/g : /(@+|#+)/g, (match: string) => { return match + match[0]; diff --git a/src/compiler/interfaces.ts b/src/compiler/interfaces.ts index d63194edcd..dd73e53e4a 100644 --- a/src/compiler/interfaces.ts +++ b/src/compiler/interfaces.ts @@ -1,3 +1,8 @@ +export interface Identifier { + type: 'Identifier'; + name: string; +} + interface BaseNode { start: number; end: number; diff --git a/test/runtime/samples/_/_config.js b/test/runtime/samples/_/_config.js new file mode 100644 index 0000000000..7ba33fb008 --- /dev/null +++ b/test/runtime/samples/_/_config.js @@ -0,0 +1,5 @@ +export default { + solo: 1, + + html: '

Hello world!

' +}; \ No newline at end of file diff --git a/test/runtime/samples/_/main.svelte b/test/runtime/samples/_/main.svelte new file mode 100644 index 0000000000..efe5048cbc --- /dev/null +++ b/test/runtime/samples/_/main.svelte @@ -0,0 +1 @@ +

Hello world!

\ No newline at end of file