WICG / import-maps

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

Merging behavior: should we merge within scopes? #153

Open domenic opened 5 years ago

domenic commented 5 years ago

(Previously the general topic was discussed in #88, but opening a new issue for this specific problem.)

The current spec defines merging very simply. Basically, you have

const mergedImportMap = {
  imports: { ...importMap1.imports, ...importMap2.imports },
  scopes: { ...importMap1.scopes, ...importMap2.scopes }
};

In particular, this means that any scope definitions in the second import map completely override the first, as shown in the example linked in the spec.

In #150 @hiroshige-g pointed out that another potential design would be to always merge at the specifier map level. That would mean that if you have the same scope defined in two import maps, the contents of that scope get merged, instead of the second scope definition overriding the first.

I can see the attraction of such a design. I am OK switching, but would like community feedback.

My only worry is that I really do not want to produce a more complex merging behavior. (For example, merging address arrays.) The current design is intentionally very simple and easy to model. I worry that by introducing something with a bit more complexity, we'll be making users expect that merging will be "smart" and "do what I mean", which is a target I do not want to aim for.

Thoughts? We should decide this soon.

For clarity let's discuss the alternatives as top-level only merge behavior or merge-within-scopes behavior.

guybedford commented 5 years ago

As an additional point on merging - is { "scopes": { "x": null } } supported for nulling out previously defined scopes? I guess { "scopes": { "x": {} } } can work just as well here though.

domenic commented 5 years ago

You gotta use {}; any non-object will throw.

What that code will do, though, is an interesting question. In the top-level merge behavior, it will null out the previously-defined scope. In the merge-within-scopes behavior, it will be a no-op. (Similar to how you can't null out the top-level "imports" via a second import map with { "imports": {} }.)

domenic commented 5 years ago

Let me drop in a concrete example to try to make this issue more approachable. Given:

<script type="importmap">
{
  "imports": {
    "a": "/a-1.mjs",
    "b": "/b-1.mjs",
    "std:kv-storage": ["std:kv-storage", "/kvs-1.mjs"]
  },
  "scopes": {
    "/scope1/": {
      "a": "/a-2.mjs"
    }
  }
}
</script>
<script type="importmap">
{
  "imports": {
    "b": null,
    "std:kv-storage": "kvs-2.mjs"
  },
  "scopes": {
    "/scope1/": {
      "b": "/b-2.mjs"
    }
  }
}
</script>

The top-level only merge (current spec) gives

<script type="importmap">
{
  "imports": {
    "a": "/a-1.mjs",
    "b": null,
    "std:kv-storage": "kvs-2.mjs"
  },
  "scopes": {
    "/scope1/": {
      "b": "/b-2.mjs"
    }
  }
}
</script>

whereas the merge-within-scopes behavior gives

<script type="importmap">
{
  "imports": {
    "a": "/a-1.mjs",
    "b": null,
    "std:kv-storage": "kvs-2.mjs"
  },
  "scopes": {
    "/scope1/": {
      "a": "/a-2.mjs",
      "b": "/b-2.mjs"
    }
  }
}
</script>
bakkot commented 5 years ago

In #167 we chose merge-within-scopes; it seems natural to use the same process to merge each individual same-scoped mapping as is used for the top-level one.

frank-dspeed commented 2 years ago

did some one consider to maybe offer importMaps based on a per Script Tag Scope? as attribute? i think that solves near any situation then we could maybe define a default importMap for that attribute? And override that if needed that is at last that what the most complet advanced module loader that i am aware of is doing

https://stealjs.com/docs/config.configMain.html

<script src="../path/to/steal/steal.js"              <!-- moduleLoader -->
        config-path="../path/to/stealconfig.js"    <!-- importMaps https://stealjs.com/docs/config.paths.html or https://stealjs.com/docs/config.map.html -->
        main="app"                                            <!-- entryPoint --> 
>
</script>
trusktr commented 1 year ago

did some one consider to maybe offer importMaps based on a per Script Tag Scope?

@frank-dspeed Being able to configure import maps per module script sounds like a really great idea. <script ... importmap="./map.json">

How can it help here?

lemanschik commented 1 year ago

My Final Conclusion is that i stay completely away from import maps i await the serviceWorker Registration and move all specifier lookups into it as it receives the raw request i am able to respond with the resolved Response. (text/javascript,text/json)

benefits: Using the Native Frontend Proxy Supplied via serviceWorkers anyway so no duplication in logic needed for cache invalidation. I Ship since some month directly everything packaged only as script that gets executed via the serviceWorker and then partial incremental updates the firstPaint of the page.

resulted in a lot of code reduction as i can use the same logic also on backends since nodejs 19 and other WInterOp Runtimes got native fetch support. I can ReUse all relevant frontend code 1:1 with the frontend without a additional dev-server in between. For dev i directly use a FileSystemDirectoryHandle to the code via the service worker to even not use Chromium Overrides and archive Hot Reload. Overall i guess leaving builds and import maps out of the process reduced my build and deploy time by n*infinity