pull/573/head
Rich-Harris 8 years ago
parent 4d5107113e
commit 78adc5b226

@ -10,15 +10,34 @@ import getIntro from './shared/utils/getIntro';
import getOutro from './shared/utils/getOutro'; import getOutro from './shared/utils/getOutro';
import processCss from './shared/processCss'; import processCss from './shared/processCss';
import annotateWithScopes from '../utils/annotateWithScopes'; import annotateWithScopes from '../utils/annotateWithScopes';
import { Node, Parsed, CompileOptions } from '../../interfaces';
const test = typeof global !== 'undefined' && global.__svelte_test; const test = typeof global !== 'undefined' && global.__svelte_test;
export default class Generator { export default class Generator {
source: string parsed: Parsed;
name: string source: string;
// TODO all the rest... name: string;
options: CompileOptions;
constructor ( parsed, source: string, name: string, options ) {
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.parsed = parsed;
this.source = source; this.source = source;
this.name = name; this.name = name;
@ -46,19 +65,19 @@ export default class Generator {
// Svelte's builtin `import { get, ... } from 'svelte/shared.ts'`; // Svelte's builtin `import { get, ... } from 'svelte/shared.ts'`;
this.importedNames = new Set(); this.importedNames = new Set();
this.aliases = new Map(); this.aliases = new Map();
this._usedNames = new Set( [ name ] ); this.usedNames = new Set( [ name ] );
} }
addSourcemapLocations ( node ) { addSourcemapLocations ( node: Node ) {
walk( node, { walk( node, {
enter: node => { enter: ( node: Node ) => {
this.code.addSourcemapLocation( node.start ); this.code.addSourcemapLocation( node.start );
this.code.addSourcemapLocation( node.end ); this.code.addSourcemapLocation( node.end );
} }
}); });
} }
alias ( name ) { alias ( name: string ) {
if ( !this.aliases.has( name ) ) { if ( !this.aliases.has( name ) ) {
this.aliases.set( name, this.getUniqueName( name ) ); this.aliases.set( name, this.getUniqueName( name ) );
} }
@ -66,10 +85,10 @@ export default class Generator {
return this.aliases.get( name ); return this.aliases.get( name );
} }
contextualise ( block, expression, context, isEventHandler ) { contextualise ( block, expression: Node, context, isEventHandler ) {
this.addSourcemapLocations( expression ); this.addSourcemapLocations( expression );
const usedContexts = []; const usedContexts: string[] = [];
const { code, helpers } = this; const { code, helpers } = this;
const { contexts, indexes } = block; const { contexts, indexes } = block;
@ -80,7 +99,7 @@ export default class Generator {
const self = this; const self = this;
walk( expression, { walk( expression, {
enter ( node, parent, key ) { enter ( node: Node, parent: Node, key: string ) {
if ( /^Function/.test( node.type ) ) lexicalDepth += 1; if ( /^Function/.test( node.type ) ) lexicalDepth += 1;
if ( node._scope ) { if ( node._scope ) {
@ -142,7 +161,7 @@ export default class Generator {
} }
}, },
leave ( node ) { leave ( node: Node ) {
if ( /^Function/.test( node.type ) ) lexicalDepth -= 1; if ( /^Function/.test( node.type ) ) lexicalDepth -= 1;
if ( node._scope ) scope = scope.parent; if ( node._scope ) scope = scope.parent;
} }
@ -159,12 +178,12 @@ export default class Generator {
if ( expression._dependencies ) return expression._dependencies; if ( expression._dependencies ) return expression._dependencies;
let scope = annotateWithScopes( expression ); let scope = annotateWithScopes( expression );
const dependencies = []; const dependencies: string[] = [];
const generator = this; // can't use arrow functions, because of this.skip() const generator = this; // can't use arrow functions, because of this.skip()
walk( expression, { walk( expression, {
enter ( node, parent ) { enter ( node: Node, parent: Node ) {
if ( node._scope ) { if ( node._scope ) {
scope = node._scope; scope = node._scope;
return; return;
@ -184,7 +203,7 @@ export default class Generator {
} }
}, },
leave ( node ) { leave ( node: Node ) {
if ( node._scope ) scope = scope.parent; if ( node._scope ) scope = scope.parent;
} }
}); });
@ -278,11 +297,11 @@ export default class Generator {
}; };
} }
getUniqueName ( name ) { getUniqueName ( name: string ) {
if ( test ) name = `${name}$`; if ( test ) name = `${name}$`;
let alias = name; let alias = name;
for ( let i = 1; reservedNames.has( alias ) || this.importedNames.has( alias ) || this._usedNames.has( alias ); alias = `${name}_${i++}` ); for ( let i = 1; reservedNames.has( alias ) || this.importedNames.has( alias ) || this.usedNames.has( alias ); alias = `${name}_${i++}` );
this._usedNames.add( alias ); this.usedNames.add( alias );
return alias; return alias;
} }
@ -291,13 +310,13 @@ export default class Generator {
return name => { return name => {
if ( test ) name = `${name}$`; if ( test ) name = `${name}$`;
let alias = 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 ); localUsedNames.add( alias );
return alias; return alias;
}; };
} }
parseJs ( ssr ) { parseJs ( ssr: boolean = false ) {
const { source } = this; const { source } = this;
const { js } = this.parsed; const { js } = this.parsed;

@ -1,8 +1,21 @@
import CodeBuilder from '../../utils/CodeBuilder'; import CodeBuilder from '../../utils/CodeBuilder';
import deindent from '../../utils/deindent.js'; import deindent from '../../utils/deindent.js';
import { DomGenerator } from './index';
import { Node } from '../../interfaces';
export interface BlockOptions {
generator: DomGenerator
name: string
expression: Node
context: string
}
export default class Block { export default class Block {
constructor ( options ) { generator: DomGenerator;
name: string;
expression: Node;
constructor ( options: BlockOptions ) {
this.generator = options.generator; this.generator = options.generator;
this.name = options.name; this.name = options.name;
this.expression = options.expression; this.expression = options.expression;

@ -9,9 +9,16 @@ import visit from './visit';
import shared from './shared'; import shared from './shared';
import Generator from '../Generator'; import Generator from '../Generator';
import preprocess from './preprocess'; import preprocess from './preprocess';
import Block from './Block';
import { Parsed, CompileOptions, Node } from '../../interfaces';
class DomGenerator extends Generator { export class DomGenerator extends Generator {
constructor ( parsed, source, name, options ) { blocks: Block[]
uses: Set<string>;
readonly: Set<string>;
metaBindings: string[];
constructor ( parsed: Parsed, source: string, name: string, options: CompileOptions ) {
super( parsed, source, name, options ); super( parsed, source, name, options );
this.blocks = []; this.blocks = [];
this.uses = new Set(); this.uses = new Set();
@ -22,7 +29,7 @@ class DomGenerator extends Generator {
this.metaBindings = []; this.metaBindings = [];
} }
helper ( name ) { helper ( name: string ) {
if ( this.options.dev && `${name}Dev` in shared ) { if ( this.options.dev && `${name}Dev` in shared ) {
name = `${name}Dev`; name = `${name}Dev`;
} }
@ -33,7 +40,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 format = options.format || 'es';
const name = options.name || 'SvelteComponent'; const name = options.name || 'SvelteComponent';
@ -49,7 +56,7 @@ export default function dom ( parsed, source, options ) {
const block = preprocess( generator, state, parsed.html ); const block = preprocess( generator, state, parsed.html );
parsed.html.children.forEach( node => { parsed.html.children.forEach( ( node: Node ) => {
visit( generator, block, state, node ); visit( generator, block, state, node );
}); });

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

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

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

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

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

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

@ -24,3 +24,22 @@ export interface Parsed {
css: Node; css: Node;
js: 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;
dev?: boolean;
shared?: boolean | string;
onerror?: (error: Error) => void
onwarn?: (warning: Warning) => void
}

@ -3,7 +3,7 @@ import validateHtml from './html/index';
import { getLocator, Location } from 'locate-character'; import { getLocator, Location } from 'locate-character';
import getCodeFrame from '../utils/getCodeFrame'; import getCodeFrame from '../utils/getCodeFrame';
import CompileError from '../utils/CompileError' import CompileError from '../utils/CompileError'
import { Node, Parsed } from '../interfaces'; import { Node, Parsed, CompileOptions, Warning } from '../interfaces';
class ValidationError extends CompileError { class ValidationError extends CompileError {
constructor ( message: string, template: string, index: number, filename: string ) { constructor ( message: string, template: string, index: number, filename: string ) {
@ -27,7 +27,7 @@ export class Validator {
helpers: Map<string, Node>; helpers: Map<string, Node>;
transitions: Map<string, Node>; transitions: Map<string, Node>;
constructor ( parsed: Parsed, source: string, options: { onwarn, name?: string, filename?: string } ) { constructor ( parsed: Parsed, source: string, options: CompileOptions ) {
this.source = source; this.source = source;
this.filename = options !== undefined ? options.filename : undefined; this.filename = options !== undefined ? options.filename : undefined;
@ -64,7 +64,9 @@ export class Validator {
} }
} }
export default function validate ( parsed: Parsed, source: string, { onerror, onwarn, name, filename } ) { export default function validate ( parsed: Parsed, source: string, options: CompileOptions ) {
const { onwarn, onerror, name, filename } = options;
try { try {
if ( name && !/^[a-zA-Z_$][a-zA-Z_$0-9]*$/.test( name ) ) { if ( name && !/^[a-zA-Z_$][a-zA-Z_$0-9]*$/.test( name ) ) {
const error = new Error( `options.name must be a valid identifier` ); const error = new Error( `options.name must be a valid identifier` );

Loading…
Cancel
Save