Herramientas de diagramación: PlantUML, GraphViz, Mermaid, BPMN y más

En este blog post vamos a hablar sobre varias herramientas de diagramación que pueden ser útiles para diferentes propósitos. Cada una de estas herramientas tiene su propia sintaxis y características únicas que las hacen útiles para diferentes tipos de diagramas.

PlantUML:

Es una herramienta para crear diagramas UML utilizando un lenguaje de marcado de texto. Puede utilizarse para crear diagramas de clases, secuencia, actividad, caso de uso, entre otros.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
@startuml
class Car {
+ int wheels
+ int doors
+ String color
+ drive()
+ park()
}

class SUV {
+ int wheels
+ int doors
+ String color
+ int groundClearance
+ drive()
+ park()
}

Car <|-- SUV
@enduml

GraphViz:

es una herramienta de visualización de gráficos que utiliza lenguaje de descripción de gráficos DOT. Puede utilizarse para crear diagramas de flujo, diagramas de red, diagramas de árbol, entre otros.

1
2
3
4
5
6
7
8
9
10
11
digraph example {
start [shape=Mdiamond,label="Inicio"]
end [shape=Msquare,label="Fin"]
proceso1 [shape=rectangle,label="Proceso 1"]
proceso2 [shape=rectangle,label="Proceso 2"]
proceso3 [shape=rectangle,label="Proceso 3"]
start -> proceso1
proceso1 -> proceso2
proceso2 -> proceso3
proceso3 -> end
}

Mermaid:

Es una herramienta para crear diagramas de flujo, diagramas de secuencia, diagramas de clase, entre otros. Utiliza una sintaxis de marcado de texto similar a PlantUML.

1
2
3
4
5
graph TD;
A-->B;
A-->C;
B-->D;
C-->D;

BPMN:

Es un estándar para modelar procesos de negocio utilizando un lenguaje gráfico. Puede utilizarse para crear diagramas de flujo de trabajo, diagramas de proceso, entre otros.

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
<?xml version="1.0" encoding="UTF-8"?>
<definitions xmlns="http://www.omg.org/spec/BPMN/20100524/MODEL"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:activiti="http://activiti.org/bpmn"
targetNamespace="http://activiti.org/bpmn20">
<process id="loanProcess" name="Solicitud de préstamo" isExecutable="true">
<startEvent id="startEvent" name="Inicio" />
<userTask id="task1" name="Solicitud de préstamo" activiti:candidateGroups="loanDepartment" />
<exclusiveGateway id="exclusiveGateway1" name="Aprobado?" />
<userTask id="task2" name="Evaluar garantía" activiti:candidateGroups="underwriter" />
<exclusiveGateway id="exclusiveGateway2" name="Aprobado?" />
<userTask id="task3" name="Aprobar préstamo" activiti:candidateGroups="loanDepartment" />
<endEvent id="endEvent" name="Fin" />
<sequenceFlow id="flow1" sourceRef="startEvent" targetRef="task1" />
<sequenceFlow id="flow2" sourceRef="task1" targetRef="exclusiveGateway1" />
<sequenceFlow id="flow3" sourceRef="exclusiveGateway1" targetRef="task2">
<conditionExpression xsi:type="tFormalExpression">${approved == 'no'}</conditionExpression>
</sequenceFlow>
<sequenceFlow id="flow4" sourceRef="exclusiveGateway1" targetRef="task3">
<conditionExpression xsi:type="tFormalExpression">${approved == 'yes'}</conditionExpression>
</sequenceFlow>
<sequenceFlow id="flow5" sourceRef="task2" targetRef="exclusiveGateway2" />
<sequenceFlow id="flow6" sourceRef="exclusiveGateway2" targetRef="task3">
<conditionExpression xsi:type="tFormalExpression">${approved == 'yes'}</conditionExpression>
</sequenceFlow>
<sequenceFlow id="flow7" sourceRef="task3" targetRef="endEvent" />
</process>
</definitions>

Structurizr:

Es una herramienta para crear diagramas de arquitectura de software. Permite modelar sistemas, componentes, contenedores, entre otros.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
Workspace workspace = new Workspace("Structurizr Example", "An example of how to use Structurizr.");
Model model = workspace.getModel();

