Uso de VS Code y Node para escribir HTML con estilo
En este artículo, voy a explicar cómo puedes usar el poder de Visual Studio Code y Node.js para crear una herramienta de edición personalizada para superar las plataformas de blogs malhumoradas y simplemente escribir Markdown.
Los detalles de este artículo se adaptan al problema que teníamos, pero la estrategia involucrada aquí se puede aplicar a una gran cantidad de tareas en las que le gustaría usar Markdown para crear contenido para un sistema heredado o crear otros flujos de trabajo de editor personalizados aún más interesantes. Por ejemplo, supongamos que desea crear HTML para incluirlo en un correo electrónico, está muy limitado en cuanto al marcado que puede usar.

El problema
Infragistics ha estado proporcionando foros y blogs durante mucho tiempo y, como suele suceder cuando necesita mantener una gran biblioteca de contenido accesible, está ejecutando un software de blogs bastante antiguo que funciona pero no es necesariamente muy agradable de trabajar. Los principales vectores para introducir contenido en el sistema incluyen:
- Trabajar con un editor WYSIWYG basado en la web con errores que no admite muy bien la introducción de fragmentos de código y que redondea incorrectamente el contenido existente, lo que hace que el contenido cambie de forma impredecible si lo vuelve a editar.
- El uso de un botón en dicho editor WYSIWYG basado en la web con errores para inyectar código HTML literal, lo que obliga al HTML inyectado en formularios a veces con errores, presumiblemente para evitar patrones inseguros, pero, en general, da como resultado que las cosas no se vean en absoluto como el autor pretendía.
- Trabajando en magia negra, y luchando contra los turbios sistemas de inicio de sesión único, para conectar Windows Live Writer (ahora Open Live Writer) a la interfaz del servicio web e inyectar contenido desde allí, solo para encontrar que el motor del blog todavía destroza el HTML enviado de maneras indeseables.
Más allá de la dificultad de introducir el contenido en el sistema, está el hecho de que el contenido de Infragistics blog está cargado de fragmentos de código. Lo ideal es que se vean bien y que se resalten la sintaxis.
If you use a snippet storage service like Github Gist, you may think this part is easy, but our issue is that out Blog software specifically suppresses and removes iframe elements from the post content. I believe this is a security conceit due to the engine being inadequately prepared to delineate between post content and comment content as far as the editing is concerned, and you wouldn’t want your comment writers injecting iframes!
Unfortunately, most decent external snippet software would like to load the snippet into an iframe, or need custom JavaScript to run (another no-no as far as our blog engine is concerned).
For a while, we were running some JavaScript at the site level which would apply some syntax highlighting to pre tags that were formatted in a very precise way, but this JavaScript kept going missing every time our Website team did some major revisions to the site, causing all the articles relying on it to suddenly look atrocious. When this went missing for the most recent time, I started cooking up this solution.
Por último, trabajar en un editor WYSIWYG en un portal de blogs propietario no te ofrece mucho en cuanto a formas de revisar e iterar el contenido antes de que se publique. Idealmente, alguien debería poder pedir la revisión de una publicación de blog a sus compañeros o editores antes de publicarla. La aproximación más cercana que tuvimos a esto fue publicar un borrador de publicación no listado y solicitar comentarios. Sin embargo, no había forma de anotar y comentar el contenido en línea, como se puede, por ejemplo, en un archivo Markdown almacenado en git con cambios realizados mediante un flujo de trabajo de solicitud de incorporación de cambios.
Ahora, afortunadamente, estoy seguro de que pronto actualizaremos nuestro software de blogs, a un motor mucho más reciente, por lo que este es un problema a corto plazo que estoy abordando (con suerte), pero esta solución a corto plazo no fue tan difícil de crear, puede seguir siendo relevante después de la actualización y ofrece una gran estrategia para crear flujos de trabajo de edición personalizados para sortear problemas similares al nuestro. Muchos de nuestros ingenieros bloguearían menos o no bloguearían en absoluto debido a las barreras para la creación y la iteración del contenido, reducir esas barreras fomenta más contenido.
El plan
Bien, vamos a resumir los problemas a superar:
- Hacer cualquier cosa que no sea inyectar HTML literal en el sistema actual es bastante difícil, e incluso esto debe hacerse con cuidado, ya que hay muchos patrones HTML que el sistema ignorará, destruirá o tergiversará.
- Trabajar con el editor WYSIWYG del motor de blogs o incluso con Open Live Writer es un gran dolor, especialmente cuando se trabaja con fragmentos de código o se itera en el contenido.
- Necesitamos fragmentos de código que se vean bien y que no exploten cuando se pierda algún JavasScript externo.
- Necesitamos alguna forma de tener un flujo de trabajo de revisión para el contenido.
Y aquí hay un plan para resolver los problemas:
- VS Code es gratuito y tiene una excelente vista previa de Markdown en paralelo al editar un archivo de Markdown.
- Node.js y trago nos permiten iniciar una tarea en segundo plano para convertir continuamente nuestros archivos Markdown a HTML, siempre que se guarden.
- Debido a que podemos ejecutar JavaScript arbitrario como parte de las canalizaciones de gulp, deberíamos poder realizar trabajo adicional para manipular el HTML producido para que el software de blogs lo acepte con gracia.
- Si la experiencia de edición es solo para iterar en archivos Markdown en VS Code, deberíamos poder almacenar esos archivos Markdown en git y usar un flujo de trabajo de revisión de solicitud de incorporación de cambios estándar para crear un proceso de revisión para nuestras publicaciones.
La solución
OK, first we need to make sure that we have Visual Studio Code and Node.js installed as they’ll be the main workhorses in this workflow. Once they are installed, we need to create a directory that will house the markdown files e.g. c:\Blogging and open VS Code pointing at that folder.
First create an empty package.json file in the directory just containing:
{ }
Ahora tendremos que ejecutar un montón de comandos en la consola en el contexto de la carpeta, así que abra el terminal integrado a través de:
View => Integrated Terminal
A continuación, debemos instalar globalmente markdown-it, que es el paquete Node.js que usaremos para convertir Markdown a HTML:
npm install -g markdown-it
A continuación, necesitamos instalar gulp global y localmente, y algunas extensiones de gulp, que ayudarán a administrar el flujo de trabajo de conversión de los archivos markdown a HTML:
npm install -g gulp npm install gulp gulp-markdown-it --save
This much should be sufficient to allow us to write a gulpfile.js that will continuously convert the Markdown files in our directory into HTML as they are saved, and to kick off the process with a Ctrl + Shift + B via some VS Code magic.
First, we’ll create a test Markdown file called test.md in the folder, and give it some content:
## Introduction
This is an introduction.
This is another paragraph.
```cs
//this is a gated code block
public class TestClass
{
public void TestMethod()
{
}
}
```
You can open the Markdown Preview with CTRL-K V and view the preview alongside the file you are editing:

