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

RFC: Partial Template #24

Open
wants to merge 5 commits into
base: master
Choose a base branch
from
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
113 changes: 113 additions & 0 deletions text/0000-partial-template.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,113 @@
---
title: Partial Template
status: DRAFTED
created_at: 2020-01-16
updated_at: 2020-01-17
pr: (leave this empty until the PR is created)
---

# Patial Template

## Summary

Partial templates allow the ability to import html templates into another template via
the normal variable syntax `{template}`.

## Basic example

```js
import partialTemplate from './partialTemplate.html';

export default class Example extends LightningElement {
partialTemplate = partialTemplate;
}
```

```html
<template>
Parent
{partialTemplate}
</template>
```

## Motivation
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I can see other motivations:

  • component inheritance. Typically, a base component can have some variables fragments in its template, which can be overridden by an inherited component. A good example is a complex commerce product detail component where you only want to change how the price is displayed.
  • Repeaters. You can easily create a 'repeat' component that takes a template as a parameter and iterate through it.


In other instances it was noted that large conditonal blocks of HTML could be more organized
Templarian marked this conversation as resolved.
Show resolved Hide resolved
into seperate template files.

## Detailed design
Copy link
Contributor

@caridy caridy Jan 17, 2020

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The biggest question for me here is what is the scope of the partial? can it be controlled? or we assume that the template have access to the component instance? To be clear, I'm talking about {x} inside the partial, what does it do? is that equivalent to {x} in the importer? What if you the template inside an iteration, what is the scope of the partial? Keep in mind that today we use the JS scoping to resolve the bindings available for a block of compiled template?

Also, I think the fact that you need JS to use this makes me doubt that this is the correct approach.

Copy link
Author

@Templarian Templarian Jan 17, 2020

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Would 100% assume it would work the same as if the partial's content had been pasted in.

template = 
<template>Hello {name}!</template>

class { template = importedTemplate; name = 'world'; }
<template>{template}</template>

Would result in:
Hello world!

Same would be the case anywhere the {template} is placed picking up any scoped variables.

Need to look into how templates are processed now to better understand how this could work. The alternative being templates are compile time placed in, but that limits what they can do and would be mainly for splitting up templates code.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

well, this is faulty for two main reasons:

  1. it is not a reusable partial because you don't have a way to specify what should be accessible to it.
  2. it is impossible for the compiler (at its current state) to invoke another function (compiled partial) while providing the same JS scope of the caller.

While 2 is just a technical limitation that we can attempt to figure, I believe 1 is an ergonomic issue that is a deal breaker here, let me put an example:

// foo.html
<template><p>Value: {value}</p></template>

// component.html
<template>
      <h1>Before</h1>
      {foo}
      <h2>After</h1>
      {foo}
</template>

I'm trying to reuse a piece of the UI so I don't have to duplicate it in my template, but also I don't have to worry about the two fragments to go out of sync. This is clearly a use-case for partials, yet it doesn't work because the partial expects value to be accessible, but I have two values in the caller that I will like to display in two different places. That's why I think the scope MUST be specified, e.g.:

// foo.html
<template><p>Value: {value}</p></template>

// component.html
<template>
      <h1>Before</h1>
      <template lwc:partial="./foo" value={myBeforeValue}></template>
      <h2>After</h1>
      <template lwc:partial="./foo" value={myAfterValue}></template>
</template>

It also provides some declarative only syntax where the value of the lwc:partial directive doesn't have to be an imported value in JS if the partial is relative.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Updated the alternative section with the example above.

That greatly simplifies the usage and allows more flexibility with template reuse. Assuming one could pair that with if:true/if:false.

Would there be a huge jump in complexity between that and...

<template lwc:partial={template} value={val}></template>

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Would that work for events as well? Let's assume that my partial has a button and I'd like to trigger an action onclick. How can I assign an action? Is it something like this:

<template lwc:partial="./foo" onbuttonclick={myHandler}></template>

<template>
  <button onclick={onbuttonclick}>Mybutton</button>
<template>

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@priandsf Assuming when it compiles down it has a way to wire up the scope it probably could do the same for events. Not really familiar enough with how the internals work though (just assuming it's a compile time in this case).

(this proposal was put on hiatus, but still something that would be beneficial)


Templates can be rendered into a component one of two ways currently.

- By matching the name of the component. Ex: `cmp.js` / `cmp.html`
- By returning a default import in the `render() {}` method.
- Ex: `import cmpTemplate from './cmp.html'` -> `render() { return cmpTemplate; }`

A template is a constant and cannot be modified. Partials will inherit this same behavior.

This simplifies things, but still may lead to complications in optimizations [unfamiliar with
what this entails, so may need LWC context here]. Example:

```
get dynamicTemplate() {
return this.conditonal ? template1 : template2;
}
```

## Drawbacks
Templarian marked this conversation as resolved.
Show resolved Hide resolved

Why should we *not* do this? Please consider:

- Static analysis. Analyzing the template can give us a lot of information, but with something
like this, it makes it more difficult.
- Conditionally swapping out partial templates could be performantly bad in themselves negating
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm curious to understand what optimizations we are thinking about here, and how bad this can be on the performance of business apps. Do you have more details?

the benfit if we're not able to optimize for this.
- Recursion good/bad.

> Note: Developers coming from other frameworks would expect this to work this way.

## Alternatives

Partials imported via the `template` tag and `lwc:partial` attribute.

- Introduce `lwc:partial="./file.html"` for compiled time content.
- Scoped `{variables}` within templates passed via attributes.
- Question: Would `lwc:partial={templateUrl}` not work? What about `lwc:partial={template}` via
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

it should work fine I believe, but @diervo will have to look at this.

an `import`.

```
// foo.html
<template><p>Value: {value}</p></template>
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

there are more details missing here, what you can pass into a template tag are attributes, what you can use in {something} are attributes, basically:

<template lwc:partial="./foo" some-value={myBeforeValue}></template>

In which case, you will use:

<template><p>Value: {someValue}</p></template>


// component.html
<template>
<h1>Before</h1>
<template lwc:partial="./foo" value={myBeforeValue}></template>
<h2>After</h1>
<template lwc:partial="./foo" value={myAfterValue}></template>
</template>
```

This syntax greatly cuts down on what would be possible and introduces verbose
conditional blocks of `if:true` that could lend themselves to unnecessary rerenders.

## Adoption strategy

Since this is not a breaking change and purely an enhancement to an existing syntax developers
adoption will be optional. If they decide to reorganize existing components they'll go in with
expectations partials are the way going forward to make complex templates more readable.

In the examples above we may want to recommend reoganization of existing components if one was
currently heavily nesting components.

# How we teach this

For any developer coming from JSX environment this pattern is already normal practice. Showing a
simple example is enough to explain how partials work.

Currently a developer would assume this syntax would include a partial and not `.toString()` the
JS template function.

# Unresolved questions

- As with all feature this will depend heavily on performance.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think there will be any performance penalty with this proposal, you can remove this unresolved question. Partial templates will just be another function invocation returning an array of virtual DOM nodes that will be inserted into the parent virtual DOM tree. This doesn't have any implications for the diffing algo.