Skip to content

Commit 4112c5d

Browse files
authored
fix: restore dynamic-import-polyfill (#3434)
1 parent f6cfe30 commit 4112c5d

File tree

8 files changed

+186
-31
lines changed

8 files changed

+186
-31
lines changed

docs/config/index.md

+17
Original file line numberDiff line numberDiff line change
@@ -480,6 +480,23 @@ export default async ({ command, mode }) => {
480480

481481
Note the build will fail if the code contains features that cannot be safely transpiled by esbuild. See [esbuild docs](https://esbuild.github.io/content-types/#javascript) for more details.
482482

483+
### build.polyfillDynamicImport
484+
485+
- **Type:** `boolean`
486+
- **Default:** `false`
487+
488+
Whether to automatically inject [dynamic import polyfill](https://github.com/GoogleChromeLabs/dynamic-import-polyfill).
489+
490+
If set to true, the polyfill is auto injected into the proxy module of each `index.html` entry. If the build is configured to use a non-html custom entry via `build.rollupOptions.input`, then it is necessary to manually import the polyfill in your custom entry:
491+
492+
```js
493+
import 'vite/dynamic-import-polyfill'
494+
```
495+
496+
When using [`@vitejs/plugin-legacy`](https://github.com/vitejs/vite/tree/main/packages/plugin-legacy), the plugin sets this option to `true` automatically.
497+
498+
Note: the polyfill does **not** apply to [Library Mode](/guide/build#library-mode). If you need to support browsers without native dynamic import, you should probably avoid using it in your library.
499+
483500
### build.outDir
484501

485502
- **Type:** `string`

docs/guide/backend-integration.md

+7
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,13 @@ Or you can follow these steps to configure it manually:
2020
}
2121
```
2222

23+
If you use [`@vitejs/plugin-legacy`](https://github.com/vitejs/vite/tree/main/packages/plugin-legacy) or manually enable the [`build.dynamicImportPolyfill` option](/config/#build-polyfilldynamicimport), remember to add the [dynamic import polyfill](/config/#build-polyfilldynamicimport) to your entry, since it will no longer be auto-injected:
24+
25+
```js
26+
// add the beginning of your app entry
27+
import 'vite/dynamic-import-polyfill'
28+
```
29+
2330
2. For development, inject the following in your server's HTML template (substitute `http://localhost:3000` with the local URL Vite is running at):
2431

2532
```html

packages/plugin-legacy/index.js

+21-1
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,21 @@ function viteLegacyPlugin(options = {}) {
6565
})
6666
}
6767

68+
/**
69+
* @type {import('vite').Plugin}
70+
*/
71+
const legacyConfigPlugin = {
72+
name: 'legacy-config',
73+
74+
apply: 'build',
75+
config(config) {
76+
if (!config.build) {
77+
config.build = {}
78+
}
79+
config.build.polyfillDynamicImport = true
80+
}
81+
}
82+
6883
/**
6984
* @type {import('vite').Plugin}
7085
*/
@@ -398,7 +413,12 @@ function viteLegacyPlugin(options = {}) {
398413
}
399414
}
400415

401-
return [legacyGenerateBundlePlugin, legacyPostPlugin, legacyEnvPlugin]
416+
return [
417+
legacyConfigPlugin,
418+
legacyGenerateBundlePlugin,
419+
legacyPostPlugin,
420+
legacyEnvPlugin
421+
]
402422
}
403423

404424
/**

packages/vite/src/node/build.ts

+3-4
Original file line numberDiff line numberDiff line change
@@ -64,7 +64,7 @@ export interface BuildOptions {
6464
/**
6565
* whether to inject dynamic import polyfill.
6666
* Note: does not apply to library mode.
67-
* @deprecated the dynamic import polyfill has been removed
67+
* @default false
6868
*/
6969
polyfillDynamicImport?: boolean
7070
/**
@@ -194,13 +194,12 @@ export interface LibraryOptions {
194194

195195
export type LibraryFormats = 'es' | 'cjs' | 'umd' | 'iife'
196196

197-
export type ResolvedBuildOptions = Required<
198-
Omit<BuildOptions, 'base' | 'polyfillDynamicImport'>
199-
>
197+
export type ResolvedBuildOptions = Required<Omit<BuildOptions, 'base'>>
200198

201199
export function resolveBuildOptions(raw?: BuildOptions): ResolvedBuildOptions {
202200
const resolved: ResolvedBuildOptions = {
203201
target: 'modules',
202+
polyfillDynamicImport: false,
204203
outDir: 'dist',
205204
assetsDir: 'assets',
206205
assetsInlineLimit: 4096,

packages/vite/src/node/config.ts

-19
Original file line numberDiff line numberDiff line change
@@ -470,25 +470,6 @@ export async function resolveConfig(
470470
}
471471
})
472472

473-
if (config.build?.polyfillDynamicImport) {
474-
logDeprecationWarning(
475-
'build.polyfillDynamicImport',
476-
'"polyfillDynamicImport" has been removed. Please use @vitejs/plugin-legacy if your target browsers do not support dynamic imports.'
477-
)
478-
}
479-
480-
Object.defineProperty(resolvedBuildOptions, 'polyfillDynamicImport', {
481-
enumerable: false,
482-
get() {
483-
logDeprecationWarning(
484-
'build.polyfillDynamicImport',
485-
'"polyfillDynamicImport" has been removed. Please use @vitejs/plugin-legacy if your target browsers do not support dynamic imports.',
486-
new Error()
487-
)
488-
return false
489-
}
490-
})
491-
492473
if (config.alias) {
493474
logDeprecationWarning('alias', 'Use "resolve.alias" instead.')
494475
}
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,27 @@
11
import { ResolvedConfig } from '..'
22
import { Plugin } from '../plugin'
3+
import { isModernFlag } from './importAnalysisBuild'
4+
import path from 'path'
35

46
export const polyfillId = 'vite/dynamic-import-polyfill'
57

6-
/**
7-
* @deprecated
8-
*/
8+
function resolveModulePath(config: ResolvedConfig) {
9+
const {
10+
base,
11+
build: { assetsDir }
12+
} = config
13+
// #2918 path.posix.join returns a wrong path when config.base is a URL
14+
if (/^(https?:)?(\/\/)/i.test(base)) {
15+
return `${base.replace(/\/$/, '')}/${assetsDir}/`
16+
}
17+
return path.posix.join(base, assetsDir, '/')
18+
}
19+
920
export function dynamicImportPolyfillPlugin(config: ResolvedConfig): Plugin {
21+
const enabled = config.build.polyfillDynamicImport
22+
const skip = !enabled || config.command === 'serve' || config.build.ssr
23+
let polyfillString: string | undefined
24+
1025
return {
1126
name: 'vite:dynamic-import-polyfill',
1227
resolveId(id) {
@@ -16,11 +31,114 @@ export function dynamicImportPolyfillPlugin(config: ResolvedConfig): Plugin {
1631
},
1732
load(id) {
1833
if (id === polyfillId) {
19-
config.logger.warn(
20-
`\n'vite/dynamic-import-polyfill' is no longer needed, refer to https://github.com/vitejs/vite/blob/main/packages/vite/CHANGELOG.md#230-2021-05-10`
34+
if (!enabled) {
35+
config.logger.warnOnce(
36+
`\n'vite/dynamic-import-polyfill' is no longer needed if you target modern browsers`
37+
)
38+
}
39+
if (skip) {
40+
return ''
41+
}
42+
// return a placeholder here and defer the injection to renderChunk
43+
// so that we can selectively skip the injection based on output format
44+
if (!polyfillString) {
45+
polyfillString =
46+
`const p = ${polyfill.toString()};` +
47+
`${isModernFlag}&&p(${JSON.stringify(resolveModulePath(config))});`
48+
}
49+
return polyfillString
50+
}
51+
},
52+
53+
renderDynamicImport({ format }) {
54+
if (skip || format !== 'es') {
55+
return null
56+
}
57+
if (!polyfillString) {
58+
throw new Error(
59+
`Vite's dynamic import polyfill is enabled but was never imported. This ` +
60+
`should only happen when using custom non-html rollup inputs. Make ` +
61+
`sure to add \`import "${polyfillId}"\` as the first statement in ` +
62+
`your custom entry.`
2163
)
22-
return ''
2364
}
65+
// we do not actually return anything here because rewriting here would
66+
// make it impossible to use es-module-lexer on the rendered chunks, which
67+
// we need for import graph optimization in ./importAnalysisBuild.
2468
}
2569
}
2670
}
71+
72+
/**
73+
The following polyfill function is meant to run in the browser and adapted from
74+
https://github.com/GoogleChromeLabs/dynamic-import-polyfill
75+
MIT License
76+
Copyright (c) 2018 uupaa and 2019 Google LLC
77+
Permission is hereby granted, free of charge, to any person obtaining a copy
78+
of this software and associated documentation files (the "Software"), to deal
79+
in the Software without restriction, including without limitation the rights
80+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
81+
copies of the Software, and to permit persons to whom the Software is
82+
furnished to do so, subject to the following conditions:
83+
The above copyright notice and this permission notice shall be included in all
84+
copies or substantial portions of the Software.
85+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
86+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
87+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
88+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
89+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
90+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
91+
*/
92+
93+
declare const self: any
94+
declare const location: any
95+
declare const document: any
96+
declare const URL: any
97+
declare const Blob: any
98+
99+
function polyfill(modulePath = '.', importFunctionName = '__import__') {
100+
try {
101+
self[importFunctionName] = new Function('u', `return import(u)`)
102+
} catch (error) {
103+
const baseURL = new URL(modulePath, location)
104+
const cleanup = (script: any) => {
105+
URL.revokeObjectURL(script.src)
106+
script.remove()
107+
}
108+
109+
self[importFunctionName] = (url: string) =>
110+
new Promise((resolve, reject) => {
111+
const absURL = new URL(url, baseURL)
112+
113+
// If the module has already been imported, resolve immediately.
114+
if (self[importFunctionName].moduleMap[absURL]) {
115+
return resolve(self[importFunctionName].moduleMap[absURL])
116+
}
117+
118+
const moduleBlob = new Blob(
119+
[
120+
`import * as m from '${absURL}';`,
121+
`${importFunctionName}.moduleMap['${absURL}']=m;`
122+
],
123+
{ type: 'text/javascript' }
124+
)
125+
126+
const script = Object.assign(document.createElement('script'), {
127+
type: 'module',
128+
src: URL.createObjectURL(moduleBlob),
129+
onerror() {
130+
reject(new Error(`Failed to import: ${url}`))
131+
cleanup(script)
132+
},
133+
onload() {
134+
resolve(self[importFunctionName].moduleMap[absURL])
135+
cleanup(script)
136+
}
137+
})
138+
139+
document.head.appendChild(script)
140+
})
141+
142+
self[importFunctionName].moduleMap = {}
143+
}
144+
}

packages/vite/src/node/plugins/html.ts

+7
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ import {
2020
getAssetFilename
2121
} from './asset'
2222
import { isCSSRequest, chunkToEmittedCssFileMap } from './css'
23+
import { polyfillId } from './dynamicImportPolyfill'
2324
import {
2425
AttributeNode,
2526
NodeTransform,
@@ -262,6 +263,12 @@ export function buildHtmlPlugin(config: ResolvedConfig): Plugin {
262263
}
263264

264265
processedHtml.set(id, s.toString())
266+
267+
// inject dynamic import polyfill
268+
if (config.build.polyfillDynamicImport) {
269+
js = `import "${polyfillId}";\n${js}`
270+
}
271+
265272
return js
266273
}
267274
},

packages/vite/src/node/plugins/importAnalysisBuild.ts

+7-1
Original file line numberDiff line numberDiff line change
@@ -204,6 +204,7 @@ export function buildImportAnalysisPlugin(config: ResolvedConfig): Plugin {
204204
return
205205
}
206206

207+
const isPolyfillEnabled = config.build.polyfillDynamicImport
207208
for (const file in bundle) {
208209
const chunk = bundle[file]
209210
// can't use chunk.dynamicImports.length here since some modules e.g.
@@ -220,7 +221,12 @@ export function buildImportAnalysisPlugin(config: ResolvedConfig): Plugin {
220221
if (imports.length) {
221222
const s = new MagicString(code)
222223
for (let index = 0; index < imports.length; index++) {
223-
const { s: start, e: end } = imports[index]
224+
const { s: start, e: end, d: dynamicIndex } = imports[index]
225+
// if dynamic import polyfill is used, rewrite the import to
226+
// use the polyfilled function.
227+
if (isPolyfillEnabled) {
228+
s.overwrite(dynamicIndex, dynamicIndex + 6, `__import__`)
229+
}
224230
// check the chunk being imported
225231
const url = code.slice(start, end)
226232
const deps: Set<string> = new Set()

0 commit comments

Comments
 (0)