Taro 小程序 Video 组件 referrer-policy="origin" 属性失效排查记

作为一名资深的前端工程师,我深知线上系统稳定运行的重要性,尤其是在 UAT(用户验收测试)阶段,任何一个小小的"风吹草动"都可能引发一场"腥风血雨"。这不,最近我们就遭遇了一场关于视频播放的"罗生门"。

故事经过二次创作,纯属虚构 如有雷同纯属巧合

故事的开端:UAT 阶段的"黑屏"疑云

那天,UAT 测试同学急匆匆地跑过来:"小王,你看看,客户电商小程序里的视频怎么都看不了了?一片黑屏!这可是商品详情页的核心功能啊!"

我心里咯噔一下。我知道这是一个正在紧锣密鼓准备发布的迭代项目,视频播放作为商品详情页的核心功能,一旦出问题,将直接影响用户体验和上线进度。更重要的是影响客户打钱的速度(狗头~)

我立刻参与到紧张的排查中。经验告诉我,这通常不外乎几种情况:

  • 网络问题? 我首先检查了开发环境和测试环境的网络连接,确保视频资源服务器可达,并且网络带宽正常。很快,我排除了网络连接异常的可能性。
  • 视频编码问题? 我尝试下载了视频文件,用本地播放器打开,确认视频本身没有损坏,并且编码格式(如 H.264)是主流且兼容性良好的。
  • 视频地址本身有问题? 但是,我在微信开发者工具中尝试播放该视频,却能正常播放,这表明视频资源本身是有效的,但是真机却不行。

在排除了这些常见问题后,我隐约觉得,问题可能出在更深层次的机制上。这时,项目经理又过来催促:"小王,明天这期需求要上线,这个视频问题必须尽快解决啊!"压力瞬间拉满。

突然我想起来视频服务为了防止盗链,做了严格的防盗链配置:只允许特定域名的请求携带 Referer 头访问视频资源。 如果 Referer 头不符合要求,或者干脆没有 Referer 头,视频服务器就会拒绝请求,返回 403 错误。

初步排查:Referer 头去哪儿了?

既然本地控制台没有直接的错误,我决定从服务器端入手。我联系了后端和运维同学,让他们帮忙查看阿里云 OSS (Object Storage Service) 的访问日志

果然,在 OSS 的访问日志中,我赫然发现那些失败的视频请求,其 Referer 字段竟然是空的 或者不符合预期的!而这些请求最终都返回了 403 Forbidden。很明显,是防盗链机制在"作祟"。

这下问题就明确了:小程序在请求视频资源时,没有带上预期的 Referer 头,导致被视频服务器无情地"拒之门外"。而开发者工具的本地网络面板,可能在某些情况下未能完全模拟真机环境下的 Referer 行为,或者由于缓存等原因,没有即时反映出真实的请求状态,从而增加了排查的难度。

按照微信小程序官方文档的说法,video 组件是支持 referrer-policy 属性的,我们可以通过设置 referrer-policy="origin" 来确保请求携带 Referer 头。

我检查了代码,我们的 video 组件确实是这样写的:

tsx 复制代码
// 我们的 Taro 代码  
<Video  
  src={videoUrl}  
  controls  
  autoplay  
  referrerPolicy="origin" // 看,我们写了!  
  // ... 其他属性  
/>

看起来一切正常啊!难道是 referrer-policy="origin" 这个属性没生效?

社区求助:似是而非的答案

我立刻在网上搜索"Taro 小程序 video referrer-policy 失效"、"小程序视频防盗链"。果然,社区里有不少同行也遇到过类似的问题。一些帖子提到可能是微信小程序版本过低、或者手机系统版本问题。

我让测试同学升级了微信和手机系统,但问题依旧。这说明,这些"通用解法"并不适用于我们这次的"疑难杂症"。问题一定出在更深层次的地方。

绝地反击:原生小程序 vs. Taro

既然社区没有直接的答案,那我就得祭出前端工程师的"杀手锏"------对比法

