preface
Over time, there are more and more IDE applications online, and they become more and more functional. Such as
- codesandbox: codesandbox.io
- Jsbin: jsbin.com/?html outpu… (Personally, this is very light)
- codepen: codepen.io/pen/
- jsfiddle: jsfiddle.net/
- Rookie tool: c.runoob.com/
- .
As a result, I was curious about the implementation principle of these online ides and wanted to try to implement a similar function myself. Here’s an article detailing how CodesandBox works: portals
We tried to implement a feature where you type code on the left and preview it on the right.
The file structure is as follows
Post the code content separately
(1) package. The json
{
"name": "react"."version": "1.0.0"."description": "React example starter project"."keywords": [
"react"."starter"]."main": "src/index.js"."dependencies": {
"@babel/runtime": "7.13.8"."@babel/standalone": "7.13.11"."acorn": "8.1.0"."escodegen": "2.0.0"."lodash": "4.17.21"."object-path": "0.11.5"."react": "17.0.1"."react-dom": "17.0.1"."react-monaco-editor": "0.43.0"."react-scripts": "4.0.0"
},
"devDependencies": {
"typescript": 4.1.3 ""
},
"scripts": {
"start": "react-scripts start"."build": "react-scripts build"."test": "react-scripts test --env=jsdom"."eject": "react-scripts eject"
},
"browserslist": [
"0.2%" >."not dead"."not ie <= 11"."not op_mini all"]}Copy the code
(2) index. Js
import { StrictMode } from "react";
import ReactDOM from "react-dom";
import App from "./App";
const rootElement = document.getElementById("root");
ReactDOM.render(
<StrictMode>
<App />
</StrictMode>,
rootElement
);
Copy the code
(3) App. JSX
import "./styles.css";
import SandBox from './SandBox';
export default function App() {
return (
<div className="App">
<SandBox />
</div>
);
}
Copy the code
(4) the SandBox. The JSX
import React, { useState, useEffect, useRef } from "react";
import _debounce from "lodash/debounce";
import { createEditor } from "./util";
// import MonacoEditor from "react-monaco-editor";
function SandBox() {
const viewRef = useRef(null);
const runtimeRef = useRef(null);
const [code, setCode] = useState(` function HolyCow() { return HolyCow, My God! }
`);
useEffect(() = >{ runtimeRef.current = createEditor(viewRef.current); runtimeRef.current.run(code); } []);const run = _debounce((newCode) = > {
runtimeRef.current.run(newCode || code);
}, 500);
const onCodeChange = ({ target: { value } }) = > {
setCode(value);
run(value);
};
return (
<div className="container" style={{ display: "flex}} ">
<div className="code-editor" style={{ flex: 1}} >
<textarea value={code} onChange={onCodeChange} />
</div>
<div style={{ flex: 1}} >
<div className="preview" ref={viewRef} />
</div>
</div>
);
}
export default SandBox;
Copy the code
(5) the util. Js
import React from "react";
import ReactDOM from "react-dom";
import ObjPath from "object-path";
import { parse } from "acorn";
import { generate as generateJs } from "escodegen";
import { transform as babelTransform } from "@babel/standalone";
// Search for the target node
export function findReactNode(ast) {
// the ast standard structure body
const { body } = ast;
// Define an iterator
return body.find((node) = > {
// React. CreateElement
const { type } = node;
// This ObjPath is similar to lodash's get
const obj = ObjPath.get(node, "expression.callee.object.name");
const func = ObjPath.get(node, "expression.callee.property.name");
return (
type === "ExpressionStatement" &&
obj === "React" &&
func === "createElement"
);
});
}
// Create method dynamically
export function createEditor(domElement, moduleResolver = () => null) {
// The run-time input parameter is used by the method
function render(node) {
ReactDOM.render(node, domElement);
}
/ / same as above
function require(moduleName) {
return moduleResolver(moduleName);
}
/ / core
function getWrapperFunction(code) {
try {
// 1. React&ES6 code
const esCode = babelTransform(code, { presets: ["es2015"."react"] })
.code;
ToAst / / 2. The native code (here playing at acorn, Babel, eslint meet the ESTree Spec standard, portal: https://github.com/estree/estree).
const ast = parse(esCode, {
sourceType: "module"
});
// 3. Our purpose is to put JSX => js and run React. CreateElement
// So you have to go to the part where JSX is installed first
const rnode = findReactNode(ast);
// 4. If you find a run statement, you must wrap the render method outside of React. CreateElemnet to run it
if (rnode) {
// Find the location first, so that you can replace it directly later
const nodeIndex = ast.body.indexOf(rnode);
// Generate a string and cut out useless information
const createElSrc = generateJs(rnode).slice(0, -1);
// Rebuild the modified AST - statements that can be executed
const renderCallAst = parse(`render(${createElSrc}) `).body[0];
ast.body[nodeIndex] = renderCallAst;
}
// Eval is not efficient, but it is not safe
// There are many runtime methods, especially in the Node side VM library - runInThisContext etc
// The first three input parameters are followed by the function body
return new Function("React"."render"."require", generateJs(ast));
} catch ({ message }) {
/ / out
render(<pre style={{ color: "red}} ">{message}</pre>); }}// The front core can't be exposed
return {
// View the compilation result
compile(code) {
return getWrapperFunction(code);
},
// Run directly
run(code) {
this.compile(code)(React, render, require);
},
// View the generated string
getCompiledCode(code) {
returngetWrapperFunction(code).toString(); }}; }Copy the code
(6) styles. CSS
.App {
font-family: sans-serif;
text-align: center;
}
.container {
width: 100vw;
height: 100vh;
}
.code-editor {
width: 50%;
}
.code-editor textarea {
padding: 1em;
width: 100%;
height: 100%;
border: solid 1px # 000;
min-height: 30em;
outline: none;
font-family: "Monaco";
font-size: 14px;
background: # 333;
color: #74b9ff;
}
Copy the code
Source code portal
We see the AST as a link between the preceding and the following, so far we have just implemented a simple edit preview of a single file, what do we do with multiple modules? The AST still needs to look it up, iterate over imported modules, and assemble all dependent modules for execution.