Ir para o conteúdo
Usando o VS Code e o Node para escrever HTML com estilo

Usando o VS Code e o Node para escrever HTML com estilo

Neste artigo, explicarei como você pode usar o poder do Visual Studio Code e do Node.js para criar uma ferramenta de edição personalizada para superar plataformas de blog teimosas e apenas escrever Markdown.

15min read

As especificidades deste artigo são adaptadas ao problema que estávamos tendo, mas a estratégia envolvida aqui pode ser aplicada a um grande número de tarefas em que você gostaria de usar o Markdown para criar algum conteúdo para um sistema legado ou criar outros fluxos de trabalho de editor personalizados ainda mais interessantes. Por exemplo, digamos que você queira criar algum HTML para inclusão em um e-mail, você está fortemente limitado quanto à marcação que pode usar.

Usando o VS Code e o Node para escrever HTML com estilo (embutido)!

O problema

Infragistics fornece fóruns e blogs há muito tempo e, como costuma acontecer quando você precisa manter uma grande biblioteca de conteúdo acessível, está executando um software de blog bastante antigo que funciona, mas não é necessariamente muito agradável de se trabalhar. Os principais vetores para colocar conteúdo no sistema incluem:

  • Trabalhar com um editor WYSIWYG baseado na web com bugs que não suporta muito bem a inserção de trechos de código e faz viagens incorretas ao conteúdo existente, fazendo com que o conteúdo mude de maneiras imprevisíveis se você editá-lo novamente.
  • Usando um botão no referido editor WYSIWYG baseado na web com bugs para injetar código HTML literal, que coage o HTML injetado em formas às vezes com bugs, presumivelmente para evitar padrões inseguros, mas, geralmente, resulta em coisas que não parecem nada como o autor pretendia.
  • Trabalhando com magias negras e lutando contra o obscuro logon único em sistemas para conectar o Windows Live Writer (agora Open Live Writer) à interface do serviço Web e injetar conteúdo de lá, apenas para descobrir que o mecanismo do blog ainda está mutilando o HTML enviado de maneiras indesejáveis.

Além da dificuldade de realmente colocar o conteúdo no sistema, há o fato de que Infragistics conteúdo do blog é pesado no uso de trechos de código. Idealmente, eles devem ter uma boa aparência e realce de sintaxe.

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.

Finalmente, trabalhar em um editor WYSIWYG em um portal de blog proprietário não oferece muito em maneiras de revisar e iterar o conteúdo antes de ir ao ar. Idealmente, alguém deve ser capaz de pedir a revisão de uma postagem de blog de colegas ou editores de texto antes de publicar. A aproximação mais próxima que tivemos disso foi postar um rascunho de postagem não listado e solicitar feedback. No entanto, não havia como anotar e comentar o conteúdo embutido, como você pode dizer, um arquivo markdown armazenado no git com alterações feitas usando um fluxo de trabalho de solicitação de pull.

Agora, felizmente, tenho certeza de que estamos atualizando nosso software de blog em breve, para um mecanismo muito mais recente, então esse é um problema de curto prazo que estou abordando (espero), mas essa solução de curto prazo não foi tão difícil de criar, pode continuar a ser relevante após a atualização e oferece uma ótima estratégia para criar fluxos de trabalho de edição personalizados para contornar problemas semelhantes aos nossos. Muitos de nossos engenheiros blogariam menos ou não blogariam devido às barreiras à criação e iteração do conteúdo, reduzir essas barreiras incentiva mais conteúdo.

O Plano

Ok, vamos resumir os problemas a serem superados:

  • Fazer qualquer coisa além de injetar HTML literal no sistema atual é muito difícil, e mesmo isso precisa ser feito com cuidado, pois há muitos padrões HTML que o sistema ignorará, destruirá ou deturpará.
  • Trabalhar com o editor WYSIWYG do mecanismo de blog ou mesmo com o Open Live Writer é uma grande dor, especialmente ao trabalhar com trechos de código ou iterar no conteúdo.
  • Precisamos de trechos de código que tenham uma boa aparência e que não explodam quando algum JavasScript externo desaparece.
  • Precisamos de alguma maneira de ter um fluxo de trabalho de revisão para o conteúdo.

E aqui está um plano para resolver os problemas:

  • O VS Code é gratuito e tem uma ótima visualização de Markdown lado a lado ao editar um arquivo markdown.
  • Node.js e gulp nos permitem iniciar uma tarefa em segundo plano para converter continuamente nossos arquivos Markdown em HTML, sempre que eles são salvos.
  • Como podemos executar JavaScript arbitrário como parte dos pipelines gulp, devemos ser capazes de realizar um trabalho adicional para manipular o HTML produzido para que o software de blog o aceite normalmente.
  • Se a experiência de edição for apenas iterar em arquivos Markdown no VS Code, devemos ser capazes de armazenar esses arquivos Markdown no git e usar um fluxo de trabalho de revisão de solicitação de pull padrão para criar um processo de revisão para nossas postagens.

A solução

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:

{ }

Agora precisaremos executar um monte de comandos no console no contexto da pasta, então abra o terminal integrado via:

View => Integrated Terminal

Em seguida, precisamos instalar globalmente o markdown-it, que é o pacote Node.js que usaremos para converter o Markdown para HTML:

