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
## 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
* 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
* [EmilTholin/svelte-test](https://github.com/EmilTholin/svelte-test)
* [lukechinworth/codenames](https://github.com/lukechinworth/codenames/tree/svelte)  example integration with Redux
## License

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

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

@ -12,7 +12,7 @@ export default {
nodeResolve({ jsnext: true, module: true }),
commonjs()
],
external: [ path.resolve( 'src/index.js' ), 'magic-string' ],
external: [ path.resolve( 'src/index.js' ), 'fs', 'magic-string' ],
paths: {
[ 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 counter from './utils/counter.js';
import flattenReference from '../utils/flattenReference.js';
import namespaces from '../utils/namespaces.js';
import getIntro from './utils/getIntro.js';
import getOutro from './utils/getOutro.js';
import visitors from './visitors/index.js';
@ -138,8 +139,8 @@ export default function generate ( parsed, source, options, names ) {
if ( isReference( node, parent ) ) {
const { name } = flattenReference( node );
if ( parent && parent.type === 'CallExpression' && node === parent.callee ) {
if ( generator.helpers[ name ] ) generator.code.prependRight( node.start, `template.helpers.` );
if ( parent && parent.type === 'CallExpression' && node === parent.callee && generator.helpers[ name ] ) {
generator.code.prependRight( node.start, `template.helpers.` );
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({
name: 'renderMainFragment',
namespace: null,
namespace,
target: 'target',
elementDepth: 0,
localElementDepth: 0,
@ -417,7 +426,7 @@ export default function generate ( parsed, source, options, names ) {
builders.init.addBlock( deindent`
this.__bindings = [];
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()();
` );
@ -425,7 +434,7 @@ export default function generate ( parsed, source, options, names ) {
} else {
builders.init.addBlock( deindent`
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}
};
this.mount = function mount ( target, anchor ) {
this._mount = function 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 );
if ( intro ) addString( intro );
// a filename is necessary for sourcemap generation
const filename = options.filename || 'SvelteComponent.html';
const { filename } = options;
parts.forEach( str => {
const chunk = str.replace( pattern, '' );

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

@ -1,6 +1,7 @@
import attributeLookup from './lookup.js';
import createBinding from './binding/index.js';
import deindent from '../../../utils/deindent.js';
import flattenReference from '../../../utils/flattenReference.js';
export default function addElementAttributes ( generator, node, local ) {
node.attributes.forEach( attribute => {
@ -114,7 +115,12 @@ export default function addElementAttributes ( generator, node, local ) {
else if ( attribute.type === 'EventHandler' ) {
// TODO verify that it's a valid callee (i.e. built-in or declared method)
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();
attribute.expression.arguments.forEach( arg => {

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

@ -1,4 +1,3 @@
import { parse, validate } from '../index.js';
import { walk } from 'estree-walker';
import deindent from '../utils/deindent.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;
export default function compile ( source, { filename }) {
const parsed = parse( source, {} );
validate( parsed, source, {} );
export default function compile ( parsed, source, { filename }) {
const code = new MagicString( source );
const templateProperties = {};
@ -117,6 +113,10 @@ export default function compile ( source, { filename }) {
let elementDepth = 0;
const stringifiers = {
Comment () {
return '';
},
Component ( node ) {
const props = node.attributes.map( attribute => {
let value;
@ -174,6 +174,8 @@ export default function compile ( source, { filename }) {
let element = `<${node.name}`;
node.attributes.forEach( attribute => {
if ( attribute.type !== 'Attribute' ) return;
let str = ` ${attribute.name}`;
if ( attribute.value !== true ) {

@ -1,7 +1,10 @@
import * as fs from 'fs';
import compile from './compile.js';
import { compile } from '../index.js';
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 );
};

@ -7,10 +7,10 @@ export default function flatten ( node ) {
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;
parts.unshift( name );
if ( !name ) return null;
parts.unshift( name );
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 ) {
let elementDepth = 0;
function visit ( node ) {
if ( node.type === 'EachBlock' ) {
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.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 ) {
node.children.forEach( visit );
}
if ( node.type === 'Element' ) {
elementDepth -= 1;
}
}
html.children.forEach( visit );

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

@ -2,6 +2,7 @@ import propValidators from './propValidators/index.js';
import FuzzySet from './utils/FuzzySet.js';
import checkForDupes from './utils/checkForDupes.js';
import checkForComputedKeys from './utils/checkForComputedKeys.js';
import namespaces from '../../utils/namespaces.js';
const validPropList = Object.keys( propValidators );
@ -29,7 +30,7 @@ export default function validateJs ( validator, js ) {
checkForDupes( validator, validator.defaultExport.declaration.properties );
validator.defaultExport.declaration.properties.forEach( prop => {
validator.templateProperties[ prop.key.value ] = prop;
validator.templateProperties[ prop.key.name ] = 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 components from './components.js';
import events from './events.js';
import namespace from './namespace.js';
export default {
data,
@ -15,5 +16,6 @@ export default {
helpers,
methods,
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 {
skip: true,
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' );

@ -1,6 +1,7 @@
<svg>
<Rect x='{{x}}' y='{{y}}' width='{{width}}' height='{{height}}'/>
</svg>
<script>
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