LOGO OA教程 ERP教程 模切知识交流 PMS教程 CRM教程 开发文档 其他文档  
 
网站管理员

如何解决前端监控上报 Script Error?

zhenglin
2026年2月6日 15:45 本文热度 109

前言

在微前端和多国业务的场景下,我们经常会遇到 HTML 页面域名和静态资源域名不统一的情况。这种架构虽然带来了部署和 CDN 优化的便利,但也引入了一个常见的问题:跨域 Script Error。本文将详细介绍这个问题的原因、影响,以及一套完整的解决方案。



问题背景:

业务场景

我们的项目是一个多国业务的前端应用,采用了以下架构:

  • HTML 页面域名:每个地区/业务线使用不同主域名

    • 例如:countryA-web.example.comcountryB-web.example.com, ...


  • 静态资源 CDN 域名:统一使用一个公共 CDN 地址

  • 例如:cdn.example.com


问题表现

在这种架构下,我们遇到了以下问题:

1.监控上报的 JS 错误全部显示为 "Script Error"

    • 无法获取详细的错误堆栈信息

    • 无法定位具体的错误位置

    • SourceMap 无法正确映射

2.错误信息丢失

    • 错误消息被浏览器隐藏

    • 无法获取错误发生的文件、行号、列号

    • 调试和问题排查变得困难


问题原因分析

1. 浏览器的同源策略

当脚本从不同源加载时,浏览器会应用同源策略(Same-Origin Policy)的安全机制:

  • 如果脚本发生错误,且脚本的源与页面不同,浏览器会隐藏错误的详细信息

  • 只返回通用的 "Script Error" 消息

  • 这是为了防止恶意网站通过错误信息获取敏感数据


2. 缺少 CORS 配置

要获取跨域脚本的详细错误信息,需要满足两个条件:

  1. 脚本标签添加 crossorigin 属性
  2. 服务器返回正确的 CORS 响应头


当你在 <script><link> 标签上添加 crossorigin="anonymous",实际上是在告诉浏览器:“允许以跨源模式拉取这个资源,并且无须附带任何凭证(cookie、鉴权头等) 。”

如果该标签没有 crossorigin 属性,浏览器以默认的“no-cors”模式加载资源,只要脚本跨域,一旦脚本发生错误,错误信息就会被隐藏,只能看到 Script Error,同时 sourceMap 也无法正确还原堆栈。


只有当 crossorigin 属性为 anonymous 并且服务器响应了正确的 CORS 头,浏览器才会呈现完整的报错信息和堆栈文件行号,对异常监控和调试至关重要。

与此同时,服务器/CDN 响应也必须包含正确的 CORS 相关头部,如 Access-Control-Allow-Origin

这是因为浏览器在跨域加载资源时,会先根据标签属性判断是否允许访问细节,再检查服务器是不是“回应放行”了该跨域请求。如果服务器未设置这些头部,哪怕你加了 crossorigin 属性,浏览器也会隐藏资源细节和所有报错内容,仅显示 Script Error

如果缺少其中任何一个,浏览器都会隐藏错误详情。


3. Webpack/Rspack 动态加载机制

现代前端构建工具(Webpack、Rspack)在实现代码分割和懒加载时,会通过 document.createElement('script') 动态创建 script 标签来加载 chunk。

如果这些动态创建的标签没有 crossorigin 属性,同样会导致 Script Error。


解决方案

我们采用了一套三层防护的解决方案:

方案架构图


HTML层:模板引用资源手动修改

对于 HTML 模板中直接引用的静态资源,我们手动添加 crossorigin="anonymous" 属性:

 

<!-- 所有跨域的 script 标签 -->

<script

  src="https://cdn.example.com/static/polyfill.min.js"

  defer

  crossorigin="anonymous"

></script>


<!-- 所有跨域的 link 标签(CSS) -->

<link

  rel="stylesheet"

  href="https://cdn.example.com/assets/iconfont.css"

  crossorigin="anonymous"

/>

打包工具层:Webpack/Rspack 插件自动处理(构建时)

对于构建工具自动插入的脚本和样式,我们创建了一个自定义插件:

class CrossOriginAssetsPlugin {

  apply(compiler) {

    const pluginName = 'CrossOriginAssetsPlugin';

    compiler.hooks.compilation.tap(pluginName, (compilation) => {

      const HtmlWebpackPlugin = require('html-webpack-plugin');

      if (HtmlWebpackPlugin && HtmlWebpackPlugin.getHooks) {

        HtmlWebpackPlugin.getHooks(compilation).alterAssetTags.tap(

          pluginName,

          data => {

            // 处理 script

            data.assetTags.scripts.forEach(tag => {

              if (isCrossOrigin(tag.attributes?.src)) {

                tag.attributes.crossorigin = 'anonymous';

              }

            });

            // 处理 link

            data.assetTags.styles.forEach(tag => {

              if (isCrossOrigin(tag.attributes?.href)) {

                tag.attributes.crossorigin = 'anonymous';

              }

            });

            return data;

          }

        );

      }

    });

  }

}


