Skip to content

Conversation

@nex3
Copy link
Member

@nex3 nex3 commented Nov 6, 2025

  • Thanks for your contribution! Please replace this text with a description of what this PR is changing or adding and why, list any relevant issues, and review the contribution guidelines below.

  • I’ve reviewed the contributor guide and applied the relevant portions to this PR.
Contribution guidelines:

Many Dart repos have a weekly cadence for reviewing PRs - please allow for some latency before initial review feedback.

Note: The Dart team is trialing Gemini Code Assist. Don't take its comments as final Dart team feedback. Use the suggestions if they're helpful; otherwise, wait for a human reviewer.

@kevmoo
Copy link
Member

kevmoo commented Nov 6, 2025

No tests? 😁

Copy link
Contributor

@srujzs srujzs left a comment

Choose a reason for hiding this comment

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

Thanks so much for adding this Natalie!

+1 to a quick test just to make sure we have the right bindings.

/// The [Date constructor] that returns the current date and time.
///
/// [Date constructor]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Date/Date
external JSDate.now();
Copy link
Contributor

Choose a reason for hiding this comment

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

Hmm, this might be a little confusing as intuitively, one would expect the static method Date.now to be called and not a constructor. What about JSDate.nowAsDate so that we don't need to rename now below?

external JSDate(int year, int month,
[int? day, int? hours, int? minutes, int? seconds, int? milliseconds]);

/// Dee [`Date.now()`].
Copy link
Contributor

Choose a reason for hiding this comment

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

nit: See

///
/// [Date constructor]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Date/Date
/// [from a string]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Date/Date#date_string
external JSDate.parse(String dateString);
Copy link
Contributor

Choose a reason for hiding this comment

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

Similar to now, what about parseAsDate instead so we can keep parse below?

