Skip to content
This repository has been archived by the owner on Sep 2, 2023. It is now read-only.

Spec: Would !!import.meta ? 'module' : 'not' be a reasonable ask? #395

Open
SMotaal opened this issue Oct 4, 2019 · 67 comments
Open

Spec: Would !!import.meta ? 'module' : 'not' be a reasonable ask? #395

SMotaal opened this issue Oct 4, 2019 · 67 comments

Comments

@SMotaal
Copy link

SMotaal commented Oct 4, 2019

I'm think we all must have considered this at some point, but cannot recall.

Q: Is there a safe way for code to test if it is module or not (ie not early/parse error)?

This code would target runtimes supporting import(…) globally, irrespective of semantics, and so my thinking was a clean nice-to-have here could be !!import.meta ? 'module' : 'not' which should not be too much to ask for… thoughts?


Possibly Related Discussions

@SMotaal SMotaal added cjs esm brainstorming Safe place to discuss ideas and provide constructive feedback labels Oct 4, 2019
@SMotaal SMotaal changed the title Spec: Would !!import.meta ? 'module' : be a reasonable ask? Spec: Would !!import.meta ? 'module' : 'not' be a reasonable ask? Oct 4, 2019
@ljharb
Copy link
Member

ljharb commented Oct 4, 2019

If code wants to assert its parse goal, it can do export {} to force ESM or with ({}) {} to force Script.

You can also wrap either of those in Function and a try/catch to detect it.

@SMotaal
Copy link
Author

SMotaal commented Oct 4, 2019

You can also wrap either of those in Function and a try/catch to detect it.

This one is more on point, but I hit this issue in node:

// sometimesModule.js — {package: type: 'module'}
console.log(
  (function() {
    try {
      import.meta.url;
      return 'module';
    } catch (e) {
      return 'not';
    }
  })(),
);
> node sometimesModule.js

SyntaxError: Cannot use 'import.meta' outside a module
    at Module._compile (internal/modules/cjs/loader.js:872:18)
    at Object.Module._extensions..js (internal/modules/cjs/loader.js:947:10)
    at Module.load (internal/modules/cjs/loader.js:790:32)
    at Function.Module._load (internal/modules/cjs/loader.js:703:12)
    at Function.Module.runMain (internal/modules/cjs/loader.js:999:10)
    at internal/main/run_main_module.js:17:11
> node --experimental-modules sometimesModule.js

(node:37127) ExperimentalWarning: The ESM module loader is experimental.
module

So what I was getting at is sometimesModule.js would be an entrypoint that could be portable everywhere and in case it is node, it will either require or import('module').createRequire(…).

If code wants to assert its parse goal, it can do export {} to force ESM or with ({}) {} to force Script.

And so, this is a separate topic, but thanks.

@ljharb
Copy link
Member

ljharb commented Oct 4, 2019

(I crossed out that part of my comment since Function always evaluates as a sloppy Script)

Why would you want a file to be both Module and Script? Can you help me understand the use case?

@bmeck
Copy link
Member

bmeck commented Oct 4, 2019

At the top level this differs between Script and Module.

// maybe "use strict" here if you wanna have a Strict Script
let isModule = this === void 0;
// ... rest of file ...

@bmeck
Copy link
Member

bmeck commented Oct 4, 2019

@SMotaal are you still wanting to pursue adding import.meta to Script or is that enough?

@SMotaal
Copy link
Author

SMotaal commented Oct 4, 2019

Why would you want a file to be both Module and Script? Can you help me understand the use case?

@ljharb Not a good way for me to be able to reframe problems (ie can you humor me anyway please), but say this is a workshop for newcomers, where they use the sometimesModule.js to bootstrap.

I'm inclined to think that for someone less experienced, an intuitive expectation would be to know for certain that they are not in a module.

As @bmeck points out:

At the top level this differs between Script and Module.

And certainly, this is not as intuitive, but also worth being aware of. It still can get messy to explain for such audience, though

