vite 打包本地文件无法直接打开, file 类型 URL 引发跨域等问题探讨

问题背景

用 vite 新建了一个 react 项目,画了个简单的页面 demo, 需要输出成静态的 html 文件给其他人预览
直接 yarn build 出来的目录结构

在这里插入图片描述

index.html 内容

<!doctype html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <link rel="icon" type="image/svg+xml" href="/vite.svg" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>xxx</title>
    <script type="module" crossorigin src="/assets/index-nGll5NEH.js"></script>
    <link rel="stylesheet" crossorigin href="/assets/index-PEdz5tmg.css">
  </head>
  <body>
    <div id="root"></div>
  </body>
</html>

直接打开这个 html, 预想中应该可以像开发那样看到相关的页面,但是打开却是一片空白。用 f12 查看一下控制台报错,注意到是跨域的问题。

在这里插入图片描述

然后产生疑问就是我这个页面全是静态资源,连服务都没调用,为什么会跨域?

初学前端时简单的写点页面和css直接引用,然后打开 html 是可以正常显示的,为什么用 vite 打包的页面就不行。

于是提出两个要解决的问题:

  1. 怎么让 vite 打包出可以直接本地访问的静态 html 页面
  2. 是什么触发了跨域报错问题

问题探讨

解决 vite 打包的静态资源无法直接访问问题

其实打包出来的资源直接挂个网络服务器就能访问,最快的方法是在 dist 目录下面运行下面指令

npm install --global serve
serve

这样会默认将 dist 文件当成 web 服务根目录来启动一个服务器,
根据提示的端口访问就能够正常看到页面

在这里插入图片描述

但我想要的是直接打开 html 文件就能看到页面,在网上搜寻一番后找到了比较有效的解决方案

先放上原博客的链接
https://www.cnblogs.com/lingern/p/17789995.html

总结来说就是修改 vite 的项目构建配置信息:

  1. 修改资源相对基址为 ./
  2. 引入 @vitejs/plugin-legacy 插件, 然后用 legacy 函数做相关配置
import { defineConfig } from "vite";
import react from "@vitejs/plugin-react";
import legacy from "@vitejs/plugin-legacy";

// https://vitejs.dev/config/
export default defineConfig({
  base: "./",
  plugins: [
    react(),
    legacy({
      targets: ["ie>=11"],
      additionalLegacyPolyfills: ["regenerator-runtime/runtime"],
    }),
  ],
});

最终打包出来的 index.html 区别

<!doctype html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <link rel="icon" type="image/svg+xml" href="/vite.svg" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>xxx</title>
    <script type="module" crossorigin src="./assets/index-b43LkIEh.js"></script>
    <link rel="stylesheet" crossorigin href="./assets/index-tvuqbY_7.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-HAG11n64.js"></script>
    <script nomodule crossorigin id="vite-legacy-entry" data-src="./assets/index-legacy-4n7y874Q.js">System.import(document.getElementById('vite-legacy-entry').getAttribute('data-src'))</script>
  </body>
</html>

可以看出和修改配置前打包出来的 html 是有区别,其中比较显眼的是和本次讨论主题——跨域相关的crossorigin关键字。

我们先去查下 MDN 了解一下这个关键字。
https://developer.mozilla.org/en-US/docs/Web/HTML/Attributes/crossorigin

当然这里就不把文档内容复述了,我直接说一下我学习后的总结:

跨域的字段标准定义在报文头部中, 具体的处理逻辑是浏览器来实现的。也就是浏览器在响应头中设置的跨域信息,需要由浏览器来实施。具体行为就是根据不同的跨域信息,判断是否将响应结果返回给客户端。

由此可以得到一个快速处理跨域的方法,服务端代理。因为如果请求是一个网络服务器发出的,那边响应头中的跨域配置就不会被解读,这时携带的资源就能被正常提取。

这也是为什么在开发一些由 webpack 或者 vite 这些工具来构建的前端项目时,如果出现了跨域问题,网上的攻略会说改一下 webpack.config.js 或者 vite.config.js 中的跨域配置或者代理就能解决。因为在开发环境时,我们的页面请求在使用相对路径情况下,是直接发往工具启动的网络服务器中。

而 crossorigin 这个关键字可以作用在 <audio>, <img>, <link>, <script> and <video> 元素中,为这些元素的资源请求添加默认的跨域配置。

当 crossorigin 没有赋值时,其默认时是空字符串,其效果等同于crossorigin=“anonymous”, 将认证标志设为 same-origin。

