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

Template keeps getting recreated for ES5 / ES3 targets (Typescript) #58

Closed
Pesthuf opened this issue Aug 21, 2017 · 5 comments
Closed

Comments

@Pesthuf
Copy link

Pesthuf commented Aug 21, 2017

Recently I tried to create a simple greeter component with an input that greets the person the user inputs. I used Typescript.

Bascially,

function myComponent(whom: string) {
    return html`Hello <input on-input=${() => updateWhom}>  ${whom}!`;
}

Where "updateWhom“ just re-renders the component with the new name

function updateWhom(e: Event) {
    let target = e.target as HTMLInputElement;
    render(myComponent(target.value), app);
}

I bundled the application using webpack and on modern browsers, it works as intended - when a user types "Bob" into the input, "Hello Bob!" is output.

Now, changing tsconfig’s "target“ from "es2015“ to "es5“ for browser compatibility changes the behavior of the application.

Whenever the user inputs something into the input, the input element loses focus after every keystroke. The greeter text still gets updated to the single character that was typed.
It seems this is because the entire Component, including the input element, gets completely recreated

Possible Explanation

The typescript compiler compiles the "myComponent“ method to

function myComponent(whom) {
return (_a = ["Hello <input on-input=", ">  ", "!"], _a.raw = ["Hello <input on-input=", ">  ", "!"], html(_a, function () { return updateWhom; }, whom));
var _a;
}

While this is correct and will call the "html" function with the same arguments as a tagged template would, it will create a new strings array (_a) on each call to "myComponent".

This breaks the "html" function, because it looks up the strings array in a Map:

function html(strings, ...values) {
    let template = templates.get(strings);
    if (template === undefined) {
        template = new Template(strings);
        templates.set(strings, template);
    }
    return new TemplateResult(template, values);
}

With real tagged templates, the "strings" parameter will always refer to the same array, so that in subsequent calls to the "html" function with the same tagged template, "strings" will be found in the map and so the Template that's saved in the map gets used as intended.

But in the compiled code, a different array gets passed in "strings" every time that just happens to have the sme content, so the template is never found in the map and a new template has to be created every time that destroys the old HTML elements.

I'd say this isn't really lit-html's fault, but Typescript's.
They should probably instead compile it like

var _tmp = ["Hello <input on-input=", ">  ", "!"];
_tmp.raw = _tmp.splice;

function myComponent(whom) {
    return html(_tmp, function () { return updateWhom; }, whom);
}

(or similar) so the same exact arrays are reused, just like they are with tagged templates.

Babel actually handles this correctly:

var _templateObject = _taggedTemplateLiteral(["Hello <input on-input=", ">  ", "!"], ["Hello <input on-input=", ">  ", "!"]);

function _taggedTemplateLiteral(strings, raw) { return Object.freeze(Object.defineProperties(strings, { raw: { value: Object.freeze(raw) } })); }

function myComponent(whom) {
    return html(_templateObject, function () {
        return updateWhom;
    }, whom);
}
@Pesthuf Pesthuf changed the title Template keeps getting recreated for ES5 / ES3 targets Template keeps getting recreated for ES5 / ES3 targets (Typescript) Aug 21, 2017
@justinfagnani
Copy link
Collaborator

justinfagnani commented Aug 21, 2017

Awesome bug report @Pesthuf! I'll look into either workarounds, or trying to get a fix in TypeScript.

Did you by any chance try Babel? Edit: oops, was not paying attention!

@PatrickJS
Copy link
Contributor

this must be the memory leak that I saw while using typescript

@justinfagnani
Copy link
Collaborator

We can work around this by generating a key from all the individual literal parts, but the overhead of having to do that is unfortunate...

@justinfagnani
Copy link
Collaborator

BTW, thanks again @Pesthuf for reporting this and @gdi2290 for the PR. Since we can pretty easily handle this we will, even though technically it's TypeScripts bug. The fix can be general enough to handle other incorrect implementations of template literals.

@justinfagnani
Copy link
Collaborator

Fixed by #75

aomarks added a commit that referenced this issue Nov 2, 2020
Add two simple working example apps
rictic added a commit that referenced this issue Mar 5, 2024
Smartly focus element's source when selected
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

3 participants