tc39 / proposal-built-in-modules

BSD 2-Clause "Simplified" License
892 stars 25 forks source link

If we don't go with npm-style module specifiers, what should the syntax be? #20

Open littledan opened 5 years ago

littledan commented 5 years ago

Ideas I've heard so far:

What else are you thinking of?

Splitting into another thread based on https://github.com/tc39/proposal-javascript-standard-library/issues/12#issuecomment-447925779 .

ljharb commented 5 years ago

@martinheidegger require is part of node, not the language, but no, it's not been confusing to anybody since node's inception that some strings come preinstalled and others need user action to exist :-)

As the person who started the discussion about namespacing node core modules, I'm firmly in favor of namespacing, but that's not the same thing as insisting on different syntax.

martinheidegger commented 5 years ago

"...as insisting on different syntax." I do not insist on it. I am looking at the pro/cons of the various approaches, some come with new syntax, some not. I believe that a template-stringish namespacing could work well for introducing people to that new namespace. It looks fairly clear & unmistakable when written in code. A require-js/node-js implementation is fairly easy possible. The other approaches also have up/downsides - sure. I am glad I do not need to choose 😉

glen-84 commented 5 years ago

@ljharb,

Could either of these options work?

import crypto from std "crypto";
import crypto from nodejs "crypto";
import crypto from "std crypto";
import crypto from "nodejs crypto";
Mouvedia commented 5 years ago

+1 for

import len from std "len";
import { len, map, foo } from std;

It looks like a natural evolution and could be retrofitted into the current system:

import name from "/path/to/module.js";

// would be equivalent to

import name from file "/path/to/module.js";

file is a tentative scope name that I am proposing, but it could be module or whatever.

TeoTN commented 5 years ago

There's also a thing about import module from std 'string'; - it is an extension of the current grammar. If that structure was an option, maybe it'd be worthwhile also to research something briefer that would be structured similiarly, e.g. import module from @'string', import { Map } from @'collections/immutable';

From the meeting notes I also can see that versioning is a feature we want, how about:

'use stdlib/2019';
import { Map } from @'collections/immutable';

or if we wanted versioning per import (I think it'd be problematic, who knows what conflicts would arise and who'd make integration tests of all possible permutations of versions? but I guess someone may want that) - maybe an option would be the following?

import { Map } from @'collections/immutable' of 'stdlib/2019';
Mouvedia commented 5 years ago

Google has chosen 'std:foo'.

ljharb commented 5 years ago

That doesn't mean the language needs to, or should, choose that - import maps mean that any choice browsers have made can be overridden to match the choice the language makes.

erights commented 5 years ago

Now that "std:" is associated with one of many hosts --- the browser --- we must choose something else for modules that are part of standard javascript, such as "js:".

devsnek commented 5 years ago

i took it more as a message that we shouldn't try to use a string specifier, because its currently entirely up to the host to interpret the meaning of specifiers.

erights commented 5 years ago

Thanks @devsnek. I see that now upthread and it is a coherent position.

azz commented 5 years ago

Am I the only one who doesn't like std at all?

  1. It's a shorthand of an English word that is non-obvious to some people.
  2. It doesn't define which standard, ECMA-262, WHATWG, etc?

I think Chrome should have gone with something like platform:, and TC39 should use js:.

obedm503 commented 5 years ago

@azz Although I don't necessarily agree with platform: or js:, the point about std still stands. There is very little precedent for shortened names. Off the top of my head, I can think of atob and btoa, and I can never remember which one does what. std: is not as self evident as we might think.

azz commented 5 years ago

The more I think about, I agree that platform:/js: are probably not a good idea, there's no need to draw a distinction between APIs offered by the web platform and APIs offered by JavaScript platform.

As an alternative to std: I would offer builtin:.

erights commented 5 years ago

there's no need to draw a distinction between APIs offered by the web platform and APIs offered by JavaScript platform.

There is an urgent an absolute need to do so. The browser is only one of many JavaScript hosts. What about Node? IoT? blockchain? A zillion embeddings within other applications as an extension language?

Code relying only on JavaScript APIs are portable across these. Those relying on APIs from a particular host are specific to that host.

