Giter VIP home page Giter VIP logo

es-module-shims's Introduction

ES Module Shims

Polyfills import maps and other ES Modules features on top of the baseline native ESM support in browsers.

With import maps now supported by all major browsers, ES Module Shims entirely bypasses processing for the 91% of users with native import maps support.

For the remaining users, the highly performant (see benchmarks) production and CSP-compatible shim kicks in to rewrite module specifiers driven by the Web Assembly ES Module Lexer.

The following modules features are polyfilled:

When running in shim mode, module rewriting is applied for all users and custom resolve and fetch hooks can be implemented allowing for custom resolution and streaming in-browser transform workflows.

Because we are still using the native module loader the edge cases work out comprehensively, including:

  • Live bindings in ES modules
  • Dynamic import expressions (import('src/' + varname'))
  • Circular references, with the execption that live bindings are disabled for the first unexecuted circular parent.

Built with Chomp

Usage

Include ES Module Shims with a async attribute on the script, then include an import map and module scripts normally:

<script async src="https://ga.jspm.io/npm:[email protected]/dist/es-module-shims.js"></script>

<!-- https://generator.jspm.io/#U2NhYGBkDM0rySzJSU1hKEpNTC5xMLTQM9Az0C1K1jMAAKFS5w0gAA -->
<script type="importmap">
{
  "imports": {
    "react": "https://ga.jspm.io/npm:[email protected]/index.js"
  },
  "scopes": {
    "https://ga.jspm.io/npm:[email protected]/": {
      "object-assign": "https://ga.jspm.io/npm:[email protected]/index.js"
    }
  }
}
</script>

<script type="module">
  import react from 'react';
  console.log(react);
</script>

Polyfill Explainer

When running the previous example in a browser without import maps support, the browser will output the following console error:

Uncaught TypeError: Failed to resolve module specifier "react". Relative references must start with either "/", "./", or "../".
  at <anonymous>:1:15

This error is important - it means that the native browser loader didn't execute any of the code at all because this error happens at link time, and before execution time. And this is what allows the polyfill to be able to reexecute the modules and their dependencies without risk of duplicate execution.

The ES Module Shims polyfill will analyze the browser to see if it supports import maps. If it does, it doesn't do anything more, otherwise it will analyze all module scripts on the page to see if any of them have bare specifier imports that will fail like this. If one is found, it will then be reexecuted through ES Module Shims using its internal shimming of modules features.

When the polyfill kicks in another console log message is output(which can be disabled or customized via the polyfill hook):

^^ Module error above is polyfilled and can be ignored ^^

Polyfill Edge Case: Dynamic Import

Only static link-time errors are polyfilled, not runtime errors.

Module feature errors that are not static errors but rather runtime errors will bypass the polyfill detection.

For example:

<script type="module">
  import './supported-relative-import.js';
  console.log('Static Ok');
  import('unsupported-import-map').then(x => {
    console.log('Dynamic Ok');
  }, err => {
    console.log('Dynamic Fail');
  });
</script>

In the above, the native browser loader without import maps support will execute the above module, but fail the dynamic import.

See the log output in various scenarios:

  • Native with Import Maps: Static Ok, Dynamic Ok
  • Native without Import Maps: Static Ok, Dynamic Fail
  • Native without Import Maps running ES Module Shims: Static Ok, Dynamic Fail

ES Module Shims does not polyfill the dynamic import. The reason for this is that if it did, it would need to reexecute the entire outer module resulting in Static Ok, Dynamic Fail, Static Ok, Dynamic Ok. This double execution would be a change of the normal execution in running code twice that would not be deemed suitable for calling it a polyfill.

This is why it is advisable to always ensure modules use a bare specifier early to avoid non-execution.

If a static failure is not possible and dynamic import must be used, one alternative is to use the importShim ES Module Shims top-level loader:

<script type="module">
  import './supported-relative-import.js';
  console.log('Static Ok');
  importShim('unsupported-import-map').then(x => {
    console.log('Ok');
  }, err => {
    console.log('Fail');
  });
</script>

importShim will automatically pass-through to the native dynamic import or polyfill as necessary, just like it does for script tags.

Polyfill Edge Case: Instance Sharing

When running in polyfill mode, it can be thought of that are effectively two loaders running on the page - the ES Module Shims polyfill loader, and the native loader.

Note that instances are not shared between these loaders for consistency and performance, since some browsers do not properly share the fetch cache and native loader cache resulting in a double fetch which would be inefficient.

As a result, if you have two module graphs - one native and one polyfilled, they will not share the same dependency instance, for example:

<script type="importmap">
{
  "imports": {
    "dep": "/dep.js"
  }
}
</script>
<script type="module">
import '/dep.js';
</script>
<script type="module">
import 'dep';
</script>
console.log('DEP');

When polyfilling import maps, ES Module Shims will pick up on the second import failure and reexecute /dep.js as a new instance, logging "DEP" twice.

For this reason it is important to always ensure all modules hit the polyfill path, either by having all graphs use import maps at the top-level, or via importShim directly.

If you really need to support instance sharing with the native loader, a useful workaround is to use the skip option to list modules which should always be loaded via the native loader:

<script type="esms-options">
{
  "skip": ["/dep.js"]
}
</script>

The above would then fully cause dependency module instance to be shared between ES Module Shims and the native loader, with the polyfill then logging "DEP" only once.

No Shim Scripts

If the polyfill is analyzing or applying to a module script that doesn't need to or shouldn't be polyfilled, adding the "noshim" attribute to the script tag will ensure that ES Module Shims ignores processing this script entirely:

<script type="module" noshim>
  // ...
</script>

Polyfill Features

If using more modern features like CSS Modules or JSON Modules, these need to be manually enabled via the polyfillEnable init option to raise the native baseline from just checking import maps to also checking that browsers support these features:

<script>
window.esmsInitOptions = { polyfillEnable: ['css-modules', 'json-modules', 'wasm-modules'] }
</script>

To verify when the polyfill is actively engaging as opposed to relying on the native loader, a polyfill hook is also provided.

Shim Mode

Shim mode is an alternative to polyfill mode and doesn't rely on native modules erroring - instead it is triggered by the existence of any <script type="importmap-shim"> or <script type="module-shim">, or when explicitly setting the shimMode init option.

In shim mode, only the above importmap-shim and module-shim tags will be parsed and executed by ES Module Shims.

Shim mode also provides some additional features that aren't yet natively supported such as supporting multiple import maps, external import maps with a "src" attribute, dynamically injecting import maps, and reading current import map state, which can be useful in certain applications.

Benchmarks

ES Module Shims is designed for production performance. A comprehensive benchmark suite tracks multiple loading scenarios for the project.

Benchmark summary:

  • ES Module Shims Chrome Passthrough (for 72% of users) results in ~5ms extra initialization time over native for ES Module Shims fetching, execution and initialization, and on a slow connection the additional non-blocking bandwidth cost of its 10KB compressed download as expected.
  • ES Module Shims Polyfilling (for the remaining 28% of users) is on average 1.4x - 1.5x slower than native module loading, and up to 1.8x slower on slow networks (most likely due to the browser preloader), both for cached and uncached loads, and this result scales linearly up to 10MB and 20k modules loaded executing on the fastest connection in just over 2 seconds in Firefox.
  • Very large import maps (100s of entries) cost only a few extra milliseconds upfront for the additional loading cost.

Features

Browser Support

Works in all browsers with baseline ES module support.

Browser Compatibility on baseline ES modules support with ES Module Shims:

ES Modules Features Chrome (71+) Firefox (60+) Safari (10.1+)
modulepreload ✔️ ✔️ ✔️
Import Maps ✔️ ✔️ ✔️
Import Map Integrity ✔️ ✔️ ✔️
JSON Modules ✔️ ✔️ ✔️
CSS Modules ✔️ ✔️ ✔️
Wasm Modules 89+ 89+ 15+

Browser compatibility without ES Module Shims:

ES Modules Features Chrome Firefox Safari
modulepreload 66+ 115+ 17.5+
import.meta.url ~76+ ~67+ ~12+
Import Maps 89+ 108+ 16.4+
Import Map Integrity Pending
JSON Modules 123+ 17.2+
CSS Modules 123+
Wasm Modules
import.meta.resolve 105+ 106+ 16.4+
Module Workers ~68+ ~113+ 15+
Top-Level Await 89+ 89+ 15+

Import Maps

Stability: WhatWG Standard, implemented in all browsers although only recently in Firefox and Safari

Import maps allow for importing "bare specifiers" in JavaScript modules, which prior to import maps throw in all browsers with native modules support.

Using this polyfill we can write:

<script type="importmap-shim">
{
  "imports": {
    "test": "/test.js"
  },
  "scopes": {
    "/": {
      "test-dep": "/test-dep.js"
    }
  },
  "integrity": {
    "/test.js": "sha386-..."
  }
}
</script>
<script type="module-shim">
  import test from "test";
  console.log(test);
</script>

All modules are still loaded with the native browser module loader, but with their specifiers rewritten then executed as Blob URLs, so there is a relatively minimal overhead to using a polyfill approach like this.

Multiple Import Maps

Multiple import maps are not currently supported in any native implementation, Chromium support is currently being tracked in https://bugs.chromium.org/p/chromium/issues/detail?id=927119.

In polyfill mode, multiple import maps are therefore not supported.

In shim mode, support for multiple importmap-shim scripts follows the import map extensions proposal.

External Import Maps

External import maps (using a "src" attribute) are not currently supported in any native implementation.

In polyfill mode, external import maps are therefore not supported.

In shim mode, external import maps are fully supported.

Dynamic Import Maps

Support for dynamically injecting import maps with JavaScript via eg:

document.body.appendChild(Object.assign(document.createElement('script'), {
  type: 'importmap',
  innerHTML: JSON.stringify({ imports: { x: './y.js' } }),
}));

is supported in Chromium, provided it is injected before any module loads and there is no other import map yet loaded (multiple import maps are not supported).

Both modes in ES Module Shims support dynamic injection using DOM Mutation Observers.

While in polyfill mode the same restrictions apply that multiple import maps, import maps with a src attribute, and import maps loaded after the first module load are not supported, in shim mode all of these behaviours are fully enabled for "importmap-shim".

Reading current import map state

To make it easy to keep track of import map state, es-module-shims provides a importShim.getImportMap utility function, available only in shim mode.

const importMap = importShim.getImportMap();

// importMap will be an object in the same shape as the json in a importmap script

Setting current import map state

To make it easy to set the import map state, es-module-shims provides a importShim.addImportMap utility function, available only in shim mode.

// importMap will be an object in the same shape as the json in a importmap script
const importMap = { imports: {/*...*/}, scopes: {/*...*/} };

importShim.addImportMap(importMap);

Shim Import

Dynamic import(...) within any modules loaded will be rewritten as importShim(...) automatically providing full support for all es-module-shims features through dynamic import.

To load code dynamically (say from the browser console), importShim can be called similarly:

importShim('/path/to/module.js').then(x => console.log(x));

import.meta.url

Stability: Stable browser standard

import.meta.url provides the full URL of the current module within the context of the module execution.

modulepreload

Stability: WhatWG Standard, Single Browser Implementer

Preloading of modules can be achieved by including a <link rel="modulepreload" href="/module.js" /> tag in the HTML or injecting it dynamically.

This tag also supports the "integrity", "crossorigin" and "referrerpolicy" attributes as supported on module scripts.

This tag just initiates a fetch request in the browser and thus works equally as a preload polyfill in both shimmed and unshimmed modes, with integrity validation support.

In browsers that don't support modulepreload, polyfilled preloading behaviour is provided using an early fetch() call with the same request options as the module script, resulting in network-level cache sharing.

Unlike the browser specification, the modulepreload polyfill does not request dependency modules by default, in order to avoid unnecessary code analysis in the polyfill scenarios, it is always recommended to preload deep imports so that this feature shouldn't be necessary.

Preload shim

When in shim mode, <link rel="modulepreload-shim" href="/module.js" /> must be used to properly cache the preloaded modules.

CSP Support

By default ES Module Shims provides full support for CSP by using the asm.js ES Module Lexer build. This is absolutely identical in performance to the Wasm version in Firefox and Chrome (in Safari the asm.js version is actually faster than Wasm).

The CSP nonce to use for module scripts will be picked up from the first script on the page or via the nonce init option.

A full example of such a CSP workflow is provided below:

<meta http-equiv="Content-Security-Policy" content="script-src 'self' 'nonce-n0nce'" />
<script async src="es-module-shims.js"></script>
<script type="importmap" nonce="n0nce">
{
  "imports": {
    "pkg": "/pkg.js"
  }
}
</script>
<script type="module" nonce="n0nce">
import pkg from 'pkg';
</script>

Wasm Build

To use the Web Assembly / non-CSP build of ES Module Shims, this is available as a self-contained single file at es-module-shims/wasm or es-module-shims/dist/es-module-shims.wasm.js in the package folder.

Import Map Integrity

The "integrity" field for import maps is supported when possible, throwing an error in es-module-shims when the integrity does not match the expected value:

<script type="importmap">
{
  "imports": {
    "pkg": "/pkg.js"
  },
  "integrity": {
    "/pkg.js": "sha384-..."
  }
}
</script>
<script>
import "/pkg.js";
</script>

Note integrity can only be validated when in shim mode or when the polyfill is definitely engaging.

JSON Modules

Stability: WhatWG Standard, Single Browser Implementer

In shim mode, JSON modules are always supported. In polyfill mode, JSON modules require the polyfillEnable: ['json-modules'] init option.

JSON Modules are currently supported in Chrome when using them via an import assertion:

<script type="module">
import json from 'https://site.com/data.json' with { type: 'json' };
</script>

In addition JSON modules need to be served with a valid JSON content type.

CSS Modules

Stability: WhatWG Standard, Single Browser Implementer

In shim mode, CSS modules are always supported. In polyfill mode, CSS modules require the polyfillEnable: ['css-modules'] init option.

CSS Modules are currently supported in Chrome when using them via an import assertion:

<script type="module">
import sheet from 'https://site.com/sheet.css' with { type: 'css' };
</script>

In addition CSS modules need to be served with a valid CSS content type.

Wasm Modules

Stability: WebAssembly Standard, Unimplemented

Implements the WebAssembly ESM Integration spec, including support for source phase imports.

In shim mode, Wasm modules are always supported. In polyfill mode, Wasm modules require the polyfillEnable: ['wasm-modules'] init option.

WebAssembly module exports are made available as module exports and WebAssembly module imports will be resolved using the browser module loader.

When using the source phase import form, this must be enabled separately via the polyfillEnable: ['wasm-modules', 'source-phase'] init option to support source imports to WebAssembly modules.

When enabling 'source-phase', WebAssembly.Module is also polyfilled to extend from AbstractModuleSource per the source phase proposal.

WebAssembly modules require native top-level await support to be polyfilled, see the compatibility table above.

<script type="module">
import { fn } from './app.wasm';
</script>

And for the source phase:

<script type="module">
import source mod from './app.wasm';
const instance = await WebAssembly.instantiate(mod, { /* ...imports */ });
</script>

If using CSP, make sure to add 'unsafe-wasm-eval' to script-src which is needed when the shim or polyfill engages, note this policy is much much safer than eval due to the Wasm secure sandbox. See https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Security-Policy/script-src#unsafe_webassembly_execution.

Module Workers

ES Module Shims can be used in module workers in browsers that provide dynamic import in worker environments, which at the moment are Chrome(80+), Edge(80+), Firefox(~113+) and Safari(15+).

By default, when there is no DOM present, ES Module Shims will switch into shim mode. An example of ES Module Shims usage through shim mode in web workers is provided below:

/**
 * 
 * @param {string} aURL a string representing the URL of the module script the worker will execute.
 * @returns {string} The string representing the URL of the script the worker will execute.
 */
function getWorkerScriptURL(aURL) {
  // baseURL, esModuleShimsURL are considered to be known in advance
  // esModuleShimsURL - must point to the non-CSP build of ES Module Shims, 
  // namely the `es-module-shim.wasm.js` output: es-module-shims/dist/es-module-shims.wasm.js

  return URL.createObjectURL(new Blob(
    [
      `importScripts('${new URL(esModuleShimsURL, baseURL).href}');
      importShim.addImportMap(${JSON.stringify(importShim.getImportMap())});
      importShim('${new URL(aURL, baseURL).href}').catch(e => setTimeout(() => { throw e; }))`
    ],
    { type: 'application/javascript' }))
}

const worker = new Worker(getWorkerScriptURL('myEsModule.js'));

Web workers must use the non-CSP build of ES Module Shims via es-module-shim.wasm.js from the dist/ folder, since the CSP build currently assumes a DOM.

Init Options

Provide a esmsInitOptions on the global scope before es-module-shims is loaded to configure various aspects of the module loading process:

<script>
window.esmsInitOptions = {
  // Enable Shim Mode
  shimMode: true, // default false
  // Enable newer modules features
  polyfillEnable: ['css-modules', 'json-modules'], // default empty
  // Custom CSP nonce
  nonce: 'n0nce', // default is automatic detection
  // Don't retrigger load events on module scripts (DOMContentLoaded, domready)
  noLoadEventRetriggers: true, // default false
  // Retrigger window 'load' event (will be combined into load event above on next major)
  globalLoadEventRetrigger: true, // default false
  // Skip source analysis of certain URLs for full native passthrough
  skip: /^https:\/\/cdn\.com/, // defaults to null
  // Clean up blob URLs after execution
  revokeBlobURLs: true, // default false
  // Secure mode to not support loading modules without integrity
  // (integrity is always verified even when disabled though)
  enforceIntegrity: true, // default false
  // Permit overrides to import maps
  mapOverrides: true, // default false

  // -- Hooks --
  // Module load error
  onerror: (e) => { /*...*/ }, // default noop
  // Called when polyfill mode first engages
  onpolyfill: () => {}, // default logs to the console
  // Hook all module resolutions
  resolve: (id, parentUrl, resolve) => resolve(id, parentUrl), // default is spec resolution
  // Hook source fetch function
  fetch: (url, options) => fetch(url, options), // default is native
  // Hook import.meta construction
  meta: (meta, url) => void // default is noop
  // Hook top-level imports
  onimport: (url, options, parentUrl) => void // default is noop
}
</script>
<script async src="es-module-shims.js"></script>

<script type="esms-options"> can also be used:

<script type="esms-options">
{
  "shimMode": true,
  "polyfillEnable": ["css-modules", "json-modules"],
  "nonce": "n0nce",
  "onpolyfill": "polyfill"
}
</script>

This can be convenient when using a CSP policy. Function strings correspond to global function names.

Shim Mode Option

Shim Mode can be overridden using the shimMode option:

<script type="esms-options">
{
  "shimMode": true
}
</script>

For example, if lazy loading <script type="module-shim"> scripts alongside static native module scripts, shim mode would not be enabled at initialization time.

DOM load events are fired for all "module-shim" scripts both for success and failure just like for native module scripts.

Pollyfill Enable Option

The polyfillEnable option allows enabling polyfill features which are newer and would otherwise result in unnecessary polyfilling in modern browsers that haven't yet updated.

This options supports "css-modules", "json-modules", "wasm-modules", "source-phase".

<script type="esms-options">
{
  "polyfillEnable": ["css-modules", "json-modules"]
}
</script>

The above is necessary to enable CSS modules and JSON modules.

Baseline Support Analysis Opt-Out

The reason the polyfillEnable option is needed is because ES Module Shims implements a performance optimization where if a browser supports modern modules features to an expected baseline of import maps support, it will skip all polyfill source analysis resulting in full native passthrough performance.

If the application code then tries to use modern features like CSS modules beyond this baseline it won't support those features. As a result all modules features which are considered newer or beyond the recommended baseline require explicit enabling. This common baseline itself will change to track the common future modules baseline supported by this project for each release cycle.

This option can also be set to true to entirely disable the native passthrough system and ensure all sources are fetched and analyzed through ES Module Shims. This will still avoid duplicate execution since module graphs are still only reexecuted when they use unsupported native features, but there is a small extra cost in doing the analysis.

Enforce Integrity

While integrity is always verified and validated when available, when enabled, enforceIntegrity will ensure that all modules must have integrity defined when loaded through ES Module Shims either on a <link rel="modulepreload" integrity="...">, a <link rel="modulepreload-shim" integrity="..."> preload tag in shim mode, or the "integrity" field in the import map. Modules without integrity will throw at fetch time.

For example in the following, only the listed app.js, dep.js and another.js modules will be able to execute with the provided integrity:

<script type="importmap">
{
  "integrity": {
    "/another.js": "sha384-..."
  }
}
</script>
<script type="esms-options">{ "enforceIntegrity": true }</script>
<link rel="modulepreload-shim" href="/app.js" integrity="sha384-..." />\
<link rel="modulepreload-shim" href="/dep.js" integrity="sha384-..." />
<script type="module-shim">
  import '/app.js';
  import '/another.js';
</script>

Strong execution guarantees are only possible in shim mode since in polyfill mode it is not possible to stop the native loader from executing code without an integrity.

Future versions of this option may provide support for origin-specific allow lists.

Nonce

The nonce option allows setting a CSP nonce to be used with all script injections for full CSP compatibility supported by the CSP build of ES Module Shims.

Alternatively, add a blob: URL policy with the CSP build to get CSP compatibility.

<script type="esms-options">
{
  "nonce": "n0nce"
}
</script>

No Load Event Retriggers

Because of the extra processing done by ES Module Shims it is possible for static module scripts to execute after the DOMContentLoaded or readystatechange events they expect, which can cause missed attachment.

In addition, script elements will also have their load events refired when polyfilled.

In order to ensure libraries that rely on these event still behave correctly, ES Module Shims will always double trigger these events that would normally have executed before the document ready state transition to completion, once all the static module scripts in the page have been completely executed through ES module shims.

In such a case, this double event firing can be disabled with the noLoadEventRetriggers option:

<script type="esms-options">
{
  // do not re-trigger DOM events (onreadystatechange, DOMContentLoaded)
  "noLoadEventRetriggers": true
}
</script>
<script async src="es-module-shims.js"></script>

Global Load Event Retrigger

In ES Module Shims 1.x, load event retriggers only apply to DOMContentLoaded and readystatechange and not to the window load event. To enable the window / worker 'load' event, set globalLoadEventRetrigger: true.

In the next major version, this will be the default for load events, at which point only noLoadEventRetriggers will remain.

Skip

When loading modules that you know will only use baseline modules features, it is possible to set a rule to explicitly opt-out modules from being polyfilled to always load and be referenced through the native loader only. This enables instance sharing with the native loader and also improves performance because those modules then do not need to be processed or transformed at all, so that only local application code is handled and not library code.

The skip option supports a string regular expression or array of exact module URLs to check:

<script type="esms-options">
{
  "skip": "^https?:\/\/(cdn\.skypack\.dev|jspm\.dev)\/"
}
</script>
<script async src="es-module-shims.js"></script>

When passing an array, relative URLs or paths ending in / can be provided:

<script type="esms-options">
{
  "skip": ["./app.js", "https://jspm.dev/"]
}
</script>
<script async src="es-module-shims.js"></script>

Revoke Blob URLs

When polyfilling the missing features es-module-shims would create in-memory blobs using URL.createObjectURL() for each processed module. In most cases, memory footprint of these blobs is negligible so there is no need to call URL.revokeObjectURL() for them, and we don't do that by default.

That said, in some scenarios, e.g. when evaluating some continuously changing modules without a page reload, like in a web-based code editor, you might want to reduce the growth of memory usage by revoking those blob URLs after they were already imported.

You can do that by enabling the revokeBlobURLs init option:

<script type="esms-options">
{
  "revokeBlobURLs": true
}
</script>
<script type="module" src="es-module-shims.js"></script>

NOTE: revoking object URLs is not entirely free, while we are trying to be smart about it and make sure it doesn't cause janks, we recommend enabling this option only if you have done the measurements and identified that you really need it.

Overriding import map entries

When dynamically injecting import maps, an error will be thrown in both polyfill and shim modes if the new import map would override existing entries with a different value.

It is possible to disable this behavior in shim mode by setting the mapOverrides option:

<script type="esms-options">
{
  "shimMode": true,
  "mapOverrides": true
}
</script>
<script type="importmap-shim">
{
  "imports": {
    "x": "/x.js"
  }
}
</script>
<script>
// No error will be thrown here
document.body.appendChild(Object.assign(document.createElement('script'), {
  type: 'importmap',
  innerHTML: JSON.stringify({ imports: { x: './y.js' } }),
}));
</script>

This can be useful for HMR workflows.

Hooks

Polyfill hook

The polyfill hook is called when running in polyfill mode and the polyfill is kicking in instead of passing through to the native loader.

This can be a useful way to verify that the native passthrough is working correctly in latest browsers for performance, while also allowing eg the ability to analyze or get metrics reports of how many users are getting the polyfill actively applying to their browser application loads.

<script>
window.polyfilling = () => console.log('The polyfill is actively applying');
</script>
<script type="esms-options">
{
  "onpolyfill": "polyfilling"
}
</script>

The default hook will log a message to the console with console.info noting that polyfill mode is enabled and that the native error can be ignored.

Overriding this hook with an empty function will disable the default polyfill log output.

In the above, running in latest Chromium browsers, nothing will be logged, while running in an older browser that does not support newer features like import maps the console log will be output.

Error hook

You can provide a function to handle errors during the module loading process by providing an onerror option:

<script>
  window.esmsInitOptions = {
    onerror: error => console.log(error) // defaults to `((e) => { throw e; })`
  }
</script>
<script async src="es-module-shims.js"></script>

Import Hook

The import hook is supported for both shim and polyfill modes and provides an async hook which can ensure any necessary work is done before a top-level module import or dynamic import() starts further processing.

<script>
  window.esmsInitOptions = {
    onimport: function (url, options, parentUrl) {
      console.log(`Top-level import for ${url}`);
    }
  }
</script>

Resolve Hook

The resolve hook is supported for both shim and polyfill modes and allows full customization of the resolver, while still having access to the original resolve function.

Note that in polyfill mode the resolve hook may not be called for all modules when native passthrough is occurring and that it still will not affect the native passthrough executions.

If the resolve hook should apply for all modules in the entire module graph, make sure to set polyfillEnable: true to disable the baseline support analysis opt-out.

<script>
  window.esmsInitOptions = {
    shimMode: true,
    resolve: function (id, parentUrl, defaultResolve) {
      if (id === 'custom' && parentUrl.startsWith('https://custom.com/'))
        return 'https://custom.com/custom.js';

      // Default resolve will handle the typical URL and import map resolution
      return defaultResolve(id, parentUrl);
    }
  }
</script>

Support for an asynchronous resolve hook has been deprecated as of 1.5.0 and will be removed in the next major.

Instead async work should be done with the import hook.

Meta Hook

The meta hook allows customizing the import.meta object in each module scope.

The function takes as arguments the import.meta object itself (with import.meta.url an import.meta.resolve already present), and the URL of the module as its second argument.

Example:

<script>
  window.esmsInitOptions = {
    shimMode: true,
    meta: function (metaObj, url) {
      metaObj.custom = `custom value for ${url}`;
    }
  }
</script>

Where within the module the following would be supported:

import assert from 'assert';
assert.ok(import.meta.custom.startsWith('custom value'));

Fetch Hook

The fetch hook is supported for shim mode only.

The ES Module Shims fetch hook can be used to implement transform plugins.

For example TypeScript support:

<script>
  window.esmsInitOptions = {
    shimMode: true,
    fetch: async function (url, options) {
      const res = await fetch(url, options);
      if (!res.ok)
        return res;
      if (res.url.endsWith('.ts')) {
        const source = await res.body();
        const transformed = tsCompile(source);
        return new Response(new Blob([transformed], { type: 'application/javascript' }));
      }
      return res;
    } // defaults to `((url, options) => fetch(url, options))`
  }
</script>
<script async src="es-module-shims.js"></script>

Because the dependency analysis applies by ES Module Shims takes care of ensuring all dependencies run through the same fetch hook, the above is all that is needed to implement custom plugins.

Streaming support is also provided, for example here is a hook with streaming support for JSON:

window.esmsInitOptions = {
  shimMode: true,
  fetch: async function (url, options) {
    const res = await fetch(url, options);
    if (!res.ok || !/^application\/json($|;)/.test(res.headers.get('content-type')))
      return res;
    const reader = res.body.getReader();
    const headers = new Headers(res.headers);
    headers.set('Content-Type', 'application/javascript');
    return new Response(new ReadableStream({
      async start (controller) {
        let done, value;
        controller.enqueue(new TextEncoder().encode('export default '));
        while (({ done, value } = await reader.read()) && !done) {
          controller.enqueue(value);
        }
        controller.close();
      }
    }), { headers });
  }
}

Implementation Details

Import Rewriting

  • Sources are fetched, import specifiers are rewritten to reference exact URLs, and then executed as BlobURLs through the whole module graph.
  • The lexer handles the full language grammar including nested template strings, comments, regexes and division operator ambiguity based on backtracking.
  • When executing a circular reference A -> B -> A, a shell module technique is used to acyclify into the graph A -> B -> A Shell, with A -> A Shell. The shell module exports an update function which is called by the original once after the last import statement, and again after the last statement of the source file.

Inspiration

Huge thanks to Rich Harris for inspiring this approach with Shimport.

License

MIT

es-module-shims's People

Contributors

aui avatar costingeana avatar ffortier avatar guybedford avatar heypiotr avatar hypercubed avatar iainbeeston avatar icodemyownlife avatar ifiokjr avatar ilteoood avatar just-dont avatar koddsson avatar larsdenbakker avatar lewisl9029 avatar loynoir avatar marklundin avatar mathiaswp avatar micahzoltu avatar mxdvl avatar paulcodiny avatar poyoho avatar pvh avatar rich-harris avatar rmacklin avatar seanpdoyle avatar thepassle avatar tommie avatar vovacodes avatar xxgjzftd avatar zhoukekestar avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

es-module-shims's Issues

Top level await

Would it make sense to implement top-level await within this project? It can be accurately simulated by exporting a Promise from each module, and waiting on Promise.all of these exports.

Support for `import "my/file.js;`

I'm facing an issue where bare imports aren't resolved using the import map. I'm not sure if this is behaviour that is compliant with the spec, so please enlighten me if that is the case.

In my components we use bare imports for web-components that register themselves as customElements.define(...).

So we import them as such: import "ui-lib/ui-button.js".
In the import-map we provide them bundled as: "ui-lib/ui-button.js": "ui-lib/bundle.js". However it seems that these aren't parsed and resolved by the shim.

No shimming for 'someObject.import'

When shimming dynamic imports, es-module-shim also transforms import properties of an object. For instance, when I have an object like this:

const myObject = {
 import: ()=> import(some_url)
}

the import property of myObject gets transformed as well to:

myObject.importShim

Incorrect resolving for scoped import maps

Hello, @guybedford,
The last es-module-shims version fails to identify the scoped import maps.

This is a piece of the import map that I use (generated with jspm):

{
  "imports": {
    "@hubgets/heros.uil.loader": "./jspm_packages/npm/@hubgets/[email protected]/lib/index.js",
    "@hubgets/heros.uil.loader/": "./jspm_packages/npm/@hubgets/[email protected]/"
},
 "scopes": {
    "jspm_packages/npm/@hubgets/[email protected]/": {
      "@hubgets/heros.datalocal": "../[email protected]/lib/index.js",
      "@hubgets/heros.datalocal/": "../[email protected]/",
      "@hubgets/lib.error": "../[email protected]/lib/index.js",
      "@hubgets/lib.error/": "../[email protected]/",
      "@hubgets/lib.useragent": "../[email protected]/lib/index.js",
      "@hubgets/lib.useragent/": "../[email protected]/"
    }
}

I investigated a little bit and I think the problem's root is in parseImportMap:

scopes[resolvedScopeName] = resolvePackages(scope, baseUrl) || {};

I think this line should be:

scopes[resolvedScopeName] = resolvePackages(scope, resolvedScopeName) || {};

baseUrl for scoped packages should be the resolved url of their 'parent'.

Please let me know if you think I identified correctly the cause of the issue and I'll come up with a pull request.

Thanks!

Plan of action when importmaps lands in browsers

Apparently import-maps are supported now in chrome (behind a flag). What's the plan for this module when that moves out? If this were a pollyfill then it would just work... but as it is will we need to start adding the import-maps twice (<script type="importmap-shim"> and <script type="importmap">)? Will es-module-shims disable if import-maps are native?

File relative paths in package maps.

my.domain/foo/bar.html

<!DOCTYPE html>
<script type='module' src='es-module-shim.js'></script>
<script type='packagemap-shim'>
{
    "packages": {
        "apple": "./index.js"
    }
}
</script>
<script type='module-shim'>
import apple
</script>

The above code will try to load my.domain/foo/apple/index.js. If I change it to /index.js it will try to load my.domain/index.js.

Why is it prefixing the path with the package name? How do I tell it to not do that? Is this part of the spec, or a bug in this library?

Error stack is missing

const startAt = new Date();importShim(CURRENT_APP_URL, undefined).then(complete, criticalError);

image

The "stack" does not contains any stack info, can not know where the error comes from.

There are no cross origin issue.

Edge 16

Hi
According to https://github.com/guybedford/es-module-shims#browser-compatibility-with-es-module-shims es-module-shims should work for Edge 16+. However, we have are getting errors for Edge 16.
I tried tried the tests in this project, but they do not work at all with Edge 16.
The situation is cumbersome for us.
In our own project we use a <script type="module"> / <script nomodule> pattern to switch between using es-module-shims for modern browsers and a SystemJs fallback for older browsers.
The problem is that this "switching" pattern identifies Edge 16 as a modern browser but es-module-shims does not work.
Does anyone else have problems with Edge 16?

Compile to SystemJS on the fly

My interest for this is primarily for module workers in browsers that already support modules, but it would apply to running ES modules in browsers that don't even support ES modules.

File extensions....

In the import-map proposal there is a note regarding file extenions:

Note how unlike some Node.js usages, we include the ending .js here. File extensions are required in browsers; unlike in Node, we do not have the luxury of trying multiple file extensions until we find a good match. Fortunately, including file extensions also works in Node.js; that is, if everyone uses file extensions for submodules, their code will work in both environments.

Is there any way to set a default extension planned either in the spec or in this shim? Without it many es6 modules will not work as they are. We would need to explicitly add each in the import map:

{
  "imports": {
    "lit-html": "/dist/pkg/[email protected]/lit-html.js",
    "lit-html/": "/dist/pkg/[email protected]/",
    "lit-html/lit-html": "/dist/pkg/[email protected]/lit-html.js",
    "lit-html/lib/shady-render": "/dist/pkg/[email protected]/lib/shady-render.js",
  }
}

Minified imports give an error

When using a minified file it doesn't split the import line on ;

Eg:

Unminified works fine:

import x from 'y';
import z from 'a';
console.log(x)

Minified is broken. We get the error Error: Unable to resolve bare specifier ";console.log(x)

import x from 'y';import z from 'a';console.log(x)

It seems that it doesn't parse the code and splits statements on ;

Fallbacks dont work as expected

For import map:

    <script type="importmap-shim">
      {
        "imports": {
          "@vaadin/router": "/web_modules/@vaadin/router.js",
          "lit-element": "/web_modules/lit-element.js",
          "/web_modules/kv-storage-polyfill.js": [
            "std:kv-storage",
            "/web_modules/kv-storage-polyfill.js"
          ]
        }
      }
    </script>

results in:

es-module-shims.js:40 Uncaught TypeError: Cannot read property 'slice' of undefined
    at resolveIfNotPlainOrUrl (es-module-shims.js:40)
    at resolvePackages (es-module-shims.js:131)
    at parseImportMap (es-module-shims.js:138)
    at es-module-shims.js:966
    at es-module-shims.js:993

It seems to be related to this line:
https://github.com/guybedford/es-module-shims/blob/master/dist/es-module-shims.js#L130

resolveIfNotPlainOrUrl takes 2 arguments, relUrl and parentUrl:

function resolveIfNotPlainOrUrl (relUrl, parentUrl) {

But on the line linked above, it only takes one arg:

outPkgs[resolveIfNotPlainOrUrl(p) || p] = value;

So obviously parentUrl.slice isn't gonna work when its undefined. As a dirty workaround, changing the line to

outPkgs[resolveIfNotPlainOrUrl(p, p) || p] = value;

Does work.

Also changing the import map to:

      {
        "imports": {
          "@vaadin/router": "/web_modules/@vaadin/router.js",
          "lit-element": "/web_modules/lit-element.js",
          "std:kv-storage": [
            "std:kv-storage",
            "/web_modules/kv-storage-polyfill.js"
          ]
        }
      }

Also works fine, but this results in an error in the chrome dev console (chrome):

Ignored an import map key "std:kv-storage": Invalid key (non-fetch scheme)

Which means I guess that the spec doesnt actually support this?

I'll try and see if I have some time today to investigate further and whip up a PR. If you have any pointers I'd be happy to hear 😊

A full non-relative URL in module-shims?

Is there a technical reason that full non-relative URLs don't work inside shimmed modules?

For example:

<script defer src="https://unpkg.com/[email protected]/dist/es-module-shims.js"></script>
<script type="importmap-shim">
{
  "imports": {
    "lit-html": "https://unpkg.com/[email protected]/lit-html.js"
  }
}
</script>
<script type="module-shim">
  import { html } from "lit-html";                                             // works
  // import { html } from "https://unpkg.com/[email protected]/lit-html.js";      // does not work
  console.log(html);
</script>

Codepen for testing: https://codepen.io/Hypercubed/pen/daLrMZ

The error is:

Uncaught (in promise) Error: Unable to resolve bare specifier "https://unpkg.com/[email protected]/lit-html.js" from https://s.codepen.io/boomerang/iFrameKey-d5cb004a-1e37-cbae-c83b-d4327172eec9/
    at throwBare (es-module-shims.js:174)
    at resolveImportMap (es-module-shims.js:170)
    at resolve (es-module-shims.js:925)
    at load.d.Promise.all.deps.map (es-module-shims.js:880)
    at Array.map (<anonymous>)
    at load.L.load.f.then (es-module-shims.js:879)

Provide a browser feature / compat table

It would be nice to explain the project with a table where the various modules features are rows, and the browsers are the columns, and in each cell we indicate which browser version supports the feature natively, along with the es-module-shims support with any compatibility notes as superscripts.

Streaming support

Matthew Phillips brought up an interesting suggestion on Twitter that we could support streaming here, in addition to an early fetch model as imports / exports are discovered.

There is no reason this cannot be supported simply, although it would add a little more complexity to the code.

We should only merge such work if there are genuine performance improvements though.

Expose import map shim on JS API

Currently, shim only supports defining import maps via dom.

<script type="importmap-shim">

We can src or inline the map, but there is a hard constraint on the existence of this element in the dom.

Would be nice to be able to use the shim in a web worker (which doesn't have a dom).

Basically, import the shim into a webworker and call something like useMap(url|object)

I understand the shim is trying to force a pattern as close to the final spec as possible... maybe the lib could detect it is in a webworker before defining/exposing the function? Or throw an exception if used on the main thread.

Happy to discuss further...

WorkerShim fails to start because of the config options

WorkerShim fails to start because of the current config options:
return new Worker(workerScriptUrl, { type: undefined, ...options });
The error is:
TypeError: Failed to execute 'importScripts' on 'WorkerGlobalScope': Module scripts don't support importScripts().

The configuration should be:
return new Worker(workerScriptUrl, { ...options, type: undefined});
because we want to 'reset' the type of the Worker so the default type will be used (i..e 'classic')

ObjectURL should to be revoked?

This library creates multiple ObjectURLs and never revokes them.
And, as stated on MDN, they should be revoked:

Each time you call createObjectURL(), a new object URL is created, even if you've already created one for the same object. Each of these must be released by calling URL.revokeObjectURL() when you no longer need them.

Browsers will release object URLs automatically when the document is unloaded; however, for optimal performance and memory usage, if there are safe times when you can explicitly unload them, you should do so.

WASM 4kb limit

The 4KB limit on new WebAssembly.instance means that this project cannot implement Web Assembly integration when hitting this limit unfortunately, as we would either (1) execute the Web Assembly too early before its imports are ready or (2) execute the Web Assembly too late as we can't ensure it happens synchronously in a module body as would be required for semantic support (any importer wouldn't be able to read the exports).

SystemJS could get around this by handling WebAssembly.instantiate as if it were a top-level await, but because this project executes only within native module bodies that isn't possible.

So unfortunately that may shatter any hopes of this project providing WASM polyfill-style workflow support, all because of this 4KB limit.

Relative file path mappings.

A comment over on the proposal issues (WICG/import-maps#89 (comment)) suggests that I should be able to map import './apple' with something like:

{ "packages": { "/apple": "/apple.js" } }

However, I am unable to get this working with this tool. Am I misunderstanding the spec/comment, or is it a bug in this tool?

Source Map support in Firefox?

I have a module that lives at <root>/apple.js. In it there is a //# sourceMappingURL=apple.js.map. However, when the browser tries to load that sourcemap it can't find it. I suspect it has to do with the whole blob:... stuff, but I don't understand it well enough to speculate beyond that.

Source map error: TypeError: apple.js.map is not a valid URL.
Resource URL: blob:http://localhost:8081/033273b5-1e9d-4506-8bb5-cd301e633116
Source Map URL: apple.js.map

note: Source maps appear to work in Chrome.

Duplicate requests

If I create two imports in my source file:

import  "@ui5/webcomponents/dist/Input.js";
import "@ui5/webcomponents/dist/TextArea.js";

with import map:

        <script type="importmap-shim">
        {
            "imports": {
                "@ui5/": "https://storage.googleapis.com/pika-web-packages-02/ui5-buffet/0.0.0/dist-es2019/ui5-buffet.min.js#"
             }
       }

My intention (admittedly a bit of a hack) is to allow the import mapping to allow multiple references to point to the same bundled file. In theory, appending anything after the hash shouldn't cause that part of the string to be passed to the server.

What I'm seeing is that two identical requests are getting sent to the server. Only one should be necessary, I'm thinking?

Parse error.

I just updated to es-module-shims 0.3.0 and am now getting the following error in the console in Firefox.

Error: Parse error at 22373. 2 es-module-shims.js:199:365
Error: Parse error at 6318. 2 es-module-shims.js:199:365
Error: Parse error at 4599. es-module-shims.js:199:365

My page is not loading properly, though I do see all of the right files being fetched from the server (including name mapped files).

text/javascript throws

I know that application/javascript is the official content-type for javascript, but my server returned text/javascript. I'll update that to application/javascript, but other people might run into this as well so it might be good to support this.

Proposal: An experimental depcache implementation

I recently opened this issue on the import maps specification to again discuss handling the waterfall problem of module loading - WICG/import-maps#208.

Problem

It is common in npm workflows to possibly have dependency trees with depths of up to 10 modules deep.

Import maps bring these kinds of workflows to the browser, where unlike other resources which typically cap out at 2-3 extra round trips of latency (eg page.html -> style.css -> common.css -> header.png). There's a natural upper bound for most resources because there aren't really development patterns that increase the depth here.

But with modules, the depth in theory can have no limit. 10 extra round trips becomes a second of latency in some areas. If we get to depths of 20 / 30 modules in a tree, that becomes worse even with geographic edge caching.

Proposal

SystemJS successfully solved this problem a long time ago with depcache, and it can map to import maps quite easily.

The concept is that the import map is generated by some tool on the server that knows about all the modules. So that tool can also inject what is called a "dependency cache". Basically just letting the system know what imports each module has.

This information is enough to flatten the full tree and request all modules in parallel.

The suggested semantics are the following:

<script type="importmap">
{
  // standard import map properties
  "imports": { ...imports... },
  "scopes": { ...scopes... }

  // proposed property:
  "depcache": {
    "/module.js": [
       "pkgimport",
       "./relimport.js",
       "//url.com/import.js"
    ],
    "/module2.js": [...]
  }
}
</script>

This proposal works well with dynamic import and lazy loading - if /module2.js is lazily imported dynamically (say a plugin system), then since we already know all its dependencies upfront we can download them in parallel.

Given that import maps aren't driving any work on this, and I've tried unsuccessfully over the past year to engage specification authors on working on a solution to this problem, it may be time to just implement the solution in es-module-shims.

This will provide the feature and benefits to users.

At the same time, a specification for depcache can be brought to the import maps spec, and whatever that turns into having this experimental feature in es-module-shims can help drive the process.

Further suggestions / thoughts on the above very welcome, otherwise I think the time has come to make a move here.

[Question] Code transformation when importing

I'm trying to create a loader for theintern/intern that supports ES modules and power-assert-js/power-assert

I got it working fantastically with ES modules using this library
But when trying to add a transformation step in the loader to transform the modules using power-assert-js/espower-source, I ran into the problem that I can't import() a module from source (using blob URLs) unless it doesn't import any other module.

Does anyone have an idea this might be done?
Any pointers are much appreciated


Update

Gist for the ES module shims loader: https://gist.github.com/zaygraveyard/ae6cc847663fdc3898e51303f1c5a66d

exploring ie11 support

  1. i'm trying to see if i can get es-module-shims working in ie11

  2. i created an es5 branch of es-module-shims, which uses babel to generate an es5 script es-module-shims.es5.js

  3. in my repo for a web component i'm working on, i've included polyfills and am using es-module-shims.es5.js as follows

    <script src="https://unpkg.com/@babel/[email protected]/dist/polyfill.min.js"></script>
    <script src="https://unpkg.com/[email protected]/dist/fetch.umd.js"></script>
    <script>
      // inject console logging for each 'fetch' call
      var f = window.fetch
      window.fetch = function() {
        console.log("fetch", arguments[0])
        return f.apply(window, arguments)
      }
    </script>
    <script src="https://unpkg.com/@webcomponents/[email protected]/webcomponents-bundle.js"></script>
    <script type="importmap-shim" src="dist/importmap.json"></script>
    <script type="module-shim" src="source/demo.js"></script>
    <script src="assets/es-module-shims.es5.js"></script>
  4. other browsers are working, but when source/demo.js goes to import "./register-all.js", ie11 incorrectly issues a request for /register-all.js instead of resolving it to source/register-all.js — in other words, in ie11, a request is made for register-all.js at the server root, not within source/

    // CHROME 71
    fetch http://localhost:5000/dist/importmap.json
    fetch http://localhost:5000/source/demo.js
    fetch http://localhost:5000/source/register-all.js
     // continues to load application
    
    // INTERNET EXPLORER 11
    fetch http://192.168.1.73:5000/dist/importmap.json
    fetch http://192.168.1.73:5000/source/demo.js
    fetch http://192.168.1.73:5000/register-all.js
     // fetch for "/register-all.js" fails with 404
    

let me know if you have a hunch about what might be going on, or if perhaps there could be further reasons of which i'm not yet aware that could make ie11 support impossible

Feature detection polyfill strategy

Chrome has import maps enabled behind a flag, and will start shipping it at some point. So will other browsers.

This means that soon there will be browsers for which the shim doesn't add any new functionality (until you implement top level await, but then it's also a matter of time).

Do you intend for es-module-shims to do feature detection to decide whether it should do any work, or do you expect the anyone who uses the shim to not load the shim unless needed?

Ability to import UMD modules

Currently UMD modules (like react or react-dom) cannot be imported directly.

I was exploring the current state and found out that, for example, React is published only as UMD, they have a ticket to add ES6 modules to the published versions. But it's a long awaited feature and there's no conclusion on how to export.

Maybe it is possible to address this feature in this library then? It would be a nice step forward.

Some other useful links:

Fallback support or note in documentation

Since fallbacks in import maps are not supported yet, please either implement them or add a note to the Readme to make it obvious to devs that this part of import maps is not supported.

I wanted to use import shims mainly for that purpose so I am a little disappointed that this basic/important feature when working with cdn provided js files is not yet implemented.

best reagards

URL-base resolution bug

Apparently /asdf resolved in blob URLs like blob:sdf can result in resolutions like blob/asdf instead of blob:asdf.

We need to ensure that these absolute cases are handled correctly in the resolver.

Automatic rewriting of new Worker

We should be able to support the following replacement in the lexer:

new Worker(...)

->

new WorkerShim(...)

This would provide comprehensive module worker support, which would be kind of amazing.

Service worker fetch for import maps

It looks like the import maps polyfill doesn't work in most browsers still. Couldn't we use a service worker to intercept all requests for JS files and just rewrite the bare imports there before returning the modified response to the web application? Seems we could then have import maps everywhere service workers with the fetch listener are implemented. I'm sure using service workers to rewrite responses could be useful elsewhere as well

Constructing the rewritten source in Wasm memory

Instead of relying on the v8 string builder, we should be able to actually construct the rewritten source directly in Wasm memory and passing that view over to the Blob URL constructor directly.

Note that UTF-8 conversion would need to be done in the process as blobs don't accept UTF-16.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.