nex3 / dep-import-when

Yet another cross-platform import DEP
1 stars 2 forks source link

html use case #8

Open jmesserly opened 9 years ago

jmesserly commented 9 years ago

How can we use this to implement package:html?

package:html/html_standlone.dart:

import "dart:html" as html when dart.html;

abstract class Element implements html.Element {
  // ...
}

package:html/html_browser.dart:

export "dart:html" when dart.html;

user code:

// What do we write here to make this work server or browser?
import 'package:html/html.dart' show Element;

// subclassing
class MyElement extends Element {
}

foo() {
  // Or just plain old construction:
  var e = new Element.html('<div>works server and client, cool!</div>');
}

If I could configure that import line to be packages/html/html_standalone.dart vs packages/html/html_browser.dart, I think it would work. Is there another proposal or an extension to this one, or some other way to finesse it?

nex3 commented 9 years ago

Maybe this isn't relevant, but I was under the impression that the plan for package:html in particular was to implement it in terms of JS interop, not in terms of dart:html. Since the exact shape of JS interop isn't known yet, I don't think I can provide a good picture of what an html package would actually look like with this proposal.

In general, though, a platform-specific class's implementation is platform-specific; if you extend Element, you'll get an UnsupportedException on the server when you try to call the superclass constructor, because there's no code there. However, this doesn't mean that it's not useful at all. You could do something like this:

// element.dart in package:html
import 'browser_element.dart' when dart.html;
import 'server_element.dart' when dart.io;

abstract class Element implements html.Element {
  factory Element.html(String html) {
    if (const bool.fromEnvironment('dart.html')) {
      return new BrowserElement.html(html);
    } else {
      return new ServerElement.html(html);
    }
}
// browser_element.dart
import 'dart:html' as html;
import 'element.dart';

class BrowserElement extends html.Element implements Element {
  BrowserElement.html(String html) : super(html);
}
// server_element.dart
import 'element.dart';

class ServerElement implements Element {
  ServerElement.html(String html) {
    // Parse the html by hand, etc etc
  }
}
jmesserly commented 9 years ago

Maybe this isn't relevant, but I was under the impression that the plan for package:html in particular was to implement it in terms of JS interop, not in terms of dart:html. Since the exact shape of JS interop isn't known yet, I don't think I can provide a good picture of what an html package would actually look like with this proposal.

For now, it's fair to image something like this: https://raw.githubusercontent.com/dart-lang/bleeding_edge/master/dart/sdk/lib/html/dart2js/html_dart2js.dart That's dart2js DOM, which is essentially implemented on top of interop. (many details could change, but the core ideas of exporting JS types to Dart names like "Element" would be the same).

the issue really is what does import "package:html/html.dart" mean? How to get it to load one implementation on the server, and another one on client?

In general, though, a platform-specific class's implementation is platform-specific; if you extend Element, you'll get an UnsupportedException on the server when you try to call the superclass constructor, because there's no code there.

Can you explain? There actually is code there on server: https://github.com/dart-lang/html/blob/master/lib/dom.dart#L447

jmesserly commented 9 years ago

Oh, I think I see what you're saying. I think Element here:

// element.dart in package:html
import 'browser_element.dart' when dart.html;
import 'server_element.dart' when dart.io;

abstract class Element implements html.Element {
  factory Element.html(String html) {
    if (const bool.fromEnvironment('dart.html')) {
      return new BrowserElement.html(html);
    } else {
      return new ServerElement.html(html);
    }
}

... can't be subclassed. Is there a way we can fix that? If we could, then we'd gain the use cases of server-side HTML and testing. Which would be awesome!

nex3 commented 9 years ago

As it happens, dart:html's Element is also not subclassable, but the same principle could be applied to (say) DivElement. The problem here is that the use of factory constructors to choose implementations fundamentally breaks subclassing. This isn't just an issue with this proposal, we see it all throughout the language; this is why most of the core library types aren't subclassable, for example. I'm tempted to say that we should find a more general way of making factory constructors work well with subclassing, rather than trying to solve it in this proposal.

It's not a perfect solution, but this is this is the workaround that most of the core libraries use:

import 'element.dart';

class ElementView implements Element {
  final Element _inner;

  ElementView(this._inner);

  Node append(Node child) => _inner.append(child);

  // ...
}
// User code
import 'package:html/html.dart';

class MyCustomElement extends ElementView {
  MyCustomElement() : super(new DivElement());
}
jmesserly commented 9 years ago

As it happens, dart:html's Element is also not subclassable, but the same principle could be applied to (say) DivElement.

Good catch. I meant HtmlElement, which can be subclassed: https://github.com/dart-lang/polymer-dart/blob/master/lib/src/instance.dart#L134

class MyElement extends HtmlElement {
  MyElement.created() : super.created();
}

The problem here is that the use of factory constructors to choose implementations fundamentally breaks subclassing.

Right.

It's not a perfect solution, but this is this is the workaround that most of the core libraries use: [...]

Unfortunately, we know from a lot of experience that having wrappers for all DOM nodes doesn't work for perf reasons.

nex3 commented 9 years ago

Unfortunately, we know from a lot of experience that having wrappers for all DOM nodes doesn't work for perf reasons.

Luckily, you don't need to wrap all DOM nodes. On the browser, anything you construct from package:html will extend the native dart:html class; it's just subclasses that have to use wrappers.

Anyway, hopefully once we have package:html we can find a better overall solution for HTML elements in particular. In general I think most use-cases won't have as much trouble with wrapping as the DOM.

jmesserly commented 9 years ago

Luckily, you don't need to wrap all DOM nodes. On the browser, anything you construct from package:html will extend the native dart:html class; it's just subclasses that have to use wrappers.

I don't think that's true. HTML nodes are created by the parser as well (e.g. document.registerElement). There is also interoperability from JavaScript to consider. Happy to be proven wrong, but I don't see how a wrapping scheme can possibly work here.

Also, the overhead is like, totally avoidable. All we need is one level of indirection, so import "package:html/html.dart" can import a different implementation file in the browser.

Anyway, hopefully once we have package:html we can find a better overall solution for HTML elements in particular.

So, we do have package:html now: https://pub.dartlang.org/packages/html. What we don't have is a way of making it work in the browser. That is why I'm hopeful configuration specific libraries can fix this problem. All we need is a bit of flexibility in how the import "package:html/html.dart" is interpreted.