tc39/proposal-asset-references

Should referenced location be lazily calculated or eagerly?

bmeck opened this issue · 11 comments

bmeck commented

When using references there are a few APIs that are interesting in if the actual finding of a resource should be done lazily or eagerly.

  • locate(specifier, asset_reference) - chained location lookup
  • fetch(asset_reference) - ability to create a "body" for the reference

This has some impact on what a host might store in the AssetReference internal fields.

  1. If we notify a host of a reference during parsing, I suspect that like preload parsers it would eagerly try to fetch a body. That would lean on eagerly calculating things.
  2. For chained lookup, it is likely that tooling can statically determine the absolute location of a variety of things. For that, the referencing module would likely be irrelevant. This is important if we encounter situations where the referencing location is only being kept alive by these references.
  3. For chained lookup, we can partially locate resources rather than doing everything lazily. consider asset plugins from 'plugins'; locate('./textures', plugins);
  4. For fetching bodies, we likely want the location to be able to be absolute.
  5. For repeated operations, once a location has been determined the host may wish to never do the location lookup a 2nd time.

Not all of these are important to the static form of this API, but the data type seems to need to have some host involvement that we need to think about.

I think allowing the host to modify these internal slots to move from non-absolute location, to absolute location should be exposed somehow. Doing so would allow the following code to avoid a lot of extraneous lookups:

// streaming parser notifies host of ~ (./data.json, import.meta)
// host may eagerly locate / fetch body
asset data from 'api-endpoint';
import engine from 'templating-engine';

// ...
async update(state) {
  await put(state, data);
  await fetch(data);
  // ...
}
// ...

Agree. This might be important to specify one way or another because for example the import maps proposal is dependent on when the map is inserted so it could have semantic meaning too. Whether the import map existed before the module was initialized or before the path was resolved.

It might not be a bad idea to require that these paths are statically resolved before the module is even executed just like other module resolution.

That's a strong argument for why we should have a separate static syntax separate from the dynamic one since the dynamic one couldn't be eager (but possibly idempotent).

bmeck commented

@sebmarkbage I'm not sure we need to enforce that these are resolved before the source starts evaluating, I'd rather let the time of finding a result just be unobservable. That means we can start evaluation of modules prior to fully locating all references, as long as we preserve the necessary data to locate them as if it was done ahead of time wrt things like import maps being added.

If a static reference isn't resolveable tho, shouldn't it throw prior to any evaluation?

bmeck commented

@ljharb that remains a question. I could imagine things that are not able to be resolved as well as things that are unable to be used.

// might never exist in prod
// might still be able to be located
// might be unable to be fetched
asset extra from 'debug';

...
if (mode === 'debug') import(extra).then(...);
...

It seems like it'd be very confusing that import 'something' and asset from 'something' wouldn't behave the same way, whether the path is resolvable or not.

@ljharb What do you mean? import does a load vs asset doesn't so it shouldn't be confusing that there can be differences. There will be differences regardless if the load works or not.

Sure, but by the time any load happens, you know that all the static specifiers will resolve - ie, the static guarantee is that the dependency graph is valid before any code evaluates.

bmeck commented

@ljharb I think we cannot know if the references are "valid" since we don't actually load them, we just want to ensure that they can be located. The reference is just a pointer and doesn't actually check the asset's response body. If an asset gives back a 404 for example. I think the debate in this issue is more about if locating the resource is done eagerly, lazily, or opaquely. This issue isn't about ensuring that we can get a valid resource back from a reference since that seems to conflict the goals of this proposal to allow lazy operations for loading.

I don't know how it can be defined as unobservable while still allowing something like "import maps" since that's a very mutable configuration during the load. Even if that proposal ends up shifting, it does highlight that making it unobservable limits what host can do. Perhaps it can be undefined.

bmeck commented

@sebmarkbage I think it would be fine to leave undefined, I personally just want things able to be done lazily and then cached so that we can avoid lookups and allow optional assets.

Per how could this work WRT maps:

// forms ('g', module)
// vm notified, saves *current* mappings via map to module
asset g from 'g';

I believe various name map ideas suffer this problem of being observable anyway since

import('foo');

Could change resolution over time even if it is idempotent once used.