From 6504a33cab7c9e8cd2e153249a4f67ca18dcd867 Mon Sep 17 00:00:00 2001 From: Vladimir Guguiev Date: Fri, 14 May 2021 10:00:02 +0200 Subject: [PATCH 1/4] Add failing test --- README.md | 3 +++ src/es-module-shims.js | 6 ++++++ test/dynamic-import-map-shim.html | 16 ++++++++++++++++ test/dynamic-import-map-shim.js | 28 ++++++++++++++++++++++++++++ 4 files changed, 53 insertions(+) create mode 100644 test/dynamic-import-map-shim.html create mode 100644 test/dynamic-import-map-shim.js diff --git a/README.md b/README.md index f3e68f9d..fe0e88f4 100644 --- a/README.md +++ b/README.md @@ -55,6 +55,9 @@ Just write your HTML modules like you would in the latest Chrome: and ES Module Shims will make it work in [all browsers with any ES Module Support](#browser-support). +NOTE: `script[type="importmap"]` and `script[type="importmap-shim"]` should be placed before +any `script[type="module"]` or `script[type="module-shim"]` in the html. + Note that you will typically see a console error in browsers without import maps support like: ``` diff --git a/src/es-module-shims.js b/src/es-module-shims.js index 12a2d2db..b8b97afc 100755 --- a/src/es-module-shims.js +++ b/src/es-module-shims.js @@ -64,6 +64,9 @@ async function topLevelLoad (url, source, polyfill) { } async function importShim (id, parentUrl = pageBaseUrl) { + console.log('importShim before await', id) + await importMapPromise; + console.log('importShim after await', id) return topLevelLoad(resolve(id, parentUrl).r || throwUnresolved(id, parentUrl)); } @@ -291,6 +294,7 @@ new MutationObserver(mutations => { if (mutation.type !== 'childList') continue; for (const node of mutation.addedNodes) { if (node.tagName === 'SCRIPT' && node.type) + console.log('MutationObserver', node.innerHTML) processScript(node, !firstTopLevelProcess); } } @@ -316,11 +320,13 @@ async function processScript (script, dynamic) { importMapSrcOrLazy = true; hasImportMap = true; importMap = resolveAndComposeImportMap(script.src ? await (await fetchHook(script.src)).json() : JSON.parse(script.innerHTML), script.src || pageBaseUrl, importMap); + console.log('Processed', script.innerHTML) }); } } function resolve (id, parentUrl) { + console.log("resolve", id) const urlResolved = resolveIfNotPlainOrUrl(id, parentUrl); const resolved = resolveImportMap(importMap, urlResolved || id, parentUrl); return { r: resolved, m: urlResolved !== resolved }; diff --git a/test/dynamic-import-map-shim.html b/test/dynamic-import-map-shim.html new file mode 100644 index 00000000..501e6b1b --- /dev/null +++ b/test/dynamic-import-map-shim.html @@ -0,0 +1,16 @@ + + + + + diff --git a/test/dynamic-import-map-shim.js b/test/dynamic-import-map-shim.js new file mode 100644 index 00000000..238c9b7c --- /dev/null +++ b/test/dynamic-import-map-shim.js @@ -0,0 +1,28 @@ +main(); +async function main() { + // Append a dynamic import map containing resolution for "react-dom". + document.body.appendChild(Object.assign(document.createElement('script'), { + type: 'importmap-shim', + innerHTML: JSON.stringify({ + "imports": { + "react-dom": "https://ga.jspm.io/npm:react-dom@17.0.2/dev.index.js" + }, + "scopes": { + "https://ga.jspm.io/": { + "scheduler": "https://ga.jspm.io/npm:scheduler@0.20.2/dev.index.js", + "scheduler/tracing": "https://ga.jspm.io/npm:scheduler@0.20.2/dev.tracing.js" + } + } + }) + })); + + const reactStart = performance.now(); + const [React, ReactDOM] = await Promise.all([ + importShim('react'), + importShim('react-dom'), + ]); + const reactEnd = performance.now(); + + console.log(`React and ReactDOM loaded in ${(reactEnd - reactStart).toFixed(2)}ms`); + console.log({ React, ReactDOM }); +} From 6f594e61c419070c65e5a3c64fab935d024f60ba Mon Sep 17 00:00:00 2001 From: Vladimir Guguiev Date: Fri, 14 May 2021 10:29:00 +0200 Subject: [PATCH 2/4] Fix the race --- src/es-module-shims.js | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/src/es-module-shims.js b/src/es-module-shims.js index b8b97afc..dfe0f653 100755 --- a/src/es-module-shims.js +++ b/src/es-module-shims.js @@ -64,9 +64,11 @@ async function topLevelLoad (url, source, polyfill) { } async function importShim (id, parentUrl = pageBaseUrl) { - console.log('importShim before await', id) + // Give dynamically inserted import maps a chance to be processed + // if `importShim()` was called synchronously after the insertion. + await new Promise(resolve => setTimeout(resolve, 0)) + // Make sure all the "in-flight" import maps are loaded and applied. await importMapPromise; - console.log('importShim after await', id) return topLevelLoad(resolve(id, parentUrl).r || throwUnresolved(id, parentUrl)); } @@ -294,7 +296,6 @@ new MutationObserver(mutations => { if (mutation.type !== 'childList') continue; for (const node of mutation.addedNodes) { if (node.tagName === 'SCRIPT' && node.type) - console.log('MutationObserver', node.innerHTML) processScript(node, !firstTopLevelProcess); } } @@ -320,13 +321,11 @@ async function processScript (script, dynamic) { importMapSrcOrLazy = true; hasImportMap = true; importMap = resolveAndComposeImportMap(script.src ? await (await fetchHook(script.src)).json() : JSON.parse(script.innerHTML), script.src || pageBaseUrl, importMap); - console.log('Processed', script.innerHTML) }); } } function resolve (id, parentUrl) { - console.log("resolve", id) const urlResolved = resolveIfNotPlainOrUrl(id, parentUrl); const resolved = resolveImportMap(importMap, urlResolved || id, parentUrl); return { r: resolved, m: urlResolved !== resolved }; From ea49518835b5cb20b8caede738b87cbc396ed0fc Mon Sep 17 00:00:00 2001 From: Guy Bedford Date: Fri, 14 May 2021 15:01:18 +0200 Subject: [PATCH 3/4] add no cache to test, use feature detection promise --- src/es-module-shims.js | 4 +--- test/server.mjs | 4 ++-- 2 files changed, 3 insertions(+), 5 deletions(-) diff --git a/src/es-module-shims.js b/src/es-module-shims.js index dfe0f653..129b8d02 100755 --- a/src/es-module-shims.js +++ b/src/es-module-shims.js @@ -64,9 +64,7 @@ async function topLevelLoad (url, source, polyfill) { } async function importShim (id, parentUrl = pageBaseUrl) { - // Give dynamically inserted import maps a chance to be processed - // if `importShim()` was called synchronously after the insertion. - await new Promise(resolve => setTimeout(resolve, 0)) + await featureDetectionPromise; // Make sure all the "in-flight" import maps are loaded and applied. await importMapPromise; return topLevelLoad(resolve(id, parentUrl).r || throwUnresolved(id, parentUrl)); diff --git a/test/server.mjs b/test/server.mjs index 4bc00089..1d8bfa35 100644 --- a/test/server.mjs +++ b/test/server.mjs @@ -94,7 +94,7 @@ http.createServer(async function (req, res) { mime = mimes[path.extname(filePath)] || 'text/plain'; const headers = filePath.endsWith('content-type-none.json') ? - {} : { 'content-type': mime } + {} : { 'content-type': mime, 'Cache-Control': 'no-cache' } res.writeHead(200, headers); fileStream.pipe(res); @@ -104,4 +104,4 @@ http.createServer(async function (req, res) { console.log(`Test server listening on http://localhost:${port}\n`); const openOptions = process.env.CI ? { app: ['firefox'] } : {}; -open(`http://localhost:${port}/test/${testName}.html`, openOptions); \ No newline at end of file +open(`http://localhost:${port}/test/${testName}.html`, openOptions); From e0820198b43d38ce64d48343b36e8986ded9eee2 Mon Sep 17 00:00:00 2001 From: Vladimir Guguiev Date: Fri, 14 May 2021 15:17:08 +0200 Subject: [PATCH 4/4] Add delayed insert-import to the dynamic-import-map-shim test --- test/dynamic-import-map-shim.js | 42 ++++++++++++++++++++++----------- 1 file changed, 28 insertions(+), 14 deletions(-) diff --git a/test/dynamic-import-map-shim.js b/test/dynamic-import-map-shim.js index 238c9b7c..2ddd0fb3 100644 --- a/test/dynamic-import-map-shim.js +++ b/test/dynamic-import-map-shim.js @@ -1,20 +1,16 @@ main(); async function main() { - // Append a dynamic import map containing resolution for "react-dom". - document.body.appendChild(Object.assign(document.createElement('script'), { - type: 'importmap-shim', - innerHTML: JSON.stringify({ - "imports": { - "react-dom": "https://ga.jspm.io/npm:react-dom@17.0.2/dev.index.js" - }, - "scopes": { - "https://ga.jspm.io/": { - "scheduler": "https://ga.jspm.io/npm:scheduler@0.20.2/dev.index.js", - "scheduler/tracing": "https://ga.jspm.io/npm:scheduler@0.20.2/dev.tracing.js" - } + insertDynamicImportMap({ + "imports": { + "react-dom": "https://ga.jspm.io/npm:react-dom@17.0.2/dev.index.js" + }, + "scopes": { + "https://ga.jspm.io/": { + "scheduler": "https://ga.jspm.io/npm:scheduler@0.20.2/dev.index.js", + "scheduler/tracing": "https://ga.jspm.io/npm:scheduler@0.20.2/dev.tracing.js" } - }) - })); + } + }); const reactStart = performance.now(); const [React, ReactDOM] = await Promise.all([ @@ -25,4 +21,22 @@ async function main() { console.log(`React and ReactDOM loaded in ${(reactEnd - reactStart).toFixed(2)}ms`); console.log({ React, ReactDOM }); + + setTimeout(async () => { + insertDynamicImportMap({ + "imports": { + "lodash": "https://ga.jspm.io/npm:lodash-es@4.17.21/lodash.js", + } + }) + + const lodash = await importShim("lodash") + console.log({ lodash }) + }, 1000) +} + +function insertDynamicImportMap(importMap) { + document.body.appendChild(Object.assign(document.createElement('script'), { + type: 'importmap-shim', + innerHTML: JSON.stringify(importMap) + })); }