简体   繁体   English

如何在 React SPA 中实现添加自定义 js 代码片段功能?

[英]How to implement adding custom js code snippet feature in React SPA?

I am building a white label web application.我正在构建一个白色的 label web 应用程序。 I need to implement adding custom js code snippet feature ("tags" in marketing terms) to let marketers add tracking code like google analytics code and heap code.我需要实现添加自定义 js 代码片段功能(营销术语中的“标签”),以让营销人员添加跟踪代码,如谷歌分析代码和堆代码。 In the admin app, admins can add custom js code snippet like this.在管理应用程序中,管理员可以像这样添加自定义 js 代码片段。

<!-- Global site tag (gtag.js) - Google Analytics -->
<script async src="https://www.googletagmanager.com/gtag/js?id=UA-160375581-1"></script>
<script>
  window.dataLayer = window.dataLayer || [];
  function gtag(){dataLayer.push(arguments);}
  gtag('js', new Date());

  gtag('config', 'UA-160375581-1');
</script>

As you can see, I need to support both <script src/> and <script>code</script> .如您所见,我需要同时支持<script src/><script>code</script>

In the web app (Create-React-App SPA), I need to add this code snippet in the document.在 web 应用程序(Create-React-App SPA)中,我需要在文档中添加此代码段。 I have no idea how to implement this feature.我不知道如何实现这个功能。 I tried to inject this code snippet using React dangersoulySetInnerHTML feature.我尝试使用 React dangersoulySetInnerHTML 功能注入此代码片段。 But innerHTML doesn't allow <script> .但是 innerHTML 不允许<script>

Please look carefully at the code snippet sample, it also has to support external script specified with src attribute.请仔细查看代码片段示例,它还必须支持使用src属性指定的外部脚本。

This screenshot will help you understand what feature I want to implement.此屏幕截图将帮助您了解我要实现的功能。

instapage 构建器的屏幕截图

I implemented the adding custom script feature like this.我实现了这样的添加自定义脚本功能。 Please review the code.请查看代码。 I hope you suggest another good solution.我希望你提出另一个好的解决方案。 The reason I make the cloneScript function is to make HTML script tag that doesn't have "already started" status.我制作 cloneScript function 的原因是制作没有“已经开始”状态的 HTML 脚本标签。 If you don't make sense, please check this doc.如果你不明白,请检查这个文档。 https://html.spec.whatwg.org/multipage/scripting.html#script-processing-model https://html.spec.whatwg.org/multipage/scripting.html#script-processing-model

 const cloneScript = (script) => { const s = document.createElement('script'); // copy attributes const attrs = script.attributes; for (let i = 0; i < attrs.length; i++) { s.setAttribute(attrs[i].name, attrs[i].value); } // copy inner text of script s.text = script.text; return s; }; const addCustomScripts = (custom_tag) => { try { const parser = new DOMParser(); const doc = parser.parseFromString(custom_tag, 'text/html'); const collection = doc.head.children; Array.from(collection).map((script) => cloneScript(script)).forEach((script) => document.body.appendChild(script)); } catch (e) { console.error(e); } }; const str = ` <.-- Global site tag (gtag:js) - Google Analytics --> <script async src="https.//www.googletagmanager?com/gtag/js.id=UA-160375581-1"></script> <script id="stella" data-state="inactive"> window.dataLayer = window;dataLayer || []. function gtag(){dataLayer;push(arguments),} gtag('js'; new Date()), gtag('config'; 'UA-160375581-1'); </script>` addCustomScripts(str);

It depends on what kind of tracking you're gonna use.这取决于您要使用哪种跟踪方式。

  1. you can directly edit the html in public/index.html folder您可以直接在 public/index.html 文件夹中编辑 html

  2. Google Tag Manager has a react package you can use谷歌标签管理器有一个反应 package你可以使用

  3. Or you could transform the script into js yourself或者你可以自己把脚本转成js

    for example例如

    const hm = document.createElement("script"); hm.src = "https://example.com/example.js"; const s = document.getElementsByTagName("script")[0]; s.parentNode.insertBefore(hm, s);

    look into the source code of react-gtm for a more detailed and polished approach.查看react-gtm的源代码以获得更详细和完善的方法。

  4. Or you could use React Helmet if you don't want to deal with all this, just copy paste and be done with it.或者,如果您不想处理所有这些,您可以使用React Helmet ,只需复制粘贴即可。

If you cannot render them on the server side, you can do them on the client side but it could be dangerous if the script has malicious content:如果您无法在服务器端渲染它们,您可以在客户端执行它们,但如果脚本包含恶意内容可能会很危险

Here is a code sample of how you can do it: https://codesandbox.io/s/2rwqy2m0r以下是如何执行此操作的代码示例: https://codesandbox.io/s/2rwqy2m0r

import React, { Component, useState, isValidElement }  from "react";
import ReactDOM from "react-dom";

const HtmlComponent = ({ html }) => (
  isValidElement(html) ? html : <div dangerouslySetInnerHTML={{ __html: html }} />
)

class ScriptComponent extends Component {
  componentDidMount() {
    eval(this.props.children);
  }

  render() {
    return null;
  }
}

const htmlTexts = {
  'With h1': {
    description: 'Normal visual HTML elements are rendered corretly',
    html: '<h1>Test</h1>',
  },
  'With script': {
    description: '<script /> tags in HTML are never executed on client side',
    html: '<script>alert(\'test\');</script>',
  },
  'With style': {
    description: '<style /> gets interputed by DOM, and updates element styles',
    html: '<style>body {background: red;}</style>',
  },
  'With script image': {
    description: 'element event actions are triggered so XSS is still possible, even without <scripts /> being evaluated',
    html: '<img src="foo" onerror="(() => alert(\'foo\'))()" />',
  },
  'With button': {
    description: 'element event actions are triggered so XSS is still possible, even without <scripts /> being evaluated',
    html: '<button onclick="(() => alert(\'foo\'))()">Click me</button>',
  },
  'As script element': {
    description: 'Event reacts own script tag doesn\'t evaluate its content',
    html: <script>alert('foo')</script>,
  },
  'As script component': {
    description: 'To run scripts on tag load, a component has to activly execute it',
    html: <ScriptComponent>alert('foo')</ScriptComponent>,
  }
}

function App() {
  const [{ html, description }, setHtml] = useState({});
  return (
    <div className="App">
      <h1>dangerouslySetInnerHTML with script</h1>
      {Object.keys(htmlTexts).map((name, i) => (
        <button
          style={{
            outline: 'none',
            border: 'solid 1px #ddd',
            margin: '10px 10px 30px 0',
            borderRadius: '8px',
            padding: '10px',
            boxShadow: html === htmlTexts[name].html ? '0 0 15px rgba(0,0,0,.3)' : 'none'
          }}
          onClick={() => setHtml(htmlTexts[name])}
        >
          {name}
        </button>
      ))}
      <div>
        {description}
      </div>
      <pre
        style={{
          background: '#222',
          color: '#fff',
          padding: '10px',
        }}
      >
        {isValidElement(html) ? (
          'React Component\n\n' +
          `type: ${typeof html.type === 'string' ? html.type : html.type.name}\n\n` +
          `props: ${JSON.stringify(html.props, null, '  ')}`
        ) : (
          `html:\n\n${html}`
        )}
      </pre>
      <HtmlComponent html={html} />
    </div>
  );
}

const rootElement = document.getElementById("root");
ReactDOM.render(<App />, rootElement);

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM