Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

User-land template-escape into JS #805

Open
Tracked by #816
NullVoxPopuli opened this issue Mar 9, 2022 · 4 comments
Open
Tracked by #816

User-land template-escape into JS #805

NullVoxPopuli opened this issue Mar 9, 2022 · 4 comments

Comments

@NullVoxPopuli
Copy link
Sponsor Contributor

NullVoxPopuli commented Mar 9, 2022

Opening this issue to track ideas and exploration, and not so much to debate if we should or not (which is tiring, and we can talk about it on discord or in person/video call).

With First-Class Component templates merged, we can use the <template> syntax as a way of discussing strict-mode transforms in a concise, human-readable format.
However, this RFC Issue assumes we have the following available in our hypothetical ember-source version:

This may accidentally come off as RFC-ish, but I just want to get my thoughts down real quick after having a chat with @JimSchofield about some directions we can go in. It's def too early to submit an actual RFC on this idea -- need to do some exploration.

Presently, double curlies are defined as an escape to glimmer-S-Expressions, everything else is text or html -- which leaves a lot of room open to explore additional syntaxes while evaluating the utility of a concept.

For example, today in Ember, we don't have any concept of "Effects", yet they are highly needed in one of the bigger concepts missing from the switch to Octane (people formally used observers for this type of behavior).

In userland, as an addon author, it would be possible to transform this:

// some-component.gjs
<template>

  {() => console.log(@data)}

</template>

into a valid Ember template today:

const helperA = (data) => console.log(data);

<template>
  {{ (helperA @data) }}
</template>

Similarly, we can make some assumptions within our hypothetical transform to handle this in a class-based component, for example:

class Demo extends Component {
  @tracked searchText = '';

  // tho, what about concurrency, cancellation, dropping, etc? lots to figure out with something like this
  <template>
    {async () => {
      let { endpoint } = @searchProvider; // destructure named args
      let response = await fetch(`${endpoint}?query=${this.searchText}`);
      let json = await response.json();
      
      // set the auto-completion
      this.searchText = json.suggestions[0];
     }}
  </template>
}

would become:

import  { isDestroying, isDestroyed } from '@ember/destroyable';

const helperA = async (ctx, searchProvider) => {
  let { endpoint } = searchProvider; // destructure named args
  let response = await fetch(`${endpoint}?query=${ctx.searchText}`);
  let json = await response.json();
  
  // we can automatically protect people from destruction blunders
  if (isDestroying(ctx) || isDestroyed(ctx)) return;

  // set the auto-completion
  ctx.searchText = json.suggestions[0];
}

class Demo extends Component {
  @tracked searchText = '';

  <template>
    {{helperA this @searchProvider}}
  </template>
}

First off, why bother? This looks pretty close to JSX and implies a bunch of things which we might open ourselves up to if we aren't careful.
But the motivation is that isn't a good way to implement "Effects" on a JS class.

Some options we have:

class MyComponent {
  @effect nameWasted = () => console.log(this.args.data);
  
  @effect(() => [this.args.data]
  nameNotNeeded(passedArg) {
    console.log(passedArg);
  }
}

Goals of an effect:

  • auto-track (consume tracked data before an await)
  • run initially during render and only when tracked data updates

Problems with effects-in-classes

  • a name is needed
  • no good way to create a syntax?

What do effects look like in React? they get around the whole problem by:

function Demo() {
  let [data] = useState();

  // runs every time data chages, due to the dependency list in the second argument
  useEffect(() => {
    console.log(data);
  }, [data])

  return <></>;
}

Some benefits of exploring this:

Outstanding things to figure out:

  • usage with let?
  • using return values?
  • how to manage async state (since _The Platform doesn't do this for us)
  • concurrency?
@wagenet
Copy link
Member

wagenet commented Jul 23, 2022

What can be done to turn this into an actual RFC? I’d prefer for this repo not to be a place where good ideas go to sit indefinitely!

@NullVoxPopuli
Copy link
Sponsor Contributor Author

I think the next step is to (have someone) build this idea's proposed transform in user-land and have folks test it out.

Some of the syntax here (highlighting, tooling, etc) requires us to finish tooling support for the base <template> stuff (main things right now are eslint and template-lint, afaict).

@wagenet
Copy link
Member

wagenet commented Jul 23, 2022

@NullVoxPopuli do you know of anyone interested in building the transform?

@NullVoxPopuli
Copy link
Sponsor Contributor Author

I know @lifeart has done most of the experimental transforms so far -- idk if they'd be interested in this one. My primary focus right now is limber.glimdown.com / glimdown in general / <template> / eslint rules / and maybe whatever else I need to meet my own goals.

I would totally experiment with this transform tho.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants