HarmonyOS Web 组件跨域实战:本地资源为什么“加载不了”,以及两套最稳的解决方案
HarmonyOS Web 组件跨域实战:本地资源为什么“加载不了”,以及两套最稳的解决方案
鸿蒙第四期开发者活动
在 ArkTS 侧把异步逻辑包装成Promise,Native 侧拿到这个 Promise 对象,然后给它挂then / catch,在回调里获取异步结果或异常。
本文用一个完整示例,走一遍从 ArkTS 到 Native 的链路。
1. 整体思路
-
ArkTS 侧:
- 调用 Native 接口时,把一个 callback 传给 Native。
- Native 调用这个 callback,ArkTS 在 callback 里 返回一个 Promise 对象。
- Promise 内部用
setTimeout模拟异步,最终resolve或reject。
-
Native 侧:
- 通过
napi_call_function调用 ArkTS 传入的 callback,得到 Promise 对象。 - 用
napi_get_named_property拿到 Promise 的then和 `cat
- 通过
我第一次在 HarmonyOS 里做离线 H5(把页面和静态资源都放到 rawfile/resfile)的时候,踩到的第一个大坑就是“跨域”。
当时的现象特别像玄学:
index.html能打开- 但
js/script.js、img/logo.png、甚至接口请求会报错 - DevTools 里一堆熟悉的字眼:CORS、blocked、origin null
- 页面看起来像“白屏 / 半残”
后来把官方文档啃了一遍才明白:这不是你写错了,而是 ArkWeb 内核为了安全,默认会拦截 file/resource 这类本地协议的跨域访问。华为开发者+1
这篇我就按“像人写的、能落地”的方式,把官方方案拆开讲清楚,并给你实战建议(什么时候用方案一,什么时候用方案二)。
一、问题根源:不是“跨域请求”,而是“本地协议的源不靠谱”
你在浏览器里做跨域,多半是 https://a.com 去请求 https://b.com。
但在 HarmonyOS 离线 H5 的场景里,很多时候你的页面来源其实是:
resource://...- 或
file://...
这类 URL 在 Web 内核里经常会对应到 origin 为 null 或特殊协议,然后一旦它去加载别的资源(脚本/图片/Fetch 请求),就容易被同源策略 + CORS 规则卡住。
官方文档的表达更直接:为了成功访问跨域资源,建议用 http/https 替代 file/resource,并配合拦截替换资源。华为开发者+1
二、官方给的两套解法(我用下来都挺稳)
方案一:把本地资源“伪装成” http/https 域名 + onInterceptRequest 拦截返回本地内容
官方思路是:
- Web 加载的 URL 用 http/https(比如
https://www.example.com/index.html) - 这个域名是你“自定义构造”的,仅供你自己项目使用,避免和真实互联网域名冲突 华为开发者+1
- 然后用
onInterceptRequest把对这个域名的请求全部拦截下来 - 你在拦截里读取
rawfile/resfile的本地文件,手动拼成响应返回给 Web
这样 Web 内核看到的是 “正常的 https 页面 + 同源静态资源”,跨域自然就不拦了。华为开发者+1
什么时候我建议用方案一?
- 你是 前端工程化产物(Vue/React 打包后资源多、路径复杂)
- 你还要让后端 给这个“自定义域名”加白名单(非常常见)思否
- 你希望统一成“线上/离线同域”,减少差异
一个“能跑的骨架”(示意)
代码写法会随 API version 略有差异,但思路固定:拦截 → 读取本地文件 → 返回 WebResourceResponse
import { webview } from '@kit.ArkWeb';
const FAKE_HOST = 'www.example.com';
Web({ src: `https://${FAKE_HOST}/index.html`, controller: this.controller })
.onInterceptRequest((event) => {
const url = event.request.getRequestUrl?.() ?? '';
if (!url.includes(FAKE_HOST)) return null; // 不拦截其它请求
// 1) 把 https://www.example.com/js/app.js 映射到 rawfile/web/js/app.js
// 2) 读本地文件
// 3) 返回 WebResourceResponse(设置 mimeType/encoding/statusCode 等)
// ——这里省略具体读取代码,避免篇幅爆炸
return this.buildLocalResponse(url);
});
实战建议:
- 路径映射要做得“可预期”:别在代码里到处 if-else
Content-Type(mimeType)要对:js/css/png/svg/json 不同类型都别乱返回- 线上接口请求如果仍然跨域,还是得后端配 CORS(别指望客户端把网络跨域也“魔法解决”)
方案二:setPathAllowingUniversalAccess 设置“允许跨域访问的本地路径白名单”
如果你的业务就是单纯离线资源加载,且你更愿意保持 file:// 访问方式,官方还有第二套更“直接”的方案:
通过
setPathAllowingUniversalAccess()设置一个路径列表。当使用 file 协议访问该列表中的资源时,允许进行跨域访问本地文件。并且一旦设置了该列表,file 协议将仅限访问列表内资源(会覆盖 fileAccess 的行为)。 华为开发者+1
这段话信息量很大,我给你翻译成“人话”:
- 你把几个目录加入白名单
- 以后 Web 用
file://访问这些目录下的资源,就不会被跨域拦 - 但也更严格:file 只能访问你给的那些目录,防止乱读文件
路径怎么写?
常见就是应用的文件目录 / 资源目录。很多资料会给出类似路径示例(比如从 Context.filesDir/resourceDir 拿到的目录及其子目录)。InfoQ+1
什么时候我建议用方案二?
- 你的离线页面结构简单
- 资源都在可控目录
- 不想维护 onInterceptRequest 的映射和响应拼装
- 你明确只想解决 “file/resource 本地跨域加载失败”,不想引入“伪域名”
三、你该怎么选?(我真实项目的选型经验)
我一般按下面这个规则走:
- Vue/React 离线包、资源一大堆、还要兼容后端白名单 → 选方案一(伪 https 域名 + onInterceptRequest)华为开发者+1
- 轻量离线页、资源少、纯本地展示为主 → 选方案二(setPathAllowingUniversalAccess)华为开发者+1
另外还有一个容易忽略的点:
如果你用了自定义协议(比如 myapp://),并且还想让它参与跨域/fetch 之类的行为,可能需要额外给协议授权(例如 customizeSchemes 相关能力),不然你会看到“只有 http/https/某些协议才支持跨域”的那类报错。bbs.itying.com+1
四、排查清单(遇到白屏我就按这个顺序查)
- 控制台是否出现 CORS blocked / origin null 之类报错
- 资源 URL 是不是
resource:///file://在互相引用(典型跨域触发点) - 如果用方案一:
- onInterceptRequest 是否拦截到了(日志打出来)
- mimeType 是否正确(js 被当成 text/plain 也可能直接炸)
- 如果用方案二:
- 白名单路径是否覆盖到了你真实访问的文件路径
- 是否因为设置白名单后,file 反而被限制访问其它目录了 华为开发者+1
五、收个尾:跨域不是“麻烦”,是你在做更安全的离线 Web
说实话,我后来反而挺能理解这个限制:
离线 H5 一旦能随便跨域读文件、随便访问资源目录,那安全边界就很容易被打穿。
所以 HarmonyOS 这块给你两条路:
你只要选对方案,离线 Web 的稳定性会提升非常明显。

浙公网安备 33010602011771号