我决定写一个最简单的原生微信小程序,里面只放一个 video 组件,同样设置 referrer-policy="origin",然后用同样的视频地址进行测试。

xml 复制代码
<!-- 原生微信小程序 WXML -->  
<video  
  src="{{videoUrl}}"  
  controls  
  autoplay  
  referrer-policy="origin"  
></video>

奇迹出现了!原生小程序写法的视频在真机完美播放,网络请求中也赫然出现了正确的 Referer 头!

这一下,真相呼之欲出:问题不在微信小程序本身,也不在防盗链配置,而在于 Taro 框架在编译或运行时对 video 组件 referrer-policy 属性的处理上!

抽丝剥茧:Taro 构建输出的"秘密"

众所周知,Taro 会将我们写的 JSX/React 代码编译成微信小程序能够识别的 WXML、WXSS 和 JavaScript。那么大概率是 Taro 在编译过程中,对 video 组件做了特殊处理,导致 referrer-policy 属性在真机上失效。 所以下一步自然是深入 Taro 源码,找出是哪个环节"吞掉"了 referrer-policy

@tarojs/shared 包中我们找到了这么一段代码:

:::info 此包是 Taro 内部使用的 utils。包含了常用的类型判断、错误断言、组件类型/声明/参数等

实际代码太长,我们只看关键片段 :::

tsx 复制代码
// src/components.ts
const Video = {
  src: NO_DEFAULT_VALUE,
  duration: NO_DEFAULT_VALUE,
  controls: DEFAULT_TRUE,
  'danmu-list': NO_DEFAULT_VALUE,
  'danmu-btn': NO_DEFAULT_VALUE,
  'enable-danmu': NO_DEFAULT_VALUE,
  autoplay: DEFAULT_FALSE,
  loop: DEFAULT_FALSE,
  muted: DEFAULT_FALSE,
  'initial-time': '0',
  'page-gesture': DEFAULT_FALSE,
  direction: NO_DEFAULT_VALUE,
  'show-progress': DEFAULT_TRUE,
  'show-fullscreen-btn': DEFAULT_TRUE,
  'show-play-btn': DEFAULT_TRUE,
  'show-center-play-btn': DEFAULT_TRUE,
  'enable-progress-gesture': DEFAULT_TRUE,
  'object-fit': singleQuote('contain'),
  poster: NO_DEFAULT_VALUE,
  'show-mute-btn': DEFAULT_FALSE,
  bindPlay: NO_DEFAULT_VALUE,
  bindPause: NO_DEFAULT_VALUE,
  bindEnded: NO_DEFAULT_VALUE,
  bindTimeUpdate: NO_DEFAULT_VALUE,
  bindFullScreenChange: NO_DEFAULT_VALUE,
  bindWaiting: NO_DEFAULT_VALUE,
  bindError: NO_DEFAULT_VALUE,
  ...animation
}

通过仔细检查 Video 对象中列出的所有属性,我们可以清晰地看到,referrer-policy 这个属性没有出现在这个映射表中。当然光靠这里还不够,我们继续看一下解析层面。

众所周知,Taro 3 之后采用了 HACK DOM 的方式实现 JSX -> VDOM -> HACK DOM 操作 -> 小程序 Template 的流程来渲染小程序。

所以继续在小程序模板编译渲染代码里面查看:

tsx 复制代码
// src/template.ts
export class BaseTemplate {
  // ...只留关键代码
  protected createMiniComponents (components: Components) {
    const result: Components = Object.create(null)
    
    for (const key in components) {
      let component = components[key] // 假设这里是 Video 组件的定义
      const compName = toDashed(key)  // 'video'
      
      for (let prop in component) {
        let propValue = component[prop]
        // 根据不同情况处理 propValue
        if (prop.startsWith('bind') || propValue === 'eh') {
          propValue = 'eh'
        } else if (propValue === '') {
          // 空字符串会映射到 i.propAlias
          propValue = `i.${propAlias}`
        } else if (isBooleanStringLiteral(propValue) || isNumber(+propValue)) {
          // 布尔值或数字会生成条件表达式
          propValue = `i.${propAlias}===undefined?${propValue}:i.${propAlias}`
        }
        // ...
      }
    }
  }

