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

Upgrade SimpleDOM and use DOM types more consistently #866

Merged
merged 3 commits into from
Nov 29, 2018

Conversation

tomdale
Copy link
Contributor

@tomdale tomdale commented Nov 28, 2018

This PR makes several changes related to how we handle types for DOM objects in Glimmer VM. Specifically, this PR:

  1. Upgrades the SimpleDOM library to the newest version, and uses the latest, non-deprecated APIs.
  2. Changes runtime and test code to always use SimpleDOM types, casting to native browser DOM types in cases where non-simple DOM API is required.
  3. Implements abstractions in the test suite for accessing native DOM elements, and asserting in cases where these are accessed in a non-browser environment. This should help us better understand which parts of our test suite are "safe for SSR" and which require a full DOM implementation to run.

Perhaps the most important impact of this change is that downstream consumers, such as Glimmer.js and Ember.js, will be able remove lots of the nasty type coercion (e.g. document.body as any as Element) that is required today when you pass real DOM elements into Glimmer VM APIs.

SimpleDOM Upgrade

This PR replaces an older version of simple-dom with the newer packages @simple-dom/document, @simple-dom/serializer, @simple-dom/void-map, and @simple-dom/interface, where appropriate. It also updates the build script to use the prebuilt AMD versions of these packages, rather than compiling SimpleDOM from scratch.

Within the VM, DOM types are imported as the Simple namespace from @glimmer/interfaces, which itself simply re-exports the types defined in SimpleDOM. I took this approach, rather than importing types from @simple-dom/interface directly in runtime code, to make it easier to decouple our types from SimpleDOM in the future if need be.

The one exception to this rule is cases where runtime code uses const enum types, like Namespace and InsertPosition. This is due to an apparent type erasure bug in the TypeScript compiler, where imported const enums that go through a re-export are not properly removed in emitted JavaScript, causing the Rollup build to fail (since it tries to import type definitions that no longer exist). This may be fixed in newer versions of TypeScript and we can ideally change this once the bug is fixed.

Ubiquitous SimpleDOM Types

Previously, types for DOM objects were used inconsistently throughout the codebase. Often Simple.*, simple-dom or native DOM types were used interchangeably. Unifying the Simple.* and simple-dom types helps, but leaves the inconsistency with the native DOM types.

In this PR, I've adopted the rule that all types should be Simple.* types unless the function in question utilizes APIs not provided by SimpleDOM. In those cases, the function is responsible for performing any necessary type coercion. This helps make code that is not SSR safe more explicit.

In the case of tests, which tend to rely on non-simple DOM APIs much more (e.g. setting innerHTML), I've added new APIs to the test infrastructure that provide natively-typed versions of DOM objects along with assertions that verify they are running in a real browser environment. My hope is that this makes tracking down browser-dependent tests easier, and in the future we can ensure that tests aren't inadvertently coupled to the browser if they would be helpful to run in SSR mode as well.

export {
ElementNamespace,
AttrNamespace,
SimpleNode as Node,
Copy link
Contributor

Choose a reason for hiding this comment

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

shadowing ambient interfaces can be a bit trollish, but I'm ok you are just trying to minimize the scope of the change here

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I agree that shadowing is trollish. We avoid maximum troll by not making it easy to import these types directly, and re-export the entire module under the Simple namespace from the package root. In practice, I don't think we ever refer to bare Node, etc. in the codebase, and always as Simple.Node.

host.appendChild(this.element);
host.appendChild(clientRemote);
clientRemote.innerHTML = serializedRemote;
this.element = host.firstChild as HTMLElement;
(clientRemote as Element).innerHTML = serializedRemote;
Copy link
Contributor

Choose a reason for hiding this comment

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

can we hide this in a TestHost interface? that provides this op and whether it is supported by the underlying DOM? it would be nice to get to the point we can run the whole suite in SSR vs not for most tests.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I tried untangling this one but it was quite hairy and I was afraid of making substantive changes in a PR this big. Will follow up with you offline because I agree we should clean this up.

@@ -30,7 +30,7 @@ function commonSetup() {
function render(template: Template, context = {}) {
self = new UpdatableReference(context);
env.begin();
let cursor = { element: root, nextSibling: null };
let cursor = { element: root, nextSibling: null } as Cursor;
Copy link
Contributor

Choose a reason for hiding this comment

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

is Cursor an interface or is it a class? let cursor: Cursor = { element: root, nextSibling: null }

Copy link
Contributor Author

Choose a reason for hiding this comment

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

It's actually a class for non-obvious reasons, but used like an interface almost everywhere. The cast is necessary because root is an HTMLElement and needs to be explicitly cast to the expected Simple.Element type.

Copy link
Contributor

@krisselden krisselden left a comment

Choose a reason for hiding this comment

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

I left a few minor comments but not blockers

@tomdale tomdale force-pushed the tomdale/upgrade-simple-dom-1-4 branch from 0c66e04 to 9411464 Compare November 29, 2018 00:06
@tomdale tomdale merged commit e838d9d into master Nov 29, 2018
@tomdale tomdale deleted the tomdale/upgrade-simple-dom-1-4 branch November 29, 2018 00:17
@tomdale tomdale added the polish label Nov 29, 2018
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants