diff --git a/package-lock.json b/package-lock.json index dcbdf879b3..e1b1d45992 100644 --- a/package-lock.json +++ b/package-lock.json @@ -507,9 +507,9 @@ "dev": true }, "code-red": { - "version": "0.0.11", - "resolved": "https://registry.npmjs.org/code-red/-/code-red-0.0.11.tgz", - "integrity": "sha512-GY5N9XC10+FZclzAGdrGFF47alu+I1xCcpM/eoYmpQZmAvOp1HkOgMLsxCtmuB94q84I693yN7SGGtu9sfOslA==", + "version": "0.0.12", + "resolved": "https://registry.npmjs.org/code-red/-/code-red-0.0.12.tgz", + "integrity": "sha512-5R8zbm2z5CmHp2iSmV/kSDhXcdISJwBr898jIPeASEdsGrjFpT4Jhky0J659jLsZL9gzDBGn32rvR0YWDXzAvw==", "dev": true, "requires": { "acorn": "^7.0.0", diff --git a/package.json b/package.json index 20f1fde658..9725d71530 100644 --- a/package.json +++ b/package.json @@ -63,7 +63,7 @@ "acorn": "^7.0.0", "agadoo": "^1.0.1", "c8": "^5.0.1", - "code-red": "0.0.11", + "code-red": "0.0.12", "codecov": "^3.5.0", "css-tree": "1.0.0-alpha22", "eslint": "^6.3.0", diff --git a/src/compiler/compile/Component.ts b/src/compiler/compile/Component.ts index 4d26867565..5c4780c8c1 100644 --- a/src/compiler/compile/Component.ts +++ b/src/compiler/compile/Component.ts @@ -2,7 +2,7 @@ import { walk, childKeys } from 'estree-walker'; import { getLocator } from 'locate-character'; import Stats from '../Stats'; -import { globals, reserved } from '../utils/names'; +import { globals, reserved, is_valid } from '../utils/names'; import { namespaces, valid_namespaces } from '../utils/namespaces'; import create_module from './create_module'; import { @@ -24,7 +24,7 @@ import TemplateScope from './nodes/shared/TemplateScope'; import fuzzymatch from '../utils/fuzzymatch'; import get_object from './utils/get_object'; import Slot from './nodes/Slot'; -import { Node, ImportDeclaration, Identifier, Program, ExpressionStatement, AssignmentExpression } from 'estree'; +import { Node, ImportDeclaration, Identifier, Program, ExpressionStatement, AssignmentExpression, Literal } from 'estree'; import add_to_set from './utils/add_to_set'; import check_graph_for_cycles from './utils/check_graph_for_cycles'; import { print, x } from 'code-red'; @@ -285,24 +285,44 @@ export default class Component { const program: any = { type: 'Program', body: result }; walk(program, { - enter: (node) => { + enter: (node, parent, key) => { if (node.type === 'Identifier' && !('name' in node)) { console.log(node); throw new Error('wtf'); } - if (node.type === 'Identifier' && node.name[0] === '@') { - // TODO temp - if (!/@\w+$/.test(node.name)) { - throw new Error(`wut "${node.name}"`); + if (node.type === 'Identifier') { + if (node.name[0] === '@') { + // TODO temp + if (!/@\w+$/.test(node.name)) { + throw new Error(`wut "${node.name}"`); + } + + if (node.name[1] === '_') { + const alias = this.global(node.name.slice(2)); + node.name = alias.name; + } else { + const alias = this.helper(node.name.slice(1)); + node.name = alias.name; + } } - if (node.name[1] === '_') { - const alias = this.global(node.name.slice(2)); - node.name = alias.name; - } else { - const alias = this.helper(node.name.slice(1)); - node.name = alias.name; + else if (node.name[0] !== '#' && !is_valid(node.name)) { + // this hack allows x`foo.${bar}` where bar could be invalid + const literal: Literal = { type: 'Literal', value: node.name }; + + if (parent.type === 'Property' && key === 'key') { + parent.key = literal; + } + + else if (parent.type === 'MemberExpression' && key === 'property') { + parent.property = literal; + parent.computed = true; + } + + else { + console.log(node); + } } } } diff --git a/src/compiler/compile/render_dom/index.ts b/src/compiler/compile/render_dom/index.ts index b0b4d8bce2..eeb4d3b2b4 100644 --- a/src/compiler/compile/render_dom/index.ts +++ b/src/compiler/compile/render_dom/index.ts @@ -41,8 +41,8 @@ export default function dom( body.push(b` function ${add_css}() { var style = @element("style"); - style.id = '${component.stylesheet.id}-style'; - style.textContent = ${styles}; + style.id = "${component.stylesheet.id}-style"; + style.textContent = "${styles}"; @append(@_document.head, style); } `); @@ -84,7 +84,7 @@ export default function dom( ${$$props} => { ${uses_props && component.invalidate('$$props', x`$$props = @assign(@assign({}, $$props), $$new_props)`)} ${writable_props.map(prop => - b`if ('${prop.export_name}' in ${$$props}) ${component.invalidate(prop.name, `${prop.name} = ${$$props}.${prop.export_name}`)};` + b`if ('${prop.export_name}' in ${$$props}) ${component.invalidate(prop.name, x`${prop.name} = ${$$props}.${prop.export_name}`)};` )} ${component.slots.size > 0 && b`if ('$$scope' in ${$$props}) ${component.invalidate('$$scope', x`$$scope = ${$$props}.$$scope`)};`} @@ -160,7 +160,7 @@ export default function dom( if (expected.length) { dev_props_check = b` - const { ctx } = this.$$; + const { ctx: #ctx } = this.$$; const props = ${options.customElement ? `this.attributes` : `options.props || {}`}; ${expected.map(prop => b` if (#ctx.${prop.name} === undefined && !('${prop.export_name}' in props)) { @@ -171,7 +171,7 @@ export default function dom( capture_state = (uses_props || writable_props.length > 0) ? x` () => { - return { ${component.vars.filter(prop => prop.writable).map(prop => prop.name).join(", ")} }; + return { ${component.vars.filter(prop => prop.writable).map(prop => p`${prop.name}`)} }; } ` : x` () => { @@ -184,7 +184,7 @@ export default function dom( ${$$props} => { ${uses_props && component.invalidate('$$props', x`$$props = @assign(@assign({}, $$props), $$new_props)`)} ${writable_vars.map(prop => b` - if ('${prop.name}' in $$props) ${component.invalidate(prop.name, `${prop.name} = ${$$props}.${prop.name}`)}; + if ('${prop.name}' in $$props) ${component.invalidate(prop.name, x`${prop.name} = ${$$props}.${prop.name}`)}; `)} } ` : x` @@ -327,13 +327,14 @@ export default function dom( const dependencies = Array.from(d.dependencies); const uses_props = !!dependencies.find(n => n === '$$props'); - const condition = !uses_props && dependencies - .filter(n => { - const variable = component.var_lookup.get(n); - return variable && (variable.writable || variable.mutated); - }) + const writable = dependencies.filter(n => { + const variable = component.var_lookup.get(n); + return variable && (variable.writable || variable.mutated); + }); + + const condition = !uses_props && writable.length > 0 && (writable .map(n => x`$$dirty.${n}`) - .reduce((lhs, rhs) => x`${lhs} || ${rhs}`); + .reduce((lhs, rhs) => x`${lhs} || ${rhs}`)); let statement = d.node; if (condition) statement = b`if (${condition}) { ${statement} }`[0]; @@ -405,7 +406,7 @@ export default function dom( ${component.slots.size && b`let { $$slots = {}, $$scope } = $$props;`} - ${renderer.binding_groups.length > 0 && `const $$binding_groups = [${renderer.binding_groups.map(_ => `[]`).join(', ')}];`} + ${renderer.binding_groups.length > 0 && b`const $$binding_groups = [${renderer.binding_groups.map(_ => x`[]`)}];`} ${component.partly_hoisted} diff --git a/src/compiler/compile/render_dom/wrappers/EachBlock.ts b/src/compiler/compile/render_dom/wrappers/EachBlock.ts index 40a5ace9d4..75d0dd3880 100644 --- a/src/compiler/compile/render_dom/wrappers/EachBlock.ts +++ b/src/compiler/compile/render_dom/wrappers/EachBlock.ts @@ -395,7 +395,7 @@ export default class EachBlockWrapper extends Wrapper { ${this.block.has_outros && b`@group_outros();`} ${this.node.has_animation && b`for (let #i = 0; #i < ${view_length}; #i += 1) ${iterations}[#i].r();`} - ${iterations} = @update_keyed_each(${iterations}, #changed, ${get_key}, ${dynamic ? '1' : '0'}, #ctx, ${this.vars.each_block_value}, ${lookup}, ${update_mount_node}, ${destroy}, ${create_each_block}, ${update_anchor_node}, ${this.vars.get_each_context}); + ${iterations} = @update_keyed_each(${iterations}, #changed, ${get_key}, ${dynamic ? 1 : 0}, #ctx, ${this.vars.each_block_value}, ${lookup}, ${update_mount_node}, ${destroy}, ${create_each_block}, ${update_anchor_node}, ${this.vars.get_each_context}); ${this.node.has_animation && b`for (let #i = 0; #i < ${view_length}; #i += 1) ${iterations}[#i].a();`} ${this.block.has_outros && b`@check_outros();`} `); @@ -508,7 +508,7 @@ export default class EachBlockWrapper extends Wrapper { } `; - const start = this.block.has_update_method ? '0' : `#old_length`; + const start = this.block.has_update_method ? 0 : `#old_length`; let remove_old_blocks; @@ -529,7 +529,7 @@ export default class EachBlockWrapper extends Wrapper { `; } else { remove_old_blocks = b` - for (${this.block.has_update_method ? `` : x`#i = ${data_length}`}; #i < ${this.block.has_update_method ? view_length : '#old_length'}; #i += 1) { + for (${this.block.has_update_method ? null : x`#i = ${data_length}`}; #i < ${this.block.has_update_method ? view_length : '#old_length'}; #i += 1) { ${iterations}[#i].d(1); } ${!fixed_length && b`${view_length} = ${data_length};`} diff --git a/src/compiler/compile/render_dom/wrappers/Element/StyleAttribute.ts b/src/compiler/compile/render_dom/wrappers/Element/StyleAttribute.ts index 4da35952dc..dec62dcb4a 100644 --- a/src/compiler/compile/render_dom/wrappers/Element/StyleAttribute.ts +++ b/src/compiler/compile/render_dom/wrappers/Element/StyleAttribute.ts @@ -3,7 +3,7 @@ import Attribute from '../../../nodes/Attribute'; import Block from '../../Block'; import AttributeWrapper from './Attribute'; import ElementWrapper from '../Element'; -import { stringify } from '../../../utils/stringify'; +import { string_literal } from '../../../utils/stringify'; import add_to_set from '../../../utils/add_to_set'; import Expression from '../../../nodes/shared/Expression'; import Text from '../../../nodes/Text'; @@ -32,12 +32,10 @@ export default class StyleAttributeWrapper extends AttributeWrapper { value = prop.value .map(chunk => { if (chunk.type === 'Text') { - return stringify(chunk.data); + return string_literal(chunk.data); } else { - const snippet = chunk.manipulate(block); - add_to_set(prop_dependencies, chunk.dynamic_dependencies()); - return snippet; + return chunk.manipulate(block); } }) .reduce((lhs, rhs) => x`${lhs} + ${rhs}`) @@ -61,11 +59,11 @@ export default class StyleAttributeWrapper extends AttributeWrapper { block.chunks.update.push(update); } } else { - value = stringify((prop.value[0] as Text).data); + value = string_literal((prop.value[0] as Text).data); } block.chunks.hydrate.push( - b`@set_style(${this.parent.var}, "${prop.key}", ${value}, ${prop.important ? 1 : ''});` + b`@set_style(${this.parent.var}, "${prop.key}", ${value}, ${prop.important ? 1 : null});` ); }); } diff --git a/src/compiler/compile/render_dom/wrappers/Element/index.ts b/src/compiler/compile/render_dom/wrappers/Element/index.ts index aa9ee0c041..ec2626d5f9 100644 --- a/src/compiler/compile/render_dom/wrappers/Element/index.ts +++ b/src/compiler/compile/render_dom/wrappers/Element/index.ts @@ -480,8 +480,21 @@ export default class ElementWrapper extends Wrapper { callee = x`#ctx.${handler}`; } + const arg = contextual_dependencies.size > 0 && { + type: 'ObjectPattern', + properties: Array.from(contextual_dependencies).map(name => { + const id = { type: 'Identifier', name }; + return { + type: 'Property', + kind: 'init', + key: id, + value: id + }; + }) + }; + this.renderer.component.partly_hoisted.push(b` - function ${handler}(${contextual_dependencies.size > 0 ? `{ ${Array.from(contextual_dependencies).join(', ')} }` : ``}) { + function ${handler}(${arg}) { ${group.bindings.map(b => b.handler.mutation)} ${Array.from(dependencies).filter(dep => dep[0] !== '$').map(dep => b`${this.renderer.component.invalidate(dep)};`)} } @@ -774,10 +787,10 @@ export default class ElementWrapper extends Wrapper { block.chunks.fix.push(b` @fix_position(${this.var}); ${stop_animation}(); - ${outro && `@add_transform(${this.var}, ${rect});`} + ${outro && b`@add_transform(${this.var}, ${rect});`} `); - const params = this.node.animation.expression ? this.node.animation.expression.manipulate(block) : '{}'; + const params = this.node.animation.expression ? this.node.animation.expression.manipulate(block) : x`{}`; const name = component.qualify(this.node.animation.name); diff --git a/src/compiler/compile/render_dom/wrappers/RawMustacheTag.ts b/src/compiler/compile/render_dom/wrappers/RawMustacheTag.ts index 74724535ac..60a6223783 100644 --- a/src/compiler/compile/render_dom/wrappers/RawMustacheTag.ts +++ b/src/compiler/compile/render_dom/wrappers/RawMustacheTag.ts @@ -27,7 +27,7 @@ export default class RawMustacheTagWrapper extends Tag { const can_use_innerhtml = !in_head && parent_node && !this.prev && !this.next; if (can_use_innerhtml) { - const insert = content => b`${parent_node}.innerHTML = ${content};`; + const insert = content => b`${parent_node}.innerHTML = ${content};`[0]; const { init } = this.rename_this_method( block, 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 8c32e6026d..93525f1020 100644 --- a/src/compiler/compile/render_dom/wrappers/shared/bind_this.ts +++ b/src/compiler/compile/render_dom/wrappers/shared/bind_this.ts @@ -25,14 +25,14 @@ export default function bind_this(component: Component, block: Block, binding: B const { snippet } = block.bindings.get(name); lhs = snippet; - body = `${lhs} = $$value`; // TODO we need to invalidate... something + body = b`${lhs} = $$value`; // TODO we need to invalidate... something } else { object = flatten_reference(binding.expression.node).name; lhs = component.source.slice(binding.expression.node.start, binding.expression.node.end).trim(); body = binding.expression.node.type === 'Identifier' ? b` - ${component.invalidate(object, `${lhs} = $$value`)}; + ${component.invalidate(object, x`${lhs} = $$value`)}; ` : b` ${lhs} = $$value; diff --git a/src/compiler/utils/names.ts b/src/compiler/utils/names.ts index 8d9150bc18..7796e5a11b 100644 --- a/src/compiler/utils/names.ts +++ b/src/compiler/utils/names.ts @@ -104,7 +104,7 @@ export function is_void(name: string) { return void_element_names.test(name) || name.toLowerCase() === '!doctype'; } -function is_valid(str: string): boolean { +export function is_valid(str: string): boolean { let i = 0; while (i < str.length) {