基于 vite 实现基本的浏览器兼容解决方案

浏览器兼容性解决方案

一、JS 兼容

推荐使用 legacy

推荐原因:为打包后的文件提供传统浏览器兼容性支持(不支持 esm 的浏览器),同时还有语法降级处理

缺点:打包时间较长

注意: vite4 的 legacy 插件最高目前是 4.1.1,再高的版本只支持 vite5 了,这里以 vite5 为例。

集成

  1. 安装依赖

使用 npm

npm i @vitejs/plugin-legacy -D
npm i terser -D

使用 yarn

yarn add @vitejs/plugin-legacy -D
yarn add terser -D

使用 pnpm

pnpm add @vitejs/plugin-legacy -D
pnpm add terser -D
  1. 配置vite.config.ts文件
// vite.config.ts
import { defineConfig } from "vite";
import vue from "@vitejs/plugin-vue";
import legacy from "@vitejs/plugin-legacy";

// https://vitejs.dev/config/
export default defineConfig({
  plugins: [
    vue(),
    legacy({
      targets: ["defaults", "Chrome >= 64"], // 指定需要支持的浏览器版本
      additionalLegacyPolyfills: ["regenerator-runtime/runtime"], // 可选, 额外的 polyfill 需要安装
      renderLegacyChunks: true, // 可选,是否生成传统版本的 chunk,默认true // 可选,不配置使用默认。具体的 polyfill 列表
      polyfills: [
        "es.symbol",
        "es.array.filter",
        "es.promise",
        "es.promise.finally",
        "es.object.assign",
        "es.map",
        "es.set",
        "es.array.for-each",
        "es.object.define-properties",
        "es.object.define-property",
        "es.object.get-own-property-descriptor",
        "es.object.get-own-property-descriptors",
        "es.object.keys",
        "es.object.to-string",
        "web.dom-collections.for-each",
        "esnext.global-this",
        "esnext.string.match-all",
        "es.array.iterator",
        "es.string.includes",
        "es.string.starts-with",
        "es.object.values",
      ],
    }),
  ], // 配置打包后的目标esm的版本
  build: {
    minify: "terser", // 使用 terser 进行压缩
  },
});

legacy 插件中如果我们没有指定targets参数时,插件会默认的取项目中的支持的浏览器范围的文件 .browserslistrc,个人对此进行了一个大概的配置

Chrome >= 51
Edge >= 15
Safari >= 10
Firefox >= 54
Opera >= 38
iOS >= 10
not ie <= 11
Android >= 5
not IE <= 11

更多配置请参考 legacy 官方文档

  1. 构建

通过npm run build打包可以看到 script 发生了变化,除了type="module"还有一个nomodule脚本,nomodule这个属性表示在支持 esm 的浏览器不运行里面的代码,而不支持 esm 的浏览器又无法识别type="module",反而会去运行 nomodule 的 script,从而实现了降级区分。而降级的脚本都会携带一个legacy文本。

大概效果如下:

<!DOCTYPE html>
<html lang="en">
  <head>
     
    <meta charset="UTF-8" />
     
    <link rel="icon" href="/favicon.ico" />
     
    <link
      rel="stylesheet"
      href="https://printjs-4de6.kxcdn.com/print.min.css"
    />
     
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
     
    <title>Vite App</title>
     
    <script type="module" crossorigin src="/assets/index-BUwO137t.js"></script>
     
    <link rel="stylesheet" crossorigin href="/assets/index-BsmRYvi8.css" />
     
    <script type="module">
      import.meta.url;
      import("_").catch(() => 1);
      (async function* () {})().next();
      if (location.protocol != "file:") {
        window.__vite_is_modern_browser = true;
      }
    </script>
     
    <script type="module">
      !(function () {
        if (window.__vite_is_modern_browser) return;
        console.warn(
          "vite: loading legacy chunks, syntax error above and the same error below should be ignored"
        );
        var e = document.getElementById("vite-legacy-polyfill"),
          n = document.createElement("script");
        (n.src = e.src),
          (n.onload = function () {
            System.import(
              document
                .getElementById("vite-legacy-entry")
                .getAttribute("data-src")
            );
          }),
          document.body.appendChild(n);
      })();
    </script>
  </head>

  <body>
     
    <div id="app"></div>
     
    <script src="https://printjs-4de6.kxcdn.com/print.min.js"></script>
         <!-- 这里是legacy处理后的 -->
     
    <script nomodule>
      !(function () {
        var e = document,
          t = e.createElement("script");
        if (!("noModule" in t) && "onbeforeload" in t) {
          var n = !1;
          e.addEventListener(
            "beforeload",
            function (e) {
              if (e.target === t) n = !0;
              else if (!e.target.hasAttribute("nomodule") || !n) return;
              e.preventDefault();
            },
            !0
          ),
            (t.type = "module"),
            (t.src = "."),
            e.head.appendChild(t),
            t.remove();
        }
      })();
    </script>
     
    <script
      nomodule
      crossorigin
      id="vite-legacy-polyfill"
      src="assets/polyfills-legacy-BuMl3xCl.js"
    ></script>
     
    <script
      nomodule
      crossorigin
      id="vite-legacy-entry"
      data-src="assets/index-legacy-BrIJujGY.js"
    >
      System.import(
        document.getElementById("vite-legacy-entry").getAttribute("data-src")
      );
    </script>
  </body>
</html>

二、CSS 兼容


推荐使用 postcss-preset-env 插件

推荐原因:

  • 自动添加浏览器前缀
  • 减少手动工作
  • 无缝集成
  • 提高兼容性和性能

官方地址

项目地址

集成

  1. 安装依赖

使用 npm

npm i postcss-preset-env -D

使用 yarn

yarn add postcss-preset-env -D

使用 pnpm

pnpm add postcss-preset-env -D
  1. 配置文件vite.config.ts
import { defineConfig } from "vite";
import vue from "@vitejs/plugin-vue";
import poscssPresetEnv from "postcss-preset-env";

// https://vitejs.dev/config/
export default defineConfig({
  plugins: [vue()],
  css: {
    postcss: {
      // ⚠️关键代码
      plugins: [
        poscssPresetEnv({
          stage: 1, // 配置阶段,1 表示启用大多数新特性
          features: {
            "nesting-rules": true, // 启用嵌套规则
            "custom-properties": true, // 启用自定义属性 // 其他特性配置
          }, // 启用 autoprefixer
          autoprefixer: {
            grid: true,
          }, // 一定要指定浏览器版本,否则autoprefixer将无法正确处理
          browsers: ["last 2 versions", "ie >= 11"],
        }),
      ],
    },
  },
});
  1. 构建

打包前:

<!DOCTYPE html>
<html lang="en">
  <head>
     
    <meta charset="UTF-8" />
     
    <link rel="icon" href="/favicon.ico" />
     
    <link
      rel="stylesheet"
      href="https://printjs-4de6.kxcdn.com/print.min.css"
    />
     
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
     
    <title>Vite App</title>
  </head>

  <body>
     
    <div id="app"></div>
  </body>

  <style>
     :root {
       --main-color: '#fef'
     }

     .card {
       width: 240px;
       margin: 10px;
       border: 1px solid #ccc;
       box-shadow: 2px 2px 6px 0px rgba(0, 0, 0, 0.3);
       border-radius: 10px;
       padding: 10px;
       transition: all .3s;
       display: grid;

       &:hover {
         color: var(--main-color);

       }
     }

     .buttom {
       display: flex;
       align-items: center;
       justify-content: center;
     }
  </style>
</html>

打包后:

可以看到对自定义属性 --main-color 的使用,会多一种兼容写法;对box-shadow transition flex 等属性,自动添加了浏览器的内核的前缀

<!DOCTYPE html>
<html lang="en">
  <head>
     
    <meta charset="UTF-8" />
     
    <link rel="icon" href="/favicon.ico" />
     
    <link
      rel="stylesheet"
      href="https://printjs-4de6.kxcdn.com/print.min.css"
    />
     
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
     
    <title>Vite App</title>
  </head>

  <body>
     
    <div id="app"></div>
  </body>

  <style>
     :root {
       --main-color: '#fef'
     }

     .card {
       width: 240px;
       margin: 10px;
       border: 1px solid #ccc;
       -webkit-box-shadow: 2px 2px 6px 0px rgba(0, 0, 0, 0.3);
       box-shadow: 2px 2px 6px 0px rgba(0, 0, 0, 0.3);
       border-radius: 10px;
       padding: 10px;
       -webkit-transition: all .3s;
       transition: all .3s;
       display: grid;
     }

     .card:hover {
         color: '#fef';
         color: var(--main-color);
       }

     .buttom {
       display: -webkit-box;
       display: -ms-flexbox;
       display: flex;
       -webkit-box-align: center;
       -ms-flex-align: center;
       align-items: center;
       -webkit-box-pack: center;
       -ms-flex-pack: center;
       justify-content: center;
     }
  </style>
</html>

三、CSS 原子化插件


推荐使用 unocss

推荐原因:unocss 是一个 css 引擎,unocss 本身不提供任何类名 css,它只是解决 Tailwind 以及 Windi 的编译和打包的某些问题,也就是它可以配合 Tailwind 或者 windi 使用,以提供更快的编译打包速度。可以在开发中尽可能的减少自定义样式的编写。

参考页面:unocss.dev/guide/

集成

  1. 安装依赖

使用 npm

npm i unocss -D

使用 yarn

yarn add unocss -D

使用 pnpm

pnpm add unocss -D
  1. 配置文件
  • 创建一个 uno.config.ts 文件
// uno.config.ts
import { defineConfig } from "unocss";

export default defineConfig({
  // ...UnoCSS options
});
  • 配置 vite.config.ts 文件
// vite.config.ts
import UnoCSS from "unocss/vite";
import { defineConfig } from "vite";

export default defineConfig({
  plugins: [UnoCSS()],
});
  • 在入口页面导入,一般为 main.ts
import "virtual:uno.css";
  1. 额外配置:

推荐 VSCode 下载一个扩展 UnoCSS 可以在开发过程中有很好的提示效果。