Snippet, ekstensi untuk VSCode dan CLI. Bagian 1





Selamat siang teman!



Saat mengembangkan Template Pemula HTML Modern, saya berpikir untuk memperluas kegunaannya. Pada saat itu, opsi penggunaannya terbatas pada kloning repositori dan mengunduh arsip. Beginilah cuplikan dan ekstensi HTML untuk Kode Microsoft Visual Studio - Template HTML , serta antarmuka baris perintah - buat-modern-template muncul . Tentu saja, alat-alat ini jauh dari sempurna dan saya akan menyempurnakannya semampu saya. Namun, dalam proses pembuatannya, saya mempelajari beberapa hal menarik yang ingin saya bagikan kepada Anda.



Pada bagian ini kita akan melihat potongan dan ekstensi, dan CLI selanjutnya.



Jika Anda hanya tertarik pada kode sumbernya, berikut ini tautan ke repositori .



Potongan



Apa itu potongan? Singkatnya, cuplikan adalah template yang digunakan editor untuk pelengkapan otomatis (penyelesaian kode).



VSCode telah membangun Emmet ( situs resmi , Emmet dalam Visual Studio Code ), yang menggunakan banyak cuplikan HTML, CSS dan JS untuk membantu Anda menulis kode Anda. Kami mengetik di editor (dalam .html) !, Tekan Tab atau Enter, kami mendapatkan markup html5 selesai. Kami mengetik nav> ul> li * 3> a.link> img, tekan Tab, kami mendapatkan:



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

      
      





dll.



Selain yang sudah ada di dalamnya, VSCode menyediakan kemampuan untuk menggunakan cuplikan khusus. Untuk membuatnya, buka File -> Preferensi -> Cuplikan Pengguna (atau klik tombol Kelola di sudut kiri bawah dan pilih Cuplikan Pengguna). Pengaturan untuk setiap bahasa disimpan dalam file JSON yang sesuai (untuk HTML di html.json, untuk JavaScript di javascript.json, dll.).



Mari berlatih membuat cuplikan JS. Temukan file javascript.json dan buka.







Kami melihat komentar yang menjelaskan secara singkat aturan untuk membuat cuplikan. Informasi lebih lanjut tentang membuat cuplikan khusus di VSCode dapat ditemukan di sini .



Mari kita mulai dengan sesuatu yang sederhana. Mari buat cuplikan untuk console.log (). Beginilah tampilannya:



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

      
      





  • Cetak ke konsol - kunci objek, nama cuplikan (wajib)
  • prefix - singkatan untuk snippet (wajib)
  • body - cuplikannya sendiri (wajib)
  • $ number - posisi kursor setelah pembuatan cuplikan; $ 1 - posisi pertama, $ 2 - kedua, dll., $ 0 - posisi terakhir (opsional)
  • description - snippet description (opsional)


Kami menyimpan file. Kami mengetik log di skrip, tekan Tab atau Enter, kami mendapatkan console.log () dengan kursor di antara tanda kurung.



Mari buat cuplikan untuk for-of loop:



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

      
      





  • Cuplikan multi-baris dibuat menggunakan array
  • $ {angka: nilai}; $ {1: item} berarti posisi kursor pertama dengan nilai item default; nilai ini disorot setelah membuat cuplikan, serta setelah berpindah ke posisi kursor berikutnya untuk pengeditan cepat
  • \ t - satu indentasi (jumlah celah ditentukan oleh pengaturan editor yang sesuai atau, dalam kasus saya, ekstensi Prettier ), \ t \ t - dua indentasi, dll.


Kami mengetik untuk di skrip, tekan Tab atau Enter, kami mendapatkan:



for (const item of arr) {

}

      
      





dengan item disorot. Tekan Tab, arr disorot. Tekan Tab lagi, pergi ke baris kedua.



Berikut beberapa contoh lainnya:



"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}",
    "}"
  ]
}

      
      





Cuplikan HTML mengikuti prinsip yang sama. Seperti inilah tampilan Template 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"
  }
}

      
      





Kami mengetik html, tekan Tab atau Enter, kami mendapatkan markup. Posisi kursor ditentukan dalam urutan berikut: nama aplikasi (judul), deskripsi (deskripsi), penulis (penulis), kata kunci (kata kunci), alamat (url).



Perpanjangan



Situs VSCode memiliki dokumentasi yang sangat baik tentang ekstensi bangunan .



Kami akan membuat dua opsi untuk ekstensi: formulir potongan dan formulir CLI. Kami akan menerbitkan opsi kedua di Visual Studio Marketplace .



Contoh ekstensi berupa potongan:





Ekstensi formulir CLI kurang populer, mungkin karena ada CLI "nyata".



Ekstensi dalam bentuk potongan


Untuk mengembangkan ekstensi untuk VSCode, selain Node.js dan Git , kita membutuhkan beberapa perpustakaan lagi, lebih tepatnya, satu perpustakaan dan plugin, yaitu yeoman dan generator-code . Instal secara global:



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

      
      





Kami menjalankan perintah kode yo, pilih Cuplikan Kode Baru, jawab pertanyaan.







Tetap menyalin cuplikan HTML yang kita buat sebelumnya ke dalam file cuplikan / snippet.code-snippet (file cuplikan juga dapat memiliki ekstensi json), mengedit package.json dan README.md, dan Anda dapat memublikasikan ekstensi tersebut ke pasar. Seperti yang Anda lihat, semuanya sangat sederhana. Terlalu sederhana, pikir saya, dan memutuskan untuk membuat ekstensi dalam bentuk CLI.



