@vite/plugins-legacy 生成产物的绝对路径不生效怎么办

1,188 阅读4分钟

使用vite 进行项目开发显著提升了启动和打包的速度。相关的配置文件通常需要写在 vite.config.ts 中。 当项目中的特性不支持旧版本浏览器或移动端时,可安装 @vitejs/plugin-legacy 插件。然而,在将配置文件中的 base 设置为绝对路径后,发现 legacy 插件生成的文件路径,未自动添加该绝对路径前缀。接下来我们一起分析一下这个问题吧

1. 关键代码示例

package.json 中配置:

"vite": "^4.0.0", // 实际安装的为 4.5.3
"@vitejs/plugin-legacy": "^5.4.2",

vite.config.ts 文件示例:

import { defineConfig } from "vite"
import legacy from "@vitejs/plugin-legacy"

export default defineConfig(({ command, mode }) => {
  return {
    base: 'https://test-demo.com', // 仅作为演示,可根据实际情况配置
    plugins: [
      legacy({
        targets: ["defaults", "not IE 11", "Android >= 4.4", "ios >= 9"],
        modernPolyfills: true,
      }),
    ]
  }
})

index.html 文件示例:

<!DOCTYPE html>
<html lang="zh-CN">
  <head>
    <meta content="text/html; charset=utf-8" http-equiv="Content-Type" />
    <meta
      name="viewport"
      content="width=device-width,initial-scale=1,minimum-scale=1,maximum-scale=1,user-scalable=no,viewport-fit=cover"
    />
    <link rel="icon" type="image/svg+xml" href="/favicon.ico" />
    <title>测试打包</title>

  </head>
  <body>
    <div id="root"></div>
    <script type="module" src="/src/main.tsx"></script>
  </body>
</html>

2. 打包后的产物

dist/index.html

<!DOCTYPE html>
<html lang="zh-CN">
  <head>
    <script type="module" crossorigin src="assets/polyfills-9cc75015.js"></script>

    <meta content="text/html; charset=utf-8" http-equiv="Content-Type" />
    <meta
      name="viewport"
      content="width=device-width,initial-scale=1,minimum-scale=1,maximum-scale=1,user-scalable=no,viewport-fit=cover"
    />
    <link rel="icon" type="image/svg+xml" href="https://test-demo.com/favicon.ico" />
    <title>测试打包</title>

    <script type="module" crossorigin src="https://test-demo.com/assets/index-5cdcd74e.js"></script>
    <link rel="stylesheet" href="https://test-demo.com/assets/index-fc97165b.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="root"></div>
    
    <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-58e04a10.js"></script>
    <script nomodule crossorigin id="vite-legacy-entry" data-src="assets/index-legacy-8be19ac8.js">System.import(document.getElementById('vite-legacy-entry').getAttribute('data-src'))</script>
  </body>
</html>

可以看到 favicon.icocss 以及业务 js 都成功添加了我们配置的绝对路径 https://test-domain.com,但是一些 polyfills 却没有。这样一来,部署到服务器后可能导致无法找到这些文件。因此,我们初步可以将问题定位于 —— @vite/plugins-legacy 插件生成的产物中,配置绝对路径未能生效。

3. 插件分析

全网搜罗,包括论坛和官方网站,都没有找到有关此问题的说明与修改建议,最终,在公司大佬的提示下,我们查看了该插件的源码。

3.1 点击 legacy 定位到源码位置

image.png

3.2 找到 index.mjs

保险起见,可以在这个文件中输出任意一条 console 信息,以验证它在打包过程中是否执行。

@vite/plugins-legacy插件源码地址

3.3 查找 base

由于配置了 base 未生效,那就找找看有没有相应的逻辑处理,发现了两个关键方法:

  1. toOutputFilePathInHtml toOutputFilePathInHtml

  2. getBaseInHTML image.png

    在这两个方法中,源码都对 config.base 进行了判断,检查其是否为空,或者是否为相对路径 ./

    由于我们传入的是绝对路径,不符合该判断,因此

    • toOutputFilePathInHtml 中,判断后执行 joinUrlSegments 方法
    • getBaseInHTML 中,判断后输出 config.base

我们接着分析一下这两个方法是被谁调用,以及方法内部都做了什么。

  1. joinUrlSegments joinUrlSegments 打包过程输出a

    这个方法接收两个参数,即上文传入的 config.decodedBasefilename,由于配置文件中我们并没有传入 decodedBase,因此直接进入第一个 if 判断,返回 undefined

  2. toAssetPathFromHtml toAssetPathFromHtml

    这个方法调用了 getBaseInHTML,将它的返回值与 filename 进行拼接,并将拼接结果作为参数 toRelative 传递给了 toOutputFilePathInHtml

    根据上文提供的代码片段,我们可以看到在 toOutputFilePathInHtml 中,由于 relative 判断为 false,因此根本没有执行 toRelative,真正执行的是 joinUrlSegments,返回结果为 undefined

终于恍然大悟,找到了绝对路径不生效的根本原因。那如何解决呢?

3.4 解决方法,使绝对路径生效

由于最终执行的是 joinUrlSegments,而我们没有在配置文件中传入 config.decodedBase,因此方法返回了 undefined,那么我们是不是可以尝试传入该参数呢?试试看吧

vite.config.ts

import { defineConfig } from "vite"
import legacy from "@vitejs/plugin-legacy"

export default defineConfig(({ command, mode }) => {
  return {
    base: 'https://test-demo.com', // 这里依然要保留,为了让业务 js 的路径可以生效
    decodedBase: 'https://test-demo.com', // 增加参数,传入的路径与 base 一致
    plugins: [
      legacy({
        targets: ["defaults", "not IE 11", "Android >= 4.4", "ios >= 9"],
        modernPolyfills: true,
      }),
    ]
  }
})

打包过程

image.png

打包后的产物

<!DOCTYPE html>
<html lang="zh-CN">
  <head>
    <script type="module" crossorigin src="https://test-demo.com/assets/polyfills-9cc75015.js"></script>

    <meta content="text/html; charset=utf-8" http-equiv="Content-Type" />
    <meta
      name="viewport"
      content="width=device-width,initial-scale=1,minimum-scale=1,maximum-scale=1,user-scalable=no,viewport-fit=cover"
    />
    <link rel="icon" type="image/svg+xml" href="https://test-demo.com/favicon.ico" />
    <title>测试打包</title>

    <script type="module" crossorigin src="https://test-demo.com/assets/index-3a0c8010.js"></script>
    <link rel="stylesheet" href="https://test-demo.com/assets/index-bf645e99.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="root"></div>
    
    <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="https://test-demo.com/assets/polyfills-legacy-58e04a10.js"></script>
    <script nomodule crossorigin id="vite-legacy-entry" data-src="https://test-demo.com/assets/index-legacy-7dc1e8da.js">System.import(document.getElementById('vite-legacy-entry').getAttribute('data-src'))</script>
  </body>
</html>

可以看到插件生成的产物文件也都成功添加上了绝对路径,部署后验证无误。

问题解决,大功告成!~🎉


从前对源码不屑一顾,如今对源码逐字分析,终于体会到懂得原理的重要性了。

欢迎各位大佬指教~