mirror of
https://github.com/vitejs/vite.git
synced 2024-11-22 07:09:05 +00:00
feat(build): default build target to 'modules' with dynamic import polyfill
This commit is contained in:
parent
0fad96e5cb
commit
756e90ff9b
@ -276,12 +276,31 @@ export default ({ command, mode }) => {
|
||||
### build.target
|
||||
|
||||
- **Type:** `string`
|
||||
- **Default:** `es2020`
|
||||
- **Default:** `'modules'`
|
||||
- **Related:** [Browser Compatibility](/guide/build#browser-compatibility)
|
||||
|
||||
Browser compatibility target for the final bundle. The transform is performed with esbuild and the lowest supported target is `es2015`. The target can also be a browser with version, e.g. `chrome58` or `safari11`, or an array of multiple target strings.
|
||||
Browser compatibility target for the final bundle. The default value is a Vite special value, `'modules'`, which targets [browsers with native ES module support](https://caniuse.com/es6-module).
|
||||
|
||||
Note the build will fail if the code contains features that cannot be safely transpiled by `esbuild`. See [esbuid docs](https://esbuild.github.io/api/#target) for more details.
|
||||
Another special value is 'esnext' - which only performs minimal trasnpiling (for minification compat) and assumes native dynamic imports support.
|
||||
|
||||
The transform is performed with esbuild and the value should be a valid [esbuild target option](https://esbuild.github.io/api/#target). Custom targets can either be a ES version (e.g. `es2015`), a browser with version (e.g. `chrome58`), or an array of multiple target strings.
|
||||
|
||||
Note the build will fail if the code contains features that cannot be safely transpiled by esbuild. See [esbuid docs](https://esbuild.github.io/content-types/#javascript) for more details.
|
||||
|
||||
### build.dynamicImportPolyfill
|
||||
|
||||
- **Type:** `boolean`
|
||||
- **Default:** `true` unless `build.target` is `'esnext'`
|
||||
|
||||
Whether to automatically inject [dynamic import polyfill](https://github.com/GoogleChromeLabs/dynamic-import-polyfill).
|
||||
|
||||
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:
|
||||
|
||||
```js
|
||||
import 'vite/dynamic-import-polyfill'
|
||||
```
|
||||
|
||||
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.
|
||||
|
||||
### build.outDir
|
||||
|
||||
|
@ -17,6 +17,13 @@ If you want to serve the HTML using a traditional backend (e.g. Rails, Laravel)
|
||||
}
|
||||
```
|
||||
|
||||
Also remember to add the [dynamic import polyfill](/config/#build-dynamicimportpolyfill) to your entry, since it will no longer be auto-injected:
|
||||
|
||||
```js
|
||||
// add the beginning of your app entry
|
||||
import 'vite/dynamic-import-polyfill'
|
||||
```
|
||||
|
||||
2. For development, inject the following in your server's HTML template (substitute `http://localhost:3000` with the local URL Vite is running at):
|
||||
|
||||
```html
|
||||
@ -31,6 +38,6 @@ If you want to serve the HTML using a traditional backend (e.g. Rails, Laravel)
|
||||
|
||||
```html
|
||||
<!-- if production -->
|
||||
<link rel="stylesheet" href="/assets/{{ manifest['style.css'].file }}" />
|
||||
<link rel="stylesheet" href="/assets/{{ manifest['index.css'].file }}" />
|
||||
<script type="module" src="/assets/{{ manifest['index.js'].file }}"></script>
|
||||
```
|
||||
|
@ -4,7 +4,18 @@ When it is time to deploy your app for production, simply run the `vite build` c
|
||||
|
||||
## Browser Compatibility
|
||||
|
||||
The production bundle assumes a baseline support for [Native ES modules dynamic imports](https://caniuse.com/es6-module-dynamic-import). By default, all code is minimally transpiled with target `es2020` (only for terser minification compatibility). You can specify the target range via the [`build.target` config option](/config/#build-target), where the lowest target available is `es2015`.
|
||||
The production bundle assumes a baseline support for modern JavaScript. By default, all code is transpiled targeting [browsers with native ESM script tag support](https://caniuse.com/es6-module):
|
||||
|
||||
- Chrome >=61
|
||||
- Firefox >=60
|
||||
- Safari >=11
|
||||
- Edge >=16
|
||||
|
||||
A lightweight [dynamic import polyfill](https://github.com/GoogleChromeLabs/dynamic-import-polyfill) is also automatically injected.
|
||||
|
||||
You can specify custom targets via the [`build.target` config option](/config/#build-target), where the lowest target is `es2015`.
|
||||
|
||||
Note that Vite only handles syntax transforms and **does not cover polyfills**. You can check out [Polyfill.io](https://polyfill.io/v3/) to build custom polyfill bundles.
|
||||
|
||||
Legacy browsers _can_ be supported via plugins that post-process the build output for compatibility (e.g. [`@rollup/plugin-babel`](https://github.com/rollup/plugins/tree/master/packages/babel) + [`@babel/preset-env`](https://babeljs.io/docs/en/babel-preset-env) + [SystemJS](https://github.com/systemjs/systemjs)). This is not a built-in feature, but there is plan to provide an official plugin that automatically emits a separate legacy bundle.
|
||||
|
||||
|
@ -44,6 +44,6 @@ Ensuring optimal output and behavioral consistency between the dev server and th
|
||||
|
||||
## Browser Support
|
||||
|
||||
- Vite requires [native ES module support](https://caniuse.com/#feat=es6-module) during development.
|
||||
- Vite requires [native ESM dynamic import support](https://caniuse.com/es6-module-dynamic-import) during development.
|
||||
|
||||
- The production build assumes a baseline support for [Native ES modules dynamic imports](https://caniuse.com/es6-module-dynamic-import). Legacy browsers can be supported via plugins that post-process the build output for compatibility. More details in the [Building for Production](./build) section.
|
||||
- The production build assumes a baseline support for [Native ESM via script tags](https://caniuse.com/es6-module), similar to [`targets.esmodules` of `@babel/preset-env`](https://babeljs.io/docs/en/babel-preset-env#targetsesmodules). Legacy browsers can be supported via plugins that post-process the build output for compatibility. More details in the [Building for Production](./build) section.
|
||||
|
@ -35,10 +35,28 @@ export interface BuildOptions {
|
||||
base?: string
|
||||
/**
|
||||
* Compatibility transform target. The transform is performed with esbuild
|
||||
* and the lowest supported target is es2015/es6. Default: es2020
|
||||
* https://esbuild.github.io/api/#target
|
||||
* and the lowest supported target is es2015/es6. Note this only handles
|
||||
* syntax transformation and does not cover polyfills (except for dynamic
|
||||
* import)
|
||||
*
|
||||
* Default: 'modules' - Similar to `@babel/preset-env`'s targets.esmodules,
|
||||
* transpile targeting browsers that natively support es module imports. Also
|
||||
* injects a light-weight dynamic import polyfill.
|
||||
* https://caniuse.com/es6-module
|
||||
*
|
||||
* Another special value is 'esnext' - which only performs minimal trasnpiling
|
||||
* (for minification compat) and assumes native dynamic imports support.
|
||||
*
|
||||
* For custom targets, see https://esbuild.github.io/api/#target and
|
||||
* https://esbuild.github.io/content-types/#javascript for more details.
|
||||
*/
|
||||
target?: TransformOptions['target'] | false
|
||||
target?: 'modules' | TransformOptions['target'] | false
|
||||
/**
|
||||
* Whether to inject dynamic import polyfill. Defaults to `true`, unless
|
||||
* `target` is `'esnext'`.
|
||||
* Note: does not apply to library mode.
|
||||
*/
|
||||
polyfillDynamicImport?: boolean
|
||||
/**
|
||||
* Directory relative from `root` where build output will be placed. If the
|
||||
* directory exists, it will be removed before the build.
|
||||
@ -136,7 +154,8 @@ export function resolveBuildOptions(
|
||||
): Required<BuildOptions> {
|
||||
const resolved: Required<BuildOptions> = {
|
||||
base: '/',
|
||||
target: 'es2019',
|
||||
target: 'modules',
|
||||
polyfillDynamicImport: raw?.target !== 'esnext' && !raw?.lib,
|
||||
outDir: 'dist',
|
||||
assetsDir: 'assets',
|
||||
assetsInlineLimit: 4096,
|
||||
@ -153,6 +172,15 @@ export function resolveBuildOptions(
|
||||
...raw
|
||||
}
|
||||
|
||||
// handle special build targets
|
||||
if (resolved.target === 'modules') {
|
||||
// https://caniuse.com/es6-module
|
||||
resolved.target = ['es2019', 'edge16', 'firefox60', 'chrome61', 'safari11']
|
||||
} else if (resolved.target === 'esnext' && resolved.minify !== 'esbuild') {
|
||||
// esnext + terser: limit to es2019 so it can be minified by terser
|
||||
resolved.target = 'es2019'
|
||||
}
|
||||
|
||||
// ensure base ending slash
|
||||
resolved.base = resolved.base.replace(/([^/])$/, '$1/')
|
||||
|
||||
|
@ -93,24 +93,21 @@ cli
|
||||
// build
|
||||
cli
|
||||
.command('build [root]')
|
||||
.option(
|
||||
'--entry <file>',
|
||||
`[string] entry file for build (default: index.html)`
|
||||
)
|
||||
.option('--base <path>', `[string] public base path (default: /)`)
|
||||
.option('--outDir <dir>', `[string] output directory (default: dist)`)
|
||||
.option('--base <path>', `[string] public base path (default: /)`)
|
||||
.option('--target <target>', `[string] transpile target (default: 'modules')`)
|
||||
.option('--outDir <dir>', `[string] output directory (default: dist)`)
|
||||
.option(
|
||||
'--assetsDir <dir>',
|
||||
`[string] directory under outDir to place assets in (default: _assets)`
|
||||
`[string] directory under outDir to place assets in (default: _assets)`
|
||||
)
|
||||
.option(
|
||||
'--assetsInlineLimit <number>',
|
||||
`[number] static asset base64 inline threshold in bytes (default: 4096)`
|
||||
`[number] static asset base64 inline threshold in bytes (default: 4096)`
|
||||
)
|
||||
.option('--ssr', `[boolean] build for server-side rendering`)
|
||||
.option('--ssr', `[boolean] build for server-side rendering`)
|
||||
.option(
|
||||
'--sourcemap',
|
||||
`[boolean] output source maps for build (default: false)`
|
||||
`[boolean] output source maps for build (default: false)`
|
||||
)
|
||||
.option(
|
||||
'--minify [minifier]',
|
||||
|
126
packages/vite/src/node/plugins/dynamicImportPolyfill.ts
Normal file
126
packages/vite/src/node/plugins/dynamicImportPolyfill.ts
Normal file
@ -0,0 +1,126 @@
|
||||
import { ResolvedConfig } from '..'
|
||||
import { Plugin } from '../plugin'
|
||||
|
||||
export const polyfillId = 'vite/dynamic-import-polyfill'
|
||||
|
||||
export function dynamicImportPolyfillPlugin(config: ResolvedConfig): Plugin {
|
||||
const skip = config.command === 'serve' || config.build.ssr
|
||||
let polyfillLoaded = false
|
||||
|
||||
return {
|
||||
name: 'vite:dynamic-import-polyfill',
|
||||
resolveId(id) {
|
||||
if (id === polyfillId) {
|
||||
return id
|
||||
}
|
||||
},
|
||||
load(id) {
|
||||
if (id === polyfillId) {
|
||||
if (skip) {
|
||||
return ''
|
||||
}
|
||||
polyfillLoaded = true
|
||||
return (
|
||||
polyfill.toString() +
|
||||
`;polyfill(${JSON.stringify(config.build.base)});`
|
||||
)
|
||||
}
|
||||
},
|
||||
renderDynamicImport() {
|
||||
if (skip) {
|
||||
return null
|
||||
}
|
||||
if (!polyfillLoaded) {
|
||||
throw new Error(
|
||||
`Vite's dynamic import polyfill is enabled but was never imported. This ` +
|
||||
`should only happen when using custom non-html rollup inputs. Make ` +
|
||||
`sure to add \`import "${polyfillId}"\` as the first statement in ` +
|
||||
`your custom entry.`
|
||||
)
|
||||
}
|
||||
return {
|
||||
left: '__import__(',
|
||||
right: ')'
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
The following polyfill function is meant to run in the browser and adapted from
|
||||
https://github.com/GoogleChromeLabs/dynamic-import-polyfill
|
||||
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2018 uupaa and 2019 Google LLC
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
*/
|
||||
|
||||
declare const self: any
|
||||
declare const location: any
|
||||
declare const document: any
|
||||
declare const URL: any
|
||||
declare const Blob: any
|
||||
|
||||
function polyfill(modulePath = '.', importFunctionName = '__import__') {
|
||||
try {
|
||||
self[importFunctionName] = new Function('u', `return import(u)`)
|
||||
} catch (error) {
|
||||
const baseURL = new URL(modulePath, location)
|
||||
const cleanup = (script: any) => {
|
||||
URL.revokeObjectURL(script.src)
|
||||
script.remove()
|
||||
}
|
||||
|
||||
self[importFunctionName] = (url: string) =>
|
||||
new Promise((resolve, reject) => {
|
||||
const absURL = new URL(url, baseURL)
|
||||
|
||||
// If the module has already been imported, resolve immediately.
|
||||
if (self[importFunctionName].moduleMap[absURL]) {
|
||||
return resolve(self[importFunctionName].moduleMap[absURL])
|
||||
}
|
||||
|
||||
const moduleBlob = new Blob(
|
||||
[
|
||||
`import * as m from '${absURL}';`,
|
||||
`${importFunctionName}.moduleMap['${absURL}']=m;`
|
||||
],
|
||||
{ type: 'text/javascript' }
|
||||
)
|
||||
|
||||
const script = Object.assign(document.createElement('script'), {
|
||||
type: 'module',
|
||||
src: URL.createObjectURL(moduleBlob),
|
||||
onerror() {
|
||||
reject(new Error(`Failed to import: ${url}`))
|
||||
cleanup(script)
|
||||
},
|
||||
onload() {
|
||||
resolve(self[importFunctionName].moduleMap[absURL])
|
||||
cleanup(script)
|
||||
}
|
||||
})
|
||||
|
||||
document.head.appendChild(script)
|
||||
})
|
||||
|
||||
self[importFunctionName].moduleMap = {}
|
||||
}
|
||||
}
|
@ -16,6 +16,7 @@ import {
|
||||
import MagicString from 'magic-string'
|
||||
import { checkPublicFile, assetUrlRE, urlToBuiltUrl } from './asset'
|
||||
import { isCSSRequest, chunkToEmittedCssFileMap } from './css'
|
||||
import { polyfillId } from './dynamicImportPolyfill'
|
||||
|
||||
const htmlProxyRE = /\?html-proxy&index=(\d+)\.js$/
|
||||
export const isHTMLProxy = (id: string) => htmlProxyRE.test(id)
|
||||
@ -208,6 +209,12 @@ export function buildHtmlPlugin(config: ResolvedConfig): Plugin {
|
||||
}
|
||||
|
||||
processedHtml.set(id, s.toString())
|
||||
|
||||
// inject dynamic import polyfill
|
||||
if (config.build.polyfillDynamicImport) {
|
||||
js = `import "${polyfillId}";\n${js}`
|
||||
}
|
||||
|
||||
return js
|
||||
}
|
||||
},
|
||||
|
@ -11,6 +11,7 @@ import { clientInjectionsPlugin } from './clientInjections'
|
||||
import { htmlPlugin } from './html'
|
||||
import { wasmPlugin } from './wasm'
|
||||
import { webWorkerPlugin } from './worker'
|
||||
import { dynamicImportPolyfillPlugin } from './dynamicImportPolyfill'
|
||||
|
||||
export async function resolvePlugins(
|
||||
config: ResolvedConfig,
|
||||
@ -26,6 +27,9 @@ export async function resolvePlugins(
|
||||
|
||||
return [
|
||||
aliasPlugin({ entries: config.alias }),
|
||||
config.build.polyfillDynamicImport
|
||||
? dynamicImportPolyfillPlugin(config)
|
||||
: null,
|
||||
...prePlugins,
|
||||
resolvePlugin({
|
||||
root: config.root,
|
||||
|
@ -445,7 +445,7 @@ export async function createPluginContainer(
|
||||
if (!plugin.load) continue
|
||||
ctx._activePlugin = plugin
|
||||
const result = await plugin.load.call(ctx as any, id)
|
||||
if (result) {
|
||||
if (result != null) {
|
||||
return result
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user