Construyendo un editor de código web

Publicado: 2022-03-10
Resumen rápido ↬ Si eres un desarrollador que está pensando en crear una plataforma que requiera un editor de código de una forma u otra, entonces este artículo es para ti. Este artículo explica cómo crear un editor de código web que muestre el resultado en tiempo real con la ayuda de HTML, CSS y JavaScript.

Un editor de código web en línea es más útil cuando no tiene la oportunidad de usar una aplicación de edición de código, o cuando desea probar rápidamente algo en la web con su computadora o incluso su teléfono móvil. Este también es un proyecto interesante para trabajar porque tener el conocimiento de cómo construir un editor de código le dará ideas sobre cómo abordar otros proyectos que requieren que integre un editor de código para mostrar alguna funcionalidad.

Aquí hay algunos conceptos de React que necesitará saber para seguir este artículo:

  • Manos,
  • estructura de componentes,
  • componentes funcionales,
  • Accesorios.

Uso de CodeMirror

Usaremos una biblioteca llamada CodeMirror para construir nuestro editor. CodeMirror es un editor de texto versátil implementado en JavaScript para el navegador. Es especialmente para editar código y viene con varios modos de idioma y complementos para una funcionalidad de edición más avanzada.

Una API de programación enriquecida y un sistema de temas CSS están disponibles para personalizar CodeMirror para que se ajuste a su aplicación y ampliarla con nuevas funciones. Nos brinda la funcionalidad para crear un editor de código enriquecido que se ejecuta en la web y nos muestra el resultado de nuestro código en tiempo real.

En la siguiente sección, configuraremos nuestro nuevo proyecto React e instalaremos las bibliotecas que necesitamos para construir nuestra aplicación web.

Creación de un nuevo proyecto React

Comencemos creando un nuevo proyecto React. En su interfaz de línea de comandos, navegue hasta el directorio en el que desea crear su proyecto, y creemos una aplicación React y asígnele el nombre code_editor :

 npx create-react-app code_editor

Habiendo creado nuestra nueva aplicación React, naveguemos al directorio de ese proyecto en la interfaz de línea de comandos:

 cd code_editor

Hay dos bibliotecas que necesitamos instalar aquí: codemirror y react-codemirror2 .

 npm install codemirror react-codemirror2

Habiendo instalado las bibliotecas que necesitamos para este proyecto, creemos nuestras pestañas y habilitemos el cambio de pestañas entre las tres pestañas que aparecerán en nuestro editor (para HTML, CSS y JavaScript).

¡Más después del salto! Continúe leyendo a continuación ↓

Componente de botón

En lugar de crear botones individuales, hagamos que el botón sea un componente reutilizable. En nuestro proyecto, el botón tendría tres instancias, según las tres pestañas que necesitemos.

Cree una carpeta llamada components en la carpeta src . En esta nueva carpeta de components , cree un archivo JSX llamado Button.jsx .

Aquí está todo el código necesario en el componente Button :

 import React from 'react' const Button = ({title, onClick}) => { return ( <div> <button style={{ maxWidth: "140px", minWidth: "80px", height: "30px", marginRight: "5px" }} onClick={onClick} > {title} </button> </div> ) } export default Button

Aquí hay una explicación completa de lo que hicimos anteriormente:

  • Creamos un componente funcional llamado Button , que luego exportamos.
  • Desestructuramos title y onClick de los accesorios que ingresan al componente. Aquí, el title sería una cadena de texto y onClick sería una función que se llama cuando se hace clic en un botón.
  • A continuación, usamos el elemento de button para declarar nuestro botón y usamos los atributos de style para darle estilo a nuestro botón para que se vea presentable.
  • Agregamos el atributo onClick y le pasamos nuestros accesorios de función onClick desestructurados.
  • Lo último que notará que hicimos en este componente fue pasar {title} como el contenido de la etiqueta del button . Esto nos permite mostrar el título de forma dinámica, en función de la propiedad que se pasa a la instancia del componente de botón cuando se llama.

Ahora que hemos creado un componente de botón reutilizable, avancemos y traigamos nuestro componente a App.js. Vaya a App.js e importe el componente de botón recién creado:

 import Button from './components/Button';

Para rastrear qué pestaña o editor está abierto, necesitamos un estado de declaración para mantener el valor del editor que está abierto. Usando el useState React, configuraremos el estado que almacenará el nombre de la pestaña del editor que está actualmente abierta cuando se hace clic en el botón de esa pestaña.

Así es como lo hacemos:

 import React, { useState } from 'react'; import './App.css'; import Button from './components/Button'; function App() { const [openedEditor, setOpenedEditor] = useState('html'); return ( <div className="App"> </div> ); } export default App;

Aquí, declaramos nuestro estado. Toma el nombre del editor que está abierto actualmente. Debido a que el valor html se pasa como el valor predeterminado del estado, el editor HTML sería la pestaña abierta de forma predeterminada.

Avancemos y escribamos la función que usará setOpenedEditor para cambiar el valor del estado cuando se haga clic en un botón de pestaña.

Nota: es posible que dos pestañas no estén abiertas al mismo tiempo, por lo que tendremos que tenerlo en cuenta al escribir nuestra función.

Así es como se ve nuestra función, llamada onTabClick :

 import React, { useState } from 'react'; import './App.css'; import Button from './components/Button'; function App() { ... const onTabClick = (editorName) => { setOpenedEditor(editorName); }; return ( <div className="App"> </div> ); } export default App;

Aquí, pasamos un único argumento de función, que es el nombre de la pestaña actualmente seleccionada. Este argumento se proporcionaría en cualquier lugar donde se llame a la función, y se pasaría el nombre relevante de esa pestaña.

Vamos a crear tres instancias de nuestro Button para las tres pestañas que necesitamos:

 <div className="App"> <p>Welcome to the editor!</p> <div className="tab-button-container"> <Button title="HTML" onClick={() => { onTabClick('html') }} /> <Button title="CSS" onClick={() => { onTabClick('css') }} /> <Button title="JavaScript" onClick={() => { onTabClick('js') }} /> </div> </div>

Aquí está lo que hicimos:

  • Comenzamos agregando una etiqueta p , básicamente solo para dar algo de contexto a lo que trata nuestra aplicación.
  • Usamos una etiqueta div para envolver nuestros botones de tabulación. La etiqueta div lleva un nombre de className que usaremos para diseñar los botones en una visualización de cuadrícula en el archivo CSS más adelante en este tutorial.
  • A continuación, declaramos tres instancias del componente Button . Si recuerda, el componente Button tiene dos accesorios, title y onClick . En cada instancia del componente Button , se proporcionan estos dos accesorios.
  • El accesorio de title toma el título de la pestaña.
  • El accesorio onClick toma una función, onTabClick , que acabamos de crear y que toma un solo argumento: el nombre de la pestaña seleccionada.

Según la pestaña actualmente seleccionada, usaríamos el operador ternario de JavaScript para mostrar la pestaña de forma condicional. Esto significa que si el valor del estado del openedEditor se establece en html (es decir setOpenedEditor('html') ), la pestaña de la sección HTML se convertirá en la pestaña actualmente visible. Lo entenderás mejor cuando lo hagamos a continuación:

 ... return ( <div className="App"> ... <div className="editor-container"> { openedEditor === 'html' ? ( <p>The html editor is open</p> ) : openedEditor === 'css' ? ( <p>The CSS editor is open!!!!!!</p> ) : ( <p>the JavaScript editor is open</p> ) } </div> </div> ); ...

Repasemos el código anterior en lenguaje sencillo. Si el valor de openedEditor es html , muestra la sección HTML. De lo contrario, si el valor de openedEditor es css , muestra la sección CSS. De lo contrario, si el valor no es ni html ni css , eso significa que el valor debe ser js , porque solo tenemos tres valores posibles para el estado del openedEditor ; Entonces, mostraríamos la pestaña de JavaScript.

Usamos etiquetas de párrafo ( p ) para las diferentes secciones en las condiciones del operador ternario. A medida que avancemos, crearemos los componentes del editor y reemplazaremos las etiquetas p con los propios componentes del editor.

¡Ya hemos llegado tan lejos! Cuando se hace clic en un botón, se activa la acción que establece la pestaña que representa en true , haciendo que esa pestaña sea visible. Así es como se ve nuestra aplicación actualmente:

Un GIF que muestra el cambio de pestaña que tenemos actualmente.
Un GIF que muestra el cambio de pestaña que tenemos actualmente. (Vista previa grande)

Agreguemos un poco de CSS al contenedor div que contiene los botones. Queremos que los botones se muestren en una cuadrícula, en lugar de estar apilados verticalmente como en la imagen de arriba. Vaya a su archivo App.css y agregue el siguiente código:

 .tab-button-container{ display: flex; }

Recuerde que agregamos className="tab-button-container" como un atributo en la etiqueta div que contiene los botones de tres pestañas. Aquí, le dimos estilo a ese contenedor, usando CSS para configurar su visualización en flex . Este es el resultado:

Usamos CSS para configurar su pantalla para que se flexione.
(Vista previa grande)

Siéntete orgulloso de todo lo que has hecho para llegar a este punto. En la siguiente sección, crearemos nuestros editores, reemplazando las etiquetas p con ellos.

Crear los editores

Debido a que ya instalamos las bibliotecas en las que vamos a trabajar dentro de nuestro editor de CodeMirror, avancemos y creemos nuestro archivo Editor.jsx en la carpeta de components .

componentes > Editor.jsx

Habiendo creado nuestro nuevo archivo, escribamos un código inicial en él:

 import React, { useState } from 'react'; import 'codemirror/lib/codemirror.css'; import { Controlled as ControlledEditorComponent } from 'react-codemirror2'; const Editor = ({ language, value, setEditorState }) => { return ( <div className="editor-container"> </div> ) } export default Editor

Esto es lo que hicimos:

  • Importamos React junto con el useState porque lo vamos a necesitar.
  • Importamos el archivo CSS de CodeMirror (que proviene de la biblioteca de CodeMirror que instalamos, por lo que no tiene que instalarlo de ninguna manera especial).
  • Importamos Controlled de react-codemirror2 y lo renombramos a ControlledEditorComponent para que quede más claro. Estaremos usando esto en breve.
  • Luego, declaramos nuestro componente funcional Editor y tenemos una declaración de retorno con un div vacío, con un nombre de className en la declaración de retorno por ahora.

En nuestro componente funcional, desestructuramos algunos valores de los accesorios, incluidos language , value y setEditorState . Estos tres accesorios se proporcionarían en cualquier instancia del editor cuando se llame en App.js

Usemos ControlledEditorComponent para escribir el código de nuestro editor. Esto es lo que haremos:

 import React, { useState } from 'react'; import 'codemirror/lib/codemirror.css'; import 'codemirror/mode/xml/xml'; import 'codemirror/mode/javascript/javascript'; import 'codemirror/mode/css/css'; import { Controlled as ControlledEditorComponent } from 'react-codemirror2'; const Editor = ({ language, value, setEditorState }) => { return ( <div className="editor-container"> <ControlledEditorComponent onBeforeChange={handleChange} value= {value} className="code-mirror-wrapper" options={{ lineWrapping: true, lint: true, mode: language, lineNumbers: true, }} /> </div> ) } export default Editor

Repasemos lo que hicimos aquí, explicando algunos términos de CodeMirror.

Los modos CodeMirror especifican para qué idioma está destinado un editor. Importamos tres modos porque tenemos tres editores para este proyecto:

  1. XML: este modo es para HTML. Utiliza el término XML.
  2. JavaScript: Este ( codemirror/mode/javascript/javascript ) trae el modo JavaScript.
  3. CSS: Esto ( codemirror/mode/css/css ) trae el modo CSS.

Nota: Debido a que el editor está construido como un componente reutilizable, no podemos poner un modo directo en el editor. Por lo tanto, proporcionamos el modo a través de la utilería del language que desestructuramos. Pero esto no cambia el hecho de que los modos deben importarse para que funcionen.

A continuación, analicemos las cosas en ControlledEditorComponent :

  • onBeforeChange
    Esto se llama cada vez que escribe o elimina del editor. Piense en esto como el controlador onChange que normalmente tendría en un campo de entrada para realizar un seguimiento de los cambios. Usando esto, podremos obtener el valor de nuestro editor cada vez que haya un nuevo cambio y guardarlo en el estado de nuestro editor. Escribiremos la función {handleChange} medida que avanzamos.
  • value = {value}
    Este es solo el contenido del editor en un momento dado. Pasamos un value con nombre de apoyo desestructurado a este atributo. Los apoyos de value es el estado que contiene el valor de ese editor. Esto se suministraría desde la instancia del editor.
  • className ="code-mirror-wrapper"
    Este nombre de clase no es un estilo que creamos nosotros mismos. Se proporciona desde el archivo CSS de CodeMirror, que importamos anteriormente.
  • options
    Este es un objeto que toma la funcionalidad diferente que queremos que tenga nuestro editor. Hay muchas opciones increíbles en CodeMirror. Veamos los que usamos aquí:
    • lineWrapping: true
      Esto significa que el código debería pasar a la siguiente línea cuando la línea esté llena.
    • lint: true
      Esto permite la pelusa.
    • mode: language
      Este modo, como se discutió anteriormente, toma el idioma para el que se utilizará el editor. El idioma ya se ha importado anteriormente, pero el editor aplicará un idioma basado en el valor de language proporcionado al editor a través de la propiedad.
    • lineNumbers: true
      Esto especifica que el editor debe tener números de línea para cada línea.

A continuación, podemos escribir la función handleChange para el controlador onBeforeChange :

 const handleChange = (editor, data, value) => { setEditorState(value); }

El controlador onBeforeChange nos da acceso a tres cosas: editor, data, value .

Solo necesitamos el value porque es lo que queremos pasar en nuestra propiedad setEditorState . La propiedad setEditorState representa el valor establecido para cada estado que declaramos en App.js y contiene el valor para cada editor. A medida que avancemos, veremos cómo pasar esto como accesorio al componente Editor .

A continuación, agregaremos un menú desplegable que nos permita seleccionar diferentes temas para el editor. Entonces, echemos un vistazo a los temas en CodeMirror.

Temas de CodeMirror

CodeMirror tiene varios temas entre los que podemos seleccionar. Visite el sitio web oficial para ver demostraciones de los diferentes temas disponibles. Hagamos un menú desplegable con diferentes temas que el usuario puede elegir en nuestro editor. Para este tutorial, agregaremos cinco temas, pero puede agregar tantos como desee.

Primero, importemos nuestros temas en el componente Editor.js :

 import 'codemirror/theme/dracula.css'; import 'codemirror/theme/material.css'; import 'codemirror/theme/mdn-like.css'; import 'codemirror/theme/the-matrix.css'; import 'codemirror/theme/night.css';

A continuación, cree una matriz de todos los temas que hemos importado:

 const themeArray = ['dracula', 'material', 'mdn-like', 'the-matrix', 'night']

useState un enlace useState para mantener el valor del tema seleccionado y establezcamos el tema predeterminado como dracula :

 const [theme, setTheme] = useState("dracula")

Vamos a crear el menú desplegable:

 ... return ( <div className="editor-container"> <div style={{marginBottom: "10px"}}> <label for="cars">Choose a theme: </label> <select name="theme" onChange={(el) => { setTheme(el.target.value) }}> { themeArray.map( theme => ( <option value={theme}>{theme}</option> )) } </select> </div> // the rest of the code comes below... </div> ) ...

En el código anterior, usamos la label HTML de la etiqueta para agregar una etiqueta a nuestro menú desplegable y luego agregamos la etiqueta HTML de select para crear nuestro menú desplegable. La etiqueta de option en el elemento select define las opciones disponibles en el menú desplegable.

Debido a que necesitábamos llenar el menú desplegable con los nombres de los temas en el themeArray que creamos, usamos el método de matriz .map para mapear el themeArray y mostrar los nombres individualmente usando la etiqueta de option .

Un momento, no hemos terminado de explicar el código anterior. En la etiqueta de select de apertura, pasamos el atributo onChange para rastrear y actualizar el estado del theme cada vez que se selecciona un nuevo valor en el menú desplegable. Cada vez que se selecciona una nueva opción en el menú desplegable, el valor se obtiene del objeto que se nos devuelve. A continuación, usamos el setTheme de nuestro enlace de estado para establecer el nuevo valor para que sea el valor que tiene el estado.

En este punto, hemos creado nuestro menú desplegable, configurado el estado de nuestro tema y escrito nuestra función para establecer el estado con el nuevo valor. Lo último que debemos hacer para que CodeMirror use nuestro tema es pasar el tema al objeto de options en ControlledEditorComponent . En el objeto de options , agreguemos un valor llamado theme y establezcamos su valor en el valor del estado para el tema seleccionado, también llamado theme .

Así es como se vería ControlledEditorComponent ahora:

 <ControlledEditorComponent onBeforeChange={handleChange} value= {value} className="code-mirror-wrapper" options={{ lineWrapping: true, lint: true, mode: language, lineNumbers: true, theme: theme, }} />

Ahora, hemos creado un menú desplegable de diferentes temas que se pueden seleccionar en el editor.

Así es como se ve el código completo en Editor.js en este momento:

 import React, { useState } from 'react'; import 'codemirror/lib/codemirror.css'; import 'codemirror/theme/dracula.css'; import 'codemirror/theme/material.css'; import 'codemirror/theme/mdn-like.css'; import 'codemirror/theme/the-matrix.css'; import 'codemirror/theme/night.css'; import 'codemirror/mode/xml/xml'; import 'codemirror/mode/javascript/javascript'; import 'codemirror/mode/css/css'; import { Controlled as ControlledEditorComponent } from 'react-codemirror2'; const Editor = ({ language, value, setEditorState }) => { const [theme, setTheme] = useState("dracula") const handleChange = (editor, data, value) => { setEditorState(value); } const themeArray = ['dracula', 'material', 'mdn-like', 'the-matrix', 'night'] return ( <div className="editor-container"> <div style={{marginBottom: "10px"}}> <label for="themes">Choose a theme: </label> <select name="theme" onChange={(el) => { setTheme(el.target.value) }}> { themeArray.map( theme => ( <option value={theme}>{theme}</option> )) } </select> </div> <ControlledEditorComponent onBeforeChange={handleChange} value= {value} className="code-mirror-wrapper" options={{ lineWrapping: true, lint: true, mode: language, lineNumbers: true, theme: theme, }} /> </div> ) } export default Editor

Solo hay un className que necesitamos diseñar. Vaya a App.css y agregue el siguiente estilo:

 .editor-container{ padding-top: 0.4%; }

Ahora que nuestros editores están listos, regresemos a App.js y usémoslos allí.

src > App.js

Lo primero que debemos hacer es importar el componente Editor.js aquí:

 import Editor from './components/Editor';

En App.js , declaremos los estados que contendrán el contenido de los editores de HTML, CSS y JavaScript, respectivamente.

 const [html, setHtml] = useState(''); const [css, setCss] = useState(''); const [js, setJs] = useState('');

Si recuerda, necesitaremos usar estos estados para mantener y proporcionar el contenido de nuestros editores.

A continuación, reemplacemos las etiquetas de párrafo ( p ) que usamos para HTML, CSS y JavaScript en las representaciones condicionales con los componentes del editor que acabamos de crear, y también pasaremos la propiedad adecuada a cada instancia del editor. componente:

 function App() { ... return ( <div className="App"> <p>Welcome to the edior</p> // This is where the tab buttons container is... <div className="editor-container"> { htmlEditorIsOpen ? ( <Editor language="xml" value={html} setEditorState={setHtml} /> ) : cssEditorIsOpen ? ( <Editor language="css" value={css} setEditorState={setCss} /> ) : ( <Editor language="javascript" value={js} setEditorState={setJs} /> ) } </div> </div> ); } export default App;

Si ha estado siguiendo hasta ahora, comprenderá lo que hicimos en el bloque de código anterior.

Aquí está en lenguaje sencillo: reemplazamos las etiquetas p (que estaban allí como marcadores de posición) con instancias de los componentes del editor. Luego, proporcionamos sus propiedades language , value y setEditorState , respectivamente, para que coincidan con sus estados correspondientes.

¡Hemos llegado tan lejos! Así es como se ve nuestra aplicación ahora:

Cómo se ve nuestra aplicación ahora
(Vista previa grande)

Introducción a los marcos flotantes

Usaremos marcos en línea (iframes) para mostrar el resultado del código ingresado en el editor.

Según MDN:

El elemento HTML Inline Frame ( <iframe> ) representa un contexto de navegación anidado, incrustando otra página HTML en la actual.

Cómo funcionan los marcos flotantes en React

Los iframes se usan normalmente con HTML simple. El uso de Iframes con React no requiere muchos cambios, el principal es convertir nombres de atributos a camelcase. Un ejemplo de esto es que srcdoc se convertiría en srcDoc .

El futuro de los iframes en la web

Los iframes siguen siendo muy útiles en el desarrollo web. Algo que tal vez quieras revisar es Portals. Como explica Daniel Brain:

“Los portales introducen un nuevo y poderoso conjunto de capacidades en esta combinación. Ahora es posible construir algo que se sienta como un iframe, que puede animarse y transformarse sin problemas y tomar el control de la ventana completa del navegador”.

Una de las cosas que Portals intenta resolver es el problema de la barra de URL. Al usar iframe, los componentes representados en el iframe no llevan una URL única en la barra de direcciones; como tal, esto podría no ser bueno para la experiencia del usuario, según el caso de uso. Vale la pena echarle un vistazo a Portals, y le sugiero que lo haga, pero debido a que no es el enfoque de nuestro artículo, esto es todo lo que diré al respecto aquí.

Creando el Iframe para albergar nuestro resultado

Avancemos con nuestro tutorial creando un iframe para albergar el resultado de nuestros editores.

 return ( <div className="App"> // ... <div> <iframe srcDoc={srcDoc} title="output" sandbox="allow-scripts" frameBorder="1" width="100%" height="100%" /> </div> </div> );

Aquí, creamos el iframe y lo alojamos en una etiqueta de contenedor div . En el iframe, pasamos algunos atributos que necesitamos:

  • srcDoc
    El atributo srcDoc está escrito en camelcase porque así es como se escriben los atributos de iframe en React. Cuando usamos un iframe, podemos incrustar una página web externa en la página o mostrar contenido HTML específico. Para cargar e incrustar una página externa, usaríamos la propiedad src en su lugar. En nuestro caso, no estamos cargando una página externa; más bien, queremos crear un nuevo documento HTML interno que albergue nuestro resultado; para esto, necesitamos el atributo srcDoc . Este atributo toma el documento HTML que queremos incrustar (aún no lo hemos creado, pero lo haremos pronto).
  • title
    El atributo de título se utiliza para describir el contenido del marco en línea.
  • sandbox
    Esta propiedad tiene muchos propósitos. En nuestro caso, lo estamos usando para permitir que los scripts se ejecuten en nuestro iframe con el valor allow-scripts . Debido a que estamos trabajando con un editor de JavaScript, esto sería útil rápidamente.
  • frameBorder
    Esto simplemente define el grosor del borde del iframe.
  • width y height
    Esto define el ancho y la altura del iframe.

Estos términos ahora deberían tener más sentido para usted. Avancemos y declaremos el estado que contendrá el documento de plantilla HTML para srcDoc . Si observa detenidamente el bloque de código anterior, verá que pasamos un valor al atributo srcDoc : srcDoc ={srcDoc} . Usemos nuestro useState() React para declarar el estado srcDoc . Para hacer esto, en el archivo App.js , vaya a donde definimos los otros estados y agregue este:

 const [srcDoc, setSrcDoc] = useState(` `);

Ahora que hemos creado el estado, lo siguiente que debemos hacer es mostrar el resultado en el estado cada vez que escribimos en el editor de código. Pero lo que no queremos es volver a renderizar el componente con cada pulsación de tecla. Con eso en mente, procedamos.

Configuración del iframe para mostrar el resultado

Cada vez que hay un cambio en cualquiera de los editores de HTML, CSS y JavaScript, respectivamente, queremos que se useEffect() , y eso generará el resultado actualizado en el iframe. Escribamos useEffect() para hacer esto en el archivo App.js :

Primero, importa el gancho useEffect() :

 import React, { useState, useEffect } from 'react';

Escribamos useEffect() así:

 useEffect(() => { const timeOut = setTimeout(() => { setSrcDoc( ` <html> <body>${html}</body> <style>${css}</style> <script>${js}</script> </html> ` ) }, 250); return () => clearTimeout(timeOut) }, [html, css, js])

Aquí, escribimos un gancho useEffect() que siempre se ejecutará cada vez que se cambien o actualicen los estados de valor que declaramos para los editores de HTML, CSS y JavaScript.

¿Por qué necesitábamos usar setTimeout() ? Bueno, si escribimos esto sin él, entonces cada vez que se presiona una sola tecla en un editor, nuestro iframe se actualizaría, y eso no es bueno para el rendimiento en general. Así que usamos setTimeout() para retrasar la actualización durante 250 milisegundos, lo que nos da tiempo suficiente para saber si el usuario todavía está escribiendo. Es decir, cada vez que el usuario presiona una tecla, se reinicia el conteo, por lo que el iframe solo se actualizaría cuando el usuario haya estado inactivo (sin escribir) durante 250 milisegundos. Esta es una manera genial de evitar tener que actualizar el iframe cada vez que se presiona una tecla.

Lo siguiente que hicimos arriba fue actualizar srcDoc con los nuevos cambios. El componente srcDoc , como explicamos anteriormente, representa el contenido HTML especificado en el iframe. En nuestro código, pasamos una plantilla HTML, tomando el estado html que contiene el código que el usuario ha escrito en el editor HTML y colocándolo entre las etiquetas del body de nuestra plantilla. También tomamos el estado css que contiene los estilos que el usuario ha escrito en el editor de CSS y lo pasamos entre las etiquetas de style . Finalmente, tomamos el estado js que contiene el código JavaScript que el usuario ha escrito en el editor de JavaScript y lo pasamos entre las etiquetas del script .

Tenga en cuenta que al configurar setSrcDoc , usamos acentos graves ( ` ` ) en lugar de comillas normales ( ' ' ). Esto se debe a que los acentos graves nos permiten pasar los valores de estado correspondientes, como hicimos en el código anterior.

La declaración de return en el useEffect() es una función de limpieza que borra setTimeout() cuando está completo, para evitar pérdidas de memoria. La documentación tiene más información sobre useEffect .

Así es como se ve nuestro proyecto en este momento:

Cómo se ve nuestro proyecto en este momento
(Vista previa grande)

Complementos de CodeMirror

Con los complementos de CodeMirror, podemos mejorar nuestro editor con más del tipo de funcionalidad que encontraríamos en otros editores de código. Veamos un ejemplo de etiquetas de cierre que se agregan automáticamente cuando se escribe una etiqueta de apertura, y otro ejemplo de un corchete que se cierra automáticamente cuando se ingresa el corchete de apertura:

Lo primero que debe hacer es importar el complemento para esto en nuestro archivo App.js :

 import 'codemirror/addon/edit/closetag'; import 'codemirror/addon/edit/closebrackets';

Pasemoslo en las opciones de ControlledEditorComponent :

 <ControlledEditorComponent ... options={{ ... autoCloseTags: true, autoCloseBrackets: true, }} />

Ahora esto es lo que tenemos:

La forma en que se ve nuestro proyecto
(Vista previa grande)

Puede agregar una tonelada de estos complementos a su editor para darle funciones más ricas. No podríamos revisarlos todos aquí.

Ahora que hemos terminado con esto, analicemos brevemente las cosas que podríamos hacer para mejorar la accesibilidad y el rendimiento de nuestra aplicación.

Rendimiento y Accesibilidad de la Solución

Mirando nuestro editor de código web, algunas cosas definitivamente podrían mejorarse.

Debido a que hemos prestado atención principalmente a la funcionalidad, es posible que hayamos descuidado un poco el diseño. Para una mejor accesibilidad, aquí hay algunas cosas que podría hacer para mejorar esta solución:

  1. Puede establecer una clase active en el botón para el editor actualmente abierto. Resaltar el botón mejoraría la accesibilidad al dar a los usuarios una indicación clara de en qué editor están trabajando actualmente.
  2. Es posible que desee que el editor ocupe más espacio en la pantalla que el que tenemos aquí. Otra cosa que podría intentar es hacer que el iframe aparezca con el clic de un botón que está acoplado en algún lugar a un lado. Hacerlo le daría al editor más espacio en la pantalla.
  3. Este tipo de editor sería útil para las personas que desean ejecutar un ejercicio rápido en su dispositivo móvil, por lo que sería necesario adaptarlo completamente al dispositivo móvil (sin mencionar los dos puntos anteriores sobre dispositivos móviles).
  4. Actualmente, podemos cambiar el tema del componente del editor entre los múltiples temas que hemos cargado, pero el tema general de la página sigue siendo el mismo. Puede permitir que el usuario cambie entre un tema oscuro y otro claro para todo el diseño. Esto sería bueno para la accesibilidad, aliviando la tensión en los ojos de las personas al mirar una pantalla brillante durante demasiado tiempo.
  5. No analizamos los problemas de seguridad con nuestro iframe, principalmente porque estábamos cargando un documento HTML interno en el iframe, en lugar de un documento externo. Por lo tanto, no necesitamos considerar esto con mucho cuidado porque los iframes son una buena opción para nuestro caso de uso.
  6. Con iframes, otra consideración sería el tiempo de carga de la página, porque el contenido que se carga en el iframe normalmente estaría fuera de su control. En nuestra aplicación, esto no es un problema porque nuestro contenido iframe no es externo.

El rendimiento y la accesibilidad merecen mucha consideración cuando crea cualquier aplicación, ya que determinarán qué tan útil y utilizable es su aplicación para los usuarios.

Shedrack ha hecho un buen trabajo al explicar los métodos para mejorar y optimizar el rendimiento en las aplicaciones React. ¡Vale la pena echarle un vistazo!

Conclusión

Trabajar a través de diferentes proyectos nos ayuda a aprender sobre una amplia gama de temas. Ahora que ha leído este artículo, siéntase libre de ampliar su experiencia experimentando con más complementos para enriquecer el editor de código, renovando la interfaz de usuario y solucionando los problemas de accesibilidad y rendimiento descritos anteriormente.

  • El código base completo para este proyecto está disponible en GitHub.

Aquí está la demostración en Codesandbox:

Enlaces y Material

  • "Portales de Google Chrome: como Iframes, pero mejores y peores", Daniel Brain
  • "Optimización del rendimiento", documentación de React
  • “Manual de usuario y guía de referencia”, documentación de CodeMirror