Skip to content

Latest commit

 

History

History
682 lines (465 loc) · 19.3 KB

1070-default-globals-for-strict-mode.md

File metadata and controls

682 lines (465 loc) · 19.3 KB
stage start-date release-date release-versions teams prs project-link suite
accepted
2025-01-18 00:00:00 UTC
cli
framework
learning
accepted

Default globals for strict mode

Summary

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.

Motivation

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>

Detailed design

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

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:

namespaces / objects

TC39:

  • globalThis - Already available.

    Example

    Link

    <template>
        {{JSON.stringify @data}}
    </template>
  • Atomics

    Example

    Link

    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>
  • JSON

    Example

    Link

    <template>
        {{JSON.stringify @data}}
    </template>
  • Math

    Example

    Link

    <template>
      {{Math.max 0 2 4}}
    </template>
  • Reflect

    Example

    Link

    const data = { greeting: 'hello there' };
    
    <template>
      {{Reflect.get data 'greeting'}}
    </template>

WHATWG:

  • location

    Example

    Link

    <template>
      {{location.href }}
    </template>
  • history

    Example

    Example1

    import { on } from '@ember/modifier';
    
    <template>
      <button {{on 'click' history.back}}>Back</button>
    </template>
  • navigator

    Example

    Link

    <template>
      {{navigator.userAgent}}
    
      <button {{on 'click' (fn navigator.clipboard.write @blob)}}>
        Copy to clipboard
      </button>
    </template>
  • window

    Most APIs are also available on window

  • document

    Example

    Example1

    <template>
      <div id='foo'></div>
      
      {{#in-element (document.getElementById 'foo')}}
          rendered elsewhere
      {{/in-element}}
    </template>
  • localStorage

    Example

    Example1

    <template>
      Current Theme: {{localStorage.getItem 'theme'}}
    
      <button {{on 'click' @toggleTheme}}>
        Toggle
      </button>
    </template>
  • sessionStorage

    Example

    Example1

    <template>
      Current Theme: {{sessionStorage.getItem 'theme'}}
    
      <button {{on 'click' @toggleTheme}}>
        Toggle
      </button>
    </template>

functions / utilities

TC39:

  • isNaN

    Example

    Link

    <template>
      {{#if (isNaN 0)}}
        is NaN
      {{else}}
        is falsey
      {{/if}}
    </template>
  • isFinite

    Example

    Link

    <template>
      {{#if (isFinite Infinity)}}
        is Finite
      {{else}}
        is falsey
      {{/if}}
    </template>
  • parseInt

    Example

    Link

    <template>
      {{parseInt 2.3}} 
    </template>
  • parseFloat

    Example

    Link

    <template>
      {{parseFloat 2.3}} 
    </template>
  • decodeURI

    Example

    Link

    <template>
      {{decodeURI "%5Bx%5D"}} 
    </template>
  • decodeURIComponent

    Example

    Link

    <template>
      {{decodeURIComponent "%5Bx%5D"}} 
    </template>
  • encodeURI

    Example

    Link

    <template>
      {{encodeURI "[x]"}} 
    </template>
  • encodeURIComponent

    Example

    Link

    <template>
      {{encodeURIComponent "[x]"}} 
    </template>

WHATWG:

  • atob

    Example

    Link

    <template>
        {{atob "aGVsbG8="}}
    </template>
  • btoa

    Example

    Link

    <template>
        {{btoa "hello"}}
    </template>
  • postMessage

    Example
    <template>
        <button {{on "click" (postMessage "Hello")>
            Greet
        </button>
    </template>
  • structuredClone

    Example
    <template>
        <SomeComponent @data={{structuredClone @inputData}} />
    </template>

new-less constructors (still functions / utilities)

TC39:

  • Array

    Example

    See note2 about (array), @ember/helper, and RFC#10002

    Link

    <template>
        {{#each (Array 1 2 3) as |item|}}
            {{item}}
        {{/each}}
    </template>
  • BigInt

    Example

    Link

    <template>
        {{BigInt "0x1fffffffffffff"}}
    </template>
  • Boolean

    Example

    Link

    <template>
        {{#if (Boolean "")}}
          true
        {{else}}
          false
        {{/if}}
    </template>
  • Date

    Example

    Link

    <template>
        {{Date 1737243005924}}
    </template>
  • Number

    Example

    Link

    <template>
        {{Number "22"}}
    </template>
  • Object

    Example

    Link

    <template>
        {{#each-in (Object a=1 b=2) as |key value|}}
          {{key}}: {{value}}
        {{/each-in}}
    </template>
  • String

    Example

    Link

    <template>
        {{String 22}}
    </template>

Values

TC39:

  • Infinity

    Example
    <template>
        {{Infinity}}
    </template>
  • NaN

    Example
    <template>
        {{NaN}}
    </template>

WHATWG:

  • isSecureContext

    Example
    <template>
        {{#if isSecureContext}}
            is secure
        {{/if}}
    </template>

Potentially matching criteria, but not included

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

How we teach this

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

Drawbacks

Takes a small amount of work to implement.

Alternatives

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..

Unresolved questions

none

Footnotes

  1. 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

  2. This is the same behavior as (array) in loose mode, and import { 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