munificent / dep-external-libraries Goto Github PK
View Code? Open in Web Editor NEWProposal for handling platform-specific code in Dart
Proposal for handling platform-specific code in Dart
In Linking to external libraries:
A canonical library can wire itself up with zero or more external libraries using an external library directive
I understand more as >=1
but in the definition of libraryDefinition
I see:
libraryDefinition:
scriptTag? libraryName? (externalLibrary|externalForLibrary)? importOrExport* partDirective* topLevelDefinition*
;
If I understand correctly that means 0 or 1 externalLibrary
.
So is it allowed to have several external libraries ?
In the context of source gen it's likely that several external lib could be used (Eg. one for JSON, one for identity(hashcode
/ .==
), one for toString()
) Moreover those external libraries would define only some of all the external members of a class from the canonical lib.
Here is an example:
external library 'person-json.g.dart';
external library 'person-identity.g.dart';
external library 'person-tostring.g.dart';
class Person {
final String name;
Person(this.name);
external Person.fromJson(Map json);
external Map toJson();
external int get hashCode;
external bool operator ==(o);
external String toString();
}
external library for 'person.dart';
import 'dart:convert';
class Person {
factory Person.fromJson(Map json) => new Person(json["name"]);
Map toJson() => {"name": name};
}
external library for 'person.dart';
import 'dart:convert';
class Person {
int get hashCode => hashObjects([name]);
bool operator ==(o) => identical(this, o) || o.runtimeType == runtimeType && o.name == name;
}
external library for 'person.dart';
import 'dart:convert';
class Person {
String toString() => "Person(name=$name)";
}
Is it a valid usage of external libraries (particularly partial patches) ?
// oauth.dart
import 'package:http/http.dart'
Future authenticate(Client cleant) { ... }
We could imagine that io_client.dart
, besides providing an implementation of Client
also provides IoClient extends Client
with io-specific features.
// my vm code
import 'package:http/io_client.dart';
import 'oauth.dart';
void main() {
var client = new IoClient(cookieStore: 'cookies.cks');
authenticate(client); // is this a type warning?
}
The Client
in io_client.dart
has no relationship to Client
in http.dart
– at least not in the URI rules we think of in Dart today.
If a class is abstract
, then it's instance members can simply omit a function body, and will be implicitly abstract
. The same could work for external
:
external class Foo {
Foo();
static bar();
foo();
}
It uses @patch
to validate that the member matches up with an external one, because that's the convention in core libraries. We use this in DDC for transforming @patch
/external into something that's valid Dart code. Linking here, in case it helps you with the transformer version.
This produces worse UI for some existing packages' use cases than either Søren's original proposal or Lasse's proposal. There are places where we actively want to have a few platform-specific APIs in an otherwise platform-independent package, and we want to make these APIs visible to the analyzer and easy to use—I don't want http
to be less usable for server-only developers because it also happens to support the browser. If it's less usable, that incentivizes the creation and use of less portable but more usable packages, which fragments the ecosystem.
In http
, we want to support a cross-platform Client
class like the one in your example, for which this works well. However, we also want to have user-visible BrowserClient
and IoClient
classes that support more powerful platform-specific APIs, such as configuring behavior for bad SSL certificates on the server or auto-parsing the response on the browser. Under this proposal, the only options for this are to either leave the APIs untyped or move any APIs with platform-specific annotations into a separate library requiring a separate import, which isn't much better than the status quo. In fact, it's worse: currently if you import package:http/http.dart
on the server, you do get access to IoClient
, thanks to a small use of dart:mirrors
.
The glob
package is another good example. It's API is based on the Glob
class; the usual pattern of use is to call new Glob.parse
once for a given pattern and then re-use that pattern to save parse and compile time, much like the pattern for RegExp
. A Glob
can be used for two things: matching paths and listing filesystem entities whose paths match the pattern. The former is entirely platform-independent, while the latter is clearly limited to the server. But Glob.list
returns a Stream<FileSystemEntity>
, which can't be represented under this proposal.
This is an even worse case than http
, since it's important that Glob.list
be an instance method of Glob
both for usability and for performance, so it can re-use the compiled representation. That rules out putting it in a separate library, so the only option is to remove the type platform-specific type annotations, producing a degraded user experience, especially for IDE users.
// warn.dart
external library 'warn_browser.dart' for dart.html;
external library 'warn_console.dart' for dart.io;
/// Warns the user about [message].
external void warn(String message);
We'll call this the canonical library. It in turn refers to two separate
external libraries:
// warn_browser.dart
external export 'warn.dart'; // reusing export is intentional
import 'dart:html';
void warn(String message) {
html.document.body.appendHtml('<div class="warn">$message</div>');
}
This allows the analyzer to help with missing/incorrect implementations of the canonical library.
It's analogous to part
and part of
.
The use of export
is intentional. If the canonical library defines types, they should be 100% compatible with the types defined by the external library.
It could also help facilitate runtime replacing libraries for testing – where the canonical library doesn't "know" it will be replaced, but you want tooling for a mock implementation.
How about
external part 'warn_browser.dart' for dart.html;
external part 'warn_console.dart' for dart.io;
instead of
external library 'warn_browser.dart' for dart.html;
external library 'warn_console.dart' for dart.io;
I was playing around with this, but slicing it a little differently:
Here's a sketch of how it would look for package:html:
Key concepts:
external
is like abstract
, but for libraries@patch
is an annotation like @override
. It is optional but provides documentation/checking.super
I think the current proposal "merges" the libraries and types at runtime. That's probably a simpler/better approach, this is just illustrating an alternative.
package:html/html.dart:
// We'd like to provide different implementations for a library
// (e.g. server ane browser), while preserving the same API structure.
// The proposal is to handle this similar to "patch" files already used for
// this in the core libraries. It accomplishes this by using "library extensions".
// external is like "abstract", but for the whole library
external library dart.html; // html.dart
external abstract class Node {
external Node parentNode, firstChild, lastChild, nextNode, previousNode;
Element get parent => parentNode is Element ? parentNode : null;
List<Node> childNodes => new _ChildNodeList(this);
}
external abstract class Element extends Node {
external factory Element.html(String html);
external factory Element.tag(String tag);
external String get localName;
}
package:html/src/standalone_html.dart:
// declares a library extension, and gives a name to the base library.
library dart.html_standalone extends 'package:html/html.dart' as base;
// @patch is like @override but for the library. Optional. Runtime will ignore.
@patch
class Node extends base.Node {
Node();
}
// Extend base.Element, which then extends our overridden "Node".
// Alternatively `extends Node`, and have the relationship to `base.Element` be implied,
// If we "merge" libraries, this becomes a non-issue: `extends Node` wouldn't even need to
// be repeated.
@patch
class Element extends base.Element {
@patch
factory Element.html(String html) {
// ... parse HTML stand such ...
}
Element(this.localName);
final String localName;
//...
}
package:html/src/browser_html.dart
library dart.html_browser extends 'package:html/html.dart' as base;
// The browser's view of HTML libs...
user code:
// Now, we just need a way to indicate to the runtime which extension to use, so
// user can write:
import 'package:html/html.dart'; // loads the appropriate extension.
// This can be accomplished through the package spec:
// dart --package-spec
Anyway, not sure we should slice it this way, but maybe it's another perspective for discussion.
Regardless, would it be worth a pull request for the HTML example? (but modified to match the existing proposal's syntax/semantics)
A declarative, efficient, and flexible JavaScript library for building user interfaces.
🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. 📊📈🎉
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google ❤️ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.