From 913dee3a1c0f60af8c630bf9809c86412527e5cd Mon Sep 17 00:00:00 2001 From: Guy Bedford Date: Sun, 18 Feb 2024 20:40:37 -0800 Subject: [PATCH] add wasm esm integration polyfill (#408) --- README.md | 25 ++++++++++++++++++++++++- src/env.js | 1 + src/es-module-shims.js | 11 ++++++----- src/features.js | 14 ++++++++------ test/fixtures/test.wasm | Bin 0 -> 56 bytes test/fixtures/wasm-import.js | 2 ++ test/polyfill.js | 24 +++++++++++++++++++++--- test/test-csp.html | 6 ++---- test/test-polyfill-wasm.html | 4 +--- test/test-polyfill.html | 4 +--- 10 files changed, 66 insertions(+), 25 deletions(-) create mode 100644 test/fixtures/test.wasm create mode 100644 test/fixtures/wasm-import.js diff --git a/README.md b/README.md index aeffd95a..eccc2000 100644 --- a/README.md +++ b/README.md @@ -12,6 +12,7 @@ The following modules features are polyfilled: * Dynamic `import()` shimming when necessary in eg older Firefox versions. * `import.meta` and `import.meta.url`. * [JSON](#json-modules) and [CSS modules](#css-modules) with import assertions (when enabled). +* [Wasm modules](#wasm-modules) when enabled. * [`` is shimmed](#modulepreload) in browsers without import maps support. When running in shim mode, module rewriting is applied for all users and custom [resolve](#resolve-hook) and [fetch](#fetch-hook) hooks can be implemented allowing for custom resolution and streaming in-browser transform workflows. @@ -182,7 +183,7 @@ If using more modern features like CSS Modules or JSON Modules, these need to be ```html ``` @@ -222,6 +223,7 @@ Browser Compatibility on baseline ES modules support **with** ES Module Shims: | [Import Maps](#import-maps) | :heavy_check_mark: | :heavy_check_mark: | :heavy_check_mark: | | [JSON Modules](#json-modules) | :heavy_check_mark: | :heavy_check_mark: | :heavy_check_mark: | | [CSS Modules](#css-modules) | :heavy_check_mark:1 | :heavy_check_mark:1 | :heavy_check_mark:1 | +| [Wasm Modules](#wasm-modules) | 89+ | 89+ | 15+ | | [import.meta.resolve](#resolve) | :heavy_check_mark: | :heavy_check_mark: | :heavy_check_mark: | | [Module Workers](#module-workers) (via wrapper) | 63+ | :x:2 | 15+ | | Top-Level Await (unpolyfilled3) | 89+ | 89+ | 15+ | @@ -240,6 +242,7 @@ Browser compatibility **without** ES Module Shims: | [Import Maps](#import-maps) | 89+ | 108+ | 16.4+ | | [JSON Modules](#json-modules) | 91+ | :x: | :x: | | [CSS Modules](#css-modules) | 95+ | :x: | :x: | +| [Wasm Modules](#wasm-modules) | :x: | :x: | :x: | | [import.meta.resolve](#resolve) | :x: | :x: | :x: | | [Module Workers](#module-workers) | ~68+ | :x: | :x: | | Top-Level Await | 89+ | 89+ | 15+ | @@ -436,6 +439,26 @@ In addition CSS modules need to be served with a valid CSS content type. Checks for assertion failures are not currently included. +### Wasm Modules + +> Stability: WebAssembly Standard, Unimplemented + +Implements the [WebAssembly ESM Integration](https://github.com/WebAssembly/esm-integration) spec (source phase imports omitted currently, tracking in https://github.com/guybedford/es-module-shims/issues/410). + +In shim mode, Wasm modules are always supported. In polyfill mode, Wasm modules require the `polyfillEnable: ['wasm-modules']` [init option](#polyfill-enable-option). + +WebAssembly module exports are made available as module exports and WebAssembly module imports will be resolved using the browser module loader. + +WebAssembly modules require native top-level await support to be polyfilled, see the [compatibility table](#browser-support) above. + +```html + +``` + +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. + ### Resolve > Stability: Draft HTML PR diff --git a/src/env.js b/src/env.js index 4110623d..3717f0ea 100644 --- a/src/env.js +++ b/src/env.js @@ -38,6 +38,7 @@ function globalHook (name) { const enable = Array.isArray(esmsInitOptions.polyfillEnable) ? esmsInitOptions.polyfillEnable : []; export const cssModulesEnabled = enable.includes('css-modules'); export const jsonModulesEnabled = enable.includes('json-modules'); +export const wasmModulesEnabled = enable.includes('wasm-modules'); export const edge = !navigator.userAgentData && !!navigator.userAgent.match(/Edge\/\d+\.\d+/); diff --git a/src/es-module-shims.js b/src/es-module-shims.js index b4ce8b98..2127b49c 100755 --- a/src/es-module-shims.js +++ b/src/es-module-shims.js @@ -20,6 +20,7 @@ import { noLoadEventRetriggers, cssModulesEnabled, jsonModulesEnabled, + wasmModulesEnabled, onpolyfill, enforceIntegrity, fromParent, @@ -34,6 +35,7 @@ import { supportsImportMaps, supportsCssAssertions, supportsJsonAssertions, + supportsWasmModules, featureDetectionPromise, } from './features.js'; import * as lexer from '../node_modules/es-module-lexer/dist/lexer.asm.js'; @@ -121,7 +123,7 @@ let importMap = { imports: {}, scopes: {} }; let baselinePassthrough; const initPromise = featureDetectionPromise.then(() => { - baselinePassthrough = esmsInitOptions.polyfillEnable !== true && supportsDynamicImport && supportsImportMeta && supportsImportMaps && (!jsonModulesEnabled || supportsJsonAssertions) && (!cssModulesEnabled || supportsCssAssertions) && !importMapSrcOrLazy; + baselinePassthrough = esmsInitOptions.polyfillEnable !== true && supportsDynamicImport && supportsImportMeta && supportsImportMaps && (!jsonModulesEnabled || supportsJsonAssertions) && (!cssModulesEnabled || supportsCssAssertions) && (!wasmModulesEnabled || supportsWasmModules) && !importMapSrcOrLazy; if (self.ESMS_DEBUG) console.info(`es-module-shims: init ${shimMode ? 'shim mode' : 'polyfill mode'}, ${baselinePassthrough ? 'baseline passthrough' : 'polyfill engaged'}`); if (hasDocument) { if (!supportsImportMaps) { @@ -409,8 +411,7 @@ async function fetchModule (url, fetchOpts, parent) { i = 0; s += `const instance = await WebAssembly.instantiate(importShim._w['${url}'], {${importObj}});\n`; for (const expt of WebAssembly.Module.exports(module)) { - s += `const expt${i} = instance['${expt.name}'];\n`; - s += `export { expt${i++} as "${expt.name}" };\n`; + s += `export const ${expt.name} = instance.exports['${expt.name}'];\n`; } return { r: res.url, s, t: 'wasm' }; } @@ -469,9 +470,9 @@ function getOrCreateLoad (url, fetchOpts, parent, source) { let t; ({ r: load.r, s: source, t } = await (fetchCache[url] || fetchModule(url, fetchOpts, parent))); if (t && !shimMode) { - if (t === 'css' && !cssModulesEnabled || t === 'json' && !jsonModulesEnabled) + if (t === 'css' && !cssModulesEnabled || t === 'json' && !jsonModulesEnabled || t === 'wasm' && !wasmModulesEnabled) throw Error(`${t}-modules require @@ -27,9 +27,7 @@