The result of this separation is an almost perfect recapitulation of the separation between user-mode (computation-only) instructions on the one hand, and system-mode instructions or system calls on the other hand. JavaScript is a language without I/O. JS code using only the JS language can compute any computable function (Turing universality) but cannot cause effects to the world outside itself. With the exception of the grandfathered-in Date.now() and new Date(), it also cannot sense any effects from the outside world.

However, the JavaScript language does provide a mechanism for JavaScript code to obtain such access: Lookup of host-specific global bindings in the global scope to host objects. When JavaScript code calls a host object, that is the analog of a system-call. Because all access to the host starts with this global scope lookup, the ability to intercept this lookup is the ability to virtualize the host and therefore the external world.

Aside from a few grandfathered-in exceptions (Date.now(), new Date(), Math.random()), there is no hidden state or I/O abilities among the standard primordials. Thus, transitively freezing all the primordials (with harden) results in them being transitively immutable and therefore safely shareable between mutually suspicious subgraphs of objects. (Indeed, EcmaScript 5 missed some hidden primordial state which we removed from EcmaScript 6 for exactly this reason.)

As we introduce modules in JavaScript, we must take care not to lose these properties. The top level state of built-in standard JavaScript modules must also have no hidden state. Their namespace must be kept distinct from the namespace of that which is not safely shareable.

glen-84 commented 5 years ago

How about:

core JavaScript, TC39 core:encoding
web Browsers, WHATWG? web:kv-storage
nodejs Node.js nodejs:fs

Having said that, I think that this discussion is off-topic, as it's supposed to be about the syntax in general, and not about the scope/prefix used.

b-strauss commented 5 years ago

I also don't like std:. web: sounds more "right" for web content. js: for the TC39 stuff. I wouldn't call it core: because the "core" stuff is on the global object. IMHO standard modules should be build upon the core stuff.

gibson042 commented 5 years ago

I would like to remind everyone again that any colon-terminated prefix beginning with a letter and consisting of ASCII alphanumerics plus select punctuation (std:, platform:, js:, core:, web:, etc.) is indistinguishable from a URI scheme and should be registered as such in order to avoid collisions with actual (and potentially fetchable, in the future if not now) URIs.

It would therefore be prudent to either avoid a strategy requiring introducing many of them, or to better cooperate with URI syntax (namespace|module, %%namespace:module, <namespace:module>, etc.).

azz commented 5 years ago

Are any of these valid?

YurySolovyov commented 5 years ago

Can we apply modifier to import statement itself?

import builtin x from 'std/x';
import std x from 'std/x';
Mouvedia commented 5 years ago

@YurySolovyov that's a variant of my proposal. What would it look like for dynamic imports?

YurySolovyov commented 5 years ago

@Mouvedia Can't really think about something less ugly than

const x = await builtin import('std/x');

Or:

const x = await import.builtin('std/x');
YurySolovyov commented 5 years ago

:: is used in E4X and JScript.

And

I am just saying that :: may already have an ingrained meaning if you used the E4X extension or the JScript dialect. More recently you had the bind operator proposal.

There is only so much of good syntax left.

I think :: can be disambiguated by context it is used in:

IMO string-less syntax is better in signaling that module is non-userland and makes it easier to analyse statically. And subjectively, it looks better.

glen-84 commented 5 years ago

@b-strauss,

js: for the TC39 stuff.

JS is very generic/literal – it's all JavaScript.

I wouldn't call it core: because the "core" stuff is on the global object.

core (central) here refers to core built-in modules. These would be implemented in all engines. Some of the items on the global object could be moved into this module. I think the idea would be to move away from using the global/window object.

A theoretical example for the poorly-named atob and btoa methods:

Old:

window.btoa("Hello, world");

New:

import {Base64} from core "encoding";

Base64.encode("Hello, world");

@gibson042,

That's one of the reasons why I suggested these alternatives, but it looks like Chrome developers are already moving forward with the colon-based syntax (although I'm sure it's not a final decision at this point in time).

My suggestions include both a keyword-based and a string-based syntax, but I'm not sure if either of them are technically viable. It'd be nice to hear from someone who knows.


@YurySolovyov,

Can we apply modifier to import statement itself?

import {Base64} from core "encoding";

// vs

import core {Base64} from "encoding";

IMO, the former reads better. The module itself is "core" or "built-in", not the export(s).

