コンテンツへスキップ
VS Code と Node を使用してスタイル付きの HTML を記述する

VS Code と Node を使用してスタイル付きの HTML を記述する

この記事では、Visual Studio Code と Node.js の機能を使用して、ブログ作成プラットフォームを克服し、Markdown を記述するだけのカスタム編集ツールを構築する方法について説明します。

15min read

この記事の詳細は、私たちが抱えていた問題に合わせて調整されていますが、ここで説明する戦略は、Markdown を使用してレガシ システムのコンテンツを作成したり、さらに興味深いカスタム エディター ワークフローを作成したりする多数のタスクに適用できます。たとえば、メールに含めるために HTML を作成したい場合、使用できるマークアップには大きな制約があります。

VS CodeとNodeを使ってスタイルのあるHTMLを書く(Baked-in)!

問題を

Infragisticsは、長い間フォーラムやブログを提供してきましたが、コンテンツの大規模なバックライブラリをアクセス可能に保つ必要がある場合によくあることですが、機能しているが必ずしも作業が快適ではないかなり古いブログソフトウェアを実行しています。コンテンツをシステムに取り込むための主なベクトルには、次のものがあります。

  • バグのあるWebベースのWYSIWYGエディターで作業していると、コードスニペットの入力がうまくサポートされておらず、既存のコンテンツが誤ってラウンドトリップされるため、再度編集するとコンテンツが予測できない方法で変更されます。
  • 上記のバグのあるWebベースのWYSIWYGエディタのボタンを使用してリテラルHTMLコードを挿入し、挿入されたHTMLをバグのあるフォームに強制します。これはおそらく安全でないパターンを防ぐためですが、一般的には、作者が意図したようにはまったく見えません。
  • Windows Live Writer (現在のOpen Live Writer) を Web サービス インターフェイスに接続し、そこからコンテンツを注入するために、闇の魔法を働かせ、曖昧なシングル サインオン システムと戦っていますが、ブログ エンジンが提出された HTML を望ましくない方法で操作していることに気付くだけです。

実際にコンテンツをシステムに取り込むことの難しさに加えて、Infragistics のブログ コンテンツはコード スニペットの使用に重点が置かれているという事実があります。これらは、理想的には、見栄えがよく、構文が強調表示されている必要があります。

Github Gistのようなスニペットストレージサービスを使っているなら、この部分は簡単だと思うかもしれませんが、私たちの問題はブログソフトウェアが投稿内容からiframe要素を抑制・削除していることです。これは、編集に関して投稿内容とコメント内容を十分に区別できていないため、セキュリティ上の仕掛けだと思います。コメント作成者がiframeを挿入するのは避けたいでしょう!

残念ながら、まともな外部スニペットソフトの多くはスニペットをiframeに読み込もうとしたり、実行するためにカスタムJavaScriptが必要になるでしょう(これは私たちのブログエンジンに関してはこれもタブーです)。

しばらくの間、サイトレベルでJavaScriptを使って、非常に正確なフォーマットでタグpreに構文ハイライトを適用していましたが、ウェブサイトチームが大幅な改訂を行うたびにこのJavaScriptが消えてしまい、それに依存する記事が急にひどく見えてしまいました。最近この方法が消えたとき、私はこの解決策を考え始めました。

最後に、独自のブログポータルでWYSIWYGエディターで作業すると、公開前にコンテンツをレビューして反復する方法があまりありません。理想的には、誰かが公開する前に、同僚やコピーエディターにブログ記事のレビューを依頼できる必要があります。これに最も近い近似は、限定公開の下書き投稿を投稿し、フィードバックを求めることでした。しかし、コンテンツにインラインで注釈を付けたりコメントしたりする方法はありませんでした。たとえば、プルリクエストワークフローを使用して行われた変更を含むgitに保存されたマークダウンファイルのように。