以上关于跨域内容的总结也是有点长了,跨域还有更多的具体规定,比如跨域可以怎么控制等等的细节,这里不宜展开讨论,因为我们本次讨论的主题是为什么默认 vite 配置打包出来的静态资源文件会触发跨域。对跨域感兴趣的小伙伴可以深入学习以下文档:
https://developer.mozilla.org/en-US/docs/Web/HTTP/CORS


绕了一个圈子去了解了一下跨域基础,回到我们刚刚中断的地方。我们知道在修改配置后打包出来的 html 变了,而我们引入的新配置是 regenerator-runtime/runtime, 让我们看一下这个库做了什么。

在这里插入图片描述

搜索 npm 可以看到这个库的介绍是一个无依赖的运行时,用于支持 Regenerator 转编生成的异步 async 和生成器。也就是将 async 关键字这些换成更加普遍的实现,增强兼容性。

我们知道 es6 在 import 一个模块时其实是引入了一个异步生成器,一般可以结合期约Promise来完成模块的动态引入。虽然现在大多数浏览器都支持了 es6, 但是这些库存在就是用来提供最大兼容性的。

小结

前面直接打包出来的静态文件无法正常渲染,应该是组件内部的导入语句及其相关关键字没有正常解析导致,与跨域其实没有太大关系,因为即使是可以正常渲染的静态页面,也会出现跨域的错误警告。

在这里插入图片描述

那么解决无法渲染的问题只需要按照上面的步骤添加一些运行时垫片即可。

火狐浏览器也不能正常显示静态页面的问题

经过实测,即使采用上述的方案进行打包,在火狐浏览器中也不能显示正常的页面。
从控制台中可以看到两种关键的错误信息:

  1. 已拦截跨源请求:同源策略禁止读取位于 file://xxxxx/dist/assets/index-legacy-4n7y874Q.js 的远程资源。(原因:CORS 请求不是 http)
  2. Uncaught (in promise) Error: file://xxxxx/dist/assets/index-legacy-4n7y874Q.js

其中的跨域报错可以查看以下文档进行了解:
https://developer.mozilla.org/zh-CN/docs/Web/HTTP/CORS/Errors/CORSRequestNotHttp?utm_source=devtools&utm_medium=firefox-cors-errors&utm_campaign=default

而请求文件引发 Promise 错误,可能也是火狐禁止加载不安全的外部文件的策略导致。在这里就不深入去探究具体原因了,先把我们的主线讲完。

继续探讨为什么静态打包会引发跨域问题

其实从上面的警告已经可以看到部分答案,那就是静态资源访问各种资源时使用的是 file 协议,而浏览器在这个协议的 URL 中解析到的 origin 的值是 null。跨域错误便由此来。

我们知道了表面的原因,那么接下来就是根据这个引导,找出相关的定义文档,了解一下 URL 的解析规则。

MDN 关于 origin 字段的说明

https://developer.mozilla.org/en-US/docs/Web/API/URL/origin

提到 file URL 的 origin 取决于浏览器的具体实现

For file: URLs, the value is browser dependent.

URL 定义文档中关于 origin 字段的说明

https://url.spec.whatwg.org/#dom-url-origin

提到 origin 字段返回 URL 的 origin 序列化后的结果

The origin getter steps are to return the serialization of this’s URL’s origin.

URL 定义文档中关于 origin 的具体值的说明

https://url.spec.whatwg.org/#concept-url-origin

在这里插入图片描述

上面的信息已经讲得很清楚了,常规的 httphttps URL 返回的 origin 信息是一个元组,当然怎么解析这个元组是浏览器内部的事。而对于 file URL 则明确表示依旧是取决于浏览器的具体实现,且说明了一般情况下的默认值是一个 opaque origin 实例。

HTML 定义文档中关于 origin 怎么取值的说明

https://html.spec.whatwg.org/multipage/browsers.html#concept-origin-opaque

在这里插入图片描述

这篇文档中详细介绍了解析 HTML 过程中关于 origin 的取值规范和步骤。然后对于 opaque origin 实例,其序列化后的结果,也就是最终的 origin 的值定义为 null

那么其实到这里就已经完成了本文的问题探讨,当然文档中关于 origin 的取值还有很多值得学习的细节,感兴趣的小伙伴可以自行研读。

总结

在浏览器直接打开打包完成的静态 html 页面时,会因为是通过 file 类型的 URL 来访问各类资源而引发跨域问题。其原因是 HTML 和 URL 规范中对 file URL 的 origin 内容的提取有着相关规定,一般情况下会返回 null

评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值