Now, we can create the gulp configuration that will set up the conversion workflow. Create a file in the directory called gulpfile.js and fill it with this content:
var gulp = require('gulp');
var markdown = require('gulp-markdown-it');
var fs = require('fs');
gulp.task('markdown', function() {
return gulp.src(['**/*.md', '!node_modules/**'])
.pipe(markdown({
options: {
html: true
}
}))
.pipe(gulp.dest(function(f) {
return f.base;
}));
});
gulp.task('default', ['markdown'], function() {
gulp.watch(['**/*.md', '!node_modules/**'], ['markdown']);
});
Con ese archivo guardado, deberíamos poder ejecutar gulp y ver los resultados, por lo que desde el terminal integrado ejecutemos:
gulp

This results in a file called test.html being created in our directory with this content:
<h2>Introduction</h2>
<p>This is an introduction.</p>
<p>This is another paragraph.</p>
<pre><code class="language-cs">//this is a gated code block
public class TestClass
{
public void TestMethod()
{
}
}
</code></pre>
De la forma en que lo tenemos configurado, gulp continuará vigilando cualquier cambio en los archivos Markdown en este directorio (o subdirectorios), y si alguno de ellos cambia, los alimentará a través de markdown-it para producir nuevo contenido HTML en un archivo html con el mismo nombre que el archivo Markdown. Si hacemos un cambio en el archivo Markdown:
## Introduction
This is an introduction.
This is another paragraph.
```cs
//this is a gated code block
public class TestClass2
{
public void TestMethod2()
{
}
}
```
Here TestClass has been changed to read TestClass2 and TestMethod has been changed to read TestMethod2. After hitting save, and waiting a moment, test.html now contains:
<h2>Introduction</h2>
<p>This is an introduction.</p>
<p>This is another paragraph.</p>
<pre><code class="language-cs">//this is a gated code block
public class TestClass2
{
public void TestMethod2()
{
}
}
</code></pre>
This is neat, but since we are using VS Code, we can even avoid needing to run the gulp command to get things started. All we need to do is create a tasks.json file in the .vscode sub folder in the project directory and provide this content:
{
"version": "2.0.0",
"tasks": [
{
"type": "gulp",
"task": "default",
"problemMatcher": [],
"group": {
"kind": "build",
"isDefault": true
}
}
]
}
This makes it so that when you press CTRL + SHIFT + B, it will start running the gulp command in the background. In this way, we are combining the markdown editor in VS Code, including its really spiffy preview pane, with Node.js, markdown-it, and gulp in order to create an awesome HTML editor.
Domando al malhumorado motor de blogs
Ahora, si no estás lidiando con algunos motores de blogs malhumorados, como nosotros, lo anterior puede ser todo lo que necesitarás. Pero incluso si es así, puede que el resto de esto te resulte interesante y útil. Estos son los problemas con nuestro motor de blogs que debemos abordar:
- Preferiríamos renderizar fragmentos de código a html de estilo estático sin hojas de estilo JS o CSS externas.
- Our blogging engine messes with tabs embedded in
pretags (why, I have no idea). - Our blogging engine fails to round trip line breaks within
pretags, often dropping them, so its safer to convert line breaks to explicit break tags or things will get messed up if we republish our HTML. - Our blogging engine likes to destroy whitespace at the start of lines even inside
pretags, so we’d like to convert those to non breaking spaces ( )
So, first, we’ll deal with highlighting the code snippets and sanitizing the problematic HTML within the pre tags. We’ll use a node package called highlightjs to format the gated code blogs in the Markdown into some syntax highlighted markup. So let’s install that first:
npm install highlightjs --save
Once that is installed, we can modify the gulpfile.js to look like this:
var gulp = require('gulp');
var markdown = require('gulp-markdown-it');
//new
var hljs = require('highlightjs/highlight.pack.js');
var fs = require('fs');
//new
hljs.configure({
tabReplace: ' '
});
gulp.task('markdown', function() {
return gulp.src(['**/*.md', '!node_modules/**'])
.pipe(markdown({
options: {
html: true,
//new
highlight: function (str, lang) {
if (lang && hljs.getLanguage(lang)) {
try {
var output = hljs.highlight(lang, str).value;
output = output.replace(/(?:\r\n|\r|\n)/g, '<br />');
output = output.replace(/^\s+/gm, function(m){ return m.replace(/\s/g, ' ');});
output = output.replace(/(\<br\s+\/\>)(\s+)/gm, function(m){ return m.replace(/\>(\s+)/g, function (n) { return n.replace(/\s/g, ' '); } ); });
return '<pre class="hljs"><code>' +
output +
'</code></pre>';
} catch (e) {}
}
return '';
}
}
}))
.pipe(gulp.dest(function(f) {
return f.base;
}));
});
gulp.task('default', ['markdown'], function() {
gulp.watch(['**/*.md', '!node_modules/**'], ['markdown']);
});
En lo anterior:
- Add a
requirestatement so that we can use the highlightjs library. - Configure la biblioteca highlightjs para usar 4 espacios que no se separen en lugar del carácter de tabulación.
- Use el enlace highlight al ejecutar markdown-it para especificar cómo se debe realizar el resaltado de sintaxis para los bloques de código cercados. En este caso, invocamos highlightjs para hacer el resaltado.
- Además de transformar los bloques de código cercados, realizamos una serie de reemplazos de expresiones regulares en el contenido de salida para eliminar patrones de caracteres que resultan problemáticos para el motor de blogs y reemplazarlos con secuencias de caracteres equivalentes más seguras.
Debe reiniciar la tarea de trago en este punto:
F1 => Terminate Running Task
And then restart the task with CTRL + SHIFT + B. At this point your test.html should read like this:
<h2>Introduction</h2>
<p>This is an introduction.</p>
<p>This is another paragraph.</p>
<pre><code class="language-cs"><pre class="hljs"><code><span class="hljs-comment">//this is a gated code block</span><br /><span class="hljs-keyword">public</span> <span class="hljs-keyword">class</span> <span class="hljs-title">TestClass2</span><br />{<br /> <span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">void</span> <span class="hljs-title">TestMethod2</span>(<span class="hljs-params"></span>)<br /> </span>{<br /> <br /> }<br />}<br /></code></pre></code></pre>
Es bastante feo con todos los saltos de línea eliminados de la etiqueta pre, pero ahora debería funcionar mejor con el motor de blogs.
Tenemos dos cuestiones finales que nos gustaría abordar antes de que podamos dar por hecho esto. En primer lugar, nos gustaría que todos los enlaces se abran automáticamente en una nueva pestaña, según las solicitudes de nuestros equipos de marketing y sitios web. En segundo lugar, el HTML resultante actual espera que se cargue una hoja de estilo CSS para que el HTML de salida se coloree realmente para los fragmentos de código. Si tuviéramos que mirar la salida en un navegador, se vería así:

Como mencioné antes, no podemos inyectar ningún CSS por publicación en los artículos de nuestro blog, por lo que preferimos que todo el color de la sintaxis se hornee directamente en el HTML en línea.
Thankfully, for both of these issues, there are some simple to use node packages to fix things up. First, we can install a package called markdown-it-link-target which is a plugin for markdown-it that will cause links to render in such a way that they will open in a new tab:
npm install markdown-it-link-target --save
A continuación, instalaremos un complemento para gulp que invocará un paquete de nodo llamado juice:
npm install gulp-juice --save
juice es una biblioteca ordenada que tomará una hoja de estilo CSS y algo de HTML, y luego horneará los estilos CSS en el HTML en línea para que el CSS externo ya no sea necesario. Para conectar estas piezas, nuevamente necesitamos actualizar nuestro gulpfile y luego reiniciar la tarea:
var gulp = require('gulp');
var markdown = require('gulp-markdown-it');
var hljs = require('highlightjs/highlight.pack.js');
//new
var juice = require('gulp-juice');
var fs = require('fs');
//new
var codeCss = fs.readFileSync("./node_modules/highlightjs/styles/atom-one-dark.css", "utf-8");
hljs.configure({
tabReplace: ' '
});
gulp.task('markdown', function() {
return gulp.src(['**/*.md', '!node_modules/**'])
.pipe(markdown({
//new
plugins: ["markdown-it-link-target"],
options: {
html: true,
highlight: function (str, lang) {
if (lang && hljs.getLanguage(lang)) {
try {
var output = hljs.highlight(lang, str).value;
output = output.replace(/(?:\r\n|\r|\n)/g, '<br />');
output = output.replace(/^\s+/gm, function(m){ return m.replace(/\s/g, ' ');});
output = output.replace(/(\<br\s+\/\>)(\s+)/gm, function(m){ return m.replace(/\>(\s+)/g, function (n) { return n.replace(/\s/g, ' '); } ); });
return '<pre class="hljs"><code>' +
output +
'</code></pre>';
} catch (e) {}
}
return '';
}
}
}))
//new
.pipe(juice({ extraCss: codeCss }))
.pipe(gulp.dest(function(f) {
return f.base;
}));
});
gulp.task('default', ['markdown'], function() {
gulp.watch(['**/*.md', '!node_modules/**'], ['markdown']);
});
With these changes, test.html should now look like this:
<h2>Introduction</h2>
<p>This is an introduction.</p>
<p>This is another paragraph.</p>
<pre><code class="language-cs"><pre class="hljs" style="background: #282c34; color: #abb2bf; display: block; overflow-x: auto; padding: 0.5em;"><code><span class="hljs-comment" style="color: #5c6370; font-style: italic;">//this is a gated code block</span><br><span class="hljs-keyword" style="color: #c678dd;">public</span> <span class="hljs-keyword" style="color: #c678dd;">class</span> <span class="hljs-title" style="color: #61aeee;">TestClass2</span><br>{<br> <span class="hljs-function"><span class="hljs-keyword" style="color: #c678dd;">public</span> <span class="hljs-keyword" style="color: #c678dd;">void</span> <span class="hljs-title" style="color: #61aeee;">TestMethod2</span>(<span class="hljs-params"></span>)<br> </span>{<br> <br> }<br>}<br></code></pre></code></pre>
En lo anterior, esta línea:
var codeCss = fs.readFileSync("./node_modules/highlightjs/styles/atom-one-dark.css", "utf-8");
carga uno de los archivos CSS que se envían con HighlightJS y lo incorpora con el HTML.
Ahora, esto simplemente se puede pegar como HTML sin procesar en casi cualquier cosa que admita HTML, ¡incluidos los motores de blogs malhumorados!
Si lo cargáramos en el navegador en este punto se vería así:

¡Espero que te haya resultado interesante y/o útil!