  // 在模板中生成属性:通过 buildAttribute 方法将处理后的属性转换为模板字符串
  private buildAttribute (attrs: Attributes, nodeName: string): string {
    return Object.keys(attrs)
      .map(k => `${k}="${k.startsWith('bind') || k.startsWith('on') || k.startsWith('catch') ? attrs[k] : `{${this.getAttrValue(attrs[k], k, nodeName)}}`}" `)
      .join('')
  }
}

问题根源分析:

Taro 在进行模板编译和渲染时,未在 internalComponents 中定义的 key 无法被使用,个人认为原因应该是:

  • 白名单机制:internalComponents 中的定义实际上起到了白名单的作用,只有预定义的属性才会被包含在最终的模板中。

  • 性能优化:这样做是为了:减少模板大小、避免不必要的属性传递、确保只有有效的小程序组件属性被使用。

实际模板输出类似下面这样:

xml 复制代码
<template name="tmpl_0_video">
  <video 
    src="{{i.src}}" 
    duration="{{i.duration}}" 
    controls="{{i.controls===undefined?!0:i.controls}}" 
    autoplay="{{i.autoplay===undefined?!1:i.autoplay}}"
    class="{{i.cl}}" 
    style="{{i.st}}"
    bindplay="eh"
    id="{{i.uid||i.sid}}" 
    data-sid="{{i.sid}}">
    <!-- 丢失了 referrer-policy -->
    <!-- children -->
  </video>
</template>

解决方案:提交 PR 或曲线救国(打 Patch)

找到了问题的根源,解决方案就清晰了:

1. 首选:向 Taro 社区提交 Pull Request (PR)

这是最优雅、最彻底的解决方案。通过修改 Taro 框架的源码,将 referrerPolicy 属性添加到 video 组件的属性映射中。一旦 PR 被合并并发布新版本,所有使用该版本的 Taro 项目都能受益。

  • 优点: 彻底解决问题,贡献社区,提高框架健壮性。
  • 缺点: 需要等待 PR 审核、合并、发布新版本,时间周期不确定。

2. 临时方案/曲线救国:使用 patch-package 或 pnpm patch 打补丁

在等待框架更新期间,如果项目紧急上线,我们可以通过打补丁(patch)的方式来修改 node_modules 中的框架代码。

对于 Yarn 项目,可以使用 patch-package:

  1. 修改 node_modules 中的代码: 首先,手动进入 node_modules/@tarojs/shared 目录(或其他包含 video 组件属性映射的 Taro 包),找到对应的文件,添加 referrerPolicy 属性的映射。

  2. 生成补丁文件: 在项目根目录下运行 npx patch-package @tarojs/shared(替换为实际修改的包名)。这会在项目根目录生成一个 patches 文件夹,里面包含一个 .patch 文件,记录了你对 node_modules 中该包的所有修改。

  3. 配置 package.json: 在 package.json 的 scripts 中添加 postinstall 命令,确保每次 yarn install 后都会自动应用补丁。

json 复制代码
{
  "scripts": {
    "postinstall": "patch-package"
  },
  "dependencies": {
    // ...
  }
}
  1. 提交补丁文件: 将生成的 patches 文件夹和 package.json 的修改一并提交到版本控制系统。这样,其他开发人员拉取代码后,执行 yarn install 即可自动应用补丁。

对于 pnpm 项目,可以使用 pnpm patch:

  1. 创建补丁会话: 运行 pnpm patch @tarojs/shared(替换为实际修改的包名)。pnpm 会将该包解压到一个临时目录,并提示你进入该目录进行修改。

  2. 修改代码: 在临时目录中修改对应的文件,添加 referrerPolicy 属性的映射。

  3. 生成补丁文件: 修改完成后,退出临时目录,运行 pnpm patch-commit <临时目录路径>。pnpm 会在项目根目录的 patches 文件夹中生成一个 .patch 文件,并在 package.json 中自动添加 pnpm.patchedDependencies 配置。

  4. 提交补丁文件: 将生成的 patches 文件夹和 package.json 的修改一并提交到版本控制系统。

