Skip to content
This repository was archived by the owner on Feb 26, 2025. It is now read-only.
This repository was archived by the owner on Feb 26, 2025. It is now read-only.

Preloading and performance with client-based resolution #21

@guybedford

Description

@guybedford

This package map implies a particular frontend workflow whereby dependencies are traced by some process to generate the manfiest, which can then used by the browser. A server can then serve resources without needing to know exact resolutions of package boundaries, with the client providing those resolutions locally.

Performance considerations should be seen as a primary concern - so I'd like to discuss how this information asymmetry between the client and server will affect preloading workflows.

We've tackled these problems with SystemJS for some time, but instead of jumping to our solutions I want to aim to clearly explain the problems.

Edit: I've included an outline of how the SystemJS approach could be adapted to module maps.

Firstly, using the Link modulepreload header on servers with plain names like "lodash" will not be suitable when it is up to the client to determine where to resolve lodash.

Thus, any workflow using this approach will need to use client-based preloading techniques.

The cases for these are:

  1. Top-level <script type="module"> in an HTML page, varying between different HTML pages of the app.
  2. Dynamic import() statements in code.
  3. new Worker('x.js', { type: 'module' }) instantiations

All of these cases should support the ability to preload the full module tree to avoid the latency waterfall of module discovery.

The best techniques likely available in these cases will be:

  1. <link rel=modulepreload> for top-level module scripts
  2. Some kind of dynamic <link rel=modulepreload> injection for dynamic imports, using a custom JS function
  3. This same sort of dynamic <link rel=modulepreload> injection for workers.

There are problems with all these techniques:

  1. <link rel=modulepreload> information is now inlined into the HTML page itself (not a separate file). This means any resolution changes result in HTML changes, even if the manifest itself is a separate file. Also the preload information may be the same between different pages of an application but must be repeated in the HTML resulting in duplication.
  2. A dynamic JS-based module preloading method is fine, but usage might look something like: dynamicPreload('/exact/path/to/lodash.js'); dynamicPreload('/exacct/path/to/lodash-dep.js'); import('thing-that-uses-lodash');. In this scenario we have now tied the exact resolution of the dependency graph to a source in the dependency graph, which is pretty much the same sort of work needed to inline our resolutions into the module specifiers to begin with. We lose a lot of the caching benefits the package name maps provided in the first place of having resource caching independent of resolution caching. We are also duplicating a lot of tree information between dynamic imports in the app, and creating code bloat.
  3. In cases where dynamic imports import dynamic specifiers - import(variableName) - it is not possible at all to inline the preloading information into the source, possibly making these scenarios worse for performance.

So my question is - how can these techniques be improved to ensure that the workflows around package name maps can be performant without losing the benefits?

Please also let me know if any of the above arguments are unclear and I will gladly clarify.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions