-
-
Notifications
You must be signed in to change notification settings - Fork 408
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
<template> layering proposal #813
Conversation
By the way, one of the things that I think was a missed opportunity is we didn't discuss whitespace-folding rules for |
In that case, it should provide an option to preserve the whitespace for particular purposes like rendering code blocks, etc. |
Edit: I talked myself in and out of an approach in this comment, and do not think this is straightforward as my first sentence states 😅
idk about that -- I think doing something like but, if we want to bike shed, I think it'd be stellar to have an option to strip all non-single space invisible characters -- But!!! with how this proposal is laid out, I don't think we need to support that, because we can do: import { stirpIndent } from 'common-tags';
const MyComponent = template(stripIndent`
<p>
hi
</p>
`, {}); which, as long as we design the build-time transform to evaluate the first arg to template (somehow? I'm fuzzy on this -- anyone know how it'd work? could lead to security problems? (I can only think of
to
|
I added a bunch more details, including the runtime opt-in, a restricted set of syntax that is guaranteed to be statically analyzable, and the question about whitespace handling. I marked it as ready for review/discussion now. @NullVoxPopuli @nightire about the questions you asked (re: whitespace), maybe read the new section on that and we can continue from there. Short answer is no, I don't think we want to create an arbitrary macro ecosystem here and in any case, solutions like |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Just a couple of notes, nothing deep.
Happy to use this feature !
Syntax support in the editor is definitely needed.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I am a very strong 👍🏼 on the introduction of the target output, of updating the addon v2 spec to target it, and of the runtime template()
import!
I am extremely confused (even after a few discussions with folks!) about what value the alternative tagged template literal template
provides here compared to <template>
, since in both cases it does nothing without a transformation. (I’m quite willing to be persuaded here, I just am not yet seeing the value there!)
Because `template()` is a standard JavaScript API that has a "real" import | ||
location, there is a natural place to document and describe its behavior in the | ||
API docs. This should be the primary reference of the feature, and should also | ||
where we document the idiomatic subset as well. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We should use that as a hook for describing the whole end-to-end bits in the API docs, including <template>
, too, since as you note below it's the actual desugaring for that.
However, even with that in mind, the default conclusion for the whitespace | ||
handling in `<template>` tags is _still_ not useful, _especially_ when the | ||
author is trying to preserve significant whitespace for something like code | ||
samples. This is because `<template>` tags often appears in JavaScript | ||
contexts that already has leading indentation (such as inside a class body), so | ||
in practice, if we preserve the whitespace exactly as it appears inside the | ||
`<template>` tag, it still would not do what the author was trying to | ||
accomplish. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Strongly agree, and in line with the discussions we've had offline, I think we should probably apply the HEREDOC
/etc. rules: leading indentation up to the indentation of the line on which the opening <template>
appears should be stripped.
About the tagged template string literal version, I think it's a useful feature to have that can stand on its own, but also fits into our bigger plan around the content-tags-in-JavaScript format. But before that, I think we are in broad agreement that However, even with a degraded experience, it is still useful to have a version of the feature using only legal JavaScript syntax. Broadly, I can think of two kind of use cases:
codesandbox.io is probably a good example of the first case. It allows running arbitrary build code in a Node/remote container environment, but the editor UI is implemented in the browser and offers little in the way of an editor customization/plug-in system. In this scenario, if you were to use the We may or may not be able to work with the codesandbox maintainers to get this particular case resolved quickly, but the general point is that "tooling support" is a long tail, and I am confident that we will get the major ones ticked off pretty quickly and provide a superb experience in those environments, there will be a bunch of places where the support trails behind or where we just cannot support due to restrictions outside of our control. As the In my opinion, having this available as an option will also help with adoption. As editor support is still being worked on, some of these benefits apply today for editors that are still unsupported, and if some of your teammates aren't using the "mainstream" environments that could be a blocker to adoption as well. The "template backtick" version can help fill the gaps in the meantime, provides the same programming model (in terms of thinking about writing components) and is 100% codemod-able into the For the second type of use cases, the RFC text alluded to a few possibilities, but mostly it amounts to syntax highlighting in blog posts, gists, that kind of stuff (so codepen actually isn't a good example of that). Again I think these reasons are good and important enough that the feature can stand on its own, but also this layering approach fits into the broader "plan" (sketch) around content-tags-in-JS. The general idea is that we write a generalized specification that enables things like
...and that's it. It doesn't synthesize a default export, doesn't automatically add imports, etc. The point of this is to specify a general-purpose syntactic transformation/desugaring for the feature into standard JavaScript, while not specifying what the syntax actually does. This allows us to write a shared tool for this "first-stage" transformation that can be used by anyone interested in the feature (say, adding supporting In terms of implementing the
We don't necessarily have to immediately go out of our way to rewrite code that already works, but that conceptual layering/desugaring should work. |
mainstream editors, this will likely remain the case for a subset of less | ||
used editors that we did not or could not prioritize supporting. | ||
|
||
2. There may be editors and environments that are impossible for us to support, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is not unique to this RFC but applies to Ember in general. As such, I am not sure it's relevant to include as a supporting reason.
how do you mean? this RFC is essential for a lot of reasons. in a modern ember world, dynamic template stuff is super annoying / nearly impossible with deep intimate knowledge of how glimmer is zipping up the already existing primitives. |
@NullVoxPopuli your response here is part of why I had actually suggested breaking this into two separate RFCs: dynamic/client-side template compilation is indeed finicky, but it does not require the This remains my concern as well: for all that the RFC indicates caution around messaging, I think it’s important that it go further—it’s totally fine by me if the tagged template literal I do not (yet) find the motivation offered here for the tagged template literal as even a secondary authoring format (which it is explicitly offered as here) to come close to warrant reopening that very, very difficult consensus thing. Mind: this really comes down to “How Do We Teach This?” from my POV because, again, I’m on board with it as a well-rationalized desugaring as part of a multi-stage build pass. I think it makes an enormous difference in terms of the relationship to the existing consensus we worked hard to achieve whether or not we treat the tagged template literal form as something we should encourage users to author under any circumstances or not. I think we can probably have consensus on it quite easily as an intermediate compilation formation, and I am not sure if or whether we could get to consensus for it at all as even a secondary authoring format. In other words, this—
—is the crux of the issue. @chancancode's RFC proposes adding a secondary, but real, authoring format for first-class component templates, not simply an update to the compilation pipeline, and that is the part which is controversial. |
Ye! To me, and what I'm most excited for, is that example implementation of `template`import { precompileJSON } from '@glimmer/compiler';
import { getTemplateLocals } from '@glimmer/syntax';
import { setComponentTemplate } from '@ember/component';
import templateOnlyComponent from '@ember/component/template-only';
import { createTemplateFactory } from '@ember/template-factory';
export function template(template, scope, klass = templateOnlyComponent()) {
return setComponentTemplate(compileTemplate(template, scope), klass);
}
function createTemplate(source: string, scope) {
let locals = getTemplateLocals(source);
let moduleName = '(dynamically compiled template)';
let options = {
strictMode: true,
moduleName,
locals,
isProduction: false,
meta: { moduleName },
};
// Copied from @glimmer/compiler/lib/compiler#precompile
let [block, usedLocals] = precompileJSON(source, options);
// some assertion here about locals missing from `scope`
assert('...', ...);
let blockJSON = JSON.stringify(block);
let templateJSONObject = {
id: moduleName,
block: blockJSON,
moduleName,
scope,
isStrictMode: true,
};
let factory = createTemplateFactory(templateJSONObject);
return factory;
}
100% on board with this.
I'm not 100% sure what's controversial, even after reading this -- but maybe I can try re-phrasing and we can see how close I am? (I like lists) ✔️ is this close? |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
👏
- It requires manually supplying the lexical scope variable bindings (the | ||
"scope function"). | ||
|
||
- It uses a [static initializer block][static-block] to associate the template. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Based on reading the Class code snippets, the proposal is to use the static initializer block to side effect the association of the template? Or is there actual static initialization on the class itself? Perhaps inconsequential, but was hoping to enhance my understanding of this proposal.
However, that is not an ideal outcome. `<template>` is a user-facing feature, | ||
and is poised to (or at least well-positioned to) become the main way Ember | ||
users read, write and reason about components in the next edition of Ember when | ||
the feature is fully rolled out. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
<template>
vs template()
. What prevents the latter from wide adoption and use in the community? Do we just expect users to prefer the format in rfc 779?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The answer is: as currently proposed in the text of this RFC, nothing about it prevents that in any strong way. It would just be a matter of what is taught in the guides and what tooling does or does not support. We would expect those to be relatively strong nudges, to be sure—for example, I strongly suspect that everybody is going to want the goodies that Glint provides, and we won’t be supporting the tagged
form. But I think it’s very likely that we’ll have a steady stream of requests for support for it if we do actively teach this somewhere.
You actually captured fairly well the issue here, if only by accident! The trick is that there are two
For now, I’m going to refer to the pieces in play here as Then the list actually looks like this, in terms of what I think we have consensus on: ✔️ As I understand it, @MelSumner's objection is to having the |
ah, I get it now! I would very much appreciate this RFC to focus on the
seems like an overall good thing. Thanks for the clarifications! (and emoji list!) |
I would put it a bit differently. I don't think "authoring format" and "primitives" are as real/concrete as a distinction than we try to make it sound. By the standard we are using here, I think you could say the You could technically say that there is nothing "preventing" the primitives from gaining wide adoption – it's not like you get a warning when you use the primitives and they didn't make you type In those examples, I think it would be wrong (at least overly strong) to say the primitives should never be recommended under any circumstances. It is true that you would generally prefer to use the high-level syntax, it feels better to use, and tools like TypeScript gives you better support. However, occasionally you find yourself in strange situations where the high-level features just didn't work for you. Maybe you are dynamically modifying a class, writing a decorator-esq thing, etc. Perhaps you should reconsider whether you really should be approaching the problem that particular way, and sometimes that leads you to better alternative designs. But there are also less common but totally valid reasons for needing to do that. It's true that when you reach for the primitives, it tends to get a bit awkward, is likely to raise some eyebrows on code reviews, and you probably need to write a comment explaining yourself. But other than that, the primitives are totally "real" and have exactly the same semantics. If you have the need for them, you can reach for them, you can write the code, you can find the docs on MDN, you can choose to save it on disk, or not. All of the "can"s here doesn't mean "recommended", but it also doesn't mean "never ever recommended" or "we didn't mean to tell anyone about these". We can also look at some examples from the Ember ecosystem. In the Octane time frame, we shipped two features that changes the "authoring experience" for components – making template-only components have outer-HTML semantics (#278) and component template co-location (#481). The intended design is for components to be a fairly lightweight concept in Octane. At the most basic form, all they are is a way to make reusable markup snippets. You can easily create one buy making a When we first proposed/shipped #278, template-only components are specified in terms of the user-facing feature – it is a bare This made sense, as that's how most users would interact with this feature on a day-to-day basis. However, over time, we realized that there are a small but real use cases or environments where these specifications didn't make sense or is not reliable/acceptable. For example, addon authors cannot generally count on the flag being enabled in consuming apps, the addon opt-in and filesystem specifications didn't necessarily make sense for Ember Twiddle and other exotic/non-Ember-CLI environments. Therefore, when we did #481, which was primarily about the user-facing component template co-location feature, we also introduced the primitives at the same time, like I think that divide held up reasonably well overtime. If you were to survey 100 random Ember users, most of them probably have never heard of or used these APIs. And yet, there just are use cases where they are useful and necessary (the Another thing that happened, TypeScript (Glint) users ended up writing But at the end of the day, the confusion we were trying to prevent was pretty bounded and manageable. We would have preferred to present the thoughtful design to all of our users, but it's not like the programming model is a fragile house of cards that falls apart immediately if you look at it under the wrong light. Perhaps it would have been better if we worked with the TypeScript/Glint sooner to develop an alternative that preserved the intended design (which we are doing with With all that said, I definitely do not regret including Another example from the Octane time frame was that we introduced the So we did #669 to introduce the primitives, which we intentionally chose to do rather than simply solving the most common symptom of the problem by introducing tracked built-ins (which is a great set of APIs are on the way of being standardized into Polaris, but they couldn't solve all the problems/scenarios). With the benefit of hindsight, IMO it would have been better if they were rolled out at the same time with So back to this RFC. The intention here is to fill out the "missing middle" APIs for the I think the context were just pretty different. In the work leading up to #779, the focus was designing and standardizing the primary end-user facing feature. That made sense, and that's what matters the most at the end of the day. I am happy with the choices we made there, and it makes sense that we prioritize the most important day-to-day use cases and focus on how we could make that the best possible experience. Like In an edition, each individual feature is just a single puzzle piece that forms a bigger picture. With Octane, there were a bunch of individual changes the the component programming model – outer-HTML template-only components, defaulting to template-only components in the generators, co-location, angle bracket invocations, splattributes, In Polaris, But at the end of the day, what is even more important is to get everyone on board with the new components programming model, and we cannot do that with And to be clear, I am not talking about presenting these as "alternative" as in "beef, chicken or vegetables". It's more like "you can get the binaries for this tool, or install it with With Now, is it possible that someone would take the primitives and use it in ways "we don't like"? Sure? Does that mean it's a bad idea to expose the primitives? I don't think so. First of all, we should probably have less opinion on what experiments others choose to do in their free time and how people want to write their code 😛 But more importantly, if people are motivated enough to experiment/deviate from the happy path, they are going to do it anyway. By providing more strategically placed branching-off points/off-ramps, we actually get to share more and keeping everyone closer together and headed in the same overall direction. If the only available branching-off point is all the way down at the bottom and they have to rebuild a lot of the same things and remake a bunch of the decisions we already made, that's when you end up getting people veering way off course. Case in point, whether you personally prefer it or not, projects like Emblem exists and some people like to use them. We don't have to go out of our way to support them, but where possible we should work towards layering the work so that it is possible for someone who is motivated enough to make it work can leverage a lot of the work we put into making So, specifically about the tagged template literal part of the layering. I am happy to call it an intermediate format. I think there are use cases for it where that makes sense, and honestly I think I made a pretty good case for it with the codesandbox scenario. There just are places where running a build is not an issue but the editor experience is the bottleneck. We can agree that this is not a super common situation and that this is not a massively popular/important use case, and that's a good thing! Otherwise, we will be in even deeper trouble! But that's not really the point and not the bar we need to clear for these middle/primitives APIs. We landed I also stipulated that some users (because of their editor preferences, or because they want to be an early adopter) may want to adopt the programming model but is constrained by the editing experience. I think that's just a special case of the same situation that is perhaps more concrete and easily to relate to, but now the concern is "woah, now that's too useful and uncanny, it solves that problem almost too well and people may get comfortable with it and not want to switch", so that kind of feels like a catch 22 situation in terms of justifying whether this feature is actually useful or not. 🙃 As far as why isn't just having Even if we ignore the writing-by-hand cases, it's still good for us to layer our tooling that way so that a third-party babel transform (say, something to do with Emblem or some other experimentation that requires splicing in components) can be inserted before ours and emit the backtick code and let our plugin handle generating the correct scope closure, rather than having to target the I have more thoughts but this is getting long and it's getting late. The bottomline is, I agree this is probably mostly a "How Do We Teach This?" issue, but I don't think it's as simple as what words we do or do not say in the docs. I think "authoring format" vs "primitives" is kind of a red herring, in the real world they are not going to be so black and white. When we introduce APIs, of course we expect some people to actually use them. We can be clear about our intentions, provide good guidelines, clear documentation for their purpose, carrots, etc, but at the end of the day we need to trust those who are writing the code to pick the right tools for their situations given our guidance. We can't be everyone's code parents. So, I think it makes more sense to focus on what future we are intending to build and make that clear – Happy to have the conversations at the meeting tomorrow and I am sure that would change things too, so I'll leave the rest for later. |
I'm really excited about this RFC. I'm working on a project that where one of our steps is to compile JS using babel at runtime. Our js includes glimmer templates. One of the challenges that we are running into is that the various babel plugins for class property and decorator support can alter a simple class into something that is almost unrecognizable. For instance if you have a class that looks like: export class Person extends Card {
@field firstName = contains(StringCard);
@field lastName = contains(StringCard);
static embedded = class Embedded extends Component<typeof this> {
<template>
<@fields.firstName/> <@fields.lastName/>
</template>
}
} it will result in this after a babel compile with the standard presets (where we use the .
.
export let Person = (_class = (_class2 = class Person extends Card {
constructor(...args) {
super(...args);
_initializerDefineProperty(this, "firstName", _descriptor, this);
_initializerDefineProperty(this, "lastName", _descriptor2, this);
}
}, _defineProperty(_class2, "embedded", (_GLIMMER_TEMPLATE = __GLIMMER_TEMPLATE(`
<@fields.firstName/> <@fields.lastName/>
`, {
strictMode: true
}), class Embedded extends Component {
constructor(...args) {
super(...args);
_defineProperty(this, _GLIMMER_TEMPLATE, void 0);
}
})),
.
. sadly that moves the
I think providing a I could see |
I'd like to try to catch up on this, but maybe someone who has been following along can summarize where this stands. |
My understanding here is that when @gitKrystan and @wycats did the low-level integration work for ESLint et al their finding was that having a JS representation like this was less useful than standardizing an AST-based representation. If that's correct, we may want to replace this RFC with one that standardizes the AST representation of |
Here are some issues we've been having that might be alleviated with a standardized AST representation: ember-tooling/prettier-plugin-ember-template-tag#43 |
Discussion from core framework review meeting:
|
This was superseded by #931 which is advancing. |
This brings back some details about runtime template compilation that got lost from #813
This brings back some details about runtime template compilation that got lost from #813
Rendered