补丁方案的优缺点:

  • 优点: 能够立即解决问题,不依赖框架发布新版本,且补丁文件可以随项目代码一起管理。
  • 缺点: 增加了项目的复杂性,需要管理补丁文件;如果框架更新导致代码结构变化,补丁可能失效,需要重新生成。

最终,我们选择了先采用打补丁的方式解决 UAT 阶段的燃眉之急。解决完问题后,走出公司大楼抬头一看,凌晨的大楼,还是那么的灯火通明呢! 😩😩😩

最终修改的文件应该是编译后的输出文件
@tarojs/shared/dist/index.js
@tarojs/shared/dist/shared.esm.js

经验总结:框架不是万能的"黑箱"

这次排查经历再次提醒我,即使是像 Taro 这样优秀的框架,也并非一个完全的"黑箱"。当遇到框架层面的疑难杂症时:

  • 不要盲目相信: 即使代码看起来"没问题",也要质疑它是否真的按预期工作。
  • 深入原理: 了解框架的编译原理、组件映射机制,是解决这类问题的关键。
  • 产物分析: 检查构建后的产物(WXML、JS 等)往往能揭示真相。
  • 对比测试: 原生实现是验证框架行为的"黄金标准"。
  • 社区与源码: 积极查阅社区、提交 issue 或直接阅读源码,是解决复杂问题的有效途径。

这次"黑屏"事件,从最初的困惑到最终的真相大白,就像一场侦探游戏。而作为前端工程师,我们不仅是代码的"搬运工",更是问题的"侦探"和"解决者"。希望我的这次"探案"经历,能为你未来的开发之路提供一些启发。

后记

你以为结束了? 当我们信心爆棚的提交UAT后。发现 IOS真机上小程序 referrer-policy设置 不!生! 效 !

并且此问题在社区存在广泛的讨论,综合全网的讨论假设。主流的观点在于以下几点:

  • IOS系统的BUG: Safari 浏览器曾出现过 Referer 头被截断或继承父页面 referrer-policy 的问题
  • IOS隐私政策: 苹果新的隐私政策,要求应用必须获得用户的明确授权才能收集 Referer 头。微信小程序没有处理这个环节
  • 小程序环境 : 微信对所有出站 HTTP/HTTPS 请求的 Referer 头进行了拦截和标准化处理。在这一层存在问题,包括但不限于:
    • 无法自定义 Referer 头
    • 无法设置 referrer-policy
    • 无法获取 Referer 头
    • 固定格式:https://servicewechat.com/{appid}/{version}/page-frame.html 设置错误

截止我编写文章截止此问题依旧没有明确的原因和解决方案。社区也一直处于官方说支持但是实操无法解决的状态。

实际上我们的后续是 客户考虑小程序是面向内部B端且IOS手机不多。放弃适配IOS播放了... 我也会继续关注问题进展。希望这个问题能真正的解决~

相关推荐
jingling555几秒前
面试版-前端开发核心知识
开发语言·前端·javascript·vue.js·面试·前端框架
拾光拾趣录5 分钟前
CSS 深入解析:提升网页样式技巧与常见问题解决方案
前端·css
莫空00006 分钟前
深入理解JavaScript属性描述符:从数据属性到存取器属性
前端·面试
guojl7 分钟前
深度剖析Kafka读写机制
前端
FogLetter7 分钟前
图片懒加载:让网页飞起来的魔法技巧 ✨
前端·javascript·css
Mxuan8 分钟前
vscode webview 插件开发(精装篇)
前端
Mxuan9 分钟前
vscode webview 插件开发(交付篇)
前端
Mxuan10 分钟前
vscode 插件与 electron 应用跳转网页进行登录的实践
前端
拾光拾趣录11 分钟前
JavaScript 加载对浏览器渲染的影响
前端·javascript·浏览器
Codebee11 分钟前
OneCode图表配置速查手册
大数据·前端·数据可视化