function isCrossOrigin(url) {

  // 补充判断逻辑:绝对路径跨域、相对路径同源

  return url && /^https?:///.test(url);

}

异步加载层:运行时全局拦截(动态加载)

在业务开发中我们会遇到许多异步动态加载的脚本文件,通过拦截 document.createElement,为所有动态 script/link 节点设置跨域属性,无死角覆盖 chunk、第三方库等异步加载场景。

function isCrossOriginUrl(url: string | null | undefined): boolean {

  return !!url && /^https?:///.test(url);

}


export function setScriptCrossOrigin(script: HTMLScriptElement) {

  const src = script.src || script.getAttribute('src');

  if (isCrossOriginUrl(src)) script.crossOrigin = 'anonymous';

}


export function setLinkCrossOrigin(link: HTMLLinkElement) {

  const href = link.href || link.getAttribute('href');

  if (isCrossOriginUrl(href)) link.crossOrigin = 'anonymous';

}


export function initCrossOriginScriptHandler() {

  const originCreateElement = document.createElement.bind(document);

  document.createElement = function (tagName: string, options?: ElementCreationOptions) {

    const el = originCreateElement(tagName, options);

    if (tagName.toLowerCase() === 'script') {

      // 当 src 插入时才设置

      const observer = new MutationObserver(mutations => {

        mutations.forEach(m => {

          if (m.type === 'attributes' && m.attributeName === 'src') {

            setScriptCrossOrigin(el as HTMLScriptElement);

            observer.disconnect();

          }

        });

      });

      observer.observe(el, { attributes: true, attributeFilter: ['src'] });

    }

    if (tagName.toLowerCase() === 'link') {

      const observer = new MutationObserver(mutations => {

        mutations.forEach(m => {

          if (m.type === 'attributes' && m.attributeName === 'href') {

            setLinkCrossOrigin(el as HTMLLinkElement);

            observer.disconnect();

          }

        });

      });

      observer.observe(el, { attributes: true, attributeFilter: ['href'] });

    }

    return el;

  };

}

在应用启动处调用初始化:


为什么这个方案可以覆盖所有场景?

Webpack/Rspack 在运行时加载 chunk 时,会生成类似这样的代码:

// Webpack 生成的 chunk 加载代码

function loadChunk(chunkId) {

  return new Promise((resolve, reject) => {

    const script = document.createElement('script');  // ← 关键

    script.src = chunkUrl;

    script.onload = resolve;

    script.onerror = reject;

    document.head.appendChild(script);

  });

}

通过拦截 document.createElement,我们可以捕获所有通过此方式创建的 script 标签,包括:

  • React.lazy 懒加载的组件

  • 动态 import() 加载的模块

  • Webpack chunk 动态加载

  • 第三方库的动态加载

  • 手动通过 loadScript() 加载的脚本


服务器端配置

后端需确保 CDN/stastic 资源的 HTTP 响应头允许跨源:

Nginx 配置样例(脱敏)

location /static/ {

  add_header 'Access-Control-Allow-Origin' '*' always;

  add_header 'Access-Control-Allow-Methods' 'GET, OPTIONS' always;

  add_header 'Access-Control-Allow-Headers' 'Range' always;

  add_header 'Access-Control-Expose-Headers' 'Content-Length, Content-Range' always;

  if ($request_method = 'OPTIONS') {

    add_header 'Access-Control-Allow-Origin' '*';

    add_header 'Access-Control-Allow-Methods' 'GET, OPTIONS';

    add_header 'Access-Control-Max-Age' 1728000;

    add_header 'Content-Type' 'text/plain; charset=utf-8';

    add_header 'Content-Length' 0;

    return 204;

  }

}

总结

通过这套三层防护的解决方案,我们成功解决了多域名架构下的跨域 Script Error 问题:

  1. HTML层:手动为静态资源添加 crossorigin 属性

  2. 打包工具层:Webpack 插件自动处理构建时插入的资源

  3. 异步加载层:全局拦截 document.createElement,处理所有动态加载


参考文章:原文链接


该文章在 2026/2/6 15:45:22 编辑过
关键字查询
相关文章
正在查询...
点晴ERP是一款针对中小制造业的专业生产管理软件系统,系统成熟度和易用性得到了国内大量中小企业的青睐。
点晴PMS码头管理系统主要针对港口码头集装箱与散货日常运作、调度、堆场、车队、财务费用、相关报表等业务管理,结合码头的业务特点,围绕调度、堆场作业而开发的。集技术的先进性、管理的有效性于一体,是物流码头及其他港口类企业的高效ERP管理信息系统。
点晴WMS仓储管理系统提供了货物产品管理,销售管理,采购管理,仓储管理,仓库管理,保质期管理,货位管理,库位管理,生产管理,WMS管理系统,标签打印,条形码,二维码管理,批号管理软件。
点晴免费OA是一款软件和通用服务都免费,不限功能、不限时间、不限用户的免费OA协同办公管理系统。
Copyright 2010-2026 ClickSun All Rights Reserved