@@ -3,7 +3,7 @@ import { precompile as glimmerPrecompile } from '@glimmer/compiler';
33import type { SerializedTemplateWithLazyBlock } from '@glimmer/interfaces' ;
44import { setComponentTemplate } from '@glimmer/manager' ;
55import { templateFactory } from '@glimmer/opcode-compiler' ;
6- import compileOptions from './compile-options' ;
6+ import compileOptions , { keywords , RUNTIME_KEYWORDS_NAME } from './compile-options' ;
77import type { EmberPrecompileOptions } from './types' ;
88
99type ComponentClass = abstract new ( ...args : any [ ] ) => object ;
@@ -237,38 +237,54 @@ export function template(
237237 templateString : string ,
238238 providedOptions ?: BaseTemplateOptions | BaseClassTemplateOptions < any >
239239) : object {
240- const options : EmberPrecompileOptions = { strictMode : true , ...providedOptions } ;
241- const evaluate = buildEvaluator ( options ) ;
240+ const options = { strictMode : true , ...providedOptions } ;
242241
242+ const evaluate = buildEvaluator ( options ) ;
243243 const normalizedOptions = compileOptions ( options ) ;
244244 const component = normalizedOptions . component ?? templateOnly ( ) ;
245245
246246 const source = glimmerPrecompile ( templateString , normalizedOptions ) ;
247- const template = templateFactory ( evaluate ( `(${ source } )` ) as SerializedTemplateWithLazyBlock ) ;
247+ const wire = evaluate ( `(${ source } )` ) as SerializedTemplateWithLazyBlock ;
248+
249+ const template = templateFactory ( wire ) ;
248250
249251 setComponentTemplate ( template , component ) ;
250252
251253 return component ;
252254}
253255
254- const evaluator = ( source : string ) => {
255- return new Function ( `return ${ source } ` ) ( ) ;
256- } ;
256+ /**
257+ * Builds the source wireformat JSON block
258+ *
259+ * @param options
260+ * @returns
261+ */
262+ function buildEvaluator ( options : Partial < EmberPrecompileOptions > ) {
263+ if ( options . eval ) {
264+ const userEval = options . eval ;
257265
258- function buildEvaluator ( options : Partial < EmberPrecompileOptions > | undefined ) {
259- if ( options === undefined ) {
260- return evaluator ;
261- }
266+ // Wrap the compiled source in a function that receives the keywords
267+ // container as a parameter. The user's eval evaluates this in the
268+ // caller's scope, so local variables (like `handleClick`) are captured
269+ // via closure, while `__keywords__` comes from the function parameter.
270+ return ( source : string ) => {
271+ let wrapperFn = userEval ( `(function(${ RUNTIME_KEYWORDS_NAME } ){ return (${ source } ); })` ) as (
272+ ...args : unknown [ ]
273+ ) => unknown ;
262274
263- if ( options . eval ) {
264- return options . eval ;
275+ return wrapperFn ( keywords ) ;
276+ } ;
265277 } else {
266- const scope = options . scope ?.( ) ;
278+ let scope = options . scope ?.( ) ;
267279
268280 if ( ! scope ) {
269- return evaluator ;
281+ return ( source : string ) => {
282+ return new Function ( RUNTIME_KEYWORDS_NAME , `return (${ source } )` ) ( keywords ) ;
283+ } ;
270284 }
271285
286+ scope = Object . assign ( { [ RUNTIME_KEYWORDS_NAME ] : keywords } , scope ) ;
287+
272288 return ( source : string ) => {
273289 let hasThis = Object . prototype . hasOwnProperty . call ( scope , 'this' ) ;
274290 let thisValue = hasThis ? ( scope as { this ?: unknown } ) . this : undefined ;
0 commit comments