SoftwareSystem mySystem = model.addSoftwareSystem("My System", "Description");
Person user = model.addPerson("User", "Description");
user.uses(mySystem, "Uses");

ViewSet views = workspace.getViews();
SystemContextView contextView = views.createSystemContextView(mySystem, "SystemContext", "An example System Context diagram.");
contextView.addAllSoftwareSystems();
contextView.addAllPeople();

Styles styles = views.getConfiguration().getStyles();
styles.addElementStyle(Tags.SOFTWARE_SYSTEM).background("#1168bd").color("#ffffff");
styles.addElementStyle(Tags.PERSON).background("#08427b").color("#ffffff").shape(Shape.Person);

PlantUMLWriter plantUMLWriter = new PlantUMLWriter();
System.out.println(plantUMLWriter.toString(contextView));

Excalidraw:

Es una herramienta de dibujo en línea que permite crear diagramas de flujo, diagramas de red, diagramas de Venn, entre otros.

1
2
3
4
5
6
7
Excalidraw
-----------
/~~~~~\
| |
| hello |
| |
\_____/

Nomnoml:

Es una herramienta para crear diagramas de clases, diagramas de secuencia, diagramas de flujo, entre otros. Utiliza una sintaxis de marcado de texto similar a PlantUML.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
#direction: down
[<frame>Hotel|
[<abstract>Customer]
[<abstract>Employee]
[Payment|
<billing>Invoice
PaymentMethod|
CreditCard
Check
]
[Booking|
<option>Reservation
Cancellation
]
]

Svgbob:

Es una herramienta para crear diagramas ASCII que pueden ser renderizados como gráficos SVG. Puede utilizarse para crear diagramas de flujo, diagramas de red, entre otros.

1
2
3
4
5
6
7
+----------+      +----------+
| User | | Database |
+----------+ +----------+
| |
| +------------------+
+----> | Application |
+------------------+

SeqDiag:

Es una herramienta para crear diagramas de secuencia utilizando una sintaxis de marcado de texto. Puede utilizarse para modelar interacciones entre objetos en un sistema.

1
2
3
4
5
6
7
8
seqdiag {
browser -> webserver [label = "GET /index.html"];
browser <-- webserver [label = "HTML content"];
browser -> webserver [label = "POST /login.cgi"];
browser <-- webserver [label = "302 Found"];
browser -> webserver [label = "GET /welcome.cgi"];
browser <-- webserver [label = "Welcome page"];
}

ActDiag:

Es una herramienta para crear diagramas de actividad utilizando una sintaxis de marcado de texto. Puede utilizarse para modelar procesos de negocio y flujo de trabajo.

1
2
3
4
5
actdiag {
A -> B -> C;
D -> E -> F;
G -> H;
}

RackDiag:

Es una herramienta para crear diagramas de rack y servidores utilizando una sintaxis de marcado de texto.

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
rackdiag {
rack_layout;
0U; 1U; 2U; 3U; 4U; 5U; 6U; 7U; 8U; 9U; 10U; 11U; 12U; 13U; 14U; 15U; 16U; 17U; 18U; 19U; 20U;
0U [label="Top of Rack"];
1U [label="Server 1"];
2U [label="Server 2"];
3U [label="Server 3"];
4U [label="Switch"];
5U [label="Server 4"];
6U [label="Server 5"];
7U [label="Server 6"];
8U [label="Switch"];
9U [label="Server 7"];
10U [label="Server 8"];
11U [label="Server 9"];
12U [label="Switch"];
13U [label="Server 10"];
14U [label="Server 11"];
15U [label="Server 12"];
16U [label="Switch"];
17U [label="Server 13"];
18U [label="Server 14"];
19U [label="Server 15"];
20U [label="Bottom of Rack"];
0U -> 20U;
1U -> 4U;
2U -> 4U;
3U -> 4U;
5U -> 8U;
6U -> 8U;
7U -> 8U;
9U -> 12U;
10U -> 12U;
11U -> 12U;
13U -> 16U;
14U -> 16U;
15U -> 16U;
17U -> 20U;
18U -> 20U;
19U -> 20U;
}

Bytefield:

Es una herramienta para crear diagramas de campos de bits utilizando una sintaxis de marcado de texto.

1
2
3
4
5
6
[ Ethernet Packet ]
Destination Address: 6 bytes
Source Address: 6 bytes
Type: 2 bytes
Data: variable
CRC: 4 bytes

NwDiag:

Es una herramienta para crear diagramas de red utilizando una sintaxis de marcado de texto. Puede utilizarse para modelar redes de computadoras y topologías de red.

1
2
3
4
5
6
7
8
9
10
11
12
[Computer] 
{
internet : "10.0.0.1"
eth0 : "192.168.0.2"
}

[Server Web]
{
eth0 : "192.168.0.1"
}

Computer -- Server Web : HTTP

PacketDiag:

Es una herramienta para crear diagramas de paquetes de red utilizando una sintaxis de marcado de texto.

1
2
3
4
5
6
digraph {
node [shape=plaintext, fontname=Helvetica, fontsize=12];

Internet -> Server [label="Request"];
Server -> Internet [label="Response"];
}

C4 with PlantUML:

Es una extensión de PlantUML que permite crear diagramas de arquitectura de software utilizando la notación C4.

Erd:

Es una herramienta para crear diagramas de modelo de entidad-relación utilizando una sintaxis de marcado de texto.

1
2
3
4
5
6
digraph G {
rankdir=LR;
node [shape=plaintext];

"order" -> "customer";
}

Ditaa:

Es una herramienta para crear diagramas de flujo utilizando una sintaxis de marcado de texto.

1
2
3
+---------+
| Hello |
+---------+

Pikchr:

Es una herramienta para crear diagramas utilizando una sintaxis de marcado de texto similar a Diagrama de flujo.

1
2
3
4
5
6
7
8
9
10
11
left
box wid 3 ht 20
box wid 3 ht 20 at 5 0
arrow from last box to 2nd box
arrow from last box down 10
box wid 3 ht 20 at 10 20
arrow from 2nd box down 10
box wid 3 ht 20 at 15 30
arrow from last box to 3rd box
arrow from last box down 10
box wid 3 ht 20 at 20 20

UMlet

UMlet es una herramienta de diagramación de UML gratuita y de código abierto que permite crear diagramas de clases, de secuencia, de actividad y más. Es fácil de usar y permite exportar los diagramas a varios formatos, como SVG y PDF.

@startuml
class Person {
    - name : String
    - age : int
}

Person -> Address
@enduml

Construyendo una API web con F# y Giraffe en .NET 6

En este tutorial, aprenderás cómo construir una API web utilizando F# y Giraffe, una biblioteca de enrutamiento HTTP para .NET. Crearemos una API simple que permita leer y crear publicaciones de blog.

F# es un lenguaje de programación funcional que se ejecuta en la plataforma .NET, y puede ser utilizado para crear aplicaciones web y servicios de backend. La sintaxis simple y expresiva de F# lo hace ideal para construir aplicaciones concisas y eficientes. Giraffe, por otro lado, es una biblioteca de enrutamiento HTTP que se integra perfectamente con F#, lo que permite a los desarrolladores crear aplicaciones web de manera fácil y rápida.

En este tutorial, utilizaremos .NET 6, la última versión del marco de trabajo de .NET. .NET 6 ofrece un gran rendimiento y mejoras de productividad para los desarrolladores.

Comencemos con la creación de nuestra API web con F# y Giraffe en .NET 6.

1
2
dotnet new console -lang "F#" -o api
cd api

Editar el archivo api.fsproj

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18

<Project Sdk="Microsoft.NET.Sdk.Web">

<PropertyGroup>
<TargetFramework>net6.0</TargetFramework>
<RootNamespace>api</RootNamespace>
</PropertyGroup>

<ItemGroup>
<Compile Include="Blog.fs" />
<Compile Include="Program.fs" />
</ItemGroup>

<ItemGroup>
<PackageReference Include="Giraffe" Version="6.0.0" />
</ItemGroup>

</Project>

En el archivo Blog.fs, definimos un módulo Blog que contiene la lógica del blog. Primero, definimos un tipo de datos BlogPost que representa una publicación en el blog y contiene un título y un contenido.

Luego, definimos un tipo de datos BlogDb que representa una base de datos de blog y contiene una lista mutable de publicaciones. El BlogDb tiene dos miembros, uno que devuelve todas las publicaciones existentes en la base de datos y otro que agrega una nueva publicación a la lista.

A continuación, definimos dos manejadores HTTP: getPostsHttpHandler y createPostHttpHandler. El primero se encarga de manejar solicitudes GET a la ruta /posts y devuelve todas las publicaciones de blog almacenadas en la base de datos. El segundo maneja solicitudes POST a la ruta /posts/create y agrega una nueva publicación a la base de datos.

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
module Blog 

open System
open System.Threading.Tasks
open Microsoft.AspNetCore.Http
open Giraffe

[<CLIMutable>]
type BlogPost = {
title: string
content: string
}

type BlogDb() =

let mutable allBlogPosts : BlogPost list = []

member this.GetAllPosts = fun() -> allBlogPosts

member this.AddPost (newPost : BlogPost) =
allBlogPosts <- (newPost::allBlogPosts)
allBlogPosts

type BlogServiceTree = {
getBlogDb : unit -> BlogDb
}

let getPostsHttpHandler (serviceTree: BlogServiceTree) =
fun (next : HttpFunc) (ctx : HttpContext) ->
json (serviceTree.getBlogDb().GetAllPosts()) next ctx

let createPostHttpHandler (serviceTree: BlogServiceTree) =
fun (next : HttpFunc) (ctx : HttpContext) ->
task {
let! newPostJson = ctx.BindJsonAsync<BlogPost>()
serviceTree.getBlogDb().AddPost(newPostJson) |> ignore
return! json (newPostJson) next ctx
}

En el archivo Program.fs, definimos el webApp, que es un árbol de rutas de Giraffe. El árbol de rutas incluye una ruta raíz que devuelve una cadena de texto y una subruta /posts que contiene dos rutas adicionales: una para obtener todas las publicaciones del blog y otra para crear una nueva publicación.

Finalmente, configuramos la aplicación y los servicios que utilizamos. En el método configureServices, agregamos la dependencia de Giraffe y en el método configureApp, configuramos la aplicación para usar Giraffe y nuestro árbol de rutas webApp.

Para ejecutar la aplicación, simplemente escribimos dotnet run en la terminal desde el directorio del proyecto.

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
open System
open Microsoft.AspNetCore.Builder
open Microsoft.AspNetCore.Hosting
open Microsoft.Extensions.Hosting
open Microsoft.Extensions.Logging
open Microsoft.Extensions.DependencyInjection

open Blog
open Giraffe

(* Web App Configuration *)

let webApp =
let blogDb = new BlogDb()

let serviceTree = {
getBlogDb = fun() -> blogDb
}

choose[
route "/" >=> text "iamanapi"
subRoute "/posts"
(choose [
route "" >=> GET >=> warbler (fun _ ->
(getPostsHttpHandler serviceTree))
route "/create"
>=> POST
>=> warbler (fun _ ->
(createPostHttpHandler serviceTree))
])
]

(* Infrastructure Configuration *)

let configureApp (app : IApplicationBuilder) =
app.UseGiraffe (webApp)

let configureServices (services : IServiceCollection) =
// Add Giraffe dependencies
services.AddGiraffe() |> ignore

[<EntryPoint>]
let main _ =
Host.CreateDefaultBuilder()
.ConfigureWebHostDefaults(
fun webHostBuilder ->
webHostBuilder
.Configure(configureApp)
.ConfigureServices(configureServices)
|> ignore)
.Build()
.Run()
0

Ejecutar

1
dotnet run

Si todo funciona correctamente, deberíamos ver un mensaje que indica que la aplicación se está ejecutando. En este punto, podemos abrir un navegador web y acceder a la dirección http://localhost:5000/posts para ver la lista de publicaciones de nuestro blog. Podemos crear una nueva publicación enviando una solicitud POST a la dirección http://localhost:5000/posts/create con el título y el contenido de la publicación en formato JSON.