YurySolovyov commented 5 years ago

@glen-84 yup, I like that one too, mainly because it does not involve string-based namespacing

gibson042 commented 5 years ago

@azz

Are any of these valid?

  • import x from '@@scope/module'

That's a valid relative URI reference (specifically relative-ref = relative-part → relative-part = path-noscheme → path-noscheme = segment-nz-nc "/" segment → segment-nz-nc = 1*( unreserved / pct-encoded / sub-delims / "@" )), but I think it would be OK to hijack leading @@ or even just leading @ and require desired relative-reference interpretation in those cases—which are certainly unusual—to be explicit (e.g., import x from './@@scope/module').

  • import x from '!scope:module'

That works. It's not a valid relative reference (it doesn't match path-noscheme and therefore relative-ref because of the :) or absolute URI (because scheme = ALPHA *( ALPHA / DIGIT / "+" / "-" / "." ) does not include !).

  • import x from [scope.module]
  • import x from [scope/module]
  • import x from <scope/module>

Those aren't even strings, so assuming ECMAScript syntax is extended to allow them, there'd be no confusion with URIs anyway. But even as strings, none of them would be valid relative or absolute URIs per RFC 3986 (literal [ and < are both disallowed in both scheme and in path segments), and therefore e.g import x from '<scope/module>' would be fine from that perspective.

rossberg commented 5 years ago

Reading this discussion I have serious trouble understanding the urge to reinvent parallel solutions to everything that URIs where already designed to solve. Using proper URIs for this is not a bug, it's the fix.

gibson042 commented 5 years ago

Riffing on that, URNs in particular seem like a very good fit for functionality provided by an implementation or baked into ECMAScript/HTML/etc.

isaacs commented 5 years ago

I know the title of this issue presents a specific framing and hypothetical, but I feel it's important to add my $0.02 (again) in favor of @scope/module syntax.

I completely agree with @erights's comments yesterday about the importance of this. The modularity of IO functionality, and the ability of platforms and users to extend and mock that functionality, are important and (afaik?) unique features of the JavaScript language. That's why JavaScript is everywhere, and why it has been able to expand so far.

There is a cowpath here. We can pave it. There are clear benefits to doing so, which would strengthen the benefits of JavaScript's particular IO abstraction mechanism.

Let's not confuse "being a host object" with "having different syntax than userland objects". Array, Object, WeakMap, Date, Math, and all the rest look just like "regular" JavaScript objects, and developers have somehow managed to use them to great effect.

Pauan commented 5 years ago

@isaacs Chrome is now shipping host APIs under the std: namespace. So there are now two cowpaths. So the decision is not clear at all.

ljharb commented 5 years ago

@Pauan an experimental decision made by one browser a week ago isn't a "cowpath", it's an attempt to create a new one. imo the decision is still just as clear.

Pauan commented 5 years ago

@ljharb Any decision will need the cooperation of everybody involved, including browsers.

The fact that (by far) the biggest browser has made a decision (even experimentally) is a strong argument that cannot be so easily dismissed.

Does that mean that we should definitely use std:? No, but it does mean we cannot dismiss it either, nor can we dismiss other similar schemes like js: or builtin:

lazarljubenovic commented 5 years ago

I don't like the idea of stuffing the syntax inside the string. import builtin 'lib' or builtin 'lib' looks much "polished" to me over import 'builtin:lib', which relies on basically inventing a small language (syntax) which we stuff into the string.

From my perspective, strings shouldn't carry so such semantic information within themselves. It always seems like a hack or someone trying to enhance the language with their own construct -- which has its use-cases when a developer does it with an existing language, but I don't think it fits the idea of language designed including it while changing language itself.

When I look at source code, my brain processes it as a (simplified) version of AST. When I see a string, it's just a sequence of characters to me and it should contain no semantic level-wide information (it shouldn't change how I "understand the imaginary AST").

Making : or any other symbol somehow special means making the string somehow different than other places where a string is used -- you'd expect different syntax coloring in IDEs, and you'd expect it to behave differently than other strings. Admittedly, this is already somewhat true with import paths, but I'd rather we stop adding more magic inside the string and instead tweak (or add more) keywords around it.

Pauan commented 5 years ago

