stage | start-date | release-date | release-versions | teams | prs | project-link | suite | |||||
---|---|---|---|---|---|---|---|---|---|---|---|---|
accepted |
2025-01-18 00:00:00 UTC |
|
|
This RFC aims to introduce platform-native globals as allowed defaults in strict-mode components, allowing for more intuitive usage, and less "know how the compiler works"
This is an ergonomics-focused RFC. The proposed changes today can be polyfilled via globalThis['...']
accesses.
Early on there was a bug in the build tools of strict-mode components that allowed any global to be used components. This was dangerous, as strict-mode allows use of all in-scoped variables to be used in all valid syntax positions in the template, and while this is what folks would expect for languages with a scope-system, it means that if someone defined window.div
, all <div>
s in components would use that implementation instead. This was fixed, but during that time, we realized that it's very convenient to use platform-native globals as utilities, such as Array
, Boolean
, String
, Infinity
, JSON
, and many more.
While we don't want to support everything on globalThis
, we can aim support a good list of utilities fitting some criteria. Committing to this list means that we promise to never create a keyword with the same name + casing as the platform-native API, likewise, having an allow-list of which platform-native APIs to support guides our decisions around what keywords to implement in templates, as the platform-native globals would take precedence over / be used instead of any same-named keywords.
Without this RFC, all platform-native globals must be accessed via globalThis:
<template>
{{globalThis.JSON.stringify @data null 3}}
</template>
or
const { JSON } = globalThis;
<template>
{{JSON.stringify @data null 3}}
</template>
After this RFC is implemented, the following would work:
<template>
{{JSON.stringify @data null 3}}
</template>
Allowing defaults means: when using JSON
(for example) in a component, the compiled-to-plain-JS output results in the reference to JSON being added to the "scope bag", for example: :
// Post-RFC 931
import { template } from '@ember/template-compiler';
const data = {};
export default template('{{JSON.stringify data}}', { scope: () => ({ JSON, data }) });
pre-RFC#931
// Pre-RFC 931
import { precompileTemplate } from "@ember/template-compilation";
import { setComponentTemplate } from "@ember/component";
import templateOnly from "@ember/component/template-only";
const data = {};
export default setComponentTemplate(
precompileTemplate('{{JSON.stringify data}}', {
strictMode: true,
scope: () => ({ JSON, data }) }
), templateOnly()
);
Criteria for a platform-native global to be accepted as default:
Any of
- begins with an uppercase letter
- guaranteed to never be added to glimmer as a keyword (e.g.:
globalThis
)
And
- must not need
new
to invoke - must not require lifetime management (e.g.:
setTimeout
) - if the API is a function, the return value should not be a promise
- must be one one of these lists:
- https://tc39.es/ecma262/#sec-global-object
- https://tc39.es/ecma262/#sec-function-properties-of-the-global-object
- https://html.spec.whatwg.org/multipage/nav-history-apis.html#window
- https://html.spec.whatwg.org/multipage/indices.html#all-interfaces
- https://html.spec.whatwg.org/multipage/webappapis.html
Important
Because all function invocations are reactive by default, every function called from these APIs will be re-called when arguments change.
Given the above criteria, the following should be added to default-available strict-mode scope:
TC39:
-
globalThis
- Already available. -
Example
const buffer = new ArrayBuffer(16); const uint8 = new Uint8Array(buffer); uint8[0] = 7; // 7 (0111) XOR 2 (0010) = 5 (0101)<br> Atomics.xor(uint8, 0, 2) <template> {{Atomics.load uint8}} === 5 </template>
-
Example
const data = { greeting: 'hello there' }; <template> {{Reflect.get data 'greeting'}} </template>
WHATWG:
-
Example
Example1
import { on } from '@ember/modifier'; <template> <button {{on 'click' history.back}}>Back</button> </template>
-
Example
<template> {{navigator.userAgent}} <button {{on 'click' (fn navigator.clipboard.write @blob)}}> Copy to clipboard </button> </template>
-
Most APIs are also available on
window
-
Example
Example1
<template> <div id='foo'></div> {{#in-element (document.getElementById 'foo')}} rendered elsewhere {{/in-element}} </template>
-
Example
Example1
<template> Current Theme: {{localStorage.getItem 'theme'}} <button {{on 'click' @toggleTheme}}> Toggle </button> </template>
-
Example
Example1
<template> Current Theme: {{sessionStorage.getItem 'theme'}} <button {{on 'click' @toggleTheme}}> Toggle </button> </template>
TC39:
-
Example
<template> {{#if (isFinite Infinity)}} is Finite {{else}} is falsey {{/if}} </template>
WHATWG:
-
Example
<template> <button {{on "click" (postMessage "Hello")> Greet </button> </template>
-
Example
<template> <SomeComponent @data={{structuredClone @inputData}} /> </template>
TC39:
-
Example
<template> {{#each-in (Object a=1 b=2) as |key value|}} {{key}}: {{value}} {{/each-in}} </template>
TC39:
WHATWG:
-
Example
<template> {{#if isSecureContext}} is secure {{/if}} </template>
Existing keywords don't need to be included in the global scope allow-list
These do not exist in all supported environments:
These are not common and / or may be actively dangerous to make easier to use:
eval
PerformEval
Function
Uncommon entries from the "Constructor Properties" list: https://tc39.es/ecma262/#sec-constructor-properties-of-the-global-object
${...}Error
, e.g.:AggregateError
${...}Int${...}Array
, e.g.:BigUint64Array
,- anything that requires
new
, e.g.:DataView
,Map
APIs from WHATWG that are highly likely to collide with user-land code or are already ambiguous (and thus would be confusing to use):
stop()
,close()
,status()
,focus()
,blur()
,open()
,parent
,confirm()
,self
, etc
Developers should primarily reference exising documentation on the web for the above-mentioned APIs, such as on MDN.
If we don't already, we should have an extensive guide on Polish Syntax, potentially similar to https://cheatsheet.glimmer.nullvoxpopuli.com/docs/templates
Takes a small amount of work to implement.
Do nothing, but this is worse, as folks intuitively expect these a lot of the above-mentioned APIs to "just work", without needing weird scope-tricks to convince our Scope-tracking tools in the build tools that certain APIs are in scope..
none
Footnotes
-
demo/example omitted because the glimmer-vm, at the time of the writing of this RFC has not fixed a bug that where this-binding is lost on method calls. ↩ ↩2 ↩3 ↩4
-
This is the same behavior as
(array)
in loose mode, andimport { array } from '@ember/helper';
, however, while the creation of the array is reactive (e.g.: if we had said(Array @foo @bar)
, changes to@foo
and@bar
would cause the creation of a new array instance), the proposed built-in(array)
keyword behavior may have reactive items, as proposed by RFC#1068 ↩ ↩2