diff --git a/src/generators/Generator.ts b/src/generators/Generator.ts index d90cfaab38..5b7ec8f071 100644 --- a/src/generators/Generator.ts +++ b/src/generators/Generator.ts @@ -1,6 +1,8 @@ import MagicString, { Bundle } from 'magic-string'; import { walk } from 'estree-walker'; import { getLocator } from 'locate-character'; +import deindent from '../utils/deindent'; +import CodeBuilder from '../utils/CodeBuilder'; import getCodeFrame from '../utils/getCodeFrame'; import isReference from '../utils/isReference'; import flattenReference from '../utils/flattenReference'; @@ -23,6 +25,51 @@ interface Computation { deps: string[] } +function detectIndentation(str: string) { + const pattern = /^[\t\s]{1,4}/gm; + let match; + + while (match = pattern.exec(str)) { + if (match[0][0] === '\t') return '\t'; + if (match[0].length === 2) return ' '; + } + + return ' '; +} + +function getIndentationLevel(str: string, b: number) { + let a = b; + while (a > 0 && str[a - 1] !== '\n') a -= 1; + return /^\s*/.exec(str.slice(a, b))[0]; +} + +function getIndentExclusionRanges(node: Node) { + const ranges: Node[] = []; + walk(node, { + enter(node: Node) { + if (node.type === 'TemplateElement') ranges.push(node); + } + }); + return ranges; +} + +function removeIndentation( + code: MagicString, + start: number, + end: number, + indentationLevel: string, + ranges: Node[] +) { + const str = code.original.slice(start, end); + const pattern = new RegExp(`^${indentationLevel}`, 'gm'); + let match; + + while (match = pattern.exec(str)) { + // TODO bail if we're inside an exclusion range + code.remove(start + match.index, start + match.index + indentationLevel.length); + } +} + export default class Generator { ast: Parsed; parsed: Parsed; @@ -43,10 +90,10 @@ export default class Generator { importedComponents: Map; namespace: string; hasComponents: boolean; - hasJs: boolean; computations: Computation[]; templateProperties: Record; slots: Set; + javascript: string; code: MagicString; @@ -59,7 +106,8 @@ export default class Generator { stylesheet: Stylesheet; - importedNames: Set; + userVars: Set; + templateVars: Map; aliases: Map; usedNames: Set; @@ -68,7 +116,8 @@ export default class Generator { source: string, name: string, stylesheet: Stylesheet, - options: CompileOptions + options: CompileOptions, + dom: boolean ) { this.ast = clone(parsed); @@ -101,11 +150,12 @@ export default class Generator { // allow compiler to deconflict user's `import { get } from 'whatever'` and // Svelte's builtin `import { get, ... } from 'svelte/shared.ts'`; - this.importedNames = new Set(); + this.userVars = new Set(); + this.templateVars = new Map(); this.aliases = new Map(); this.usedNames = new Set(); - this.parseJs(); + this.parseJs(dom); this.name = this.alias(name); if (options.customElement === true) { @@ -198,7 +248,11 @@ export default class Generator { usedContexts.add(name); } else if (helpers.has(name)) { - code.prependRight(node.start, `${self.alias('template')}.helpers.`); + let object = node; + while (object.type === 'MemberExpression') object = object.object; + + const alias = self.templateVars.get(`helpers-${name}`); + if (alias !== name) code.overwrite(object.start, object.end, alias); } else if (indexes.has(name)) { const context = indexes.get(name); usedContexts.add(context); // TODO is this right? @@ -367,7 +421,7 @@ export default class Generator { for ( let i = 1; reservedNames.has(alias) || - this.importedNames.has(alias) || + this.userVars.has(alias) || this.usedNames.has(alias); alias = `${name}_${i++}` ); @@ -383,7 +437,7 @@ export default class Generator { } reservedNames.forEach(add); - this.importedNames.forEach(add); + this.userVars.forEach(add); return (name: string) => { if (test) name = `${name}$`; @@ -399,30 +453,40 @@ export default class Generator { }; } - parseJs() { - const { source } = this; + parseJs(dom: boolean) { + const { code, source } = this; const { js } = this.parsed; const imports = this.imports; const computations: Computation[] = []; const templateProperties: Record = {}; + const componentDefinition = new CodeBuilder(); let namespace = null; - let hasJs = !!js; if (js) { this.addSourcemapLocations(js.content); + + const indentation = detectIndentation(source.slice(js.start, js.end)); + const indentationLevel = getIndentationLevel(source, js.content.body[0].start); + const indentExclusionRanges = getIndentExclusionRanges(js.content); + + const scope = annotateWithScopes(js.content); + scope.declarations.forEach(name => { + this.userVars.add(name); + }); + const body = js.content.body.slice(); // slice, because we're going to be mutating the original // imports need to be hoisted out of the IIFE for (let i = 0; i < body.length; i += 1) { const node = body[i]; if (node.type === 'ImportDeclaration') { - removeNode(this.code, js.content, node); + removeNode(code, js.content, node); imports.push(node); node.specifiers.forEach((specifier: Node) => { - this.importedNames.add(specifier.local.name); + this.userVars.add(specifier.local.name); }); } } @@ -435,176 +499,197 @@ export default class Generator { defaultExport.declaration.properties.forEach((prop: Node) => { templateProperties[prop.key.name] = prop; }); - } - ['helpers', 'events', 'components', 'transitions'].forEach(key => { - if (templateProperties[key]) { - templateProperties[key].value.properties.forEach((prop: Node) => { - this[key].add(prop.key.name); + ['helpers', 'events', 'components', 'transitions'].forEach(key => { + if (templateProperties[key]) { + templateProperties[key].value.properties.forEach((prop: Node) => { + this[key].add(prop.key.name); + }); + } + }); + + const addArrowFunctionExpression = (name: string, node: Node) => { + const { body, params } = node; + + const paramString = params.length ? + `[✂${params[0].start}-${params[params.length - 1].end}✂]` : + ``; + + if (body.type === 'BlockStatement') { + componentDefinition.addBlock(deindent` + function ${name}(${paramString}) [✂${body.start}-${body.end}✂] + `); + } else { + componentDefinition.addBlock(deindent` + function ${name}(${paramString}) { + return [✂${body.start}-${body.end}✂]; + } + `); + } + }; + + const addFunctionExpression = (name: string, node: Node) => { + let c = node.start; + while (this.source[c] !== '(') c += 1; + componentDefinition.addBlock(deindent` + function ${name}[✂${c}-${node.end}✂]; + `); + }; + + const addValue = (name: string, node: Node) => { + componentDefinition.addBlock(deindent` + var ${name} = [✂${node.start}-${node.end}✂]; + `); + }; + + const addDeclaration = (key: string, node: Node, disambiguator?: string) => { + const qualified = disambiguator ? `${disambiguator}-${key}` : key; + + if (node.type === 'Identifier' && node.name === key) { + this.templateVars.set(qualified, key); + return; + } + + let name = this.getUniqueName(key); + this.templateVars.set(qualified, name); + + // deindent + const indentationLevel = getIndentationLevel(source, node.start); + if (indentationLevel) { + removeIndentation(code, node.start, node.end, indentationLevel, indentExclusionRanges); + } + + if (node.type === 'ArrowFunctionExpression') { + addArrowFunctionExpression(name, node); + } else if (node.type === 'FunctionExpression') { + addFunctionExpression(name, node); + } else { + addValue(name, node); + } + }; + + if (templateProperties.components) { + templateProperties.components.value.properties.forEach((property: Node) => { + addDeclaration(property.key.name, property.value, 'components'); }); } - }); - if (templateProperties.computed) { - const dependencies = new Map(); + if (templateProperties.computed) { + const dependencies = new Map(); - templateProperties.computed.value.properties.forEach((prop: Node) => { - const key = prop.key.name; - const value = prop.value; + templateProperties.computed.value.properties.forEach((prop: Node) => { + const key = prop.key.name; + const value = prop.value; - const deps = value.params.map( - (param: Node) => - param.type === 'AssignmentPattern' ? param.left.name : param.name - ); - dependencies.set(key, deps); - }); + const deps = value.params.map( + (param: Node) => + param.type === 'AssignmentPattern' ? param.left.name : param.name + ); + dependencies.set(key, deps); + }); - const visited = new Set(); + const visited = new Set(); - const visit = function visit(key: string) { - if (!dependencies.has(key)) return; // not a computation + const visit = (key: string) => { + if (!dependencies.has(key)) return; // not a computation - if (visited.has(key)) return; - visited.add(key); + if (visited.has(key)) return; + visited.add(key); - const deps = dependencies.get(key); - deps.forEach(visit); + const deps = dependencies.get(key); + deps.forEach(visit); - computations.push({ key, deps }); - } + computations.push({ key, deps }); - templateProperties.computed.value.properties.forEach((prop: Node) => - visit(prop.key.name) - ); - } + const prop = templateProperties.computed.value.properties.find((prop: Node) => prop.key.name === key); + addDeclaration(key, prop.value, 'computed'); + }; - if (templateProperties.namespace) { - const ns = templateProperties.namespace.value.value; - namespace = namespaces[ns] || ns; + templateProperties.computed.value.properties.forEach((prop: Node) => + visit(prop.key.name) + ); + } - removeObjectKey(this.code, defaultExport.declaration, 'namespace'); - } + if (templateProperties.data) { + addDeclaration('data', templateProperties.data.value); + } - if (templateProperties.components) { - let hasNonImportedComponent = false; - templateProperties.components.value.properties.forEach( - (property: Node) => { - const key = property.key.name; - const value = source.slice( - property.value.start, - property.value.end - ); - if (this.importedNames.has(value)) { - this.importedComponents.set(key, value); - } else { - hasNonImportedComponent = true; - } - } - ); - if (hasNonImportedComponent) { - // remove the specific components that were imported, as we'll refer to them directly - Array.from(this.importedComponents.keys()).forEach(key => { - removeObjectKey( - this.code, - templateProperties.components.value, - key - ); + if (templateProperties.events && dom) { + templateProperties.events.value.properties.forEach((property: Node) => { + addDeclaration(property.key.name, property.value, 'events'); }); - } else { - // remove the entire components portion of the export - removeObjectKey(this.code, defaultExport.declaration, 'components'); } - } - // Remove these after version 2 - if (templateProperties.onrender) { - const { key } = templateProperties.onrender; - this.code.overwrite(key.start, key.end, 'oncreate', { - storeName: true, - contentOnly: false, - }); - templateProperties.oncreate = templateProperties.onrender; - } + if (templateProperties.helpers) { + templateProperties.helpers.value.properties.forEach((property: Node) => { + addDeclaration(property.key.name, property.value, 'helpers'); + }); + } - if (templateProperties.onteardown) { - const { key } = templateProperties.onteardown; - this.code.overwrite(key.start, key.end, 'ondestroy', { - storeName: true, - contentOnly: false, - }); - templateProperties.ondestroy = templateProperties.onteardown; - } + if (templateProperties.methods && dom) { + addDeclaration('methods', templateProperties.methods.value); + } - if (templateProperties.tag) { - this.tag = templateProperties.tag.value.value; - removeObjectKey(this.code, defaultExport.declaration, 'tag'); - } + if (templateProperties.namespace) { + const ns = templateProperties.namespace.value.value; + namespace = namespaces[ns] || ns; + } - if (templateProperties.props) { - this.props = templateProperties.props.value.elements.map((element: Node) => element.value); - removeObjectKey(this.code, defaultExport.declaration, 'props'); - } + if (templateProperties.onrender) templateProperties.oncreate = templateProperties.onrender; // remove after v2 + if (templateProperties.oncreate && dom) { + addDeclaration('oncreate', templateProperties.oncreate.value); + } - // now that we've analysed the default export, we can determine whether or not we need to keep it - let hasDefaultExport = !!defaultExport; - if (defaultExport && defaultExport.declaration.properties.length === 0) { - hasDefaultExport = false; - removeNode(this.code, js.content, defaultExport); - } + if (templateProperties.onteardown) templateProperties.ondestroy = templateProperties.onteardown; // remove after v2 + if (templateProperties.ondestroy && dom) { + addDeclaration('ondestroy', templateProperties.ondestroy.value); + } - // if we do need to keep it, then we need to generate a return statement - if (hasDefaultExport) { - const finalNode = body[body.length - 1]; - if (defaultExport === finalNode) { - // export is last property, we can just return it - this.code.overwrite( - defaultExport.start, - defaultExport.declaration.start, - `return ` - ); - } else { - const { declarations } = annotateWithScopes(js); - let template = 'template'; - for ( - let i = 1; - declarations.has(template); - template = `template_${i++}` - ); + if (templateProperties.props) { + this.props = templateProperties.props.value.elements.map((element: Node) => element.value); + } - this.code.overwrite( - defaultExport.start, - defaultExport.declaration.start, - `var ${template} = ` - ); + if (templateProperties.setup) { + addDeclaration('setup', templateProperties.setup.value); + } - let i = defaultExport.start; - while (/\s/.test(source[i - 1])) i--; + if (templateProperties.tag) { + this.tag = templateProperties.tag.value.value; + } - const indentation = source.slice(i, defaultExport.start); - this.code.appendLeft( - finalNode.end, - `\n\n${indentation}return ${template};` - ); + if (templateProperties.transitions) { + templateProperties.transitions.value.properties.forEach((property: Node) => { + addDeclaration(property.key.name, property.value, 'transitions'); + }); + } + } + + if (indentationLevel) { + if (defaultExport) { + removeIndentation(code, js.content.start, defaultExport.start, indentationLevel, indentExclusionRanges); + removeIndentation(code, defaultExport.end, js.content.end, indentationLevel, indentExclusionRanges); + } else { + removeIndentation(code, js.content.start, js.content.end, indentationLevel, indentExclusionRanges); } } - // user code gets wrapped in an IIFE - if (js.content.body.length) { - const prefix = hasDefaultExport - ? `var ${this.alias('template')} = (function() {` - : `(function() {`; - this.code - .prependRight(js.content.start, prefix) - .appendLeft(js.content.end, '}());'); + let a = js.content.start; + while (/\s/.test(source[a])) a += 1; + + let b = js.content.end; + while (/\s/.test(source[b - 1])) b -= 1; + + if (defaultExport) { + this.javascript = ''; + if (a !== defaultExport.start) this.javascript += `[✂${a}-${defaultExport.start}✂]`; + if (!componentDefinition.isEmpty()) this.javascript += componentDefinition; + if (defaultExport.end !== b) this.javascript += `[✂${defaultExport.end}-${b}✂]`; } else { - // if there's no need to include user code, remove it altogether - this.code.remove(js.content.start, js.content.end); - hasJs = false; + this.javascript = a === b ? null : `[✂${a}-${b}✂]`; } } this.computations = computations; - this.hasJs = hasJs; this.namespace = namespace; this.templateProperties = templateProperties; } diff --git a/src/generators/dom/index.ts b/src/generators/dom/index.ts index 33bcd0d9db..bccfa3288b 100644 --- a/src/generators/dom/index.ts +++ b/src/generators/dom/index.ts @@ -39,7 +39,7 @@ export class DomGenerator extends Generator { stylesheet: Stylesheet, options: CompileOptions ) { - super(parsed, source, name, stylesheet, options); + super(parsed, source, name, stylesheet, options, true); this.blocks = []; this.readonly = new Set(); @@ -60,7 +60,7 @@ export class DomGenerator extends Generator { } reservedNames.forEach(add); - this.importedNames.forEach(add); + this.userVars.forEach(add); for (const name in shared) { localUsedNames.add(test ? `${name}$` : name); } @@ -92,7 +92,6 @@ export default function dom( const { computations, - hasJs, name, templateProperties, namespace, @@ -127,7 +126,7 @@ export default function dom( const condition = `${deps.map(dep => `changed.${dep}`).join(' || ')}`; - const statement = `if (@differs(state.${key}, (state.${key} = @template.computed.${key}(${deps + const statement = `if (@differs(state.${key}, (state.${key} = %computed-${key}(${deps .map(dep => `state.${dep}`) .join(', ')})))) changed.${key} = true;`; @@ -135,8 +134,8 @@ export default function dom( }); } - if (hasJs) { - builder.addBlock(`[✂${parsed.js.content.start}-${parsed.js.content.end}✂]`); + if (generator.javascript) { + builder.addBlock(generator.javascript); } if (generator.needsEncapsulateHelper) { @@ -173,7 +172,7 @@ export default function dom( const prototypeBase = `${name}.prototype` + - (templateProperties.methods ? `, @template.methods` : ''); + (templateProperties.methods ? `, %methods` : ''); const proto = sharedPath ? `@proto` : deindent` @@ -192,7 +191,7 @@ export default function dom( @init(this, options); ${generator.usesRefs && `this.refs = {};`} this._state = ${templateProperties.data - ? `@assign(@template.data(), options.data)` + ? `@assign(%data(), options.data)` : `options.data || {}`}; ${generator.metaBindings} ${computations.length && `this._recompute({ ${Array.from(computationDeps).map(dep => `${dep}: 1`).join(', ')} }, this._state);`} @@ -204,7 +203,7 @@ export default function dom( ${generator.bindingGroups.length && `this._bindingGroups = [${Array(generator.bindingGroups.length).fill('[]').join(', ')}];`} - ${templateProperties.ondestroy && `this._handlers.destroy = [@template.ondestroy]`} + ${templateProperties.ondestroy && `this._handlers.destroy = [%ondestroy]`} ${generator.slots.size && `this._slotted = options.slots || {};`} @@ -217,16 +216,16 @@ export default function dom( `if (!document.getElementById("${generator.stylesheet.id}-style")) @add_css();`) } - ${templateProperties.oncreate && `var oncreate = @template.oncreate.bind(this);`} + ${templateProperties.oncreate && `var _oncreate = %oncreate.bind(this);`} ${(templateProperties.oncreate || generator.hasComponents || generator.hasComplexBindings || generator.hasIntroTransitions) && deindent` if (!options._root) { - this._oncreate = [${templateProperties.oncreate && `oncreate`}]; + this._oncreate = [${templateProperties.oncreate && `_oncreate`}]; ${(generator.hasComponents || generator.hasComplexBindings) && `this._beforecreate = [];`} ${(generator.hasComponents || generator.hasIntroTransitions) && `this._aftercreate = [];`} } ${templateProperties.oncreate && deindent` else { - this._root._oncreate.push(oncreate); + this._root._oncreate.push(_oncreate); } `} `} @@ -338,22 +337,28 @@ export default function dom( } ` : (!sharedPath && `${name}.prototype._recompute = @noop;`)} - ${templateProperties.setup && `@template.setup(${name});`} + ${templateProperties.setup && `%setup(${name});`} `); const usedHelpers = new Set(); let result = builder .toString() - .replace(/(@+)(\w*)/g, (match: string, sigil: string, name: string) => { - if (sigil !== '@') return sigil.slice(1) + name; + .replace(/(%+|@+)(\w*(?:-\w*)?)/g, (match: string, sigil: string, name: string) => { + if (sigil === '@') { + if (name in shared) { + if (options.dev && `${name}Dev` in shared) name = `${name}Dev`; + usedHelpers.add(name); + } + + return generator.alias(name); + } - if (name in shared) { - if (options.dev && `${name}Dev` in shared) name = `${name}Dev`; - usedHelpers.add(name); + if (sigil === '%') { + return generator.templateVars.get(name); } - return generator.alias(name); + return sigil.slice(1) + name; }); let helpers; diff --git a/src/generators/dom/visitors/Component.ts b/src/generators/dom/visitors/Component.ts index 30f912690d..1a06ebadec 100644 --- a/src/generators/dom/visitors/Component.ts +++ b/src/generators/dom/visitors/Component.ts @@ -216,10 +216,7 @@ export default function visitComponent( } } - const expression = node.name === ':Self' - ? generator.name - : generator.importedComponents.get(node.name) || - `@template.components.${node.name}`; + const expression = node.name === ':Self' ? generator.name : `%components-${node.name}`; block.builders.init.addBlock(deindent` ${statements.join('\n')} diff --git a/src/generators/dom/visitors/Element/EventHandler.ts b/src/generators/dom/visitors/Element/EventHandler.ts index ded9abc44b..0a887f63f7 100644 --- a/src/generators/dom/visitors/Element/EventHandler.ts +++ b/src/generators/dom/visitors/Element/EventHandler.ts @@ -79,7 +79,7 @@ export default function visitEventHandler( block.addVariable(handlerName); block.builders.hydrate.addBlock(deindent` - ${handlerName} = @template.events.${name}.call(#component, ${state.parentNode}, function(event) { + ${handlerName} = %events-${name}.call(#component, ${state.parentNode}, function(event) { ${handlerBody} }); `); diff --git a/src/generators/dom/visitors/Element/addTransitions.ts b/src/generators/dom/visitors/Element/addTransitions.ts index 98e76b518e..b10f404de2 100644 --- a/src/generators/dom/visitors/Element/addTransitions.ts +++ b/src/generators/dom/visitors/Element/addTransitions.ts @@ -20,7 +20,7 @@ export default function addTransitions( block.addVariable(name); - const fn = `@template.transitions.${intro.name}`; + const fn = `%transitions-${intro.name}`; block.builders.intro.addBlock(deindent` #component._root._aftercreate.push(function() { @@ -48,7 +48,7 @@ export default function addTransitions( ? block.contextualise(intro.expression).snippet : '{}'; - const fn = `@template.transitions.${intro.name}`; // TODO add built-in transitions? + const fn = `%transitions-${intro.name}`; // TODO add built-in transitions? if (outro) { block.builders.intro.addBlock(deindent` @@ -73,7 +73,7 @@ export default function addTransitions( ? block.contextualise(outro.expression).snippet : '{}'; - const fn = `@template.transitions.${outro.name}`; + const fn = `%transitions-${outro.name}`; // TODO hide elements that have outro'd (unless they belong to a still-outroing // group) prior to their removal from the DOM diff --git a/src/generators/server-side-rendering/index.ts b/src/generators/server-side-rendering/index.ts index d624400b91..5e6bd25d8d 100644 --- a/src/generators/server-side-rendering/index.ts +++ b/src/generators/server-side-rendering/index.ts @@ -21,42 +21,14 @@ export class SsrGenerator extends Generator { stylesheet: Stylesheet, options: CompileOptions ) { - super(parsed, source, name, stylesheet, options); + super(parsed, source, name, stylesheet, options, false); this.bindings = []; this.renderCode = ''; this.appendTargets = []; - // in an SSR context, we don't need to include events, methods, oncreate or ondestroy - const { templateProperties, defaultExport } = this; - preprocess(this, parsed.html); this.stylesheet.warnOnUnusedSelectors(options.onwarn); - - if (templateProperties.oncreate) - removeNode( - this.code, - defaultExport.declaration, - templateProperties.oncreate - ); - if (templateProperties.ondestroy) - removeNode( - this.code, - defaultExport.declaration, - templateProperties.ondestroy - ); - if (templateProperties.methods) - removeNode( - this.code, - defaultExport.declaration, - templateProperties.methods - ); - if (templateProperties.events) - removeNode( - this.code, - defaultExport.declaration, - templateProperties.events - ); } append(code: string) { @@ -80,7 +52,7 @@ export default function ssr( const generator = new SsrGenerator(parsed, source, options.name || 'SvelteComponent', stylesheet, options); - const { computations, name, hasJs, templateProperties } = generator; + const { computations, name, templateProperties } = generator; // create main render() function const mainBlock = new Block({ @@ -99,24 +71,24 @@ export default function ssr( generator.stylesheet.render(options.filename, true); const result = deindent` - ${hasJs && `[✂${parsed.js.content.start}-${parsed.js.content.end}✂]`} + ${generator.javascript} var ${name} = {}; ${options.filename && `${name}.filename = ${stringify(options.filename)}`}; ${name}.data = function() { - return ${templateProperties.data ? `@template.data()` : `{}`}; + return ${templateProperties.data ? `%data()` : `{}`}; }; ${name}.render = function(state, options) { ${templateProperties.data - ? `state = Object.assign(@template.data(), state || {});` + ? `state = Object.assign(%data(), state || {});` : `state = state || {};`} ${computations.map( ({ key, deps }) => - `state.${key} = @template.computed.${key}(${deps.map(dep => `state.${dep}`).join(', ')});` + `state.${key} = %computed-${key}(${deps.map(dep => `state.${dep}`).join(', ')});` )} ${generator.bindings.length && @@ -159,10 +131,8 @@ export default function ssr( }); } - ${templateProperties.components.value.properties.map(prop => { - const { name } = prop.key; - const expression = generator.importedComponents.get(name) || `@template.components.${name}`; - return `addComponent(${expression});`; + ${templateProperties.components.value.properties.map((prop: Node) => { + return `addComponent(%components-${prop.key.name});`; })} `} @@ -184,8 +154,10 @@ export default function ssr( function __escape(html) { return String(html).replace(/["'&<>]/g, match => escaped[match]); } - `.replace(/(@+|#+)(\w*)/g, (match: string, sigil: string, name: string) => { - return sigil === '@' ? generator.alias(name) : sigil.slice(1) + name; + `.replace(/(@+|#+|%+)(\w*(?:-\w*)?)/g, (match: string, sigil: string, name: string) => { + if (sigil === '@') return generator.alias(name); + if (sigil === '%') return generator.templateVars.get(name); + return sigil.slice(1) + name; }); return generator.generate(result, options, { name, format }); diff --git a/src/generators/server-side-rendering/visitors/Component.ts b/src/generators/server-side-rendering/visitors/Component.ts index 7af91a0aad..0ab6604be3 100644 --- a/src/generators/server-side-rendering/visitors/Component.ts +++ b/src/generators/server-side-rendering/visitors/Component.ts @@ -69,10 +69,7 @@ export default function visitComponent( ) .join(', '); - const expression = node.name === ':Self' - ? generator.name - : generator.importedComponents.get(node.name) || - `@template.components.${node.name}`; + const expression = node.name === ':Self' ? generator.name : `%components-${node.name}`; bindings.forEach(binding => { block.addBinding(binding, expression); diff --git a/src/utils/stringify.ts b/src/utils/stringify.ts index ab83bec5b4..26037827c7 100644 --- a/src/utils/stringify.ts +++ b/src/utils/stringify.ts @@ -3,7 +3,7 @@ export function stringify(data: string, options = {}) { } export function escape(data: string, { onlyEscapeAtSymbol = false } = {}) { - return data.replace(onlyEscapeAtSymbol ? /(@+)/g : /(@+|#+)/g, (match: string) => { + return data.replace(onlyEscapeAtSymbol ? /(%+|@+)/g : /(%+|@+|#+)/g, (match: string) => { return match + match[0]; }); } diff --git a/test/css/index.js b/test/css/index.js index 65867b294a..3310be8267 100644 --- a/test/css/index.js +++ b/test/css/index.js @@ -101,34 +101,44 @@ describe('css', () => { if (expected.html !== null) { const window = env(); - const Component = eval( - `(function () { ${dom.code}; return SvelteComponent; }())` - ); - const target = window.document.querySelector('main'); - - new Component({ target, data: config.data }); - const html = target.innerHTML; - - fs.writeFileSync(`test/css/samples/${dir}/_actual.html`, html); - // dom - assert.equal( - normalizeHtml(window, html.replace(/svelte-\d+/g, 'svelte-xyz')), - normalizeHtml(window, expected.html) - ); + try { + const Component = eval( + `(function () { ${dom.code}; return SvelteComponent; }())` + ); + const target = window.document.querySelector('main'); + + new Component({ target, data: config.data }); + const html = target.innerHTML; + + fs.writeFileSync(`test/css/samples/${dir}/_actual.html`, html); + + assert.equal( + normalizeHtml(window, html.replace(/svelte-\d+/g, 'svelte-xyz')), + normalizeHtml(window, expected.html) + ); + } catch (err) { + console.log(dom.code); + throw err; + } // ssr - const component = eval( - `(function () { ${ssr.code}; return SvelteComponent; }())` - ); - - assert.equal( - normalizeHtml( - window, - component.render(config.data).replace(/svelte-\d+/g, 'svelte-xyz') - ), - normalizeHtml(window, expected.html) - ); + try { + const component = eval( + `(function () { ${ssr.code}; return SvelteComponent; }())` + ); + + assert.equal( + normalizeHtml( + window, + component.render(config.data).replace(/svelte-\d+/g, 'svelte-xyz') + ), + normalizeHtml(window, expected.html) + ); + } catch (err) { + console.log(ssr.code); + throw err; + } } }); }); @@ -140,4 +150,4 @@ function read(file) { } catch (err) { return null; } -} +} \ No newline at end of file diff --git a/test/js/index.js b/test/js/index.js index 6d3b1ddfc7..07bdc183de 100644 --- a/test/js/index.js +++ b/test/js/index.js @@ -62,13 +62,13 @@ describe("js", () => { ); assert.equal( - actual.trim().replace(/^\s+$/gm, ""), - expected.trim().replace(/^\s+$/gm, "") + actual.trim().replace(/^[ \t]+$/gm, ""), + expected.trim().replace(/^[ \t]+$/gm, "") ); assert.equal( - code.trim().replace(/^\s+$/gm, ""), - expectedBundle.trim().replace(/^\s+$/gm, "") + code.trim().replace(/^[ \t]+$/gm, ""), + expectedBundle.trim().replace(/^[ \t]+$/gm, "") ); }).catch(err => { if (err.loc) console.error(err.loc); diff --git a/test/js/samples/collapses-text-around-comments/expected-bundle.js b/test/js/samples/collapses-text-around-comments/expected-bundle.js index 402045ae74..af3a60b888 100644 --- a/test/js/samples/collapses-text-around-comments/expected-bundle.js +++ b/test/js/samples/collapses-text-around-comments/expected-bundle.js @@ -190,13 +190,9 @@ var proto = { }; /* generated by Svelte vX.Y.Z */ -var template = (function() { - return { - data: function () { - return { foo: 42 } - } - }; -}()); +function data() { + return { foo: 42 } +} function encapsulateStyles(node) { setAttribute(node, "svelte-3590263702", ""); @@ -244,7 +240,7 @@ function create_main_fragment(state, component) { function SvelteComponent(options) { init(this, options); - this._state = assign(template.data(), options.data); + this._state = assign(data(), options.data); if (!document.getElementById("svelte-3590263702-style")) add_css(); diff --git a/test/js/samples/collapses-text-around-comments/expected.js b/test/js/samples/collapses-text-around-comments/expected.js index b4ba5f9c54..0ad2ccd29e 100644 --- a/test/js/samples/collapses-text-around-comments/expected.js +++ b/test/js/samples/collapses-text-around-comments/expected.js @@ -1,13 +1,9 @@ /* generated by Svelte vX.Y.Z */ import { appendNode, assign, createElement, createText, detachNode, init, insertNode, noop, proto, setAttribute } from "svelte/shared.js"; -var template = (function() { - return { - data: function () { - return { foo: 42 } - } - }; -}()); +function data() { + return { foo: 42 } +}; function encapsulateStyles(node) { setAttribute(node, "svelte-3590263702", ""); @@ -55,7 +51,7 @@ function create_main_fragment(state, component) { function SvelteComponent(options) { init(this, options); - this._state = assign(template.data(), options.data); + this._state = assign(data(), options.data); if (!document.getElementById("svelte-3590263702-style")) add_css(); diff --git a/test/js/samples/component-static/expected-bundle.js b/test/js/samples/component-static/expected-bundle.js index 888c437806..12c0a0cd4b 100644 --- a/test/js/samples/component-static/expected-bundle.js +++ b/test/js/samples/component-static/expected-bundle.js @@ -166,17 +166,11 @@ var proto = { }; /* generated by Svelte vX.Y.Z */ -var template = (function() { - return { - components: { - Nested: window.Nested - } - }; -}()); +var Nested = window.Nested; function create_main_fragment(state, component) { - var nested = new template.components.Nested({ + var nested = new Nested({ _root: component._root, data: { foo: "bar" } }); diff --git a/test/js/samples/component-static/expected.js b/test/js/samples/component-static/expected.js index 7ca873cc5f..039ef606ed 100644 --- a/test/js/samples/component-static/expected.js +++ b/test/js/samples/component-static/expected.js @@ -1,17 +1,11 @@ /* generated by Svelte vX.Y.Z */ import { assign, callAll, init, noop, proto } from "svelte/shared.js"; -var template = (function() { - return { - components: { - Nested: window.Nested - } - }; -}()); +var Nested = window.Nested; function create_main_fragment(state, component) { - var nested = new template.components.Nested({ + var nested = new Nested({ _root: component._root, data: { foo: "bar" } }); diff --git a/test/js/samples/computed-collapsed-if/expected-bundle.js b/test/js/samples/computed-collapsed-if/expected-bundle.js index 949b0e8e26..41c35ea754 100644 --- a/test/js/samples/computed-collapsed-if/expected-bundle.js +++ b/test/js/samples/computed-collapsed-if/expected-bundle.js @@ -166,14 +166,13 @@ var proto = { }; /* generated by Svelte vX.Y.Z */ -var template = (function() { - return { - computed: { - a: x => x * 2, - b: x => x * 3 - } - }; -}()); +function a(x) { + return x * 2; +} + +function b(x) { + return x * 3; +} function create_main_fragment(state, component) { @@ -207,8 +206,8 @@ assign(SvelteComponent.prototype, proto); SvelteComponent.prototype._recompute = function _recompute(changed, state) { if (changed.x) { - if (differs(state.a, (state.a = template.computed.a(state.x)))) changed.a = true; - if (differs(state.b, (state.b = template.computed.b(state.x)))) changed.b = true; + if (differs(state.a, (state.a = a(state.x)))) changed.a = true; + if (differs(state.b, (state.b = b(state.x)))) changed.b = true; } }; diff --git a/test/js/samples/computed-collapsed-if/expected.js b/test/js/samples/computed-collapsed-if/expected.js index 1ad70b617d..bc3fcc37d5 100644 --- a/test/js/samples/computed-collapsed-if/expected.js +++ b/test/js/samples/computed-collapsed-if/expected.js @@ -1,14 +1,13 @@ /* generated by Svelte vX.Y.Z */ import { assign, differs, init, noop, proto } from "svelte/shared.js"; -var template = (function() { - return { - computed: { - a: x => x * 2, - b: x => x * 3 - } - }; -}()); +function a(x) { + return x * 2; +} + +function b(x) { + return x * 3; +} function create_main_fragment(state, component) { @@ -42,8 +41,8 @@ assign(SvelteComponent.prototype, proto); SvelteComponent.prototype._recompute = function _recompute(changed, state) { if (changed.x) { - if (differs(state.a, (state.a = template.computed.a(state.x)))) changed.a = true; - if (differs(state.b, (state.b = template.computed.b(state.x)))) changed.b = true; + if (differs(state.a, (state.a = a(state.x)))) changed.a = true; + if (differs(state.b, (state.b = b(state.x)))) changed.b = true; } } export default SvelteComponent; \ No newline at end of file diff --git a/test/js/samples/event-handlers-custom/expected-bundle.js b/test/js/samples/event-handlers-custom/expected-bundle.js index b94a6a848a..a712f5b742 100644 --- a/test/js/samples/event-handlers-custom/expected-bundle.js +++ b/test/js/samples/event-handlers-custom/expected-bundle.js @@ -178,20 +178,15 @@ var proto = { }; /* generated by Svelte vX.Y.Z */ -var template = (function() { - return { - methods: { - foo ( bar ) { - console.log( bar ); - } - }, - events: { - foo ( node, callback ) { - // code goes here - } - } - }; -}()); +function foo( node, callback ) { + // code goes here +} + +var methods = { + foo ( bar ) { + console.log( bar ); + } +}; function create_main_fragment(state, component) { var button, foo_handler; @@ -204,7 +199,7 @@ function create_main_fragment(state, component) { }, h: function hydrate() { - foo_handler = template.events.foo.call(component, button, function(event) { + foo_handler = foo.call(component, button, function(event) { var state = component.get(); component.foo( state.bar ); }); @@ -238,6 +233,6 @@ function SvelteComponent(options) { } } -assign(SvelteComponent.prototype, template.methods, proto); +assign(SvelteComponent.prototype, methods, proto); export default SvelteComponent; diff --git a/test/js/samples/event-handlers-custom/expected.js b/test/js/samples/event-handlers-custom/expected.js index 5164130066..3739ada3a6 100644 --- a/test/js/samples/event-handlers-custom/expected.js +++ b/test/js/samples/event-handlers-custom/expected.js @@ -1,20 +1,15 @@ /* generated by Svelte vX.Y.Z */ import { assign, createElement, detachNode, init, insertNode, noop, proto } from "svelte/shared.js"; -var template = (function() { - return { - methods: { - foo ( bar ) { - console.log( bar ); - } - }, - events: { - foo ( node, callback ) { - // code goes here - } - } - }; -}()); +function foo( node, callback ) { + // code goes here +}; + +var methods = { + foo ( bar ) { + console.log( bar ); + } +}; function create_main_fragment(state, component) { var button, foo_handler; @@ -27,7 +22,7 @@ function create_main_fragment(state, component) { }, h: function hydrate() { - foo_handler = template.events.foo.call(component, button, function(event) { + foo_handler = foo.call(component, button, function(event) { var state = component.get(); component.foo( state.bar ); }); @@ -61,5 +56,5 @@ function SvelteComponent(options) { } } -assign(SvelteComponent.prototype, template.methods, proto); +assign(SvelteComponent.prototype, methods, proto); export default SvelteComponent; \ No newline at end of file diff --git a/test/js/samples/non-imported-component/expected-bundle.js b/test/js/samples/non-imported-component/expected-bundle.js index f6cd719f54..0aefe73b06 100644 --- a/test/js/samples/non-imported-component/expected-bundle.js +++ b/test/js/samples/non-imported-component/expected-bundle.js @@ -180,14 +180,6 @@ var proto = { }; /* generated by Svelte vX.Y.Z */ -var template = (function() { - return { - components: { - NonImported - } - }; -}()); - function create_main_fragment(state, component) { var text; @@ -195,7 +187,7 @@ function create_main_fragment(state, component) { _root: component._root }); - var nonimported = new template.components.NonImported({ + var nonimported = new NonImported({ _root: component._root }); diff --git a/test/js/samples/non-imported-component/expected.js b/test/js/samples/non-imported-component/expected.js index aee3a35280..1f07457149 100644 --- a/test/js/samples/non-imported-component/expected.js +++ b/test/js/samples/non-imported-component/expected.js @@ -2,13 +2,7 @@ import { assign, callAll, createText, detachNode, init, insertNode, noop, proto } from "svelte/shared.js"; import Imported from 'Imported.html'; -var template = (function() { - return { - components: { - NonImported - } - }; -}()); + function create_main_fragment(state, component) { var text; @@ -17,7 +11,7 @@ function create_main_fragment(state, component) { _root: component._root }); - var nonimported = new template.components.NonImported({ + var nonimported = new NonImported({ _root: component._root }); diff --git a/test/js/samples/onrender-onteardown-rewritten/expected-bundle.js b/test/js/samples/onrender-onteardown-rewritten/expected-bundle.js index 57bbff1fc0..28dafe0c0d 100644 --- a/test/js/samples/onrender-onteardown-rewritten/expected-bundle.js +++ b/test/js/samples/onrender-onteardown-rewritten/expected-bundle.js @@ -166,13 +166,9 @@ var proto = { }; /* generated by Svelte vX.Y.Z */ -var template = (function() { - return { - // this test should be removed in v2 - oncreate () {}, - ondestroy () {} - }; -}()); +function oncreate() {} + +function ondestroy() {} function create_main_fragment(state, component) { @@ -193,14 +189,14 @@ function SvelteComponent(options) { init(this, options); this._state = options.data || {}; - this._handlers.destroy = [template.ondestroy]; + this._handlers.destroy = [ondestroy]; - var oncreate = template.oncreate.bind(this); + var _oncreate = oncreate.bind(this); if (!options._root) { - this._oncreate = [oncreate]; + this._oncreate = [_oncreate]; } else { - this._root._oncreate.push(oncreate); + this._root._oncreate.push(_oncreate); } this._fragment = create_main_fragment(this._state, this); diff --git a/test/js/samples/onrender-onteardown-rewritten/expected.js b/test/js/samples/onrender-onteardown-rewritten/expected.js index 0464d7a0d2..95f923e4d4 100644 --- a/test/js/samples/onrender-onteardown-rewritten/expected.js +++ b/test/js/samples/onrender-onteardown-rewritten/expected.js @@ -1,13 +1,9 @@ /* generated by Svelte vX.Y.Z */ import { assign, callAll, init, noop, proto } from "svelte/shared.js"; -var template = (function() { - return { - // this test should be removed in v2 - oncreate () {}, - ondestroy () {} - }; -}()); +function oncreate() {}; + +function ondestroy() {}; function create_main_fragment(state, component) { @@ -28,14 +24,14 @@ function SvelteComponent(options) { init(this, options); this._state = options.data || {}; - this._handlers.destroy = [template.ondestroy] + this._handlers.destroy = [ondestroy] - var oncreate = template.oncreate.bind(this); + var _oncreate = oncreate.bind(this); if (!options._root) { - this._oncreate = [oncreate]; + this._oncreate = [_oncreate]; } else { - this._root._oncreate.push(oncreate); + this._root._oncreate.push(_oncreate); } this._fragment = create_main_fragment(this._state, this); diff --git a/test/js/samples/setup-method/expected-bundle.js b/test/js/samples/setup-method/expected-bundle.js index 145647d7c2..42eed6868b 100644 --- a/test/js/samples/setup-method/expected-bundle.js +++ b/test/js/samples/setup-method/expected-bundle.js @@ -166,24 +166,21 @@ var proto = { }; /* generated by Svelte vX.Y.Z */ -var template = (function() { - return { - methods: { - foo ( bar ) { - console.log( bar ); - } - }, - setup: (Component) => { - Component.SOME_CONSTANT = 42; - Component.factory = function (target) { - return new Component({ - target: target - }); - }; - Component.prototype.foo( 'baz' ); - } +var methods = { + foo ( bar ) { + console.log( bar ); + } +}; + +function setup(Component) { + Component.SOME_CONSTANT = 42; + Component.factory = function (target) { + return new Component({ + target: target + }); }; -}()); + Component.prototype.foo( 'baz' ); +} function create_main_fragment(state, component) { @@ -212,8 +209,8 @@ function SvelteComponent(options) { } } -assign(SvelteComponent.prototype, template.methods, proto); +assign(SvelteComponent.prototype, methods, proto); -template.setup(SvelteComponent); +setup(SvelteComponent); export default SvelteComponent; diff --git a/test/js/samples/setup-method/expected.js b/test/js/samples/setup-method/expected.js index e7cd138aa3..f49817bb99 100644 --- a/test/js/samples/setup-method/expected.js +++ b/test/js/samples/setup-method/expected.js @@ -1,24 +1,21 @@ /* generated by Svelte vX.Y.Z */ import { assign, init, noop, proto } from "svelte/shared.js"; -var template = (function() { - return { - methods: { - foo ( bar ) { - console.log( bar ); - } - }, - setup: (Component) => { - Component.SOME_CONSTANT = 42; - Component.factory = function (target) { - return new Component({ - target: target - }); - } - Component.prototype.foo( 'baz' ); - } - }; -}()); +var methods = { + foo ( bar ) { + console.log( bar ); + } +}; + +function setup(Component) { + Component.SOME_CONSTANT = 42; + Component.factory = function (target) { + return new Component({ + target: target + }); + } + Component.prototype.foo( 'baz' ); +} function create_main_fragment(state, component) { @@ -47,7 +44,7 @@ function SvelteComponent(options) { } } -assign(SvelteComponent.prototype, template.methods, proto); +assign(SvelteComponent.prototype, methods, proto); -template.setup(SvelteComponent); +setup(SvelteComponent); export default SvelteComponent; \ No newline at end of file diff --git a/test/js/samples/ssr-no-oncreate-etc/_config.js b/test/js/samples/ssr-no-oncreate-etc/_config.js new file mode 100644 index 0000000000..803712adec --- /dev/null +++ b/test/js/samples/ssr-no-oncreate-etc/_config.js @@ -0,0 +1,5 @@ +export default { + options: { + generate: 'ssr' + } +}; \ No newline at end of file diff --git a/test/js/samples/ssr-no-oncreate-etc/expected-bundle.js b/test/js/samples/ssr-no-oncreate-etc/expected-bundle.js new file mode 100644 index 0000000000..6ea9ece9d4 --- /dev/null +++ b/test/js/samples/ssr-no-oncreate-etc/expected-bundle.js @@ -0,0 +1,23 @@ +var SvelteComponent = {}; + +SvelteComponent.data = function() { + return {}; +}; + +SvelteComponent.render = function(state, options) { + state = state || {}; + + return ``.trim(); +}; + +SvelteComponent.renderCss = function() { + var components = []; + + return { + css: components.map(x => x.css).join('\n'), + map: null, + components + }; +}; + +module.exports = SvelteComponent; diff --git a/test/js/samples/ssr-no-oncreate-etc/expected.js b/test/js/samples/ssr-no-oncreate-etc/expected.js new file mode 100644 index 0000000000..6de85e548c --- /dev/null +++ b/test/js/samples/ssr-no-oncreate-etc/expected.js @@ -0,0 +1,37 @@ +"use strict"; + +var SvelteComponent = {};; + +SvelteComponent.data = function() { + return {}; +}; + +SvelteComponent.render = function(state, options) { + state = state || {}; + + return ``.trim(); +}; + +SvelteComponent.renderCss = function() { + var components = []; + + return { + css: components.map(x => x.css).join('\n'), + map: null, + components + }; +}; + +var escaped = { + '"': '"', + "'": ''', + '&': '&', + '<': '<', + '>': '>' +}; + +function __escape(html) { + return String(html).replace(/["'&<>]/g, match => escaped[match]); +} + +module.exports = SvelteComponent; \ No newline at end of file diff --git a/test/js/samples/ssr-no-oncreate-etc/input.html b/test/js/samples/ssr-no-oncreate-etc/input.html new file mode 100644 index 0000000000..f8c26b5825 --- /dev/null +++ b/test/js/samples/ssr-no-oncreate-etc/input.html @@ -0,0 +1,23 @@ + \ No newline at end of file diff --git a/test/runtime/samples/deconflict-vars/_config.js b/test/runtime/samples/deconflict-vars/_config.js new file mode 100644 index 0000000000..b3303b13fe --- /dev/null +++ b/test/runtime/samples/deconflict-vars/_config.js @@ -0,0 +1,3 @@ +export default { + html: `

ab

` +}; diff --git a/test/runtime/samples/deconflict-vars/main.html b/test/runtime/samples/deconflict-vars/main.html new file mode 100644 index 0000000000..5f96bf932c --- /dev/null +++ b/test/runtime/samples/deconflict-vars/main.html @@ -0,0 +1,26 @@ +

{{value}}

+ + diff --git a/test/runtime/samples/escaped-text/_config.js b/test/runtime/samples/escaped-text/_config.js index 93a393d8cf..43644dde82 100644 --- a/test/runtime/samples/escaped-text/_config.js +++ b/test/runtime/samples/escaped-text/_config.js @@ -1,3 +1,7 @@ export default { - html: `@@x` + html: ` + @@x + %1 + %%2 + ` }; \ No newline at end of file diff --git a/test/runtime/samples/escaped-text/main.html b/test/runtime/samples/escaped-text/main.html index 6a173c026f..603b331ca6 100644 --- a/test/runtime/samples/escaped-text/main.html +++ b/test/runtime/samples/escaped-text/main.html @@ -1 +1,3 @@ -@@x \ No newline at end of file +@@x +%1 +%%2 \ No newline at end of file diff --git a/test/runtime/samples/setup/_config.js b/test/runtime/samples/setup/_config.js new file mode 100644 index 0000000000..47b0cf332e --- /dev/null +++ b/test/runtime/samples/setup/_config.js @@ -0,0 +1,5 @@ +export default { + test(assert, component) { + assert.ok(component.constructor.FOO); + } +}; \ No newline at end of file diff --git a/test/runtime/samples/setup/main.html b/test/runtime/samples/setup/main.html new file mode 100644 index 0000000000..a1192323c9 --- /dev/null +++ b/test/runtime/samples/setup/main.html @@ -0,0 +1,7 @@ + \ No newline at end of file