From fcf2b03ba6c4adfc43ca40fb198a9039776bf948 Mon Sep 17 00:00:00 2001 From: Rich Harris Date: Tue, 19 Sep 2017 12:59:12 -0400 Subject: [PATCH 1/7] take template object out of IIFE --- src/generators/Generator.ts | 67 ++++++------------- src/generators/dom/index.ts | 2 +- test/js/index.js | 2 +- .../samples/deconflict-vars/_config.js | 3 + .../runtime/samples/deconflict-vars/main.html | 26 +++++++ 5 files changed, 50 insertions(+), 50 deletions(-) create mode 100644 test/runtime/samples/deconflict-vars/_config.js create mode 100644 test/runtime/samples/deconflict-vars/main.html diff --git a/src/generators/Generator.ts b/src/generators/Generator.ts index d90cfaab38..efd5ff7441 100644 --- a/src/generators/Generator.ts +++ b/src/generators/Generator.ts @@ -59,7 +59,7 @@ export default class Generator { stylesheet: Stylesheet; - importedNames: Set; + userVars: Set; aliases: Map; usedNames: Set; @@ -101,7 +101,7 @@ 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.aliases = new Map(); this.usedNames = new Set(); @@ -367,7 +367,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 +383,7 @@ export default class Generator { } reservedNames.forEach(add); - this.importedNames.forEach(add); + this.userVars.forEach(add); return (name: string) => { if (test) name = `${name}$`; @@ -412,6 +412,12 @@ export default class Generator { if (js) { this.addSourcemapLocations(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 @@ -422,7 +428,7 @@ export default class Generator { imports.push(node); node.specifiers.forEach((specifier: Node) => { - this.importedNames.add(specifier.local.name); + this.userVars.add(specifier.local.name); }); } } @@ -494,7 +500,7 @@ export default class Generator { property.value.start, property.value.end ); - if (this.importedNames.has(value)) { + if (this.userVars.has(value)) { this.importedComponents.set(key, value); } else { hasNonImportedComponent = true; @@ -552,51 +558,16 @@ export default class Generator { removeNode(this.code, js.content, defaultExport); } - // if we do need to keep it, then we need to generate a return statement + // if we do need to keep it, then we need to replace `export default` 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++}` - ); - - this.code.overwrite( - defaultExport.start, - defaultExport.declaration.start, - `var ${template} = ` - ); - - let i = defaultExport.start; - while (/\s/.test(source[i - 1])) i--; - - const indentation = source.slice(i, defaultExport.start); - this.code.appendLeft( - finalNode.end, - `\n\n${indentation}return ${template};` - ); - } + this.code.overwrite( + defaultExport.start, + defaultExport.declaration.start, + `var ${this.alias('template')} = ` + ); } - // 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, '}());'); - } else { + if (js.content.body.length === 0) { // if there's no need to include user code, remove it altogether this.code.remove(js.content.start, js.content.end); hasJs = false; diff --git a/src/generators/dom/index.ts b/src/generators/dom/index.ts index 22371c71bc..ebc78e2fd2 100644 --- a/src/generators/dom/index.ts +++ b/src/generators/dom/index.ts @@ -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); } diff --git a/test/js/index.js b/test/js/index.js index 6d3b1ddfc7..3096bdf4c4 100644 --- a/test/js/index.js +++ b/test/js/index.js @@ -4,7 +4,7 @@ import * as path from "path"; import { rollup } from "rollup"; import { loadConfig, svelte } from "../helpers.js"; -describe("js", () => { +describe.skip("js", () => { fs.readdirSync("test/js/samples").forEach(dir => { if (dir[0] === ".") return; 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}}

+ + From 2865a98e57ee487ecb88efb5e9c062cd39b0586c Mon Sep 17 00:00:00 2001 From: Rich Harris Date: Tue, 19 Sep 2017 16:24:03 -0400 Subject: [PATCH 2/7] move properties out of the template IIFE (#756) --- src/generators/Generator.ts | 249 +++++++++++------- src/generators/dom/index.ts | 18 +- .../dom/visitors/Element/EventHandler.ts | 2 +- .../dom/visitors/Element/addTransitions.ts | 6 +- src/generators/server-side-rendering/index.ts | 58 ++-- test/css/index.js | 62 +++-- 6 files changed, 240 insertions(+), 155 deletions(-) diff --git a/src/generators/Generator.ts b/src/generators/Generator.ts index efd5ff7441..9827c09b0d 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'; @@ -47,6 +49,7 @@ export default class Generator { computations: Computation[]; templateProperties: Record; slots: Set; + javascript: string; code: MagicString; @@ -198,7 +201,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.alias(name); + if (alias !== name) code.overwrite(object.start, object.end, `${self.alias(name)}`); } else if (indexes.has(name)) { const context = indexes.get(name); usedContexts.add(context); // TODO is this right? @@ -406,6 +413,7 @@ export default class Generator { const imports = this.imports; const computations: Computation[] = []; const templateProperties: Record = {}; + const componentDefinition = new CodeBuilder(); let namespace = null; let hasJs = !!js; @@ -441,114 +449,175 @@ 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); - }); - } - }); - - if (templateProperties.computed) { - const dependencies = new Map(); - - 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); + ['helpers', 'events', 'components', 'transitions'].forEach(key => { + if (templateProperties[key]) { + templateProperties[key].value.properties.forEach((prop: Node) => { + this[key].add(prop.key.name); + }); + } }); - const visited = new Set(); - - const visit = function visit(key: string) { - if (!dependencies.has(key)) return; // not a computation - - if (visited.has(key)) return; - visited.add(key); - - const deps = dependencies.get(key); - deps.forEach(visit); + const addArrowFunctionExpression = (key: string, node: Node) => { + const { body, params } = node; - computations.push({ key, deps }); - } - - templateProperties.computed.value.properties.forEach((prop: Node) => - visit(prop.key.name) - ); - } + const paramString = params.length ? + `[✂${params[0].start}-${params[params.length - 1].end}✂]` : + ``; - if (templateProperties.namespace) { - const ns = templateProperties.namespace.value.value; - namespace = namespaces[ns] || ns; + if (body.type === 'BlockStatement') { + componentDefinition.addBlock(deindent` + function @${key}(${paramString}) [✂${body.start}-${body.end}✂] + `); + } else { + componentDefinition.addBlock(deindent` + function @${key}(${paramString}) { + return [✂${body.start}-${body.end}✂]; + } + `); + } + }; + + const addFunctionExpression = (key: string, node: Node) => { + let c = node.start; + while (this.source[c] !== '(') c += 1; + componentDefinition.addBlock(deindent` + function @${key}[✂${c}-${node.end}✂]; + `); + }; + + const addValue = (key: string, node: Node) => { + const alias = this.alias(key); + if (node.type !== 'Identifier' || node.name !== alias) { + componentDefinition.addBlock(deindent` + var ${alias} = [✂${node.start}-${node.end}✂]; + `); + } + }; + + const addDeclaration = (key: string, node: Node) => { + // TODO disambiguate between different categories, and ensure + // no conflicts with existing aliases + if (node.type === 'ArrowFunctionExpression') { + addArrowFunctionExpression(key, node); + } else if (node.type === 'FunctionExpression') { + addFunctionExpression(key, node); + } else { + addValue(key, node); + } + }; - removeObjectKey(this.code, defaultExport.declaration, 'namespace'); - } + if (templateProperties.components) { + templateProperties.components.value.properties.forEach((property: Node) => { + // TODO replace all the guff below with this: + // addValue(property.key.name, property.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.userVars.has(value)) { - this.importedComponents.set(key, value); + + if (key !== value) { + const alias = this.alias(key); + componentDefinition.addLine( + `var ${alias} = [✂${property.value.start}-${property.value.end}✂];` + ); + this.importedComponents.set(key, alias); } else { - hasNonImportedComponent = true; + this.importedComponents.set(key, key); } - } - ); - 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.computed) { + const dependencies = new Map(); + + 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 visited = new Set(); + + const visit = (key: string) => { + if (!dependencies.has(key)) return; // not a computation + + if (visited.has(key)) return; + visited.add(key); + + const deps = dependencies.get(key); + deps.forEach(visit); + + computations.push({ key, deps }); + + const prop = templateProperties.computed.value.properties.find((prop: Node) => prop.key.name === key); + addDeclaration(key, prop.value); + }; + + templateProperties.computed.value.properties.forEach((prop: Node) => + visit(prop.key.name) + ); + } + + if (templateProperties.data) { + addDeclaration('data', templateProperties.data.value); + } + + if (templateProperties.events) { + templateProperties.events.value.properties.forEach((property: Node) => { + addDeclaration(property.key.name, property.value); }); - } 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); + }); + } - 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) { + 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) { + addDeclaration('oncreate', templateProperties.oncreate.value); + } + + if (templateProperties.onteardown) templateProperties.ondestroy = templateProperties.onteardown; // remove after v2 + if (templateProperties.ondestroy) { + addDeclaration('ondestroy', templateProperties.ondestroy.value); + } + + if (templateProperties.props) { + this.props = templateProperties.props.value.elements.map((element: Node) => element.value); + } + + if (templateProperties.setup) { + addDeclaration('setup', templateProperties.setup.value); + } + + if (templateProperties.tag) { + this.tag = templateProperties.tag.value.value; + } + + if (templateProperties.transitions) { + templateProperties.transitions.value.properties.forEach((property: Node) => { + addDeclaration(property.key.name, property.value); + }); + } } // now that we've analysed the default export, we can determine whether or not we need to keep it @@ -572,6 +641,10 @@ export default class Generator { this.code.remove(js.content.start, js.content.end); hasJs = false; } + + this.javascript = hasDefaultExport ? + `[✂${js.content.start}-${defaultExport.start}✂]${componentDefinition}[✂${defaultExport.end}-${js.content.end}✂]` : + `[✂${js.content.start}-${js.content.end}✂]`; } this.computations = computations; diff --git a/src/generators/dom/index.ts b/src/generators/dom/index.ts index ebc78e2fd2..7eac27be55 100644 --- a/src/generators/dom/index.ts +++ b/src/generators/dom/index.ts @@ -127,7 +127,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} = @${key}(${deps .map(dep => `state.${dep}`) .join(', ')})))) changed.${key} = true;`; @@ -135,8 +135,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 +173,7 @@ export default function dom( const prototypeBase = `${name}.prototype` + - (templateProperties.methods ? `, @template.methods` : ''); + (templateProperties.methods ? `, @methods` : ''); const proto = sharedPath ? `@proto` : deindent` @@ -192,7 +192,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 +204,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 +217,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); } `} `} diff --git a/src/generators/dom/visitors/Element/EventHandler.ts b/src/generators/dom/visitors/Element/EventHandler.ts index ded9abc44b..f14c5e145f 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} = @${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..739fe1b6bb 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 = `@${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 = `@${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 = `@${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..1ab3f68bc9 100644 --- a/src/generators/server-side-rendering/index.ts +++ b/src/generators/server-side-rendering/index.ts @@ -33,30 +33,32 @@ export class SsrGenerator extends Generator { 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 - ); + // TODO how to exclude non-SSR-able stuff? + + // 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) { @@ -99,24 +101,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} = @${key}(${deps.map(dep => `state.${dep}`).join(', ')});` )} ${generator.bindings.length && 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 From 49403d4326bab9047c188052f5abfb76d249d9d1 Mon Sep 17 00:00:00 2001 From: Rich Harris Date: Tue, 19 Sep 2017 18:43:24 -0400 Subject: [PATCH 3/7] deconflict --- mocha.opts | 1 + src/generators/Generator.ts | 42 ++++++++++--------- src/generators/dom/index.ts | 13 +++--- .../dom/visitors/Element/EventHandler.ts | 2 +- .../dom/visitors/Element/addTransitions.ts | 6 +-- src/generators/server-side-rendering/index.ts | 12 +++--- 6 files changed, 43 insertions(+), 33 deletions(-) diff --git a/mocha.opts b/mocha.opts index 427b029758..af6b17a845 100644 --- a/mocha.opts +++ b/mocha.opts @@ -1 +1,2 @@ +--bail test/test.js \ No newline at end of file diff --git a/src/generators/Generator.ts b/src/generators/Generator.ts index 9827c09b0d..e7243bc6c5 100644 --- a/src/generators/Generator.ts +++ b/src/generators/Generator.ts @@ -63,6 +63,7 @@ export default class Generator { stylesheet: Stylesheet; userVars: Set; + templateVars: Map; aliases: Map; usedNames: Set; @@ -105,6 +106,7 @@ 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.userVars = new Set(); + this.templateVars = new Map(); this.aliases = new Map(); this.usedNames = new Set(); @@ -204,8 +206,8 @@ export default class Generator { let object = node; while (object.type === 'MemberExpression') object = object.object; - const alias = self.alias(name); - if (alias !== name) code.overwrite(object.start, object.end, `${self.alias(name)}`); + 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? @@ -458,7 +460,7 @@ export default class Generator { } }); - const addArrowFunctionExpression = (key: string, node: Node) => { + const addArrowFunctionExpression = (name: string, node: Node) => { const { body, params } = node; const paramString = params.length ? @@ -467,43 +469,45 @@ export default class Generator { if (body.type === 'BlockStatement') { componentDefinition.addBlock(deindent` - function @${key}(${paramString}) [✂${body.start}-${body.end}✂] + function ${name}(${paramString}) [✂${body.start}-${body.end}✂] `); } else { componentDefinition.addBlock(deindent` - function @${key}(${paramString}) { + function ${name}(${paramString}) { return [✂${body.start}-${body.end}✂]; } `); } }; - const addFunctionExpression = (key: string, node: Node) => { + const addFunctionExpression = (name: string, node: Node) => { let c = node.start; while (this.source[c] !== '(') c += 1; componentDefinition.addBlock(deindent` - function @${key}[✂${c}-${node.end}✂]; + function ${name}[✂${c}-${node.end}✂]; `); }; - const addValue = (key: string, node: Node) => { - const alias = this.alias(key); - if (node.type !== 'Identifier' || node.name !== alias) { + const addValue = (name: string, node: Node) => { + if (node.type !== 'Identifier' || node.name !== name) { componentDefinition.addBlock(deindent` - var ${alias} = [✂${node.start}-${node.end}✂]; + var ${name} = [✂${node.start}-${node.end}✂]; `); } }; - const addDeclaration = (key: string, node: Node) => { + const addDeclaration = (key: string, node: Node, disambiguator?: string) => { + let name = this.getUniqueName(key); + this.templateVars.set(disambiguator ? `${disambiguator}-${key}` : key, name); + // TODO disambiguate between different categories, and ensure // no conflicts with existing aliases if (node.type === 'ArrowFunctionExpression') { - addArrowFunctionExpression(key, node); + addArrowFunctionExpression(name, node); } else if (node.type === 'FunctionExpression') { - addFunctionExpression(key, node); + addFunctionExpression(name, node); } else { - addValue(key, node); + addValue(name, node); } }; @@ -558,7 +562,7 @@ export default class Generator { computations.push({ key, deps }); const prop = templateProperties.computed.value.properties.find((prop: Node) => prop.key.name === key); - addDeclaration(key, prop.value); + addDeclaration(key, prop.value, 'computed'); }; templateProperties.computed.value.properties.forEach((prop: Node) => @@ -572,13 +576,13 @@ export default class Generator { if (templateProperties.events) { templateProperties.events.value.properties.forEach((property: Node) => { - addDeclaration(property.key.name, property.value); + addDeclaration(property.key.name, property.value, 'events'); }); } if (templateProperties.helpers) { templateProperties.helpers.value.properties.forEach((property: Node) => { - addDeclaration(property.key.name, property.value); + addDeclaration(property.key.name, property.value, 'helpers'); }); } @@ -615,7 +619,7 @@ export default class Generator { if (templateProperties.transitions) { templateProperties.transitions.value.properties.forEach((property: Node) => { - addDeclaration(property.key.name, property.value); + addDeclaration(property.key.name, property.value, 'transitions'); }); } } diff --git a/src/generators/dom/index.ts b/src/generators/dom/index.ts index 7eac27be55..eee9c5669c 100644 --- a/src/generators/dom/index.ts +++ b/src/generators/dom/index.ts @@ -127,7 +127,7 @@ export default function dom( const condition = `${deps.map(dep => `changed.${dep}`).join(' || ')}`; - const statement = `if (@differs(state.${key}, (state.${key} = @${key}(${deps + const statement = `if (@differs(state.${key}, (state.${key} = %computed-${key}(${deps .map(dep => `state.${dep}`) .join(', ')})))) changed.${key} = true;`; @@ -173,7 +173,7 @@ export default function dom( const prototypeBase = `${name}.prototype` + - (templateProperties.methods ? `, @methods` : ''); + (templateProperties.methods ? `, %methods` : ''); const proto = sharedPath ? `@proto` : deindent` @@ -192,7 +192,7 @@ export default function dom( @init(this, options); ${generator.usesRefs && `this.refs = {};`} this._state = ${templateProperties.data - ? `@assign(@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 +204,7 @@ export default function dom( ${generator.bindingGroups.length && `this._bindingGroups = [${Array(generator.bindingGroups.length).fill('[]').join(', ')}];`} - ${templateProperties.ondestroy && `this._handlers.destroy = [@ondestroy]`} + ${templateProperties.ondestroy && `this._handlers.destroy = [%ondestroy]`} ${generator.slots.size && `this._slotted = options.slots || {};`} @@ -217,7 +217,7 @@ export default function dom( `if (!document.getElementById("${generator.stylesheet.id}-style")) @add_css();`) } - ${templateProperties.oncreate && `var _oncreate = @oncreate.bind(this);`} + ${templateProperties.oncreate && `var _oncreate = %oncreate.bind(this);`} ${(templateProperties.oncreate || generator.hasComponents || generator.hasComplexBindings || generator.hasIntroTransitions) && deindent` if (!options._root) { @@ -345,6 +345,9 @@ export default function dom( let result = builder .toString() + .replace(/%(\w+(?:-\w+)?)/gm, (match: string, name: string) => { + return generator.templateVars.get(name); + }) .replace(/(@+)(\w*)/g, (match: string, sigil: string, name: string) => { if (sigil !== '@') return sigil.slice(1) + name; diff --git a/src/generators/dom/visitors/Element/EventHandler.ts b/src/generators/dom/visitors/Element/EventHandler.ts index f14c5e145f..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} = @${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 739fe1b6bb..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 = `@${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 = `@${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 = `@${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 1ab3f68bc9..96f1b1fe40 100644 --- a/src/generators/server-side-rendering/index.ts +++ b/src/generators/server-side-rendering/index.ts @@ -108,17 +108,17 @@ export default function ssr( ${options.filename && `${name}.filename = ${stringify(options.filename)}`}; ${name}.data = function() { - return ${templateProperties.data ? `@data()` : `{}`}; + return ${templateProperties.data ? `%data()` : `{}`}; }; ${name}.render = function(state, options) { ${templateProperties.data - ? `state = Object.assign(@data(), state || {});` + ? `state = Object.assign(%data(), state || {});` : `state = state || {};`} ${computations.map( ({ key, deps }) => - `state.${key} = @${key}(${deps.map(dep => `state.${dep}`).join(', ')});` + `state.${key} = %computed-${key}(${deps.map(dep => `state.${dep}`).join(', ')});` )} ${generator.bindings.length && @@ -186,8 +186,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 }); From 4dbfc65e744b32fa9ab8a9bb1e58c1812ad34b6a Mon Sep 17 00:00:00 2001 From: Rich Harris Date: Wed, 20 Sep 2017 11:06:23 -0400 Subject: [PATCH 4/7] fix indentation, update snapshot tests --- src/generators/Generator.ts | 106 ++++++++++++++---- src/generators/dom/index.ts | 3 +- src/generators/dom/visitors/Component.ts | 2 +- src/generators/server-side-rendering/index.ts | 2 +- .../visitors/Component.ts | 2 +- test/js/index.js | 10 +- .../expected-bundle.js | 12 +- .../expected.js | 12 +- .../component-static/expected-bundle.js | 10 +- test/js/samples/component-static/expected.js | 10 +- .../computed-collapsed-if/expected-bundle.js | 19 ++-- .../samples/computed-collapsed-if/expected.js | 19 ++-- .../event-handlers-custom/expected-bundle.js | 27 ++--- .../samples/event-handlers-custom/expected.js | 27 ++--- .../non-imported-component/expected-bundle.js | 10 +- .../non-imported-component/expected.js | 10 +- .../expected-bundle.js | 18 ++- .../onrender-onteardown-rewritten/expected.js | 18 ++- .../samples/setup-method/expected-bundle.js | 35 +++--- test/js/samples/setup-method/expected.js | 37 +++--- test/runtime/samples/setup/_config.js | 5 + test/runtime/samples/setup/main.html | 7 ++ 22 files changed, 208 insertions(+), 193 deletions(-) create mode 100644 test/runtime/samples/setup/_config.js create mode 100644 test/runtime/samples/setup/main.html diff --git a/src/generators/Generator.ts b/src/generators/Generator.ts index e7243bc6c5..bd781c05fc 100644 --- a/src/generators/Generator.ts +++ b/src/generators/Generator.ts @@ -25,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; @@ -45,7 +90,6 @@ export default class Generator { importedComponents: Map; namespace: string; hasComponents: boolean; - hasJs: boolean; computations: Computation[]; templateProperties: Record; slots: Set; @@ -409,7 +453,7 @@ export default class Generator { } parseJs() { - const { source } = this; + const { code, source } = this; const { js } = this.parsed; const imports = this.imports; @@ -418,11 +462,14 @@ export default class Generator { 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); @@ -500,6 +547,12 @@ export default class Generator { let name = this.getUniqueName(key); this.templateVars.set(disambiguator ? `${disambiguator}-${key}` : key, name); + // deindent + const indentationLevel = getIndentationLevel(source, node.start); + if (indentationLevel) { + removeIndentation(code, node.start, node.end, indentationLevel, indentExclusionRanges); + } + // TODO disambiguate between different categories, and ensure // no conflicts with existing aliases if (node.type === 'ArrowFunctionExpression') { @@ -624,35 +677,46 @@ export default class Generator { } } - // 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 we do need to keep it, then we need to replace `export default` - if (hasDefaultExport) { - this.code.overwrite( - defaultExport.start, - defaultExport.declaration.start, - `var ${this.alias('template')} = ` - ); + // if (defaultExport) { + // this.code.overwrite( + // defaultExport.start, + // defaultExport.declaration.start, + // `var ${this.alias('template')} = ` + // ); + // } + + 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); + } } if (js.content.body.length === 0) { // 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 = hasDefaultExport ? - `[✂${js.content.start}-${defaultExport.start}✂]${componentDefinition}[✂${defaultExport.end}-${js.content.end}✂]` : - `[✂${js.content.start}-${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 { + 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 eee9c5669c..e9fe9805c7 100644 --- a/src/generators/dom/index.ts +++ b/src/generators/dom/index.ts @@ -92,7 +92,6 @@ export default function dom( const { computations, - hasJs, name, templateProperties, namespace, @@ -338,7 +337,7 @@ export default function dom( } ` : (!sharedPath && `${name}.prototype._recompute = @noop;`)} - ${templateProperties.setup && `@template.setup(${name});`} + ${templateProperties.setup && `%setup(${name});`} `); const usedHelpers = new Set(); diff --git a/src/generators/dom/visitors/Component.ts b/src/generators/dom/visitors/Component.ts index 5085ee9969..67f647a755 100644 --- a/src/generators/dom/visitors/Component.ts +++ b/src/generators/dom/visitors/Component.ts @@ -219,7 +219,7 @@ export default function visitComponent( const expression = node.name === ':Self' ? generator.name : generator.importedComponents.get(node.name) || - `@template.components.${node.name}`; + `@template.components.${node.name}`; // TODO this is out of date block.builders.init.addBlock(deindent` ${statements.join('\n')} diff --git a/src/generators/server-side-rendering/index.ts b/src/generators/server-side-rendering/index.ts index 96f1b1fe40..58d410fbe6 100644 --- a/src/generators/server-side-rendering/index.ts +++ b/src/generators/server-side-rendering/index.ts @@ -82,7 +82,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({ diff --git a/src/generators/server-side-rendering/visitors/Component.ts b/src/generators/server-side-rendering/visitors/Component.ts index 7af91a0aad..8c527ac361 100644 --- a/src/generators/server-side-rendering/visitors/Component.ts +++ b/src/generators/server-side-rendering/visitors/Component.ts @@ -72,7 +72,7 @@ export default function visitComponent( const expression = node.name === ':Self' ? generator.name : generator.importedComponents.get(node.name) || - `@template.components.${node.name}`; + `@template.components.${node.name}`; // TODO out of date bindings.forEach(binding => { block.addBinding(binding, expression); diff --git a/test/js/index.js b/test/js/index.js index 3096bdf4c4..07bdc183de 100644 --- a/test/js/index.js +++ b/test/js/index.js @@ -4,7 +4,7 @@ import * as path from "path"; import { rollup } from "rollup"; import { loadConfig, svelte } from "../helpers.js"; -describe.skip("js", () => { +describe("js", () => { fs.readdirSync("test/js/samples").forEach(dir => { if (dir[0] === ".") return; @@ -62,13 +62,13 @@ describe.skip("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 5d44708a27..a69b1fcc77 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 83841b4eb8..f19c316d12 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 a9769110cb..fc1860c060 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 8a22292037..593f8b6a89 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 e6e9152b1e..013e7f1bbc 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 7b9207a955..21cd8bc338 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 7b896bf8f1..d6794a3cfb 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) { }, hydrate: function() { - 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 34832b6000..ebf6a8ee03 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) { }, hydrate: function() { - 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 2252eb1635..9a68b51d17 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 e424bb6ffd..6959e54da5 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 bebd336b6c..bf7688dc3a 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 d29f9fd752..21a5d0ed86 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 85bc643281..f92175d261 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 b3bd9fcc8e..cfacd329dc 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/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 From 7f39b5be165c9314a545f16f6465f72d2ff15c89 Mon Sep 17 00:00:00 2001 From: Rich Harris Date: Wed, 20 Sep 2017 11:42:07 -0400 Subject: [PATCH 5/7] tidy up --- src/generators/Generator.ts | 54 +++++-------------- src/generators/dom/visitors/Component.ts | 5 +- src/generators/server-side-rendering/index.ts | 6 +-- .../visitors/Component.ts | 5 +- 4 files changed, 17 insertions(+), 53 deletions(-) diff --git a/src/generators/Generator.ts b/src/generators/Generator.ts index bd781c05fc..1255773897 100644 --- a/src/generators/Generator.ts +++ b/src/generators/Generator.ts @@ -481,7 +481,7 @@ export default class Generator { 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) => { @@ -536,16 +536,21 @@ export default class Generator { }; const addValue = (name: string, node: Node) => { - if (node.type !== 'Identifier' || node.name !== name) { - componentDefinition.addBlock(deindent` - var ${name} = [✂${node.start}-${node.end}✂]; - `); - } + 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(disambiguator ? `${disambiguator}-${key}` : key, name); + this.templateVars.set(qualified, name); // deindent const indentationLevel = getIndentationLevel(source, node.start); @@ -553,8 +558,6 @@ export default class Generator { removeIndentation(code, node.start, node.end, indentationLevel, indentExclusionRanges); } - // TODO disambiguate between different categories, and ensure - // no conflicts with existing aliases if (node.type === 'ArrowFunctionExpression') { addArrowFunctionExpression(name, node); } else if (node.type === 'FunctionExpression') { @@ -566,24 +569,7 @@ export default class Generator { if (templateProperties.components) { templateProperties.components.value.properties.forEach((property: Node) => { - // TODO replace all the guff below with this: - // addValue(property.key.name, property.value); - - const key = property.key.name; - const value = source.slice( - property.value.start, - property.value.end - ); - - if (key !== value) { - const alias = this.alias(key); - componentDefinition.addLine( - `var ${alias} = [✂${property.value.start}-${property.value.end}✂];` - ); - this.importedComponents.set(key, alias); - } else { - this.importedComponents.set(key, key); - } + addDeclaration(property.key.name, property.value, 'components'); }); } @@ -677,15 +663,6 @@ export default class Generator { } } - // if we do need to keep it, then we need to replace `export default` - // if (defaultExport) { - // this.code.overwrite( - // defaultExport.start, - // defaultExport.declaration.start, - // `var ${this.alias('template')} = ` - // ); - // } - if (indentationLevel) { if (defaultExport) { removeIndentation(code, js.content.start, defaultExport.start, indentationLevel, indentExclusionRanges); @@ -695,11 +672,6 @@ export default class Generator { } } - if (js.content.body.length === 0) { - // if there's no need to include user code, remove it altogether - this.code.remove(js.content.start, js.content.end); - } - let a = js.content.start; while (/\s/.test(source[a])) a += 1; diff --git a/src/generators/dom/visitors/Component.ts b/src/generators/dom/visitors/Component.ts index 67f647a755..2263aab1fb 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}`; // TODO this is out of date + const expression = node.name === ':Self' ? generator.name : `%components-${node.name}`; block.builders.init.addBlock(deindent` ${statements.join('\n')} diff --git a/src/generators/server-side-rendering/index.ts b/src/generators/server-side-rendering/index.ts index 58d410fbe6..8971110d57 100644 --- a/src/generators/server-side-rendering/index.ts +++ b/src/generators/server-side-rendering/index.ts @@ -161,10 +161,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});`; })} `} diff --git a/src/generators/server-side-rendering/visitors/Component.ts b/src/generators/server-side-rendering/visitors/Component.ts index 8c527ac361..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}`; // TODO out of date + const expression = node.name === ':Self' ? generator.name : `%components-${node.name}`; bindings.forEach(binding => { block.addBinding(binding, expression); From 643688650035f84117740929a103de50e3e200d1 Mon Sep 17 00:00:00 2001 From: Rich Harris Date: Wed, 20 Sep 2017 11:51:01 -0400 Subject: [PATCH 6/7] exclude irrelevant stuff from SSR output --- src/generators/Generator.ts | 15 ++++---- src/generators/dom/index.ts | 2 +- src/generators/server-side-rendering/index.ts | 32 +--------------- .../js/samples/ssr-no-oncreate-etc/_config.js | 5 +++ .../ssr-no-oncreate-etc/expected-bundle.js | 23 ++++++++++++ .../samples/ssr-no-oncreate-etc/expected.js | 37 +++++++++++++++++++ .../js/samples/ssr-no-oncreate-etc/input.html | 23 ++++++++++++ 7 files changed, 98 insertions(+), 39 deletions(-) create mode 100644 test/js/samples/ssr-no-oncreate-etc/_config.js create mode 100644 test/js/samples/ssr-no-oncreate-etc/expected-bundle.js create mode 100644 test/js/samples/ssr-no-oncreate-etc/expected.js create mode 100644 test/js/samples/ssr-no-oncreate-etc/input.html diff --git a/src/generators/Generator.ts b/src/generators/Generator.ts index 1255773897..5b7ec8f071 100644 --- a/src/generators/Generator.ts +++ b/src/generators/Generator.ts @@ -116,7 +116,8 @@ export default class Generator { source: string, name: string, stylesheet: Stylesheet, - options: CompileOptions + options: CompileOptions, + dom: boolean ) { this.ast = clone(parsed); @@ -154,7 +155,7 @@ export default class Generator { this.aliases = new Map(); this.usedNames = new Set(); - this.parseJs(); + this.parseJs(dom); this.name = this.alias(name); if (options.customElement === true) { @@ -452,7 +453,7 @@ export default class Generator { }; } - parseJs() { + parseJs(dom: boolean) { const { code, source } = this; const { js } = this.parsed; @@ -613,7 +614,7 @@ export default class Generator { addDeclaration('data', templateProperties.data.value); } - if (templateProperties.events) { + if (templateProperties.events && dom) { templateProperties.events.value.properties.forEach((property: Node) => { addDeclaration(property.key.name, property.value, 'events'); }); @@ -625,7 +626,7 @@ export default class Generator { }); } - if (templateProperties.methods) { + if (templateProperties.methods && dom) { addDeclaration('methods', templateProperties.methods.value); } @@ -635,12 +636,12 @@ export default class Generator { } if (templateProperties.onrender) templateProperties.oncreate = templateProperties.onrender; // remove after v2 - if (templateProperties.oncreate) { + if (templateProperties.oncreate && dom) { addDeclaration('oncreate', templateProperties.oncreate.value); } if (templateProperties.onteardown) templateProperties.ondestroy = templateProperties.onteardown; // remove after v2 - if (templateProperties.ondestroy) { + if (templateProperties.ondestroy && dom) { addDeclaration('ondestroy', templateProperties.ondestroy.value); } diff --git a/src/generators/dom/index.ts b/src/generators/dom/index.ts index e9fe9805c7..cc7997455d 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(); diff --git a/src/generators/server-side-rendering/index.ts b/src/generators/server-side-rendering/index.ts index 8971110d57..5e6bd25d8d 100644 --- a/src/generators/server-side-rendering/index.ts +++ b/src/generators/server-side-rendering/index.ts @@ -21,44 +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); - - // TODO how to exclude non-SSR-able stuff? - - // 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) { 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 From dc3785c1e21a5306ea5e45e03494106eb7acd733 Mon Sep 17 00:00:00 2001 From: Rich Harris Date: Wed, 20 Sep 2017 12:00:42 -0400 Subject: [PATCH 7/7] fix escaping of %-prefixed names --- mocha.opts | 1 - src/generators/dom/index.ts | 23 +++++++++++--------- src/utils/stringify.ts | 2 +- test/runtime/samples/escaped-text/_config.js | 6 ++++- test/runtime/samples/escaped-text/main.html | 4 +++- 5 files changed, 22 insertions(+), 14 deletions(-) diff --git a/mocha.opts b/mocha.opts index af6b17a845..427b029758 100644 --- a/mocha.opts +++ b/mocha.opts @@ -1,2 +1 @@ ---bail test/test.js \ No newline at end of file diff --git a/src/generators/dom/index.ts b/src/generators/dom/index.ts index cc7997455d..2ee1a2eef5 100644 --- a/src/generators/dom/index.ts +++ b/src/generators/dom/index.ts @@ -344,18 +344,21 @@ export default function dom( let result = builder .toString() - .replace(/%(\w+(?:-\w+)?)/gm, (match: string, name: string) => { - return generator.templateVars.get(name); - }) - .replace(/(@+)(\w*)/g, (match: string, sigil: string, name: string) => { - if (sigil !== '@') return sigil.slice(1) + name; - - if (name in shared) { - if (options.dev && `${name}Dev` in shared) name = `${name}Dev`; - usedHelpers.add(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 (sigil === '%') { + return generator.templateVars.get(name); } - return generator.alias(name); + return sigil.slice(1) + name; }); let helpers; 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/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