@ -42,7 +42,12 @@ export default class Generator {
aliases : Map < string , string > ;
usedNames : Set < string > ;
constructor ( parsed : Parsed , source : string , name : string , options : CompileOptions ) {
constructor (
parsed : Parsed ,
source : string ,
name : string ,
options : CompileOptions
) {
this . parsed = parsed ;
this . source = source ;
this . name = name ;
@ -91,7 +96,12 @@ export default class Generator {
return this . aliases . get ( name ) ;
}
contextualise ( block : DomBlock | SsrBlock , expression : Node , context : string , isEventHandler : boolean ) {
contextualise (
block : DomBlock | SsrBlock ,
expression : Node ,
context : string ,
isEventHandler : boolean
) {
this . addSourcemapLocations ( expression ) ;
const usedContexts : string [ ] = [ ] ;
@ -114,37 +124,36 @@ export default class Generator {
}
if ( node . type === 'ThisExpression' ) {
if ( lexicalDepth === 0 && context ) code . overwrite ( node . start , node . end , context , { storeName : true , contentOnly : false } ) ;
}
else if ( isReference ( node , parent ) ) {
if ( lexicalDepth === 0 && context )
code . overwrite ( node . start , node . end , context , {
storeName : true ,
contentOnly : false
} ) ;
} else if ( isReference ( node , parent ) ) {
const { name } = flattenReference ( node ) ;
if ( scope . has ( name ) ) return ;
if ( name === 'event' && isEventHandler ) {
// noop
}
else if ( contexts . has ( name ) ) {
} else if ( contexts . has ( name ) ) {
const contextName = contexts . get ( name ) ;
if ( contextName !== name ) {
// this is true for 'reserved' names like `state` and `component`
code . overwrite ( node . start , node . start + name . length , contextName , { storeName : true , contentOnly : false } ) ;
code . overwrite (
node . start ,
node . start + name . length ,
contextName ,
{ storeName : true , contentOnly : false }
) ;
}
if ( ! ~ usedContexts . indexOf ( name ) ) usedContexts . push ( name ) ;
}
else if ( helpers . has ( name ) ) {
} else if ( helpers . has ( name ) ) {
code . prependRight ( node . start , ` ${ self . alias ( 'template' ) } .helpers. ` ) ;
}
else if ( indexes . has ( name ) ) {
} else if ( indexes . has ( name ) ) {
const context = indexes . get ( name ) ;
if ( ! ~ usedContexts . indexOf ( context ) ) usedContexts . push ( context ) ;
}
else {
} else {
// handle shorthand properties
if ( parent && parent . type === 'Property' && parent . shorthand ) {
if ( key === 'key' ) {
@ -155,7 +164,10 @@ export default class Generator {
if ( globalWhitelist . has ( name ) ) {
code . prependRight ( node . start , ` ( ' ${ name } ' in state ? state. ` ) ;
code . appendLeft ( node . object ? node.object.end : node.end , ` : ${ name } ) ` ) ;
code . appendLeft (
node . object ? node.object.end : node.end ,
` : ${ name } ) `
) ;
} else {
code . prependRight ( node . start , ` state. ` ) ;
}
@ -180,7 +192,11 @@ export default class Generator {
} ;
}
findDependencies ( contextDependencies : Map < string , string [ ] > , indexes : Map < string , string > , expression : Node ) {
findDependencies (
contextDependencies : Map < string , string [ ] > ,
indexes : Map < string , string > ,
expression : Node
) {
if ( expression . _dependencies ) return expression . _dependencies ;
let scope = annotateWithScopes ( expression ) ;
@ -229,23 +245,40 @@ export default class Generator {
this . imports . forEach ( ( declaration , i ) = > {
if ( format === 'es' ) {
statements . push ( this . source . slice ( declaration . start , declaration . end ) ) ;
statements . push (
this . source . slice ( declaration . start , declaration . end )
) ;
return ;
}
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 } ` ;
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 : Node ) = > {
statements . push ( ` var ${ specifier . local . name } = ${ name } . ${ specifier . imported . name } ` ) ;
statements . push (
` var ${ specifier . local . name } = ${ name } . ${ specifier . imported . name } `
) ;
} ) ;
if ( defaultImport ) {
statements . push ( ` ${ name } = ( ${ name } && ${ name } .__esModule ) ? ${ name } ['default'] : ${ name } ; ` ) ;
statements . push (
` ${ name } = ( ${ name } && ${ name } .__esModule ) ? ${ name } ['default'] : ${ name } ; `
) ;
}
} ) ;
@ -298,7 +331,10 @@ export default class Generator {
return {
code : compiled.toString ( ) ,
map : compiled.generateMap ( { includeContent : true , file : options.outputFilename } ) ,
map : compiled.generateMap ( {
includeContent : true ,
file : options.outputFilename
} ) ,
css : this.css
} ;
}
@ -306,7 +342,13 @@ export default class Generator {
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 ++ } ` ) ;
for (
let i = 1 ;
reservedNames . has ( alias ) ||
this . importedNames . has ( alias ) ||
this . usedNames . has ( alias ) ;
alias = ` ${ name } _ ${ i ++ } `
) ;
this . usedNames . add ( alias ) ;
return alias ;
}
@ -316,7 +358,14 @@ 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 ;
} ;
@ -350,7 +399,9 @@ export default class Generator {
}
}
const defaultExport = body . find ( ( node : Node ) = > node . type === 'ExportDefaultDeclaration' ) ;
const defaultExport = body . find (
( node : Node ) = > node . type === 'ExportDefaultDeclaration'
) ;
if ( defaultExport ) {
defaultExport . declaration . properties . forEach ( ( prop : Node ) = > {
@ -373,7 +424,10 @@ export default class Generator {
const key = prop . key . name ;
const value = prop . value ;
const deps = value . params . map ( ( param : Node ) = > 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 ) ;
} ) ;
@ -391,7 +445,9 @@ export default class Generator {
computations . push ( { key , deps } ) ;
}
templateProperties . computed . value . properties . forEach ( ( prop : Node ) = > visit ( prop . key . name ) ) ;
templateProperties . computed . value . properties . forEach ( ( prop : Node ) = >
visit ( prop . key . name )
) ;
}
if ( templateProperties . namespace ) {
@ -403,19 +459,28 @@ export default class Generator {
if ( templateProperties . components ) {
let hasNonImportedComponent = false ;
templateProperties . components . value . properties . forEach ( ( property : Node ) = > {
templateProperties . components . value . properties . forEach (
( property : Node ) = > {
const key = property . key . name ;
const value = source . slice ( property . value . start , property . value . end ) ;
const value = source . slice (
property . value . start ,
property . value . end
) ;
if ( this . importedNames . has ( value ) ) {
this . importedComponents . set ( key , value ) ;
} else {
hasNonImportedComponent = true ;
}
} ) ;
}
) ;
if ( hasNonImportedComponent ) {
// remove the specific components that were imported, as we'll refer to them directly
Array . from ( this . importedComponents . keys ( ) ) . forEach ( key = > {
removeObjectKey ( this . code , templateProperties . components . value , key ) ;
removeObjectKey (
this . code ,
templateProperties . components . value ,
key
) ;
} ) ;
} else {
// remove the entire components portion of the export
@ -426,22 +491,48 @@ export default class Generator {
// Remove these after version 2
if ( templateProperties . onrender ) {
const { key } = templateProperties . onrender ;
this . code . overwrite ( key . start , key . end , 'oncreate' , { storeName : true , contentOnly : false } ) ;
this . code . overwrite ( key . start , key . end , 'oncreate' , {
storeName : true ,
contentOnly : false
} ) ;
templateProperties . oncreate = templateProperties . onrender ;
}
if ( templateProperties . onteardown ) {
const { key } = templateProperties . onteardown ;
this . code . overwrite ( key . start , key . end , 'ondestroy' , { storeName : true , contentOnly : false } ) ;
this . code . overwrite ( key . start , key . end , 'ondestroy' , {
storeName : true ,
contentOnly : false
} ) ;
templateProperties . ondestroy = templateProperties . onteardown ;
}
// in an SSR context, we don't need to include events, methods, oncreate or ondestroy
if ( ssr ) {
if ( templateProperties . oncreate ) removeNode ( this . code , defaultExport . declaration , templateProperties . oncreate ) ;
if ( templateProperties . ondestroy ) removeNode ( this . code , defaultExport . declaration , templateProperties . ondestroy ) ;
if ( templateProperties . methods ) removeNode ( this . code , defaultExport . declaration , templateProperties . methods ) ;
if ( templateProperties . events ) removeNode ( this . code , defaultExport . declaration , templateProperties . events ) ;
if ( templateProperties . oncreate )
removeNode (
this . code ,
defaultExport . declaration ,
templateProperties . oncreate
) ;
if ( templateProperties . ondestroy )
removeNode (
this . code ,
defaultExport . declaration ,
templateProperties . ondestroy
) ;
if ( templateProperties . methods )
removeNode (
this . code ,
defaultExport . declaration ,
templateProperties . methods
) ;
if ( templateProperties . events )
removeNode (
this . code ,
defaultExport . declaration ,
templateProperties . events
) ;
}
// now that we've analysed the default export, we can determine whether or not we need to keep it
@ -456,30 +547,47 @@ export default class Generator {
const finalNode = body [ body . length - 1 ] ;
if ( defaultExport === finalNode ) {
// export is last property, we can just return it
this . code . overwrite ( defaultExport . start , defaultExport . declaration . start , ` return ` ) ;
this . code . overwrite (
defaultExport . start ,
defaultExport . declaration . start ,
` return `
) ;
} else {
const { declarations } = annotateWithScopes ( js ) ;
let template = 'template' ;
for ( let i = 1 ; declarations . has ( template ) ; template = ` template_ ${ i ++ } ` ) ;
this . code . overwrite ( defaultExport . start , defaultExport . declaration . start , ` var ${ template } = ` ) ;
for (
let i = 1 ;
declarations . has ( template ) ;
template = ` template_ ${ i ++ } `
) ;
this . code . overwrite (
defaultExport . start ,
defaultExport . declaration . start ,
` var ${ template } = `
) ;
let i = defaultExport . start ;
while ( /\s/ . test ( source [ i - 1 ] ) ) i -- ;
const indentation = source . slice ( i , defaultExport . start ) ;
this . code . appendLeft ( finalNode . end , ` \ n \ n ${ indentation } return ${ template } ; ` ) ;
this . code . appendLeft (
finalNode . end ,
` \ n \ n ${ indentation } return ${ template } ; `
) ;
}
}
// user code gets wrapped in an IIFE
if ( js . content . body . length ) {
const prefix = hasDefaultExport ? ` var ${ this . alias ( 'template' ) } = (function () { ` : ` (function () { ` ;
this . code . prependRight ( js . content . start , prefix ) . appendLeft ( js . content . end , '}());' ) ;
}
const prefix = hasDefaultExport
? ` var ${ this . alias ( 'template' ) } = (function () { `
: ` (function () { ` ;
this . code
. prependRight ( js . content . start , prefix )
. appendLeft ( js . content . end , '}());' ) ;
} else {
// if there's no need to include user code, remove it altogether
else {
this . code . remove ( js . content . start , js . content . end ) ;
hasJs = false ;
}