endo icon indicating copy to clipboard operation
endo copied to clipboard

support dynamic requires

Open boneskull opened this issue 1 year ago • 5 comments

Description

  • This PR adds support for dynamic requires via loadFromMap() and link() (in import-lite.js and link.js, respectively). importLocation()'s signature has also been modified for support.

    To use this feature in either function, the following must be true:

    1. The moduleTransforms option (in the appropriate options parameter) must not be present. These are asynchronous module transforms, which cannot be used by dynamic require.
    2. The ReadPowers param must be a proper ReadPowers object (not just a ReadFn) and must contain both the new maybeReadSync, new isAbsolute, and fileURLToPath functions. The new type representing this is SyncReadPowers.

    If all of the above are true, then a compartment will be allowed to dynamically require something. If that thing still cannot be found in the compartment map, the sync "fallback" exit hook (see next item) will be executed.

  • The new importHookNow property can be provided via options, which is a synchronous exit module import hook.

  • About SyncReadPowers:

    • SyncReadPowers.maybeReadSync() is necessary to read files synchronously which is necessary to load them synchronously. This fails gracefully if the file is not found; the implementation is platform-dependent.
    • SyncReadPowers.isAbsolute() is necessary to determine if the module specifier of a dynamic require is absolute. If it is, it could be just about anything, and must be loaded via the user-provided importNowHook (the sync exit module import hook).
    • SyncReadPowers.fileURLToPath() is needed for __filename and __dirname, which is often used to create absolute paths for requiring dynamically.
  • As an alternative to moduleTransforms, synchronous module transforms may be provided via the new syncModuleTransforms object. In a non-dynamic-require use-case, if present, syncModuleTransforms are combined with the moduleTransforms option; all sync module transforms are module transforms, but not all module transforms are sync module transforms.

  • All builtin parsers are now synchronous. User-defined parsers can be async, but this will disable dynamic require support.

  • @endo/evasive-transform now exports evadeCensorSync() in addition to evadeCensor(). This is possible because I've swapped the async-only source-map with source-map-js, which is a fork of the former before it went async-only. source-map-js claims comparable performance.

Security Considerations

Dynamically requiring an exit module (e.g., a Node.js builtin) requires a user-defined hook, which has the same security considerations as a user-defined exit module hook.

Swapping out a dependency (source-map-js for source-map) incurs risk.

Scaling Considerations

n/a

Documentation Considerations

Should be announced as a user-facing feature

Testing Considerations

I've added some fixtures and tested around the conditionals I've added, but am open to any suggestions for additional coverage.

Compatibility Considerations

This increases ecosystem compatibility considerably; use of dynamic require in the ecosystem is not rare.

For example, most packages which ship a native module will be using dynamic require, because the filepath of the build artifact is dependent upon the platform and architecture.

Upgrade Considerations

Everything else should be backwards-compatible, as long as source-map-js does as it says on the tin.

Users of @endo/evasive-transform may note that native modules are neither downloaded/compiled (due to the switch from source-map to source-map-js).


UPDATE: Aug 18 2024

This PR now targets the boneskull/supertramp branch, which is PR #2263

boneskull avatar Jun 07 '24 20:06 boneskull