WICG / import-maps

How to control the behavior of JavaScript imports
https://html.spec.whatwg.org/multipage/webappapis.html#import-maps
Other
2.66k stars 69 forks source link

how are direct and indirect "absolute cross origin urls" modules resolved? #236

Closed serapath closed 3 years ago

serapath commented 3 years ago

I skimmed the spec and read through this repo and it's issues, but still - there seem to be so many details involved that i couldn't figure it out easily. I appreciate all the ideas about "depcache" which to me sounds like package-lock.json or npm shrinkwrap so you can snapshot the entire dependency tree to not only freeze versions, but also to make lookup easier. The issue though is, that if you want to just set up your website and require an external module that has many dependencies, you need tooling (something like npm shrinkwrap) to correctly generate the importMap.


It would be nice though if there was a solution (maybe slightly more inefficient than "depcache" - at least for the first load that could then cache and internally build up such a "depcache" file), that does not require tooling :-)

A. what i was looking for but could not figure out: how are "absolute-cross-origin-module-urls" resolved?

  1. foo.com/index.html loads <script src="index.js"></script> and has an importMap
  2. index.js imports a bunch of modules:
    • => some with a local relative path which resolves just fine
    • => some with absolut identifier like e.g. import("lodash") which is specified in the importMap
      • to either another local javascript module
      • to an absolute same origin javascript module
      • to an "absolute-cross-origin-module-urls"
    • => some with absolute same origin urls, e.g. foo.com/foo.js
    • => some with "absolute-cross-origin-module-urls", e.g. bar.com/bar.js

B. what i imagine the solution looks like, but maybe somebody can point me to how it is actually solved:

  1. all imports which need to reference directly or indirectly via local importmap a absolute-cross-origin-url need to point to a manifest.json, which defines at least:
    1. a main script, which is the file that is actually imported via the original import statement
    2. a importMap field, which specifies an import map or the exact link to an importMap to look up all scripts imported directly or indirectly via main script - until it hits again an absolute-cross-origin-url where this whole procedure repeats
domenic commented 3 years ago

There is no difference between same-origin and cross-origin URLs in terms of how they are treated by an import map.

I'll close this, since I think the readme already covers this in the https://github.com/WICG/import-maps#general-url-like-specifier-remapping example, but I'm happy to reopen and continue discussing if I misunderstood!

serapath commented 3 years ago

Thx, @domenic I think this is a misunderstanding. As mentioned in the first parargraph, my understanding is, that import maps work somewhat similar in spirit to e.g. package-lock.json or npm-shrinkwrap.json and have to define all dependencies and the dependencies of dependencies, and so on. So, what I mean by "cross-origin" (full url) imports is, that if that imported script itself specifies local imports in its definition, how are they resolved?

serapath commented 3 years ago

I wish there was a way so a cross origin full url lookup for a script would allow that "remote script" to specify a file for its local dependencies, so I don't have to do that and I don't have to run tooling to generate my "import maps".

domenic commented 3 years ago

Can you provide a code example of the situation you're wondering about? E.g. give a series of URLs and their contents, and then ask a specific question where you're not sure what the current proposal does?

serapath commented 3 years ago

ok, let me try. maybe i got it all wrong - hopefully not :-)

Do I need to list ./baz.js in my import map? What if baz.js was a name instead that you would look up in an import map when resolving inside ./bar.js?


https://foo.com/foo.js

export const foo = "foo"

https://foo.com/index.html

<!doctype html>
<html>
  <head><meta charset="utf-8"></head>
  <body><script type="module">
    import { foo } from "./foo.js"
    import { bar } from "https://bar.com/bar.js"

    console.log({ foo, bar })
  </script></body>
</html>

https://bar.com/bar.js

import { baz } from "./baz.js"
export const bar = baz

https://bar.com/baz.js

export const baz = "baz"
domenic commented 3 years ago

Do I need to list ./baz.js in my import map?

No

What if baz.js was a name instead that you would look up in an import map when resolving inside ./bar.js?

I don't really understand this question? Perhaps this needs a second code example?

would the foo.com import map have a way too use the import map from bar.com ?

No. See https://github.com/WICG/import-maps#scope

serapath commented 3 years ago

https://foo.com

https://foo.com/foo.js

export const foo = "foo"

https://foo.com/index.html

<!doctype html>
<html>
  <head>
    <meta charset="utf-8">
    <script type="importmap" src="./package.json"></script>
  </head>
  <body><script type="module">
    import { foo } from "foo"
    import { bar } from "bar"
    console.log({ foo, bar }) // { foo: "foo", bar:    ??????    }
  </script></body>
</html>

https://foo.com/package.json

{
  "imports": {
    "foo": "./foo.js",
    "bar": "https://bar.com/bar.js"
  }
}

https://bar.com

https://bar.com/bar.js

import { baz } from "baz"
export const bar = baz

