Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[WIP] Convert to TypeScript #573

Merged
merged 15 commits into from
May 22, 2017
4 changes: 3 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -74,9 +74,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": [
Expand Down
9 changes: 9 additions & 0 deletions rename.js
Original file line number Diff line number Diff line change
@@ -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 );
});
23 changes: 17 additions & 6 deletions rollup/rollup.config.main.js
Original file line number Diff line number Diff line change
@@ -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
Expand Down
4 changes: 2 additions & 2 deletions rollup/rollup.config.ssr.js
Original file line number Diff line number Diff line change
Expand Up @@ -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
};
113 changes: 69 additions & 44 deletions src/generators/Generator.js → src/generators/Generator.ts
Original file line number Diff line number Diff line change
@@ -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;
Expand All @@ -39,33 +64,33 @@ 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 ) );
}

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;
Expand All @@ -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 ) {
Expand Down Expand Up @@ -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;
}
Expand All @@ -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;
Expand All @@ -180,7 +205,7 @@ export default class Generator {
}
},

leave ( node ) {
leave ( node: Node ) {
if ( node._scope ) scope = scope.parent;
}
});
Expand All @@ -196,22 +221,22 @@ 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' ) {
statements.push( this.source.slice( declaration.start, declaration.end ) );
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}` );
});

Expand All @@ -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 )
});
Expand All @@ -250,7 +275,7 @@ export default class Generator {
});
}

parts.forEach( str => {
parts.forEach( ( str: string ) => {
const chunk = str.replace( pattern, '' );
if ( chunk ) addString( chunk );

Expand All @@ -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;
}

Expand All @@ -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;

Expand All @@ -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 );
});
}
Expand All @@ -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 );
});

Expand All @@ -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 ) {
Expand All @@ -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 ) ) {
Expand Down
Loading