vite + vue3 + ts 安装 @vitejs/plugin-legacy 兼容旧版浏览器
安装浏览器兼容插件@vitejs/plugin-legacy:
npm install @vitejs/plugin-legacy -D
必须安装Terser压缩器,因为插件Legacy,使用Terser进行压缩。
npm install terser -D
打开vite.config.ts,配置插件。
import { fileURLToPath, URL } from "node:url"; import { defineConfig } from "vite"; import vue from "@vitejs/plugin-vue"; import legacy from "@vitejs/plugin-legacy"; import AutoImport from "unplugin-auto-import/vite"; import Components from "unplugin-vue-components/vite"; import { ElementPlusResolver } from "unplugin-vue-components/resolvers"; import Icons from "unplugin-icons/vite"; import IconsResolver from "unplugin-icons/resolver"; import path from "path"; const pathSrc = path.resolve(__dirname, "src"); export default defineConfig({plugins : [ vue(), legacy({ targets: ["defaults", "not IE 11"], }), AutoImport({ resolvers: [ ElementPlusResolver(), IconsResolver({ prefix: "Icon", }), ], dts: path.resolve(pathSrc, "auto-imports.d.ts"), }), Components({ resolvers: [ ElementPlusResolver(), IconsResolver({ enabledCollections: ["ep"], }), ], dts: path.resolve(pathSrc, "components.d.ts"), }), Icons({ autoInstall: true, }), ], build: { target: "modules", // 默认 "modules" },resolve : { alias: { "@": fileURLToPath(new URL("./src", import.meta.url)), }, }, });
build.target
- 类型:string|string[]
- 默认:
modules - 作用:浏览器兼容性
设置最终构建的浏览器兼容目标。默认值是一个 Vite 特有的值: | |
自 es2015 以后,所有 es 的版本,2022年6月将要发布的标准。即假设有原生动态导入支持,并且将会转译得尽可能小:如果 build.minify 选项为 | |
自定义版本 | 自定义目标也可以是一个 ES 版本(例如: |
转换过程将会由 esbuild 执行,并且此值应该是一个合法的 esbuild 目标选项。注意:如果代码包含不能被 esbuild 安全地编译的特性,那么构建将会失败。
build.minify
- 类型:boolean|
terser |esbuild - 默认:
esbuild
设置为
注意,在 lib 模式下使用'es'时,build.minify 选项将失效。
当设置为
ECMAScript 2015
ECMAScript 2015(又称为 es6),在 2015 年发布,有重大意义的里程碑。发展到现在的版本:es2015、es2016、es2017、es2018、es2019、es2020。对于浏览器实现支持ES6+ JavaScript 语法的浏览器,被称呼为现代浏览器。不支持的,称呼为传统旧版浏览器。
- chrome 51+,全部支持。2016 年开始支持。
- Safari 10+,全部支持。2016 年开始支持。
- Opera 38+,全部支持。2016 年开始支持。
- firefox 54+,全部支持。2017 年开始支持。
- ie 6 ~ 10,都不支持。ie 11 支持部分语法。
- edage 15+,全部支持。2017 年开始支持。
- QQ Browser 10.4+,全部支持。2020 年开始支持。
- Android Browser 5+,全部支持。它是 2015 年开始支持。
- Safari on iOS 10+,全部支持。它是 2016 年开始支持。
ES6+ JavaScript 语法的支持率,在移动端,支持广泛,在 pc 端,略逊色。
浏览器内核
浏览器内核(Rendering Engine),是指浏览器最核心的部分,负责对网页语法的解释(对HTML、XML、图像、JavaScript、CSS 等等)并渲染(显示)网页。所以,通常所谓的浏览器内核也就是浏览器所采用的渲染引擎,渲染引擎决定了浏览器如何显示网页的内容以及页面的格式信息。不同的浏览器内核对网页编写语法的解释也有不同,因此同一网页在不同的内核的浏览器里的渲染(显示)效果也可能不同,这也是网页编写者需要在不同内核的浏览器中测试网页显示效果的原因。
Webkit | 苹果公司研发内核,也是苹果的 Safari 浏览器使用的内核。Webkit 开放源代码。 |
Chromium | 谷歌 Google 公司开发主导。Chromium 内核是基于开源 webkit 内核,是 Webkit 内核的分支之一。Chromium 开放源代码。2020 年之前 Chrome 浏览器,采用此内核,2020 年之后,Edge 浏览器,采用 Chromium 内核。而谷歌 Chrome 浏览器改用 Blink 内核。 |
Blink | 由 Google 和 Opera Software 开发,在 Webkit 的基础上演变而来,应用于 Chromium 项目上,是 Webkit 内核的分支之一。现在 Chrome 浏览器、Opera 浏览器,都使用Blink内核。 |
Trident | 微软 IE 浏览器内核,并沿用到 IE11。Trident 实际上是一款开放的内核,有许多采用 IE 内核而非IE的浏览器(壳浏览器)涌现。 |
Gecko | Mozilla 基金会的火狐浏览器 Mozilla Firefox 内核。 |
Presto | 欧朋 Opera 浏览器内核。现在已废弃。Opera 浏览器改用 Blink 内核。 |
vite 运行需要浏览器基准
使用 vite 构建 vue3 项目,其构建的生产环境,需要目标浏览器支持现代 JavaScript 语法:原生 ES 模块(es2015)、原生 ESM 动态导入(es2020)以及import.meta(es2020)。即浏览器需要支持
浏览器版本要求:
- Chrome >=87
- Firefox >=78
- Safari >=13
- Edge >=88
若构建的项目,绝大部分客户能满足上述要求,只有非常很少部分用户不能满足条件,那么需要@vitejs/plugin-legacy插件来做兼容,它会自动生成兼容性(legacy)chunk 以及相应的 ES 语言功能的polyfill。
vite 使用create-vite搭建的项目:
- vue、vue-ts
- react、react-ts
- vanilla、vanilla-ts
- preact、preact-ts
- lit、lit-ts
- svelte、svelte-ts
vue3 兼容性
vue2 项目,浏览器兼容,最低 ie8,vue3项目,不兼任何版本 ie。vite + vue3 搭建的项目,除去上面要求的 vite 现代浏览器标准之外,vue3 要求浏览器必须支持Proxy。Element Plus 也放弃了对 ie 支持,并且要求浏览器支持ES2018和ResizeObserver。
移动 APP 应用中,vue3 支持率
IOS:9.3 是 2016 年发布,现在市场上 10.0 以上;安卓 5.0 是 2015 年发布的,现在市场上也都在 5.0 以上。所以客户端对 vue3,支持率约 99%,不支持约 1%。对于不支持的,使用@vitejs/plugin-legacy做兼容性处理。
ESM 模块化
历史上,JavaScript 一直没有模块(module)体系,无法将一个大程序拆分成互相依赖的小文件,再用简单的方法拼装起来。其他语言都有这项功能,比如 Ruby 的require、Python 的import,甚至就连 CSS 都有@import,但是 JavaScript 任何这方面的支持都没有,这对开发大型的、复杂的项目形成了巨大障碍。开发社区制定了一些模块加载方案:用于服务器nodejs环境,有CommonJS;用于浏览器环境,有AMD等。
SystemJS诞生于 2015 年,那个时候 ES Module 还未成为标准,在浏览器端只能通过 requirejs、seajs 等方案实现模块加载,随着 npm 在前端界的流行,一个项目中可能存在多种模块规范,所以我认为 SystemJS 最初诞生的目的是为了做一个通用的模块加载器,在浏览器端实现对 CommonJS、AMD、UMD 等各种模块的加载。后来,随着 ES6 的普及,越来越多的浏览器开始支持 ES Module,SystemJS 能够跑在旧版本的浏览器当中。
es2015(ES6)增加了模块化的功能(ESM),支持 nodejs 环境和浏览器环境。ESM(模块功能):
export :用于规定模块的对外接口。import :用于输入其他模块提供的功能。module :用于浏览器加载。在 HTML 中加载使用传统的 JavaScript 脚本,加载ES6模块,就得在 script 标签上增加type="module" 属性
<scripttype="module" >import foo from '../foo.js';import bar from '../bar.js' foo(bar); //... to do </script>
@vitejs/plugin-legacy
在不支持ESM模块化的旧版浏览器中,vite 官方插件@vitejs/plugin-legacy来实现。默认情况下,其所做的事:
- legacy chunk:使用@babel/preset-env,把开发项目中,用es6+语法书写的代码,转变为相应es5语法的兼容性代码(legacy chunk),仍然支持代码拆分。使之能在SystemJS模块化功能下,运行。兼容版的legacy chunk只会在不支持原生 ESM 的浏览器中进行按需加载。
- polyfill chunk:生成polyfill代码块。运行于目标浏览器,所需要polyfill;以及 SystemJS 运行时,所需polyfill。polyfill:术语。一块代码(通常是 Web 上的 JavaScript),用来为旧浏览器提供它没有原生支持的较新的功能。是针对目标浏览器而实现的模拟环境代码。
- script nomodule:将
<script nomodule>
标记,注入生成的 HTML 中,以便仅在不支持原生 ESM 的浏览器中,有条件地加载polyfill chunk和legacy chunk。 - import.meta.env.LEGACY:注入import.meta.env.LEGACYenv变量,在旧版生产版本中,其值为
true ,在所有其他情况下,其值为false 。
那些版本较低的浏览器不支持 ES6+的语法和新 API,而@babel/preset-env只转换新的 JavaScript 句法,不转换新的 API,比如 Proxy、Symbol、Promise等全局对象,以及一些定义在全局对象上的方法都不会转码。
配置选项
targets
设置目标浏览器兼容的范围。
- 类型:string|string[]|{[key: string]: string}
- 默认值:
defaults
如果显式设置,则将其传递给@babel/preset-env。该选项与Browserslist 兼容。默认值:
Browserslist 的默认浏览器( | |
没有官方支持或更新的浏览器 24 个月。现在它是IE 11, IE_Mob 11, BlackBerry 10, BlackBerry 7, Samsung 4 和 OperaMobile 12.1,所有版本的 Baidu 浏览器。 | |
从以前的查询中排除 IE 8 及更低版本。 | |
按使用统计 |
|
最新版本 |
|
浏览器版本 |
|
Node.js 版本 |
|
从 browserslist-config-mycompanynpm 包中获取查询。 | |
支持特定功能的浏览器。es6-module 这是 Can I Use 页面 feat 的 URL 中的参数。可以在 caniuse-lite/data/features 找到所有可用功能的列表。 | |
Browserslist 配置中定义的浏览器。在差异服务中很有用,可以修改用户的配置,例如 browserslist config and supports es6-module. | |
自 2015 年以来发布的所有版本(也 | |
alpha 和 beta 版本。 |
在项目根目录中,查询选择了哪些浏览器。
npx browserslist
polyfills
- 类型:string|string[]
- 默认值:
true
默认情况下,根据目标浏览器范围和最终包中的实际使用情况(通过@babel/preset-env检测到useBuiltIns:'usage'
)生成一个 polyfills 块。
设置为字符串列表,以显式控制,要包含的 polyfill。有关详细信息,请参阅Polyfill说明符。
设置为
additionalLegacyPolyfills
- 类型:string[]
将自定义导入方式添加到legacy的polyfill块中。由于基于使用的polyfill检测,仅涵盖 ES 语言功能,因此可能需要使用此选项,手动指定额外的DOM APIpolyfill。
注意:如果现代和legacy块都需要额外的polyfill,可以简单地在应用程序源代码中导入它们。
ignoreBrowserslistConfig
- 类型:boolean
- 默认值:
false
@babel/preset-env自动检测browserslist 配置源:package.json中 browserslist 字段,或者.browserslistrc文件中的配置。设置为
modernPolyfills
- 类型:boolean|string[]
- 默认值:
false
默认为
若启用此选项,将为现代构建(针对具有原生 ESM 支持的浏览器),生成一个单独的polyfill块。
请注意,不建议使用该
设置为字符串列表,以显式控制要包含的polyfill。有关详细信息,请参阅Polyfill说明符。
如果您不完全依赖最前沿的运行时特性,那么避免在现代构建中完全使用polyfill并不难。或者,考虑使用像 Polyfill.io 这样的按需服务,仅根据实际浏览器用户代理注入必要的polyfill(大多数现代浏览器不需要任何东西!)。
renderLegacyChunks
- 类型:boolean
- 默认值:
true
设置为
import legacy from '@vitejs/plugin-legacy' export default { plugins: [ legacy({ modernPolyfills: [ /* ... */ ], renderLegacyChunks: false }) ] }
externalSystemJS
- 类型:boolean
- 默认值:
false
默认为
import()动态导入
在支持原生 ESM 但不支持动态导入的浏览器中(例如 Legacy Edge),legacy chunk,提供了一种方法,使支持原生
- 在所有 ESM 浏览器中,Modern bundle 总被加载。
- 在没有动态导入的情况下,在浏览器中 Modern bundle 抛出 SyntaxError。
Polyfill
polyfill配置选项polyfills
、modernPolyfills
,可以是下面任意一种:
- core-js的任意 3 个子导入路径。例如
es/map
,将导入core-js/es/map
。 - core-js的任意 3 个模块。例如
es.array.iterator
,将导入core-js/modules/es.array.iterator.js
。
import legacy from '@vitejs/plugin-legacy' export default { plugins: [legacy ({ polyfills: ['es.promise.finally', 'es/map', 'es/set'], modernPolyfills: ['es.promise.finally'] }) ] }
Content-Security-Policy
在 Safari 10.1 nomodule 修复、SystemJS 初始化和动态导入中,需要内置 scripts legacy。如果您有严格的 HTTP Content-Security-Policy(CSP)策略要求,则需要将相应的哈希添加到您的script-src
列表中:
- sha256-MS6/3FCg4WjP9gwgaBGwLpRCY6fZBgwmhVCdrPrNf3E=
- sha256-tQjf8gvb2ROOMapIxFvFAYBeUJ0v1HCbOcSmDNXGtDo=
- sha256-BoFUHKsYhJ9tbsHugtNQCmnkBbZ11pcW6kZguu+T+EU=
- sha256-A18HC3jLpyEc9B8oyxq/NBFCyFBJFSsRLt0gmT9kft8=
这些值(不带 sha256-前缀)也可以通过以下方式检索
const { cspHashes } = require('@vitejs/plugin-legacy')
当使用regenerator-runtimepolyfill时,它会尝试使用globalThis
对象来注册自己。如果globalThis
不可用(它相当新且未被广泛支持,包括 IE 11),它会尝试执行Function(...)
违反 CSP 的动态调用。为了避免动态eval
,在没有globalThis
时,考虑把core-js/proposals/global-this,添加到additionalLegacyPolyfills来定义它。