Because there are other ways for such a module to evaluate with this being undefined that can merely be due to a bug in a some loader on some platform:

const evaluate = (1, eval)(`
  function() {
    "use strict";
    return eval(arguments[0]);
  }
`);

It is fair imho to say that semantics of this being undefined and the code being evaluate itself is module or not are rough and hacky to relate to for at least a few folks.

Therefore: The argument I am trying to consider making is that semantically speaking, import.meta is an object in module, otherwise not — and that could just as well be undefined.


(I crossed out that part of my comment since Function always evaluates as a sloppy Script)

Yup, was mid way trying to clean up the code I did towards it and saw it as I replied… thanks though!

@ljharb
Copy link
Member

ljharb commented Oct 4, 2019

It seems to me that newcomers should understand that the two parse goals are different, and which one they’re using, before writing a line of code - just like they’d need to understand which language they’re writing beforehand.

@SMotaal
Copy link
Author

SMotaal commented Oct 4, 2019

@ljharb I appreciate there being different views on this, and appreciate others appreciating that when it comes to whohow to go about improving opportunities for newcomers learning, it is fair to assume that there will be different schools of thought, all worth appreciating.

Fair?

@SMotaal
Copy link
Author

SMotaal commented Oct 4, 2019

@SMotaal are you still wanting to pursue adding import.meta to Script or is that enough?

@bmeck Yes please — sorry, was midway in posting :)

@SMotaal
Copy link
Author

SMotaal commented Oct 4, 2019

@ljharb if we just considered how this plays out if it was introduced, where do you see !!import.meta causing runtime ambiguities or concerns?

@devsnek
Copy link
Member

devsnek commented Oct 4, 2019

@SMotaal showing people the differences is good, but there's no reason to actually rely on these checks in code. Maybe if you're doing some advanced bundling or something, although even then it's iffy.

Some web people wanted import.meta in scripts (as an object) and I'm more interested in pursuing that.

@ljharb
Copy link
Member

ljharb commented Oct 4, 2019

For one, the proposal for it explicitly and intentionally disallows it to be parsed in Scripts.

@SMotaal
Copy link
Author

SMotaal commented Oct 4, 2019

Some web people wanted import.meta in scripts (as an object) and I'm more interested in pursuing that.

@devsnek certainly… this is the path I was thinking down the road btw.

Something like (but not quite) !!import.meta && (!('type' in import.meta) || import.meta.type === 'module') ? … but I was thinking that would be in progression and open to variation.

So, first we want import.meta to not throw to pan out (the least amount of objections imho being just undefined at first), so this expression then would not be a parse error.

Workable?


For one, the proposal for it explicitly and intentionally disallows it to be parsed in Scripts.

@ljharb Can we know that the intent was to prevent this or could this just have been the leftover from the pre import() revisions?

@devsnek
Copy link
Member

devsnek commented Oct 4, 2019

just to be clear, I'm against import.meta.type. The objects should be identical to the ones in modules.

@SMotaal
Copy link
Author

SMotaal commented Oct 4, 2019

just to be clear, I'm against import.meta.type. The objects should be identical to the ones in modules.

@devsnek I'm willing to leave this in for the right time, but would it be reasonable to consider letting it afford more meta about its source, irrespective of bikeshedding, fields… etc.?

@SMotaal
Copy link
Author

SMotaal commented Oct 4, 2019

Side question to help me appreciate things better please… I hope it is fair to ask, is there a sense of reluctance with wanting to block sometimesModule.js from ever panning out?

@devsnek
Copy link
Member

devsnek commented Oct 4, 2019

sometimesModule.js shouldn't be possible. If it is possible, we have a bug.

@ljharb
Copy link
Member

ljharb commented Oct 4, 2019

@SMotaal
Copy link
Author

SMotaal commented Oct 4, 2019

sometimesModule.js shouldn't be possible. If it is possible, we have a bug.

@devsnek It would be really helpful to help others understand why it should though? At least I disagree with this, but if there is more to it beyond preference I am more than happy to revise my position. Please help me appreciate your rationale for it.


