Merge branch 'master' into builder

pull/183/head
Rich-Harris 9 years ago
commit 0551d2646a

@ -0,0 +1,19 @@
<!--
Thanks for raising an issue! To help us help you, if you've found a bug please consider the following:
* If you can demonstrate the bug using https://svelte.technology/repl, please do.
* If that's not possible, we recommend creating a small repo that illustrates the problem.
* Make sure you include information about the browser, and which version of Svelte you're using
Reproductions should be small, self-contained, correct examples http://sscce.org.
Occasionally, this won't be possible, and that's fine we still appreciate you raising the issue. But please understand that Svelte is run by unpaid volunteers in their free time, and we will prioritise bugs that follow these instructions.
If you have a stack trace to include, we recommend putting inside a `<details>` block for the sake of the thread's readability:
<details>
<summary>Stack trace</summary>
Stack trace goes here...
</details>
-->

@ -0,0 +1,7 @@
<!--
Thank you for creating a pull request. Before submitting, please note the following:
* If your pull request implements a new feature, please raise an issue to discuss it before sending code. In many cases features are absent for a reason.
* This message body should clearly illustrate what problems it solves. If there are related issues, remember to reference them.
* Ideally, include a test that fails without this PR but passes with it. PRs will only be merged once they pass CI. (Remember to `npm run lint`!)
-->