さて、ありがたいことに、私たちはブログソフトウェアをすぐに、はるかに新しいエンジンに更新することが確実ですので、これは私が対処している短期的な問題です(願いが、この短期的な解決策を作成するのはそれほど難しくなく、更新後も関連性があり続ける可能性があり、カスタム編集ワークフローを作成するための優れた戦略を提供します私たちの問題と同様の問題を回避します。当社のエンジニアの多くは、コンテンツのオーサリングとイテレーションの障壁のために、ブログを書くことが少なくなるか、まったくブログを書かなくなりますが、それらの障壁を下げることで、より多くのコンテンツが促進されます。

プラン

では、克服すべき問題を要約しましょう。

  • 現在のシステムにリテラルHTMLを注入する以外のことはかなり難しく、システムが無視したり、破壊したり、誤って表現したりするHTMLパターンが多数あるため、これも慎重に行う必要があります。
  • ブログエンジンのWYSIWYGエディターやOpen Live Writerでの作業は、特にコードスニペットを操作したり、コンテンツを反復処理したりする場合、大きな苦痛です。
  • 見栄えがよく、外部のJavasScriptが失われても爆発しないコードスニペットが必要です。
  • コンテンツのレビューワークフローを持つための何らかの方法が必要です。

そして、問題を解決するための計画は次のとおりです。

  • VS Codeは無料で、Markdownファイルを編集するときには、Markdownプレビューを並べて表示することができます。
  • Node.jsとgulpを使用すると、Markdownファイルが保存されるたびにHTMLに継続的に変換するためのバックグラウンドタスクを開始できます。
  • gulp パイプラインの一部として任意の JavaScript を実行できるため、生成された HTML を操作するための追加の作業を実行して、ブログ ソフトウェアがそれを適切に受け入れることができるはずです。
  • 編集エクスペリエンスが VS Code で Markdown ファイルを反復処理するだけの場合は、それらの Markdown ファイルを Git に保存し、標準のプルリクエストレビューワークフローを使用して投稿のレビュープロセスを作成できるはずです。

ソリューション

まずはVisual Studio CodeSearch Node.jsインストールしていることを確認する必要があります。これらはこのワークフローの主な作業場となります。インストール後は、マークダウンファイル(例:c:\Blogging VS Codeをそのフォルダに向けて開くディレクトリを作成する必要があります。

まず、ディレクトリ内に以下だけを含む空のpackage.jsonファイルを作成します。

{ }

次に、フォルダのコンテキストでコンソールで多数のコマンドを実行する必要があるため、次のように統合ターミナルを開きます。

View => Integrated Terminal

次に、MarkdownをHTMLに変換するために使用するNode.jsパッケージであるmarkdown-itをグローバルにインストールする必要があります。

npm install -g markdown-it

次に、gulpをグローバルおよびローカルにインストールし、マークダウンファイルをHTMLに変換するワークフローを管理するのに役立ついくつかのgulp拡張機能をインストールする必要があります。

npm install -g gulp
npm install gulp gulp-markdown-it --save

これだけで十分な量で、ディレクトリ内のMarkdownファイルを保存中にHTMLに継続的に変換し、VS Codeの魔法でプロセスを始めるgulpfile.jsCtrl + Shift + Bに十分でしょう。

まず、フォルダ内のtest.mdというテスト用Markdownファイルを作成し、いくつかの内容を付与します。

## Introduction
This is an introduction.

This is another paragraph.

```cs
//this is a gated code block
public class TestClass
{
    public void TestMethod()
    {

    }
}
```

Markdownプレビューを開いて、編集中のファイルと一緒にプレビューを閲覧できますCTRL-K V:

これで、変換ワークフローを設定するためのgulp設定を作成できます。ディレクトリgulpfile.jsにファイルを作成し、以下の内容で満たしてください:

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']);
});

そのファイルを保存すると、gulpを実行して結果を確認できるはずなので、統合されたターミナルから次のコマンドを実行します。

gulp

その結果、ディレクトリに「test.htmlBeing が作成されます」というファイルが作成され、以下の内容が入ります。

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

私たちが設定した方法では、gulp はこのディレクトリ (またはサブディレクトリ) 内の Markdown ファイルへの変更を監視し続け、それらのいずれかが変更された場合は、markdown-it を介してこれらをフィードし、Markdown ファイルと同じ名前の html ファイルに新しい HTML コンテンツを生成します。Markdownファイルに変更を加えると、次のようになります。

## Introduction
This is an introduction.

This is another paragraph.

```cs
//this is a gated code block
public class TestClass2
{
    public void TestMethod2()
    {

    }
}
```

ここではTestClassがreadに変更されましたTestClass2およびTestMethodはreadに変更されましたTestMethod2。セーブを押して少し待った後、test.html現在は以下の内容を含みます:

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

これは面白いですが、VS Codeを使っているので、gulpコマンドを実行しずに作業を始めることも可能です。プロジェクトディレクトリの thetasks.json sub フォルダに.vscodeファイルを作成し、次のコンテンツを提供するだけです。

{
    "version": "2.0.0",
    "tasks": [
        {
            "type": "gulp",
            "task": "default",
            "problemMatcher": [],
            "group": {
                "kind": "build",
                "isDefault": true
            }
        }
    ]
}

