Movatterモバイル変換


[0]ホーム

URL:


Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings
This repository was archived by the owner on Jul 13, 2020. It is now read-only.

Polyfill for the ES Module Loader

License

NotificationsYou must be signed in to change notification settings

ModuleLoader/es-module-loader

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Provideslow-level hooks for creating ES module loaders, roughly based on the API of theWhatWG loader spec,but withadjustments to match the current proposals for the HTML modules specification,unspecified WhatWG changes, andNodeJS ES module adoption.

Supports theloader import and registry API with theSystem.register module format to provide exact module loading semantics for ES modules in environments today. In addition, support for theSystem.registerDynamic is provided to allow the linkingof module graphs consisting of inter-dependent ES modules and CommonJS modules with their respective semantics retained.

This project aims to provide a fast, minimal, unopinionated loader API on top of which custom loaders can easily be built.

See thespec differences section for a detailed description of some of the specification decisions made.

ES6 Module Loader Polyfill, the previous version of this project was built to theoutdated ES6 loader specification and can still be found at the0.17 branch.

Module Loader Examples

Some examples of common use case module loaders built with this project are provided below:

  • Browser ES Module Loader:A demonstration-only loader to load ES modules in the browser including support for the<script type="module"> tag as specified in HTML.

  • Node ES Module LoaderAllows loading ES modules with CommonJS interop in Node vianode-esml module/path.js in line with the current Nodeplans for implementing ES modules. Used to run the tests and benchmarks in this project.

  • System Register Loader:A fast optimized production loader that only loadsSystem.register modules, recreating ES module semantics with CSP support.

Installation

npm install es-module-loader --save-dev

Creating a Loader

This project exposes a public API of ES modules in thecore folder.

The minimalpolyfill loader API is provided incore/loader-polyfill.js. On top of this main API file iscore/register-loader.js which provides a base loader class with the non-specSystem.register andSystem.registerDynamic support to enable the exactlinking semantics.

Helper functions are available incore/resolve.js andcore/common.js. Everything that is exported can be consideredpart of the publicly versioned API of this project.

Any tool can be used to build the loader distribution file from these core modules -Rollup is used to do these builds in the example loaders above, provided by therollup.config.js file in the example loader repos listed above.

Base Loader Polyfill API

TheLoader andModuleNamespace classes incore/loader-polyfill.js provide the basic spec API method shells for a loader instanceloader:

  • new Loader(): Instantiate a newloader instance.Defaults to environment baseURI detection in NodeJS and browsers.
  • loader.import(key [, parentKey]): Promise for importing and executing a given module, returning its module instance.
  • loader.resolve(key [, parentKey]): Promise for resolving the idempotent fully-normalized string key for a module.
  • new ModuleNamespace(bindings): Creates a new module namespace object instance for the given bindings object. The iterable propertiesof the bindings object are created as getters returning the corresponding values from the bindings object.
  • loader.registry.set(resolvedKey, namespace): Set a module namespace into the registry.
  • loader.registry.get(resolvedKey): Get a module namespace (if any) from the registry.
  • loader.registry.has(resolvedKey): Boolean indicating whether the given key is present in the registry.
  • loader.registry.delete(resolvedKey): Removes the given module from the registry (if any), returning true or false.
  • loader.registry.keys(): Function returning the keys iterator for the registry.
  • loader.registry.values(): Function returning the values iterator for the registry.
  • loader.registry.entries(): Function returning the entries iterator for the registry (keys and values).
  • loader.registry[Symbol.iterator]: In supported environments, provides registry entries iteration.

Example of using the base loader API:

import{Loader,ModuleNamespace}from'es-module-loader/core/loader-polyfill.js';letloader=newLoader();// override the resolve hookloader[Loader.resolve]=function(key,parent){// intercept the load of "x"if(key==='x'){this.registry.set('x',newModuleNamespace({some:'exports'}));returnkey;}returnLoader.prototype[Loader.resolve](key,parent);};loader.import('x').then(function(m){console.log(m.some);});

RegisterLoader Hooks

Instead of just hooking modules within the resolve hook, theRegisterLoader base class provides an instantiate hookto separate execution from resolution and enable spec linking semantics.

Implementing a loader on top of theRegisterLoader base class involves extending that class and providing theseresolve andinstantiate prototype hook methods:

importRegisterLoaderfrom'es-module-loader/core/register-loader.js';import{ModuleNamespace}from'es-module-loader/core/loader-polyfill.js';classMyCustomLoaderextendsRegisterLoader{/*   * Constructor   * Purely for completeness in this example   */constructor(baseKey){super(baseKey);}/*   * Default resolve hook   *   * The default parent resolution matches the HTML spec module resolution   * So super[RegisterLoader.resolve](key, parentKey) will return:   *  - undefined if "key" is a plain names (eg 'lodash')   *  - URL resolution if "key" is a relative URL (eg './x' will resolve to parentKey as a URL, or the baseURI)   *   * So relativeResolved becomes either a fully normalized URL or a plain name (|| key) in this example   */[RegisterLoader.resolve](key,parentKey){varrelativeResolved=super[RegisterLoader.resolve](key,parentKey,metadata)||key;returnrelativeResolved;}/*   * Default instantiate hook   *   * This is one form of instantiate which is to return a ModuleNamespace directly   * This will result in every module supporting:   *   *   import { moduleName } from 'my-module-name';   *   assert(moduleName === 'my-module-name');   */[RegisterLoader.instantiate](key){returnnewModuleNamespace({moduleName:key});}}

The return value ofresolve is the final key that is set in the registry.

