可以使用fetch来替换整个文档或HTML元素吗?

3
在一个网站扩展中,我将模板元素从后台页面传递为字符串到一个空的本地HTML文件,并使用browser.tabs.insertCSSbrowser.tabs.executeScript来传递CSS和JS。这一直很有效,但最近我了解到可以使用fetch直接从扩展中检索文件,这将减少后台页面的大小或完全消除它,只剩下后台脚本,从而减少RAM的使用。
我在这个SO问题中看到了一些例子,它们使用innerHTML将HTML字符串写入body元素,还有一个使用DOMParser。但如果你尝试,HTML会添加到body元素中。
另一个SO问题,十年前,有一个五年前的回答对像我这样的新手来说很有趣; 我尝试了这个,看起来可行。
fetch('file.html')
.then( response => response.text() )
.then( result => {
        let parser = new DOMParser(),
            doc = parser.parseFromString( result, 'text/html' );
        document.replaceChild( doc.documentElement, document.documentElement );
    } );

我的问题是,这是否是一种有效可靠的方法完全替换HTML元素?

谢谢。


你的JavaScript事件处理程序等会发生什么? - vvg
我可以在检查器中看到脚本标签随 HTML 文档一起出现,但是事件在我的测试文件中无法正常工作。感谢您指出这个问题。我想我需要找出如何使脚本运行或通过扩展程序进行注入。 - Gary
脚本标签出现在检查器中,但源数据仍然是旧的HTML和脚本。 - Gary
2个回答

3

不行。这可能会在某些情况下产生意外的副作用。

以下是演示此问题的简单示例:

index.html

<html>
<head>
  <title>DOM Parser test</title>
  <link rel="stylesheet" href="style.css">
</head>
<body>
  <h1>This is the original file</h1>
  <button id="btn1" type="button">Click to reload</button>

  <script>
    var btn1 = document.getElementById('btn1');
    function onClick() {
      fetch('file.html')
       .then( response => response.text() )
       .then( result => {
          let parser = new DOMParser();
          doc = parser.parseFromString( result, 'text/html' );
          document.replaceChild( doc.documentElement, document.documentElement );
        } );
    }

    btn1.addEventListener('click', onClick);
  </script>
</body>
</html>

file.html

<html>
<head>
  <title>DOM Parser test</title>
  <link rel="stylesheet" href="style.css">
</head>
<body>
  <h1>This is the second file</h1>
  <button id="btn1" type="button">Click to reload</button>

  <script>
    var btn1 = document.getElementById('btn1');
    function onClick() {
      fetch('file.html')
       .then( response => response.text() )
       .then( result => {
          let parser = new DOMParser();
          doc = parser.parseFromString( result, 'text/html' );
          document.replaceChild( doc.documentElement, document.documentElement );
        } );
    }

    btn1.addEventListener('click', onClick);
  </script>
</body>
</html>

style.css

html body {
      background: #00003f;
}

我使用Node.js http-server ./ 服务来提供这些静态文件,并访问 index.html。

enter image description here

现在点击 "Click to Reload" 按钮。这应该获取 file.html 并将其呈现出来。

enter image description here

正如您在 index.html 和 file.html 的源代码中所看到的,它们都引用了 style.css 样式表。index.html 呈现了引用的样式,而被获取并用于更新 documentElement 的 file.html 没有遵守样式表。

这是事情出错的一个例子。


谢谢您的例子。 非常有趣,让我省了很多麻烦,可能会浪费几个小时的时间,因为我以为只是在扩展内做错了什么。 使用fetch获取其他DOM元素的内容是否安全,这些内容将通过后台页面中的模板元素提供,然后通过browser.tab方法添加CSS和JS? - Gary
我一直在使用insertAdjacentHTML将模板作为字符串添加,因为无法从后台传递DOM节点到页面。也许这些模板可以是扩展中的单独HTML文件,这样它们就可以被获取、解析并用于替换body元素或body元素的内容,之后浏览器选项卡方法可以添加CSS和JS。这仍然可以减小背景HTML页面的大小或删除它。 - Gary
是的,如果您想插入需要遵守全局上下文的组件,则可以使用insertAdjacentHTML;如果您想使用自包含组件,则可以使用Shadow DOM(请参见:https://developer.mozilla.org/zh-CN/docs/Web/Web_Components/Using_shadow_DOM)。 - vvg

0

我问这个问题已经有一段时间了,当时我几乎不懂得足够的知识来提问。自那时以来,我学到了一些东西,认为这是可能的。我并不是在声称这是专业的做法,甚至不是一个好主意,但对于我的项目来说效果不错。

请不要认为我写这篇文章是因为我发现了什么新奇的东西。我写这篇文章是因为,如果我在当时问这个问题的时候知道了这个方法,我可以更轻松地达到我的目标。

下面的rspObj.data是HTML文档的文本,无论是通过fetch还是其他方式获取的,其中不包括代码。

问题是脚本没有执行,样式表也没有应用,尽管它们在控制台中显示为已加载。如果将样式表添加到head中,并将script元素添加到body中,并分别设置它们的属性,那么它们将被接受并执行。

之前的脚本似乎仍然在开发者工具的源列表中,但似乎无法再次被新文档使用。这可能是因为我在使用模块;我不确定,但我无法在新文档的模块中调用函数,即使它是由先前的模块导出和导入的。但是,如果脚本再次加载,那么一切都像加载了一个新页面一样正常工作。
我并不是在暗示这是一个好的做法,但问题似乎被留下来,好像这一切都是神秘的非故意副作用,而不是与样式表和脚本在解析文档时的执行方式有关。这似乎与一些非常古老的问题没有什么不同,这些问题涉及如何运行从XMLHttpRequest中检索到的脚本。
  let
     d = new DOMParser(),
     b = d.parseFromString( rspObj.data, 'text/html' ),
     src = [],
     c;

  if ( (c = document.querySelector("head")).replaceChildren ) {
    // Not supported in olders version of Safari.
    c.replaceChildren( ...b.head.children );
  } else {
    c.parentNode.replaceChild( b.head, c );
  }
  b.querySelectorAll("script").forEach( v => {
     src.push(v.src); // Or it could be textContent.
     v.remove();
  });

  document.documentElement.replaceChild( b.body, document.body );
  src.forEach( v => {
     (c = document.createElement("SCRIPT")).src = v;
     c.type = "module";
     document.body.appendChild(c);
  });

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接