From fc6d8f1d3efe836f17f3c45375dd3749128b8137 Mon Sep 17 00:00:00 2001 From: Roman Date: Fri, 23 Jul 2021 10:38:07 +0500 Subject: [PATCH] fix(plugin-legacy): legacy fallback for dynamic import (#3885) --- packages/plugin-legacy/README.md | 12 +++++-- packages/plugin-legacy/index.js | 61 ++++++++++++++++++++------------ 2 files changed, 49 insertions(+), 24 deletions(-) diff --git a/packages/plugin-legacy/README.md b/packages/plugin-legacy/README.md index 208c1352f845df..ae0c3de6516b3f 100644 --- a/packages/plugin-legacy/README.md +++ b/packages/plugin-legacy/README.md @@ -122,6 +122,13 @@ export default { } ``` +## Dynamic Import + +The legacy plugin offers a way to use native `import()` in the modern build while falling back to the legacy build in browsers with native ESM but without dynamic import support (e.g. Legacy Edge). This feature works by injecting a runtime check and loading the legacy bundle with SystemJs runtime if needed. There are the following drawbacks: + +- Modern bundle is downloaded in all ESM browsers +- Modern bundle throws `SyntaxError` in browsers without dynamic import + ## Polyfill Specifiers Polyfill specifier strings for `polyfills` and `modernPolyfills` can be either of the following: @@ -147,12 +154,13 @@ export default { ## Content Security Policy -The legacy plugin requires inline scripts for [Safari 10.1 `nomodule` fix](https://gist.github.com/samthor/64b114e4a4f539915a95b91ffd340acc) and SystemJS initialization. If you have a strict CSP policy requirement, you will need to [add the corresponding hashes to your `script-src` list](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Security-Policy/script-src#unsafe_inline_script): +The legacy plugin requires inline scripts for [Safari 10.1 `nomodule` fix](https://gist.github.com/samthor/64b114e4a4f539915a95b91ffd340acc), SystemJS initialization, and dynamic import fallback. If you have a strict CSP policy requirement, you will need to [add the corresponding hashes to your `script-src` list](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Security-Policy/script-src#unsafe_inline_script): - `MS6/3FCg4WjP9gwgaBGwLpRCY6fZBgwmhVCdrPrNf3E=` - `tQjf8gvb2ROOMapIxFvFAYBeUJ0v1HCbOcSmDNXGtDo=` +- `T9h4ixy0FtNsCwAyTfBtIY6uV5ZhMeNQIlL42GAKEME=` -These values can also be retrived via +These values can also be retrieved via ```js const { cspHashes } = require('@vitejs/plugin-legacy') diff --git a/packages/plugin-legacy/index.js b/packages/plugin-legacy/index.js index 44d1bbc4e00ebc..0e9f0eec0803a8 100644 --- a/packages/plugin-legacy/index.js +++ b/packages/plugin-legacy/index.js @@ -15,8 +15,12 @@ const loadBabel = () => babel || (babel = require('@babel/standalone')) // DO NOT ALTER THIS CONTENT const safari10NoModuleFix = `!function(){var e=document,t=e.createElement("script");if(!("noModule"in t)&&"onbeforeload"in t){var n=!1;e.addEventListener("beforeload",(function(e){if(e.target===t)n=!0;else if(!e.target.hasAttribute("nomodule")||!n)return;e.preventDefault()}),!0),t.type="module",t.src=".",e.head.appendChild(t),t.remove()}}();` +const legacyPolyfillId = 'vite-legacy-polyfill' const legacyEntryId = 'vite-legacy-entry' const systemJSInlineCode = `System.import(document.getElementById('${legacyEntryId}').getAttribute('data-src'))` +const dynamicFallbackInlineCode = `!function(){try{new Function("m","return import(m)")}catch(o){console.warn("vite: loading legacy build because dynamic import is unsupported, syntax error above should be ignored");var e=document.getElementById("${legacyPolyfillId}"),n=document.createElement("script");n.src=e.src,n.onload=function(){${systemJSInlineCode}},document.body.appendChild(n)}}();` + +const blankDynamicImport = `import('data:text/javascript;base64,Cg==');` const legacyEnvVarMarker = `__VITE_IS_LEGACY__` @@ -31,6 +35,7 @@ function viteLegacyPlugin(options = {}) { let config const targets = options.targets || 'defaults' const genLegacy = options.renderLegacyChunks !== false + const genDynamicFallback = genLegacy const debugFlag = process.env.DEBUG const isDebug = debugFlag === 'vite:*' || debugFlag === 'vite:legacy' @@ -76,9 +81,6 @@ function viteLegacyPlugin(options = {}) { if (!config.build) { config.build = {} } - if (genLegacy) { - config.build.polyfillDynamicImport = true - } } } @@ -123,7 +125,7 @@ function viteLegacyPlugin(options = {}) { } // legacy bundle - if (legacyPolyfills.size) { + if (legacyPolyfills.size || genDynamicFallback) { if (!legacyPolyfills.has('es.promise')) { // check if the target needs Promise polyfill because SystemJS relies // on it @@ -228,28 +230,31 @@ function viteLegacyPlugin(options = {}) { detectPolyfills(raw, { esmodules: true }, modernPolyfills) } + const ms = new MagicString(raw) + + if (genDynamicFallback && chunk.isEntry) { + ms.prepend(blankDynamicImport) + } + if (raw.includes(legacyEnvVarMarker)) { const re = new RegExp(legacyEnvVarMarker, 'g') - if (config.build.sourcemap) { - const s = new MagicString(raw) - let match - while ((match = re.exec(raw))) { - s.overwrite( - match.index, - match.index + legacyEnvVarMarker.length, - `false` - ) - } - return { - code: s.toString(), - map: s.generateMap({ hires: true }) - } - } else { - return raw.replace(re, `false`) + let match + while ((match = re.exec(raw))) { + ms.overwrite( + match.index, + match.index + legacyEnvVarMarker.length, + `false` + ) } } - return null + if (config.build.sourcemap) { + return { + code: ms.toString(), + map: ms.generateMap({ hires: true }) + } + } + return ms.toString() } if (!genLegacy) { @@ -358,6 +363,7 @@ function viteLegacyPlugin(options = {}) { tag: 'script', attrs: { nomodule: true, + id: legacyPolyfillId, src: `${config.base}${legacyPolyfillFilename}` }, injectTo: 'body' @@ -392,6 +398,16 @@ function viteLegacyPlugin(options = {}) { ) } + // 5. inject dynamic import fallback entry + if (genDynamicFallback && legacyPolyfillFilename && legacyEntryFilename) { + tags.push({ + tag: 'script', + attrs: { type: 'module' }, + children: dynamicFallbackInlineCode, + injectTo: 'head' + }) + } + return { html, tags @@ -639,5 +655,6 @@ viteLegacyPlugin.default = viteLegacyPlugin viteLegacyPlugin.cspHashes = [ createHash('sha256').update(safari10NoModuleFix).digest('base64'), - createHash('sha256').update(systemJSInlineCode).digest('base64') + createHash('sha256').update(systemJSInlineCode).digest('base64'), + createHash('sha256').update(dynamicFallbackInlineCode).digest('base64') ]