Ekstensi CLI


Jalankan kembali perintah kode yo. Kali ini kita pilih New Extension (TypeScript) (jangan takut, hampir tidak ada TypeScript di kode kita, dan di mana itu, saya akan memberikan penjelasan yang diperlukan), jawab pertanyaannya.







Untuk memastikan bahwa ekstensi berfungsi, buka proyek di editor:



cd htmltemplate
code .

      
      





Tekan F5 atau tombol Jalankan (Ctrl / Cmd + Shift + D) di sebelah kiri dan tombol Mulai Debugging di atas. Terkadang Anda mendapatkan kesalahan saat memulai. Dalam kasus ini, kami membatalkan peluncuran (Batal) dan mengulangi prosedurnya.



Di editor yang terbuka, klik View -> Command Palette (Ctrl / Cmd + Shift + P), ketik hello dan pilih Hello World.







Kami menerima pesan informasi dari VSCode dan pesan yang sesuai (selamat) di konsol.







Dari semua file dalam proyek ini, kami tertarik dengan package.json dan src / extension.ts. Direktori src / test dan file vsc-extension-quickstart.md dapat dihapus.



Mari kita lihat extension.ts (komentar dihapus agar terbaca):



//   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() {}

      
      





Poin penting: 'extension.command' di extension.ts harus cocok dengan nilai dari activationEvents dan bidang perintah di package.json:



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

      
      





  • perintah - daftar perintah
  • activationEvents - fungsi yang akan dipanggil selama eksekusi perintah


Mari mulai mengembangkan ekstensi.



Kami ingin ekstensi kami menyerupai fungsi create-react-app atau vue-cli , mis. pada perintah create, buat proyek yang berisi semua file yang diperlukan di direktori target.



Pertama, mari edit package.json:



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

      
      





Buat direktori src / komponen untuk menyimpan file proyek yang akan disalin ke direktori target.



Buat file proyek sebagai modul ES6 (VSCode menggunakan modul ES6 secara default (ekspor / impor), tetapi mendukung modul CommonJS (module.exports / require)): index.html.js, css / style.css.js , script.js, dll. Isi file diekspor secara default:



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

      
      





Perhatikan bahwa dengan pendekatan ini semua gambar (dalam kasus kami, ikon) harus dikodekan Base64: berikut adalah satu alat online yang sesuai . Kehadiran baris "data: image / png; base64", di awal file yang dikonversi bukanlah hal yang penting.



Kami akan menggunakan fs-extra untuk menyalin (menulis) file . Metode outputFile dari pustaka ini melakukan hal yang sama seperti metode writeFile Node.js bawaan, tetapi juga membuat direktori untuk file yang sedang ditulis jika tidak ada: misalnya, jika kita menentukan create css / style.css, dan direktori css tidak ada, outputFile akan membuatnya dan akan menulis style.css di sana (writeFile akan menampilkan pengecualian jika tidak ada direktori).



File extension.ts terlihat seperti ini:



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() {}

      
      





Untuk mencegah TypeScript memperhatikan kurangnya jenis file modul yang diimpor, buat src / global.d.ts dengan konten berikut:



declare module '*'

      
      





Mari kita uji ekstensinya. Buka di editor:



cd htmltemplate
code .

      
      





Mulai debugging (F5). Buka direktori target (test-dir, misalnya) dan jalankan perintah create di Command Palette.







Kami menerima pesan informasi tentang keberhasilan pembuatan file. Hore!







Menerbitkan ekstensi ke Visual Studio Marketplace


Agar dapat menerbitkan ekstensi untuk VSCode, Anda perlu melakukan hal berikut:





Mengedit 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"
      }
    ]
  },
  ...
}

      
      





Mengedit README.md.



Jalankan perintah paket vsce di direktori ekstensi untuk membuat paket yang diterbitkan dengan ekstensi vsix. Kami mendapatkan file htmltemplate-1.0.0.vsix.



Di halaman untuk mengelola ekstensi pasar, klik tombol Ekstensi baru dan pilih Kode Visual Studio. Transfer atau muat file VSIX ke jendela modal. Kami menunggu verifikasi selesai.







Setelah tanda centang hijau muncul di sebelah nomor versi, ekstensi tersebut menjadi tersedia untuk instalasi di VSCode.







Untuk memperbarui ekstensi, Anda perlu mengubah nomor versi di package.json, membuat file VSIX dan mengunggahnya ke pasar dengan mengklik tombol Tindakan lainnya dan memilih Perbarui.



Seperti yang Anda lihat, tidak ada yang supernatural tentang membuat dan menerbitkan ekstensi untuk VSCode. Dalam hal ini, biarkan aku pergi.



Di bagian selanjutnya, kita akan membuat antarmuka baris perintah lengkap, pertama menggunakan kerangka Heroku - oclif , lalu tanpa itu. Node.js-CLI kami akan sangat berbeda dari ekstensi, ia akan memiliki beberapa visualisasi, kemampuan untuk menginisialisasi git dan menginstal dependensi secara opsional.



Saya harap Anda menemukan sesuatu yang menarik untuk diri Anda sendiri. Terima kasih atas perhatian Anda.



All Articles