Open leafpetersen opened 2 years ago
it makes it easy to migrate: just shave off the package and .dart and you're done.
Yeah I was wrong on that, not sure why I thought so. It does make the migration slightly more involved.
It's closest to the current syntax.
Packages are already denoted by a :
. True it's not currently between the package name and the path, but the token itself is associated with having a package followed by a path. In other words, the following two transformations both have the same elements, but the :
and /
are recognizable/distinct to different people.
import "package:flutter/material.dart";
import flutter:material; // IMO, represents a library in a package
import flutter/material; // IMO, represents a path that happens to be a package
Does the distinction have to be made between a library and a file ? Aren't they all in essence the same ?
What is the difference between a file with library at the top that exports other files, and the same file without library at the top ? What is the difference with that same file, which instead of exporting other files had all the files content copy pasted there ?
First of all, the presence of :
signifies that the import is coming from a package, as it currently does. /
doesn't carry the same connotation because it's used in every path.
What is the difference between a file with library at the top that exports other files, and the same file without library at the top?
When I say library, I'm not referring to the library foo;
statement on the top of the file, nor is there any technical difference in a file that has one and one that doesn't. It's about how the importer of the package thinks about what to import. This has already been brought up, so I'll just briefly describe what I mean using an example from Flutter.
When you import your own files in your own package, you do so using a relative import, like import "auth.dart";
. The intention is to import a file in the same folder that has functions/classes to help with authentication.
But when writing the UI, you want to import Flutter, specifically the Material components. In that case, you don't want to focus on a specific file from Flutter, because you don't care how it's organized. You just want to say "give me all the Material components from Flutter".
The current way of importing feels like you need to know a bit of the internals of Flutter, because you're spelling out a path: import "package:flutter/material.dart";
. This literally means "go to the flutter package folder, and import the file named material.dart
". Instead, it would be nice if we could make it import flutter:material;
, which would more accurately represent the idea of grabbing all Material-related libraries/classes/functions from Flutter. This is closely related to dart run package:executable
, which doesn't concern itself with lib
vs bin
, and instead says "get me the executable
from package
".
Of course, this is all possible because Flutter has a file called material.dart
in the flutter
folder that exports all the related Material material. But the user shouldn't have to know or understand that to use it.
So it's to communicate the intent. That begs the question why does this have to be reserver for the root of a package then ? If I have a nested models/models.dart
file that exports all my models it's essentially a library so shouldn't there be a :
somewhere ?
Given that you wrote this:
import flutter:material; // IMO, represents a library in a package
import flutter/material; // IMO, represents a path that happens to be a package
The following does not make much sens to me:
import `../:models`;
but I don't see why it would not be valid given your description
If I have a nested
models/models.dart
file that exports all my models it's essentially a library so shouldn't there be a:
somewhere?
Correct. Let's make this concrete; I structure my projects like this:
my-project/
- lib/
- main.dart
- models.dart
- data.dart
- src/
- data/
- dataclass1.dart
- dataclass2.dart
- models/
- model1.dart
- model2.dart
- pubspec.yaml
Here's what my lib/models.dart
file looks like:
// models.dart
export "src/models/model1.dart";
export "src/models/model2.dart";
So, if you wanted to import my-project/lib/src/models/model1.dart
, as in your comment, you would simply do:
// main.dart
import "package:my-project/models.dart";
// or
import :models;
The following does not make much sense to me:
That's because you're mixing up the ideas of relative imports and package imports. My comment is only about importing from other packages, since you don't know how those are laid out and using the top-level (non-src
) files are kind of like using an API. When it comes to importing from your own package, you know the entire layout of the files. I would use a package import like I described above when possible, but plenty of times you just need to import a single file, and that's where you would use a relative import.
I fully believe that the precise syntax is not very important. That's probably why the discussion is so vicious :grin:
I believe that Dart authors will quickly get used to any of:
import packagename/path/library;
import packagename:path/library;
import packagename.path.library;
The driving force behind the discussion about these is not usability, it's aesthetics. That's incredibly subjective.
The .
version has practical problems (there are package names with .
s in them already). Most people don't see that problem, but it's there.
For the other two, the preference seems to be driven by whether one sees packagename/path/library
as a single opaque designator (which the .
version definitely does), or as a path. If it's a path, then the first /
is not like the others, and it's easier to argue for :
instead. If it's one opaque library designation, then there is no need to have separate separators.
Perception and aesthetics based on that perception.
If we introduce a shorthand, we'll have to pick one approach, one perception, and make that the official one. And again, I fully believe that whichever we choose, people will use it and like it (and some will complain that it doesn't match their expectations, no matter which one we choose).
import :models;
What if the models folder was nested in a core library / folder ? so src/core/models/models.dart
where there exist a src/core/core.dart
file that exports :models
Would it be
import :core:models
// or
import :models
// or both are valid ?
if import :models
is ever valid it solves the alias issue.
Which has usability implications and that is not entirely subjective
@cedvdb I think you're extrapolating way too much from my comment. Pub conventions are that importable code should be directly in your lib
folder and implementation files go in lib/src
-- I'm not trying to change that. So regarding your example, you shouldn't be exporting publicly importable files in src
, because people shouldn't be importing from src
(and if you tried to, you'd get a lint telling you not to).
My proposal to use :
is based on the current convention: if you have a package named myPackage
with an importable file (in lib
) named library
, you would import it using myPackage:library
. If for some reason you need to import beyond lib
-- in other words, you're no longer importing what the package author intended and are using your own knowledge of the package's file structure -- you should use a path-like syntax to indicate you're trying to get to a specific path. So if you have a package named myPackage
, and you want to dig into src/models/model1.dart
, you would do myPackage:src/models/model1.dart
(maybe omitting the .dart
).
@lrhn, I think there's no reason to choose between :
and /
; both have their uses. :
is already used for reaching into packages (with package:myPackage
), and /
is known for reaching into directories. We can use both like I described above to keep that distinction clear.
For those following along, I took this comment and fleshed it out into a full proposal here.
Should the import 'package:widget.tla.server/server.dart';
= import widget.tla.server;
rule be kept? I don't see how it adds any value. Dots are the directory and file name separators in the import syntax of some programming languages. Another reason to remove this rule is because sneak_case is the naming convention for Dart packages on pub.dev.
There was a proposal to allow
`something-that-if-not-an-identifier` // in backticks
to serve as a valid identifier. If it gets implemented, then
import a.b.c/dartFile
// becomes
import `a.b.c`/dartFile
and, more generally, every "unconvertible" case (even containing the allowed Unicode characters) could become "convertible" automatically.
I don't see how it adds any value. Dots are the directory and file name separators in the import syntax of some programming languages.
That's the purpose they serve here too, it's just that we don't have any public facing package organization tools that use it that way. The dart pub
tooling supports only a flat package namespace. The rest of our tooling allows dotted package names. In practice this flexibility is only used by our (internal) integration with the bazel build system.
In fact, every "package:" import today literally contains the proposed syntax inside its import string:
import 'package:flutter/material.dart'; import flutter/material ;
import 'package:analyzer/dart/ast/visitor/visitor.dart'; import analyzer/dart/ast/visitor/visitor ;
import 'package:widget.tla.proto/client/component.dart'; import widget.tla.proto/client/component ;
> This strongly suggests users will have no trouble reading the syntax.
While I did find this compelling, there's a slight nuance. The keyword `package:` separates `package/library` from `relative/path`.
```dart
import flutter/material;
import "src/material.dart";
Now, there are the quotes and the .dart
, so I don't see this as a major issue, but I do still think it would help readability to be able to denote which package you're using separately from which file within the package. This would be especially helpful for someone scanning import statements for use of a specific package (say, something they want to remove from the pubspec):
import dart:convert;
import flutter:material;
import otherPackage:something/inside;
import "src/material.dart";
import "src/something/deep/inside.dart";
Personally I'm in the team that:
I don't think many people actually care about imports. IMO for most people, they'd likely rather have everything imported by default, and not have to type imports. Some functional languages kind of do that already.
So to me, working on the syntax for typing imports is wasted effort. I personally only care about them in the case of a name conflict. But the IDE could highlight imports with symbol conflicts when that happens. For everything else, the IDE already adds/removes imports automatically when needed.
Reading the import shorthand proposal, I am struck at the level of complexity for a user that this is introducing. Consider this text:
This is an extraordinary amount of new cognitive load for a user. There are now somewhere around 8 ways to write an import of
serializer.dart
, some of which are only valid in certain locations, and all of which use overlapping syntax to mean different things (":something
means thepackage
part was elided, butfoo:something
means add thepackage:
back in, change the:
to a/
and add a.dart
"). There's numerous ways to write the same thing:foo
andfoo:foo
andpackage:foo/foo.dart
and:./foo
and.foo
and"./foo.dart"
and"foo.dart"
all might mean the same thing (I think? I'm a little lost in the weeds.).Is this really worth the shorter syntax? I really worry that we're making code significantly less consistent and readable in the name of brevity.
cc @lrhn @munificent @eernstg @jakemac53 @natebosch @bwilkerson