From a81362638fd167397f79213db10c426b26ab5da5 Mon Sep 17 00:00:00 2001 From: Taylor Zane Glaeser Date: Tue, 7 Mar 2017 11:42:57 -0600 Subject: [PATCH 01/14] Implemented a `create` method that outputs a Svelte component constructor. Added a format called `eval` that returns a string that when called by `eval` will return a Svelte component constructor. Wrote tests for the `create` method, and the `eval` format. All these tests pass. --- src/generators/shared/utils/getIntro.js | 5 +++ src/generators/shared/utils/getOutro.js | 5 +++ src/index.js | 24 ++++++++++++++ test/create.js | 40 +++++++++++++++++++++++ test/formats.js | 42 +++++++++++++++++++++++++ 5 files changed, 116 insertions(+) create mode 100644 test/create.js diff --git a/src/generators/shared/utils/getIntro.js b/src/generators/shared/utils/getIntro.js index 3a11c1d2370a..c0fa4b4a6986 100644 --- a/src/generators/shared/utils/getIntro.js +++ b/src/generators/shared/utils/getIntro.js @@ -7,6 +7,7 @@ export default function getIntro ( format, options, imports ) { if ( format === 'cjs' ) return getCjsIntro( options, imports ); if ( format === 'iife' ) return getIifeIntro( options, imports ); if ( format === 'umd' ) return getUmdIntro( options, imports ); + if ( format === 'eval' ) return getEvalIntro( options, imports ); throw new Error( `Not implemented: ${format}` ); } @@ -60,6 +61,10 @@ function getUmdIntro ( options, imports ) { }(this, (function (${paramString( imports )}) { 'use strict';` + '\n\n'; } +function getEvalIntro ( options, imports ) { + return `(function (${paramString( imports )}) { 'use strict';\n\n`; +} + function paramString ( imports ) { return imports.length ? ` ${imports.map( dep => dep.name ).join( ', ' )} ` : ''; } diff --git a/src/generators/shared/utils/getOutro.js b/src/generators/shared/utils/getOutro.js index 283151267b2c..7f7c9354cf86 100644 --- a/src/generators/shared/utils/getOutro.js +++ b/src/generators/shared/utils/getOutro.js @@ -18,6 +18,11 @@ export default function getOutro ( format, name, options, imports ) { return `return ${name};\n\n}(${globals.join( ', ' )}));`; } + if ( format === 'eval' ) { + const globals = getGlobals( imports, options ); + return `return ${name};\n\n}(${globals.join( ', ' )}));`; + } + if ( format === 'umd' ) { return `return ${name};\n\n})));`; } diff --git a/src/index.js b/src/index.js index b3d85116e391..ceb94442f4dd 100644 --- a/src/index.js +++ b/src/index.js @@ -46,4 +46,28 @@ export function compile ( source, _options ) { return compiler( parsed, source, options, names ); } +export function create ( source, _options = {} ) { + _options.format = 'eval'; + + const compiled = compile( source, _options ); + + if ( !compiled || !compiled.code ) { + return; + } + + let result; + try { + result = ( 1, eval )( compiled.code ); + } catch ( err ) { + if ( _options.onerror ) { + _options.onerror( err ); + } else { + throw err; + } + return; + } + + return result; +} + export { parse, validate, version as VERSION }; diff --git a/test/create.js b/test/create.js new file mode 100644 index 000000000000..153192de27d3 --- /dev/null +++ b/test/create.js @@ -0,0 +1,40 @@ +import deindent from '../src/utils/deindent.js'; +import assert from 'assert'; +import { svelte } from './helpers.js'; + +describe( 'create', () => { + it( 'should return a component constructor', () => { + const source = deindent` +
{{prop}}
+ `; + + const component = svelte.create( source ); + assert( component instanceof Function ); + }); + + it( 'should throw error when source is invalid ', done => { + const source = deindent` +
{{prop}
+ `; + + const component = svelte.create( source, { + onerror: () => { + done(); + } + }); + + assert.equal( component, undefined ); + }); + + it( 'should return undefined when source is invalid ', () => { + const source = deindent` +
{{prop}
+ `; + + const component = svelte.create( source, { + onerror: () => {} + }); + + assert.equal( component, undefined ); + }); +}); diff --git a/test/formats.js b/test/formats.js index 9b803cadcc0c..16ad77ce556e 100644 --- a/test/formats.js +++ b/test/formats.js @@ -63,6 +63,21 @@ function testIife ( code, name, globals, html ) { }); } +function testEval ( code, name, globals, html ) { + const fn = new Function( Object.keys( globals ), `return ${code};` ); + + return env().then( window => { + const SvelteComponent = fn( ...Object.keys( globals ).map( key => globals[ key ] ) ); + + const main = window.document.body.querySelector( 'main' ); + const component = new SvelteComponent({ target: main }); + + assert.htmlEqual( main.innerHTML, html ); + + component.destroy(); + }); +} + describe( 'formats', () => { before( setupHtmlEqual ); @@ -175,4 +190,31 @@ describe( 'formats', () => { .then( () => testIife( code, 'Foo', { answer: 42 }, `
42
` ) ); }); }); + + describe( 'eval', () => { + it( 'generates a self-executing script that returns the component on eval', () => { + const source = deindent` +
{{answer}}
+ + + `; + + const { code } = svelte.compile( source, { + format: 'eval', + globals: { + answer: 'answer' + } + }); + + return testEval( code, 'Foo', { answer: 42 }, `
42
` ); + }); + }); }); From 57caf86c464824f86eaeb3787070979ac3cd2b40 Mon Sep 17 00:00:00 2001 From: Conduitry Date: Sun, 5 Mar 2017 20:55:35 -0500 Subject: [PATCH 02/14] deconflict helpers when not using shared helpers --- src/generators/dom/index.js | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/src/generators/dom/index.js b/src/generators/dom/index.js index 5ac11c9414c8..441a8d9a47f7 100644 --- a/src/generators/dom/index.js +++ b/src/generators/dom/index.js @@ -410,11 +410,13 @@ export default function dom ( parsed, source, options, names ) { `import { ${names.join( ', ' )} } from ${JSON.stringify( sharedPath )}` ); } else { - builders.main.addBlock( `var ${generator.aliases.dispatchObservers} = ${shared.dispatchObservers.toString()}` ); - Object.keys( generator.uses ).forEach( key => { const fn = shared[ key ]; // eslint-disable-line import/namespace - builders.main.addBlock( fn.toString() ); + if ( key !== generator.aliases[ key ] ) { + builders.main.addBlock( `var ${generator.aliases[ key ]} = ${fn.toString()}}` ); + } else { + builders.main.addBlock( fn.toString() ); + } }); } From b5e9a2f99eed90321c14e91c9c965ec72c83679e Mon Sep 17 00:00:00 2001 From: Conduitry Date: Mon, 6 Mar 2017 12:49:43 -0500 Subject: [PATCH 03/14] typecheck argument in _set when in dev mode --- src/generators/dom/index.js | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/src/generators/dom/index.js b/src/generators/dom/index.js index 441a8d9a47f7..4a0b096d1529 100644 --- a/src/generators/dom/index.js +++ b/src/generators/dom/index.js @@ -211,6 +211,14 @@ export default function dom ( parsed, source, options, names ) { _set: new CodeBuilder() }; + if ( options.dev ) { + builders._set.addBlock ( deindent` + if ( typeof newState !== 'object' ) { + throw new Error( 'Component .set was called without an object of data key-values to update.' ); + } + `); + } + builders._set.addLine( 'var oldState = this._state;' ); builders._set.addLine( 'this._state = Object.assign( {}, oldState, newState );' ); From f5fefeb6527831fc7aa1bcac900cd786900bf2c2 Mon Sep 17 00:00:00 2001 From: Taylor Zane Glaeser Date: Mon, 6 Mar 2017 12:33:44 -0600 Subject: [PATCH 04/14] Resolved an issue with raw Svelte JS in a script block breaking an HTML document. --- src/parse/read/script.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/parse/read/script.js b/src/parse/read/script.js index 9ebff7f620f6..09e1ba917497 100644 --- a/src/parse/read/script.js +++ b/src/parse/read/script.js @@ -17,7 +17,7 @@ export default function readScript ( parser, start, attributes ) { break; } - if ( parser.eat( '' ) ) { + if ( parser.eat( '<\/script>' ) ) { break; } } From 6cfb4d4470812bf913ef81848e7c74dc3257aeff Mon Sep 17 00:00:00 2001 From: Rich Harris Date: Mon, 6 Mar 2017 16:17:31 -0500 Subject: [PATCH 05/14] -> v1.10.1 --- CHANGELOG.md | 5 +++++ package.json | 2 +- 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 6e8e1658629c..46d079d1cd5a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,10 @@ # Svelte changelog +## 1.10.1 + +* typecheck argument in _set when in dev mode ([#342](https://github.com/sveltejs/svelte/issues/342)) +* Prevent duplicate helpers in non-shared mode ([#337](https://github.com/sveltejs/svelte/issues/337)) + ## 1.10.0 * Component self-references with `<:Self/>` ([#51](https://github.com/sveltejs/svelte/issues/51)) diff --git a/package.json b/package.json index 3bbb8354e91d..b851fdec9ddc 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "svelte", - "version": "1.10.0", + "version": "1.10.1", "description": "The magical disappearing UI framework", "main": "compiler/svelte.js", "files": [ From 1d891de648294cb6f3d06f0513111240042468b5 Mon Sep 17 00:00:00 2001 From: Conduitry Date: Mon, 6 Mar 2017 17:01:47 -0500 Subject: [PATCH 06/14] accept DOCTYPE element in any case --- src/generators/server-side-rendering/visitors/Element.js | 4 ++-- src/parse/state/tag.js | 6 +++--- src/utils/isVoidElementName.js | 5 +++++ src/utils/voidElementNames.js | 1 - 4 files changed, 10 insertions(+), 6 deletions(-) create mode 100644 src/utils/isVoidElementName.js delete mode 100644 src/utils/voidElementNames.js diff --git a/src/generators/server-side-rendering/visitors/Element.js b/src/generators/server-side-rendering/visitors/Element.js index c5fd7abf31bd..4b750bf7b1fa 100644 --- a/src/generators/server-side-rendering/visitors/Element.js +++ b/src/generators/server-side-rendering/visitors/Element.js @@ -1,5 +1,5 @@ import Component from './Component.js'; -import voidElementNames from '../../../utils/voidElementNames.js'; +import isVoidElementName from '../../../utils/isVoidElementName.js'; export default { enter ( generator, node ) { @@ -44,7 +44,7 @@ export default { return; } - if ( !voidElementNames.test( node.name ) ) { + if ( !isVoidElementName( node.name ) ) { generator.append( `` ); } } diff --git a/src/parse/state/tag.js b/src/parse/state/tag.js index 09eda8da9cc1..b399371fac7e 100644 --- a/src/parse/state/tag.js +++ b/src/parse/state/tag.js @@ -4,7 +4,7 @@ import readStyle from '../read/style.js'; import { readEventHandlerDirective, readBindingDirective } from '../read/directives.js'; import { trimStart, trimEnd } from '../utils/trim.js'; import { decodeCharacterReferences } from '../utils/html.js'; -import voidElementNames from '../../utils/voidElementNames.js'; +import isVoidElementName from '../../utils/isVoidElementName.js'; const validTagName = /^\!?[a-zA-Z]{1,}:?[a-zA-Z0-9\-]*/; const invalidUnquotedAttributeCharacters = /[\s"'=<>\/`]/; @@ -84,7 +84,7 @@ export default function tag ( parser ) { parser.allowWhitespace(); if ( isClosingTag ) { - if ( voidElementNames.test( name ) ) { + if ( isVoidElementName( name ) ) { parser.error( `<${name}> is a void element and cannot have children, or a closing tag`, start ); } @@ -155,7 +155,7 @@ export default function tag ( parser ) { parser.current().children.push( element ); - const selfClosing = parser.eat( '/' ) || voidElementNames.test( name ); + const selfClosing = parser.eat( '/' ) || isVoidElementName( name ); parser.eat( '>', true ); diff --git a/src/utils/isVoidElementName.js b/src/utils/isVoidElementName.js new file mode 100644 index 000000000000..8af4f34db8fc --- /dev/null +++ b/src/utils/isVoidElementName.js @@ -0,0 +1,5 @@ +const voidElementNames = /^(?:area|base|br|col|command|embed|hr|img|input|keygen|link|meta|param|source|track|wbr)$/; + +export default function isVoidElementName ( name ) { + return voidElementNames.test( name ) || name.toLowerCase() === '!doctype'; +} diff --git a/src/utils/voidElementNames.js b/src/utils/voidElementNames.js deleted file mode 100644 index 46306ac8c31f..000000000000 --- a/src/utils/voidElementNames.js +++ /dev/null @@ -1 +0,0 @@ -export default /^(?:area|base|br|col|command|\!doctype|embed|hr|img|input|keygen|link|meta|param|source|track|wbr)$/; From b61f81e1316f1c7e15f2a07e35a294d3c4a72156 Mon Sep 17 00:00:00 2001 From: Conduitry Date: Mon, 6 Mar 2017 17:21:33 -0500 Subject: [PATCH 07/14] only process special tags (script, style) when they are top-level --- src/parse/state/tag.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/parse/state/tag.js b/src/parse/state/tag.js index b399371fac7e..c6007318af13 100644 --- a/src/parse/state/tag.js +++ b/src/parse/state/tag.js @@ -130,13 +130,13 @@ export default function tag ( parser ) { parser.allowWhitespace(); - // special cases – '` string occurence breaking pages ([#349](https://github.com/sveltejs/svelte/pull/349)) +* Allow reference to whitelisted globals without properties ([#333](https://github.com/sveltejs/svelte/pull/333)) +* Don't remove ` ` incorrectly ([#348](https://github.com/sveltejs/svelte/issues/348)) +* `let` -> `var` in `addCss` block ([#351](https://github.com/sveltejs/svelte/pull/351)) + ## 1.10.2 * Accept any case for doctype declarations ([#336](https://github.com/sveltejs/svelte/issues/336)) diff --git a/package.json b/package.json index 588066accbd0..49c3d803c7e9 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "svelte", - "version": "1.10.2", + "version": "1.10.3", "description": "The magical disappearing UI framework", "main": "compiler/svelte.js", "files": [