これにより、CTRL + SHIFT + Bを押すとバックグラウンドでgulpコマンドが実行され始めます。このようにして、VS Codeのマークダウンエディター(そのとても洗練されたプレビューペインを含む)と、Node.js、マークダウンイット、Gulpを組み合わせて、素晴らしいHTMLエディタを作り上げています。

Orneryブログエンジンを使いこなす

さて、あなたが私たちのようにいくつかの孤独なブログエンジンを扱っていない場合、上記はあなたが必要とするすべてかもしれません。しかし、たとえそうであっても、この残りの部分は興味深く、役に立つかもしれません。ここでは、ブログエンジンの問題点をご紹介します。

  • 私たちは、外部のJSやCSSスタイルシートがない状況に応じて、コードスニペットを静的なスタイルのhtmlにレンダリングすることを好みます。
  • 私たちのブログエンジンはpretagに埋め込まれたタブをいじっています(なぜかは全くわかりません)。
  • 私たちのブログエンジンはpretag内での行改りを往復できず、しばしば省略してしまうため、行改行を明示的な区切りタグに変換しないとHTMLを再公開すると問題が起きてしまいます。
  • 私たちのブログエンジンは、タグpreの内側でも行の先頭の空白を潰しがちなので、それらを区切りのないスペース(&nbsp)に変換したいと考えています

まずはコードのスニペットをハイライトし、タグ内の問題のあるHTMLをサニティ化することpre。highlightjsというノードパッケージを使って、Markdown内のゲート付きコードブログを構文でハイライトされたマークアップにフォーマットします。では、まずそれをインストールしましょう:

npm install highlightjs --save

それが取り付けられたら、gulpfile.jsを次のように修正できます:

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']);
});

上記では、次のことを行います。

  • highlightjsライブラリを使えるようにrequireステートメントを追加してください。
  • highlightjsライブラリを設定して、タブ文字の代わりに4つの改行しないスペースを使用します。
  • markdown-it を実行するときに highlight フックを使用して、フェンスされたコードブロックに対して構文の強調表示を実行する方法を指定します。この例では、highlightjs を呼び出して強調表示を行います。
  • フェンスされたコードブロックを変換するだけでなく、出力コンテンツに対して一連の正規表現置換を実行して、ブログエンジンにとって問題となる文字のパターンを削除し、それらを同等の安全な文字シーケンスに置き換えます。

この時点で gulp タスクを再開する必要があります。

F1 => Terminate Running Task 

そして、タスクをCTRL + SHIFT + Bでやり直します。この時点で、あなたのtest.htmlは次のように書かれるべきです:

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

preタグからすべての改行が削除されているため、かなり醜いですが、ブログエンジンでよりうまく機能するはずです。

最後に2つの問題があり、その前に対処しておきたいと思います。まず、すべてのリンクが新しいタブで自動的に開くようにしたいのですが、これはウェブサイト/マーケティングチームのリクエストに応じて行います。次に、現在の結果のHTMLは、出力HTMLが実際にコードスニペットに対して色付けされるためには、CSSスタイルシートがロードされることを想定しています。ブラウザで出力を見ると、次のようになります。

コード スニペットに対して実際に色付けされる出力 HTML。ブラウザで出力を見ると、次のようになります

前にも触れたように、ブログ記事に投稿ごとの CSS を挿入することはできないため、すべての構文の色分けを HTML インラインに直接焼き付けたいと考えています。

幸いなことに、これらの問題に対しては、簡単に使えるNodeパッケージで解決できます。まず、Markdown-it用のプラグインmarkdown-it-link-targetリンクを新しいタブで開くようにレンダリングするパッケージをインストールします。

npm install markdown-it-link-target --save

次に、juice というノードパッケージを呼び出す gulp のプラグインをインストールします。

npm install gulp-juice --save

juice は、CSS スタイルシートと HTML の一部を取り、CSS スタイルをインラインで HTML に焼き付けて、外部 CSS が不要になるようにする、きちんとしたライブラリです。これらのピースを接続するには、再度gulpfileを更新してからタスクを再開する必要があります。

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']);
});

これらの変更により、test.htmlはこうなるはずです。

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

上記の行では、次のようになります。

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

HighlightJSに同梱されているCSSファイルの1つを読み込み、HTMLにベイクします。

これで、これは、装飾的なブログエンジンを含む、HTMLをサポートするほとんどすべてのものに生のHTMLとして簡単に貼り付けることができます。

この時点でブラウザにロードすると、次のようになります。

この時点でブラウザにロードすると、次のようになります

これが興味深い、および/または役に立つとお考えいただければ幸いです。

デモを予約