Skip to content

Commit

Permalink
feat(build): default build target to 'modules' with dynamic import po…
Browse files Browse the repository at this point in the history
…lyfill
  • Loading branch information
yyx990803 committed Jan 5, 2021
1 parent 0fad96e commit 756e90f
Show file tree
Hide file tree
Showing 10 changed files with 221 additions and 22 deletions.
25 changes: 22 additions & 3 deletions docs/config/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
9 changes: 8 additions & 1 deletion docs/guide/backend-integration.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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>
```
13 changes: 12 additions & 1 deletion docs/guide/build.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.

Expand Down
4 changes: 2 additions & 2 deletions docs/guide/introduction.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.
36 changes: 32 additions & 4 deletions packages/vite/src/node/build.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down Expand Up @@ -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,
Expand All @@ -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/')

Expand Down
17 changes: 7 additions & 10 deletions packages/vite/src/node/cli.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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]',
Expand Down
126 changes: 126 additions & 0 deletions packages/vite/src/node/plugins/dynamicImportPolyfill.ts
Original file line number Diff line number Diff line change
@@ -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 = {}
}
}
7 changes: 7 additions & 0 deletions packages/vite/src/node/plugins/html.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down Expand Up @@ -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
}
},
Expand Down
4 changes: 4 additions & 0 deletions packages/vite/src/node/plugins/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand All @@ -26,6 +27,9 @@ export async function resolvePlugins(

return [
aliasPlugin({ entries: config.alias }),
config.build.polyfillDynamicImport
? dynamicImportPolyfillPlugin(config)
: null,
...prePlugins,
resolvePlugin({
root: config.root,
Expand Down
2 changes: 1 addition & 1 deletion packages/vite/src/node/server/pluginContainer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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
}
}
Expand Down

0 comments on commit 756e90f

Please sign in to comment.