Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix: feature detection, add debug build #364

Merged
merged 9 commits into from
Mar 28, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 1 addition & 3 deletions chompfile.toml
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,6 @@ version = 0.1

extensions = ['chomp@0.1:footprint', 'chomp@0.1:npm']

default-task = 'build'

[server]
port = 8080
root = "."
Expand All @@ -18,7 +16,7 @@ run = 'rm -rf bench/results'

[[task]]
name = 'build'
targets = ['dist/es-module-shims.js', 'dist/es-module-shims.wasm.js']
targets = ['dist/es-module-shims.js', 'dist/es-module-shims.wasm.js', 'dist/es-module-shims.debug.js']
deps = ['src/*.js', 'npm:install', 'README.md']
run = 'rollup -c'

Expand Down
13 changes: 7 additions & 6 deletions rollup.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,21 +5,22 @@ import path from 'path';
const version = JSON.parse(fs.readFileSync('package.json')).version;

export default [
config(true),
config(false),
config(true, false),
config(false, false),
config(false, true),
];

function config (isWasm) {
function config (isWasm, isDebug) {
const name = 'es-module-shims'

return {
input: `src/${name}.js`,
output: {
file: `dist/${name}${isWasm ? '.wasm' : ''}.js`,
file: `dist/${name}${isWasm ? '.wasm' : ''}${isDebug ? '.debug' : ''}.js`,
format: 'iife',
strict: false,
sourcemap: false,
banner: `/* ES Module Shims ${isWasm ? 'Wasm ' : ''}${version} */`
banner: `/* ES Module Shims ${isWasm ? 'Wasm ' : ''}${isDebug ? 'DEBUG BUILD ' : ''}${version} */`
},
plugins: [
{
Expand All @@ -29,7 +30,7 @@ function config (isWasm) {
}
},
replace({
'self.ESMS_DEBUG': 'false',
'self.ESMS_DEBUG': isDebug.toString(),
preventAssignment: true
}),
]
Expand Down
20 changes: 15 additions & 5 deletions src/es-module-shims.js
Original file line number Diff line number Diff line change
Expand Up @@ -120,7 +120,8 @@ let importMap = { imports: {}, scopes: {} };
let baselinePassthrough;

const initPromise = featureDetectionPromise.then(() => {
baselinePassthrough = esmsInitOptions.polyfillEnable !== true && supportsDynamicImport && supportsImportMeta && supportsImportMaps && (!jsonModulesEnabled || supportsJsonAssertions) && (!cssModulesEnabled || supportsCssAssertions) && !importMapSrcOrLazy && !self.ESMS_DEBUG;
baselinePassthrough = esmsInitOptions.polyfillEnable !== true && supportsDynamicImport && supportsImportMeta && supportsImportMaps && (!jsonModulesEnabled || supportsJsonAssertions) && (!cssModulesEnabled || supportsCssAssertions) && !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) {
const supports = HTMLScriptElement.supports || (type => type === 'classic' || type === 'module');
Expand Down Expand Up @@ -174,6 +175,7 @@ async function topLevelLoad (url, fetchOpts, source, nativelyLoaded, lastStaticL
if (importHook) await importHook(url, typeof fetchOpts !== 'string' ? fetchOpts : {}, '');
// early analysis opt-out - no need to even fetch if we have feature support
if (!shimMode && baselinePassthrough) {
if (self.ESMS_DEBUG) console.info(`es-module-shims: load skipping polyfill due to baseline passthrough applying: ${url}`);
// for polyfill case, only dynamic import needs a return value here, and dynamic import will never pass nativelyLoaded
if (nativelyLoaded)
return null;
Expand All @@ -186,7 +188,7 @@ async function topLevelLoad (url, fetchOpts, source, nativelyLoaded, lastStaticL
lastLoad = undefined;
resolveDeps(load, seen);
await lastStaticLoadPromise;
if (source && !shimMode && !load.n && !self.ESMS_DEBUG) {
if (source && !shimMode && !load.n) {
if (nativelyLoaded) return;
if (revokeBlobURLs) revokeObjectURLs(Object.keys(seen));
return await dynamicImport(createBlob(source), { errUrl: source });
Expand Down Expand Up @@ -463,6 +465,7 @@ function getOrCreateLoad (url, fetchOpts, parent, source) {
}

function processScriptsAndPreloads (mapsOnly = false) {
if (self.ESMS_DEBUG) console.info(`es-module-shims: processing scripts`);
if (!mapsOnly)
for (const link of document.querySelectorAll(shimMode ? 'link[rel=modulepreload-shim]' : 'link[rel=modulepreload]'))
processPreload(link);
Expand Down Expand Up @@ -492,8 +495,10 @@ let lastStaticLoadPromise = Promise.resolve();

let domContentLoadedCnt = 1;
function domContentLoadedCheck () {
if (--domContentLoadedCnt === 0 && !noLoadEventRetriggers && (shimMode || !baselinePassthrough))
if (--domContentLoadedCnt === 0 && !noLoadEventRetriggers && (shimMode || !baselinePassthrough)) {
if (self.ESMS_DEBUG) console.info(`es-module-shims: DOMContentLoaded refire`);
document.dispatchEvent(new Event('DOMContentLoaded'));
}
}
// this should always trigger because we assume es-module-shims is itself a domcontentloaded requirement
if (hasDocument) {
Expand All @@ -505,8 +510,10 @@ if (hasDocument) {

let readyStateCompleteCnt = 1;
function readyStateCompleteCheck () {
if (--readyStateCompleteCnt === 0 && !noLoadEventRetriggers && (shimMode || !baselinePassthrough))
if (--readyStateCompleteCnt === 0 && !noLoadEventRetriggers && (shimMode || !baselinePassthrough)) {
if (self.ESMS_DEBUG) console.info(`es-module-shims: readystatechange complete refire`);
document.dispatchEvent(new Event('readystatechange'));
}
}

const hasNext = script => script.nextSibling || script.parentNode && hasNext(script.parentNode);
Expand Down Expand Up @@ -544,12 +551,15 @@ function processScript (script, ready = readyStateCompleteCnt > 0) {
const isDomContentLoadedScript = domContentLoadedCnt > 0;
if (isBlockingReadyScript) readyStateCompleteCnt++;
if (isDomContentLoadedScript) domContentLoadedCnt++;
if (self.ESMS_DEBUG) console.info(`es-module-shims: processing ${script.src || '<inline>'}`);
const loadPromise = topLevelLoad(script.src || pageBaseUrl, getFetchOpts(script), !script.src && script.innerHTML, !shimMode, isBlockingReadyScript && lastStaticLoadPromise)
.then(() => {
// if the type of the script tag "module-shim", browser does not dispatch a "load" event
// see https://github.com/guybedford/es-module-shims/issues/346
if (shimMode)
if (shimMode) {
if (self.ESMS_DEBUG) console.info(`es-module-shims: load even refire ${script.src || '<inline>'}`);
script.dispatchEvent(new Event('load'));
}
})
.catch(throwError);
if (isBlockingReadyScript)
Expand Down
47 changes: 36 additions & 11 deletions src/features.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,15 +5,17 @@ import { createBlob, noop, nonce, cssModulesEnabled, jsonModulesEnabled, hasDocu
export let supportsJsonAssertions = false;
export let supportsCssAssertions = false;

export let supportsImportMaps = hasDocument && HTMLScriptElement.supports ? HTMLScriptElement.supports('importmap') : false;
export let supportsImportMeta = supportsImportMaps;
const supports = hasDocument && HTMLScriptElement.supports;

export let supportsImportMaps = supports && supports.name === 'supports' && supports('importmap');
export let supportsImportMeta = supportsDynamicImport;

const importMetaCheck = 'import.meta';
const cssModulesCheck = `import"x"assert{type:"css"}`;
const jsonModulesCheck = `import"x"assert{type:"json"}`;

export const featureDetectionPromise = Promise.resolve(dynamicImportCheck).then(() => {
if (!supportsDynamicImport || supportsImportMaps && !cssModulesEnabled && !jsonModulesEnabled)
export let featureDetectionPromise = Promise.resolve(dynamicImportCheck).then(() => {
if (!supportsDynamicImport)
return;

if (!hasDocument)
Expand All @@ -24,14 +26,19 @@ export const featureDetectionPromise = Promise.resolve(dynamicImportCheck).then(
]);

return new Promise(resolve => {
if (self.ESMS_DEBUG) console.info(`es-module-shims: performing feature detections for ${`${supportsImportMaps ? '' : 'import maps, '}${cssModulesEnabled ? 'css modules, ' : ''}${jsonModulesEnabled ? 'json modules, ' : ''}`.slice(0, -2)}`);
const iframe = document.createElement('iframe');
iframe.style.display = 'none';
iframe.setAttribute('nonce', nonce);
function cb ({ data: [a, b, c, d] }) {
supportsImportMaps = a;
supportsImportMeta = b;
supportsCssAssertions = c;
supportsJsonAssertions = d;
function cb ({ data }) {
// failed feature detection (security policy) -> revert to default assumptions
if (Array.isArray(data)) {
supportsImportMaps = data[0];
supportsImportMeta = data[1];
supportsCssAssertions = data[2];
supportsJsonAssertions = data[3];
}
else if (self.ESMS_DEBUG) console.info(`es-module-shims: feature detection failure, using defaults`);
resolve();
document.head.removeChild(iframe);
window.removeEventListener('message', cb, false);
Expand All @@ -42,7 +49,14 @@ export const featureDetectionPromise = Promise.resolve(dynamicImportCheck).then(
supportsImportMaps ? 'true,true' : `'x',b('${importMetaCheck}')`}, ${cssModulesEnabled ? `b('${cssModulesCheck}'.replace('x',b('','text/css')))` : 'false'}, ${
jsonModulesEnabled ? `b('${jsonModulesCheck}'.replace('x',b('{}','text/json')))` : 'false'}].map(x =>typeof x==='string'?import(x).then(x =>!!x,()=>false):x)).then(a=>parent.postMessage(a,'*'))<${''}/script>`;

iframe.onload = () => {
// Safari will call onload eagerly on head injection, but we don't want the Wechat
// path to trigger before setting srcdoc, therefore we track the timing
let readyForOnload = false, onloadCalledWhileNotReady = false;
function doOnload () {
if (!readyForOnload) {
onloadCalledWhileNotReady = true;
return;
}
// WeChat browser doesn't support setting srcdoc scripts
// But iframe sandboxes don't support contentDocument so we do this as a fallback
const doc = iframe.contentDocument;
Expand All @@ -53,15 +67,26 @@ export const featureDetectionPromise = Promise.resolve(dynamicImportCheck).then(
s.innerHTML = importMapTest.slice(15 + (nonce ? nonce.length : 0), -9);
doc.head.appendChild(s);
}
};
}

iframe.onload = doOnload;
// WeChat browser requires append before setting srcdoc
document.head.appendChild(iframe);

// setting srcdoc is not supported in React native webviews on iOS
// setting src to a blob URL results in a navigation event in webviews
// document.write gives usability warnings
readyForOnload = true;
if ('srcdoc' in iframe)
iframe.srcdoc = importMapTest;
else
iframe.contentDocument.write(importMapTest);
// retrigger onload for Safari only if necessary
if (onloadCalledWhileNotReady) doOnload();
});
});

if (self.ESMS_DEBUG)
featureDetectionPromise = featureDetectionPromise.then(() => {
console.info(`es-module-shims: detected native support - ${supportsDynamicImport ? '' : 'no '}dynamic import, ${supportsImportMeta ? '' : 'no '}import meta, ${supportsImportMaps ? '' : 'no '}import maps`);
});
2 changes: 0 additions & 2 deletions test/test-shim-map-overrides.html
Original file line number Diff line number Diff line change
Expand Up @@ -25,8 +25,6 @@
shimMode: true,
mapOverrides: true,
};

window.ESMS_DEBUG = true;
</script>
<script type="module" src="../src/es-module-shims.js"></script>
<script type="module" noshim>
Expand Down
2 changes: 0 additions & 2 deletions test/test-shim.html
Original file line number Diff line number Diff line change
Expand Up @@ -77,8 +77,6 @@
},
onerror: e => window.e = e,
};

window.ESMS_DEBUG = true;
</script>
<script type="module" src="../src/es-module-shims.js"></script>
<script type="module" noshim>
Expand Down