Skip to content

Commit 8c34870

Browse files
committed
feat(plugin-legacy): @vitejs/plugin-legacy
1 parent 2cb217e commit 8c34870

File tree

18 files changed

+1434
-26
lines changed

18 files changed

+1434
-26
lines changed

docs/guide/build.md

+1-1
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ A lightweight [dynamic import polyfill](https://github.com/GoogleChromeLabs/dyna
1515

1616
You can specify custom targets via the [`build.target` config option](/config/#build-target), where the lowest target is `es2015`.
1717

18-
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.
18+
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.
1919

2020
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.
2121

Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
test('should work', async () => {
2+
expect(await page.textContent('#app')).toMatch('Hello')
3+
})

packages/playground/legacy/async.js

+5
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
export function fn() {
2+
const m = new Map()
3+
m.set('foo', 'Hello')
4+
document.querySelector('#app').textContent = m.get('foo')
5+
}

packages/playground/legacy/index.html

+2
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
<h1 id="app"></h1>
2+
<script type="module" src="./main.js"></script>

packages/playground/legacy/main.js

+6
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
async function run() {
2+
const { fn } = await import('./async.js')
3+
fn()
4+
}
5+
6+
run()
+14
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
{
2+
"name": "test-legacy",
3+
"private": true,
4+
"version": "0.0.0",
5+
"scripts": {
6+
"dev": "vite",
7+
"build": "vite build --debug legacy",
8+
"debug": "node --inspect-brk ../../vite/bin/vite"
9+
},
10+
"devDependencies": {
11+
"@vitejs/plugin-legacy": "^1.0.0"
12+
},
13+
"dependencies": {}
14+
}
+28
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
const fs = require('fs')
2+
const path = require('path')
3+
const legacy = require('@vitejs/plugin-legacy').default
4+
5+
module.exports = {
6+
plugins: [
7+
legacy({
8+
targets: 'IE 11'
9+
})
10+
],
11+
12+
build: {
13+
// make tests faster
14+
minify: false
15+
},
16+
17+
// special test only hook
18+
// for tests, remove `<script type="module">` tags and remove `nomodule`
19+
// attrs so that we run the legacy bundle instead.
20+
__test__() {
21+
const indexPath = path.resolve(__dirname, './dist/index.html')
22+
let index = fs.readFileSync(indexPath, 'utf-8')
23+
index = index
24+
.replace(/<script type="module".*?<\/script>/g, '')
25+
.replace(/<script nomodule/g, '<script')
26+
fs.writeFileSync(indexPath, index)
27+
}
28+
}

packages/playground/react/vite.config.js

+4
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,10 @@ const reactRefresh = require('@vitejs/plugin-react-refresh')
55
*/
66
module.exports = {
77
plugins: [reactRefresh()],
8+
build: {
9+
// to make tests faster
10+
minify: false
11+
},
812
esbuild: {
913
jsxInject: `import React from 'react'`
1014
}

packages/playground/vue-jsx/vite.config.js

+5-1
Original file line numberDiff line numberDiff line change
@@ -4,5 +4,9 @@ const vueJsxPlugin = require('@vitejs/plugin-vue-jsx')
44
* @type {import('vite').UserConfig}
55
*/
66
module.exports = {
7-
plugins: [vueJsxPlugin()]
7+
plugins: [vueJsxPlugin()],
8+
build: {
9+
// to make tests faster
10+
minify: false
11+
}
812
}

packages/playground/vue/vite.config.ts

+5-1
Original file line numberDiff line numberDiff line change
@@ -3,5 +3,9 @@ import vuePlugin from '@vitejs/plugin-vue'
33
import { vueI18nPlugin } from './CustomBlockPlugin'
44

55
export default defineConfig({
6-
plugins: [vuePlugin(), vueI18nPlugin]
6+
plugins: [vuePlugin(), vueI18nPlugin],
7+
build: {
8+
// to make tests faster
9+
minify: false
10+
}
711
})

packages/plugin-legacy/README.md

+133
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,133 @@
1+
# @vitejs/plugin-legacy
2+
3+
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.
4+
5+
By default, this plugin will:
6+
7+
- 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!).
8+
9+
- Generate a polyfill chunk including SystemJS runtime, and any necessary polyfills determined by specified browser targets and **acutal usage** in the bundle.
10+
11+
- Inject `<script nomdule>` tags into generated HTML to conditionally load the polyfills and legacy bundle only in browsers without native ESM support.
12+
13+
## Usage
14+
15+
```js
16+
// vite.config.js
17+
import legacy from '@vitejs/plugin-legacy'
18+
19+
export default {
20+
plugins: [
21+
legacy({
22+
targets: [
23+
'defaults',
24+
'not IE 11
25+
]
26+
})
27+
]
28+
}
29+
```
30+
31+
## Options
32+
33+
### `targets`
34+
35+
- **Type:** `string | string[] | { [key: string]: string }`
36+
- **Default:** `'defaults'`
37+
38+
If explicitly set, it's passed on to [`@babel/preset-env`](https://babeljs.io/docs/en/babel-preset-env#targets).
39+
40+
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.
41+
42+
### `polyfills`
43+
44+
- **Type:** `boolean | string[]`
45+
- **Default:** `true`
46+
47+
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'`).
48+
49+
Set to a list of strings to explicitly control which polyfills to include. See [Polyfill Specifiers](#polyfill-specifiers) for details.
50+
51+
Set to `false` to avoid generating polyfills and handle it yourself (will still generate legacy chunks with syntax transformations).
52+
53+
### `ignoreBrowserslistConfig`
54+
55+
- **Type:** `boolean`
56+
- **Default:** `false`
57+
58+
`@babel/preset-env` automatically detects [`browserslist` config sources](https://github.com/browserslist/browserslist#browserslist-):
59+
60+
- `browserslist` field in `package.json`
61+
- `.browserslistrc` file in cwd.
62+
63+
Set to `false` to ignore these sources.
64+
65+
### `modernPolyfills`
66+
67+
- **Type:** `boolean | string[]`
68+
- **Default:** `false`
69+
70+
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)).
71+
72+
Set to a list of strings to explicitly control which polyfills to include. See [Polyfill Specifiers](#polyfill-specifiers) for details.
73+
74+
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!
75+
76+
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!).
77+
78+
### `renderLegacyChunks`
79+
80+
- **Type:** `boolean`
81+
- **Default:** `true`
82+
83+
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:
84+
85+
```js
86+
import legacy from '@vitejs/plugin-legacy'
87+
88+
export default {
89+
plugins: [
90+
legacy({
91+
modernPolyfills: [
92+
/* ... */
93+
],
94+
renderLegacyChunks: false
95+
})
96+
]
97+
}
98+
```
99+
100+
## Polyfill Sepcifiers
101+
102+
Polyfill specifier strings for `polyfills` and `modernPolyfills` can be either of the following:
103+
104+
- Any [`core-js` 3 sub import paths](https://unpkg.com/browse/[email protected]/) - e.g. `es/map` will import `core-js/es/map`
105+
106+
- Any [individual `core-js` 3 modules](https://unpkg.com/browse/[email protected]/modules/) - e.g. `es.array.iterator` will import `core-js/modules/es.array.iterator.js`
107+
108+
**Example**
109+
110+
```js
111+
import legacy from '@vitejs/plugin-legacy'
112+
113+
export default {
114+
plugins: [
115+
legacy({
116+
polyfills: [
117+
'es.promise.finally',
118+
'es/map',
119+
'es/set'
120+
],
121+
modernPolyfills: [
122+
'es.promise.finally'
123+
]
124+
})
125+
]
126+
}
127+
```
128+
129+
## References
130+
131+
- [Vue CLI modern mode](https://cli.vuejs.org/guide/browser-compatibility.html#modern-mode)
132+
- [Using Native JavaScript Modules in Production Today](https://philipwalton.com/articles/using-native-javascript-modules-in-production-today/)
133+
- [rollup-native-modules-boilerplate](https://github.com/philipwalton/rollup-native-modules-boilerplate)

packages/plugin-legacy/index.d.ts

+28
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
import { Plugin } from 'vite'
2+
3+
export interface Options {
4+
/**
5+
* default: 'defaults'
6+
*/
7+
targets?: string | string[] | { [key: string]: string }
8+
/**
9+
* default: false
10+
*/
11+
ignoreBrowserslistConfig?: boolean
12+
/**
13+
* default: true
14+
*/
15+
polyfills?: boolean | string[]
16+
/**
17+
* default: false
18+
*/
19+
modernPolyfills?: boolean | string[]
20+
/**
21+
* default: true
22+
*/
23+
renderLegacyChunks?: boolean
24+
}
25+
26+
declare function createPlugin(options?: Options): Plugin
27+
28+
export default createPlugin

0 commit comments

Comments
 (0)