@SMotaal the intent is explicit: https://github.com/tc39/proposal-import-meta#proposed-solution

@ljharb Assuming you might be referring to this bit (please be more explicit otherwise)…

and should not be repurposed for information about the currently-running classic script.

If so, that is natural in the progression of spec things… as in proposed things must not be watery, they must be explicit (and that is not the same as contention for what is being asked), but later things are subject to elicit such things to be changed, right?

@devsnek
Copy link
Member

devsnek commented Oct 4, 2019

@SMotaal ambiguity breeds confusion. This is one of the reasons I'm not a fan of type: module. Changing the location of a file on disk really shouldn't change how it's interpreted by node.

@SMotaal
Copy link
Author

SMotaal commented Oct 4, 2019

@SMotaal ambiguity breeds confusion. This is one of the reasons I'm not a fan of type: module. Changing the location of a file on disk really shouldn't change how it's interpreted by node.

@devsnek I am not talking about just node but even node can have more than one way of looking at a file for many reasons — ie same file but not the single instance of some node build — right?

@devsnek
Copy link
Member

devsnek commented Oct 4, 2019

Sure different versions of node could do that, but they could also break your detection system. I don't think that's a path worth going down.

@SMotaal
Copy link
Author

SMotaal commented Oct 4, 2019

I am still feeling I am missing context which I really am curious about and hope you can appreciate me wanting to step back from those tangents and may be try and approach things differently…

It helps to directly restate the ask here… import.meta not being a parse error being an opinion of equal merit and one held by those using the language and hitting this to want to explore things more closely, as in purely about the semantics of ECMAScript, not specifics of some implementation, including our own work-in-progress, or node in general.

