Skip to content

Commit

Permalink
feat: still polyfill when early module loads stop import maps
Browse files Browse the repository at this point in the history
  • Loading branch information
guybedford committed Sep 27, 2021
1 parent 295a363 commit bca301e
Show file tree
Hide file tree
Showing 4 changed files with 76 additions and 41 deletions.
8 changes: 6 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -12,9 +12,10 @@
"footprint": "npm run build && cat dist/es-module-shims.js | brotli | wc -c",
"footprint:csp": "npm run build && cat dist/es-module-shims.csp.js | brotli | wc -c",
"prepublishOnly": "npm run build",
"test": "npm run test:test-base-href && npm run test:test-csp && npm run test:test-polyfill && npm run test:test-polyfill-wasm && npm run test:test-preload-case && npm run test:test-revoke-blob-urls && npm run test:test-shim",
"test": "npm run test:test-base-href && npm run test:test-csp && npm run test:test-early-module-load && npm run test:test-polyfill && npm run test:test-polyfill-wasm && npm run test:test-preload-case && npm run test:test-revoke-blob-urls && npm run test:test-shim",
"test:test-base-href": "cross-env TEST_NAME=test-base-href node test/server.mjs",
"test:test-csp": "cross-env TEST_NAME=test-csp node test/server.mjs",
"test:test-early-module-load": "cross-env TEST_NAME=test-early-module-load node test/server.mjs",
"test:test-polyfill": "cross-env TEST_NAME=test-polyfill node test/server.mjs",
"test:test-polyfill-wasm": "cross-env TEST_NAME=test-polyfill-wasm node test/server.mjs",
"test:test-preload-case": "cross-env TEST_NAME=test-preload-case node test/server.mjs",
Expand Down Expand Up @@ -51,5 +52,8 @@
"bugs": {
"url": "https://github.com/guybedford/es-module-shims/issues"
},
"homepage": "https://github.com/guybedford/es-module-shims#readme"
"homepage": "https://github.com/guybedford/es-module-shims#readme",
"dependencies": {
"rimraf": "^3.0.2"
}
}
69 changes: 37 additions & 32 deletions src/es-module-shims.js
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ import {
noLoadEventRetriggers,
cssModulesEnabled,
jsonModulesEnabled,
onpolyfill,
} from './options.js';
import { dynamicImport } from './dynamic-import-csp.js';
import {
Expand Down Expand Up @@ -67,10 +68,24 @@ let importMapSrcOrLazy = false;
let baselinePassthrough;

const initPromise = featureDetectionPromise.then(() => {
baselinePassthrough = supportsDynamicImport && supportsImportMeta && supportsImportMaps && (!jsonModulesEnabled || supportsJsonAssertions) && (!cssModulesEnabled || supportsCssAssertions) && !importMapSrcOrLazy && !self.ESMS_DEBUG;
// shim mode is determined on initialization, no late shim mode
if (!shimMode && document.querySelectorAll('script[type="module-shim"],script[type="importmap-shim"]').length)
setShimMode();
if (!shimMode) {
let seenScript = false;
for (const script of document.querySelectorAll('script[type="module-shim"],script[type="importmap-shim"],script[type="module"],script[type="importmap"]')) {
if (!seenScript && script.type === 'module')
seenScript = true;
if (script.type.endsWith('-shim')) {
setShimMode();
break;
}
if (seenScript && script.type === 'importmap') {
importMapSrcOrLazy = true;
break;
}
}
}
baselinePassthrough = supportsDynamicImport && supportsImportMeta && supportsImportMaps && (!jsonModulesEnabled || supportsJsonAssertions) && (!cssModulesEnabled || supportsCssAssertions) && !importMapSrcOrLazy && !self.ESMS_DEBUG;
if (!baselinePassthrough) onpolyfill();
if (shimMode || !baselinePassthrough) {
new MutationObserver(mutations => {
for (const mutation of mutations) {
Expand All @@ -95,16 +110,9 @@ const initPromise = featureDetectionPromise.then(() => {
let importMapPromise = initPromise;

let acceptingImportMaps = true;
let nativeAcceptingImportMaps = true;
async function topLevelLoad (url, fetchOpts, source, nativelyLoaded, lastStaticLoadPromise) {
if (acceptingImportMaps) {
if (!shimMode) {
acceptingImportMaps = false;
}
else {
nativeAcceptingImportMaps = false;
}
}
if (!shimMode)
acceptingImportMaps = false;
await importMapPromise;
// early analysis opt-out - no need to even fetch if we have feature support
if (!shimMode && baselinePassthrough) {
Expand Down Expand Up @@ -157,12 +165,8 @@ async function importShim (id, parentUrl = pageBaseUrl, _assertion) {
await initPromise;
if (acceptingImportMaps || shimMode || !baselinePassthrough) {
processImportMaps();
if (!shimMode) {
if (!shimMode)
acceptingImportMaps = false;
}
else {
nativeAcceptingImportMaps = false;
}
}
await importMapPromise;
return topLevelLoad((await resolve(id, parentUrl)).r || throwUnresolved(id, parentUrl), { credentials: 'same-origin' });
Expand Down Expand Up @@ -375,7 +379,7 @@ function getOrCreateLoad (url, fetchOpts, source) {
load.n = true;
if (!n) return;
const { r, b } = await resolve(n, load.r || load.u);
if (b && !supportsImportMaps)
if (b && (!supportsImportMaps || importMapSrcOrLazy))
load.n = true;
if (d !== -1) return;
if (!r)
Expand Down Expand Up @@ -435,42 +439,40 @@ document.addEventListener('DOMContentLoaded', async () => {
});

let readyStateCompleteCnt = 1;
if (document.readyState === 'complete')
if (document.readyState === 'complete') {
readyStateCompleteCheck();
else
}
else {
document.addEventListener('readystatechange', async () => {
processImportMaps();
await initPromise;
readyStateCompleteCheck();
});
}
function readyStateCompleteCheck () {
if (--readyStateCompleteCnt === 0 && !noLoadEventRetriggers)
document.dispatchEvent(new Event('readystatechange'));
}

function processImportMap (script) {
if (!acceptingImportMaps)
return;
if (script.ep) // ep marker = script processed
return;
// empty inline scripts sometimes show before domready
if (!script.src && !script.innerHTML)
return;
script.ep = true;
// we dont currently support multiple, external or dynamic imports maps in polyfill mode to match native
if (script.src || !nativeAcceptingImportMaps) {
if (script.src) {
if (!shimMode)
return;
importMapSrcOrLazy = true;
}
if (!shimMode) {
if (acceptingImportMaps)
importMapPromise = importMapPromise.then(async () => {
importMap = resolveAndComposeImportMap(script.src ? await (await fetchHook(script.src)).json() : JSON.parse(script.innerHTML), script.src || pageBaseUrl, importMap);
});
else if (!shimMode)
acceptingImportMaps = false;
}
else {
nativeAcceptingImportMaps = false;
}
importMapPromise = importMapPromise.then(async () => {
importMap = resolveAndComposeImportMap(script.src ? await (await fetchHook(script.src)).json() : JSON.parse(script.innerHTML), script.src || pageBaseUrl, importMap);
});
}

function processScript (script) {
Expand All @@ -488,7 +490,10 @@ function processScript (script) {
const isDomContentLoadedScript = domContentLoadedCnt > 0;
if (isReadyScript) readyStateCompleteCnt++;
if (isDomContentLoadedScript) domContentLoadedCnt++;
const loadPromise = topLevelLoad(script.src || `${pageBaseUrl}?${id++}`, getFetchOpts(script), !script.src && script.innerHTML, !shimMode, isReadyScript && lastStaticLoadPromise).catch(onerror);
const loadPromise = topLevelLoad(script.src || `${pageBaseUrl}?${id++}`, getFetchOpts(script), !script.src && script.innerHTML, !shimMode, isReadyScript && lastStaticLoadPromise).catch(e => {
setTimeout(() => { throw e });
onerror(e);
});
if (isReadyScript)
lastStaticLoadPromise = loadPromise.then(readyStateCompleteCheck);
if (isDomContentLoadedScript)
Expand Down
18 changes: 11 additions & 7 deletions src/options.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ const optionsScript = document.querySelector('script[type=esms-options]');
const esmsInitOptions = optionsScript ? JSON.parse(optionsScript.innerHTML) : self.esmsInitOptions ? self.esmsInitOptions : {};

export let shimMode = !!esmsInitOptions.shimMode;
export const resolveHook = shimMode && esmsInitOptions.resolve;
export const resolveHook = globalHook(shimMode && esmsInitOptions.resolve);

export const skip = esmsInitOptions.skip ? new RegExp(esmsInitOptions.skip) : null;

Expand All @@ -17,12 +17,16 @@ if (!nonce) {
nonce = nonceElement.getAttribute('nonce');
}

export const {
fetchHook = fetch,
onerror = noop,
revokeBlobURLs,
noLoadEventRetriggers,
} = esmsInitOptions;
export const onerror = globalHook(esmsInitOptions.onerror || noop);
export const onpolyfill = globalHook(esmsInitOptions.onpolyfill || noop);

export const { revokeBlobURLs, noLoadEventRetriggers } = esmsInitOptions;

export const fetchHook = esmsInitOptions.fetchHook ? globalHook(esmsInitOptions.fetchHook) : fetch;

function globalHook (name) {
return typeof name === 'string' ? self[name] : name;
}

const enable = Array.isArray(esmsInitOptions.polyfillEnable) ? esmsInitOptions.polyfillEnable : [];
export const cssModulesEnabled = enable.includes('css-modules');
Expand Down
22 changes: 22 additions & 0 deletions test/test-early-module-load.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
<!doctype html>
<script>
window.polyfill = () => {
window.calledPolyfillHook = true;
};
</script>
<script type="esms-options">
{
"onpolyfill": "polyfill"
}
</script>
<script async type="module" src="../src/es-module-shims.js"></script>
<script type="importmap">
{
"imports": {
"app": "data:text/javascript,if (calledPolyfillHook) fetch('/done')"
}
}
</script>
<script type="module">
import 'app';
</script>

0 comments on commit bca301e

Please sign in to comment.