PageSourceSearch

https://babel.dev/js/components/mini-repl.js

js babel.dev collected 2026-10-02 17:41:16 UTC 6,270 bytes, 265 lines download raw bytes

1/* globals Babel */
2
3import { basicSetup } from "https://esm.sh/[email protected]";
4import { EditorState } from "https://esm.sh/@codemirror/state@^6.0.0";
5import { EditorView } from "https://esm.sh/@codemirror/view@^6.0.0";
6import { oneDark } from "https://esm.sh/@codemirror/theme-one-dark@^6.0.0";
7import { javascriptLanguage } from "https://esm.sh/@codemirror/lang-javascript@^6.0.0";
8import debounce from "https://esm.sh/lodash.debounce";
9
10const template = document.createElement("template");
11template.innerHTML = `
12  <div>
13    <div class="repl">
14      <h3 class="input-title">Input code</h3>
15      <h3 class="output-title">Output code</h3>
16      <div id="repl-in" class="repl__code input-editor"></div>
17      <div class="output-editor-container">
18        <div id="repl-out" class="repl__code"></div>
19        <div id="error" class="output-error"></div>
20      </div>
21    </div>
22
23    <style>
24      .repl {
25        color: white;
26
27        display: grid;
28        grid-template:
29          "input-title"
30          "input-editor"
31          "output-title"
32          "output-editor";
33
34        background: #353634;
35      }
36
37      @media (min-width: 992px) {
38        .repl:not(.vertical) {
39          grid-template:
40            "input-title output-title"
41            "input-editor output-editor"
42            / 50% 50%;
43        }
44      }
45
46      .input-title { grid-area: input-title }
47      .output-title { grid-area: output-title }
48      .input-editor { grid-area: input-editor }
49      .output-editor-container { grid-area: output-editor }
50
51      @media (min-width: 992px) {
52        .input-title, .input-editor {
53          border-right: 1px solid #4f504d;
54        }
55      }
56
57      .input-editor, .output-editor-container {
58        overflow-x: auto;
59      }
60
61      .output-editor-container {
62        position: relative;
63      }
64
65      .output-error {
66        position: absolute;
67        top: 0;
68        bottom: 0;
69        right: 0;
70        left: 0;
71
72        background: #702141;
73        font-family: monospace;
74        font-size: 0.83rem;
75        opacity: 0;
76        overflow-y: auto;
77        padding: 16px 24px;
78        text-align: left;
79        transition: opacity 0.25s ease-out;
80        white-space: pre;
81        display: none;
82      }
83
84      .output-error--visible {
85        opacity: 1;
86        display: block;
87      }
88
89      .repl__pane {
90        background: #353634;
91        border: 1px solid #4f504d;
92        position: relative;
93      }
94
95      @media (min-width: 992px) {
96        .repl__pane {
97          width: 50%;
98        }
99      }
100
101      .repl__code {
102        background: rgb(40, 44, 52);
103        font-family: monospace;
104        font-size: 0.83rem;
105        min-height: 125px;
106        height: 100%;
107        text-align: left;
108      }
109      @media (min-width: 992px) {
110        .repl__code {
111          font-size: 1rem;
112          min-height: 200px;
113        }
114
115        .vertical .repl__code {
116          min-height: 150px;
117        }
118      }
119
120      .repl h3 {
121        color: #b7b8b7;
122        font-size: 0.875rem;
123        margin: 0;
124        padding: 12px 24px;
125        text-align: center;
126      }
127      @media (min-width: 992px) {
128        .repl h3 {
129          font-size: 1rem;
130          text-align: left;
131        }
132      }
133
134      .repl__code .cm-wrap {
135        height: 100%;
136      }
137    </style>
138  </div>
139`;
140
141class MiniRepl extends HTMLElement {
142  _inEditor;
143  _outEditor;
144
145  _debouncedShowError = debounce(this._showError.bind(this), 1000);
146
147  constructor() {
148    super();
149
150    this.attachShadow({ mode: "open" });
151    this.shadowRoot.appendChild(template.content.cloneNode(true));
152  }
153
154  connectedCallback() {
155    this._defaultCode = this.getAttribute("default-code");
156    this._vertical = this.hasAttribute("vertical");
157    this._options = JSON.parse(this.getAttribute("options")) ?? {};
158
159    if (this._vertical) {
160      this.shadowRoot.querySelector(".repl").classList.add("vertical");
161    }
162
163    const defaultCode = this.getAttribute("default-code") ?? "";
164
165    this._inEditor = new EditorView({
166      state: this._createInputEditorState(defaultCode),
167      parent: this.shadowRoot.querySelector("#repl-in"),
168      root: this.shadowRoot,
169      dispatch: (tr) => {
170        this._inEditor.update([tr]);
171        this._render();
172      },
173    });
174    this._outEditor = new EditorView({
175      state: this._createOutputEditorState(""),
176      parent: this.shadowRoot.querySelector("#repl-out"),
177      root: this.shadowRoot,
178    });
179
180    this.shadowRoot.querySelector("#repl-in").addEventListener("click", () => {
181      this._inEditor.focus();
182    });
183  }
184
185  setInput(code) {
186    this._inEditor.setState(this._createInputEditorState(code));
187    this._render();
188  }
189
190  get options() {
191    return this._options;
192  }
193  set options(options) {
194    this._options = options;
195    this._render();
196  }
197
198  _render() {
199    const inputCode = this._inEditor.state.doc.toString();
200
201    if (!inputCode) {
202      this._outEditor.setState(this._createOutputEditorState(""));
203      this._hideError();
204      return;
205    }
206
207    try {
208      const compiled = this._compile(inputCode);
209      this._outEditor.setState(this._createOutputEditorState(compiled));
210      this._hideError();
211    } catch (err) {
212      this._debouncedShowError(err.message);
213    }
214  }
215
216  _compile(code) {
217    return Babel.transform(code, this._options).code;
218  }
219
220  _createInputEditorState(content) {
221    return EditorState.create({
222      doc: content,
223      extensions: [
224        basicSetup,
225        oneDark,
226        javascriptLanguage,
227        //EditorView.lineWrapping,
228      ],
229    });
230  }
231
232  _createOutputEditorState(content) {
233    return EditorState.create({
234      doc: content,
235      extensions: [
236        basicSetup,
237        oneDark,
238        javascriptLanguage,
239        //EditorView.lineWrapping,
240        EditorView.editable.of(false),
241      ],
242    });
243  }
244
245  _showError(babelError) {
246    const err = this.shadowRoot.getElementById("error");
247    err.textContent = babelError;
248    err.classList.add("output-error--visible");
249  }
250
251  _hideError() {
252    this._debouncedShowError.cancel();
253    this.shadowRoot
254      .getElementById("error")
255      .classList.remove("output-error--visible");
256  }
257}
258
259customElements.define("mini-repl", MiniRepl);
260
261/*
262<h2>noDocumentAll</h2>
263
264<div class="repl" data-original-input="foo?.bar"></div>
265*/

Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.