resultado en consola

Discriminated Unions

Las Discriminated Unions, también conocidas como Sum Types, son un concepto importante en programación funcional y son utilizadas para modelar tipos de datos que pueden tener varios valores posibles.

Una Discriminated Union es un tipo de dato que puede contener uno de varios valores diferentes, cada uno de los cuales está etiquetado con un identificador único o discriminador.

En términos más simples, las Discriminated Unions se utilizan para definir un tipo de dato que puede tener varios valores posibles y que se pueden distinguir entre sí mediante una etiqueta o identificador único.

Por ejemplo, supongamos que estamos escribiendo una aplicación de procesamiento de pedidos en línea. Podríamos definir un tipo de dato para representar un pedido, que podría tener varios estados posibles, como “pendiente”, “en camino” o “entregado”. Utilizando Discriminated Unions, podríamos definir este tipo de dato como:

type OrderStatus =
| Pending
| InTransit
| Delivered

Una de las ventajas de las Discriminated Unions es que nos permiten manejar tipos de datos complejos de manera clara y concisa. Por ejemplo, podríamos definir un tipo de dato para representar una figura geométrica, que podría ser un círculo, un cuadrado o un triángulo. Utilizando Discriminated Unions, podríamos definir este tipo de dato como:

type Shape =
| Circle of float
| Square of float
| Triangle of float * float

En resumen, las Discriminated Unions son una herramienta poderosa para modelar tipos de datos complejos en programación funcional. Al permitirnos definir tipos de datos que pueden tener varios valores posibles, etiquetados con identificadores únicos, nos permiten manejar estructuras de datos complejas de manera clara y concisa.

Instalar oh my posh para mejorar powershell en windows

Paso 1: Instalar PSReadLine

Install-Module PSReadLine -Scope CurrentUser

Paso 2: Instalar Terminal-Icons

Install-Module -Name Terminal-Icons -Repository PSGallery -Scope CurrentUser

Paso 3: Instalar Oh My Posh

Install-Module oh-my-posh -Scope CurrentUser

Paso 4: Instalar una fuente

Descarga e instala la fuente recomendada, Cascadia Code PL, desde el sitio web de Microsoft. o desde esta web https://www.nerdfonts.com/font-downloads

Paso 5: Configurar Oh My Posh

Abre tu perfil de PowerShell con notepad $PROFILE y agrega estas líneas al final:

oh-my-posh --init --shell pwsh --config C:/Users/User/Dropbox/Computer/AppData/powershell.json | Invoke-Expression

