使用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.ico
、css
以及业务 js
都成功添加了我们配置的绝对路径 https://test-domain.com
,但是一些 polyfills
却没有。这样一来,部署到服务器后可能导致无法找到这些文件。因此,我们初步可以将问题定位于 —— @vite/plugins-legacy
插件生成的产物中,配置绝对路径未能生效。
3. 插件分析
全网搜罗,包括论坛和官方网站,都没有找到有关此问题的说明与修改建议,最终,在公司大佬的提示下,我们查看了该插件的源码。
3.1 点击 legacy
定位到源码位置
3.2 找到 index.mjs
保险起见,可以在这个文件中输出任意一条 console
信息,以验证它在打包过程中是否执行。
3.3 查找 base
由于配置了 base
未生效,那就找找看有没有相应的逻辑处理,发现了两个关键方法:
-
toOutputFilePathInHtml
-
getBaseInHTML
在这两个方法中,源码都对
config.base
进行了判断,检查其是否为空,或者是否为相对路径./
。由于我们传入的是绝对路径,不符合该判断,因此
toOutputFilePathInHtml
中,判断后执行joinUrlSegments
方法getBaseInHTML
中,判断后输出config.base
我们接着分析一下这两个方法是被谁调用,以及方法内部都做了什么。
-
joinUrlSegments
这个方法接收两个参数,即上文传入的
config.decodedBase
和filename
,由于配置文件中我们并没有传入decodedBase
,因此直接进入第一个if
判断,返回undefined
。 -
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,
}),
]
}
})
打包过程
打包后的产物
<!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>
可以看到插件生成的产物文件也都成功添加上了绝对路径,部署后验证无误。
问题解决,大功告成!~🎉
从前对源码不屑一顾,如今对源码逐字分析,终于体会到懂得原理的重要性了。
欢迎各位大佬指教~