Merge branch 'master' into gh-592

pull/603/head
Rich Harris 8 years ago
commit 77fb38a123

@ -42,6 +42,8 @@
},
"homepage": "https://github.com/sveltejs/svelte#README",
"devDependencies": {
"@types/mocha": "^2.2.41",
"@types/node": "^7.0.22",
"acorn": "^4.0.4",
"babel": "^6.23.0",
"babel-core": "^6.23.1",
@ -74,9 +76,11 @@
"rollup-plugin-commonjs": "^7.0.0",
"rollup-plugin-json": "^2.1.0",
"rollup-plugin-node-resolve": "^2.0.0",
"rollup-plugin-typescript": "^0.8.1",
"rollup-watch": "^3.2.2",
"source-map": "^0.5.6",
"source-map-support": "^0.4.8"
"source-map-support": "^0.4.8",
"typescript": "^2.3.2"
},
"nyc": {
"include": [

@ -0,0 +1,9 @@
const glob = require( 'glob' );
const fs = require( 'fs' );
glob.sync( 'src/**/*.js' ).forEach( file => {
console.log( file );
const js = fs.readFileSync( file, 'utf-8' );
fs.writeFileSync( file.replace( /\.js$/, '.ts' ), js );
fs.unlinkSync( file );
});

@ -1,24 +1,35 @@
import path from 'path';
import nodeResolve from 'rollup-plugin-node-resolve';
import commonjs from 'rollup-plugin-commonjs';
import json from 'rollup-plugin-json';
import buble from 'rollup-plugin-buble';
import typescript from 'rollup-plugin-typescript';
const src = path.resolve( 'src' );
export default {
entry: 'src/index.js',
entry: 'src/index.ts',
moduleName: 'svelte',
targets: [
{ dest: 'compiler/svelte.js', format: 'umd' }
],
plugins: [
{
resolveId ( importee, importer ) {
// bit of a hack — TypeScript only really works if it can resolve imports,
// but they misguidedly chose to reject imports with file extensions. This
// means we need to resolve them here
if ( importer && importer.startsWith( src ) && importee[0] === '.' && path.extname( importee ) === '' ) {
return path.resolve( path.dirname( importer ), `${importee}.ts` );
}
}
},
nodeResolve({ jsnext: true, module: true }),
commonjs(),
json(),
buble({
typescript({
include: 'src/**',
exclude: 'src/shared/**',
target: {
node: 4
}
typescript: require( 'typescript' )
})
],
sourceMap: true

@ -20,9 +20,9 @@ export default {
}
})
],
external: [ path.resolve( 'src/index.js' ), 'fs', 'path' ],
external: [ path.resolve( 'src/index.ts' ), 'fs', 'path' ],
paths: {
[ path.resolve( 'src/index.js' ) ]: '../compiler/svelte.js'
[ path.resolve( 'src/index.ts' ) ]: '../compiler/svelte.js'
},
sourceMap: true
};

@ -1,20 +1,45 @@
import MagicString, { Bundle } from 'magic-string';
import { walk } from 'estree-walker';
import isReference from '../utils/isReference.js';
import flattenReference from '../utils/flattenReference.js';
import globalWhitelist from '../utils/globalWhitelist.js';
import reservedNames from '../utils/reservedNames.js';
import namespaces from '../utils/namespaces.js';
import { removeNode, removeObjectKey } from '../utils/removeNode.js';
import getIntro from './shared/utils/getIntro.js';
import getOutro from './shared/utils/getOutro.js';
import processCss from './shared/processCss.js';
import annotateWithScopes from '../utils/annotateWithScopes.js';
import isReference from '../utils/isReference';
import flattenReference from '../utils/flattenReference';
import globalWhitelist from '../utils/globalWhitelist';
import reservedNames from '../utils/reservedNames';
import namespaces from '../utils/namespaces';
import { removeNode, removeObjectKey } from '../utils/removeNode';
import getIntro from './shared/utils/getIntro';
import getOutro from './shared/utils/getOutro';
import processCss from './shared/processCss';
import annotateWithScopes from '../utils/annotateWithScopes';
import DomBlock from './dom/Block';
import SsrBlock from './server-side-rendering/Block';
import { Node, Parsed, CompileOptions } from '../interfaces';
const test = typeof global !== 'undefined' && global.__svelte_test;
export default class Generator {
constructor ( parsed, source, name, options ) {
parsed: Parsed;
source: string;
name: string;
options: CompileOptions;
imports: Node[];
helpers: Set<string>;
components: Set<string>;
events: Set<string>;
transitions: Set<string>;
importedComponents: Map<string, string>;
bindingGroups: string[];
expectedProperties: Set<string>;
css: string;
cssId: string;
usesRefs: boolean;
importedNames: Set<string>;
aliases: Map<string, string>;
usedNames: Set<string>;
constructor ( parsed: Parsed, source: string, name: string, options: CompileOptions ) {
this.parsed = parsed;
this.source = source;
this.name = name;
@ -39,22 +64,22 @@ export default class Generator {
this.usesRefs = false;
// allow compiler to deconflict user's `import { get } from 'whatever'` and
// Svelte's builtin `import { get, ... } from 'svelte/shared.js'`;
// Svelte's builtin `import { get, ... } from 'svelte/shared.ts'`;
this.importedNames = new Set();
this.aliases = new Map();
this._usedNames = new Set( [ name ] );
this.usedNames = new Set( [ name ] );
}
addSourcemapLocations ( node ) {
addSourcemapLocations ( node: Node ) {
walk( node, {
enter: node => {
enter: ( node: Node ) => {
this.code.addSourcemapLocation( node.start );
this.code.addSourcemapLocation( node.end );
}
});
}
alias ( name ) {
alias ( name: string ) {
if ( !this.aliases.has( name ) ) {
this.aliases.set( name, this.getUniqueName( name ) );
}
@ -62,10 +87,10 @@ export default class Generator {
return this.aliases.get( name );
}
contextualise ( block, expression, context, isEventHandler ) {
contextualise ( block: DomBlock | SsrBlock, expression: Node, context: string, isEventHandler: boolean ) {
this.addSourcemapLocations( expression );
const usedContexts = [];
const usedContexts: string[] = [];
const { code, helpers } = this;
const { contexts, indexes } = block;
@ -76,7 +101,7 @@ export default class Generator {
const self = this;
walk( expression, {
enter ( node, parent, key ) {
enter ( node: Node, parent: Node, key: string ) {
if ( /^Function/.test( node.type ) ) lexicalDepth += 1;
if ( node._scope ) {
@ -138,7 +163,7 @@ export default class Generator {
}
},
leave ( node ) {
leave ( node: Node ) {
if ( /^Function/.test( node.type ) ) lexicalDepth -= 1;
if ( node._scope ) scope = scope.parent;
}
@ -151,16 +176,16 @@ export default class Generator {
};
}
findDependencies ( contextDependencies, indexes, expression ) {
findDependencies ( contextDependencies: Map<string, string[]>, indexes: Map<string, string>, expression: Node ) {
if ( expression._dependencies ) return expression._dependencies;
let scope = annotateWithScopes( expression );
const dependencies = [];
const dependencies: string[] = [];
const generator = this; // can't use arrow functions, because of this.skip()
walk( expression, {
enter ( node, parent ) {
enter ( node: Node, parent: Node ) {
if ( node._scope ) {
scope = node._scope;
return;
@ -180,7 +205,7 @@ export default class Generator {
}
},
leave ( node ) {
leave ( node: Node ) {
if ( node._scope ) scope = scope.parent;
}
});
@ -196,7 +221,7 @@ export default class Generator {
generate ( result, options, { name, format } ) {
if ( this.imports.length ) {
const statements = [];
const statements: string[] = [];
this.imports.forEach( ( declaration, i ) => {
if ( format === 'es' ) {
@ -204,14 +229,14 @@ export default class Generator {
return;
}
const defaultImport = declaration.specifiers.find( x => x.type === 'ImportDefaultSpecifier' || x.type === 'ImportSpecifier' && x.imported.name === 'default' );
const namespaceImport = declaration.specifiers.find( x => x.type === 'ImportNamespaceSpecifier' );
const namedImports = declaration.specifiers.filter( x => x.type === 'ImportSpecifier' && x.imported.name !== 'default' );
const defaultImport = declaration.specifiers.find( ( x: Node ) => x.type === 'ImportDefaultSpecifier' || x.type === 'ImportSpecifier' && x.imported.name === 'default' );
const namespaceImport = declaration.specifiers.find( ( x: Node ) => x.type === 'ImportNamespaceSpecifier' );
const namedImports = declaration.specifiers.filter( ( x: Node ) => x.type === 'ImportSpecifier' && x.imported.name !== 'default' );
const name = ( defaultImport || namespaceImport ) ? ( defaultImport || namespaceImport ).local.name : `__import${i}`;
declaration.name = name; // hacky but makes life a bit easier later
namedImports.forEach( specifier => {
namedImports.forEach( ( specifier: Node ) => {
statements.push( `var ${specifier.local.name} = ${name}.${specifier.imported.name}` );
});
@ -230,7 +255,7 @@ export default class Generator {
const compiled = new Bundle({ separator: '' });
function addString ( str ) {
function addString ( str: string ) {
compiled.addSource({
content: new MagicString( str )
});
@ -250,7 +275,7 @@ export default class Generator {
});
}
parts.forEach( str => {
parts.forEach( ( str: string ) => {
const chunk = str.replace( pattern, '' );
if ( chunk ) addString( chunk );
@ -274,11 +299,11 @@ export default class Generator {
};
}
getUniqueName ( name ) {
getUniqueName ( name: string ) {
if ( test ) name = `${name}$`;
let alias = name;
for ( let i = 1; reservedNames.has( alias ) || this.importedNames.has( alias ) || this._usedNames.has( alias ); alias = `${name}_${i++}` );
this._usedNames.add( alias );
for ( let i = 1; reservedNames.has( alias ) || this.importedNames.has( alias ) || this.usedNames.has( alias ); alias = `${name}_${i++}` );
this.usedNames.add( alias );
return alias;
}
@ -287,13 +312,13 @@ export default class Generator {
return name => {
if ( test ) name = `${name}$`;
let alias = name;
for ( let i = 1; reservedNames.has( alias ) || this.importedNames.has( alias ) || this._usedNames.has( alias ) || localUsedNames.has( alias ); alias = `${name}_${i++}` );
for ( let i = 1; reservedNames.has( alias ) || this.importedNames.has( alias ) || this.usedNames.has( alias ) || localUsedNames.has( alias ); alias = `${name}_${i++}` );
localUsedNames.add( alias );
return alias;
};
}
parseJs ( ssr ) {
parseJs ( ssr: boolean = false ) {
const { source } = this;
const { js } = this.parsed;
@ -315,23 +340,23 @@ export default class Generator {
removeNode( this.code, js.content, node );
imports.push( node );
node.specifiers.forEach( specifier => {
node.specifiers.forEach( ( specifier: Node ) => {
this.importedNames.add( specifier.local.name );
});
}
}
const defaultExport = body.find( node => node.type === 'ExportDefaultDeclaration' );
const defaultExport = body.find( ( node: Node ) => node.type === 'ExportDefaultDeclaration' );
if ( defaultExport ) {
defaultExport.declaration.properties.forEach( prop => {
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 => {
templateProperties[ key ].value.properties.forEach( ( prop: node ) => {
this[ key ].add( prop.key.name );
});
}
@ -340,11 +365,11 @@ export default class Generator {
if ( templateProperties.computed ) {
const dependencies = new Map();
templateProperties.computed.value.properties.forEach( prop => {
templateProperties.computed.value.properties.forEach( ( prop: Node ) => {
const key = prop.key.name;
const value = prop.value;
const deps = value.params.map( param => param.type === 'AssignmentPattern' ? param.left.name : param.name );
const deps = value.params.map( ( param: Node ) => param.type === 'AssignmentPattern' ? param.left.name : param.name );
dependencies.set( key, deps );
});
@ -362,7 +387,7 @@ export default class Generator {
computations.push({ key, deps });
}
templateProperties.computed.value.properties.forEach( prop => visit( prop.key.name ) );
templateProperties.computed.value.properties.forEach( ( prop: Node ) => visit( prop.key.name ) );
}
if ( templateProperties.namespace ) {
@ -374,7 +399,7 @@ export default class Generator {
if ( templateProperties.components ) {
let hasNonImportedComponent = false;
templateProperties.components.value.properties.forEach( property => {
templateProperties.components.value.properties.forEach( ( property: Node ) => {
const key = property.key.name;
const value = source.slice( property.value.start, property.value.end );
if ( this.importedNames.has( value ) ) {

@ -1,8 +1,70 @@
import CodeBuilder from '../../utils/CodeBuilder.js';
import CodeBuilder from '../../utils/CodeBuilder';
import deindent from '../../utils/deindent.js';
import { DomGenerator } from './index';
import { Node } from '../../interfaces';
export interface BlockOptions {
name: string;
generator?: DomGenerator;
expression?: Node;
context?: string;
key?: string;
contexts?: Map<string, string>;
indexes?: Map<string, string>;
contextDependencies?: Map<string, string[]>;
params?: string[];
indexNames?: Map<string, string>;
listNames?: Map<string, string>;
indexName?: string;
listName?: string;
dependencies?: Set<string>;
}
export default class Block {
constructor ( options ) {
generator: DomGenerator;
name: string;
expression: Node;
context: string;
key: string;
first: string;
contexts: Map<string, string>;
indexes: Map<string, string>;
contextDependencies: Map<string, string[]>;
dependencies: Set<string>;
params: string[];
indexNames: Map<string, string>;
listNames: Map<string, string>;
indexName: string;
listName: string;
builders: {
create: CodeBuilder;
mount: CodeBuilder;
update: CodeBuilder;
intro: CodeBuilder;
outro: CodeBuilder;
detach: CodeBuilder;
detachRaw: CodeBuilder;
destroy: CodeBuilder;
}
hasIntroMethod: boolean;
hasOutroMethod: boolean;
outros: number;
aliases: Map<string, string>;
variables: Map<string, string>;
getUniqueName: (name: string) => string;
component: string;
target: string;
hasUpdateMethod: boolean;
autofocus: string;
constructor ( options: BlockOptions ) {
this.generator = options.generator;
this.name = options.name;
this.expression = options.expression;
@ -55,7 +117,7 @@ export default class Block {
});
}
addElement ( name, renderStatement, parentNode, needsIdentifier = false ) {
addElement ( name: string, renderStatement: string, parentNode: string, needsIdentifier = false ) {
const isToplevel = !parentNode;
if ( needsIdentifier || isToplevel ) {
this.builders.create.addLine(
@ -72,7 +134,7 @@ export default class Block {
}
}
addVariable ( name, init ) {
addVariable ( name: string, init?: string ) {
if ( this.variables.has( name ) && this.variables.get( name ) !== init ) {
throw new Error( `Variable '${name}' already initialised with a different value` );
}
@ -80,7 +142,7 @@ export default class Block {
this.variables.set( name, init );
}
alias ( name ) {
alias ( name: string ) {
if ( !this.aliases.has( name ) ) {
this.aliases.set( name, this.getUniqueName( name ) );
}
@ -88,11 +150,11 @@ export default class Block {
return this.aliases.get( name );
}
child ( options ) {
child ( options: BlockOptions ) {
return new Block( Object.assign( {}, this, options, { parent: this } ) );
}
contextualise ( expression, context, isEventHandler ) {
contextualise ( expression: Node, context?: string, isEventHandler?: boolean ) {
return this.generator.contextualise( this, expression, context, isEventHandler );
}
@ -100,7 +162,7 @@ export default class Block {
return this.generator.findDependencies( this.contextDependencies, this.indexes, expression );
}
mount ( name, parentNode ) {
mount ( name: string, parentNode: string ) {
if ( parentNode ) {
this.builders.create.addLine( `${this.generator.helper( 'appendNode' )}( ${name}, ${parentNode} );` );
} else {

@ -1,17 +1,28 @@
import MagicString from 'magic-string';
import { parseExpressionAt } from 'acorn';
import annotateWithScopes from '../../utils/annotateWithScopes.js';
import isReference from '../../utils/isReference.js';
import annotateWithScopes from '../../utils/annotateWithScopes';
import isReference from '../../utils/isReference';
import { walk } from 'estree-walker';
import deindent from '../../utils/deindent.js';
import CodeBuilder from '../../utils/CodeBuilder.js';
import visit from './visit.js';
import shared from './shared.js';
import Generator from '../Generator.js';
import preprocess from './preprocess.js';
class DomGenerator extends Generator {
constructor ( parsed, source, name, options ) {
import CodeBuilder from '../../utils/CodeBuilder';
import visit from './visit';
import shared from './shared';
import Generator from '../Generator';
import preprocess from './preprocess';
import Block from './Block';
import { Parsed, CompileOptions, Node } from '../../interfaces';
export class DomGenerator extends Generator {
blocks: Block[]
uses: Set<string>;
readonly: Set<string>;
metaBindings: string[];
hasIntroTransitions: boolean;
hasOutroTransitions: boolean;
hasComplexBindings: boolean;
constructor ( parsed: Parsed, source: string, name: string, options: CompileOptions ) {
super( parsed, source, name, options );
this.blocks = [];
this.uses = new Set();
@ -22,7 +33,7 @@ class DomGenerator extends Generator {
this.metaBindings = [];
}
helper ( name ) {
helper ( name: string ) {
if ( this.options.dev && `${name}Dev` in shared ) {
name = `${name}Dev`;
}
@ -33,7 +44,7 @@ class DomGenerator extends Generator {
}
}
export default function dom ( parsed, source, options ) {
export default function dom ( parsed: Parsed, source: string, options: CompileOptions ) {
const format = options.format || 'es';
const name = options.name || 'SvelteComponent';
@ -41,15 +52,9 @@ export default function dom ( parsed, source, options ) {
const { computations, hasJs, templateProperties, namespace } = generator.parseJs();
const state = {
namespace,
parentNode: null,
isTopLevel: true
};
const block = preprocess( generator, state, parsed.html );
const { block, state } = preprocess( generator, namespace, parsed.html );
parsed.html.children.forEach( node => {
parsed.html.children.forEach( ( node: Node ) => {
visit( generator, block, state, node );
});

@ -0,0 +1,11 @@
export interface State {
name: string;
namespace: string;
parentNode: string;
isTopLevel: boolean
parentNodeName?: string;
basename?: string;
inEachBlock?: boolean;
allUsedContexts?: string[];
usesComponent?: boolean;
}

@ -1,12 +1,15 @@
import Block from './Block.js';
import { trimStart, trimEnd } from '../../utils/trim.js';
import Block from './Block';
import { trimStart, trimEnd } from '../../utils/trim';
import { assign } from '../../shared/index.js';
import { DomGenerator } from './index';
import { Node } from '../../interfaces';
import { State } from './interfaces';
function isElseIf ( node ) {
function isElseIf ( node: Node ) {
return node && node.children.length === 1 && node.children[0].type === 'IfBlock';
}
function getChildState ( parent, child ) {
function getChildState ( parent: State, child = {} ) {
return assign( {}, parent, { name: null, parentNode: null }, child || {} );
}
@ -25,7 +28,7 @@ const elementsWithoutText = new Set([
]);
const preprocessors = {
MustacheTag: ( generator, block, state, node ) => {
MustacheTag: ( generator: DomGenerator, block: Block, state: State, node: Node ) => {
const dependencies = block.findDependencies( node.expression );
block.addDependencies( dependencies );
@ -34,7 +37,7 @@ const preprocessors = {
});
},
RawMustacheTag: ( generator, block, state, node ) => {
RawMustacheTag: ( generator: DomGenerator, block: Block, state: State, node: Node ) => {
const dependencies = block.findDependencies( node.expression );
block.addDependencies( dependencies );
@ -44,7 +47,7 @@ const preprocessors = {
node._state = getChildState( state, { basename, name });
},
Text: ( generator, block, state, node ) => {
Text: ( generator: DomGenerator, block: Block, state: State, node: Node ) => {
node._state = getChildState( state );
if ( !/\S/.test( node.data ) ) {
@ -56,13 +59,13 @@ const preprocessors = {
node._state.name = block.getUniqueName( `text` );
},
IfBlock: ( generator, block, state, node ) => {
const blocks = [];
IfBlock: ( generator: DomGenerator, block: Block, state: State, node: Node ) => {
const blocks: Block[] = [];
let dynamic = false;
let hasIntros = false;
let hasOutros = false;
function attachBlocks ( node ) {
function attachBlocks ( node: Node ) {
const dependencies = block.findDependencies( node.expression );
block.addDependencies( dependencies );
@ -113,7 +116,7 @@ const preprocessors = {
generator.blocks.push( ...blocks );
},
EachBlock: ( generator, block, state, node ) => {
EachBlock: ( generator: DomGenerator, block: Block, state: State, node: Node ) => {
const dependencies = block.findDependencies( node.expression );
block.addDependencies( dependencies );
@ -175,7 +178,7 @@ const preprocessors = {
}
},
Element: ( generator, block, state, node ) => {
Element: ( generator: DomGenerator, block: Block, state: State, node: Node ) => {
const isComponent = generator.components.has( node.name ) || node.name === ':Self';
if ( isComponent ) {
@ -193,9 +196,9 @@ const preprocessors = {
});
}
node.attributes.forEach( attribute => {
node.attributes.forEach( ( attribute: Node ) => {
if ( attribute.type === 'Attribute' && attribute.value !== true ) {
attribute.value.forEach( chunk => {
attribute.value.forEach( ( chunk: Node ) => {
if ( chunk.type !== 'Text' ) {
const dependencies = block.findDependencies( chunk.expression );
block.addDependencies( dependencies );
@ -238,12 +241,12 @@ const preprocessors = {
}
};
function preprocessChildren ( generator, block, state, node, isTopLevel ) {
function preprocessChildren ( generator: DomGenerator, block: Block, state: State, node: Node, isTopLevel: boolean = false ) {
// glue text nodes together
const cleaned = [];
let lastChild;
const cleaned: Node[] = [];
let lastChild: Node;
node.children.forEach( child => {
node.children.forEach( ( child: Node ) => {
if ( child.type === 'Comment' ) return;
if ( child.type === 'Text' && lastChild && lastChild.type === 'Text' ) {
@ -273,7 +276,7 @@ function preprocessChildren ( generator, block, state, node, isTopLevel ) {
lastChild = null;
cleaned.forEach( child => {
cleaned.forEach( ( child: Node ) => {
const preprocess = preprocessors[ child.type ];
if ( preprocess ) preprocess( generator, block, state, child );
@ -292,7 +295,7 @@ function preprocessChildren ( generator, block, state, node, isTopLevel ) {
node.children = cleaned;
}
export default function preprocess ( generator, state, node ) {
export default function preprocess ( generator: DomGenerator, namespace: string, node: Node ) {
const block = new Block({
generator,
name: generator.alias( 'create_main_fragment' ),
@ -309,9 +312,15 @@ export default function preprocess ( generator, state, node ) {
dependencies: new Set()
});
const state: State = {
namespace,
parentNode: null,
isTopLevel: true
};
generator.blocks.push( block );
preprocessChildren( generator, block, state, node, true );
block.hasUpdateMethod = block.dependencies.size > 0;
return block;
return { block, state };
}

@ -0,0 +1,35 @@
// this file is auto-generated, do not edit it
export default {
"appendNode": "function appendNode ( node, target ) {\n\ttarget.appendChild( node );\n}",
"insertNode": "function insertNode ( node, target, anchor ) {\n\ttarget.insertBefore( node, anchor );\n}",
"detachNode": "function detachNode ( node ) {\n\tnode.parentNode.removeChild( node );\n}",
"detachBetween": "function detachBetween ( before, after ) {\n\twhile ( before.nextSibling && before.nextSibling !== after ) {\n\t\tbefore.parentNode.removeChild( before.nextSibling );\n\t}\n}",
"destroyEach": "function destroyEach ( iterations, detach, start ) {\n\tfor ( var i = start; i < iterations.length; i += 1 ) {\n\t\tif ( iterations[i] ) iterations[i].destroy( detach );\n\t}\n}",
"createElement": "function createElement ( name ) {\n\treturn document.createElement( name );\n}",
"createSvgElement": "function createSvgElement ( name ) {\n\treturn document.createElementNS( 'http://www.w3.org/2000/svg', name );\n}",
"createText": "function createText ( data ) {\n\treturn document.createTextNode( data );\n}",
"createComment": "function createComment () {\n\treturn document.createComment( '' );\n}",
"addEventListener": "function addEventListener ( node, event, handler ) {\n\tnode.addEventListener( event, handler, false );\n}",
"removeEventListener": "function removeEventListener ( node, event, handler ) {\n\tnode.removeEventListener( event, handler, false );\n}",
"setAttribute": "function setAttribute ( node, attribute, value ) {\n\tnode.setAttribute( attribute, value );\n}",
"setXlinkAttribute": "function setXlinkAttribute ( node, attribute, value ) {\n\tnode.setAttributeNS( 'http://www.w3.org/1999/xlink', attribute, value );\n}",
"getBindingGroupValue": "function getBindingGroupValue ( group ) {\n\tvar value = [];\n\tfor ( var i = 0; i < group.length; i += 1 ) {\n\t\tif ( group[i].checked ) value.push( group[i].__value );\n\t}\n\treturn value;\n}",
"differs": "function differs ( a, b ) {\n\treturn ( a !== b ) || ( a && ( typeof a === 'object' ) || ( typeof a === 'function' ) );\n}",
"dispatchObservers": "function dispatchObservers ( component, group, newState, oldState ) {\n\tfor ( var key in group ) {\n\t\tif ( !( key in newState ) ) continue;\n\n\t\tvar newValue = newState[ key ];\n\t\tvar oldValue = oldState[ key ];\n\n\t\tif ( differs( newValue, oldValue ) ) {\n\t\t\tvar callbacks = group[ key ];\n\t\t\tif ( !callbacks ) continue;\n\n\t\t\tfor ( var i = 0; i < callbacks.length; i += 1 ) {\n\t\t\t\tvar callback = callbacks[i];\n\t\t\t\tif ( callback.__calling ) continue;\n\n\t\t\t\tcallback.__calling = true;\n\t\t\t\tcallback.call( component, newValue, oldValue );\n\t\t\t\tcallback.__calling = false;\n\t\t\t}\n\t\t}\n\t}\n}",
"get": "function get ( key ) {\n\treturn key ? this._state[ key ] : this._state;\n}",
"fire": "function fire ( eventName, data ) {\n\tvar handlers = eventName in this._handlers && this._handlers[ eventName ].slice();\n\tif ( !handlers ) return;\n\n\tfor ( var i = 0; i < handlers.length; i += 1 ) {\n\t\thandlers[i].call( this, data );\n\t}\n}",
"observe": "function observe ( key, callback, options ) {\n\tvar group = ( options && options.defer ) ? this._observers.post : this._observers.pre;\n\n\t( group[ key ] || ( group[ key ] = [] ) ).push( callback );\n\n\tif ( !options || options.init !== false ) {\n\t\tcallback.__calling = true;\n\t\tcallback.call( this, this._state[ key ] );\n\t\tcallback.__calling = false;\n\t}\n\n\treturn {\n\t\tcancel: function () {\n\t\t\tvar index = group[ key ].indexOf( callback );\n\t\t\tif ( ~index ) group[ key ].splice( index, 1 );\n\t\t}\n\t};\n}",
"observeDev": "function observeDev ( key, callback, options ) {\n\tvar c = ( key = '' + key ).search( /[^\\w]/ );\n\tif ( c > -1 ) {\n\t\tvar message = \"The first argument to component.observe(...) must be the name of a top-level property\";\n\t\tif ( c > 0 ) message += \", i.e. '\" + key.slice( 0, c ) + \"' rather than '\" + key + \"'\";\n\n\t\tthrow new Error( message );\n\t}\n\n\treturn observe.call( this, key, callback, options );\n}",
"on": "function on ( eventName, handler ) {\n\tif ( eventName === 'teardown' ) return this.on( 'destroy', handler );\n\n\tvar handlers = this._handlers[ eventName ] || ( this._handlers[ eventName ] = [] );\n\thandlers.push( handler );\n\n\treturn {\n\t\tcancel: function () {\n\t\t\tvar index = handlers.indexOf( handler );\n\t\t\tif ( ~index ) handlers.splice( index, 1 );\n\t\t}\n\t};\n}",
"onDev": "function onDev ( eventName, handler ) {\n\tif ( eventName === 'teardown' ) {\n\t\tconsole.warn( \"Use component.on('destroy', ...) instead of component.on('teardown', ...) which has been deprecated and will be unsupported in Svelte 2\" );\n\t\treturn this.on( 'destroy', handler );\n\t}\n\n\treturn on.call( this, eventName, handler );\n}",
"set": "function set ( newState ) {\n\tthis._set( assign( {}, newState ) );\n\tthis._root._flush();\n}",
"_flush": "function _flush () {\n\tif ( !this._renderHooks ) return;\n\n\twhile ( this._renderHooks.length ) {\n\t\tthis._renderHooks.pop()();\n\t}\n}",
"proto": "{\n\tget: get,\n\tfire: fire,\n\tobserve: observe,\n\ton: on,\n\tset: set,\n\t_flush: _flush\n}",
"protoDev": "{\n\tget: get,\n\tfire: fire,\n\tobserve: observeDev,\n\ton: onDev,\n\tset: set,\n\t_flush: _flush\n}",
"linear": "function linear ( t ) {\n\treturn t;\n}",
"generateKeyframes": "function generateKeyframes ( a, b, delta, duration, ease, fn, node, style ) {\n\tvar id = '__svelte' + ~~( Math.random() * 1e9 ); // TODO make this more robust\n\tvar keyframes = '@keyframes ' + id + '{\\n';\n\n\tfor ( var p = 0; p <= 1; p += 16.666 / duration ) {\n\t\tvar t = a + delta * ease( p );\n\t\tkeyframes += ( p * 100 ) + '%{' + fn( t ) + '}\\n';\n\t}\n\n\tkeyframes += '100% {' + fn( b ) + '}\\n}';\n\tstyle.textContent += keyframes;\n\n\tdocument.head.appendChild( style );\n\n\tnode.style.animation = ( node.style.animation || '' ).split( ',' )\n\t\t.filter( function ( anim ) {\n\t\t\t// when introing, discard old animations if there are any\n\t\t\treturn anim && ( delta < 0 || !/__svelte/.test( anim ) );\n\t\t})\n\t\t.concat( id + ' ' + duration + 'ms linear 1 forwards' )\n\t\t.join( ', ' );\n}",
"wrapTransition": "function wrapTransition ( node, fn, params, intro, outgroup ) {\n\tvar obj = fn( node, params );\n\tvar duration = obj.duration || 300;\n\tvar ease = obj.easing || linear;\n\n\t// TODO share <style> tag between all transitions?\n\tif ( obj.css ) {\n\t\tvar style = document.createElement( 'style' );\n\t}\n\n\tif ( intro && obj.tick ) obj.tick( 0 );\n\n\treturn {\n\t\tt: intro ? 0 : 1,\n\t\trunning: false,\n\t\tprogram: null,\n\t\tpending: null,\n\t\trun: function ( intro, callback ) {\n\t\t\tvar program = {\n\t\t\t\tstart: window.performance.now() + ( obj.delay || 0 ),\n\t\t\t\tintro: intro,\n\t\t\t\tcallback: callback\n\t\t\t};\n\n\t\t\tif ( obj.delay ) {\n\t\t\t\tthis.pending = program;\n\t\t\t} else {\n\t\t\t\tthis.start( program );\n\t\t\t}\n\n\t\t\tif ( !this.running ) {\n\t\t\t\tthis.running = true;\n\t\t\t\ttransitionManager.add( this );\n\t\t\t}\n\t\t},\n\t\tstart: function ( program ) {\n\t\t\tprogram.a = this.t;\n\t\t\tprogram.b = program.intro ? 1 : 0;\n\t\t\tprogram.delta = program.b - program.a;\n\t\t\tprogram.duration = duration * Math.abs( program.b - program.a );\n\t\t\tprogram.end = program.start + program.duration;\n\n\t\t\tif ( obj.css ) {\n\t\t\t\tgenerateKeyframes( program.a, program.b, program.delta, program.duration, ease, obj.css, node, style );\n\t\t\t}\n\n\t\t\tthis.program = program;\n\t\t\tthis.pending = null;\n\t\t},\n\t\tupdate: function ( now ) {\n\t\t\tvar program = this.program;\n\t\t\tif ( !program ) return;\n\n\t\t\tvar p = now - program.start;\n\t\t\tthis.t = program.a + program.delta * ease( p / program.duration );\n\t\t\tif ( obj.tick ) obj.tick( this.t );\n\t\t},\n\t\tdone: function () {\n\t\t\tthis.t = this.program.b;\n\t\t\tif ( obj.tick ) obj.tick( this.t );\n\t\t\tif ( obj.css ) document.head.removeChild( style );\n\t\t\tthis.program.callback();\n\t\t\tthis.program = null;\n\t\t\tthis.running = !!this.pending;\n\t\t},\n\t\tabort: function () {\n\t\t\tif ( obj.tick ) obj.tick( 1 );\n\t\t\tif ( obj.css ) document.head.removeChild( style );\n\t\t\tthis.program = this.pending = null;\n\t\t\tthis.running = false;\n\t\t}\n\t};\n}",
"transitionManager": "{\n\trunning: false,\n\ttransitions: [],\n\tbound: null,\n\n\tadd: function ( transition ) {\n\t\tthis.transitions.push( transition );\n\n\t\tif ( !this.running ) {\n\t\t\tthis.running = true;\n\t\t\tthis.next();\n\t\t}\n\t},\n\n\tnext: function () {\n\t\tthis.running = false;\n\n\t\tvar now = window.performance.now();\n\t\tvar i = this.transitions.length;\n\n\t\twhile ( i-- ) {\n\t\t\tvar transition = this.transitions[i];\n\n\t\t\tif ( transition.program && now >= transition.program.end ) {\n\t\t\t\ttransition.done();\n\t\t\t}\n\n\t\t\tif ( transition.pending && now >= transition.pending.start ) {\n\t\t\t\ttransition.start( transition.pending );\n\t\t\t}\n\n\t\t\tif ( transition.running ) {\n\t\t\t\ttransition.update( now );\n\t\t\t\tthis.running = true;\n\t\t\t} else if ( !transition.pending ) {\n\t\t\t\tthis.transitions.splice( i, 1 );\n\t\t\t}\n\t\t}\n\n\t\tif ( this.running ) {\n\t\t\trequestAnimationFrame( this.bound || ( this.bound = this.next.bind( this ) ) );\n\t\t}\n\t}\n}",
"noop": "function noop () {}",
"assign": "function assign ( target ) {\n\tfor ( var i = 1; i < arguments.length; i += 1 ) {\n\t\tvar source = arguments[i];\n\t\tfor ( var k in source ) target[k] = source[k];\n\t}\n\n\treturn target;\n}"
};

@ -1,6 +0,0 @@
import visitors from './visitors/index.js';
export default function visit ( generator, block, state, node ) {
const visitor = visitors[ node.type ];
visitor( generator, block, state, node );
}

@ -0,0 +1,9 @@
import visitors from './visitors/index';
import { DomGenerator } from './index';
import Block from './Block';
import { Node } from '../../interfaces';
export default function visit ( generator: DomGenerator, block: Block, state, node: Node ) {
const visitor = visitors[ node.type ];
visitor( generator, block, state, node );
}

@ -1,4 +1,9 @@
export default function visitAttribute ( generator, block, state, node, attribute, local ) {
import { DomGenerator } from '../../index';
import Block from '../../Block';
import { Node } from '../../../../interfaces';
import { State } from '../../interfaces';
export default function visitAttribute ( generator: DomGenerator, block: Block, state: State, node: Node, attribute, local ) {
if ( attribute.value === true ) {
// attributes without values, e.g. <textarea readonly>
local.staticAttributes.push({

@ -1,8 +1,12 @@
import deindent from '../../../../utils/deindent.js';
import flattenReference from '../../../../utils/flattenReference.js';
import getSetter from '../shared/binding/getSetter.js';
import flattenReference from '../../../../utils/flattenReference';
import getSetter from '../shared/binding/getSetter';
import { DomGenerator } from '../../index';
import Block from '../../Block';
import { Node } from '../../../../interfaces';
import { State } from '../../interfaces';
export default function visitBinding ( generator, block, state, node, attribute, local ) {
export default function visitBinding ( generator: DomGenerator, block: Block, state: State, node: Node, attribute, local ) {
const { name } = flattenReference( attribute.value );
const { snippet, contexts, dependencies } = block.contextualise( attribute.value );

@ -1,12 +1,16 @@
import deindent from '../../../../utils/deindent.js';
import CodeBuilder from '../../../../utils/CodeBuilder.js';
import visit from '../../visit.js';
import visitAttribute from './Attribute.js';
import visitEventHandler from './EventHandler.js';
import visitBinding from './Binding.js';
import visitRef from './Ref.js';
function stringifyProps ( props ) {
import CodeBuilder from '../../../../utils/CodeBuilder';
import visit from '../../visit';
import visitAttribute from './Attribute';
import visitEventHandler from './EventHandler';
import visitBinding from './Binding';
import visitRef from './Ref';
import { DomGenerator } from '../../index';
import Block from '../../Block';
import { Node } from '../../../../interfaces';
import { State } from '../../interfaces';
function stringifyProps ( props: string[] ) {
if ( !props.length ) return '{}';
const joined = props.join( ', ' );
@ -32,7 +36,7 @@ const visitors = {
Ref: visitRef
};
export default function visitComponent ( generator, block, state, node ) {
export default function visitComponent ( generator: DomGenerator, block: Block, state: State, node: Node ) {
const hasChildren = node.children.length > 0;
const name = block.getUniqueName( ( node.name === ':Self' ? generator.name : node.name ).toLowerCase() );
@ -120,7 +124,7 @@ export default function visitComponent ( generator, block, state, node ) {
componentInitProperties.push( `_yield: ${yieldFragment}`);
}
const statements = [];
const statements: string[] = [];
if ( local.staticAttributes.length || local.dynamicAttributes.length || local.bindings.length ) {
const initialProps = local.staticAttributes

@ -1,12 +1,16 @@
import deindent from '../../../../utils/deindent.js';
import { DomGenerator } from '../../index';
import Block from '../../Block';
import { Node } from '../../../../interfaces';
import { State } from '../../interfaces';
export default function visitEventHandler ( generator, block, state, node, attribute, local ) {
export default function visitEventHandler ( generator: DomGenerator, block: Block, state: State, node: Node, attribute: Node, local ) {
// 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, `${block.component}.` );
const usedContexts = [];
attribute.expression.arguments.forEach( arg => {
const usedContexts: string[] = [];
attribute.expression.arguments.forEach( ( arg: Node ) => {
const { contexts } = block.contextualise( arg, null, true );
contexts.forEach( context => {

@ -1,6 +1,10 @@
import deindent from '../../../../utils/deindent.js';
import { DomGenerator } from '../../index';
import Block from '../../Block';
import { Node } from '../../../../interfaces';
import { State } from '../../interfaces';
export default function visitRef ( generator, block, state, node, attribute, local ) {
export default function visitRef ( generator: DomGenerator, block: Block, state: State, node: Node, attribute: Node, local ) {
generator.usesRefs = true;
local.create.addLine(

@ -1,7 +1,11 @@
import deindent from '../../../utils/deindent.js';
import visit from '../visit.js';
import visit from '../visit';
import { DomGenerator } from '../index';
import Block from '../Block';
import { Node } from '../../../interfaces';
import { State } from '../interfaces';
export default function visitEachBlock ( generator, block, state, node ) {
export default function visitEachBlock ( generator: DomGenerator, block: Block, state: State, node: Node ) {
const each_block = generator.getUniqueName( `each_block` );
const create_each_block = node._block.name;
const each_block_value = node._block.listName;
@ -86,18 +90,18 @@ export default function visitEachBlock ( generator, block, state, node ) {
` );
}
node.children.forEach( child => {
node.children.forEach( ( child: Node ) => {
visit( generator, node._block, node._state, child );
});
if ( node.else ) {
node.else.children.forEach( child => {
node.else.children.forEach( ( child: Node ) => {
visit( generator, node.else._block, node.else._state, child );
});
}
}
function keyed ( generator, block, state, node, snippet, { each_block, create_each_block, each_block_value, i, params, anchor, mountOrIntro } ) {
function keyed ( generator: DomGenerator, block: Block, state: State, node: Node, snippet, { each_block, create_each_block, each_block_value, i, params, anchor, mountOrIntro } ) {
const key = block.getUniqueName( 'key' );
const lookup = block.getUniqueName( `${each_block}_lookup` );
const iteration = block.getUniqueName( `${each_block}_iteration` );
@ -151,8 +155,6 @@ function keyed ( generator, block, state, node, snippet, { each_block, create_ea
function ${fn} ( iteration ) {
iteration.outro( function () {
iteration.destroy( true );
if ( iteration.next ) iteration.next.last = iteration.last;
if ( iteration.last ) iteration.last.next = iteration.next;
${lookup}[iteration.key] = null;
});
}
@ -175,8 +177,6 @@ function keyed ( generator, block, state, node, snippet, { each_block, create_ea
block.builders.create.addBlock( deindent`
function ${fn} ( iteration ) {
iteration.destroy( true );
if ( iteration.next && iteration.next.last === iteration ) iteration.next.last = iteration.last;
if ( iteration.last && iteration.last.next === iteration ) iteration.last.next = iteration.next;
${lookup}[iteration.key] = null;
}
` );
@ -200,7 +200,7 @@ function keyed ( generator, block, state, node, snippet, { each_block, create_ea
var ${each_block_value} = ${snippet};
var ${expected} = ${head};
var ${last};
var ${last} = null;
var discard_pile = [];
@ -216,24 +216,24 @@ function keyed ( generator, block, state, node, snippet, { each_block, create_ea
} else {
if ( ${iteration} ) {
// probably a deletion
do {
while ( ${expected} && ${expected}.key !== ${key} ) {
${expected}.discard = true;
discard_pile.push( ${expected} );
${expected} = ${expected}.next;
} while ( ${expected} && ${expected}.key !== ${key} );
};
${expected} = ${expected} && ${expected}.next;
${iteration}.discard = false;
${iteration}.last = ${last};
${iteration}.next = ${expected};
${iteration}.mount( ${parentNode}, ${expected} ? ${expected}.first : ${anchor} );
if (!${expected}) ${iteration}.mount( ${parentNode}, ${anchor} );
} else {
// key is being inserted
${iteration} = ${lookup}[${key}] = ${create_each_block}( ${params}, ${each_block_value}, ${each_block_value}[${i}], ${i}, ${block.component}, ${key} );
${iteration}.${mountOrIntro}( ${parentNode}, ${expected}.first );
if ( ${expected} ) ${expected}.last = ${iteration};
${expected}.last = ${iteration};
${iteration}.next = ${expected};
}
}
@ -271,7 +271,7 @@ function keyed ( generator, block, state, node, snippet, { each_block, create_ea
` );
}
function unkeyed ( generator, block, state, node, snippet, { create_each_block, each_block_value, iterations, i, params, anchor, mountOrIntro } ) {
function unkeyed ( generator: DomGenerator, block: Block, state: State, node: Node, snippet, { create_each_block, each_block_value, iterations, i, params, anchor, mountOrIntro } ) {
block.builders.create.addBlock( deindent`
var ${iterations} = [];
@ -363,4 +363,4 @@ function unkeyed ( generator, block, state, node, snippet, { create_each_block,
block.builders.destroy.addBlock(
`${generator.helper( 'destroyEach' )}( ${iterations}, ${state.parentNode ? 'false' : 'detach'}, 0 );`
);
}
}

@ -1,8 +1,12 @@
import attributeLookup from './lookup.js';
import attributeLookup from './lookup';
import deindent from '../../../../utils/deindent.js';
import getStaticAttributeValue from './getStaticAttributeValue.js';
import getStaticAttributeValue from './getStaticAttributeValue';
import { DomGenerator } from '../../index';
import Block from '../../Block';
import { Node } from '../../../../interfaces';
import { State } from '../../interfaces';
export default function visitAttribute ( generator, block, state, node, attribute ) {
export default function visitAttribute ( generator: DomGenerator, block: Block, state: State, node: Node, attribute: Node ) {
const name = attribute.name;
let metadata = state.namespace ? null : attributeLookup[ name ];
@ -32,7 +36,7 @@ export default function visitAttribute ( generator, block, state, node, attribut
} else {
// '{{foo}} {{bar}}' — treat as string concatenation
value = ( attribute.value[0].type === 'Text' ? '' : `"" + ` ) + (
attribute.value.map( chunk => {
attribute.value.map( ( chunk: Node ) => {
if ( chunk.type === 'Text' ) {
return JSON.stringify( chunk.data );
} else {

@ -1,9 +1,13 @@
import deindent from '../../../../utils/deindent.js';
import flattenReference from '../../../../utils/flattenReference.js';
import getSetter from '../shared/binding/getSetter.js';
import getStaticAttributeValue from './getStaticAttributeValue.js';
export default function visitBinding ( generator, block, state, node, attribute ) {
import flattenReference from '../../../../utils/flattenReference';
import getSetter from '../shared/binding/getSetter';
import getStaticAttributeValue from './getStaticAttributeValue';
import { DomGenerator } from '../../index';
import Block from '../../Block';
import { Node } from '../../../../interfaces';
import { State } from '../../interfaces';
export default function visitBinding ( generator: DomGenerator, block: Block, state: State, node: Node, attribute: Node ) {
const { name, parts } = flattenReference( attribute.value );
const { snippet, contexts, dependencies } = block.contextualise( attribute.value );
@ -15,7 +19,7 @@ export default function visitBinding ( generator, block, state, node, attribute
const eventName = getBindingEventName( node, attribute );
const handler = block.getUniqueName( `${state.parentNode}_${eventName}_handler` );
const isMultipleSelect = node.name === 'select' && node.attributes.find( attr => attr.name.toLowerCase() === 'multiple' ); // TODO use getStaticAttributeValue
const isMultipleSelect = node.name === 'select' && node.attributes.find( ( attr: Node ) => attr.name.toLowerCase() === 'multiple' ); // TODO use getStaticAttributeValue
const type = getStaticAttributeValue( node, 'type' );
const bindingGroup = attribute.name === 'group' ? getBindingGroup( generator, parts.join( '.' ) ) : null;
const value = getBindingValue( generator, block, state, node, attribute, isMultipleSelect, bindingGroup, type );
@ -141,9 +145,9 @@ export default function visitBinding ( generator, block, state, node, attribute
}
}
function getBindingEventName ( node, attribute ) {
function getBindingEventName ( node: Node, attribute: Node ) {
if ( node.name === 'input' ) {
const typeAttribute = node.attributes.find( attr => attr.type === 'Attribute' && attr.name === 'type' );
const typeAttribute = node.attributes.find( ( attr: Node ) => attr.type === 'Attribute' && attr.name === 'type' );
const type = typeAttribute ? typeAttribute.value[0].data : 'text'; // TODO in validation, should throw if type attribute is not static
return type === 'checkbox' || type === 'radio' ? 'change' : 'input';
@ -157,7 +161,7 @@ function getBindingEventName ( node, attribute ) {
return 'change';
}
function getBindingValue ( generator, block, state, node, attribute, isMultipleSelect, bindingGroup, type ) {
function getBindingValue ( generator: DomGenerator, block: Block, state: State, node: Node, attribute: Node, isMultipleSelect: boolean, bindingGroup: number, type: string ) {
// <select multiple bind:value='selected>
if ( isMultipleSelect ) {
return `[].map.call( ${state.parentNode}.querySelectorAll(':checked'), function ( option ) { return option.__value; })`;
@ -186,7 +190,7 @@ function getBindingValue ( generator, block, state, node, attribute, isMultipleS
return `${state.parentNode}.${attribute.name}`;
}
function getBindingGroup ( generator, keypath ) {
function getBindingGroup ( generator: DomGenerator, keypath: string ) {
// TODO handle contextual bindings — `keypath` should include unique ID of
// each block that provides context
let index = generator.bindingGroups.indexOf( keypath );

@ -1,12 +1,16 @@
import deindent from '../../../../utils/deindent.js';
import visit from '../../visit.js';
import visitComponent from '../Component/Component.js';
import visitWindow from './meta/Window.js';
import visitAttribute from './Attribute.js';
import visitEventHandler from './EventHandler.js';
import visitBinding from './Binding.js';
import visitRef from './Ref.js';
import addTransitions from './addTransitions.js';
import visit from '../../visit';
import visitComponent from '../Component/Component';
import visitWindow from './meta/Window';
import visitAttribute from './Attribute';
import visitEventHandler from './EventHandler';
import visitBinding from './Binding';
import visitRef from './Ref';
import addTransitions from './addTransitions';
import { DomGenerator } from '../../index';
import Block from '../../Block';
import { Node } from '../../../../interfaces';
import { State } from '../../interfaces';
const meta = {
':Window': visitWindow
@ -26,7 +30,7 @@ const visitors = {
Ref: visitRef
};
export default function visitElement ( generator, block, state, node ) {
export default function visitElement ( generator: DomGenerator, block: Block, state: State, node: Node ) {
if ( node.name in meta ) {
return meta[ node.name ]( generator, block, node );
}
@ -46,13 +50,13 @@ export default function visitElement ( generator, block, state, node ) {
block.builders.create.addLine( `${generator.helper( 'setAttribute' )}( ${name}, '${generator.cssId}', '' );` );
}
function visitAttributes () {
function visitAttributesAndAddProps () {
let intro;
let outro;
node.attributes
.sort( ( a, b ) => order[ a.type ] - order[ b.type ] )
.forEach( attribute => {
.sort( ( a: Node, b: Node ) => order[ a.type ] - order[ b.type ] )
.forEach( ( attribute: Node ) => {
if ( attribute.type === 'Transition' ) {
if ( attribute.intro ) intro = attribute;
if ( attribute.outro ) outro = attribute;
@ -63,6 +67,37 @@ export default function visitElement ( generator, block, state, node ) {
});
if ( intro || outro ) addTransitions( generator, block, childState, node, intro, outro );
if ( childState.allUsedContexts.length || childState.usesComponent ) {
const initialProps: string[] = [];
const updates: string[] = [];
if ( childState.usesComponent ) {
initialProps.push( `component: ${block.component}` );
}
childState.allUsedContexts.forEach( ( contextName: string ) => {
if ( contextName === 'state' ) return;
const listName = block.listNames.get( contextName );
const indexName = block.indexNames.get( contextName );
initialProps.push( `${listName}: ${listName},\n${indexName}: ${indexName}` );
updates.push( `${name}._svelte.${listName} = ${listName};\n${name}._svelte.${indexName} = ${indexName};` );
});
if ( initialProps.length ) {
block.builders.create.addBlock( deindent`
${name}._svelte = {
${initialProps.join( ',\n' )}
};
` );
}
if ( updates.length ) {
block.builders.update.addBlock( updates.join( '\n' ) );
}
}
}
if ( !state.parentNode ) {
@ -74,47 +109,16 @@ export default function visitElement ( generator, block, state, node ) {
if ( node.name !== 'select' ) {
// <select> value attributes are an annoying special case — it must be handled
// *after* its children have been updated
visitAttributes();
visitAttributesAndAddProps();
}
// special case bound <option> without a value attribute
if ( node.name === 'option' && !node.attributes.find( attribute => attribute.type === 'Attribute' && attribute.name === 'value' ) ) { // TODO check it's bound
if ( node.name === 'option' && !node.attributes.find( ( attribute: Node ) => attribute.type === 'Attribute' && attribute.name === 'value' ) ) { // TODO check it's bound
const statement = `${name}.__value = ${name}.textContent;`;
node.initialUpdate = node.lateUpdate = statement;
}
if ( childState.allUsedContexts.length || childState.usesComponent ) {
const initialProps = [];
const updates = [];
if ( childState.usesComponent ) {
initialProps.push( `component: ${block.component}` );
}
childState.allUsedContexts.forEach( contextName => {
if ( contextName === 'state' ) return;
const listName = block.listNames.get( contextName );
const indexName = block.indexNames.get( contextName );
initialProps.push( `${listName}: ${listName},\n${indexName}: ${indexName}` );
updates.push( `${name}._svelte.${listName} = ${listName};\n${name}._svelte.${indexName} = ${indexName};` );
});
if ( initialProps.length ) {
block.builders.create.addBlock( deindent`
${name}._svelte = {
${initialProps.join( ',\n' )}
};
` );
}
if ( updates.length ) {
block.builders.update.addBlock( updates.join( '\n' ) );
}
}
node.children.forEach( child => {
node.children.forEach( ( child: Node ) => {
visit( generator, block, childState, child );
});
@ -123,7 +127,7 @@ export default function visitElement ( generator, block, state, node ) {
}
if ( node.name === 'select' ) {
visitAttributes();
visitAttributesAndAddProps();
}
if ( node.initialUpdate ) {
@ -131,7 +135,7 @@ export default function visitElement ( generator, block, state, node ) {
}
}
function getRenderStatement ( generator, namespace, name ) {
function getRenderStatement ( generator: DomGenerator, namespace: string, name: string ) {
if ( namespace === 'http://www.w3.org/2000/svg' ) {
return `${generator.helper( 'createSvgElement' )}( '${name}' )`;
}

@ -1,7 +1,11 @@
import deindent from '../../../../utils/deindent.js';
import flattenReference from '../../../../utils/flattenReference.js';
import flattenReference from '../../../../utils/flattenReference';
import { DomGenerator } from '../../index';
import Block from '../../Block';
import { Node } from '../../../../interfaces';
import { State } from '../../interfaces';
export default function visitEventHandler ( generator, block, state, node, attribute ) {
export default function visitEventHandler ( generator: DomGenerator, block: Block, state: State, node: Node, attribute: Node ) {
const name = attribute.name;
const isCustomEvent = generator.events.has( name );
const shouldHoist = !isCustomEvent && state.inEachBlock;
@ -17,8 +21,8 @@ export default function visitEventHandler ( generator, block, state, node, attri
}
const context = shouldHoist ? null : state.parentNode;
const usedContexts = [];
attribute.expression.arguments.forEach( arg => {
const usedContexts: string[] = [];
attribute.expression.arguments.forEach( ( arg: Node ) => {
const { contexts } = block.contextualise( arg, context, true );
contexts.forEach( context => {

@ -1,6 +1,10 @@
import deindent from '../../../../utils/deindent.js';
import { DomGenerator } from '../../index';
import Block from '../../Block';
import { Node } from '../../../../interfaces';
import { State } from '../../interfaces';
export default function visitRef ( generator, block, state, node, attribute ) {
export default function visitRef ( generator: DomGenerator, block: Block, state: State, node: Node, attribute: Node ) {
const name = attribute.name;
block.builders.create.addLine(

@ -1,6 +1,10 @@
import deindent from '../../../../utils/deindent.js';
import { DomGenerator } from '../../index';
import Block from '../../Block';
import { Node } from '../../../../interfaces';
import { State } from '../../interfaces';
export default function addTransitions ( generator, block, state, node, intro, outro ) {
export default function addTransitions ( generator: DomGenerator, block: Block, state: State, node: Node, intro, outro ) {
const wrapTransition = generator.helper( 'wrapTransition' );
if ( intro === outro ) {

@ -1,5 +1,7 @@
export default function getStaticAttributeValue ( node, name ) {
const attribute = node.attributes.find( attr => attr.name.toLowerCase() === name );
import { Node } from '../../../../interfaces';
export default function getStaticAttributeValue ( node: Node, name: string ) {
const attribute = node.attributes.find( ( attr: Node ) => attr.name.toLowerCase() === name );
if ( !attribute ) return null;
if ( attribute.value.length !== 1 || attribute.value[0].type !== 'Text' ) {

@ -1,5 +1,8 @@
import flattenReference from '../../../../../utils/flattenReference.js';
import flattenReference from '../../../../../utils/flattenReference';
import deindent from '../../../../../utils/deindent.js';
import { DomGenerator } from '../../../index';
import Block from '../../../Block';
import { Node } from '../../../../../interfaces';
const associatedEvents = {
innerWidth: 'resize',
@ -19,18 +22,18 @@ const readonly = new Set([
'online'
]);
export default function visitWindow ( generator, block, node ) {
export default function visitWindow ( generator: DomGenerator, block: Block, node: Node ) {
const events = {};
const bindings = {};
node.attributes.forEach( attribute => {
node.attributes.forEach( ( attribute: Node ) => {
if ( attribute.type === 'EventHandler' ) {
// TODO verify that it's a valid callee (i.e. built-in or declared method)
generator.addSourcemapLocations( attribute.expression );
let usesState = false;
attribute.expression.arguments.forEach( arg => {
attribute.expression.arguments.forEach( ( arg: Node ) => {
const { contexts } = block.contextualise( arg, null, true );
if ( contexts.length ) usesState = true;
});

@ -1,7 +1,11 @@
import deindent from '../../../utils/deindent.js';
import visit from '../visit.js';
import visit from '../visit';
import { DomGenerator } from '../index';
import Block from '../Block';
import { Node } from '../../../interfaces';
import { State } from '../interfaces';
function isElseIf ( node ) {
function isElseIf ( node: Node ) {
return node && node.children.length === 1 && node.children[0].type === 'IfBlock';
}
@ -9,7 +13,7 @@ function isElseBranch ( branch ) {
return branch.block && !branch.condition;
}
function getBranches ( generator, block, state, node ) {
function getBranches ( generator: DomGenerator, block: Block, state: State, node: Node ) {
const branches = [{
condition: block.contextualise( node.expression ).snippet,
block: node._block.name,
@ -41,13 +45,13 @@ function getBranches ( generator, block, state, node ) {
return branches;
}
function visitChildren ( generator, block, state, node ) {
node.children.forEach( child => {
function visitChildren ( generator: DomGenerator, block: Block, state: State, node: Node ) {
node.children.forEach( ( child: Node ) => {
visit( generator, node._block, node._state, child );
});
}
export default function visitIfBlock ( generator, block, state, node ) {
export default function visitIfBlock ( generator: DomGenerator, block: Block, state: State, node: Node ) {
const name = generator.getUniqueName( `if_block` );
const anchor = node.needsAnchor ? block.getUniqueName( `${name}_anchor` ) : ( node.next && node.next._state.name ) || 'null';
const params = block.params.join( ', ' );
@ -79,7 +83,7 @@ export default function visitIfBlock ( generator, block, state, node ) {
}
}
function simple ( generator, block, state, node, branch, dynamic, { name, anchor, params, if_name } ) {
function simple ( generator: DomGenerator, block: Block, state: State, node: Node, branch, dynamic, { name, anchor, params, if_name } ) {
block.builders.create.addBlock( deindent`
var ${name} = (${branch.condition}) && ${branch.block}( ${params}, ${block.component} );
` );
@ -153,7 +157,7 @@ function simple ( generator, block, state, node, branch, dynamic, { name, anchor
);
}
function compound ( generator, block, state, node, branches, dynamic, { name, anchor, params, hasElse, if_name } ) {
function compound ( generator: DomGenerator, block: Block, state: State, node: Node, branches, dynamic, { name, anchor, params, hasElse, if_name } ) {
const get_block = block.getUniqueName( `get_block` );
const current_block = block.getUniqueName( `current_block` );
const current_block_and = hasElse ? '' : `${current_block} && `;
@ -209,7 +213,7 @@ function compound ( generator, block, state, node, branches, dynamic, { name, an
// if any of the siblings have outros, we need to keep references to the blocks
// (TODO does this only apply to bidi transitions?)
function compoundWithOutros ( generator, block, state, node, branches, dynamic, { name, anchor, params, hasElse } ) {
function compoundWithOutros ( generator: DomGenerator, block: Block, state: State, node: Node, branches, dynamic, { name, anchor, params, hasElse } ) {
const get_block = block.getUniqueName( `get_block` );
const current_block_index = block.getUniqueName( `current_block_index` );
const previous_block_index = block.getUniqueName( `previous_block_index` );

@ -1,6 +1,10 @@
import deindent from '../../../utils/deindent.js';
import { DomGenerator } from '../index';
import Block from '../Block';
import { Node } from '../../../interfaces';
import { State } from '../interfaces';
export default function visitMustacheTag ( generator, block, state, node ) {
export default function visitMustacheTag ( generator: DomGenerator, block: Block, state: State, node: Node ) {
const name = node._state.name;
const value = block.getUniqueName( `${name}_value` );

@ -1,6 +1,10 @@
import deindent from '../../../utils/deindent.js';
import { DomGenerator } from '../index';
import Block from '../Block';
import { Node } from '../../../interfaces';
import { State } from '../interfaces';
export default function visitRawMustacheTag ( generator, block, state, node ) {
export default function visitRawMustacheTag ( generator: DomGenerator, block: Block, state: State, node: Node ) {
const name = node._state.basename;
const before = node._state.name;
const value = block.getUniqueName( `${name}_value` );

@ -1,6 +0,0 @@
export default function visitText ( generator, block, state, node ) {
if ( !node._state.shouldCreate ) return;
block.addElement( node._state.name, `${generator.helper( 'createText' )}( ${JSON.stringify( node.data )} )`, state.parentNode, node.usedAsAnchor );
}

@ -0,0 +1,9 @@
import { DomGenerator } from '../index';
import Block from '../Block';
import { Node } from '../../../interfaces';
import { State } from '../interfaces';
export default function visitText ( generator: DomGenerator, block: Block, state: State, node: Node ) {
if ( !node._state.shouldCreate ) return;
block.addElement( node._state.name, `${generator.helper( 'createText' )}( ${JSON.stringify( node.data )} )`, state.parentNode, node.usedAsAnchor );
}

@ -1,4 +1,8 @@
export default function visitYieldTag ( generator, block, state ) {
import { DomGenerator } from '../index';
import Block from '../Block';
import { State } from '../interfaces';
export default function visitYieldTag ( generator: DomGenerator, block: Block, state: State ) {
const parentNode = state.parentNode || block.target;
( state.parentNode ? block.builders.create : block.builders.mount ).addLine(

@ -1,17 +0,0 @@
import EachBlock from './EachBlock.js';
import Element from './Element/Element.js';
import IfBlock from './IfBlock.js';
import MustacheTag from './MustacheTag.js';
import RawMustacheTag from './RawMustacheTag.js';
import Text from './Text.js';
import YieldTag from './YieldTag.js';
export default {
EachBlock,
Element,
IfBlock,
MustacheTag,
RawMustacheTag,
Text,
YieldTag
};

@ -0,0 +1,17 @@
import EachBlock from './EachBlock';
import Element from './Element/Element';
import IfBlock from './IfBlock';
import MustacheTag from './MustacheTag';
import RawMustacheTag from './RawMustacheTag';
import Text from './Text';
import YieldTag from './YieldTag';
export default {
EachBlock,
Element,
IfBlock,
MustacheTag,
RawMustacheTag,
Text,
YieldTag
};

@ -1,12 +1,25 @@
import deindent from '../../utils/deindent.js';
import flattenReference from '../../utils/flattenReference.js';
import flattenReference from '../../utils/flattenReference';
import { SsrGenerator } from './index';
import { Node } from '../../interfaces';
interface BlockOptions {
// TODO
}
export default class Block {
constructor ( options ) {
generator: SsrGenerator;
conditions: string[];
contexts: Map<string, string>;
indexes: Map<string, string>;
contextDependencies: Map<string, string[]>;
constructor ( options: BlockOptions ) {
Object.assign( this, options );
}
addBinding ( binding, name ) {
addBinding ( binding: Node, name: string ) {
const conditions = [ `!( '${binding.name}' in state )`].concat( // TODO handle contextual bindings...
this.conditions.map( c => `(${c})` )
);
@ -24,11 +37,11 @@ export default class Block {
` );
}
child ( options ) {
child ( options: BlockOptions ) {
return new Block( Object.assign( {}, this, options, { parent: this } ) );
}
contextualise ( expression, context, isEventHandler ) {
contextualise ( expression: Node, context?: string, isEventHandler?: boolean ) {
return this.generator.contextualise( this, expression, context, isEventHandler );
}
}

@ -1,21 +1,26 @@
import deindent from '../../utils/deindent.js';
import Generator from '../Generator.js';
import Block from './Block.js';
import visit from './visit.js';
import Generator from '../Generator';
import Block from './Block';
import visit from './visit';
import { Parsed, Node, CompileOptions } from '../../interfaces';
class SsrGenerator extends Generator {
constructor ( parsed, source, name, options ) {
export class SsrGenerator extends Generator {
bindings: string[];
renderCode: string;
elementDepth: number;
constructor ( parsed: Parsed, source: string, name: string, options: CompileOptions ) {
super( parsed, source, name, options );
this.bindings = [];
this.renderCode = '';
}
append ( code ) {
append ( code: string ) {
this.renderCode += code;
}
}
export default function ssr ( parsed, source, options ) {
export default function ssr ( parsed: Parsed, source: string, options: CompileOptions ) {
const format = options.format || 'cjs';
const name = options.name || 'SvelteComponent';
@ -31,7 +36,7 @@ export default function ssr ( parsed, source, options ) {
conditions: []
});
parsed.html.children.forEach( node => {
parsed.html.children.forEach( ( node: Node ) => {
visit( generator, mainBlock, node );
});

@ -1,6 +0,0 @@
import visitors from './visitors/index.js';
export default function visit ( generator, fragment, node ) {
const visitor = visitors[ node.type ];
visitor( generator, fragment, node );
}

@ -0,0 +1,9 @@
import visitors from './visitors/index';
import { SsrGenerator } from './index';
import Block from './Block';
import { Node } from '../../interfaces';
export default function visit ( generator: SsrGenerator, block: Block, node: Node ) {
const visitor = visitors[ node.type ];
visitor( generator, block, node );
}

@ -1,8 +1,11 @@
import flattenReference from '../../../utils/flattenReference.js';
import visit from '../visit.js';
import flattenReference from '../../../utils/flattenReference';
import visit from '../visit';
import { SsrGenerator } from '../index';
import Block from '../Block';
import { Node } from '../../../interfaces';
export default function visitComponent ( generator, block, node ) {
function stringify ( chunk ) {
export default function visitComponent ( generator: SsrGenerator, block: Block, node: Node ) {
function stringify ( chunk: Node ) {
if ( chunk.type === 'Text' ) return chunk.data;
if ( chunk.type === 'MustacheTag' ) {
const { snippet } = block.contextualise( chunk.expression );
@ -10,10 +13,10 @@ export default function visitComponent ( generator, block, node ) {
}
}
const attributes = [];
const bindings = [];
const attributes: Node[] = [];
const bindings: Node[] = [];
node.attributes.forEach( attribute => {
node.attributes.forEach( ( attribute: Node ) => {
if ( attribute.type === 'Attribute' ) {
attributes.push( attribute );
} else if ( attribute.type === 'Binding' ) {
@ -66,7 +69,7 @@ export default function visitComponent ( generator, block, node ) {
generator.elementDepth += 1;
node.children.forEach( child => {
node.children.forEach( ( child: Node ) => {
visit( generator, block, child );
});

@ -1,6 +1,9 @@
import visit from '../visit.js';
import visit from '../visit';
import { SsrGenerator } from '../index';
import Block from '../Block';
import { Node } from '../../../interfaces';
export default function visitEachBlock ( generator, block, node ) {
export default function visitEachBlock ( generator: SsrGenerator, block: Block, node: Node ) {
const { dependencies, snippet } = block.contextualise( node.expression );
const open = `\${ ${snippet}.map( ${ node.index ? `( ${node.context}, ${node.index} )` : node.context} => \``;
@ -23,7 +26,7 @@ export default function visitEachBlock ( generator, block, node ) {
contextDependencies
});
node.children.forEach( child => {
node.children.forEach( ( child: Node ) => {
visit( generator, childBlock, child );
});

@ -1,13 +1,16 @@
import visitComponent from './Component.js';
import isVoidElementName from '../../../utils/isVoidElementName.js';
import visit from '../visit.js';
import visitWindow from './meta/Window.js';
import visitComponent from './Component';
import isVoidElementName from '../../../utils/isVoidElementName';
import visit from '../visit';
import visitWindow from './meta/Window';
import { SsrGenerator } from '../index';
import Block from '../Block';
import { Node } from '../../../interfaces';
const meta = {
':Window': visitWindow
};
export default function visitElement ( generator, block, node ) {
export default function visitElement ( generator: SsrGenerator, block: Block, node: Node ) {
if ( node.name in meta ) {
return meta[ node.name ]( generator, block, node );
}
@ -19,13 +22,13 @@ export default function visitElement ( generator, block, node ) {
let openingTag = `<${node.name}`;
node.attributes.forEach( attribute => {
node.attributes.forEach( ( attribute: Node ) => {
if ( attribute.type !== 'Attribute' ) return;
let str = ` ${attribute.name}`;
if ( attribute.value !== true ) {
str += `="` + attribute.value.map( chunk => {
str += `="` + attribute.value.map( ( chunk: Node ) => {
if ( chunk.type === 'Text' ) {
return chunk.data;
}
@ -48,7 +51,7 @@ export default function visitElement ( generator, block, node ) {
generator.elementDepth += 1;
node.children.forEach( child => {
node.children.forEach( ( child: Node ) => {
visit( generator, block, child );
});

@ -1,6 +1,9 @@
import visit from '../visit.js';
import visit from '../visit';
import { SsrGenerator } from '../index';
import Block from '../Block';
import { Node } from '../../../interfaces';
export default function visitIfBlock ( generator, block, node ) {
export default function visitIfBlock ( generator: SsrGenerator, block: Block, node: Node ) {
const { snippet } = block.contextualise( node.expression );
generator.append( '${ ' + snippet + ' ? `' );
@ -9,14 +12,14 @@ export default function visitIfBlock ( generator, block, node ) {
conditions: block.conditions.concat( snippet )
});
node.children.forEach( child => {
node.children.forEach( ( child: Node ) => {
visit( generator, childBlock, child );
});
generator.append( '` : `' );
if ( node.else ) {
node.else.children.forEach( child => {
node.else.children.forEach( ( child: Node ) => {
visit( generator, childBlock, child );
});
}

@ -1,4 +0,0 @@
export default function visitMustacheTag ( generator, block, node ) {
const { snippet } = block.contextualise( node.expression );
generator.append( '${__escape( ' + snippet + ' )}' );
}

@ -0,0 +1,8 @@
import { SsrGenerator } from '../index';
import Block from '../Block';
import { Node } from '../../../interfaces';
export default function visitMustacheTag ( generator: SsrGenerator, block: Block, node: Node ) {
const { snippet } = block.contextualise( node.expression );
generator.append( '${__escape( ' + snippet + ' )}' );
}

@ -1,4 +0,0 @@
export default function visitRawMustacheTag ( generator, block, node ) {
const { snippet } = block.contextualise( node.expression );
generator.append( '${' + snippet + '}' );
}

@ -0,0 +1,8 @@
import { SsrGenerator } from '../index';
import Block from '../Block';
import { Node } from '../../../interfaces';
export default function visitRawMustacheTag ( generator: SsrGenerator, block: Block, node: Node ) {
const { snippet } = block.contextualise( node.expression );
generator.append( '${' + snippet + '}' );
}

@ -1,3 +0,0 @@
export default function visitText ( generator, block, node ) {
generator.append( node.data.replace( /\${/g, '\\${' ) );
}

@ -0,0 +1,7 @@
import { SsrGenerator } from '../index';
import Block from '../Block';
import { Node } from '../../../interfaces';
export default function visitText ( generator: SsrGenerator, block: Block, node: Node ) {
generator.append( node.data.replace( /\${/g, '\\${' ) );
}

@ -1,3 +0,0 @@
export default function visitYieldTag ( generator ) {
generator.append( `\${options && options.yield ? options.yield() : ''}` );
}

@ -0,0 +1,5 @@
import { SsrGenerator } from '../index';
export default function visitYieldTag ( generator: SsrGenerator ) {
generator.append( `\${options && options.yield ? options.yield() : ''}` );
}

@ -1,19 +0,0 @@
import Comment from './Comment.js';
import EachBlock from './EachBlock.js';
import Element from './Element.js';
import IfBlock from './IfBlock.js';
import MustacheTag from './MustacheTag.js';
import RawMustacheTag from './RawMustacheTag.js';
import Text from './Text.js';
import YieldTag from './YieldTag.js';
export default {
Comment,
EachBlock,
Element,
IfBlock,
MustacheTag,
RawMustacheTag,
Text,
YieldTag
};

@ -0,0 +1,19 @@
import Comment from './Comment';
import EachBlock from './EachBlock';
import Element from './Element';
import IfBlock from './IfBlock';
import MustacheTag from './MustacheTag';
import RawMustacheTag from './RawMustacheTag';
import Text from './Text';
import YieldTag from './YieldTag';
export default {
Comment,
EachBlock,
Element,
IfBlock,
MustacheTag,
RawMustacheTag,
Text,
YieldTag
};

@ -1,6 +1,8 @@
import { Parsed, Node } from '../../interfaces';
const commentsPattern = /\/\*[\s\S]*?\*\//g;
export default function processCss ( parsed, code ) {
export default function processCss ( parsed: Parsed, code ) {
const css = parsed.css.content.styles;
const offset = parsed.css.content.start;
@ -8,9 +10,9 @@ export default function processCss ( parsed, code ) {
const keyframes = new Map();
function walkKeyframes ( node ) {
function walkKeyframes ( node: Node ) {
if ( node.type === 'Atrule' && node.name.toLowerCase() === 'keyframes' ) {
node.expression.children.forEach( expression => {
node.expression.children.forEach( ( expression: Node ) => {
if ( expression.type === 'Identifier' ) {
const newName = `svelte-${parsed.hash}-${expression.name}`;
code.overwrite( expression.start, expression.end, newName );
@ -26,8 +28,8 @@ export default function processCss ( parsed, code ) {
parsed.css.children.forEach( walkKeyframes );
function transform ( rule ) {
rule.selector.children.forEach( selector => {
function transform ( rule: Node ) {
rule.selector.children.forEach( ( selector: Node ) => {
const start = selector.start - offset;
const end = selector.end - offset;
@ -50,11 +52,11 @@ export default function processCss ( parsed, code ) {
code.overwrite( start + offset, end + offset, transformed );
});
rule.block.children.forEach( block => {
rule.block.children.forEach( ( block: Node ) => {
if ( block.type === 'Declaration' ) {
const property = block.property.toLowerCase();
if ( property === 'animation' || property === 'animation-name' ) {
block.value.children.forEach( block => {
block.value.children.forEach( ( block: Node ) => {
if ( block.type === 'Identifier' ) {
const name = block.name;
if ( keyframes.has( name ) ) {
@ -67,7 +69,7 @@ export default function processCss ( parsed, code ) {
});
}
function walk ( node ) {
function walk ( node: Node ) {
if ( node.type === 'Rule' ) {
transform( node );
} else if ( node.type === 'Atrule' && node.name.toLowerCase() === 'keyframes' ) {

@ -1,4 +1,9 @@
export default function getGlobals ( imports, { globals, onerror, onwarn } ) {
import { Declaration, Options } from './getIntro';
export type Globals = (id: string) => any;
export default function getGlobals ( imports: Declaration[], options: Options ) {
const { globals, onerror, onwarn } = options;
const globalFn = getGlobalFn( globals );
return imports.map( x => {
@ -33,7 +38,7 @@ export default function getGlobals ( imports, { globals, onerror, onwarn } ) {
});
}
function getGlobalFn ( globals ) {
function getGlobalFn ( globals: any ): Globals {
if ( typeof globals === 'function' ) return globals;
if ( typeof globals === 'object' ) {
return id => globals[ id ];

@ -1,7 +1,26 @@
import deindent from '../../../utils/deindent.js';
import getGlobals from './getGlobals.js';
import getGlobals, { Globals } from './getGlobals';
export type ModuleFormat = "es" | "amd" | "cjs" | "iife" | "umd" | "eval";
export interface Options {
name: string;
amd?: {
id?: string;
};
globals: Globals | object;
onerror: (err: Error) => void;
onwarn: (obj: Error | { message: string }) => void;
}
export interface Declaration {
name: string;
source: {
value: string;
};
}
export default function getIntro ( format, options, imports ) {
export default function getIntro ( format: ModuleFormat, options: Options, imports: Declaration[] ) {
if ( format === 'es' ) return '';
if ( format === 'amd' ) return getAmdIntro( options, imports );
if ( format === 'cjs' ) return getCjsIntro( options, imports );
@ -12,7 +31,7 @@ export default function getIntro ( format, options, imports ) {
throw new Error( `Not implemented: ${format}` );
}
function getAmdIntro ( options, imports ) {
function getAmdIntro ( options: Options, imports: Declaration[] ) {
const sourceString = imports.length ?
`[ ${imports.map( declaration => `'${removeExtension( declaration.source.value )}'` ).join( ', ' )} ], ` :
'';
@ -22,7 +41,7 @@ function getAmdIntro ( options, imports ) {
return `define(${id ? ` '${id}', ` : ''}${sourceString}function (${paramString( imports )}) { 'use strict';\n\n`;
}
function getCjsIntro ( options, imports ) {
function getCjsIntro ( options: Options, imports: Declaration[] ) {
const requireBlock = imports
.map( declaration => `var ${declaration.name} = require( '${declaration.source.value}' );` )
.join( '\n\n' );
@ -34,7 +53,7 @@ function getCjsIntro ( options, imports ) {
return `'use strict';\n\n`;
}
function getIifeIntro ( options, imports ) {
function getIifeIntro ( options: Options, imports: Declaration[] ) {
if ( !options.name ) {
throw new Error( `Missing required 'name' option for IIFE export` );
}
@ -42,7 +61,7 @@ function getIifeIntro ( options, imports ) {
return `var ${options.name} = (function (${paramString( imports )}) { 'use strict';\n\n`;
}
function getUmdIntro ( options, imports ) {
function getUmdIntro ( options: Options, imports: Declaration[] ) {
if ( !options.name ) {
throw new Error( `Missing required 'name' option for UMD export` );
}
@ -61,15 +80,15 @@ function getUmdIntro ( options, imports ) {
}(this, (function (${paramString( imports )}) { 'use strict';` + '\n\n';
}
function getEvalIntro ( options, imports ) {
function getEvalIntro ( options: Options, imports: Declaration[] ) {
return `(function (${paramString( imports )}) { 'use strict';\n\n`;
}
function paramString ( imports ) {
function paramString ( imports: Declaration[] ) {
return imports.length ? ` ${imports.map( dep => dep.name ).join( ', ' )} ` : '';
}
function removeExtension ( file ) {
function removeExtension ( file: string ) {
const index = file.lastIndexOf( '.' );
return ~index ? file.slice( 0, index ) : file;
}

@ -1,6 +1,6 @@
import getGlobals from './getGlobals.js';
import getGlobals from './getGlobals';
export default function getOutro ( format, name, options, imports ) {
export default function getOutro ( format: string, name: string, options, imports ) {
if ( format === 'es' ) {
return `export default ${name};`;
}

@ -1,12 +1,14 @@
export default function walkHtml ( html, visitors ) {
function visit ( node ) {
import { Node } from '../../../interfaces';
export default function walkHtml ( html: Node, visitors ) {
function visit ( node: Node ) {
const visitor = visitors[ node.type ];
if ( !visitor ) throw new Error( `Not implemented: ${node.type}` );
if ( visitor.enter ) visitor.enter( node );
if ( node.children ) {
node.children.forEach( child => {
node.children.forEach( ( child: Node ) => {
visit( child );
});
}

@ -1,18 +1,19 @@
import parse from './parse/index.js';
import validate from './validate/index.js';
import generate from './generators/dom/index.js';
import generateSSR from './generators/server-side-rendering/index.js';
import parse from './parse/index';
import validate from './validate/index';
import generate from './generators/dom/index';
import generateSSR from './generators/server-side-rendering/index';
import { assign } from './shared/index.js';
import { version } from '../package.json';
import { Parsed, CompileOptions, Warning } from './interfaces';
function normalizeOptions ( options ) {
return assign( {
function normalizeOptions ( options: CompileOptions ) :CompileOptions {
return assign({
generate: 'dom',
// a filename is necessary for sourcemap generation
filename: 'SvelteComponent.html',
onwarn: warning => {
onwarn: ( warning: Warning ) => {
if ( warning.loc ) {
console.warn( `(${warning.loc.line}:${warning.loc.column}) ${warning.message}` ); // eslint-disable-line no-console
} else {
@ -20,16 +21,16 @@ function normalizeOptions ( options ) {
}
},
onerror: error => {
onerror: ( error: Error ) => {
throw error;
}
}, options );
}
export function compile ( source, _options ) {
export function compile ( source: string, _options: CompileOptions ) {
const options = normalizeOptions( _options );
let parsed;
let parsed: Parsed;
try {
parsed = parse( source, options );
@ -47,7 +48,7 @@ export function compile ( source, _options ) {
return compiler( parsed, source, options );
}
export function create ( source, _options = {} ) {
export function create ( source: string, _options: CompileOptions = {} ) {
_options.format = 'eval';
const compiled = compile( source, _options );

@ -0,0 +1,46 @@
export interface Node {
start: number;
end: number;
type: string;
[propName: string]: any;
}
export interface Parser {
readonly template: string;
readonly filename?: string;
index: number;
stack: Array<Node>;
html: Node;
css: Node;
js: Node;
metaTags: {};
}
export interface Parsed {
hash: number;
html: Node;
css: Node;
js: Node;
}
export interface Warning {
loc?: {line: number, column: number, pos: number};
message: string
filename?: string
toString: () => string
}
export interface CompileOptions {
format?: string;
name?: string;
filename?: string;
generate?: string;
dev?: boolean;
shared?: boolean | string;
onerror?: (error: Error) => void
onwarn?: (warning: Warning) => void
}

@ -1,179 +0,0 @@
import { locate } from 'locate-character';
import fragment from './state/fragment.js';
import { whitespace } from '../utils/patterns.js';
import { trimStart, trimEnd } from '../utils/trim.js';
import getCodeFrame from '../utils/getCodeFrame.js';
import hash from './utils/hash.js';
function ParseError ( message, template, index, filename ) {
const { line, column } = locate( template, index );
this.name = 'ParseError';
this.message = message;
this.frame = getCodeFrame( template, line, column );
this.loc = { line: line + 1, column };
this.pos = index;
this.filename = filename;
}
ParseError.prototype.toString = function () {
return `${this.message} (${this.loc.line}:${this.loc.column})\n${this.frame}`;
};
export default function parse ( template, options = {} ) {
if ( typeof template !== 'string' ) {
throw new TypeError( 'Template must be a string' );
}
template = template.replace( /\s+$/, '' );
const parser = {
index: 0,
template,
stack: [],
metaTags: {},
current () {
return this.stack[ this.stack.length - 1 ];
},
acornError ( err ) {
parser.error( err.message.replace( / \(\d+:\d+\)$/, '' ), err.pos );
},
error ( message, index = this.index ) {
throw new ParseError( message, this.template, index, options.filename );
},
eat ( str, required ) {
if ( this.match( str ) ) {
this.index += str.length;
return true;
}
if ( required ) {
this.error( `Expected ${str}` );
}
},
match ( str ) {
return this.template.slice( this.index, this.index + str.length ) === str;
},
allowWhitespace () {
while ( this.index < this.template.length && whitespace.test( this.template[ this.index ] ) ) {
this.index++;
}
},
read ( pattern ) {
const match = pattern.exec( this.template.slice( this.index ) );
if ( !match || match.index !== 0 ) return null;
parser.index += match[0].length;
return match[0];
},
readUntil ( pattern ) {
if ( this.index >= this.template.length ) parser.error( 'Unexpected end of input' );
const start = this.index;
const match = pattern.exec( this.template.slice( start ) );
if ( match ) {
const start = this.index;
this.index = start + match.index;
return this.template.slice( start, this.index );
}
this.index = this.template.length;
return this.template.slice( start );
},
remaining () {
return this.template.slice( this.index );
},
requireWhitespace () {
if ( !whitespace.test( this.template[ this.index ] ) ) {
this.error( `Expected whitespace` );
}
this.allowWhitespace();
},
html: {
start: null,
end: null,
type: 'Fragment',
children: []
},
css: null,
js: null
};
parser.stack.push( parser.html );
let state = fragment;
while ( parser.index < parser.template.length ) {
state = state( parser ) || fragment;
}
if ( parser.stack.length > 1 ) {
const current = parser.current();
const type = current.type === 'Element' ? `<${current.name}>` : 'Block';
parser.error( `${type} was left open`, current.start );
}
if ( state !== fragment ) {
parser.error( 'Unexpected end of input' );
}
// trim unnecessary whitespace
while ( parser.html.children.length ) {
const firstChild = parser.html.children[0];
parser.html.start = firstChild.start;
if ( firstChild.type !== 'Text' ) break;
const length = firstChild.data.length;
firstChild.data = trimStart( firstChild.data );
if ( firstChild.data === '' ) {
parser.html.children.shift();
} else {
parser.html.start += length - firstChild.data.length;
break;
}
}
while ( parser.html.children.length ) {
const lastChild = parser.html.children[ parser.html.children.length - 1 ];
parser.html.end = lastChild.end;
if ( lastChild.type !== 'Text' ) break;
const length = lastChild.data.length;
lastChild.data = trimEnd( lastChild.data );
if ( lastChild.data === '' ) {
parser.html.children.pop();
} else {
parser.html.end -= length - lastChild.data.length;
break;
}
}
return {
hash: hash( template ),
html: parser.html,
css: parser.css,
js: parser.js
};
}

@ -0,0 +1,190 @@
import { locate, Location } from 'locate-character';
import fragment from './state/fragment';
import { whitespace } from '../utils/patterns';
import { trimStart, trimEnd } from '../utils/trim';
import getCodeFrame from '../utils/getCodeFrame';
import hash from './utils/hash';
import { Node, Parsed } from '../interfaces';
import CompileError from '../utils/CompileError'
class ParseError extends CompileError {
constructor ( message: string, template: string, index: number, filename: string ) {
super( message, template, index, filename );
this.name = 'ParseError';
}
}
interface ParserOptions {
filename?: string
}
export class Parser {
readonly template: string;
readonly filename?: string;
index: number;
stack: Array<Node>;
html: Node;
css: Node;
js: Node;
metaTags: {}
constructor ( template: string, options: ParserOptions ) {
if ( typeof template !== 'string' ) {
throw new TypeError( 'Template must be a string' );
}
this.template = template.replace( /\s+$/, '' );
this.filename = options.filename;
this.index = 0;
this.stack = [];
this.metaTags = {};
this.html = {
start: null,
end: null,
type: 'Fragment',
children: []
};
this.css = null;
this.js = null;
this.stack.push( this.html );
let state = fragment;
while ( this.index < this.template.length ) {
state = state( this ) || fragment;
}
if ( this.stack.length > 1 ) {
const current = this.current();
const type = current.type === 'Element' ? `<${current.name}>` : 'Block';
this.error( `${type} was left open`, current.start );
}
if ( state !== fragment ) {
this.error( 'Unexpected end of input' );
}
// trim unnecessary whitespace
while ( this.html.children.length ) {
const firstChild = this.html.children[0];
this.html.start = firstChild.start;
if ( firstChild.type !== 'Text' ) break;
const length = firstChild.data.length;
firstChild.data = trimStart( firstChild.data );
if ( firstChild.data === '' ) {
this.html.children.shift();
} else {
this.html.start += length - firstChild.data.length;
break;
}
}
while ( this.html.children.length ) {
const lastChild = this.html.children[ this.html.children.length - 1 ];
this.html.end = lastChild.end;
if ( lastChild.type !== 'Text' ) break;
const length = lastChild.data.length;
lastChild.data = trimEnd( lastChild.data );
if ( lastChild.data === '' ) {
this.html.children.pop();
} else {
this.html.end -= length - lastChild.data.length;
break;
}
}
}
current () {
return this.stack[ this.stack.length - 1 ];
}
acornError ( err: Error ) {
this.error( err.message.replace( / \(\d+:\d+\)$/, '' ), err.pos );
}
error ( message: string, index = this.index ) {
throw new ParseError( message, this.template, index, this.filename );
}
eat ( str: string, required?: boolean ) {
if ( this.match( str ) ) {
this.index += str.length;
return true;
}
if ( required ) {
this.error( `Expected ${str}` );
}
}
match ( str: string ) {
return this.template.slice( this.index, this.index + str.length ) === str;
}
allowWhitespace () {
while ( this.index < this.template.length && whitespace.test( this.template[ this.index ] ) ) {
this.index++;
}
}
read ( pattern: RegExp ) {
const match = pattern.exec( this.template.slice( this.index ) );
if ( !match || match.index !== 0 ) return null;
this.index += match[0].length;
return match[0];
}
readUntil ( pattern: RegExp ) {
if ( this.index >= this.template.length ) this.error( 'Unexpected end of input' );
const start = this.index;
const match = pattern.exec( this.template.slice( start ) );
if ( match ) {
const start = this.index;
this.index = start + match.index;
return this.template.slice( start, this.index );
}
this.index = this.template.length;
return this.template.slice( start );
}
remaining () {
return this.template.slice( this.index );
}
requireWhitespace () {
if ( !whitespace.test( this.template[ this.index ] ) ) {
this.error( `Expected whitespace` );
}
this.allowWhitespace();
}
}
export default function parse ( template: string, options: ParserOptions = {} ) :Parsed {
const parser = new Parser( template, options );
return {
hash: hash( parser.template ),
html: parser.html,
css: parser.css,
js: parser.js
};
}

@ -1,7 +1,8 @@
import { parseExpressionAt } from 'acorn';
import spaces from '../../utils/spaces.js';
import { Parser } from '../index';
function readExpression ( parser, start, quoteMark ) {
function readExpression ( parser: Parser, start: number, quoteMark ) {
let str = '';
let escaped = false;
@ -43,7 +44,7 @@ function readExpression ( parser, start, quoteMark ) {
return expression;
}
export function readEventHandlerDirective ( parser, start, name ) {
export function readEventHandlerDirective ( parser: Parser, start: number, name: string ) {
const quoteMark = (
parser.eat( `'` ) ? `'` :
parser.eat( `"` ) ? `"` :
@ -67,7 +68,7 @@ export function readEventHandlerDirective ( parser, start, name ) {
};
}
export function readBindingDirective ( parser, start, name ) {
export function readBindingDirective ( parser: Parser, start: number, name: string ) {
let value;
if ( parser.eat( '=' ) ) {
@ -130,7 +131,7 @@ export function readBindingDirective ( parser, start, name ) {
};
}
export function readTransitionDirective ( parser, start, name, type ) {
export function readTransitionDirective ( parser: Parser, start: number, name: string, type: string ) {
let expression = null;
if ( parser.eat( '=' ) ) {

@ -1,4 +1,5 @@
import { parseExpressionAt } from 'acorn';
import { Parser } from '../index';
const literals = new Map([
[ 'true', true ],
@ -6,7 +7,7 @@ const literals = new Map([
[ 'null', null ]
]);
export default function readExpression ( parser ) {
export default function readExpression ( parser: Parser ) {
const start = parser.index;
const name = parser.readUntil( /\s*}}/ );

@ -1,9 +1,10 @@
import { parse } from 'acorn';
import spaces from '../../utils/spaces.js';
import { Parser } from '../index';
const scriptClosingTag = '<\/script>';
export default function readScript ( parser, start, attributes ) {
export default function readScript ( parser: Parser, start: number, attributes ) {
const scriptStart = parser.index;
const scriptEnd = parser.template.indexOf( scriptClosingTag, scriptStart );

@ -1,7 +1,8 @@
import parse from 'css-tree/lib/parser/index.js';
import walk from 'css-tree/lib/utils/walk.js';
import { Parser } from '../index';
export default function readStyle ( parser, start, attributes ) {
export default function readStyle ( parser: Parser, start: number, attributes ) {
const contentStart = parser.index;
const styles = parser.readUntil( /<\/style>/ );
const contentEnd = parser.index;

@ -1,15 +0,0 @@
import tag from './tag.js';
import mustache from './mustache.js';
import text from './text.js';
export default function fragment ( parser ) {
if ( parser.match( '<' ) ) {
return tag;
}
if ( parser.match( '{{' ) ) {
return mustache;
}
return text;
}

@ -0,0 +1,16 @@
import tag from './tag';
import mustache from './mustache';
import text from './text';
import { Parser } from '../index';
export default function fragment ( parser: Parser ) {
if ( parser.match( '<' ) ) {
return tag;
}
if ( parser.match( '{{' ) ) {
return mustache;
}
return text;
}

@ -1,6 +1,8 @@
import readExpression from '../read/expression.js';
import { whitespace } from '../../utils/patterns.js';
import { trimStart, trimEnd } from '../../utils/trim.js';
import readExpression from '../read/expression';
import { whitespace } from '../../utils/patterns';
import { trimStart, trimEnd } from '../../utils/trim';
import { Parser } from '../index';
import { Node } from '../../interfaces';
const validIdentifier = /[a-zA-Z_$][a-zA-Z0-9_$]*/;
@ -27,7 +29,7 @@ function trimWhitespace ( block, trimBefore, trimAfter ) {
}
}
export default function mustache ( parser ) {
export default function mustache ( parser: Parser ) {
const start = parser.index;
parser.index += 2;
@ -145,7 +147,7 @@ export default function mustache ( parser ) {
const expression = readExpression( parser );
const block = {
const block: Node = {
start,
end: null,
type,

@ -1,10 +1,12 @@
import readExpression from '../read/expression.js';
import readScript from '../read/script.js';
import readStyle from '../read/style.js';
import { readEventHandlerDirective, readBindingDirective, readTransitionDirective } from '../read/directives.js';
import { trimStart, trimEnd } from '../../utils/trim.js';
import { decodeCharacterReferences } from '../utils/html.js';
import isVoidElementName from '../../utils/isVoidElementName.js';
import readExpression from '../read/expression';
import readScript from '../read/script';
import readStyle from '../read/style';
import { readEventHandlerDirective, readBindingDirective, readTransitionDirective } from '../read/directives';
import { trimStart, trimEnd } from '../../utils/trim';
import { decodeCharacterReferences } from '../utils/html';
import isVoidElementName from '../../utils/isVoidElementName';
import { Parser } from '../index';
import { Node } from '../../interfaces';
const validTagName = /^\!?[a-zA-Z]{1,}:?[a-zA-Z0-9\-]*/;
const invalidUnquotedAttributeCharacters = /[\s"'=<>\/`]/;
@ -61,7 +63,7 @@ function stripWhitespace ( element ) {
}
}
export default function tag ( parser ) {
export default function tag ( parser: Parser ) {
const start = parser.index++;
let parent = parser.current();
@ -162,7 +164,7 @@ export default function tag ( parser ) {
return;
}
const element = {
const element: Node = {
start,
end: null, // filled in later
type: 'Element',
@ -187,7 +189,7 @@ export default function tag ( parser ) {
return null;
}
function readTagName ( parser ) {
function readTagName ( parser: Parser ) {
const start = parser.index;
if ( parser.eat( SELF ) ) {
@ -222,7 +224,7 @@ function readTagName ( parser ) {
return name;
}
function readAttribute ( parser, uniqueNames ) {
function readAttribute ( parser: Parser, uniqueNames ) {
const start = parser.index;
let name = parser.readUntil( /(\s|=|\/|>)/ );
@ -277,13 +279,13 @@ function readAttribute ( parser, uniqueNames ) {
};
}
function readAttributeValue ( parser ) {
function readAttributeValue ( parser: Parser ) {
let quoteMark;
if ( parser.eat( `'` ) ) quoteMark = `'`;
if ( parser.eat( `"` ) ) quoteMark = `"`;
let currentChunk = {
let currentChunk: Node = {
start: parser.index,
end: null,
type: 'Text',
@ -347,7 +349,7 @@ function readAttributeValue ( parser ) {
parser.error( `Unexpected end of input` );
}
function getShorthandValue ( start, name ) {
function getShorthandValue ( start: number, name: string ) {
const end = start + name.length;
return [{

@ -1,6 +1,7 @@
import { decodeCharacterReferences } from '../utils/html.js';
import { decodeCharacterReferences } from '../utils/html';
import { Parser } from '../index';
export default function text ( parser ) {
export default function text ( parser: Parser ) {
const start = parser.index;
let data = '';

@ -1,5 +1,5 @@
// https://github.com/darkskyapp/string-hash/blob/master/index.js
export default function hash ( str ) {
export default function hash ( str: string ) :number {
let hash = 5381;
let i = str.length;

@ -1,9 +1,9 @@
import htmlEntities from './entities.js';
import htmlEntities from './entities';
const windows1252 = [ 8364, 129, 8218, 402, 8222, 8230, 8224, 8225, 710, 8240, 352, 8249, 338, 141, 381, 143, 144, 8216, 8217, 8220, 8221, 8226, 8211, 8212, 732, 8482, 353, 8250, 339, 157, 382, 376 ];
const entityPattern = new RegExp( `&(#?(?:x[\\w\\d]+|\\d+|${Object.keys( htmlEntities ).join( '|' )}));?`, 'g' );
export function decodeCharacterReferences ( html ) {
export function decodeCharacterReferences ( html: string ) {
return html.replace( entityPattern, ( match, entity ) => {
let code;
@ -31,7 +31,7 @@ const NUL = 0;
// to replace them ourselves
//
// Source: http://en.wikipedia.org/wiki/Character_encodings_in_HTML#Illegal_characters
function validateCode ( code ) {
function validateCode ( code: number ) {
// line feed becomes generic whitespace
if ( code === 10 ) {
return 32;

@ -1,6 +1,6 @@
import * as fs from 'fs';
import * as path from 'path';
import { compile } from '../index.js';
import { compile } from '../index.ts';
function capitalise ( name ) {
return name[0].toUpperCase() + name.slice( 1 );

@ -31,5 +31,5 @@ fs.readdirSync( __dirname ).forEach( file => {
});
});
fs.writeFileSync( 'src/generators/dom/shared.js', `// this file is auto-generated, do not edit it
fs.writeFileSync( 'src/generators/dom/shared.ts', `// this file is auto-generated, do not edit it
export default ${JSON.stringify( declarations, null, '\t' )};` );

@ -18,7 +18,7 @@ export function generateKeyframes ( a, b, delta, duration, ease, fn, node, style
document.head.appendChild( style );
node.style.animation = node.style.animation.split( ',' )
node.style.animation = ( node.style.animation || '' ).split( ',' )
.filter( function ( anim ) {
// when introing, discard old animations if there are any
return anim && ( delta < 0 || !/__svelte/.test( anim ) );
@ -104,9 +104,10 @@ export function wrapTransition ( node, fn, params, intro, outgroup ) {
export var transitionManager = {
running: false,
transitions: [],
bound: null,
add: function ( transition ) {
transitionManager.transitions.push( transition );
this.transitions.push( transition );
if ( !this.running ) {
this.running = true;
@ -115,13 +116,13 @@ export var transitionManager = {
},
next: function () {
transitionManager.running = false;
this.running = false;
var now = window.performance.now();
var i = transitionManager.transitions.length;
var i = this.transitions.length;
while ( i-- ) {
var transition = transitionManager.transitions[i];
var transition = this.transitions[i];
if ( transition.program && now >= transition.program.end ) {
transition.done();
@ -133,14 +134,14 @@ export var transitionManager = {
if ( transition.running ) {
transition.update( now );
transitionManager.running = true;
this.running = true;
} else if ( !transition.pending ) {
transitionManager.transitions.splice( i, 1 );
this.transitions.splice( i, 1 );
}
}
if ( transitionManager.running ) {
requestAnimationFrame( transitionManager.next );
if ( this.running ) {
requestAnimationFrame( this.bound || ( this.bound = this.next.bind( this ) ) );
}
}
};

@ -1,18 +1,25 @@
const LINE = {};
const BLOCK = {};
enum ChunkType {
Line,
Block
}
export default class CodeBuilder {
result: string
first: ChunkType
last: ChunkType
lastCondition: string
constructor ( str = '' ) {
this.result = str;
const initial = str ? ( /\n/.test( str ) ? BLOCK : LINE ) : null;
const initial = str ? ( /\n/.test( str ) ? ChunkType.Block : ChunkType.Line ) : null;
this.first = initial;
this.last = initial;
this.lastCondition = null;
}
addConditionalLine ( condition, line ) {
addConditionalLine ( condition: string, line: string ) {
if ( condition === this.lastCondition ) {
this.result += `\n\t${line}`;
} else {
@ -24,41 +31,41 @@ export default class CodeBuilder {
this.lastCondition = condition;
}
this.last = BLOCK;
this.last = ChunkType.Block;
}
addLine ( line ) {
addLine ( line: string ) {
if ( this.lastCondition ) {
this.result += `\n}`;
this.lastCondition = null;
}
if ( this.last === BLOCK ) {
if ( this.last === ChunkType.Block ) {
this.result += `\n\n${line}`;
} else if ( this.last === LINE ) {
} else if ( this.last === ChunkType.Line ) {
this.result += `\n${line}`;
} else {
this.result += line;
}
this.last = LINE;
if ( !this.first ) this.first = LINE;
this.last = ChunkType.Line;
if ( !this.first ) this.first = ChunkType.Line;
}
addLineAtStart ( line ) {
if ( this.first === BLOCK ) {
addLineAtStart ( line: string ) {
if ( this.first === ChunkType.Block ) {
this.result = `${line}\n\n${this.result}`;
} else if ( this.first === LINE ) {
} else if ( this.first === ChunkType.Line ) {
this.result = `${line}\n${this.result}`;
} else {
this.result += line;
}
this.first = LINE;
if ( !this.last ) this.last = LINE;
this.first = ChunkType.Line;
if ( !this.last ) this.last = ChunkType.Line;
}
addBlock ( block ) {
addBlock ( block: string ) {
if ( this.lastCondition ) {
this.result += `\n}`;
this.lastCondition = null;
@ -70,19 +77,19 @@ export default class CodeBuilder {
this.result += block;
}
this.last = BLOCK;
if ( !this.first ) this.first = BLOCK;
this.last = ChunkType.Block;
if ( !this.first ) this.first = ChunkType.Block;
}
addBlockAtStart ( block ) {
addBlockAtStart ( block: string ) {
if ( this.result ) {
this.result = `${block}\n\n${this.result}`;
} else {
this.result += block;
}
this.first = BLOCK;
if ( !this.last ) this.last = BLOCK;
this.first = ChunkType.Block;
if ( !this.last ) this.last = ChunkType.Block;
}
isEmpty () {

@ -0,0 +1,25 @@
import { locate } from 'locate-character';
import getCodeFrame from '../utils/getCodeFrame';
export default class CompileError extends Error {
frame: string
loc: { line: number, column: number }
pos: number
filename: string
constructor ( message: string, template: string, index: number, filename: string ) {
super( message );
const { line, column } = locate( template, index );
this.loc = { line: line + 1, column };
this.pos = index;
this.filename = filename;
this.frame = getCodeFrame( template, line, column );
}
toString () {
return `${this.message} (${this.loc.line}:${this.loc.column})\n${this.frame}`;
}
}

@ -1,6 +1,6 @@
import * as assert from 'assert';
import deindent from './deindent.js';
import CodeBuilder from './CodeBuilder.js';
import CodeBuilder from './CodeBuilder';
describe( 'deindent', () => {
it( 'deindents a simple string', () => {

@ -1,10 +1,11 @@
import { walk } from 'estree-walker';
import { Node } from '../interfaces';
export default function annotateWithScopes ( expression ) {
export default function annotateWithScopes ( expression: Node ) {
let scope = new Scope( null, false );
walk( expression, {
enter ( node ) {
enter ( node: Node ) {
if ( /Function/.test( node.type ) ) {
if ( node.type === 'FunctionDeclaration' ) {
scope.declarations.add( node.id.name );
@ -13,7 +14,7 @@ export default function annotateWithScopes ( expression ) {
if ( node.id ) scope.declarations.add( node.id.name );
}
node.params.forEach( param => {
node.params.forEach( ( param: Node ) => {
extractNames( param ).forEach( name => {
scope.declarations.add( name );
});
@ -33,7 +34,7 @@ export default function annotateWithScopes ( expression ) {
}
},
leave ( node ) {
leave ( node: Node ) {
if ( node._scope ) {
scope = scope.parent;
}
@ -44,17 +45,21 @@ export default function annotateWithScopes ( expression ) {
}
class Scope {
constructor ( parent, block ) {
parent: Scope
block: boolean
declarations: Set<string>
constructor ( parent: Scope, block: boolean ) {
this.parent = parent;
this.block = block;
this.declarations = new Set();
}
addDeclaration ( node ) {
addDeclaration ( node: Node ) {
if ( node.kind === 'var' && !this.block && this.parent ) {
this.parent.addDeclaration( node );
} else if ( node.type === 'VariableDeclaration' ) {
node.declarations.forEach( declarator => {
node.declarations.forEach( ( declarator: Node ) => {
extractNames( declarator.id ).forEach( name => {
this.declarations.add( name );
});
@ -64,39 +69,39 @@ class Scope {
}
}
has ( name ) {
has ( name: string ) :boolean {
return this.declarations.has( name ) || this.parent && this.parent.has( name );
}
}
function extractNames ( param ) {
const names = [];
function extractNames ( param: Node ) {
const names: string[] = [];
extractors[ param.type ]( names, param );
return names;
}
const extractors = {
Identifier ( names, param ) {
Identifier ( names: string[], param: Node ) {
names.push( param.name );
},
ObjectPattern ( names, param ) {
param.properties.forEach( prop => {
ObjectPattern ( names: string[], param: Node ) {
param.properties.forEach( ( prop: Node ) => {
extractors[ prop.value.type ]( names, prop.value );
});
},
ArrayPattern ( names, param ) {
param.elements.forEach( element => {
ArrayPattern ( names: string[], param: Node ) {
param.elements.forEach( ( element: Node ) => {
if ( element ) extractors[ element.type ]( names, element );
});
},
RestElement ( names, param ) {
RestElement ( names: string[], param: Node ) {
extractors[ param.argument.type ]( names, param.argument );
},
AssignmentPattern ( names, param ) {
AssignmentPattern ( names: string[], param: Node ) {
extractors[ param.left.type ]( names, param.left );
}
};

@ -1,4 +1,6 @@
export default function flatten ( node ) {
import { Node } from '../interfaces';
export default function flatten ( node: Node ) {
const parts = [];
const propEnd = node.end;

@ -1,10 +1,10 @@
import spaces from './spaces.js';
function tabsToSpaces ( str ) {
function tabsToSpaces ( str: string ) {
return str.replace( /^\t+/, match => match.split( '\t' ).join( ' ' ) );
}
export default function getCodeFrame ( source, line, column ) {
export default function getCodeFrame ( source: string, line: number, column: number ) {
const lines = source.split( '\n' );
const frameStart = Math.max( 0, line - 2 );

@ -1,4 +1,6 @@
export default function isReference ( node, parent ) {
import { Node } from '../interfaces';
export default function isReference ( node: Node, parent: Node ): boolean {
if ( node.type === 'MemberExpression' ) {
return !node.computed && isReference( node.object, node );
}

@ -1,5 +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 ) {
export default function isVoidElementName ( name: string ) {
return voidElementNames.test( name ) || name.toLowerCase() === '!doctype';
}

@ -1,3 +1,5 @@
import { Node } from '../interfaces';
const keys = {
ObjectExpression: 'properties',
Program: 'body'
@ -8,7 +10,7 @@ const offsets = {
Program: [ 0, 0 ]
};
export function removeNode ( code, parent, node ) {
export function removeNode ( code, parent: Node, node: Node ) {
const key = keys[ parent.type ];
const offset = offsets[ parent.type ];
if ( !key || !offset ) throw new Error( `not implemented: ${parent.type}` );

@ -1,44 +0,0 @@
import deindent from './deindent.js';
export default function toSource ( thing ) {
if ( typeof thing === 'function' ) {
return normaliseIndentation( thing.toString() );
}
if ( Array.isArray( thing ) ) {
if ( thing.length === 0 ) return '[]';
throw new Error( 'TODO' ); // not currently needed
}
if ( thing && typeof thing === 'object' ) {
const keys = Object.keys( thing );
if ( keys.length === 0 ) return '{}';
const props = keys.map( key => `${key}: ${toSource( thing[ key ] )}` ).join( ',\n' );
return deindent`
{
${props}
}
`;
}
return JSON.stringify( thing );
}
function normaliseIndentation ( str ) {
const lines = str.split( '\n' ).slice( 1, -1 );
let minIndentation = Infinity;
lines.forEach( line => {
if ( !/\S/.test( line ) ) return;
const indentation = /^\t*/.exec( line )[0].length;
if ( indentation < minIndentation ) minIndentation = indentation;
});
if ( minIndentation !== Infinity && minIndentation !== 1 ) {
const pattern = new RegExp( `^\\t{${minIndentation - 1}}`, 'gm' );
return str.replace( pattern, '' );
}
return str;
}

@ -1,13 +1,13 @@
import { whitespace } from './patterns.js';
import { whitespace } from './patterns';
export function trimStart ( str ) {
export function trimStart ( str: string ) {
let i = 0;
while ( whitespace.test( str[i] ) ) i += 1;
return str.slice( i );
}
export function trimEnd ( str ) {
export function trimEnd ( str: string ) {
let i = str.length;
while ( whitespace.test( str[ i - 1 ] ) ) i -= 1;

@ -1,6 +1,8 @@
import * as namespaces from '../../utils/namespaces.js';
import validateElement from './validateElement.js';
import validateWindow from './validateWindow.js';
import * as namespaces from '../../utils/namespaces';
import validateElement from './validateElement';
import validateWindow from './validateWindow';
import { Validator } from '../index';
import { Node } from '../../interfaces';
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)$/;
@ -8,10 +10,10 @@ const meta = new Map([
[ ':Window', validateWindow ]
]);
export default function validateHtml ( validator, html ) {
export default function validateHtml ( validator: Validator, html: Node ) {
let elementDepth = 0;
function visit ( node ) {
function visit ( node: Node ) {
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 );

@ -1,13 +1,15 @@
import validateEventHandler from './validateEventHandler.js';
import validateEventHandler from './validateEventHandler';
import { Validator } from '../index';
import { Node } from '../../interfaces';
export default function validateElement ( validator, node ) {
export default function validateElement ( validator: Validator, node: Node ) {
const isComponent = node.name === ':Self' || validator.components.has( node.name );
let hasIntro;
let hasOutro;
let hasTransition;
let hasIntro: boolean;
let hasOutro: boolean;
let hasTransition: boolean;
node.attributes.forEach( attribute => {
node.attributes.forEach( ( attribute: Node ) => {
if ( !isComponent && attribute.type === 'Binding' ) {
const { name } = attribute;
@ -35,13 +37,13 @@ export default function validateElement ( validator, node ) {
const type = getType( validator, node );
if ( type !== 'checkbox' && type !== 'radio' ) {
validator.error( `'checked' binding can only be used with <input type="checkbox"> or <input type="radio">` );
validator.error( `'checked' binding can only be used with <input type="checkbox"> or <input type="radio">`, attribute.start );
}
}
else if ( name === 'currentTime' || name === 'duration' || name === 'paused' ) {
if ( node.name !== 'audio' && node.name !== 'video' ) {
validator.error( `'${name}' binding can only be used with <audio> or <video>` );
validator.error( `'${name}' binding can only be used with <audio> or <video>`, attribute.start );
}
}
@ -78,8 +80,8 @@ export default function validateElement ( validator, node ) {
});
}
function getType ( validator, node ) {
const attribute = node.attributes.find( attribute => attribute.name === 'type' );
function getType ( validator: Validator, node: Node ) {
const attribute = node.attributes.find( ( attribute: Node ) => attribute.name === 'type' );
if ( !attribute ) return null;
if ( attribute.value === true ) {

@ -1,5 +1,7 @@
import flattenReference from '../../utils/flattenReference.js';
import list from '../utils/list.js';
import flattenReference from '../../utils/flattenReference';
import list from '../utils/list';
import { Validator } from '../index';
import { Node } from '../../interfaces';
const validBuiltins = new Set([
'set',
@ -7,7 +9,7 @@ const validBuiltins = new Set([
'destroy'
]);
export default function validateEventHandlerCallee ( validator, attribute ) {
export default function validateEventHandlerCallee ( validator: Validator, attribute: Node ) {
const { callee, start, type } = attribute.expression;
if ( type !== 'CallExpression' ) {
@ -31,5 +33,5 @@ export default function validateEventHandlerCallee ( validator, attribute ) {
message += `. '${callee.name}' exists on 'helpers', did you put it in the wrong place?`;
}
validator.error( message, start );
}
validator.warn( message, start );
}

@ -1,7 +1,9 @@
import flattenReference from '../../utils/flattenReference.js';
import fuzzymatch from '../utils/fuzzymatch.js';
import list from '../utils/list.js';
import validateEventHandler from './validateEventHandler.js';
import flattenReference from '../../utils/flattenReference';
import fuzzymatch from '../utils/fuzzymatch';
import list from '../utils/list';
import validateEventHandler from './validateEventHandler';
import { Validator } from '../index';
import { Node } from '../../interfaces';
const validBindings = [
'innerWidth',
@ -12,8 +14,8 @@ const validBindings = [
'scrollY'
];
export default function validateWindow ( validator, node ) {
node.attributes.forEach( attribute => {
export default function validateWindow ( validator: Validator, node: Node ) {
node.attributes.forEach( ( attribute: Node ) => {
if ( attribute.type === 'Binding' ) {
if ( attribute.value.type !== 'Identifier' ) {
const { parts } = flattenReference( attribute.value );

@ -1,79 +0,0 @@
import validateJs from './js/index.js';
import validateHtml from './html/index.js';
import { getLocator } from 'locate-character';
import getCodeFrame from '../utils/getCodeFrame.js';
export default function validate ( parsed, source, { onerror, onwarn, name, filename } ) {
const locator = getLocator( source );
const validator = {
error: ( message, pos ) => {
const { line, column } = locator( pos );
const error = new Error( message );
error.frame = getCodeFrame( source, line, column );
error.loc = { line: line + 1, column };
error.pos = pos;
error.filename = filename;
error.toString = () => `${error.message} (${error.loc.line}:${error.loc.column})\n${error.frame}`;
throw error;
},
warn: ( message, pos ) => {
const { line, column } = locator( pos );
const frame = getCodeFrame( source, line, column );
onwarn({
message,
frame,
loc: { line: line + 1, column },
pos,
filename,
toString: () => `${message} (${line + 1}:${column})\n${frame}`
});
},
source,
namespace: null,
defaultExport: null,
properties: {},
components: new Map(),
methods: new Map(),
helpers: new Map(),
transitions: new Map()
};
try {
if ( name && !/^[a-zA-Z_$][a-zA-Z_$0-9]*$/.test( name ) ) {
const error = new Error( `options.name must be a valid identifier` );
throw error;
}
if ( name && !/^[A-Z]/.test( name ) ) {
const message = `options.name should be capitalised`;
onwarn({
message,
filename,
toString: () => message
});
}
if ( parsed.js ) {
validateJs( validator, parsed.js );
}
if ( parsed.html ) {
validateHtml( validator, parsed.html );
}
} catch ( err ) {
if ( onerror ) {
onerror( err );
} else {
throw err;
}
}
}

Some files were not shown because too many files have changed in this diff Show More

Loading…
Cancel
Save