The default normalization provided (super[RegisterLoader.resolve] above) follows the same approach as the HTML specification for module resolution, wherebyplain module names that are not valid URLs, and not starting with./,../ or/ returnundefined.

So for examplelodash will returnundefined, while./x will resolve to[baseURI]/x. In NodeJS afile:/// URL is used for the baseURI.

Instantiate Hook

Using these three types of return values for theRegisterLoader instantiate hook,we can recreate ES module semantics interacting with legacy module formats:

1. Instantiating Dynamic Modules via ModuleNamespace

If the exact module definition is already known, or loaded through another method (like calling out fully to the Node require in the node-es-module-loader),then the direct module namespace value can be returned from instantiate:

import{ModuleNamespace}from'es-module-loader/core/loader-polyfill.js';// ...instantiate(key){varmodule=customModuleLoad(key);returnnewModuleNamespace({default:module,customExport:'value'});}
2. Instantiating ES Modules via System.register

When instantiate returnsundefined, it is assumed that the module key has already been registered through aloader.register(key, deps, declare) call, following the System.register module format.

For example:

[RegisterLoader.instantate](key){// System.registerthis.register(key,['./dep'],function(_export){// ...});}

When using the anonymous form of System.register -loader.register(deps, declare), in order to knowthe context in which it was called, it is necessary to call theprocessAnonRegister method passed to instantiate:

[RegisterLoader.instantiate](key,processAnonRegister){// System.registerthis.register(deps,declare);processAnonRegister();}

The loader can then match the anonymousSystem.register call to correct module in the registry. This is used to support<script> loading.

System.register is not designed to be a handwritten module format, and would usually generated from a Babel or TypeScript conversion into the "system"module format.

3. Instantiating Legacy Modules via System.registerDynamic

This is identical to theSystem.register process above, only runningloader.registerDynamic instead ofloader.register:

[RegisterLoader.instantiate](key,processAnonRegister){// System.registerDynamic CommonJS wrapper formatthis.registerDynamic(['dep'],true,function(require,exports,module){module.exports=require('dep').y;});processAnonRegister();}

For more information on theSystem.registerDynamic formatsee the format explanation.

Performance

Some simple benchmarks loading System.register modules are provided in thebench folder:

Each test operation includes a new loader class instantiation,System.register declarations, binding setup for ES module trees, loading and execution.

Sample results:

TestES Module Loader 1.3
Importing multiple trees at the same time654 ops/sec
Importing a deep tree of modules4,162 ops/sec
Importing a single module with deps8,817 ops/sec
Importing a single module without deps16,536 ops/sec

Tracing API

Whenloader.trace = true is set,loader.loads provides a simple tracing API.

Also not in the spec, this allows useful tooling to build on top of the loader.

loader.loads is keyed by the module ID, with each record of the form:

{key,// String, keydeps,// Array, unnormalized dependenciesdepMap,// Object, mapping unnormalized dependencies to normalized dependenciesmetadata// Object, exactly as from normalize and instantiate hooks}

Spec Differences

The loader API incore/loader-polyfill.js matches the API of the currentWhatWG loader specification as closely as possible, whilemaking a best-effort implementation of the upcoming loader simplification changes as descibred inwhatwg/loader#147.

  • Default normalization and error handling is implemented as in the HTML specification for module loading. Default normalization follows the HTML specification treatment of module keys as URLs, with plain names ignored by default (effectively erroring unless altering this behaviour through the hooks). Errors are cached in the registry, until thedelete API method is called for the module that has errored. Resolve and fetch errors throw during the tree instantiation phase, while evaluation errors throw during the evaluation phase, and this is true for cached errors as well in line with the spec -whatwg/html#2595.
  • A directModuleNamespace constructor is provided over theModule mutator proposal in the WhatWG specification.Instead of storing a registry ofModule.Status objects, we then store a registry of Module Namespace objects. The reason for this is that asynchronous rejection of registry entries as a source of truth leads to partial inconsistent rejection states(it is possible for the tick between the rejection of one load and its parent to have to deal with an overlapping in-progress tree),so in order to have a predictable load error rejection process, loads are only stored in the registry as fully-linked Namespace objectsand not ModuleStatus objects as promises for Namespace objects. The custom privateModuleNamespace constructor is used over theModule.Status proposal to ensure a stable API instead of tracking in-progress specification work.
  • Linking between module formats does not usezebra striping anymore, but rather relies on linking the whole graph in deterministic order for each module format down the tree as is planned for NodeJS. This is made possible by thedynamic modules TC39 proposal which allows the export named bindings to only be determined at evaluation time for CommonJS modules. We do not currently provide tracking of circular references across module format boundaries so these will hang indefinitely like writing an infinite loop.
  • Loader is available as a named export fromcore/loader-polyfill.js but is not by default exported to theglobal.Reflect object.This is to allow individual loader implementations to determine their own impact on the environment.
  • A constructor argument is added to the loader that takes the environmentbaseKey to be used as the default normalization parent.
  • TheRegisterLoader splits up theresolve hook intoresolve andinstantiate. TheWhatWG reduced specification proposal to remove the loader hooks implies having a singleresolve hook by having the module set into the registry using theregistry.set API as a side-effect of resolution to allow custom interception as in the first loader example above. As discussed inwhatwg/loader#147 (comment), this may cause unwanted execution of modules when only resolution is needed vialoader.resolve calls, so the approach taken in theRegisterLoader is to implement separateresolve andinstantiate hooks.

License

Licensed under the MIT license.

About

Polyfill for the ES Module Loader

Resources

License

Stars

Watchers

Forks

Packages

No packages published

Contributors43


[8]ページ先頭

©2009-2025 Movatter.jp