• 編集プロダクション・リブロワークスのWebサイト

    Atomがそろそろ更新停止というニュースを聞いて、社内の編集執筆に使っている拡張機能のMDBPをVisual Studio Code(VSCode)に移植しました。

    https://marketplace.visualstudio.com/items?itemName=Libroworks.mdbp-vscode

    どんなツールかは下記記事にて。

    MarkdownプレビューツールをAtomパッケージ化しました

    mdbp_vscode extension

    意外と短時間で終わったので、記録代わりに記事にまとめてみます。

    意外とそのまま動く

    AtomとVSCodeはどちらもElectronというフレームワークで作られていて、共通点は多いです。とはいえ、別物だからいろいろトラブルかもと思ったのですが、コード自体は思った以上にそのまま動きました。

    警告などを表示する場合は……

    // Atomの場合
    atom.notifications.addWarning('[Live Server] すでに開始しています' + this.liverserverURL);
    
    // VSCodeの場合
    vscode.window.showWarningMessage('[Live Server] すでに開始しています' + this.liverserverURL);
    

    作業フォルダのパスを取得する場合は……

    // Atomの場合
    const targetPath = atom.project.getPaths()[0];
    
    // VSCodeの場合
    const targetPath = vscode.workspace.workspaceFolders[0].uri.fsPath;

    アクティブエディタからファイルのパスを取得する場合は……

    // Atomの場合
    const editor = atom.workspace.getActiveTextEditor();
    const path = editor.getPath();
    
    // VSCodeの場合
    const editor = vscode.window.activeTextEditor;
    const path = editor.document.fileName;

    HTMLをWebブラウザで開く場合は……

    // Atomの場合
    shell.openExternal(url);
    
    // VSCodeの場合
    vscode.env.openExternal(url);

    その他、コマンド登録などVSCode特有部分も変更が必要でしたが、主に直したのは上記のところあたりです。もともとエディタの機能を利用する部分がかなり少なく、大部分はNode.jsの各種パッケージを利用していたので、主要部分はそのまま動いたようです。

    また、VSCodeの機能拡張はF5キーを押すだけでデバッグできるので、そこもすごい楽でした。

    これだけなら1時間ぐらいで終わる話なのですが、実際は2日ほどかかったので、ここから先はつまずいた部分を紹介していきます。

    つまずき1:yoで作成するときは説明必須

    適当なフォルダでnpx yo codeを実行すると、拡張機能のひながたができるのですが、途中でdescription(説明)を聞かれたときに何も入力しないと、作成に失敗します。

    npx yo code 拡張機能名
    
    
    npx yo code

    TypeScriptも選べますが、今回はAtom版が生JavaScriptだったので、それに合わせてNew Extension(JavaScript)を選びました。ただし、TypeScriptのサンプルコードのほうが圧倒的に多いので、VSCodeの機能をいろいろ使いたい場合はTypeScriptを選んだほうがいいと思います。

    つまずき2:Export方法が違う……

    最初に数時間つまずいたのは、別ファイルのクラスや関数をexport/importする部分。ここで半日ほど消費しました。

    Atomのときはこんな感じでしたが、

    // Atomの場合
    'use babel';
    export default class MarkdownBookPreviewServer {
        ……
    }

    VSCodeは「exports.プロパティ」という形で書きます。どうもNode.js式のExport方法らしいです。

    // VSCodeの場合
    
    class MarkdownBookPreviewServer {
        ……
    }
    
    exports.MarkdownBookPreviewServer = MarkdownBookPreviewServer;

    export/importみたいな基礎的なところで色々なやり方があるところが、JavaScriptのやっかいなところです(TypeScriptの場合はまた違う書き方をします)。

    つまずき3:コード補完があてにならない

    JavaScriptは型がないせいか、コード補完(IntelliSense)がとにかくあてにならなかったです。間違ったものを何度かすすめられました。みんながTypeScriptをつかいたがる理由がわかるというものです。

    あと、VSCodeのAPIリファレンスのサイトがすごい重いです。

    つまずき4:vsceコマンドが動かない

    拡張機能を公開するには、vsceというコマンドを使うのですが、これがしばらく動かなくて困りました。

    npm install -g vsce
    npx vsce publish
    npx vsce publish

    数時間をかけて調べた結果、大昔にインストールしたnodistというツール(Node.jsのバージョンを切り替えるツール)が悪さ(古いNnode.jsで実行しようとしてエラー)をしていたことがわかり、下記サイトを参考にアンインストールしました。

    2020 年ではもう使えない Nodist はアンインストールする (Windows)

    記事の途中にある「.npmrc ファイルを削除しろ」という記述を見落としていて、最新のNode.jsを2回インストールするはめになりました。

    つまずき5:パーソナルアクセストークンの発行でつまずく

    VSCodeの拡張機能を公開するためには、

    1. Azureのアカウントを作ってパーソナルアクセストークンを発行する
    2. VSCodeのPublisherを作る
    3. 拡張機能のフォルダ内でvsce publishコマンドを実行する

    という手順を取るのですが、パーソナルアクセストークン(PAT)を発行するところでまたつまずきました。

    以下の画面を出すところまではそう難しくない(ので覚えていない)ので省きますが、この画面では「Organization」で「All accessible organizations」を選び、「Scopes」の下にある「Show more scopes」をクリックしてMarketplaceのスコープを表示する必要があります。

    Edit a personal access token

    このあとPATが表示されるので、どこかにコピーしておきます。同じPATは二度と表示されないので、コピー必須です(再発行はできます)。

    PATは vsce publish の実行時に求められます。

    つまずき6:Publisherを作成する方法が変わってる

    Publisherの作成方法が、ネットで検索すると出てくるコマンドではなく、サイト上で作成する方法に変わっていました。エラーメッセージの最後にあるURLからサイトを表示して作成します。

    PS C:\Users\ohtsu> npx vsce create-publisher パブリッシャー名
    ERROR  The 'create-publisher' command is no longer available. You can create a publisher directly in the Marketplace: https://aka.ms/vscode-create-publisher

    わかってしまえばそう難しくはない(サイトを表示して「Create publisher」をクリックするだけ)ですが、つまずき5と同時発生していたので、原因を絞るのに時間がかかりました。

    Visual Studio Marketplace

    Publisherを作るときは、会社のURLや128x128pxのアイコンの設定を求められますが、これはそう難しくはないですね。

    つまずき7:READMEとLicenseを用意する

    vsce publish(npx vsce publish)を実行すると、何回かREADMEが変だとか、Licenseがないとか怒られます。

    最初にyoコマンドでREADME.mdが自動生成されるのですが、その一部が残っているとエラーになるようです。全部書き換えて不要な部分を削除したら発行できました。「ちゃんと自分で書け!」ということでしょうか。

    LicenseはLicense.txtを入れておくだけです。

    これで何とか動くものが公開できました。

    publish時に「ファイルが既定より多すぎる。bundlerを使え」と怒られているのですが、見よう見まねでesbuildというものを入れたら動かなくなったので、時間があるときに再挑戦します。現状でもAtom版よりは速い気がしますし……。

    多くのnpmパッケージが最新になったせいで挙動が予想つかないので、しばらくは細々と実践テストですね。

    MDBPの動作イメージ

    つまずき8:Webpackでbundle(2022/07/26追記)

    esbuildでのbundleに失敗したままだとどうにも気持ち悪いので、1週間ほど経ってから再挑戦しました。ただ、esbuildをかけると、コマンド実行時に「command not found」というエラーが出てしまってどうしてもうまくいかず、MSのサイトに載っていたもう1つのバンドルツールWebpackに切り替えてみました。

    Bundling Extensions

    Webpackをインストールします。生JavaScriptなのでts-loaderは不要。

    npm i --save-dev webpack webpack-cli

    あとは見よう見まねで、webpack.config.jsファイルを作り(ここがesubildより面倒)、package.jsonのmainセクションとscriptsセクションをちょっと書き換えて終わりです。

      "main": "./dist/extension.js",
    ……中略……
      "scripts": {
        "vscode:prepublish": "webpack --mode production",
        "webpack": "webpack --mode development",
        "webpack-dev": "webpack --mode development --watch",
        "lint": "eslint .",
        "pretest": "npm run lint",
        "test": "node ./test/runTest.js"
      },

    Webpackでも最初はうまくいかなかったのですが、実行テスト中によく見ると画面に「injection.htmlがない」的なメッセージが出ていました。何だっけそれと調べてみたところ、確かにMDBP内でinjection.htmlというHTMLファイルが存在しました。HTMLファイルなのでbundleされず、ファイルがないのが原因で拡張機能のコマンド登録に失敗し、結果としてcommand not foundになっていたようです。

    esbuildのほうが新しくて速いらしいんですが、Webpackのほうがエラーが丁寧に出るのかもしれません(このあたりは不慣れなので話半分に)。

    この部分は「tapio/live-server」をちょっと改造して使っていまして、injection.html内にはプレビュー中の更新を監視するために、HTMLに仕込むコードが入っていました。HTMLファイルをbundleする方法もたぶんあるんだろうなと思ったのですが、今どき短いコードだったらJSのテンプレート文字列で書いたほうがスマートなはずなので、JSコードにまとめました。

    これで何とかWebpackでのbundleが成功し、ファイルサイズが約10分の1に減りました。こんなに小さくなるとはビックリ……。拡張機能の起動速度もいくらか速くなってるんじゃないでしょうか(体感ではよくわかりませんでしたが)。

    ちなみに、テスト途中のものをプレリリースで公開したいときは、このコマンドを使うそうです。

    npx vsce publish --pre-release

    まぁ、何とかカンとかこれでAtom版と同じ動作、かつVSCodeの仕様に沿う形にはなったようなので、0.1.0にしました。

    次は、Markdownの変換器をmarkdedから他(VFMとか)に変えられるようにできればしたいところ。