https://foo.com/index.html

<!doctype html>
<html>
  <head>
    <meta charset="utf-8">
    <script type="importmap" src="./package.json"></script>
  </head>
  <body><script type="module">
    import { bar } from "bar"
    console.log({ bar })
  </script></body>
</html>

https://bar.com/package.json

{
  "imports": {
    "bar": "./bar.js",
    "baz": "./baz.js", // <== this has the needed info
  }
}

https://bar.com/baz.js

export const baz = "baz"
serapath commented 3 years ago

I assume the above does not work at the moment.

The "problematic" situation described above is:

  1. a module is imported with a full url with a different origin
    • e.g. https://foo.com/foo.js imports https://bar.com/bar.js
  2. and that "cross-origin-module" (e.g. https://bar.com/bar.js) needs to lookup an import like (e.g. "baz")

My favourite solution to this would be something like:

https://foo.com/package.json (the used "importmap")

{
  "imports": {
    "foo": "./foo.js",
    "bar": "https://bar.com/package.json"
  }
}

so i can use: https://foo.com/index.html

<!doctype html>
<html>
  <head>
    <meta charset="utf-8">
    <script type="importmap" src="./package.json"></script>
  </head>
  <body><script type="module">
    import { foo } from "foo"
    import { bar } from "bar"
    console.log({ foo, bar }) // { foo: "foo", bar: "baz" }
  </script></body>
</html>

which in turn knows how to use the "cross-origin-import-map": https://bar.com/package.json

{
  "imports": {
    "bar": "./bar.js",
    "baz": "./baz.js",
  }
}

That way, when "bar" specifier is used in the "importmap" on "foo.com", it will use (delegate to) the "https://bar.com/package.json" to look up "bar" instead and from then on, resolve all imports that directly or indirectly happen in "bar.js" by using that same "sub-importmap" :-)

domenic commented 3 years ago

Yes, the above does not work as you're hoping. This is intentional, with the reasons why being covered in https://github.com/WICG/import-maps#scope.

serapath commented 3 years ago

I understand that reason, but everything could just work the way it works. Only when you reference a json file in your import maps you explicitly opt into that mode. it might be you in your personal project it might be the CTO in your company whoever it is - it is a conscious decision to decide its fast enough. this could still be intercepted and cached by the service worker and it is all fine then. ...so why would anyone prevent the above feature given that?

I can't see any section in what you linked that would really rule out such an opt-in feature, unless the reasoning is stated in an open issue linked... haven't checked those.

could you give me a more specific hint or do you think as an opt-in feature this might be viable?

domenic commented 3 years ago

The idea is to ensure that there is a central place to control an application's module resolution. This is somewhat important from a security perspective, for example.

serapath commented 3 years ago

I understand that.

Maybe just to make it clear again. An importmap will work the way it works right now with full control over everything. There is no change at all. The proposed idea is to only allow an "opt in" and of course an "opt out" too at any time, by (un)delegating parts of the module resolution to another importmap. All it needs is to specify another "importmap" as the lookup value.

Not having the proposed feature has no security upsides, but huge development inconveniences and makes developing without tooling so tough, that i think it sadly is unlikely to happen in practice, making developing for the web harder than it needs to be.

{
  "imports": {
    "foo": "./foo.js",
    "bar": "https://bar.com/package.json"
  }
}

when would you use the proposed feature? you would only "opt in" for example to delegate to importmaps of:

benefits: developers can experiment/prototype/develop quickly and try out things and when an app becomes something more serious and production ready, relied upon by many users, you just stop linking to delegated importmaps and instead use tools to generate an exhaustive local importmap to have full security control.

downsides of NOT having it: If importmaps continue to work the way they are currently proposed, most people will just use tools to generate the map and anyway not ahve the time or skills or budget to audit things, which will make security worse. Being able to delegate to trusted third parties is beneficial for security.

domenic commented 3 years ago

I don't agree that delegating is a reasonable thing to do, sorry. I think you must be looking for a different proposal than this one. We're not going to add such functionality to import maps; they're one-per-application, assembled by build tools.

serapath commented 3 years ago

You say you do not agree and that it is not reasonable, but could you tell me why? you mentioned security concerns, but if the json file is not referenced

  1. security depends on your projects import maps and ability to audit.
  2. if projects decide to delegate that to specialists or partners they trust, they can, and then they help making it secure
  3. if you change your mind, update your import map and take back control

so that concern is gone. why do you think it is unreasonable?

in reality most people will just use tooling to generate the import map and wont anyway have the time, or skill to actually do a proper audit anyway, so allowing that kind of delegation will probably increase security rather than decrease it.

domenic commented 3 years ago

I'm sorry, it feels like I'm not really getting through no matter what I say, and I don't really have the time to repeat the same explanations. Maybe someone else watching the repository can help, but I need to disengage from this thread now, as to me it feels like it's going in circles.