npm install -g markdown-it

Em seguida, precisamos instalar o gulp global e localmente, e algumas extensões gulp, que ajudarão a gerenciar o fluxo de trabalho de conversão dos arquivos markdown para 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']);
});

Com esse arquivo salvo, devemos ser capazes de executar o gulp e ver os resultados, portanto, a partir do terminal integrado, execute:

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>

Da maneira como o configuramos, o gulp continuará a observar quaisquer alterações nos arquivos Markdown neste diretório (ou subdiretórios) e, se algum deles for alterado, ele os alimentará por meio do markdown-it para produzir novo conteúdo HTML em um arquivo html com o mesmo nome do arquivo Markdown. Se fizermos uma alteração no arquivo 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 o mecanismo de blog teimoso

Agora, se você não está lidando com alguns mecanismos de blog teimosos, como nós, o acima pode ser tudo o que você precisa. Mas mesmo assim, você pode achar o resto interessante e útil. Aqui estão os problemas com nosso mecanismo de blog que precisamos resolver:

  • Preferimos renderizar trechos de código para html de estilo estático, dependendo de nenhuma folha de estilo JS ou CSS externa.
  • Our blogging engine messes with tabs embedded in pre tags (why, I have no idea).
  • Our blogging engine fails to round trip line breaks within pre tags, 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 pre tags, so we’d like to convert those to non breaking spaces (&nbsp)

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: '&nbsp;&nbsp;&nbsp;&nbsp;'
});

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, '&nbsp;');});
                            output = output.replace(/(\<br\s+\/\>)(\s+)/gm, function(m){ return m.replace(/\>(\s+)/g, function (n) { return n.replace(/\s/g, '&nbsp;'); } ); });
                            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']);
});

No acima, nós:

  • Add a require statement so that we can use the highlightjs library.
  • Configure a biblioteca highlightjs para usar 4 espaços não separáveis em vez do caractere de tabulação.
  • Use o gancho de destaque ao executar markdown-it para especificar como o realce de sintaxe deve ser executado para blocos de código protegidos. Nesse caso, invocamos highlightjs para fazer o realce.
  • Além de transformar os blocos de código cercados, realizamos uma série de substituições de expressões regulares no conteúdo de saída para remover padrões de caracteres que se mostram problemáticos para o mecanismo de blog e substituí-los por sequências equivalentes mais seguras de caracteres.

Você deve reiniciar a tarefa gulp neste ponto:

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 />&nbsp;&nbsp;&nbsp;&nbsp;<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 />&nbsp;&nbsp;&nbsp;&nbsp;</span>{<br />&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<br />&nbsp;&nbsp;&nbsp;&nbsp;}<br />}<br /></code></pre></code></pre>

É muito feio com todas as quebras de linha removidas da tag pre, mas agora deve funcionar mais bem com o mecanismo de blog.

Temos duas questões finais que gostaríamos de abordar antes de podermos encerrar isso. Primeiro, gostaríamos que todos os links fossem abertos automaticamente em uma nova guia, de acordo com as solicitações de nosso site/equipes de marketing. Em segundo lugar, o HTML resultante atual espera que uma folha de estilo CSS seja carregada para que o HTML de saída seja realmente colorido para os snippets de código. Se fôssemos olhar para a saída em um navegador, ficaria assim:

o HTML de saída seja realmente colorido para os snippets de código. Se olhássemos para a saída em um navegador, ficaria assim

Como mencionei antes, não podemos injetar nenhum CSS por postagem em nossos artigos de blog, então preferimos que toda a coloração da sintaxe seja incorporada diretamente no HTML embutido.

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

Em seguida, instalaremos um plugin para gulp que invocará um pacote de nó chamado juice:

npm install gulp-juice --save

juice é uma biblioteca elegante que pegará uma folha de estilo CSS e um pouco de HTML e, em seguida, assará os estilos CSS no HTML embutido para que o CSS externo não seja mais necessário. Para conectar essas peças, precisamos atualizar novamente nosso gulpfile e reiniciar a tarefa:

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: '&nbsp;&nbsp;&nbsp;&nbsp;'
});
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, '&nbsp;');});
                            output = output.replace(/(\<br\s+\/\>)(\s+)/gm, function(m){ return m.replace(/\>(\s+)/g, function (n) { return n.replace(/\s/g, '&nbsp;'); } ); });
                            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>&nbsp;&nbsp;&nbsp;&nbsp;<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>&nbsp;&nbsp;&nbsp;&nbsp;</span>{<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<br>&nbsp;&nbsp;&nbsp;&nbsp;}<br>}<br></code></pre></code></pre>

No anterior, esta linha:

var codeCss = fs.readFileSync("./node_modules/highlightjs/styles/atom-one-dark.css", "utf-8");

carrega um dos arquivos css que acompanham o highlightjs e o incorpora com o HTML.

Agora, isso pode simplesmente ser colado como HTML bruto em praticamente qualquer coisa que suporte HTML, incluindo mecanismos de blog teimosos!

Se fôssemos carregá-lo no navegador neste momento, ficaria assim:

Se fôssemos carregá-lo no navegador neste momento, ficaria assim

Espero que você tenha achado isso interessante e / ou útil!

Solicite uma demonstração