11/** 
22 * @import  { TemplateOperations } from "../types.js" 
33 * @import  { Namespace } from "#compiler" 
4-  * @import  { CallExpression, Statement } from "estree" 
4+  * @import  { CallExpression, Statement, ObjectExpression, Identifier, ArrayExpression, Property, Expression, Literal  } from "estree" 
55 */ 
66import  {  NAMESPACE_SVG ,  NAMESPACE_MATHML  }  from  '../../../../../constants.js' ; 
77import  *  as  b  from  '../../../../utils/builders.js' ; 
8+ import  {  regex_is_valid_identifier  }  from  '../../../patterns.js' ; 
89import  fix_attribute_casing  from  './fix-attribute-casing.js' ; 
910
10- class  Scope  { 
11- 	declared  =  new  Map ( ) ; 
12- 
13- 	/** 
14- 	 * @param  {string } _name 
15- 	 */ 
16- 	generate ( _name )  { 
17- 		let  name  =  _name . replace ( / [ ^ a - z A - Z 0 - 9 _ $ ] / g,  '_' ) . replace ( / ^ [ 0 - 9 ] / ,  '_' ) ; 
18- 		if  ( ! this . declared . has ( name ) )  { 
19- 			this . declared . set ( name ,  1 ) ; 
20- 			return  name ; 
21- 		} 
22- 		let  count  =  this . declared . get ( name ) ; 
23- 		this . declared . set ( name ,  count  +  1 ) ; 
24- 		return  `${ name }  _${ count }  ` ; 
25- 	} 
26- } 
27- 
2811/** 
2912 * @param  {TemplateOperations } items 
30-  * @param  {Namespace } namespace 
3113 */ 
32- export  function  template_to_functions ( items ,  namespace )  { 
33- 	let  elements  =  [ ] ; 
34- 
35- 	let  body  =  [ ] ; 
36- 
37- 	let  scope  =  new  Scope ( ) ; 
14+ export  function  template_to_functions ( items )  { 
15+ 	let  elements  =  b . array ( [ ] ) ; 
3816
3917	/** 
4018	 * @type  {Array<Element> } 
4119	 */ 
4220	let  elements_stack  =  [ ] ; 
4321
44- 	/** 
45- 	 * @type  {Array<string> } 
46- 	 */ 
47- 	let  namespace_stack  =  [ ] ; 
48- 
49- 	/** 
50- 	 * @type  {number } 
51- 	 */ 
52- 	let  foreign_object_count  =  0 ; 
53- 
5422	/** 
5523	 * @type  {Element | undefined } 
5624	 */ 
@@ -71,199 +39,138 @@ export function template_to_functions(items, namespace) {
7139		// we closed one element, we remove it from the stack and eventually revert back 
7240		// the namespace to the previous one 
7341		if  ( instruction . kind  ===  'pop_element' )  { 
74- 			const  removed  =  elements_stack . pop ( ) ; 
75- 			if  ( removed ?. namespaced )  { 
76- 				namespace_stack . pop ( ) ; 
77- 			} 
78- 			if  ( removed ?. element  ===  'foreignObject' )  { 
79- 				foreign_object_count -- ; 
80- 			} 
42+ 			elements_stack . pop ( ) ; 
8143			continue ; 
8244		} 
8345
84- 		// if the inserted node is in the svg/mathml we push the namespace to the stack because we need to 
85- 		// create with createElementNS 
86- 		if  ( instruction . metadata ?. svg  ||  instruction . metadata ?. mathml )  { 
87- 			namespace_stack . push ( instruction . metadata . svg  ? NAMESPACE_SVG  : NAMESPACE_MATHML ) ; 
88- 		} 
89- 
9046		// @ts -expect-error we can't be here if `swap_current_element` but TS doesn't know that 
9147		const  value  =  map [ instruction . kind ] ( 
9248			...[ 
93- 				// for set prop we need to send the last element (not the one in the stack since 
94- 				// it get's added to the stack only after the push_element instruction)...for all the rest 
95- 				// the first prop is a the scope to generate the name of the variable 
96- 				...( instruction . kind  ===  'set_prop'  ? [ last_current_element ]  : [ scope ] ) , 
97- 				// for create element we also need to add the namespace...namespaces in the stack get's precedence over 
98- 				// the "global" namespace (and if we are in a foreignObject we default to html) 
9949				...( instruction . kind  ===  'create_element' 
100- 					? [ 
101- 							foreign_object_count  >  0 
102- 								? undefined 
103- 								: namespace_stack . at ( - 1 )  ?? 
104- 									( namespace  ===  'svg' 
105- 										? NAMESPACE_SVG 
106- 										: namespace  ===  'mathml' 
107- 											? NAMESPACE_MATHML 
108- 											: undefined ) 
109- 						] 
110- 					: [ ] ) , 
50+ 					? [ ] 
51+ 					: [ instruction . kind  ===  'set_prop'  ? last_current_element  : elements_stack . at ( - 1 ) ] ) , 
11152				...( instruction . args  ??  [ ] ) 
11253			] 
11354		) ; 
11455
115- 		if  ( value )  { 
116- 			// this will compose the body of the function 
117- 			body . push ( value . call ) ; 
118- 		} 
119- 
12056		// with set_prop we don't need to do anything else, in all other cases we also need to 
12157		// append the element/node/anchor to the current active element or push it in the elements array 
12258		if  ( instruction . kind  !==  'set_prop' )  { 
123- 			if  ( elements_stack . length  >=  1  &&  value )  { 
124- 				const  {  call }  =  map . insert ( /** @type  {Element } */  ( elements_stack . at ( - 1 ) ) ,  value ) ; 
125- 				body . push ( call ) ; 
126- 			}  else  if  ( value )  { 
127- 				elements . push ( b . id ( value . name ) ) ; 
59+ 			if  ( elements_stack . length  >=  1  &&  value  !==  undefined )  { 
60+ 				map . insert ( /** @type  {Element } */  ( elements_stack . at ( - 1 ) ) ,  value ) ; 
61+ 			}  else  if  ( value  !==  undefined )  { 
62+ 				elements . elements . push ( value ) ; 
12863			} 
12964			// keep track of the last created element (it will be pushed to the stack after the props are set) 
13065			if  ( instruction . kind  ===  'create_element' )  { 
13166				last_current_element  =  /** @type  {Element } */  ( value ) ; 
132- 				if  ( last_current_element . element  ===  'foreignObject' )  { 
133- 					foreign_object_count ++ ; 
134- 				} 
13567			} 
13668		} 
13769	} 
138- 	// every function needs to return a fragment so we create one and push all the elements there 
139- 	const  fragment  =  scope . generate ( 'fragment' ) ; 
140- 	body . push ( b . var ( fragment ,  b . call ( 'document.createDocumentFragment' ) ) ) ; 
141- 	body . push ( b . call ( fragment  +  '.append' ,  ...elements ) ) ; 
142- 	body . push ( b . return ( b . id ( fragment ) ) ) ; 
14370
144- 	return  b . arrow ( [ ] ,   b . block ( body ) ) ; 
71+ 	return  elements ; 
14572} 
14673
14774/** 
148-  * @typedef  {{ call: Statement, name: string, add_is: (value: string)=>void, namespaced: boolean; element: string; } } Element 
75+  * @typedef  {ObjectExpression } Element 
14976 */ 
15077
15178/** 
152-  * @typedef  {{ call: Statement, name: string } } Anchor 
79+  * @typedef  {void | null | ArrayExpression } Anchor 
15380 */ 
15481
15582/** 
156-  * @typedef  {{ call: Statement, name: string } } Text 
83+  * @typedef  {void | Literal } Text 
15784 */ 
15885
15986/** 
16087 * @typedef  { Element | Anchor| Text  } Node 
16188 */ 
16289
16390/** 
164-  * @param  {Scope } scope 
165-  * @param  {Namespace } namespace 
16691 * @param  {string } element 
16792 * @returns  {Element } 
16893 */ 
169- function  create_element ( scope ,  namespace ,  element )  { 
170- 	const  name  =  scope . generate ( element ) ; 
171- 	let  fn  =  namespace  !=  null  ? 'document.createElementNS'  : 'document.createElement' ; 
172- 	let  args  =  [ b . literal ( element ) ] ; 
173- 	if  ( namespace  !=  null )  { 
174- 		args . unshift ( b . literal ( namespace ) ) ; 
175- 	} 
176- 	const  call  =  b . var ( name ,  b . call ( fn ,  ...args ) ) ; 
177- 	/** 
178- 	 * if there's an "is" attribute we can't just add it as a property, it needs to be 
179- 	 * specified on creation like this `document.createElement('button', { is: 'my-button' })` 
180- 	 * 
181- 	 * Since the props are appended after the creation we change the generated call arguments and we push 
182- 	 * the is attribute later on on `set_prop` 
183- 	 * @param  {string } value 
184- 	 */ 
185- 	function  add_is ( value )  { 
186- 		/** @type  {CallExpression } */  ( call . declarations [ 0 ] . init ) . arguments . push ( 
187- 			b . object ( [ b . prop ( 'init' ,  b . literal ( 'is' ) ,  b . literal ( value ) ) ] ) 
188- 		) ; 
94+ function  create_element ( element )  { 
95+ 	return  b . object ( [ b . prop ( 'init' ,  b . id ( 'e' ) ,  b . literal ( element ) ) ] ) ; 
96+ } 
97+ 
98+ /** 
99+  * 
100+  * @param  {Element } element 
101+  * @param  {string } name 
102+  * @param  {Expression } init 
103+  * @returns  {Property } 
104+  */ 
105+ function  get_or_create_prop ( element ,  name ,  init )  { 
106+ 	let  prop  =  element . properties . find ( 
107+ 		( prop )  =>  prop . type  ===  'Property'  &&  /** @type  {Identifier } */  ( prop . key ) . name  ===  name 
108+ 	) ; 
109+ 	if  ( ! prop )  { 
110+ 		prop  =  b . prop ( 'init' ,  b . id ( name ) ,  init ) ; 
111+ 		element . properties . push ( prop ) ; 
189112	} 
190- 	return  { 
191- 		call, 
192- 		name, 
193- 		element, 
194- 		add_is, 
195- 		namespaced : namespace  !=  null 
196- 	} ; 
113+ 	return  /** @type  {Property } */  ( prop ) ; 
197114} 
198115
199116/** 
200-  * @param  {Scope } scope  
117+  * @param  {Element } element  
201118 * @param  {string } data 
202119 * @returns  {Anchor } 
203120 */ 
204- function  create_anchor ( scope ,  data  =  '' )  { 
205- 	const  name  =  scope . generate ( 'comment' ) ; 
206- 	return  { 
207- 		call : b . var ( name ,  b . call ( 'document.createComment' ,  b . literal ( data ) ) ) , 
208- 		name
209- 	} ; 
121+ function  create_anchor ( element ,  data  =  '' )  { 
122+ 	if  ( ! element )  return  data  ? b . array ( [ b . literal ( data ) ] )  : null ; 
123+ 	const  c  =  get_or_create_prop ( element ,  'c' ,  b . array ( [ ] ) ) ; 
124+ 	/** @type  {ArrayExpression } */  ( c . value ) . elements . push ( data  ? b . array ( [ b . literal ( data ) ] )  : null ) ; 
210125} 
211126
212127/** 
213-  * @param  {Scope } scope  
128+  * @param  {Element } element  
214129 * @param  {string } value 
215130 * @returns  {Text } 
216131 */ 
217- function  create_text ( scope ,  value )  { 
218- 	const  name  =  scope . generate ( 'text' ) ; 
219- 	return  { 
220- 		call : b . var ( name ,  b . call ( 'document.createTextNode' ,  b . literal ( value ) ) ) , 
221- 		name
222- 	} ; 
132+ function  create_text ( element ,  value )  { 
133+ 	if  ( ! element )  return  b . literal ( value ) ; 
134+ 	const  c  =  get_or_create_prop ( element ,  'c' ,  b . array ( [ ] ) ) ; 
135+ 	/** @type  {ArrayExpression } */  ( c . value ) . elements . push ( b . literal ( value ) ) ; 
223136} 
224137
225138/** 
226139 * 
227-  * @param  {Element } el  
140+  * @param  {Element } element  
228141 * @param  {string } prop 
229142 * @param  {string } value 
230143 */ 
231- function  set_prop ( el ,  prop ,  value )  { 
232- 	// see comment above about the "is" attribute 
144+ function  set_prop ( element ,  prop ,  value )  { 
145+ 	const  p  =  get_or_create_prop ( element ,  'p' ,  b . object ( [ ] ) ) ; 
146+ 
233147	if  ( prop  ===  'is' )  { 
234- 		el . add_is ( value ) ; 
148+ 		element . properties . push ( b . prop ( 'init' ,   b . id ( prop ) ,   b . literal ( value ) ) ) ; 
235149		return ; 
236150	} 
237151
238- 	const  [ namespace ]  =  prop . split ( ':' ) ; 
239- 	let  fn  =  namespace  !==  prop  ? '.setAttributeNS'  : '.setAttribute' ; 
240- 	let  args  =  [ b . literal ( fix_attribute_casing ( prop ) ) ,  b . literal ( value  ??  '' ) ] ; 
152+ 	const  prop_correct_case  =  fix_attribute_casing ( prop ) ; 
241153
242- 	// attributes like `xlink:href` need to be set with the `xlink` namespace 
243- 	if  ( namespace  ===  'xlink' )  { 
244- 		args . unshift ( b . literal ( 'http://www.w3.org/1999/xlink' ) ) ; 
245- 	} 
154+ 	const  is_valid_id  =  regex_is_valid_identifier . test ( prop_correct_case ) ; 
246155
247- 	return  { 
248- 		call : b . call ( el . name  +  fn ,  ...args ) 
249- 	} ; 
156+ 	/** @type  {ObjectExpression } */  ( p . value ) . properties . push ( 
157+ 		b . prop ( 
158+ 			'init' , 
159+ 			( is_valid_id  ? b . id  : b . literal ) ( prop_correct_case ) , 
160+ 			b . literal ( value ) , 
161+ 			! is_valid_id 
162+ 		) 
163+ 	) ; 
250164} 
251165
252166/** 
253167 * 
254-  * @param  {Element } el 
255-  * @param  {Node } child 
256-  * @param  {Node } [anchor] 
168+  * @param  {Element } element 
169+  * @param  {Element } child 
257170 */ 
258- function  insert ( el ,  child ,  anchor )  { 
259- 	return  { 
260- 		call : b . call ( 
261- 			// if we have a template element we need to push into it's content rather than the element itself 
262- 			el . name  +  ( el . element  ===  'template'  ? '.content'  : '' )  +  '.insertBefore' , 
263- 			b . id ( child . name ) , 
264- 			b . id ( anchor ?. name  ??  'undefined' ) 
265- 		) 
266- 	} ; 
171+ function  insert ( element ,  child )  { 
172+ 	const  c  =  get_or_create_prop ( element ,  'c' ,  b . array ( [ ] ) ) ; 
173+ 	/** @type  {ArrayExpression } */  ( c . value ) . elements . push ( child ) ; 
267174} 
268175
269176let  map  =  { 
0 commit comments