@lazarljubenovic To be clear, there is no "invention" of a new mini-language or syntax. Imports in JS already use URLs, and the foo:bar syntax is a part of URLs.

Yes, that does mean that import * as foo from "http://someurl.com" is valid, and already works. That's just how imports in JS are.

So whether you like it or not, imports in JS already carry additional semantic information. So there are no changes or additions to the language. It's just about deciding how to use the already existing URL system.

ljharb commented 5 years ago

@Pauan no, imports in js use specifiers. Browsers have chosen (as the spec allows them to do) to treat specifiers as URLs. The difference is important - browsers aren’t the entirety of what matters.

Mouvedia commented 5 years ago

As Iv pointed out several times, we cannot make an educated decision if we don't clarify whether we plan to have:

  1. only one unique scope (probably named std)
  2. one current scope (which name MAY change depending on the platform)
  3. several scopes that can coexist (and one shared by all platforms)
  4. several scopes but no compulsory requirement on a name for the "standard" library

Once the possibility/enablement/authorization is acknowledged—or not, we will be able to pick the right proposal for the job. Should we vote on this? Has this already been decided? Should the TC39 officially weight in on the issue?

Pauan commented 5 years ago

@ljharb Node also treats them as URLs: import * as foo from "file://path/to/file.js"

This was intentionally done to match with browsers, to avoid fragmenting the ecosystem.

In addition, even if it was browser-specific, that's irrelevant. @lazarljubenovic 's point was that he didn't want a new syntax added to strings.

And my point is that the "new" syntax (i.e. URLs) has already been added. That's still true, even if it was browser-only (which it isn't).

riggs commented 5 years ago

Hello, I'm an outsider to this community and process who just saw Chrome's blog post. After reading through a fair bit of the issue threads about the namespace, I'm left wondering why I haven't seen any discussion of simply introducing a new global object, std?

obedm503 commented 5 years ago

@riggs I believe polluting the global is exactly why an import-based standard library was suggested.

isaacs commented 5 years ago

Chrome shipping an experimental API is far from being a second cowpath. It's an experiment. @-prefixed namespaces for JavaScript modules has been in use for years by the platform used by something like 93% of all JavaScript users.

A "cowpath" is not just "an existing implemented way to do a thing". It's the path that is beaten down by people wandering about without a particular direction or intent, who all happen to land on a given convention or pattern, and then settle on that pattern because doing things mostly the same thing as everyone else is valuable in a community.

Chrome shipping an experimental API is... well, I'm not sure what the metaphorical equivalent would be, but it's about as close to the exact opposite of a cowpath that could exist.

It might attract us cows, but that seems unlikely, especially in the short term. It's not something that's supported by other browsers (yet?) and since it's experimental, it's not something you can rely on anyone having, or even existing for the long term.

Mouvedia commented 5 years ago

@isaacs The thing that you don't seem to understand is that Microsoft mostly gave up on Chakra and switched to Blink for Edge—like Opera before. Chromium is dominant.

Now check this page; out of the 21 members listed you have 10 that work for a company using webkit or blink (6 google, 2 apple, 2 microsoft) and only 3 working for Mozilla. This list isn't even up-to-date, it's probably way more now.

What Google picks matters. And you will have a hard time reverting what they will eventually choose. It might even be too late…

devsnek commented 5 years ago

Now check this page

that page isn't anywhere near accurate, if you go to the github repo it was last updated 7 years ago.

aside from that, google doesn't own tc39. please stop assuming otherwise.

martinheidegger commented 5 years ago

On other notes: I can't see this topic on the next TC39 agenda: https://github.com/tc39/agendas/blob/master/2019/03.md

Mouvedia commented 5 years ago

@devsnek please don't distort what I am saying. TL;DR: Mozilla is a minority in the TC39 and browser market share wise as well. Google is the dominant player like Microsoft used to be.

Now here's the part which is subjective: whatever Google will pick will be the standard.

riggs commented 5 years ago

@riggs I believe polluting the global is exactly why an import-based standard library was suggested.

Does a single additional global really count pollution?

It handily side-steps the whole import namespace problem, and can readily reuse extremely common existing patterns for compatibility.

obedm503 commented 5 years ago

@riggs

Does a single additional global really count pollution?