@ -1,5 +1,17 @@
# Svelte changelog # Svelte changelog
## 1.2.2
* Omit directives in server-side rendering ([#163](https://github.com/sveltejs/svelte/issues/167))
* Handle comments in SSR ([#165](https://github.com/sveltejs/svelte/issues/165))
* Support calling methods of `event`/`this` in event handlers ([#162](https://github.com/sveltejs/svelte/issues/162))
* Remove `mount` from public API ([#150](https://github.com/sveltejs/svelte/issues/150))
## 1.2.1
* Server-side rendering is available as a compiler option (`generate: 'ssr'`) ([#159](https://github.com/sveltejs/svelte/pull/159))
* Allow call expressions where function is not in `helpers` ([#163](https://github.com/sveltejs/svelte/issues/163))
## 1.2.0 ## 1.2.0
* Server-side rendering of HTML ([#148](https://github.com/sveltejs/svelte/pull/148)) and CSS ([#154](https://github.com/sveltejs/svelte/pull/154)) * Server-side rendering of HTML ([#148](https://github.com/sveltejs/svelte/pull/148)) and CSS ([#154](https://github.com/sveltejs/svelte/pull/154))

@ -56,6 +56,7 @@ const { code, map } = svelte.compile( source, {
* [charpeni/svelte-example](https://github.com/charpeni/svelte-example) - Some Svelte examples with configured Rollup, Babel, ESLint, directives, Two-Way binding, and nested components * [charpeni/svelte-example](https://github.com/charpeni/svelte-example) - Some Svelte examples with configured Rollup, Babel, ESLint, directives, Two-Way binding, and nested components
* [EmilTholin/svelte-test](https://github.com/EmilTholin/svelte-test) * [EmilTholin/svelte-test](https://github.com/EmilTholin/svelte-test)
* [lukechinworth/codenames](https://github.com/lukechinworth/codenames/tree/svelte)  example integration with Redux
## License ## License

@ -1,6 +1,6 @@
{ {
"name": "svelte", "name": "svelte",
"version": "1.2.0", "version": "1.2.2",
"description": "The magical disappearing UI framework", "description": "The magical disappearing UI framework",
"main": "compiler/svelte.js", "main": "compiler/svelte.js",
"files": [ "files": [

@ -12,5 +12,8 @@ export default {
commonjs() commonjs()
], ],
external: [ 'magic-string' ], external: [ 'magic-string' ],
globals: {
'magic-string': 'MagicString'
},
sourceMap: true sourceMap: true
}; };

@ -12,7 +12,7 @@ export default {
nodeResolve({ jsnext: true, module: true }), nodeResolve({ jsnext: true, module: true }),
commonjs() commonjs()
], ],
external: [ path.resolve( 'src/index.js' ), 'magic-string' ], external: [ path.resolve( 'src/index.js' ), 'fs', 'magic-string' ],
paths: { paths: {
[ path.resolve( 'src/index.js' ) ]: '../compiler/svelte.js' [ path.resolve( 'src/index.js' ) ]: '../compiler/svelte.js'
}, },

@ -5,6 +5,7 @@ import deindent from '../utils/deindent.js';
import isReference from '../utils/isReference.js'; import isReference from '../utils/isReference.js';
import counter from './utils/counter.js'; import counter from './utils/counter.js';
import flattenReference from '../utils/flattenReference.js'; import flattenReference from '../utils/flattenReference.js';
import namespaces from '../utils/namespaces.js';
import getIntro from './utils/getIntro.js'; import getIntro from './utils/getIntro.js';
import getOutro from './utils/getOutro.js'; import getOutro from './utils/getOutro.js';
import visitors from './visitors/index.js'; import visitors from './visitors/index.js';
@ -138,8 +139,8 @@ export default function generate ( parsed, source, options, names ) {
if ( isReference( node, parent ) ) { if ( isReference( node, parent ) ) {
const { name } = flattenReference( node ); const { name } = flattenReference( node );
if ( parent && parent.type === 'CallExpression' && node === parent.callee ) { if ( parent && parent.type === 'CallExpression' && node === parent.callee && generator.helpers[ name ] ) {
if ( generator.helpers[ name ] ) generator.code.prependRight( node.start, `template.helpers.` ); generator.code.prependRight( node.start, `template.helpers.` );
return; return;
} }
@ -278,9 +279,17 @@ export default function generate ( parsed, source, options, names ) {
}); });
} }
let namespace = null;
if ( templateProperties.namespace ) {
const ns = templateProperties.namespace.value;
namespace = namespaces[ ns ] || ns;
// TODO remove the namespace property from the generated code, it's unused past this point
}
generator.push({ generator.push({
name: 'renderMainFragment', name: 'renderMainFragment',
namespace: null, namespace,
target: 'target', target: 'target',
elementDepth: 0, elementDepth: 0,
localElementDepth: 0, localElementDepth: 0,
@ -417,7 +426,7 @@ export default function generate ( parsed, source, options, names ) {
builders.init.addBlock( deindent` builders.init.addBlock( deindent`
this.__bindings = []; this.__bindings = [];
var mainFragment = renderMainFragment( state, this ); var mainFragment = renderMainFragment( state, this );
if ( options.target ) this.mount( options.target ); if ( options.target ) this._mount( options.target );
while ( this.__bindings.length ) this.__bindings.pop()(); while ( this.__bindings.length ) this.__bindings.pop()();
` ); ` );
@ -425,7 +434,7 @@ export default function generate ( parsed, source, options, names ) {
} else { } else {
builders.init.addBlock( deindent` builders.init.addBlock( deindent`
var mainFragment = renderMainFragment( state, this ); var mainFragment = renderMainFragment( state, this );
if ( options.target ) this.mount( options.target ); if ( options.target ) this._mount( options.target );
` ); ` );
} }
@ -507,7 +516,7 @@ export default function generate ( parsed, source, options, names ) {
${builders.set} ${builders.set}
}; };
this.mount = function mount ( target, anchor ) { this._mount = function mount ( target, anchor ) {
mainFragment.mount( target, anchor ); mainFragment.mount( target, anchor );
} }
@ -580,8 +589,7 @@ export default function generate ( parsed, source, options, names ) {
const intro = getIntro( format, options, imports ); const intro = getIntro( format, options, imports );
if ( intro ) addString( intro ); if ( intro ) addString( intro );
// a filename is necessary for sourcemap generation const { filename } = options;
const filename = options.filename || 'SvelteComponent.html';
parts.forEach( str => { parts.forEach( str => {
const chunk = str.replace( pattern, '' ); const chunk = str.replace( pattern, '' );

@ -80,7 +80,7 @@ export default {
` ); ` );
if ( isToplevel ) { if ( isToplevel ) {
local.mount.unshift( `${name}.mount( target, anchor );` ); local.mount.unshift( `${name}._mount( target, anchor );` );
} }
if ( local.dynamicAttributes.length ) { if ( local.dynamicAttributes.length ) {

@ -1,6 +1,7 @@
import attributeLookup from './lookup.js'; import attributeLookup from './lookup.js';
import createBinding from './binding/index.js'; import createBinding from './binding/index.js';
import deindent from '../../../utils/deindent.js'; import deindent from '../../../utils/deindent.js';
import flattenReference from '../../../utils/flattenReference.js';
export default function addElementAttributes ( generator, node, local ) { export default function addElementAttributes ( generator, node, local ) {
node.attributes.forEach( attribute => { node.attributes.forEach( attribute => {
@ -114,7 +115,12 @@ export default function addElementAttributes ( generator, node, local ) {
else if ( attribute.type === 'EventHandler' ) { else if ( attribute.type === 'EventHandler' ) {
// TODO verify that it's a valid callee (i.e. built-in or declared method) // TODO verify that it's a valid callee (i.e. built-in or declared method)
generator.addSourcemapLocations( attribute.expression ); generator.addSourcemapLocations( attribute.expression );
generator.code.prependRight( attribute.expression.start, 'component.' );
const flattened = flattenReference( attribute.expression.callee );
if ( flattened.name !== 'event' && flattened.name !== 'this' ) {
// allow event.stopPropagation(), this.select() etc
generator.code.prependRight( attribute.expression.start, 'component.' );
}
const usedContexts = new Set(); const usedContexts = new Set();
attribute.expression.arguments.forEach( arg => { attribute.expression.arguments.forEach( arg => {

@ -1,23 +1,36 @@
import parse from './parse/index.js'; import parse from './parse/index.js';
import validate from './validate/index.js'; import validate from './validate/index.js';
import generate from './generate/index.js'; import generate from './generate/index.js';
import generateSSR from './server-side-rendering/compile.js';
export function compile ( source, options = {} ) { function normalizeOptions ( options ) {
const parsed = parse( source, options ); return Object.assign( {
generate: 'dom',
// a filename is necessary for sourcemap generation
filename: 'SvelteComponent.html',
if ( !options.onwarn ) { onwarn: warning => {
options.onwarn = warning => {
if ( warning.loc ) { if ( warning.loc ) {
console.warn( `(${warning.loc.line}:${warning.loc.column}) ${warning.message}` ); // eslint-disable-line no-console console.warn( `(${warning.loc.line}:${warning.loc.column}) ${warning.message}` ); // eslint-disable-line no-console
} else { } else {
console.warn( warning.message ); // eslint-disable-line no-console console.warn( warning.message ); // eslint-disable-line no-console
} }
}; }
} }, options );
}
export function compile ( source, _options ) {
const options = normalizeOptions( _options );
const parsed = parse( source, options );
const { names } = validate( parsed, source, options ); const { names } = validate( parsed, source, options );
return generate( parsed, source, options, names ); const compiler = options.generate === 'ssr'
? generateSSR
: generate;
return compiler( parsed, source, options, names );
} }
export { parse, validate }; export { parse, validate };

@ -1,4 +1,3 @@
import { parse, validate } from '../index.js';
import { walk } from 'estree-walker'; import { walk } from 'estree-walker';
import deindent from '../utils/deindent.js'; import deindent from '../utils/deindent.js';
import isReference from '../utils/isReference.js'; import isReference from '../utils/isReference.js';
@ -8,10 +7,7 @@ import processCss from '../generate/css/process.js';
const voidElementNames = /^(?:area|base|br|col|command|doctype|embed|hr|img|input|keygen|link|meta|param|source|track|wbr)$/i; const voidElementNames = /^(?:area|base|br|col|command|doctype|embed|hr|img|input|keygen|link|meta|param|source|track|wbr)$/i;
export default function compile ( source, { filename }) { export default function compile ( parsed, source, { filename }) {
const parsed = parse( source, {} );
validate( parsed, source, {} );
const code = new MagicString( source ); const code = new MagicString( source );
const templateProperties = {}; const templateProperties = {};
@ -117,6 +113,10 @@ export default function compile ( source, { filename }) {
let elementDepth = 0; let elementDepth = 0;
const stringifiers = { const stringifiers = {
Comment () {
return '';
},
Component ( node ) { Component ( node ) {
const props = node.attributes.map( attribute => { const props = node.attributes.map( attribute => {
let value; let value;
@ -174,6 +174,8 @@ export default function compile ( source, { filename }) {
let element = `<${node.name}`; let element = `<${node.name}`;
node.attributes.forEach( attribute => { node.attributes.forEach( attribute => {
if ( attribute.type !== 'Attribute' ) return;
let str = ` ${attribute.name}`; let str = ` ${attribute.name}`;
if ( attribute.value !== true ) { if ( attribute.value !== true ) {

@ -1,7 +1,10 @@
import * as fs from 'fs'; import * as fs from 'fs';
import compile from './compile.js'; import { compile } from '../index.js';
require.extensions[ '.html' ] = function ( module, filename ) { require.extensions[ '.html' ] = function ( module, filename ) {
const { code } = compile( fs.readFileSync( filename, 'utf-8' ), { filename }); const { code } = compile( fs.readFileSync( filename, 'utf-8' ), {
filename,
generate: 'ssr'
});
return module._compile( code, filename ); return module._compile( code, filename );
}; };

@ -7,10 +7,10 @@ export default function flatten ( node ) {
node = node.object; node = node.object;
} }
if ( node.type !== 'Identifier' ) return null; const name = node.type === 'Identifier' ? node.name : node.type === 'ThisExpression' ? 'this' : null;
const name = node.name; if ( !name ) return null;
parts.unshift( name );
parts.unshift( name );
return { name, keypath: parts.join( '.' ) }; return { name, keypath: parts.join( '.' ) };
} }

@ -0,0 +1,8 @@
export const html = 'http://www.w3.org/1999/xhtml';
export const mathml = 'http://www.w3.org/1998/Math/MathML';
export const svg = 'http://www.w3.org/2000/svg';
export const xlink = 'http://www.w3.org/1999/xlink';
export const xml = 'http://www.w3.org/XML/1998/namespace';
export const xmlns = 'http://www.w3.org/2000/xmlns';
export default { html, mathml, svg, xlink, xml, xmlns };

@ -1,13 +1,31 @@
import * as namespaces from '../../utils/namespaces.js';
const svg = /^(?:altGlyph|altGlyphDef|altGlyphItem|animate|animateColor|animateMotion|animateTransform|circle|clipPath|color-profile|cursor|defs|desc|discard|ellipse|feBlend|feColorMatrix|feComponentTransfer|feComposite|feConvolveMatrix|feDiffuseLighting|feDisplacementMap|feDistantLight|feDropShadow|feFlood|feFuncA|feFuncB|feFuncG|feFuncR|feGaussianBlur|feImage|feMerge|feMergeNode|feMorphology|feOffset|fePointLight|feSpecularLighting|feSpotLight|feTile|feTurbulence|filter|font|font-face|font-face-format|font-face-name|font-face-src|font-face-uri|foreignObject|g|glyph|glyphRef|hatch|hatchpath|hkern|image|line|linearGradient|marker|mask|mesh|meshgradient|meshpatch|meshrow|metadata|missing-glyph|mpath|path|pattern|polygon|polyline|radialGradient|rect|set|solidcolor|stop|switch|symbol|text|textPath|title|tref|tspan|unknown|use|view|vkern)$/;
export default function validateHtml ( validator, html ) { export default function validateHtml ( validator, html ) {
let elementDepth = 0;
function visit ( node ) { function visit ( node ) {
if ( node.type === 'EachBlock' ) { if ( node.type === 'EachBlock' ) {
if ( !~validator.names.indexOf( node.context ) ) validator.names.push( node.context ); if ( !~validator.names.indexOf( node.context ) ) validator.names.push( node.context );
if ( node.index && !~validator.names.indexOf( node.index ) ) validator.names.push( node.index ); if ( node.index && !~validator.names.indexOf( node.index ) ) validator.names.push( node.index );
} }
if ( node.type === 'Element' ) {
if ( elementDepth === 0 && validator.namespace !== namespaces.svg && svg.test( node.name ) ) {
validator.warn( `<${node.name}> is an SVG element did you forget to add { namespace: 'svg' } ?`, node.start );
}
elementDepth += 1;
}
if ( node.children ) { if ( node.children ) {
node.children.forEach( visit ); node.children.forEach( visit );
} }
if ( node.type === 'Element' ) {
elementDepth -= 1;
}
} }
html.children.forEach( visit ); html.children.forEach( visit );

@ -40,17 +40,19 @@ export default function validate ( parsed, source, options ) {
templateProperties: {}, templateProperties: {},
names: [] names: [],
};
if ( parsed.html ) { namespace: null
validateHtml( validator, parsed.html ); };
}
if ( parsed.js ) { if ( parsed.js ) {
validateJs( validator, parsed.js ); validateJs( validator, parsed.js );
} }
if ( parsed.html ) {
validateHtml( validator, parsed.html );
}
return { return {
names: validator.names names: validator.names
}; };

@ -2,6 +2,7 @@ import propValidators from './propValidators/index.js';
import FuzzySet from './utils/FuzzySet.js'; import FuzzySet from './utils/FuzzySet.js';
import checkForDupes from './utils/checkForDupes.js'; import checkForDupes from './utils/checkForDupes.js';
import checkForComputedKeys from './utils/checkForComputedKeys.js'; import checkForComputedKeys from './utils/checkForComputedKeys.js';
import namespaces from '../../utils/namespaces.js';
const validPropList = Object.keys( propValidators ); const validPropList = Object.keys( propValidators );
@ -29,7 +30,7 @@ export default function validateJs ( validator, js ) {
checkForDupes( validator, validator.defaultExport.declaration.properties ); checkForDupes( validator, validator.defaultExport.declaration.properties );
validator.defaultExport.declaration.properties.forEach( prop => { validator.defaultExport.declaration.properties.forEach( prop => {
validator.templateProperties[ prop.key.value ] = prop; validator.templateProperties[ prop.key.name ] = prop;
}); });
validator.defaultExport.declaration.properties.forEach( prop => { validator.defaultExport.declaration.properties.forEach( prop => {
@ -48,5 +49,10 @@ export default function validateJs ( validator, js ) {
} }
} }
}); });
if ( validator.templateProperties.namespace ) {
const ns = validator.templateProperties.namespace.value.value;
validator.namespace = namespaces[ ns ] || ns;
}
} }
} }

@ -6,6 +6,7 @@ import helpers from './helpers.js';
import methods from './methods.js'; import methods from './methods.js';
import components from './components.js'; import components from './components.js';
import events from './events.js'; import events from './events.js';
import namespace from './namespace.js';
export default { export default {
data, data,
@ -15,5 +16,6 @@ export default {
helpers, helpers,
methods, methods,
components, components,
events events,
namespace
}; };

@ -0,0 +1,5 @@
export default function namespace ( validator, prop ) {
if ( prop.value.type !== 'Literal' || typeof prop.value.value !== 'string' ) {
validator.error( `The 'namespace' property must be a string literal representing a valid namespace`, prop.start );
}
}

@ -0,0 +1,14 @@
export default {
html: '<p>50</p>',
test ( assert, component, target ) {
component.set({ range: [ 50, 100 ] });
assert.htmlEqual( target.innerHTML, '<p>75</p>' );
component.set({ range: [ 50, 60 ] });
assert.htmlEqual( target.innerHTML, '<p>55</p>' );
component.set({ x: 8 });
assert.htmlEqual( target.innerHTML, '<p>58</p>' );
}
};

@ -0,0 +1,20 @@
<p>{{scale(x)}}</p>
<script>
export default {
data: () => ({
x: 5,
domain: [ 0, 10 ],
range: [ 0, 100 ]
}),
computed: {
scale: ( domain, range ) => {
return num => {
const t = domain[0] + ( num - domain[0] ) / ( domain[1] - domain[0] );
return range[0] + t * ( range[1] - range[0] );
}
}
}
};
</script>

@ -0,0 +1,8 @@
export default {
html: '<h1>Hello world!</h1>',
test ( assert, component, target ) {
component.set({ name: () => 'everybody' });
assert.htmlEqual( target.innerHTML, '<h1>Hello everybody!</h1>' );
}
};

@ -0,0 +1,9 @@
<h1>Hello {{name()}}!</h1>
<script>
export default {
data: () => ({
name: () => 'world'
})
};
</script>

@ -0,0 +1,12 @@
export default {
test ( assert, component, target, window ) {
const allow = target.querySelector( '.allow-propagation' );
const stop = target.querySelector( '.stop-propagation' );
allow.dispatchEvent( new window.MouseEvent( 'click', { bubbles: true }) );
stop.dispatchEvent( new window.MouseEvent( 'click', { bubbles: true }) );
assert.equal( component.get( 'foo' ), true );
assert.equal( component.get( 'bar' ), false );
}
};

@ -0,0 +1,18 @@
<div on:click='set({ foo: true })'>
<button class='allow-propagation'>click me</button>
</div>
<div on:click='set({ bar: true })'>
<button class='stop-propagation' on:click='event.stopPropagation()'>click me</button>
</div>
<script>
export default {
data () {
return {
foo: false,
bar: false
};
}
};
</script>

@ -0,0 +1,16 @@
export default {
test ( assert, component, target, window ) {
// Click events don't focus elements in JSDOM obviously they would
// in real browsers. More realistically, you'd use this for e.g.
// this.select(), but that's harder to test than this.focus()
const wont = target.querySelector( '.wont-focus' );
const will = target.querySelector( '.will-focus' );
wont.dispatchEvent( new window.MouseEvent( 'click' ) );
assert.equal( window.document.activeElement, window.document.body );
will.dispatchEvent( new window.MouseEvent( 'click' ) );
assert.equal( window.document.activeElement, will );
}
};

@ -0,0 +1,2 @@
<input class='wont-focus'>
<input class='will-focus' on:click='this.focus()'>

@ -0,0 +1,7 @@
<rect x='{{x}}' y='{{y}}' width='{{width}}' height='{{height}}'/>
<script>
export default {
namespace: 'svg'
};
</script>

@ -1,12 +1,13 @@
export default { export default {
skip: true,
data: { data: {
x: 0, x: 0,
y: 0, y: 0,
width: 100, width: 100,
height: 100 height: 100
}, },
html: `<svg><rect x="0" y="0" width="100" height="100"></rect></svg>`, html: `<svg><rect x="0" y="0" width="100" height="100"></rect></svg>`,
test ( assert, component, target ) { test ( assert, component, target ) {
const svg = target.querySelector( 'svg' ); const svg = target.querySelector( 'svg' );
const rect = target.querySelector( 'rect' ); const rect = target.querySelector( 'rect' );

@ -1,6 +1,7 @@
<svg> <svg>
<Rect x='{{x}}' y='{{y}}' width='{{width}}' height='{{height}}'/> <Rect x='{{x}}' y='{{y}}' width='{{width}}' height='{{height}}'/>
</svg> </svg>
<script> <script>
import Rect from './Rect.html'; import Rect from './Rect.html';

Before

Width:  |  Height:  |  Size: 178 B

After

Width:  |  Height:  |  Size: 179 B

@ -0,0 +1,7 @@
<rect x='{{x}}' y='{{y}}' width='{{width}}' height='{{height}}'/>
<script>
export default {
namespace: 'http://www.w3.org/2000/svg'
};
</script>

@ -0,0 +1,21 @@
export default {
data: {
x: 0,
y: 0,
width: 100,
height: 100
},
html: `<svg><rect x="0" y="0" width="100" height="100"></rect></svg>`,
test ( assert, component, target ) {
const svg = target.querySelector( 'svg' );
const rect = target.querySelector( 'rect' );
assert.equal( svg.namespaceURI, 'http://www.w3.org/2000/svg' );
assert.equal( rect.namespaceURI, 'http://www.w3.org/2000/svg' );
component.set({ width: 150, height: 50 });
assert.equal( target.innerHTML, `<svg><rect x="0" y="0" width="150" height="50"></rect></svg>` );
}
};

@ -0,0 +1,11 @@
<svg>
<Rect x='{{x}}' y='{{y}}' width='{{width}}' height='{{height}}'/>
</svg>
<script>
import Rect from './Rect.html';
export default {
components: { Rect }
};
</script>

After

Width:  |  Height:  |  Size: 179 B

@ -0,0 +1,3 @@
<p>before</p>
<p>after</p>

@ -0,0 +1,3 @@
<p>before</p>
<!-- a comment -->
<p>after</p>

@ -0,0 +1 @@
<button on:click='set({ foo: "bar" })'>click me</button>

@ -0,0 +1,7 @@
<rect x='{{x}}' y='{{y}}' width='{{width}}' height='{{height}}'/>
<script>
export default {
namespace: 'svg'
};
</script>

@ -0,0 +1,8 @@
[{
"message": "<rect> is an SVG element did you forget to add { namespace: 'svg' } ?",
"loc": {
"line": 1,
"column": 0
},
"pos": 0
}]
Loading…
Cancel
Save