Snippet、VSCodeおよびCLIの拡張。パート1





良い一日、友達!最新のHTMLスターターテンプレート



を開発しているときに、その使いやすさを拡張することを考えました。当時、その使用オプションは、リポジトリのクローン作成とアーカイブのダウンロードに限定されていました。これは、Microsoft Visual StudioコードHTMLスニペットと拡張機能(HTMLテンプレート)、およびコマンドラインインターフェイス(create-modern-template)がどのように表示されたかを示しています。もちろん、これらのツールは完璧にはほど遠いので、できる限り改良していきます。しかし、それらを作成する過程で、私はあなたと共有したいいくつかの興味深いことを学びました。 このパートでは、スニペットと拡張機能、そして次のCLIについて見ていきます。







ソースコードのみに関心がある場合は、ここにリポジトリへのリンクがあります



スニペット



スニペットとは何ですか?つまり、スニペットは、エディターが自動完了(コード完了)に使用するテンプレートです。



VSCodeにはEmmet(公式サイトVisual Studio CodeのEmmetが組み込まれており、コードの記述に役立つ多数のHTML、CSS、およびJSスニペットを使用します。エディター(.html)を入力し、TabキーまたはEnterキーを押すと、完成したhtml5マークアップが得られます。nav> ul> li * 3> a.link> imgと入力し、Tabキーを押すと、次のようになります。



<nav>
    <ul>
      <li><a href="" class="link"><img src="" alt=""></a></li>
      <li><a href="" class="link"><img src="" alt=""></a></li>
      <li><a href="" class="link"><img src="" alt=""></a></li>
    </ul>
  </nav>

      
      









組み込みのものに加えて、VSCodeはカスタムスニペットを使用する機能を提供します。それらを作成するには、[ファイル]-> [設定]-> [ユーザースニペット]に移動します(または、左下隅の[管理]ボタンをクリックして[ユーザースニペット]を選択します)。各言語の設定は、対応するJSONファイルに保存されます(html.jsonのHTMLの場合、javascript.jsonのJavaScriptの場合など)。



JSスニペットの作成を練習しましょう。javascript.jsonファイルを見つけて開きます。







スニペットを作成するためのルールを簡単に説明するコメントが表示されます。VSCodeでカスタムスニペットを作成する方法の詳細については、こちらをご覧ください



簡単なことから始めましょう。console.log()のスニペットを作成しましょう。これはそれがどのように見えるかです:



"Print to console": {
  "prefix": "log",
  "body": "console.log($0)",
  "description": "Create console.log()"
},

      
      





  • コンソールに出力-オブジェクトキー、スニペット名(必須)
  • プレフィックス-スニペットの省略形(必須)
  • body-スニペット自体(必須)
  • $ number-スニペット作成後のカーソル位置。$ 1-最初の位置、$ 2-2番目など、$ 0-最後の位置(オプション)
  • description-スニペットの説明(オプション)


ファイルを保存します。スクリプトにlogと入力し、TabキーまたはEnterキーを押すと、括弧の間にカーソルが置かれたconsole.log()が表示されます。



for-ofループのスニペットを作成しましょう。



"For-of loop": {
  "prefix": "fo",
  "body": [
    "for (const ${1:item} of ${2:arr}) {",
    "\t$0",
    "}"
  ]
},

      
      





  • 複数行のスニペットは、配列を使用して作成されます
  • $ {数値:値}; $ {1:item}は、デフォルトのアイテム値を持つ最初のカーソル位置を意味します。この値は、スニペットを作成した後、およびすばやく編集するためにカーソルの次の位置に移動した後に強調表示されます
  • \ t-1つのインデント(ギャップの量は、エディターの対応する設定、または私の場合はPrettier拡張機能によって決定されます)、\ t \ t-2つのインデントなど。


スクリプトにfoと入力し、TabキーまたはEnterキーを押すと、次のようになります。



for (const item of arr) {

}

      
      





アイテムが強調表示されます。Tabキーを押すと、arrが強調表示されます。もう一度Tabキーを押して、2行目に移動します。



さらにいくつかの例を示します。



"For-in loop": {
  "prefix": "fi",
  "body": [
    "for (const ${1:key} in ${2:obj}) {",
    "\t$0",
    "}"
  ]
},
"Get one element": {
  "prefix": "qs",
  "body": "const $1 = ${2:document}.querySelector('$0')"
},
"Get all elements": {
  "prefix": "qsa",
  "body": "const $1 = [...${2:document}.querySelectorAll('$0')]"
},
"Add listener": {
  "prefix": "al",
  "body": [
    "${1:document}.addEventListener('${2:click}', (${3:{ target }}) => {",
    "\t$0",
    "})"
  ]
},
"Async function": {
  "prefix": "af",
  "body": [
    "const $1 = async ($2) => {",
    "\ttry {",
    "\t\tconst response = await fetch($3)",
    "\t\tconst data = await res.json()",
    "\t\t$0",
    "\t} catch (err) {",
    "\t\tconsole.error(err)",
    "\t}",
    "}"
  ]
}

      
      





HTMLスニペットも同じ原則に従います。HTMLテンプレートは次のようになります。



{
  "HTML Template": {
    "prefix": "html",
    "body": [
      "<!DOCTYPE html>",
      "<html",
      "\tlang='en'",
      "\tdir='ltr'",
      "\titemscope",
      "\titemtype='https://schema.org/WebPage'",
      "\tprefix='og: http://ogp.me/ns#'",
      ">",
      "\t<head>",
      "\t\t<meta charset='UTF-8' />",
      "\t\t<meta name='viewport' content='width=device-width, initial-scale=1' />",
      "",
      "\t\t<title>$1</title>",
      "",
      "\t\t<meta name='referrer' content='origin' />",
      "\t\t<link rel='canonical' href='$0' />",
      "\t\t<link rel='icon' type='image/png' href='./icons/64x64.png' />",
      "\t\t<link rel='manifest' href='./manifest.json' />",
      "",
      "\t\t<!-- Security -->",
      "\t\t<meta http-equiv='X-Content-Type-Options' content='nosniff' />",
      "\t\t<meta http-equiv='X-XSS-Protection' content='1; mode=block' />",
      "",
      "\t\t<meta name='author' content='$3' />",
      "\t\t<meta name='description' content='$2' />",
      "\t\t<meta name='keywords' content='$4' />",
      "",
      "\t\t<meta itemprop='name' content='$1' />",
      "\t\t<meta itemprop='description' content='$2' />",
      "\t\t<meta itemprop='image' content='./icons/128x128.png' />",
      "",
      "\t\t<!-- Microsoft -->",
      "\t\t<meta http-equiv='x-ua-compatible' content='ie=edge' />",
      "\t\t<meta name='application-name' content='$1' />",
      "\t\t<meta name='msapplication-tooltip' content='$2' />",
      "\t\t<meta name='msapplication-starturl' content='/' />",
      "\t\t<meta name='msapplication-config' content='browserconfig.xml' />",
      "",
      "\t\t<!-- Facebook -->",
      "\t\t<meta property='og:type' content='website' />",
      "\t\t<meta property='og:url' content='$0' />",
      "\t\t<meta property='og:title' content='$1' />",
      "\t\t<meta property='og:image' content='./icons/256x256.png' />",
      "\t\t<meta property='og:site_name' content='$1' />",
      "\t\t<meta property='og:description' content='$2' />",
      "\t\t<meta property='og:locale' content='en_US' />",
      "",
      "\t\t<!-- Twitter -->",
      "\t\t<meta name='twitter:title' content='$1' />",
      "\t\t<meta name='twitter:description' content='$2' />",
      "\t\t<meta name='twitter:url' content='$0' />",
      "\t\t<meta name='twitter:image' content='./icons/128x128.png' />",
      "",
      "\t\t<!-- IOS -->",
      "\t\t<meta name='apple-mobile-web-app-title' content='$1' />",
      "\t\t<meta name='apple-mobile-web-app-capable' content='yes' />",
      "\t\t<meta name='apple-mobile-web-app-status-bar-style' content='#222' />",
      "\t\t<link rel='apple-touch-icon' href='./icons/256x256.png' />",
      "",
      "\t\t<!-- Android -->",
      "\t\t<meta name='theme-color' content='#eee' />",
      "\t\t<meta name='mobile-web-app-capable' content='yes' />",
      "",
      "\t\t<!-- Google Verification Tag -->",
      "",
      "\t\t<!-- Global site tag (gtag.js) - Google Analytics -->",
      "",
      "\t\t<!-- Global site tag (gtag.js) - Google Analytics -->",
      "",
      "\t\t<!-- Yandex Verification Tag -->",
      "",
      "\t\t<!-- Yandex.Metrika counter -->",
      "",
      "\t\t<!-- Mail Verification Tag -->",
      "",
      "\t\t<!-- JSON-LD -->",
      "\t\t<script type='application/ld+json'>",
      "\t\t\t{",
      "\t\t\t\t'@context': 'http://schema.org/',",
      "\t\t\t\t'@type': 'WebPage',",
      "\t\t\t\t'name': '$1',",
      "\t\t\t\t'image': [",
      "\t\t\t\t\t'$0icons/512x512.png'",
      "\t\t\t\t],",
      "\t\t\t\t'author': {",
      "\t\t\t\t\t'@type': 'Person',",
      "\t\t\t\t\t'name': '$3'",
      "\t\t\t\t},",
      "\t\t\t\t'datePublished': '2020-11-20',",
      "\t\t\t\t'description': '$2',",
      "\t\t\t\t'keywords': '$4'",
      "\t\t\t}",
      "\t\t</script>",
      "",
      "\t\t<!-- Google Fonts -->",
      "",
      "\t\t<style>",
      "\t\t\t/* Critical CSS */",
      "\t\t</style>",
      "",
      "\t\t<link rel='preload' href='./css/style.css' as='style'>",
      "\t\t<link rel='stylesheet' href='./css/style.css' />",
      "",
      "<link rel='preload' href='./script.js' as='script'>",
      "\t</head>",
      "\t<body>",
      "\t\t<!-- HTML5 -->",
      "\t\t<header>",
      "\t\t\t<h1>$1</h1>",
      "\t\t\t<nav>",
      "\t\t\t\t<a href='#' target='_blank' rel='noopener'>Link 1</a>",
      "\t\t\t\t<a href='#' target='_blank' rel='noopener'>Link 2</a>",
      "\t\t\t</nav>",
      "\t\t</header>",
      "",
      "\t\t<main></main>",
      "",
      "\t\t<footer>",
      "\t\t\t<p>© 2020. All rights reserved</p>",
      "\t\t</footer>",
      "",
      "\t\t<script src='./script.js' type='module'></script>",
      "\t</body>",
      "</html>"
    ],
    "description": "Create Modern HTML Template"
  }
}

      
      





htmlと入力し、TabキーまたはEnterキーを押すと、マークアップが取得されます。カーソル位置は、アプリケーション名(タイトル)、説明(説明)、作成者(作成者)、キーワード(キーワード)、アドレス(url)の順序で定義されます。



拡張



VSCodeサイトには、拡張機能の構築に関する優れたドキュメントがあります。



拡張機能には、スニペットフォームとCLIフォームの2つのオプションを作成します。私たちは、第二のオプション公開する予定のVisual Studioマーケットプレイス



スニペット形式の拡張機能の例:





おそらく「実際の」CLIがあるため、CLIフォーム拡張機能はあまり一般的ではありません。



スニペット形式の拡張


Node.jsGit に加えて、VSCodeの拡張機能を開発するには、さらに2つのライブラリ、より正確には1つのライブラリとプラグイン(yeomangenerator-code)が必要です。それらをグローバルにインストールします。



npm i -g yo generator-code
// 
yarn global add yo generator-code

      
      





yo codeコマンドを実行し、[New Code Snippets]を選択して、質問に答えます。







以前に作成したHTMLスニペットをスニペット/snippets.code-snippetsファイルにコピーし(スニペットファイルにjson拡張子を付けることもできます)、package.jsonとREADME.mdを編集して、拡張機能をマーケットプレイスに公開できます。ご覧のとおり、すべてが非常に単純です。単純すぎると思い、CLIの形で拡張機能を作成することにしました。



CLI拡張


yocodeコマンドを再度実行してください。今回は、新しい拡張機能(TypeScript)を選択し(恐れることはありません。コードにはTypeScriptがほとんどないので、必要な説明を行います)、質問に答えます。







拡張機能が機能していることを確認するには、エディターでプロジェクトを開きます。



cd htmltemplate
code .

      
      





左側のF5または[実行]ボタン(Ctrl / Cmd + Shift + D)を押し、上部の[デバッグの開始]ボタンを押します。起動時にエラーが発生することがあります。この場合、起動をキャンセル(キャンセル)して、手順を繰り返します。



開いたエディターで、[表示]-> [コマンドパレット](Ctrl / Cmd + Shift + P)をクリックし、helloと入力して、[HelloWorld]を選択します。







VSCodeから情報メッセージを受け取り、対応するメッセージ(おめでとう)をコンソールに受け取ります。







プロジェクト内のすべてのファイルの中で、package.jsonとsrc /extension.tsに関心があります。src / testディレクトリとvsc-extension-quickstart.mdファイルを削除できます。



extension.ts(読みやすくするためにコメントを削除)を見てみましょう。



//   VSCode
import * as vscode from 'vscode'

// ,    
export function activate(context: vscode.ExtensionContext) {
  // ,    ,
  //     
  console.log('Congratulations, your extension "htmltemplate" is now active!')

  //  
  //  -   
  // htmltemplate -  
  // helloWorld -  
  let disposable = vscode.commands.registerCommand(
    'htmltemplate.helloWorld',
    () => {
      //  ,   
      //    
      vscode.window.showInformationMessage('Hello World from htmltemplate!')
    }
  )

  //  
  //   ,     "/",
  //     ""
  context.subscriptions.push(disposable)
}

// ,    
export function deactivate() {}

      
      





重要なポイント:extension.tsの「extension.command」は、package.jsonのactivationEventsおよびコマンドフィールドの値と一致する必要があります。



"activationEvents": [
  "onCommand:htmltemplate.helloWorld"
],
"contributes": {
  "commands": [
    {
      "command": "htmltemplate.helloWorld",
      "title": "Hello World"
    }
  ]
},

      
      





  • コマンド-コマンドのリスト
  • ActivationEvents-コマンドの実行中に呼び出される関数


拡張機能の開発を始めましょう。



拡張機能は機能的create-react-appまたはvue-cliに似ている必要がありますcreateコマンドで、ターゲットディレクトリに必要なすべてのファイルを含むプロジェクトを作成しました。



まず、package.jsonを編集しましょう。



"displayName": "HTML Template",
"activationEvents": [
  "onCommand:htmltemplate.create"
],
"contributes": {
  "commands": [
    {
      "command": "htmltemplate.create",
      "title": "Create Template"
    }
  ]
},

      
      





ターゲットディレクトリにコピーされるプロジェクトファイルを格納するためのsrc / componentsディレクトリを作成します。



ES6モジュールの形式でプロジェクトファイルを作成します(VSCodeはデフォルトでES6モジュールを使用します(エクスポート/インポート)が、CommonJSモジュールをサポートします(module.exports / require)):index.html.js、css / style.css.js 、script.jsなど。ファイルの内容はデフォルトでエクスポートされます。



// index.html.js
export default `
<!DOCTYPE html>
<html
  lang="en"
  dir="ltr"
  itemscope
  itemtype="https://schema.org/WebPage"
  prefix="og: http://ogp.me/ns#"
>
  ...
</html>
`

      
      





このアプローチでは、すべての画像(この場合はアイコン)をBase64でエンコードする必要があることに注意してください。これが適切なオンラインツールの1つです。変換されたファイルの先頭に「data:image / png; base64」という行が存在することは、基本的に重要ではありません。fs-extra



を使用してファイルをコピー(書き込み)します。このライブラリのoutputFileメソッドは、組み込みのNode.js writeFileメソッドと同じことを行いますが、ファイルが存在しない場合は、書き込まれるファイルのディレクトリも作成します。たとえば、create css / style.cssを指定し、cssディレクトリが存在しない場合、outputFileはファイルを作成します。そこにstyle.cssを書き込みます(ディレクトリがない場合、writeFileは例外をスローします)。 extension.tsファイルは次のようになります。







import * as vscode from 'vscode'
//   fs-extra
const fs = require('fs-extra')
const path = require('path')

//   , ,   
import indexHTML from './components/index.html.js'
import styleCSS from './components/css/style.css.js'
import scriptJS from './components/script.js'
import icon64 from './components/icons/icon64.js'
// ...

export function activate(context: vscode.ExtensionContext) {
  console.log('Congratulations, your extension "htmltemplate" is now active!')

  let disposable = vscode.commands.registerCommand(
    'htmltemplate.create',
    () => {
      //  ,       html-template
      // filename: string  TypeScript-,
      //   ,  ,
      //   
      const folder = (filename: string) =>
        path.join(vscode.workspace.rootPath, `html-template/${filename}`)

      //    
      // files: string[] ,    files   
      const files: string[] = [
        indexHTML,
        styleCSS,
        scriptJS,
        icon64,
        ...
      ]

      //    
      //  ,        
      const fileNames: string[] = [
        'index.html',
        'css/style.css',
        'script.js',
        'server.js',
        'icons/64x64.png',
        ...
      ]

      ;(async () => {
        try {
          //    
          for (let i = 0; i < files.length; i++) {

            //  outputFile       :
            //    ( ),     (  UTF-8)

            //     png,
            // ,     Base64-:
            //   
            if (fileNames[i].includes('png')) {
              await fs.outputFile(folder(fileNames[i]), files[i], 'base64')
            // ,    
            } else {
              await fs.outputFile(folder(fileNames[i]), files[i])
            }
          }

          //     
          return vscode.window.showInformationMessage(
            'All files created successfully'
          )
        } catch {
          //   
          return vscode.window.showErrorMessage('Failed to create files')
        }
      })()
    }
  )

  context.subscriptions.push(disposable)
}

export function deactivate() {}

      
      





TypeScriptがインポートされたモジュールファイルのタイプの不足に注意を払わないようにするには、次の内容でsrc /global.d.tsを作成します。



declare module '*'

      
      





拡張機能をテストしてみましょう。エディターで開きます。



cd htmltemplate
code .

      
      





デバッグを開始します(F5)。ターゲットディレクトリ(たとえば、test-dir)に移動し、コマンドパレットでcreateコマンドを実行します。







ファイルの作成が成功したことに関する情報メッセージを受け取ります。やったー!







Visual StudioMarketplaceへの拡張機能の公開


VSCodeの拡張機能を公開できるようにするには、次のことを行う必要があります。





package.jsonの編集:



{
  "name": "htmltemplate",
  "displayName": "HTML Template",
  "description": "Modern HTML Starter Template",
  "version": "1.0.0",
  "publisher": "puslisher-name",
  "license": "MIT",
  "keywords": [
    "html",
    "html5",
    "css",
    "css3",
    "javascript",
    "js"
  ],
  "icon": "build/128x128.png",
  "author": {
    "name": "Author Name @githubusername"
  },
  "repository": {
    "type": "git",
    "url": "https://github.com/username/dirname"
  },
  "engines": {
    "vscode": "^1.51.0"
  },
  "categories": [
    "Snippets"
  ],
  "activationEvents": [
    "onCommand:htmltemplate.create"
  ],
  "main": "./dist/extension.js",
  "contributes": {
    "commands": [
      {
        "command": "htmltemplate.create",
        "title": "Create Template"
      }
    ]
  },
  ...
}

      
      





README.mdを編集しています。



拡張ディレクトリでvscepackageコマンドを実行して、vsix拡張を含む公開パッケージを作成します。 htmltemplate-1.0.0.vsixファイルを取得します。



マーケットプレイス拡張機能を管理するためのページで、[新しい拡張機能]ボタンをクリックし、[Visual StudioCode]を選択します。 VSIXファイルをモーダルウィンドウに転送またはロードします。検証が完了するのを待っています。







バージョン番号の横に緑色のチェックマークが表示された後、拡張機能をVSCodeにインストールできるようになります。







拡張機能を更新するには、package.jsonのバージョン番号を変更し、VSIXファイルを生成して、[その他のアクション]ボタンをクリックし、[更新]を選択してマーケットプレイスにアップロードする必要があります。



ご覧のとおり、VSCodeの拡張機能を作成して公開することについて超自然的なことは何もありません。これで、私は私の休暇を取りましょう。



次のパートでは、最初にHerokuフレームワーク(oclifを使用し、次にそれを使用せずに、本格的なコマンドラインインターフェイスを作成しますNode.js-CLIは拡張機能とは大きく異なり、視覚化され、オプションでgitを初期化して依存関係をインストールする機能があります。



あなたがあなた自身のために何か面白いものを見つけたことを願っています。清聴ありがとうございました。



All Articles