Make compatible with closure advanced compilation#374
Conversation
Currently, compiling lit-element with the Closure compiler results in a clean build but a number of hard JS errors due to a few issues: - static methods being collapsed (i.e. removed entirely) - Object.hasOwnPropertyName() looking for static properties that have been rewritten by the compiler - static property initializers not being run when @exported The last issue is a compiler bug being tracked internally, but all three need to be worked around before this will compile and run cleanly. This includes all of the necessary fixes, including addressing the last issue by moving the Object.hasOwnPropertyName('_classProperties') check to _finalize(). This is necessary to ensure that _classProperties is defined before it's iterated in observedProperties on an element that has no properties. Another option for making _finalized and _classProperties public would be to move to checking this._finalized and this._classProperties and not needing to access them by name - I thought that was out of scope for this but would be happy to change to that style if reviewers prefer.
Fully support Closure compiler including ADVANCED_OPTIMIZATIONS
* uses `JSCompiler_renameProperty` where literal names are needed instead of using `@export` * The private `_classProperties` is no longer created by default. Instead `_ensureClassProperties` was added and is called to make sure that the specific class has its own storage for class properties. * Adds a test that ensure a subclass cannot alter a superclass. The condition tested here would only fail after merging #359. * optimization: ensures `LitElement` is marked as `finalized` so that it does not ever try to finalize.
|
Does this need docs changes? |
|
@katejeffreys No, it's just making the type annotations and constructions compatible with closure; no real user-facing considerations there. |
src/lib/updating-element.ts
Outdated
|
|
||
| /** | ||
| * When using Closure Compiler, JSCompiler_renameProperty(property, object) is | ||
| * replaced by the munged name for object[property] We cannot alias this |
There was a problem hiding this comment.
| * replaced by the munged name for object[property] We cannot alias this | |
| * replaced *at compile time* by the munged name for object[property] We cannot alias this |
src/lib/updating-element.ts
Outdated
| /** | ||
| * Maps attribute names to properties; for example `foobar` attribute | ||
| * to `fooBar` property. | ||
| * @nocollapse |
There was a problem hiding this comment.
Can we make it clear in here somehow why some properties need nocollapse and others don't? I assume properties that we use only via own property lookup, and not static inheritance, don't need nocollapse. That distinction might be subtle for maintainers w/o everyday Closure experience. Maybe comment the exceptions?
There was a problem hiding this comment.
No, not related to hasOwnProperty (that is dealt with by making sure we use JSCompiler_renameProperty anywhere we use a property by name. It's working around ES6 compiler bugs -- and definitely deserve a comment.
I believe this is right:
- any static method needs @nocollapse to not be removed altogether
- static fields need @nocollapse to get their initializers
@rictic Does the above sound correct?
That said, similar to why Steve removed the initializer for _classProperties, I'm not entirely sure why _attributeToPropertyMap and properties need initial values on UpdatingElement either... will take a look.
There was a problem hiding this comment.
I think the simplest guidance is that all static methods and fields should have @nocollapse in order to preserve correct semantics.
jscompiler started the perspective that there is no inheritance of static members, and that static members can be rewritten to global variables. These assumptions don't hold up well with ES6+ code, and work is underway to handle them more correctly, but for the time being @nocollapse is the easiest way to tell it not to apply dangerous optimizations to them.
It's called @nocollapse because we're asking for these members not to be collapsed down into global variables.
There was a problem hiding this comment.
Removed initial values for _attributeToPropertyMap and properties as well.
Added comment block describing when @nocollapse is needed.
| static createProperty(name: PropertyKey, | ||
| options: | ||
| PropertyDeclaration = defaultPropertyDeclaration) { | ||
| /** @nocollapse */ |
There was a problem hiding this comment.
I'm surprised we need nocollapse here. Is it because of this access? If so, seems like an unsafe optimization by Closure.
There was a problem hiding this comment.
See above, all static methods need @nocollapse currently (closure bug workaround).
| * Memoized list of all class properties, including any superclass properties. | ||
| */ | ||
| private static _classProperties: PropertyDeclarationMap = new Map(); | ||
| private static _classProperties?: PropertyDeclarationMap; |
There was a problem hiding this comment.
why remove the initializer?
There was a problem hiding this comment.
UpdatingElement itself will never set anything into its _classProperties (since it never has properties); the set is created lazily during finalization on actual user subclasses
There was a problem hiding this comment.
Can add a comment to that effect.
| * Ensure this class is marked as `finalized` as an optimization ensuring | ||
| * it will not needlessly try to `finalize`. | ||
| */ | ||
| protected static finalized = true; |
There was a problem hiding this comment.
Shouldn't we initialize UpdatingElement.finalized to true too then?
There was a problem hiding this comment.
JSCompiler_renamePropertywhere literal names are needed instead of using@export_classPropertiesis no longer created by default. Instead_ensureClassPropertieswas added and is called to make sure that the specific class has its own storage for class properties.LitElementis marked asfinalizedso that it does not ever try to finalize.