Skip to content

Commit

Permalink
feat(plugin-legacy): @vitejs/plugin-legacy
Browse files Browse the repository at this point in the history
  • Loading branch information
yyx990803 committed Jan 7, 2021
1 parent 2cb217e commit 8c34870
Show file tree
Hide file tree
Showing 18 changed files with 1,434 additions and 26 deletions.
2 changes: 1 addition & 1 deletion docs/guide/build.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ A lightweight [dynamic import polyfill](https://github.com/GoogleChromeLabs/dyna

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.
Note that Vite only handles syntax transforms and **does not cover polyfills by default**. You can check out [Polyfill.io](https://polyfill.io/v3/) or use[create-polyfill-service-url](https://www.npmjs.com/package/create-polyfill-service-url) to automatically generate 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
3 changes: 3 additions & 0 deletions packages/playground/legacy/__tests__/legacy.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
test('should work', async () => {
expect(await page.textContent('#app')).toMatch('Hello')
})
5 changes: 5 additions & 0 deletions packages/playground/legacy/async.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
export function fn() {
const m = new Map()
m.set('foo', 'Hello')
document.querySelector('#app').textContent = m.get('foo')
}
2 changes: 2 additions & 0 deletions packages/playground/legacy/index.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
<h1 id="app"></h1>
<script type="module" src="./main.js"></script>
6 changes: 6 additions & 0 deletions packages/playground/legacy/main.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
async function run() {
const { fn } = await import('./async.js')
fn()
}

run()
14 changes: 14 additions & 0 deletions packages/playground/legacy/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
{
"name": "test-legacy",
"private": true,
"version": "0.0.0",
"scripts": {
"dev": "vite",
"build": "vite build --debug legacy",
"debug": "node --inspect-brk ../../vite/bin/vite"
},
"devDependencies": {
"@vitejs/plugin-legacy": "^1.0.0"
},
"dependencies": {}
}
28 changes: 28 additions & 0 deletions packages/playground/legacy/vite.config.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
const fs = require('fs')
const path = require('path')
const legacy = require('@vitejs/plugin-legacy').default

module.exports = {
plugins: [
legacy({
targets: 'IE 11'
})
],

build: {
// make tests faster
minify: false
},

// special test only hook
// for tests, remove `<script type="module">` tags and remove `nomodule`
// attrs so that we run the legacy bundle instead.
__test__() {
const indexPath = path.resolve(__dirname, './dist/index.html')
let index = fs.readFileSync(indexPath, 'utf-8')
index = index
.replace(/<script type="module".*?<\/script>/g, '')
.replace(/<script nomodule/g, '<script')
fs.writeFileSync(indexPath, index)
}
}
4 changes: 4 additions & 0 deletions packages/playground/react/vite.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,10 @@ const reactRefresh = require('@vitejs/plugin-react-refresh')
*/
module.exports = {
plugins: [reactRefresh()],
build: {
// to make tests faster
minify: false
},
esbuild: {
jsxInject: `import React from 'react'`
}
Expand Down
6 changes: 5 additions & 1 deletion packages/playground/vue-jsx/vite.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,5 +4,9 @@ const vueJsxPlugin = require('@vitejs/plugin-vue-jsx')
* @type {import('vite').UserConfig}
*/
module.exports = {
plugins: [vueJsxPlugin()]
plugins: [vueJsxPlugin()],
build: {
// to make tests faster
minify: false
}
}
6 changes: 5 additions & 1 deletion packages/playground/vue/vite.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,5 +3,9 @@ import vuePlugin from '@vitejs/plugin-vue'
import { vueI18nPlugin } from './CustomBlockPlugin'

export default defineConfig({
plugins: [vuePlugin(), vueI18nPlugin]
plugins: [vuePlugin(), vueI18nPlugin],
build: {
// to make tests faster
minify: false
}
})
133 changes: 133 additions & 0 deletions packages/plugin-legacy/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,133 @@
# @vitejs/plugin-legacy

Vite's default browser support baseline is [Native ESM](https://caniuse.com/es6-module). This plugin provides support for legacy browsers that do not support native ESM.

By default, this plugin will:

- Generate a corresponding legacy chunk for every chunk in the final bundle, transformed with [@babel/preset-env](https://babeljs.io/docs/en/babel-preset-env) and emitted as [SystemJS modules](https://github.com/systemjs/systemjs) (code splitting is still supported!).

- Generate a polyfill chunk including SystemJS runtime, and any necessary polyfills determined by specified browser targets and **acutal usage** in the bundle.

- Inject `<script nomdule>` tags into generated HTML to conditionally load the polyfills and legacy bundle only in browsers without native ESM support.

## Usage

```js
// vite.config.js
import legacy from '@vitejs/plugin-legacy'

export default {
plugins: [
legacy({
targets: [
'defaults',
'not IE 11
]
})
]
}
```
## Options
### `targets`
- **Type:** `string | string[] | { [key: string]: string }`
- **Default:** `'defaults'`
If explicitly set, it's passed on to [`@babel/preset-env`](https://babeljs.io/docs/en/babel-preset-env#targets).

The query is also [Browserslist compatible](https://github.com/browserslist/browserslist). The default value, `'defaults'`, is what is recommended by Browserslist. See [Browserslist Best Practices](https://github.com/browserslist/browserslist#best-practices) for more details.

### `polyfills`

- **Type:** `boolean | string[]`
- **Default:** `true`

By default, a polyfills chunks is generated based on the target browser ranges and actual usage in the final bundle (detected via `@babel/preset-env`'s `useBuiltIns: 'usage'`).
Set to a list of strings to explicitly control which polyfills to include. See [Polyfill Specifiers](#polyfill-specifiers) for details.
Set to `false` to avoid generating polyfills and handle it yourself (will still generate legacy chunks with syntax transformations).
### `ignoreBrowserslistConfig`
- **Type:** `boolean`
- **Default:** `false`
`@babel/preset-env` automatically detects [`browserslist` config sources](https://github.com/browserslist/browserslist#browserslist-):
- `browserslist` field in `package.json`
- `.browserslistrc` file in cwd.
Set to `false` to ignore these sources.
### `modernPolyfills`
- **Type:** `boolean | string[]`
- **Default:** `false`
Defaults to `false`. Enabling this option will generate a separate polyfills chunk for the modern build (targeting browsers with [native ESM support](https://caniuse.com/es6-module)).
Set to a list of strings to explicitly control which polyfills to include. See [Polyfill Specifiers](#polyfill-specifiers) for details.
Note it is **not recommended** to use the `true` value (which uses auto detection) because `core-js@3` is very aggressive in polyfill inclusions due to all the bleeding edge features it supports. Even when targeting native ESM support, it injects 15kb of polyfills!
If you don't have hard reliance on bleeding edge runtime features, it is not that hard to avoid having to use polyfills in the modern build altogether. Alternatively, consider using an on-demand service like [Polyfill.io](https://polyfill.io/v3/) to only inject necessary polyfills based on actual browser useragents (most modern brwosers will need nothing!).

### `renderLegacyChunks`

- **Type:** `boolean`
- **Default:** `true`

Set to `false` to disable legacy chunks. This is only useful if you are using `modernPolyfills`, which essentially allows you to use this plugin for injecting polyfills to the modern build only:

```js
import legacy from '@vitejs/plugin-legacy'
export default {
plugins: [
legacy({
modernPolyfills: [
/* ... */
],
renderLegacyChunks: false
})
]
}
```

## Polyfill Sepcifiers

Polyfill specifier strings for `polyfills` and `modernPolyfills` can be either of the following:

- Any [`core-js` 3 sub import paths](https://unpkg.com/browse/core-js@3.8.2/) - e.g. `es/map` will import `core-js/es/map`

- Any [individual `core-js` 3 modules](https://unpkg.com/browse/core-js@3.8.2/modules/) - e.g. `es.array.iterator` will import `core-js/modules/es.array.iterator.js`

**Example**

```js
import legacy from '@vitejs/plugin-legacy'
export default {
plugins: [
legacy({
polyfills: [
'es.promise.finally',
'es/map',
'es/set'
],
modernPolyfills: [
'es.promise.finally'
]
})
]
}
```

## References

- [Vue CLI modern mode](https://cli.vuejs.org/guide/browser-compatibility.html#modern-mode)
- [Using Native JavaScript Modules in Production Today](https://philipwalton.com/articles/using-native-javascript-modules-in-production-today/)
- [rollup-native-modules-boilerplate](https://github.com/philipwalton/rollup-native-modules-boilerplate)
28 changes: 28 additions & 0 deletions packages/plugin-legacy/index.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
import { Plugin } from 'vite'

export interface Options {
/**
* default: 'defaults'
*/
targets?: string | string[] | { [key: string]: string }
/**
* default: false
*/
ignoreBrowserslistConfig?: boolean
/**
* default: true
*/
polyfills?: boolean | string[]
/**
* default: false
*/
modernPolyfills?: boolean | string[]
/**
* default: true
*/
renderLegacyChunks?: boolean
}

declare function createPlugin(options?: Options): Plugin

export default createPlugin
Loading

0 comments on commit 8c34870

Please sign in to comment.