if ($host.Name -eq 'ConsoleHost' -or $host.Name -eq 'Visual Studio Code Host' ) {

  Import-Module PSReadline -RequiredVersion 2.2.6
  Set-PSReadLineOption -EditMode Windows
  Set-PSReadLineOption -PredictionSource History

  Set-PSReadlineOption -Color @{
    "Command"          = [ConsoleColor]::Green
    "Parameter"        = [ConsoleColor]::Gray
    "Operator"         = [ConsoleColor]::Magenta
    "Variable"         = [ConsoleColor]::Yellow
    "String"           = [ConsoleColor]::Yellow
    "Number"           = [ConsoleColor]::Yellow
    "Type"             = [ConsoleColor]::Cyan
    "Comment"          = [ConsoleColor]::DarkCyan
    "InlinePrediction" = '#70A99F'
  }

  Set-PSReadLineKeyHandler -Function AcceptSuggestion -Key 'Ctrl+Spacebar'
  Set-PSReadLineKeyHandler -Key UpArrow -Function HistorySearchBackward
  Set-PSReadLineKeyHandler -Key DownArrow -Function HistorySearchForward 

  Set-PSReadLineKeyHandler -Key Ctrl+Shift+b `
                       -BriefDescription BuildCurrentDirectory `
                       -LongDescription "Build the current directory" `
                       -ScriptBlock {
    [Microsoft.PowerShell.PSConsoleReadLine]::RevertLine()
    [Microsoft.PowerShell.PSConsoleReadLine]::Insert("dotnet build")
    [Microsoft.PowerShell.PSConsoleReadLine]::AcceptLine()
  }
}

Import-Module -Name Terminal-Icons

El resultado son iconos, la barra de direcciones reducida y autocomplete de comandos.

powershell configurado con oh my posh

Lenguaje Gherkin

El lenguaje Gherkin utiliza palabras clave especiales como “Feature”, “Scenario”, “Given”, “When” y “Then” para definir el comportamiento esperado de la aplicación. Cada “Scenario” describe un caso de prueba específico, y las instrucciones “Given”, “When” y “Then” indican las condiciones iniciales, la acción que se realiza y el resultado esperado, respectivamente.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
Feature: Sistema de nómina
El sistema de nómina permite a los empleados ver su información de pago y a los administradores de recursos humanos gestionar los pagos de los empleados.

Scenario: Ver información de pago
Given estoy conectado como un empleado
When accedo a mi página de información de pago
Then se muestran mis datos de pago, como mi salario, impuestos y deducciones

Scenario: Gestionar pagos de empleados
Given estoy conectado como un administrador de recursos humanos
When accedo a la página de gestión de pagos de empleados
Then puedo ver una lista de todos los empleados y sus respectivos datos de pago
And puedo modificar la información de pago de cualquier empleado
And puedo generar y descargar un informe de pago para cada empleado

Scenario: Generar informe de pago
Given estoy conectado como un administrador de recursos humanos
And he seleccionado un empleado en la página de gestión de pagos de empleados
When hago clic en el botón "Generar informe de pago"
Then se descarga un archivo PDF con el informe de pago del empleado seleccionado

sintaxis del lenguaje

¿Qué es Jamstack?

La palabra “Jamstack” es un acrónimo que se refiere a “JavaScript, APIs y Markup”. Se trata de un enfoque para la construcción de sitios web y aplicaciones que se basa en el uso de servicios externos para el alojamiento, el procesamiento y la entrega de contenido. En lugar de utilizar un servidor web tradicional para ejecutar el código y servir contenido dinámico, Jamstack se apoya en servicios externos (conocidos como “APIs”) para manejar estas tareas.

¿Por qué elegir Jamstack? Hay varias razones por las que el enfoque Jamstack está ganando tanto terreno en el mundo del desarrollo web. En primer lugar, es increíblemente escalable. Al utilizar servicios externos para manejar tareas como el procesamiento y el alojamiento, puedes ahorrar recursos y aumentar la velocidad de tu sitio o aplicación. Esto es especialmente importante si tienes un gran tráfico o si tu sitio o aplicación necesita soportar picos de tráfico imprevistos.

Otra ventaja de Jamstack es que es extremadamente seguro. Al no tener que preocuparte por el servidor web y la ejecución de código en el lado del servidor, hay menos oportunidades para que alguien pueda atacar tu sitio o aplicación. Además, al no tener que preocuparte por el servidor, puedes enfocarte en lo que realmente importa: el contenido y la experiencia de usuario.

Por último, Jamstack es increíblemente rápido. Al utilizar servicios externos para manejar tareas como el procesamiento y el alojamiento, puedes ahorrar recursos y aumentar la velocidad de tu sitio o aplicación. Esto es especialmente importante si tienes un gran tráfico o si tu sitio o aplicación necesita soportar picos de tráfico imprevistos.

En resumen, Jamstack es un enfoque innovador para la construcción de sitios web y aplicaciones

Emmet toolkit

Emmet es un plugin para editores que permite agilizar la escritura de codigo HTML. El proyecto fue iniciado por Vadim Makeev en 2008 y continúa siendo desarrollado activamente por los usuarios de Sergey Chikuyonok y Emmet. Desde 2015.

Esta herramienta se ha incorporado a varios editores de texto populares. Sin embargo, Emmet es principalmente independiente de cualquier editor de texto, ya que el motor funciona directamente con el texto en lugar de con cualquier software en particular.

Emmet es de código abierto bajo la licencia MIT. source code

Ejemplos

image
Para crear una estructura de html simple se utiliza el signo de exclamacion “!” y tab.

Emmet cheatsheet