UNPKG

8.32 kBTypeScriptView Raw
1declare module '@ember/template-compiler/lib/template' {
2 import { type TemplateOnlyComponent } from '@ember/component/template-only';
3 type ComponentClass = abstract new (...args: any[]) => object;
4 /**
5 * All possible options passed to `template()` may specify a `moduleName`.
6 */
7 export interface BaseTemplateOptions {
8 moduleName?: string;
9 /**
10 * Whether the template should be treated as a strict-mode template. Defaults
11 * to `true`.
12 */
13 strictMode?: boolean;
14 }
15 /**
16 * When using `template` in a class, you call it in a `static` block and pass
17 * the class as the `component` option.
18 *
19 * ```ts
20 * class MyComponent extends Component {
21 * static {
22 * template('{{this.greeting}}, {{@place}}!',
23 * { component: this },
24 * // explicit or implicit option goes here
25 * );
26 * }
27 * }
28 * ```
29 *
30 * For the full explicit form, see {@linkcode ExplicitClassOptions}. For the
31 * full implicit form, see {@linkcode ImplicitClassOptions}.
32 */
33 export interface BaseClassTemplateOptions<C extends ComponentClass> extends BaseTemplateOptions {
34 component: C;
35 }
36 /**
37 * When using `template` outside of a class (i.e. a "template-only component"), you can pass
38 * a `scope` option that explicitly provides the lexical scope for the template.
39 *
40 * This is called the "explicit form".
41 *
42 * ```ts
43 * const greeting = 'Hello';
44 * const HelloWorld = template('{{greeting}} World!', { scope: () => ({ greeting }) });
45 * ```
46 */
47 export interface ExplicitTemplateOnlyOptions extends BaseTemplateOptions {
48 scope(): Record<string, unknown>;
49 }
50 /**
51 * When using `template` *inside* a class (see
52 * {@linkcode BaseClassTemplateOptions}), you can pass a `scope` option that
53 * explicitly provides the lexical scope for the template, just like a template-only
54 * component (see {@linkcode ExplicitTemplateOnlyOptions}).
55 *
56 * ```ts
57 * class MyComponent extends Component {
58 * static {
59 * template('{{this.greeting}}, {{@place}}!',
60 * { component: this },
61 * // explicit or implicit option goes here
62 * );
63 * }
64 * }
65 * ```
66 *
67 * ## The Scope Function's `instance` Parameter
68 *
69 * However, the explicit `scope` function in a *class* also takes an `instance` option
70 * that provides access to the component's instance.
71 *
72 * Once it's supported in Handlebars, this will make it possible to represent private
73 * fields when using the explicit form.
74 *
75 * ```ts
76 * class MyComponent extends Component {
77 * static {
78 * template('{{this.#greeting}}, {{@place}}!',
79 * { component: this },
80 * scope: (instance) => ({ '#greeting': instance.#greeting }),
81 * );
82 * }
83 * }
84 * ```
85 */
86 export interface ExplicitClassOptions<C extends ComponentClass>
87 extends BaseClassTemplateOptions<C> {
88 scope(instance?: InstanceType<C>): Record<string, unknown>;
89 }
90 /**
91 * The *implicit* form of the `template` function takes an `eval` option that
92 * allows the runtime compiler to evaluate local template variables without
93 * needing to maintain an explicit list of the local variables used in the
94 * template scope.
95 *
96 * The eval options *must* be passed in the following form:
97 *
98 * ```ts
99 * {
100 * eval() { return eval(arguments[0]) }
101 * }
102 * ```
103 *
104 * ## Requirements of the `eval` Option
105 *
106 * **The syntactic form presented above is the only form you should use when
107 * passing an `eval` option.**
108 *
109 * This is _required_ if you want your code to be compatible with the
110 * compile-time implementation of `@ember/template-compiler`. While the runtime
111 * compiler offers a tiny bit of additional wiggle room, you still need to follow
112 * very strict rules.
113 *
114 * We don't recommend trying to memorize the rules. Instead, we recommend using
115 * the snippet presented above and supported by the compile-time implementation.
116 *
117 * ### The Technical Requirements of the `eval` Option
118 *
119 * The `eval` function is passed a single parameter that is a JavaScript
120 * identifier. This will be extended in the future to support private fields.
121 *
122 * Since keywords in JavaScript are contextual (e.g. `await` and `yield`), the
123 * parameter might be a keyword. The `@ember/template-compiler/runtime` expects
124 * the function to throw a `SyntaxError` if the identifier name is not valid in
125 * the current scope. (The direct `eval` function takes care of this out of the
126 * box.)
127 *
128 * Requirements:
129 *
130 * 1. The `eval` method must receive its parameter as `arguments[0]`, which
131 * ensures that the variable name passed to `eval()` is not shadowed by the
132 * function's parameter name.
133 * 2. The `eval` option must be a function or concise method, and not an arrow.
134 * This is because arrows do not have their own `arguments`, which breaks
135 * (1).
136 * 3. The `eval` method must call "*direct* `eval`", and not an alias of `eval`.
137 * Direct `eval` evaluates the code in the scope it was called from, while
138 * aliased versions of `eval` (including `new Function`) evaluate the code in
139 * the global scope.
140 * 4. The `eval` method must return the result of calling "direct `eval`".
141 *
142 * The easiest way to achieve these requirements is to use the exact syntax
143 * presented above. This is *also* the only way to be compatible
144 *
145 * ## Rationale
146 *
147 * This is useful for two reasons:
148 *
149 * 1. This form is a useful _intermediate_ form for the compile-time toolchain.
150 * It allows the content-tag preprocessor to convert the `<template>` syntax
151 * into valid JavaScript without needing to involve full-fledged lexical
152 * analysis.
153 * 2. This form is a convenient form for manual prototyping when using the
154 * runtime compiler directly. While it requires some extra typing relative to
155 * `<template>`, it's a mechanical 1:1 transformation of the syntax.
156 *
157 * In practice, implementations that use a runtime compiler (for example, a
158 * playground running completely in the browser) should probably use the
159 * `content-tag` preprocessor to convert the template into the implicit form,
160 * and then rely on `@ember/template-compiler/runtime` to evaluate the template.
161 */
162 export interface ImplicitEvalOption {
163 eval(): unknown;
164 }
165 /**
166 * When using `template` outside of a class (i.e. a "template-only component"), you can pass
167 * an `eval` option that _implicitly_ provides the lexical scope for the template.
168 *
169 * This is called the "implicit form".
170 *
171 * ```ts
172 * const greeting = 'Hello';
173 * const HelloWorld = template('{{greeting}} World!', {
174 * eval() { return arguments[0] }
175 * });
176 * ```
177 *
178 * For more details on the requirements of the `eval` option, see {@linkcode ImplicitEvalOption}.
179 */
180 export type ImplicitTemplateOnlyOptions = BaseTemplateOptions & ImplicitEvalOption;
181 /**
182 * When using `template` inside of a class, you can pass an `eval` option that
183 * _implicitly_ provides the lexical scope for the template, just as you can
184 * with a {@linkcode ImplicitTemplateOnlyOptions | template-only component}.
185 *
186 * This is called the "implicit form".
187 *
188 * ```ts
189 * class MyComponent extends Component {
190 * static {
191 * template('{{this.greeting}}, {{@place}}!',
192 * { component: this },
193 * eval() { return arguments[0] }
194 * );
195 * }
196 * }
197 * ```
198 *
199 * ## Note on Private Fields
200 *
201 * The current implementation of `@ember/template-compiler` does not support
202 * private fields, but once the Handlebars parser adds support for private field
203 * syntax and it's implemented in the Glimmer compiler, the implicit form should
204 * be able to support them.
205 */
206 export type ImplicitClassOptions<C extends ComponentClass> = BaseClassTemplateOptions<C> &
207 ImplicitEvalOption;
208 export function template(
209 templateString: string,
210 options?: ExplicitTemplateOnlyOptions | ImplicitTemplateOnlyOptions
211 ): TemplateOnlyComponent;
212 export function template<C extends ComponentClass>(
213 templateString: string,
214 options: ExplicitClassOptions<C> | ImplicitClassOptions<C> | BaseClassTemplateOptions<C>
215 ): C;
216 export {};
217}