///
/// [`Date.utc()`]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Date/UTC
@JS('UTC')
external static int utc(int year,
Copy link
Contributor

Choose a reason for hiding this comment

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

We avoid renaming to lowercase with package:web e.g. URL, maybe worth doing the same?

Same comment on toIsoString, toUtcString, and toDartUtc.

/// [`Date.getDate()`]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Date/getDate
/// [`Date.setDate()`]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Date/setDate
int get date => _getDate();
set date(int value) => _setDate(value);
Copy link
Contributor

Choose a reason for hiding this comment

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

Maybe we should keep the syntax the same as JS here by just exposing the methods instead of trying to be more Dart-y like we do in package:web.

Same comment everywhere else.

@nex3
Copy link
Member Author

nex3 commented Nov 7, 2025

Do you have an example of tests for thin wrapper declarations like this?


Since we're still defining the exact shape and conventions of this package, I'd like to make a bit of an impassioned plea that this not match web in hewing closely to the JS names and API shapes. For web itself, that design philosophy makes a lot of sense: the APIs are almost entirely autogenerated from interface definitions, so it would be onerous to manually make them match Dart conventions and have to keep those changes up to date. In addition, WebIDL already has a strong precedent of being implemented with few variations across multiple languages.

But js_interop has a different context which I think motivates a different design. Everything we expose here will be human-authored anyway, so the cost of a human hand making the design feel Dart-native is lower (and in fact I've already done most of this design work in my js_core repo). In fact, some of the APIs we intend to expose here (like JSRrecord) will necessarily be Dart-style, so there's a consistency case to be made for matching that across the package.

More importantly, though, these APIs are going to be foundational to the programming environment and commonly-used by authors doing any more than cursory Dart/JS interaction. Making them ergonomic to Dart authors and Dart conventions has an outsized benefit relative to the web APIs l, most of which will each individually be used less. That's particularly true for the APIs I'm planning to add to dart:js_interop, which this package should certainly match in style.

Along that line, I'd also make the case that dart:js_interop already leans towards using Dart conventions rather than JS. JSFunction has methods named callAsMethod and callAsConstructor when the JS equivalents are named apply() and construct(). While JSAny? does have an all-lowercase instanceof method, that naming is already inconsistent: it also defines instanceOfEquals.

The final case I'll make for using a more Dart-native style is for the ecosystem as a whole. Since their inception with collection, core-extension packages in the Dart ecosystem have served as the blueprints for what well-designed, idiomatic Dart APIs look like. We're in the very early days of the new (and likely final) shape of JS interop in Dart, and the APIs we release here and in my upcoming additions to the SDK will go a long way towards defining what a good JS API wrapper should look like everywhere. One of the things that makes the new JS interop system so exciting is that it makes it possible to use JS in a Dart-y way with near zero overhead. For a simple data type like Date this just looks like a few functions with different capitalization, but once we get into more complex and interesting APIs the usability difference will become profound. Let's not let the potential of this new system go to waste.

@srujzs
Copy link
Contributor

srujzs commented Nov 8, 2025

re: tests, we do this for some of the dart:js_interop APIs by just testing that a method gives the expected value with the expected type e.g. https://github.com/dart-lang/sdk/blob/59905c43f1a0394394ad5545ee439bcba63dea55/tests/lib/js/static_interop_test/js_types_test.dart#L237. Nothing fancy, just making sure there are no spelling or type issues. Ideally, we'd have these for all of the members in package:web, but that's impractical and because it's generated, it's less of an issue.


I think there's a valid argument to say this package is a better candidate to be Dart-y than package:web. The latter is generated and making that Dart-y would revive what made dart:html hard to maintain. Unlike package:web too, I expect the spec for the types in this package to be much more stable.

I'd still argue there are some benefits to having a model that translates directly to JS. I want and expect users to look for JS documentation when dealing with interop types, and I think it's easier for users to directly write those method/member names and have them exist/autocomplete properly. It's a (small) tax to have to look at the API and figure out how to make the equivalent JS call. I bet this tax goes up as the APIs get more complex and we get more opinionated.

Along that line, I'd also make the case that dart:js_interop already leans towards using Dart conventions rather than JS. JSFunction has methods named callAsMethod and callAsConstructor when the JS equivalents are named apply() and construct(). While JSAny? does have an all-lowercase instanceof method, that naming is already inconsistent: it also defines instanceOfEquals.

The instances where we do this are usually out of necessity. callAsFunction is actually call but we didn't want to expose that as call because then you can call it with () syntax, which sounds great, until you realize call takes in a this parameter as the first parameter. I suppose we could have a patched invoke method that actually invokes the function instead of .call, and I believe there's a request somewhere for that. callAsConstructor also doesn't use Reflect - it's a patched member that invokes the function with new. instanceOfString is more of a helper and we provide instanceof still (typeofEquals should really have an equivalent typeof member users can call). There are a few other odd cases in dart:js_interop like JSArray.add (instead of push) that have some specific context behind them.

All of this being said, helpers are great. That's partially why we have some of the "helpers" in package:web as well as conversion methods. But if we're only going to provide the "helper" version, it does mean we're going to need to be opinionated on how to expose JS interop and stay consistent with that opinion. More specifically to this PR, a third option is providing both e.g. the interop method that the more Dart-y APIs call and the Dart-y APIs themselves. That may be overkill though.

The final case I'll make for using a more Dart-native style is for the ecosystem as a whole. Since their inception with collection, core-extension packages in the Dart ecosystem have served as the blueprints for what well-designed, idiomatic Dart APIs look like. We're in the very early days of the new (and likely final) shape of JS interop in Dart, and the APIs we release here and in my upcoming additions to the SDK will go a long way towards defining what a good JS API wrapper should look like everywhere.

I think I feel slightly different about interop-related packages because they're inherently dealing with another language. This argument makes sense for any helpers we add, but they shouldn't be complete replacements.

For a simple data type like Date this just looks like a few functions with different capitalization, but once we get into more complex and interesting APIs the usability difference will become profound.

My above comment is relevant here: I expect the tax of trying to match the JS call to the Dart API we expose will go up as we look at the more complex cases but we can talk about those when we get there.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants