Repository navigation
VisualEditor es
🌐 Esta página en: English · Português · Español · Français · Deutsch
Una extensión de VS Code de primera mano que convierte el SDK en un entorno de desarrollo visual: una pantalla se renderiza al lado del archivo que la produce, desde el búfer en el que estás escribiendo, cada elemento renderizado sabe qué expresión de C# lo construyó, y seleccionar uno muestra, y edita, esa expresión.
La vista previa es el realizador web real ejecutando el módulo compilado real. No es un parecido, y ahí está el punto: una vista previa que se renderiza es evidencia de que la página se renderiza. Nada nuevo dibuja píxeles.
Estado. La vista previa, el mecanismo de identidad, la selección, el inspector de propiedades y los gestos estructurales (insertar, mover, duplicar, quitar y arrastrar entre contenedores) están en su lugar y cubiertos, por pruebas unitarias contra una compilación de proyecto real, y por una suite de integración que corre dentro de un VS Code real. Se empaqueta en un
.vsixinstalable; todavía no está en el Marketplace. La pista E de la Hoja de ruta tiene los hitos.
Desde 0.2.0-preview.30
eqdesign es un proceso de larga vida que tiene la compilación de Roslyn del proyecto y compila el
búfer del editor, no el archivo en disco. Esa distinción es toda la razón de que exista: eqc lee
archivos y el servicio de recarga en caliente vigila el sistema de archivos, así que ninguno puede
mostrarte el texto que estás mirando ahora, solo el texto que guardaste la última vez.
Habla JSON delimitado por saltos de línea sobre stdio. Ningún puerto que elegir, nada que autorizar, nada escuchando si la ventana muere, y funciona sin cambios sobre Remote-SSH porque el host de la extensión corre en la máquina remota de todos modos.
Medido en samples/DefaultUIDashboard (16 archivos fuente, 316 referencias):
| Paso | Tiempo |
|---|---|
| Construir la compilación, una vez al activar | 271 ms |
Compilar una edición (PaymentsPage de 662 líneas, en caliente) |
p50 293 ms |
| Solo diagnósticos (enlazar, sin transpilar) | 36 ms |
De ahí dos cadencias: los errores se revisan de nuevo tras una pausa de 150 ms al escribir, la vista previa recompila tras 400 ms. Un error de C# detiene la compilación antes de que se emita nada, Roslyn parsea con indulgencia, así que una llave que falta todavía produce un árbol que el transpilador recorrería, produciendo un módulo que se monta y lanza, que llega a la pantalla como un fotograma en blanco sin nada que lo explique.
El host se niega a arrancar si la lista de referencias está vacía, en vez de compilar contra un modelo incompleto. Ese fallo es invisible de otro modo: sin el modelo semántico, los argumentos nombrados se emiten en orden sintáctico, y la pantalla se renderiza con sus valores en las ranuras equivocadas y ningún error en ninguna parte.
Una pantalla rara vez es un archivo. Un shell, una fila, un ayudante de datos, así que la extensión envía todo búfer de C# que el editor tiene ahora, no solo el activo, y el host los superpone todos sobre la compilación antes de compilar. De lo contrario la vista previa te mostraría la última versión guardada de todo excepto del archivo en el que resulta que estás escribiendo.
Desde 0.2.0-preview.30
VisualNode lleva un Origin, el tramo de fuente de la expresión que lo construyó, o null, que es
lo que es toda compilación entregada. Una compilación en modo diseño lo rellena:
var compiler = new ComponentCompiler { DesignMode = true };
// Cada construcción de nodo se emite envuelta, para que el nodo construido recuerde de dónde vino:
// $eq.origin(UI.text(…), "…/PaymentsPage.cs|28:12|28:73")Los dos realizadores lo llevan al DOM como data-eq-origin, adjunto en su único despacho:
<div data-eq-origin="/src/Screens/PaymentsPage.cs|27:8|34:10">
<span data-eq-origin="/src/Screens/PaymentsPage.cs|28:12|28:73">Count: 0</span>
</div>El formato es path|startLine:startColumn|endLine:endColumn, con base cero, las coordenadas propias
del editor.
Por qué no mapas de fuente. La respuesta obvia no funciona. Todo el cuerpo de Build se convierte
en una sola cadena plana y se emite por una sola llamada, así que la posición más fina que un mapa V3
puede nombrar es el inicio del método: un módulo entregado tiene una línea de 942 caracteres. Un
origen es exacto en cambio, y como es una cadena simple en el nodo abstracto, la pista nativa (Photon)
hereda el mismo mecanismo el día que lo quiera.
Solo las construcciones se sellan, nunca las referencias. Sellar una variable donde solo se
menciona sobrescribiría el origen de la construcción con el tramo de un uso, y seleccionaría la línea
equivocada. Un nodo construido dentro de un foreach lleva el tramo de la única expresión que lo
construye, que de todos modos es lo único editable allí.
El modo diseño está apagado por defecto y nunca encendido en una compilación del SDK: el envoltorio
es código emitido de verdad, y entregarlo pondría la preocupación de una herramienta de diseño en el
bundle de cada usuario. Un render de producción no contiene ningún data-eq-origin, y la prueba de
huella de SSR e hidratación lo fija.
Desde 0.2.0-preview.30
Un origen dice de dónde vino un nodo. No dice si ese lugar puede editarse, y tratar las dos como una
sola pregunta es cómo un editor visual corrompe un archivo. Una fila construida quinientas veces
dentro de un foreach tiene un sitio de fuente y ninguna existencia separada; un nodo devuelto por un
método ayudante está escrito en otra parte por completo. «Borra esta fila» está mal para los dos.
Así que todo nodo seleccionado se clasifica, recorriendo los ancestros de Roslyn desde la posición del origen:
| Nivel | Dónde está escrito | Qué ofrece el lienzo |
|---|---|---|
literal |
Sin condición, en el Build del que se hace la vista previa |
todo, incluidas las ediciones estructurales |
derived |
Dentro de un bucle, un condicional o un callback | seleccionar, inspeccionar, editar propiedades |
foreign |
Otro miembro, una función local, u otro archivo | seleccionar, inspeccionar, y saltar a donde está escrito |
Una función local cuenta aquí como su propio miembro, porque un nodo construido allí está escrito una vez y se alcanza por una llamada, exactamente como un método ayudante.
Todo rechazo se explica antes de que se dibuje la posibilidad, nunca después de que se pulse:
Construido dentro de un bucle: toda repetición viene de esta única expresión, así que no hay una sola fila que mover o borrar.
Construido por
PaymentRow(), no porBuild().
La distinción que importa en la práctica: el entendimiento no está cercado, solo la escritura. El editor abre código que ya existe, y el código real se reparte por métodos ayudantes y archivos. Seleccionar, inspeccionar y editar una propiedad funcionan donde sea que el nodo se haya escrito, incluso en otro archivo, que es por lo que al host se le envía todo búfer abierto. La inserción estructural es lo que se queda dentro de la forma declarativa.
Desde 0.2.0-preview.30
Seleccionar un elemento abre un panel acoplado, en el cromo propio del editor, listando lo que el componente toma: el tipo de cada propiedad, el comentario de documentación leído del símbolo de fuente, y cómo está escrita ahora, como argumento, en un inicializador de objeto, o en absoluto.
Las ediciones se escriben de vuelta como fuente de C#, porque el archivo es C#. Un enum ofrece sus
miembros como una lista en vez de un cuadro de texto; todo lo demás es un valor que escribes, y 12,
"Save" y Variant.Secondary son todos simplemente expresiones.
Tres cosas mantienen eso honesto:
- Se compila antes de ofrecerse. Una edición candidata se parsea y se enlaza, y un rechazo cita al compilador: Eso no compilaría: CS0117 ….
-
Pasa por
WorkspaceEdit, nunca por una escritura de archivo. El cambio aterriza en la pila de deshacer propia del documento, así que un solo Ctrl+Z revierte el gesto entero y un búfer sin guardar sigue sin guardar. -
Nunca reescribe la forma en la que el archivo está escrito. Los miembros init-only de un
componente, el
LoadingdeButton, elWidthdeFlexNode, se alcanzan solo por un inicializador de objeto. En una llamada de fábrica la respuesta honesta es «no desde aquí, y aquí está por qué»: la superficie de fábrica existe precisamente para que nadie escribanew Button("Save") { … }, y convertir una en la otra a espaldas del autor no es una edición que haya pedido. Una construcción ya escrita connewha elegido esa forma, así que sus llaves simplemente se añaden cuando algo tiene que ir en ellas.
Un conjunto cerrado se ofrece como lista, y el tipo no es lo único que decide qué está cerrado. La
mitad de los tokens del framework no son enums: Space es una clase estática de const float, así
que un float gap se lee como un número. Lo que está escrito ahí dice otra cosa: Space.S3 nombra
un miembro de una clase estática, y todo hermano del mismo tipo pertenece a esa ranura, así que la fila
se abre como la escala de la que vino. Un gap: 7 simple sigue siendo un número, porque nada allí
nombra una escala.
Un valor no es una cadena. Un BoxStyle llegaría de otro modo como una larga línea de C# en una
sola celda, lo que un autor más quiere cambiar, en el único lugar donde no puede cambiarlo. Un valor
escrito como new T { … } reporta sus miembros como propiedades ordinarias, y el panel los renderiza
como una pequeña hoja bajo la fila que los lleva:
style BoxStyle 2 of 9 set
│ Padding EdgeInsets EdgeInsets.All(Space.S4) ×
│ Background ColorToken context.Theme.Surface ×
│ [ BorderWidth ▾ ] float + set
Claves a la izquierda, valores en la entrada propia del editor, y una última fila cuya clave es un
select de todo lo que no está puesto todavía. Sacar un miembro de vuelta es la × de una fila:
una hoja a la que solo puedes añadir filas tiene fugas. Todo aquí es la misma maquinaria de
propiedades un nivel más abajo, las mismas listas de enum, la misma guarda de compilación, el mismo
rechazo cuando el valor está escrito sin un inicializador en el que ponerlo.
El columns de un Grid es una expresión de colección cuyos elementos tienen tramos de verdad,
exactamente como un children, y así son también los items de un menú y las actions de un
diálogo. Todos reciben lo que recibieron los hijos: el panel los lista, e insertar o quitar toma el
nombre de la lista.
Lo que difiere es lo que va en ellos. Un GridTrack es datos: nunca se renderiza, así que nada en
el lienzo lleva su tramo y no hay nada que pulsar. Esas listas se direccionan por tanto por posición
desde el panel, y su paleta son las maneras propias que el tipo de elemento tiene de escribirse,
GridTrack.Flex(), GridTrack.Auto, y new(…) tipado por destino, ya que el framework no da fábrica
a los records de valor a propósito. Los dos hechos se leen de la firma en vez de escribirse como una
regla sobre grids.
Desde 0.2.0-preview.30
Los gestos estructurales están cercados a la forma declarativa, una expresión de colección
children: [ … ]:
Column(gap: Space.S4, children: [
Text("Total", TypeRole.LabelM, context.Theme.TextSecondary),
Text(total, TypeRole.TitleM, context.Theme.TextPrimary),
])Esa lista es la única forma en la que una inserción puede empalmarse con seguridad: sus elementos
tienen tramos de verdad, así que uno nuevo va entre dos de ellos sin tocar nada más. Un contenedor
construido con sentencias (var column = new Column(); column.Add(…);) es flujo de datos, no una
lista, y reordenarlo es otro problema. El panel pregunta primero y lo dice, en vez de ofrecer un
control que se niega.
-
Insertar viene de una paleta derivada de las superficies de fábrica mismas, así que un
componente nuevo aparece en ella sin que nadie mantenga una lista. Eso incluye los componentes
propios de la app: el generador les escribe una superficie
AppUIexactamente como la del framework, y se ofrecen primero, bajo su propio encabezado. Toda entrada está probada de que compila donde aterriza: una prueba inserta cada una en una lista real y falla con cualquiera que no lo hiciera. - Mover intercambia dos elementos y deja todo lo que está entre ellos exactamente donde está: la coma, el salto de línea, la sangría. Reescribir la lista desde los textos de sus elementos sería más simple y descartaría en silencio un comentario que alguien dejó entre dos hijos. Queda una imperfección, y se nombra en vez de ocultarse: un comentario escrito encima de un hijo se queda con la posición, no con el hijo.
- Quitar toma el elemento y exactamente un separador. El elemento solo deja una coma perdida; los dos dejan un agujero.
Ni mover ni quitar se ofrecen donde no significan nada: un nodo que no es elemento de una lista no tiene orden dentro del cual moverse, así que los controles se ocultan en vez de deshabilitarse, no hay nada que explicar sobre un arreglo que no existe.
- Duplicar copia el texto de un nodo y aterriza la copia inmediatamente después, y la copia es lo que queda seleccionado, porque es la cosa que acabas de hacer.
Cada gesto es un solo WorkspaceEdit, así que cada uno es un solo deshacer.
La pantalla entera es también un árbol, en el Explorador al lado del Esquema. Cada fila dice dónde
está escrita, PaymentsPage.cs:28, que es lo único que una lista puede decir y una imagen no, y
alcanza lo que el lienzo no puede: un contenedor llenado de borde a borde por sus propios hijos no
tiene ningún píxel propio.
Elegir una fila la selecciona en el lienzo, por el mismo camino que toma un clic, así que el esquema, el panel y el cursor del editor van todos detrás. Seleccionar en el lienzo marca la fila.
Todo gesto de arriba es también una tecla, y son el mismo gesto en vez de una segunda implementación de él:
| Flechas | recorrer el árbol: arriba al padre, abajo al primer hijo, izquierda y derecha por los hermanos |
| Alt+Flechas | mover el nodo en vez de la selección |
| Delete | quitarlo |
| Cmd/Ctrl+D | duplicarlo |
| Esc | soltar: primero la selección, después el modo inspección |
Respetan las mismas guardas que el puntero: nada estructural mientras el árbol se asienta, y nada en absoluto mientras el puntero está sobre el panel, donde esas teclas son alguien escribiendo.
Los mismos tres gestos están en el puntero, donde el árbol es legible:
- Arrastra un hijo para reordenarlo. Un caret marca el hueco en el que aterrizaría, y la insignia dice a dónde va, Text → 3 of 5. Por lejos que viaje, es una edición y un deshacer.
-
Pasa por encima de un hijo y un
+aparece en cada extremo suyo, antes de este, después de este, a lo largo del eje propio del contenedor. Qué eje se lee del resultado renderizado en vez de del CSS: unaRowes flex, unGrides grid y unStackes absoluto, y en pantalla los tres responden «¿están estos lado a lado?» del mismo modo. (Con menos de dos hijos no hay resultado que leer, y solo ahí el lienzo pregunta a la maquetación misma.) -
Pasa por encima del espacio propio de un contenedor, incluido uno vacío, y el hueco más cercano
al puntero se marca con una línea discontinua y un solo
+. Esa marca es la respuesta a dónde podría ir algo, que es una pregunta que un lienzo nunca debería hacer adivinar a nadie. La raya es a propósito: el caret marca un compromiso, la raya marca una posibilidad.
Un montón no es una fila. Los hijos de un Stack se solapan, y el orden de pintado es el orden de
los hijos, así que su lista es significativa y su geometría no. El lienzo no puede saberlo (dos hijos
que se solapan se ven exactamente como dos que nadie maquetó), así que el host dice qué listas están
en capas. Allí, la marca de soltar se vuelve una cubierta sobre el hijo delante del cual el nodo
vendría a descansar, la insignia lee in front of Card en vez de 3 of 5, y las flechas del panel
mantienen su gesto mientras descartan el nombre equivocado para él: Enviar atrás y Traer
adelante.
Un arrastre no está confinado a la lista en la que empezó: el destino de soltar se recalcula desde el
puntero en cada movimiento, así que el caret va detrás hacia cualquier contenedor sobre el que esté.
Salir de su propia lista es un quitar y un insertar, que el host escribe como un solo reemplazo
que abarca los dos, una edición, un Ctrl+Z. El texto movido se vuelve a sangrar a su nueva profundidad
y por lo demás se lleva literalmente, que es lo que hace del compilador la cerca: un nodo escrito
contra una variable local del método del que vino no compila en su nuevo hogar, y el rechazo cita
CS0103 en vez de inventar una regla sobre qué es portable.
Los rechazos se dibujan antes del gesto en vez de después. La pantalla y la lista de C# tienen que alinearse hijo por hijo: un hijo que se renderiza como varios elementos, o como ninguno, rompe esa correspondencia, y un índice calculado desde píxeles nombraría entonces otro elemento en el archivo, así que la posibilidad se retira en vez de adivinarse. Un nodo no puede soltarse dentro de su propio subárbol, dentro de un contenedor que no toma lista, ni dentro de una lista en otro archivo, eso editaría dos documentos a la vez.
Un movimiento invalida el tramo que el panel tenía, así que el host responde con dónde aterrizó el nodo y la selección va detrás hacia allí. Sin eso, preguntar por las viejas coordenadas describe al hermano con el que acababa de intercambiarse.
A la vista previa no le importa por qué SDK se entrega el proyecto. Un proyecto nativo (Photon),
que no tiene wwwroot y nunca corre eqc, tiene vista previa igual: el SDK nativo escribe la lista de
referencias para las herramientas, la extensión lleva el runtime de navegador que el proyecto
legítimamente no tiene, y el mismo árbol abstracto de componentes se renderiza por el realizador web.
La barra de herramientas lo dice exactamente, native project · web realizer, y abre en el shell de
teléfono, porque esa es la forma en la que la app se entrega. Un valor por defecto, no una jaula: en
cuanto una mano toca el selector de formato, el proyecto deja de tener opinión.
Un contador pulsado hasta 7 se queda en 7 a través de la siguiente pulsación. Antes de cada remontaje
los campos propios de la página se capturan, distinguidos de los del framework sondeando la clase
base propia de la página, no una lista escrita a mano, y se devuelven por la misma puerta
__INITIAL_STATE__ que usa el traspaso del SSR. Guardado por nombre de clase, así que una página
renombrada empieza de cero en vez de heredar los campos de un extraño.
El botón del cubo en la barra de herramientas renderiza el búfer actual por el motor nativo y muestra el fotograma, glifos reales, tema real, en el formato que el selector haya elegido. Estos son los píxeles contra los que se fijan los backends de GPU: el fotograma viene del rasterizador de Referencia, la fuente normativa de píxeles del motor, y una prueba de paridad permanente demuestra que Metal dibuja la misma lista de display a los mismos píxeles.
Corre en un proceso hijo descartable, porque el Build() de una página es el código del autor: uno
que nunca retorna se mata tras diez segundos y vuelve como una frase, con la vista previa todavía viva
detrás. Los servicios que pide el constructor de una página se satisfacen como lo haría el contenedor
de DI, menos el contenedor, una implementación del ensamblado propio de la app cuando hay una, un stub
benigno si no, y una página que aun así lanza renderiza la superficie de diagnóstico propia del
framework, nombrando el componente y la excepción.
Una imagen fija, a propósito, pero una pulsable. Antes de que el fotograma se emita, toda expresión que construye un nodo se reescribe para sellar el tramo de C# que lo construyó (una pasada de C# a C# sobre la compilación de diseño, nunca sobre nada que un usuario entregue), y el renderizador devuelve un mapa de impactos junto al PNG: el origen de cada nodo y sus límites maquetados, en orden de pintado. Un clic en la imagen encuentra el componente más arriba bajo el cursor, la última entrada que contiene el punto, lo marca en el fotograma, abre el inspector y revela la línea exacta. Es la misma cadena de origen con la que selecciona el lienzo web, así que la imagen y la página viva responden a un clic de forma idéntica; los métodos ayudantes se sellan en su sitio de llamada, y el tramo propio de una construcción siempre le gana a la llamada que solo la devolvió.
Las interacciones más allá de la selección se quedan en la vista previa viva, y una recompilación descarta un fotograma de la página que reemplazó.
Empaquétalo una vez e instala el .vsix:
cd extensions/vscode && npm install && npm run packageEso compila la extensión, publica el host de diseño dentro de ella y escribe equantic-ui.vsix
(10,7 MB), el host viaja con la extensión, así que una instalación no necesita checkout. Es
dependiente del framework: una publicación autocontenida serían ~70 MB por plataforma y tres de
ellas, y la única dependencia que esto asume es el runtime de .NET contra el que ya estás compilando.
Trabajando EN la extensión en cambio, npm run compile es suficiente: en desarrollo la compilación
propia del repositorio del host gana sobre una empaquetada, así que un .vsix compilado ayer no puede
responder en silencio por la fuente de hoy.
Luego abre una página o componente de C# y ejecuta eQuantic UI: Open Preview. El proyecto debe
haberse compilado una vez, la vista previa se apoya en la salida de una compilación ordinaria (la
lista de referencias que escribe el SDK, y wwwroot/_equantic/runtime.js), así que «compila el
proyecto una vez» es la instrucción honesta cuando falta una pieza.
Los controles propios de la vista previa viven en la barra de título del editor, aportados como comandos ordinarios, así que heredan el tema y el conjunto de iconos como cualquier otra acción del editor: el puntero empieza y detiene la inspección, y un reinicio está ahí para cuando hay que traer de vuelta al host.
- No arrastra entre archivos. Un nodo puede arrastrarse a cualquier lista del archivo que se está editando, pero un contenedor escrito en otro se rechaza: esa edición tocaría dos documentos a la vez.
-
GridyStacknecesitan su propio gesto. Las columnas de un Grid son pistas cuya adición vuelve a fluir a cada hijo, y el orden de lista de un Stack es orden de pintado, ninguno de los dos es el gesto «inserta un hermano» que comparten los otros contenedores. -
Sin vista previa nativa. La identidad ya está resuelta en Photon (
LayoutNodelleva el nodo, los límites absolutos y una ruta estable); solo falta meter los fotogramas en un webview. -
Sin llamadas a
[ServerAction]. Los ids de acción vienen de un escaneo de ensamblado al arrancar, así que una clase de vista previa ad-hoc nunca está en el registro. - El tema de base. Una app selecciona su tema al arrancar, y leer eso de vuelta significa ejecutar la raíz de composición de la app. Las formas y la maquetación son exactas; una paleta de otra marca todavía no se refleja.
- Depuración y herramientas de desarrollo: el logger y la superposición de errores que la vista previa reutiliza.
- Compilador: cómo el C# se vuelve el módulo que la vista previa ejecuta.
- Hoja de ruta: la pista E y lo que está antes de ella.
🏁 Empieza aquí
🏗️ Arquitectura
- Visión general de la arquitectura
- Componentes write-once
- Superficie declarativa
- Arquitectura de paquetes
- Componentes
- Estilos
- Localización
- Analytics y GTM
📱 Write-once
- Motor Photon
- Design System
- Capacidades
- Almacenamiento
- Formularios
- Editor de código
- Markdown
- Mermaid
- Renderizado de email
⚙️ Compilación
⚡ Runtime
🔌 Servidor
🎨 Ecosistema
🚀 Desarrollo