Two views on a matter here is okay with me, but if it is not merely refuting my view because it is opposite to your own, it might help to reconsider things from the other perspective, not consider it being wrong the foregone conclusion (sorry, this is just how it feels from my where I'm sitting) to the discussion, and that would help make this feel like a fair space for debating with good constructive arguments.

Conclusion: This clearly is not wrong from my perspective here, based on this discussion so far, just different and I have not picked up on anything that leads to this breaking semantics (aside from not throwing I mean).

@jkrems
Copy link
Contributor

jkrems commented Oct 4, 2019

If all you want is a file that could be either, just uses import(), maybe just:

'use strict';

const isModule = typeof require === 'undefined';

Not sure why it would need import.meta - the above seems simpler than the import.meta check.

@SMotaal
Copy link
Author

SMotaal commented Oct 4, 2019

typeof require

here is fair for node specifically, not generally though — as in sometimesModule.js cannot dictate which runtime and loading approach is used, and just as loading can mean cjs or esm in node, it can mean more than just esm elsewhere because loading existed there too prior to esm times (ie not always just typeof require).

@jkrems fair?

@ljharb
Copy link
Member

ljharb commented Oct 4, 2019

That's where this becomes the better choice.

@MylesBorins
Copy link
Member

MylesBorins commented Oct 4, 2019 via email

@devsnek
Copy link
Member

devsnek commented Oct 4, 2019

@MylesBorins i edited my comment to clarify what i meant, apologies for the confusion.

@bmeck
Copy link
Member

bmeck commented Oct 4, 2019

It seems the desire is still just to detect the current goal regarding any proposal, which we have a workflow that allows it. For detecting node there are existing workflows. I'm not seeing anything that needs a new feature explicitly.

@SMotaal
Copy link
Author

SMotaal commented Oct 4, 2019

@bmeck can you point to that (would really be help if there is one missed in this thread), but please note the above considerations — ie this===undefined and typeof require!=='function' being only workarounds that work under the right conditions — ie specific to certain implementation assumptions, and not explicit semantically speaking.

A language spec should not relate to a "by the chance of it there was…" kind of assertions that are not part of its own text, but rather explicit assertions that people can trust to author code around — or not in this case — because when there is no contract or guarantee that such implementation-specifics not spec being tested will only ever mean the implied thing to be asserted.

I really hope this is not considered a faulty premise to want us to work from and that others at least find it a little disturbing that we are repeating patterns that already showed us how future problems can be avoided.

@bmeck
Copy link
Member

bmeck commented Oct 4, 2019

@SMotaal I'm not sure import.meta being undefined is any less esoteric in my mind. this === undefined is a contract guaranteed by the spec which should work in all conditions. The language itself cannot provide a guarantee about host provided APIs like require, that is the realm of feature detection; I'm not sure what is being proposed to make a contract there.

@SMotaal
Copy link
Author

SMotaal commented Oct 4, 2019

@SMotaal I'm not sure import.meta being undefined is any less esoteric in my mind. this === undefined is a contract guaranteed by the spec which should work in all conditions.

@bmeck please note above

The language itself cannot provide a guarantee about host provided APIs like require, that is the realm of feature detection; I'm not sure what is being proposed to make a contract there.

Also, I am not hard set on it being import.meta===undefined but rather any similar approximation for something that is more readily intuitive enmasse — that does not come with circumstantial trimmings like needing to guard for require because this is not a spec thing in itself — this being a premise in itself for wanting this detection without require being the test.

@bmeck
Copy link
Member

bmeck commented Oct 4, 2019

@bmeck please note above

I've seen that; as I noted, it should be done at the top level. I would assume import.meta to be undefined inside of eval inside of Module source text as well. I don't see a uniform solution.

Also, I am not hard set on it being import.meta===undefined but rather any similar approximation for something that is more readily intuitive enmasse — that does not come with circumstantial trimmings like needing to guard for require because this is not a spec thing in itself — this being a premise in itself for wanting this detection without require being the test.

I don't understand this.

@ljharb
Copy link
Member

ljharb commented Oct 4, 2019

If there can be a bug about top level this, there can be one with import.meta. It’s not worth speculating about possible bugs.

Top level this is a reliable means of differentiating Script and Module, per spec, in every engine or context I’m aware of. The possibility of bugs in that mechanic doesn’t alter reliability.

@devsnek
Copy link
Member

devsnek commented Oct 4, 2019

fwiw, eval('import.meta') is always a syntax error.

@SMotaal
Copy link
Author

SMotaal commented Oct 4, 2019

I've seen that; as I noted, it should be done at the top level.

@bmeck It is fair to state that the intent here is for the code to know, "Top level" being an assumption that cannot be verified by the code itself here leaves the gap. The bigger gap here though is going from this===undefined to deducing module/not is quite a leap, my inclination towards import.… here would be to somewhat shorten this (cognitive) distance with what it is already on hand to work with today.

@devsnek
Copy link
Member

devsnek commented Oct 4, 2019

I'm still really confused about what we're actually trying to enable here

@ljharb
Copy link
Member

ljharb commented Oct 4, 2019

Code that is wrapped also can’t know if it’s transformed - which means import.meta, or any syntax, would be unreliable anyways.

@SMotaal
Copy link
Author

SMotaal commented Oct 4, 2019

fwiw, eval('import.meta') is always a syntax error.

@devsnek it does help, I somehow forgot to explicitly note it above… thanks.

Just to reiterate the thought at this point, pursuing first-class detection by code itself, with import.… merely being a more intuitive place to work with than this… and more so than the alternatives making implementation-specific stipulations (like require being or not anything).

@SMotaal
Copy link
Author

SMotaal commented Oct 4, 2019

Code that is wrapped also can’t know if it’s transformed - which means import.meta, or any syntax, would be unreliable anyways.

@ljharb sure, possibly, but a runtime taking steps towards conformance and the fact there are potentially bugs related to some loader extensions (or not), all this room for unknowns leaves it at least reasonable to not wanting to be making these kinds of generalized statements — does not change the fact that code itself can benefit from knowing a little more about things to become more reliable.

@ljharb
Copy link
Member

ljharb commented Oct 4, 2019

I think per your original question, it is indeed unreasonable to ask for a new feature because of “possible bugs” or because your code might be transformed in ways beyond your control.

Top level code already knows the context it’s in via this - your concern about that not being intuitive/explicit is a fair point, but that’s imo the only justification for any new features so far in this issue.

@devsnek
Copy link
Member

devsnek commented Oct 4, 2019

@SMotaal

pursuing first-class detection by code itself

Right, but what does that enable us to do? That's the part I'm confused about.

@SMotaal
Copy link
Author

SMotaal commented Oct 4, 2019

unreasonable to ask for a new feature because of “possible bugs”
Top level code already knows the context it’s in via this…

@ljharb but my point is not about bugs (only the tail I was constructively hoping to be respond to per your request)… or this being the keyword intentionally chosen in the spect to offer the "module" assertions reiterated many times above…

Just to point out this===undefined only incidentally happens to be fact in "module" being that "module" is always "strict" and this===undefined is actually the reality of "strict" itself.


but what does that enable us to do

@devsnek It is about concrete first-class reflection (an expectation that is not at all unreasonable)…


Sorry, once again, I really need to step away because I feel like my sincere efforts to be constructively working through every scenario posed (with sincere appreciation for them) ends up leaving over-saturating tails that are already addressed in the issue, and cannot be reiterated enough.

@ljharb
Copy link
Member

ljharb commented Oct 4, 2019

You are right that it’s incidental, but it can still be used for this purpose all the same.

@devsnek
Copy link
Member

devsnek commented Oct 4, 2019

reflection is good, but it should also be justified.

@SMotaal
Copy link
Author

SMotaal commented Oct 4, 2019

reflection is good, but it should also be justified.

@devsnek I feel a little unsure how all my effort here and your statement can be viewed not with some confusion (I am inclined to trust it is not intended to be offensive, dismissive… etc. not relevant in the context of our forum and mutual collaboration).

@devsnek
Copy link
Member

devsnek commented Oct 4, 2019

@SMotaal it is very difficult to solve a problem if you don't know what the problem is. I would love to sit here and brainstorm, it's why I'm subscribed to issues here, but I still have no idea what the problem we're trying to solve is.

@SMotaal

This comment has been minimized.

@targos

This comment has been minimized.

@bmeck
Copy link
Member

bmeck commented Mar 3, 2020

Does this want some kind of change to be requested to the spec?

@devsnek
Copy link
Member

devsnek commented Mar 3, 2020

it appears to want import.meta to be valid syntax and return undefined in a script.

@SMotaal
Copy link
Author

SMotaal commented Mar 4, 2020

@bmeck I guess the whole point of code deciding to respond to how it is loaded based on import.meta seems fair if import() already accounts to where it is loaded from.

So, yes, thanks @devsnek, but fair to note undefined here is just meant to stand in for something other than throwing SyntaxError. This would allow code to respond to type in a browser or equivalent. Now import() would likely not be the only way to continue loading its own dependencies, and authors get to decide how they want to do that.

@DerekNonGeneric
Copy link
Contributor

FWIW, I've been able to detect the current goal w/o throwing SyntaxError with this one-liner.

const isModule = typeof module === 'undefined' ? true : false;

This is because module is not a global furnished in ES module context by any versions of Node.js thus far, while it is always furnished in a CommonJS context.

@SMotaal, does this solve for you?

@ljharb
Copy link
Member

ljharb commented Mar 10, 2020

global.module = true anywhere in the app would break that code, though.

@DerekNonGeneric
Copy link
Contributor

DerekNonGeneric commented Mar 10, 2020

@ljharb, the same would apply for global.import.meta = true.

Of course, one would need to be careful. This check would need to be performed prior to any instantiation too. So rather than doing the following.

import module from 'module';

Prefer:

import { Module } from 'module';

Otherwise, it will also result in false-positives.

@devsnek
Copy link
Member

devsnek commented Mar 10, 2020

import.meta is not a property lookup, it's a special syntax. you can't override it on global.

Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Projects
None yet
Development

No branches or pull requests

9 participants