-
Notifications
You must be signed in to change notification settings - Fork 1.7k
even without dart:html anywhere in the code it throws conflict with native dart:html type #46248
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
Comments
This sounds like a more general dart sdk issue? Transfering it to that repro. |
@jodinathan - indeed, we added checks for JS-interop because commonly users find later in time type errors due to conflicts of this form. Today, the checks are done before tree-shaking, so we don't know whether the app is using dart:html or not at that point in time. @srujzs - one possibility if we would like to reduce the noise here would be to try to detect whether there are any @jodinathan - another thought. If you intend to always compile without support for |
@sigmundch I don't think that using --server-mode is an option because I guess we lose other stuff like pub serve (webdev serve) which uses DDC but I am not sure. This topic is very, very important to us because we are pondering in using Dart for a very big project (millions revenue) that will lead to a new cross platform framework. Dart was a strong candidate until this. Do you think this could be fixed? |
One thing I'm worried about with the approximation approach is that while the current file doesn't use With DDC, this is an issue regardless of if the error is disabled, because DDC has native types live always. So, if you were to use This really boils down to
Yes, and we definitely want to. However, suppressing the conflict check isn't a great fix without addressing some of the other issues that come without the check, because that means errors may pop up in different ways. I'd like to know more about your use case though if possible. Is there a reason for not using the |
Two reasons:
We are designing and about to develop a framework pretty much like Svelte and we are checking if it is possible to use Dart instead of TS or Svelte. |
Thanks for sharing more details. Indeed, our long term plan is to revamp Input like this is very helpful, so if you have additional details on ares of dart:html that are specifically too much boilerplate, let us know. I'm sure you are also aware, but just in case, I should highlight that flutter also supports the web, in case your goal is to share more logic between web and mobile long term. Circling back to your original issue: it would be great to figure out a way for you to be unblocked, even if the current solution is not perfect. Some options we have discussed: (b) a flag to disable this error: as @srujzs mentioned, such change will surface inconsistencies and sharp corners hard to navigate. For example, consider this program: @JS('MediaDevices')
class MediaDevices {
external factory MediaDevices();
external String get id;
} If we didn't give you the static error, you may find some surprises like:
(c) update the type system and relax the error: We need to investigate this, but possibly dart2js and DDC type systems could consider native types subtypes of some JSInterop types. It's tricky because we can't make JSinterop types include all native types, since doing so will conflate all JS-interop methods on top of native types and sometimes give conflicting and surprising behavior (e.g. unclear precedence when both native and JSinterop types define the same method). One option we are thinking of is to make this change only for JSInterop types with no instance members. Similar to the match and implement types idea proposed in #39753. |
@sigmundch I have, or I am trying to, roll our own html interop package here https://pub.dev/packages/js_bindings. You mentioned a flag to disable the error. I wonder if we could have a flag to disable the use of dart:html at all? |
Cool and exciting to see your approach!
That is what A middle ground could be to have DDC enforce no-imports, but continue to include dart:html in the output. DDC has fewer risks of invoking the wrong code, so it could work as an approach. Another way to enforce this outside DDC could be to define a custom lint for your projects to ensure no one imports The big caveat with all these approaches is that the don't compose well. If you release packages using js_bindings and other developers want to consume them, they also can't use We want to explore further the idea (c) above. This approach, however, will require changing the structure of To make this concrete, instead of: @JS()
class MediaDevices withEventTarget {
external factory MediaDevices();
external Promise<MediaStream> getUserMedia([MediaStreamConstraints? constraints]);
...
} You'd need to have: @JS()
class MediaDevices {
external factory MediaDevices();
}
extension MediaDevicesAPI on MediaDevices {
Promise<MediaStream> getUserMedia([MediaStreamConstraints? constraints]) => callMethod(this, 'getUserMedia', [constraints]);
// Note: eventually we will support the external syntax within static extension methods:
// external Promise<MediaStream> getUserMedia([MediaStreamConstraints? constraints]);
// but for now we need to use js_util until that gets implemented.
} At the use-site, code using This change would be very aligned with the direction that JS-interop is moving towards. We plan to have better language support and syntax, so it's not quite the same, but the basic idea that JS-interop methods are only available with static types is a center piece of the design. That is the mechanism we will have to allow renames as well as pre processing of arguments and post processing of results (e.g. to convert promises to futures like you did in your Promise extension). |
I really like this approach but I have a question: what about boilerplate? |
If I understand correctly, your concern is about the final output JavaScript code size, correct? We have active work to make this be pretty lean and efficient:
Putting these together, we expect that something like |
that is exactly my concern.
so if I change my extension MediaDevicesAPI on MediaDevices {
Promise<MediaStream> getUserMedia([MediaStreamConstraints? constraints]) => callMethod(this, 'getUserMedia', [constraints]); thanks for the info, very interesting! |
/cc @rileyporter Precisely! Just to be clear, that would be mostly in dart2js, in DDC you'll see additional calls/indirection at first. After some extension MediaDevicesAPI on MediaDevices {
external Promise<MediaStream> getUserMedia([MediaStreamConstraints? constraints]); Because these are statically dispatched and understood by the compilers, we finally have a way to extension MediaDevicesAPI on MediaDevices {
// directly convert promise to futures:
Future<MediaStream> getUserMedia([MediaStreamConstraints? constraints]) =>
js_utils.promiseToFuture(js_utils.callMethod(this, 'getUserMedia', [constraints])); |
awesome! I will change js_bindings to use extensions and will get back here once it is done |
Hey @sigmundch will these optimizations also apply to Similarly to @jodinathan I was experimenting with a As an example here's the import 'dart:js';
extension ElementJsObject on JsObject {
bool get isInstanceOf => instanceof(context['Element'] as JsFunction);
Object get attributes => this['attributes'] as Object;
String get tagName => this['tagName'] as String;
String get className => this['className'] as String;
set className(String value) {
this['className'] = value;
}
String get id => this['id'] as String;
set id(String value) {
this['id'] = value;
}
String get slot => this['slot'] as String;
set slot(String value) {
this['slot'] = value;
}
Object? get shadowRoot => this['shadowRoot'];
Object attachShadow(JsObject init) =>
callMethod('attachShadow', <Object?>[init]) as Object;
} Then there's a Dart implementation of /// Browser implementation of [Element].
class ElementImpl extends NodeImpl
with
ParentNodeImpl,
ChildNodeImpl,
NonDocumentTypeChildNodeImpl,
SlotableImpl
implements Element {
/// Creates an instance of [ElementImpl] from the [jsObject].
ElementImpl.fromJsObject(JsObject jsObject) : super.fromJsObject(jsObject);
@override
String get tagName => jsObject.tagName;
@override
String get id => jsObject.id;
@override
set id(String value) {
jsObject.id = value;
}
@override
String get className => jsObject.className;
@override
set className(String value) {
jsObject.className = value;
}
@override
late Map<String, String> attributes = NamedNodeMap.fromJsObject(
JsObject.fromBrowserObject(jsObject.attributes));
@override
String get slot => jsObject.slot;
@override
set slot(String value) {
jsObject.slot = value;
}
@override
ShadowRoot? get shadowRoot =>
safeShadowRootFromObjectNullable(jsObject.shadowRoot);
@override
ShadowRoot attachShadow() =>
ShadowRootImpl.fromJsObject(JsObject.fromBrowserObject(
jsObject.attachShadow(
ShadowRootInitOptionsJsObject.construct(
mode: 'open',
delegatesFocus: false,
),
),
));
} And if you look over in https://github.com/rampage-dart/rampage/tree/main/html/web there's a Custom Element that does a click counter which runs when compiled with DDC or dart2js. Happy to see I came to a similar conclusion with using |
Hi @donny-dont
Only
Cool! Indeed once we have the new feature, I expect the code may look very similar to what you have in Until we have the new feature, though, our intermediate step is to use @JS()
@anonymous
class ElementJsObject {}
extension ElementJsObjectExtension on ElementJsObject {
... /// everything you have in your ElementJsObject today, but replace all uses of `context` to use `js_util` instead?
} I haven't put much thought into this, but I also wonder whether we could eliminate the second wrapper with |
Hey @sigmundch I hand rolled some JS interop using https://github.com/donny-dont/dart-custom-element-demo/tree/js-util It has the click counter working. It has Mutation Observers working. It has Events working. Please take a look at it at your earliest convenience to see if I got things going how you all envision JS interop working in the future. I have my WebIDL parser going and am using Thanks in advance! |
Thanks @donny-dont - that's very cool. I took a brief look and precisely, the pattern of JS-interop using an empty class with extensions matches what we are going after. While browsing around, I noticed a couple things I want to point out:
Cheers! |
Thanks for taking a look @sigmundch ! So for the extension ElementImpl on Element {
bool get isInstanceOf => instanceof(this, context['Element']);
} With regards to the wrapper-less version that's something I could experiment with but it seemed like a non-starter with how the JS Interop is currently. Jenny mentioned "Dart classes should be able to extend a JS interop class (e.g. for custom elements), and vice versa if possible (probably opt-in from the Dart class, so it explicitly exports itself for JS subclassing)" in her list for JS Interop improvements. Also I'm not sure how one could do wrapper-less code with quality of life improvements like making Happy to work with you all to dog food more of this if you have any specific ideas to try out. Also the code is there if anyone wants to monkey with it directly. |
Right, with the caveat that
I don't believe this will be feasible with the extension types model (and not possible with
Yeah, I think supporting List-like capabilities will be difficult without wrappers and while extension types allow composability, they don't have the guarantees inheritance affords. I believe that for the most part, extensions types seems still compatible with your library (and migrating off of |
@srujzs I've fixed js_bindings and the example is now working. Tried using constructor tear-off but didn't work: import 'package:js_bindings/js_bindings.dart';
class Foo implements HTMLElement {
Foo() {
// final pElem = document.createElement('p');
// pElem.textContent = getAttribute('text');
//
// final shadowRoot = attachShadow(ShadowRootInit(mode: 'open'));
// shadowRoot.appendChild(pElem);
}
}
void main() {
window.customElements.define('foo-cmp', allowInterop(Foo.new));
} Error: Compiling Could I somehow force |
I thought we were pretty much blocked on making a wrapper-less object without dart-lang/language#1474 |
Custom Elements/Web Components is indeed a pain point, and that won't work as expected. There's a bit of back and forth between Siggi and Don above about possibly implementing code to handle custom elements. They, iirc, also had some current mechanisms to do this (although I can only assume it's painful today unfortunately). I haven't taken a look at the work there in a while, how would adding views/ |
@donny-dont it would be feasible if the compiled JS was more strict regarding the function it creates from classes: #48269 @srujzs is there any |
yeah, it's just too complicated to use dart2js and webcomponents. I was able to make it work by doing some magical JS, even without wrappers, however, hacking like this is just not right. Maybe I could make a builder... Even so, just too much work for something relatively simple. Well, I am closing this issue because |
Dart SDK version: 2.13.1 (stable) (Fri May 21 12:45:36 2021 +0200) on "macos_x64"
build_web_compilers 3.0.0
I am not using dart:html and it still complains when I try to make a JS wrapper around MediaStream: Error: JS interop class 'MediaStream' conflicts with natively supported class 'MediaStream' in 'dart:html'.
The text was updated successfully, but these errors were encountered: