How to handle non-type=”module” scripts in HTML Modules?
A side-effect of removing the type="module" declarations from scripts in an html module file is that the html file itself may become invalid (when loaded directly as an HTML file, not as a module), because in this scenario the scripts will not be interpreted as module scripts. By going through with this, you are essentially creating two classes of *.html files; one that may be opened directly in a browser and one that may only be loaded as a module.
I'm not sure how much of a concern that is, but I think it is worth considering.
Variables (that is, custom properties) have no relevance here. They're already correctly element-scoped+inherited, so there's no sense in which scoped() can apply to them.
Perhaps it is a good idea at this point to summarise the current proposal in its entirety, which I will attempt below.
Name-defining construct Any CSS rule that creates a new 'name' that values can refer to, such as @font-face or @keyframes.
Named value Any CSS property value that refers to a 'name' that can be defined using a name-defining construct, such as values of font-family and animation-name.
CSS name-defining constructs (@font-face, @keyframes, etc) define those names in the nearest enclosing ShadowRoot or Document of the <style> node or constructed stylesheet that contains the name-defining construct.
CSS named values should resolve by looking up the name in the nearest enclosing ShadowRoot or Document of the <style> node or constructed stylesheet where the name was used.
If a CSS named value is set through the .style property of a Node, the nearest enclosing ShadowRoot or Document of that node shall be used to look up the value instead.
If a name is not defined in a scope, a recursive lookup should find that name through the containing shadow tree hierarchy up to the document (similar to CSS variables).
The internal representation of a named value consists of a name and scope. The scope is a ShadowRoot or Document object and refers to a shadow tree or document where the name should be looked up.
Serialising a named value (through getComputedStyle or other methods) is unchanged, which means it returns only the name, and scope information is lost.
When retrieving a named value through TypedOM, both the name and scope component will be available through name and scope properties.
When setting a named value through TypedOM, a specific scope can be provided, which overrides the default of the nearest enclosing ShadowRoot or Document of the element.
For CSS properties that are inherited through shadow tree boundaries (such as font-family) the inherited value retains the original scope.
What happens when CSS documents are loaded in through <link rel="stylesheet"> elements inside a shadow tree?
Do name-defining constructs in that CSS document re-define the name inside that shadow tree?
Do properties using named values look up the names again in the context of that shadow tree?
What happens when an element has a style with a named value set with the default scope of the nearest enclosing ShadowRoot or Document, and that element is moved to another document or shadow tree? Does the named value retain it's original scope, or does the scope change to the new location?
When using traditional (non-TypedOM) methods to retrieve a named value, some information is lost. This means that reading and setting the same value may have the side-effect of changing the scope component of an element's style, e.g. el.style.fontFamily = el.style.fontFamily is not side-effect free.
font, font-family, animation, animation-name, ...?
It will be impossible to write an inline style declaration that refers to a variable that exists in an outer scope but is overridden in the scope of the element's shadow tree. I expect this to be extremely rare, and there exists an easy workaround of re-defining the outer variable with another name.
If the inline style declaration is always scoped to the shadow tree scope of the element, it will be impossible to write an inline style declaration on a slotted element that refers to a variable defined inside the shadow tree it is slotted into. This leads to a weird situation where if a slotted element is assigned a style from within the shadow tree (with some ::slotted() CSS selector), and that style is copied verbatim to the element's inline style (say through the browser inspector), it will change in behaviour. Again, I expect this to be extremely rare.
I agree with @rniwa that we should optimise for common usecases and scenarios first. The proposed model seems consistent from a developer's perspective, and most of the issues mentioned only arise when existing objects are modified imperatively by an external force. In those cases there is always an alternative more heavy-handed solution to solve the same problem, and I don't think we can get to a perfect level of consistency that includes all these edge scenarios without sacrificing ease of use (by introducing additional syntax).
The behaviour described by @rniwa is what currently happens in all browsers for the @keyframes global name (See https://codepen.io/ruphin/pen/pBpxvx). I think this is the most expected and reasonable behaviour, and @font-face should probably behave in a similar way.
Regarding inheritance and variables leaking outside the shadow tree; this is already the case currently. Custom Elements are able to style direct descendants from styles defined in the shadow root, and they can add any style to a direct child, including styles that are inherited. (e.g. font-family: monospace). I don't think this is considered 'leaking out' of the shadow tree, otherwise the ability to style descendants would not be part of the spec in the first place. It makes sense that for any styles that are inherited, the complete style is inherited, which in this case includes the correct reference to the correct @font-family.
There is still the question of what happens to slotted content. Currently this is the only feature that exhibits inconsistent behaviour between browsers. The complexity is that when the element is assigned a style (say, animation) that refers to a global variable (in this case @keyframes), it is not clear which scope it should find this variable.
In my opinion the only reasonable behaviour is to look up that variable in the scope where the original style was assigned (in this case wherever the animation style was assigned). Safari correctly does this, but Chrome and Firefox look up the variable in the scope of the element itself which is always outside the shadow root.
The most obvious argument why the behaviour implemented by Chrome and Firefox is wrong, is that it is literally impossible to animate slotted content using @keyframes definitions without polluting the global scope.
Personally I never understood the need for a scoped() function. The only purpose would be to allow a global variable to be used in a shadow tree instead of the local one, but since the Custom Element implementer has full control over the shadow tree, this can effectively already be done by naming the local variable differently.
Open Web Components provides a set of defaults, recommendations and tools to help facilitate your Web Component.
Mark fields as readonly where possible. Likewise use ReadonlyArray. (#749) * Mark fields as readonly where possible. Likewise use ReadonlyArray.
Prevent empty styles from causing problems in `shady-render` (#864) * Prevent empty styles from causing problems in `shady-render` Fixes #760. ShadyCSS eliminates empty styles. This caused 2 problems in `shady-render` when using native Shadow DOM: 1. When re-adding the style to the rendered content, the code assumed a style would exist after ShadyCSS handled the content. It now checks. 2. When modifying the lit template used for subsequent renders, the style could be eliminated. This would break node ordering. A dummy style is now inserted in this case. * Update CHANGELOG.md Co-Authored-By: sorvell <sorvell@google.com> * Update comment in src/lib/shady-render.ts Co-Authored-By: sorvell <sorvell@google.com> * Address review feedback. * Simplify logic slightly * Fix typo * Format.
Add Creating directives doc. (#874) * Add Creating directives doc. * Addressed feedback.
Document how to render no content (#785) * Draft of fixing #784 * Addressed some review feedback: added import for nothing * Addressed some review feedback: added slot fallback content on when to use nothing. * Rephrased the last sentence * Added more information on rendering nothing with empty strings, null or undefined. * Added another example using undefined and added ifDefined comment * Moved section to writing templates * Addressed review feedback * Added a line addressing inconsistency of null and undefined.
Shorten lastAttributeNameRegex a bit (#850) The Unicode control characters include all space characters except " ". So the attribute name character class doesn't need to include them, they're already part of the `\x-\x1F` range.
Mangle private properties (#859) This enables mangling of private properties by giving them the `__foo` naming convention. Really, that's just to distinguish them from the protected `_bar` pattern that was introduced with the Part APIs.
Fix rendering comment binding followed by attribute (#845) We forgot to update the `partIndex`, so when the attribute tried to execute the `lastAttributeNameRegex` on the last string (determined by the `partIndex`), it would fail to match anything. I'm guessing this will fix #613.
Early exit template creation (#849) We know how many parts are in the template by the number of values that the `TemplateResult` is holding. So, when we've found that many parts, we can stop traversing the template tree and go back to rendering that DOM.
Found the problem. The zero-md element has a style contain: content that is not recognised on non-chrome browsers. This is also why the top and bottom padding disappears in the markdown frame. If this rule is removed, Chrome behaves the same as the other browsers.
It's trivial to fix, the zero-md element just needs a classic overflow: auto or overflow: hidden as a fallback to the newer content CSS rule.
The parser/template instantiation system I'm working on fixes this issue. It annotates the bound attributes during initial template creation, as suggested here.
For real world use cases I may be able to provide some insights from the perspective of ING, particularly in the area of accessibility.
How will the directive work in multi-part attributes?
The reason LitHTML is fast is because it uses mostly native capabilities to do rendering, and does not have to build virtual trees. However, there is no Element.changeTag()-like API in the platform. If you want to change a node type, you have to re-render that node and all it's content. Even the most advanced implementation using some form of templateString-caching as suggested by @luwes is indistinguishable performance-wise from switching between different static templates (when the dynamic tag changes).
VirtualDOM lends itself to this type of rendering, and if you wish to use dynamic tags, you will probably get more efficiency out of VirtualDOM rendering techniques.
If you want to get that same level of efficiency with LitHTML, you will have to implement a more complex re-parenting scheme as described higher up in the thread, at which point you are essentially re-implementing functionality from VirtualDOM-like renderers. It is not technically impossible, but implementing something like that is way beyond the capabilities of most users, and far more advanced than the solutions suggested in this thread.
As an aside, the performance cost of naive implementations right now is probably not very high, but this is because of a bug in Safari that causes the object ID of StringsArrays to occasionally change. To solve this we have a fallback key mechanism that checks for string equality, which means that naive implementations get "caching" for free right now (the key comparison/lookup is still strictly slower). Once Safari has been fixed and the workaround is removed from LitHTML, these implementations will suddenly stop working and lose all caching capabilities, which means they will fully re-build the templates and re-render the DOM on every render call, even if the tag stays the same.
Even when some kind of cache is implemented correctly (which is not trivial), changing the value of a tag will still cause all the nodes in the template to re-render, including the static ones, which breaks user expectation. This breaks all templates with input elements in their static content. Right now this is safe because regardless of dynamic values inserted by LitHTML, the static parts of the template are unchanged. If you have a dynamic tag anywhere inside the template, all nodes will be destroyed and recreated when the dynamic tag changes, which destroys all state of the input elements (like the value, or the fact that they may currently be the active/selected element).
These are all implementation details which normal users are not expected to be aware of or understand. This is why I think it is important that average users don't get the idea that using dynamic tags is "fine" or even a consideration. The risk is average users googling how to use dynamic tags, landing on a thread like this with naive implementations that seem to solve their problem, and them using it without understanding the implications. There are other solutions than dynamic tags to solve the problems that users have, and they should look into those instead.
Yes, it is possible to a degree, but this requires a specific implementation that none of the above examples include.
It also means that when the dynamic tag changes, the entire template is re-rendered, which is not what developers expect coming from React or similar environments. This is why I think it is a bad idea to promote this pattern in general, because it breaks expectations of what lit-html does.
Experienced developers with very specific use cases may find a purpose for this, but for general public this pattern should not be promoted in any way.
Note that constructs like this disable the ability of lit-html to effectively cache your templates, which means that on every re-render, it will destroy the DOM and rebuild it from template. Dynamic tags are not supported by lit-html for this reason.
Just leaving this warning here since people seem to keep finding this thread. I strongly recommend NOT to do anything like this, unless you have a VERY good reason to do so. Your rendering performance will degrade significantly when you do this.
What is the benefit of moving the complexity elsewhere? Won't that just bloat ShadyCSS for all non-lit-html users? Or is there a significant amount of duplicate code that can be eliminated by merging the codebases?
It is a very small edge-case that is currently unspecified in the specs, but it can be resolved without touching larger issues like scoping of css name defining constructs.
I think it is trivial to demonstrate that the Safari implementation is correct. It is currently impossible to apply animations to slotted content in Chrome and Firefox due to this.
If I have five minutes to make the case, I am sure we can get a consensus on it, so we can suggest a possible resolution to the csswg.
It is this mentioned in this issue: https://github.com/w3c/csswg-drafts/issues/1995 Although I think that issue got somewhat derailed into a larger discussion on handling of name-defined constructs in shadow trees.
I'm only concerned about the current incompatibility between Safari and Chrome/Firefox regarding the lookup scope of name-defined constructs for slotted content.
What does the 'outstanding issues' list look like, and is there a way to put items on this list? There is a small outstanding compatibility issue I reported a year ago that needs some kind of resolution, and can hopefully be resolved in a few minutes.
The ifDefined directive will render the attribute if the value passed in is not undefined. However, it will not work for falsy values.
This is a refactor of the template parsing system. It is a work in process, making this PR solely for testing purposes.
I made a small correction to the description, but I'm not sure if the wording is correct.
Here is a very crude proof of concept: https://codepen.io/ruphin/pen/jJVpNa?editors=0010 It seems to be able to handle almost every weird combination of content that I can throw at it, and correctly parse the context and part type.
It still has one minor issue, it does not insert a marker before nodes with an attribute with "" delimiters, but I should be able to put that in when I clean it up.
It looks like a lot of code, but note that it solves a number of other issues that we currently have workarounds for, for example it removes bound attributes from the template entirely so we don't have problems with IE's attribute rewriting. If we add up all the code for those workarounds and the current implementation, I'm sure we can reach a similar byte size compared to the old implementation.
I have no idea yet about runtime performance. I need to clean this code up and implement a constructor for TemplateInstances and Parts that works with this scheme before I can run any benchmarks.
I am working on a prototype template parser that uses ideas from this PR to improve the template creation process.
A Template only needs to hold the TemplateElement constructed with the HTML string, and the above information. With these two values we should be capable to construct TemplateInstances directly and with minimal intervention (only walking comments, no parsing of any content required).
The main complexity is in the HTML parsing that we have to do to figure out the context of these parts, but I believe it is relatively easy to do so. We don't have to implement a 'complete' HTML parser, it only needs to differentiate between different basic contexts (between node tags, inside a node tag between < and >, inside a comment, and inside a style tag). I'm working on a basic implementation for this at the moment, and it seems promising.
I have a hard time judging if this HTML string parsing is going to be more efficient than the current tree walking and parsing, but my intuition tells me that it should be. Especially when considering all the random edge cases we are running into with the current parser.
Fix attribute-like text rendering "$lit$"
The parser doesn't have to be fully compliant. It just needs to pass along all information it has access to so we don't have to collect this information again.
I don't like the current implementation. I don't understand why we start off with a well-formed StringsArray, only form a blob of HTML where we lose a lot of information, and then proceed to parse all the TextNodes and attributes to get that information back.
I think this is an opportunity to create a similar parser that works well with #847, by inserting the correct commentNodes and passing on the relevant information that is required by #847, so we can skip most of the work in the Template constructor as well.
I'm looking into fixing that right now.
<!-- Tell us about the issue you saw. A clear description, links, and screenshots help us fix it faster. --> The CustomElementRegistry.upgrade() method is missing from documentation.
I can write up a draft for this documentation page, but I do not have the required rights to create new pages.
Cleanup styleMap directive (#838) - `styleMapStatics` is unnecessary, we can determine if this is the first render by just checking `styleMapCache`. - Use variables for repeated property accesses to save some space.
Cleanup until directive (#839) Gets rid of a few useless if checks, and uses an infinity value so we don't have to explicitly check for `undefined`. Also guards against an out-of-bound array access on `previousValues`. I took a bit of liberty with using a 2<sup>31</sup>-1 as "infinity". If you want, we can use the real `Infinity` value.
Adopt+upgrade template fragments after processing for parts (#831) Importing the node can cause any custom elements to upgrade, and non-compliant custom elements can cause DOM mutations. If this happens before we process the template parts, our `part.index`s walking will be broken. So, it has to wait till afterwards. But we still have to return upgraded elements before the template part's interact with the DOM. Luckily, we just adopt and upgrade them after the processing. Fixes #561.
Optimize TemplateInstance cloning (#827) I'm assuming it's very common to have a large number of nodes in between the template's parts. If so, creating a small inner loop to advance the tree walker should make everything quicker.
Use tagName when appropriate (#840) Just a small nit.
Reformat the repeat directive's comment (#842) It was annoying me too much to read it this way.
shady-render: fix warning formatting (#834) This fixes spaces inside the warning that gets emitted for incompatible ShadyCSS versions.
Use a stack to create walk nested templates (#828) On the heels of #825, this unrolls our recursive template processor into a stack based one.
Remove unnecessary type assertions (#846) * Remove unnecessary type assertions This is the result of turning on the no-unnecessary-type-assertion tslint pass and running `--fix`, then fixing up a few tests using `Deferred` and adding an additional assertion in places where TypeScript couldn't infer that a variable was always assigned before being read. I like this lint pass because it makes the code a bit easier to read and understand. I will admit though that I'm proposing this change because we use this lint pass downstream, and it's annoying seeing the lint warnings.
Can we use document.createComment(data || '') here so we can drop the '' parameters at most of the createComment calls?
Overall I'm not a fan of this change. None of the codebases or applications that I have seen ever drop references to templates, so this change would not make any difference other than open up the opportunity for developers to explicitly remove templates from cache, but I don't think there is any demand for this.
Given the temporary nature of the issue, I prefer the current architecture.
Wouldn't this.clear(itemPart!.endNode) also work? I'm not super familiar with TypeScript so it might be functionally different, but it seems more in line with general TypeScript style.
As mentioned before, lit-html does not process your HTML in any way, and anything outside dynamic bindings is left untouched. This is by design, and it is not an issue.
This will only set the property once during the initial render, and does nothing on consecutive renders.
Remove almost all uses of "any" outside of test code. (#741) * Remove all uses of "any" outside of test code. * Format * Give better types for EventPart * Format * Address review feedback. * Address more review feedback. * Get tests compiling cleanly. * Don't use isThenable function. * Add accidentally deleted check. * Revert isIterable, potentially too expensive. * Use EventListenerOrEventListenerObject instead of reinventing it. * Reformat * Use EventListenerObject type.
Update directive docs (#675) * Update guard directive docs. * Fix typo. * Update template reference. * Update directive reference. * Apply suggestions from code review Co-Authored-By: arthurevans <arthure@google.com> * Address feedback.
Added Styling & Rendering sections. (#754) * Added Styling & Rendering sections. * Update docs/_guide/03-styling-templates.md Co-Authored-By: arthurevans <arthure@google.com> * Address feedback.
This test is flawed, you're never releasing the tagged template itself. You have to set getTemplateStringsArray to null or place the entire code in a scope that is itself cleaned up. Try on https://output.jsbin.com/wobolap/12/quiet.
I don't quite understand. What I'm seeing is that the key still exists after the timeout: TemplateStringsArray after Timeout true. You are getting the same result judging from the screenshot you posted. Am I interpreting those results wrong?
This change is specifically for Safari. When it's TemplateStringsArray is purged (in error), it'll generate a new Template.
It doesn't. There are two levels of caching, one with the TemplateStringsArray and one with the joined Strings. If Safari purges the TemplateStringsArray objects, the second cache will hit and use the cached Template.
Can you describe a specific scenario where the old implementation does not correctly use a cached Template, or performs more than necessary equality checks? I can't think of one.
From a logical perspective, I believe the original implementation was already optimal. In all cases (new template, existing template and equal to previous template, and existing template and not equal to previous template) it performs the minimum number of operations required (where the relevant operations are object equality check, strings equality check, and template instantiation).
The real question is if we want to actively purge from cache any templates that are not currently rendered.
As far as I can tell, it never clears the key/value pair from the map. Because this refactor still keeps a map of TemplateStringsArrays to Templates, the Templates are still never cleared from memory, which means this refactor effectively does nothing.
The original issue outlined in #561 is that we don't know if the clone-then-adopt approach has a significant performance impact compared to the direct import we have now.
This change does simplify Custom Element handling and fixes several edge cases, but we should get some benchmarks and measure the performance impact before making this change.
This should be a.strings === b.strings.
Or better, aStrings === bStrings and placed after the constant definitions below.