搭建一个简易的Playground
前言
看一个动效的效果时,看到源码用到了ace实现了代码实时效果。(案例是直接使用eval执行代码,但是看了一下codepen的html结构,所以自己利用ace+iframe来搞一个简单的Playground玩玩。
“玩后感”:感觉ace这个库使用起来还挺不方便的(比如设置主题什么的,参数是ace/theme/twilight这种形式,还得另外import),实际使用可能还应该调研一下其他的editor工具。
1 2 3 4
   | import 'ace-builds/src-noconflict/theme-twilight';
 
  editor.setTheme('ace/theme/twilight');
 
  | 
 
核心
准备一个iframe用的html,然后对这个html的内容进行处理,比如<script></script>替换成<script>${content}</script>。处理结果会是字符串,所以还需要使用URL.createObjectURL转换成URL。
1 2 3
   | URL.createObjectURL(   new Blob([result], { type: "text/html" }) )
 
  | 
 
实现
用到ace的部分比较简单:
ace.edit():参数为dom元素,把dom元素变成一个编辑器,返回editor对象 
editor.setTheme():设置主题 
editor.session.setValue(): 设置编辑器初始值 
editor.getValue():获取编辑器当前值 
代码:
App.tsx
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67
   | import { useCallback, useEffect, useRef, useState } from "react"; import ace, { Ace } from "ace-builds"; import confettiRaw from "./assets/confetti.browser.js?raw";    import iframeRaw from "./assets/iframe.html?raw"; import 'ace-builds/src-noconflict/theme-twilight';
  import "./App.css";
  function App() {   const editorRef = useRef<HTMLDivElement>(null);   const [editor, setEditor] = useState<Ace.Editor>();
    const handleInit = useCallback(() => {     const defaultCode = `       confetti({         particleCount: 100,           spread: 70,           origin: { y: 1 },       });     `;
      if (editorRef.current) {       const newEditor = ace.edit(editorRef.current);       newEditor.setTheme('ace/theme/twilight');       newEditor.session.setValue(defaultCode);
        setEditor(newEditor);     }   }, []);
    useEffect(() => {     handleInit();   }, [handleInit]);
    const importScriptSrc = URL.createObjectURL(     new Blob([confettiRaw], { type: "text/javascript" })   );   const getIframeUrl = () => {     const res = iframeRaw     .replace(       '<script id="import-script"></script>',       `<script id="import-script" src="${importScriptSrc}"></script>`     )     .replace("<script></script>", `<script>${editor?.getValue()}</script>`);
    return URL.createObjectURL(     new Blob([res], { type: "text/html" })   ); }
    const [iframeUrl, setIframeUrl] = useState<string>(getIframeUrl());   const handleClick = () => {     setIframeUrl(getIframeUrl());   };
    return (     <>       <button onClick={handleClick}>fire</button>       <div className="box">         <div ref={editorRef} id="editor"></div>         <iframe src={iframeUrl}></iframe>       </div>     </>   ); }
  export default App;
 
  | 
 
iframe.html
1 2 3 4 5 6 7 8 9 10 11 12
   | <!DOCTYPE html> <html lang="en">   <head>     <meta charset="UTF-8" />     <meta name="viewport" content="width=device-width, initial-scale=1.0" />     <title>Iframe</title>   </head>   <body>     <script id="import-script"></script>     <script></script>   </body> </html>
 
  | 
 
第三方javascript: confetti.browser.js可以替换成自己需要的第三方资源
效果

添加js-beautify美化代码
初始化编辑器的代码时,传入的字符串格式可能会有问题。

比如上面的例子,初始化的效果就是这样。所以可以setValue之前用js-beautify做一下美化。
1 2 3 4 5 6 7 8
   | import beautify from 'js-beautify';
  const pretty = (code: string) => {   return beautify.js(code); }
 
  newEditor.session.setValue(pretty(defaultCode));
 
  | 
 

使用monaco-editor
monaco-editor是VSCode的代码编辑器。monaco-editor也是功能很强大,还有代码提示功能,写法也是组件写法。
1 2 3 4 5 6 7 8 9 10 11
   | import Editor from "@monaco-editor/react"; import "./App.css";
  function App() {
    return (     <Editor height="60vh" defaultLanguage="javascript" defaultValue="console.log('Hello clz!!!')" theme="vs-dark" />   ); }
  export default App;
 
  | 
 