perhaps you're right, but either way, there is already a global to which all of the current standard library is attached: window (at least on the browser).

For a common Global across all environments, there is already a proposal https://github.com/tc39/proposal-global/


From my perspective, builtin modules give us a chance to "fix" issues in the current standard. Like the new Temporal proposal, or @glen-84's suggestion:

import {Base64} from core "encoding";

Base64.encode("Hello, world");

as replacements for Date, and btoa and atob.

And while new we could add new Globals like Temporal and Base64, I find imports more aesthetically pleasing; they make JS feel more mature. ¯\_(ツ)_/¯

Disclaimer: There are some better and more logical arguments, this is just my take.

Pauan commented 5 years ago

@-prefixed namespaces for JavaScript modules has been in use for years by the platform used by something like 93% of all JavaScript users.

Citation needed. Is npm big? Yes, definitely. 93%? Highly unlikely.

And in any case, a solution requires the consensus of browsers. So if Chrome has decided on one path, then that carries a lot of weight, regardless of how big or not big npm is.

riggs commented 5 years ago

@obedm503 I think globalThis has value and seems distinct from this discussion.

I'm mostly curious as to why I haven't seen discussion of, e.g.:

stdLib.Base64.encode("Hello, world");

Edit: Or std.lib / std.web / etc.

Namespaces in the import path for stdlib stuff just seems needlessly complicated.

littledan commented 5 years ago

Google's been doing great work in TC39, and V8 achieves a high level of spec-compliance. I'm confident that we can work towards a common conclusion here through standards.

azz commented 5 years ago

Having a subglobal God object hanging off of window/global seems like a poor path to me. Isn't the point of imports to only load code that an application is using, and make code easier to statically analyse?

isaacs commented 5 years ago

Yes, Google is the most dominant player in the browser market, and they swing a lot of weight, indeed quite a bit like Microsoft used to back in the day.

But that's still not a cowpath, is what I'm saying. It's a highway being paved by a dominant technocrat.

Which might be fine in a lot of cases, I mean, you don't want cows to run the show, and presumably the technocrats are smarter than cows about a lot of things.

The idea of "paving cowpaths" in language design is that, when the goal is to design language features that will satisfy a community and be easily adopted with a minimum of disruption, it's good to take stock of the patterns that have naturally emerged.

Google putting something in an experimental release is not "paving a cowpath", because in this context, Google is not the cows.

Citation needed. Is npm big? Yes, definitely. 93%? Highly unlikely.

Well, "big" is subjective.

I can say that more than 11 million people use npm. This number is leveling out as we reach saturation of all JavaScript developers worldwide, and is now growing merely at the rate of new JavaScript developers entering the field or switching from other languages. This is about 50% of all developers worldwide (on the order of 15-30M, depending on what you count as a "developer"). (A few years ago, it was much more of a hockey-stick.)

The 93% number is based on a survey done by the JS foundation, Node.js foundation, and npm, Inc., of a worldwide sample of JavaScript (and non-JavaScript, but JavaScript-using) developers around the world, spanning an impressive number of industries.

So, I don't know if that's "big", but according to the best data anyone's been able to gather, it is about 93% of all JavaScript users using npm. Of course, that doesn't mean that 93% of JS devs are publishing packages to npm. We've only got a million packages or so, in ~8M versions. But they're accessing the registry to do their JS work, so they're being exposed to the patterns of module naming conventions, and getting along fine with them.

I'll leave the googling as an exercise for the reader. Start with IDC's reports on the number of devs worldwide, the JS ecosystem survey from 2018, and the great work that Laurie Voss has presented again and again, correlating the survey data to npm's internal data. Or just go find 100 JS devs, and let me know if more than 7 of them never touch npm.

But this isn't about npm. This is about the namespacing pattern that people are using. npm didn't really even "invent" that so much as choose it by a process of elimination, and I don't think we've ever claimed to own it. You can use @foo/bar in a node app without ever touching npm, or (with a bit of webpack and babel) in any browser app.

The point is that this is the cowpath, so why not pave it? Chrome is probably gonna implement the standard, because they trust TC-39 to wisely design a standard that serves the needs of JS developers, but they'll also push on it to make things move. So why not make the standard be the thing that people apparently are cool with using, and have been using for years? Why redesign this bikeshed?