美文网首页Web前端之路让前端飞
简单的在线代码编辑(防取出时Js污染全局)

简单的在线代码编辑(防取出时Js污染全局)

作者: 虚拟J | 来源:发表于2021-02-05 20:31 被阅读0次

    样子是和 codepen 很像没错,因为我就是参考这里的样式。

    前景提要

    react-monaco-editorMonaco Editor 的 react 版本,而 Monaco Editor 就是为我们 VS Code 提供代码编辑器的支持。

    要使用代码高亮,自动补全,验证功能,需要再安装个 monaco-editor-webpack-plugin 插件。

    我是用 umi 开发,所以是要在.umirc.ts

    import { defineConfig } from 'umi';
    import Monacoplugin from 'monaco-editor-webpack-plugin';
    export default defineConfig({
      chainWebpack(config, { webpack }) {
        config.plugin('Monacoplugin').use(Monacoplugin);
      },
    });
    

    具体的配置项 options ,参考 Monaco 接口 IEditorConstructionOptions
    具体支持的编程语言可以去这里,Language 旁边的选择框里查看

    初次

    初次写的时候,本想简单粗暴,通过 dangerouslySetInnerHTML 直接生成。

    <style dangerouslySetInnerHTML={{ __html:Css }}></style> 
    <script type="text/javascript" dangerouslySetInnerHTML={{ __html:Js }}></script>
    <div dangerouslySetInnerHTML={{ __html:Html }}></div> 
    

    这样子,html 和 css 是没问题,但 js 是不生效的。(应该和安全机制有关系)
    要怎么做呢?用最原始的方式。

    let script = document.createElement('script');
    script.type = 'text/javascript';
    script.appendChild(document.createTextNode("function chestnut() {alert('hi');}"),);
    document.body.appendChild(script);
    

    但这样子,会污染到全局变量,所以最后还是要用 iframe 来隔离一下。

    iframe 版

    想要获取到 iframe 内部的 DOM,可以使用 contentWindow 去访问。

    html 和 css 我是让它实时更新,js 做了下防抖。
    更新 js 代码里面用了 eval 去执行 js 代码,用 try catch 去捕获错误,以保证 js 是正确可执行的代码。
    然后每一次 js 更改,都会刷新 iframe,为了清空 iframe 内之前的变量和函数。

    取出时的命名冲突解决

    代码存了之后,有需要取出来的需求。但 css 和 js 会有命名冲突的可能。

    • css 的隔离是通过给 html 一个 id ,然后通过下面的正则去修改下 css 的字符串的
    let bubbleCss = obj.bubbleCss.replace(
         /(?<!\w)\./g,
         `#bubble${obj.id} .`,
       );
    
    • js 的隔离,蛮花费我心思的。我是通过把 js 的作用域放到一个函数内来减少污染全局的。然后修改 html 事件上调用的方法(下面的代码是特地写成 var 的)。
    function scope(someJs: string) {
      try {
        return new Function(
          "func",
          "event",
          `${someJs}
           eval(func)
         `
        );
      } catch (e) {
        console.error(someJs);
      }
    }
    
    //隔离js到一个函数内
    let sandbox = scope(obj.bubbleJs);
    let script = document.createElement("script");
    script.type = "text/javascript";
    script.id = "script";
    script.appendChild(document.createTextNode(`var bubble${obj.id}=${sandbox}`));
    document.body.appendChild(script);
    document.body.removeChild(document.getElementById("script")!);
    
    // 更改div中的调用事件方法名
    let cunHtml = obj.bubbleHtml.replace(/'/g, '"');
    let div = document.createElement("div");
    div.innerHTML = obj.bubbleHtml;
    
    changeOnNmae(div.childNodes);
    
    function changeOnNmae(childNodes) {
      for (let node of childNodes) {
        for (let attr of node.attributes) {
          if (attr.nodeName.includes("on")) {
            cunHtml = cunHtml.replace(
              `${attr.nodeValue}`,
              `bubble${obj.id}('${attr.nodeValue}')`
            );
          }
        }
        if (node.childNodes > 0) {
          changeOnNmae(node.childNodes);
        }
      }
    }
    obj.bubbleHtml = cunHtml;
    

    https://cloud.tencent.com/developer/article/1519556 这篇文章给的灵感,让我这么隔离。

    在线编辑主要代码

    代码进行了一些删除,只留下了关键的代码。

    const Chestnut: React.FC = (props) => {
      const [bubbleHtml, setBubbleHtml] = useState('<div class="bubbleBox"></div>');
      const [bubbleCss, setBubbleCss] = useState(
        `.bubbleBox{
          background: radial-gradient(blue, transparent);
          } `
      );
      const [bubbleJs, setBubbleJs] = useState(``);
      const timerJs = useRef<null | NodeJS.Timeout>(null);
    
      function init(html: string, css: string, js: string) {
        updateHtml(html);
        updateCss(css);
        updateJs(js);
      }
      // 更新iframe内的html
      function updateHtml(newValue: string) {
        let iframe = document.getElementsByTagName("iframe")[0].contentWindow!;
        iframe.document.body.innerHTML = newValue;
      }
      // 更新iframe内的css
      function updateCss(newValue: string) {
        let iframe = document.getElementsByTagName("iframe")[0].contentWindow!;
        iframe.document.head.innerHTML = `<style>
            body{
              display: flex;
              justify-content: center;
              align-items: center;
            }
            body >div{ min-width: 150px;
            min-height: 32px;
          }
      ${newValue}</style>`;
      }
      // 更新iframe内的Js
      function updateJs(newValue: string) {
        try {
          eval(newValue); //判断是否是可执行的代码
          let iframe = document.getElementsByTagName("iframe")[0].contentWindow!;
          let script = document.createElement("script");
          script.type = "text/javascript";
          script.appendChild(document.createTextNode(newValue));
          iframe.document.body.appendChild(script);
          iframe.document.body.removeChild(
            iframe.document.body.getElementsByTagName("script")[0]
          );
        } catch (e) {
          console.log(newValue);
        }
      }
    
      return (
        <div>
          <MonacoEditor
            width="100%"
            height="calc( 100% - 30px)"
            language="html"
            theme="vs-dark"
            value={bubbleHtml}
            options={{
              automaticLayout: true,
              formatOnType: true,
            }}
            onChange={(newValue) => {
              setBubbleHtml(newValue);
              updateHtml(newValue);
            }}
          />
    
          <MonacoEditor
            width="100%"
            height="calc( 100% - 30px)"
            language="css"
            theme="vs-dark"
            value={bubbleCss}
            options={{
              automaticLayout: true,
              formatOnType: true,
            }}
            onChange={(newValue) => {
              setBubbleCss(newValue);
              updateCss(newValue);
            }}
          />
          <MonacoEditor
            width="100%"
            height="calc( 100% - 30px)"
            language="javascript"
            theme="vs-dark"
            value={bubbleJs}
            options={{
              automaticLayout: true,
              formatOnType: true,
            }}
            onChange={(newValue) => {
              setBubbleJs(newValue);
              if (timerJs.current) {
                clearTimeout(timerJs.current);
              }
              //刷新iframe,重置全局执行环境Windows,清空之前推入的变量和函数
              document
                .getElementsByTagName("iframe")[0]
                .contentWindow!.location.reload(true);
    
              timerJs.current = setTimeout(() => {
                init(bubbleHtml, bubbleCss, newValue);
              }, 1000);
            }}
          />
          <iframe style={{ width: "100%", height: "100%", border: 0 }}></iframe>
        </div>
      );
    };
    
    

    相关文章

      网友评论

        本文标题:简单的在线代码编辑(防取出时Js污染全局)

        本文链接:https://www.haomeiwen.com/subject/gxystltx.html