13
6

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 1 year has passed since last update.

AWS CDKAdvent Calendar 2021

Day 10

Vim で AWS CDK for TypeScript を快適に書くための 12 のプラグイン

Last updated at Posted at 2021-12-10

こちらは AWS CDK Advent Calendar 2021 の 10 日目の記事です。

こんにちは、高野(@konokenj)です。AWS CDK v2 がリリースされましたね!
VSCode で AWS CDK を開発するガイドはいくつかありますが、今回は私の好きな Vim で開発するためのプラグインと設定を紹介します。
後半では、Amazon EC2 インスタンス上に構築した Ubuntu 20.04 の環境でゼロから Vim を設定し、AWS CDK を記述してデプロイする手順を紹介します。

vim-lsp を中心として TypeScript の開発に必要な最小限の Vim プラグインを組み込んだ vimrc の全文を掲載しています。この設定を適用すると、次のようなイメージで編集が可能になります。

qiita-aws-cdk-on-vim.gif

import 文を書かなくても自動的にモジュールが追加されている様子が分かるでしょうか。ここにもシングルパッケージ化された AWS CDK v2 のメリットが現れていますね。
入力中の構文チェック結果や入力補完、リファクタリング機能として識別子のリネームと定義元へのジャンプも試しています。TypeScript の LSP だけでなく、ESLint と Prettier も連携されています。

Vim の設定には AWS CDK 固有の設定は必要ありません。TypeScript での開発において汎用的に使える内容となっています。Vim は使い慣れているけれど VSCode はあまり使い込んでいない、という方が AWS CDK や TypeScript の開発にチャレンジするきっかけになるとうれしいです。

Vimrc とプラグインの解説

バッファを編集する操作は主に <leader> キーを使って設定しています。<leader><space> にしていますが、お好みで変更してください。

vimrc
let g:mapleader = "\<Space>"

vim-plug

Vim プラグインの管理には vim-plug を使用しています。シンプルな設定で外部依存がなく、非同期読み込みにも対応しているためです。

vim-plug 自体の読み込み処理は vimrc には設定されていません。インストールするには curl を使用して Vim script を ~/.vim/autoload/ 配下に Vim スクリプトをダウンロードします。このパスにあるスクリプトは Vim の起動時に自動で読み込まれます。詳細は :h autoload を参照してください。

vim-lsp

プログラミング言語のオートコンプリート(補完)、定義箇所へのジャンプ、関数のシグネチャやドキュメントの表示、コンパイルエラー情報の表示などの機能は、Language Server Protocol(LSP) によって提供されるのが一般的になってきました。

テキストエディタのプラグインが LSP とやりとりするインタフェースを実装していれば、ユーザーは使いたいプログラミング言語の LSP サーバを導入するだけでよいわけです。 Vim では、LSP のサポートに vim-lsp を使用します。

vim-lsp はインストールしただけではキーマッピングを定義しないため、自分で任意の設定を行う必要があります。また、LSP が有効になっているバッファだけでキーマッピングを定義するために autocmd を使用して関数を呼び出しています。

vimrc
" vim-lsp がバッファで有効になったときに実行される関数
" バッファローカルなキーバインドやオプションを指定
" See: https://mattn.kaoriya.net/software/vim/20191231213507.htm
function! s:on_lsp_buffer_enabled() abort
    if &ft =~ 'ctrlp\|dirvish'
        return
    endif
    setlocal omnifunc=lsp#complete
    setlocal signcolumn=yes
    nmap <buffer> <leader>a <plug>(lsp-code-action)
    nmap <buffer> <leader>l <plug>(lsp-code-lens)
    nmap <buffer> <leader>L <plug>(lsp-document-diagnostics)
    " nmap <buffer> <leader>d <plug>(lsp-decralation)
    nmap <buffer> <leader>D <plug>(lsp-definition)
    nmap <buffer> <leader>y <plug>(lsp-type-definition)
    nmap <buffer> <leader>i <plug>(lsp-implementation)
    nmap <buffer> <leader>r <plug>(lsp-references)
    nmap <buffer> <leader>R <plug>(lsp-rename)
    nmap <buffer> <leader>S <plug>(lsp-document-symbol)
    nmap <silent><buffer> <C-j> <plug>(lsp-next-diagnostic)
    nmap <silent><buffer> <C-k> <plug>(lsp-previous-diagnostic)

    if &ft =~ 'typescript\|javascript'
        nmap <buffer> <leader>f :LspDocumentFormatSync --server=efm-langserver<CR>
        xmap <buffer> <leader>f :LspDocumentRangeFormatSync --server=efm-langserver<CR>
    else
        nmap <buffer> <leader>f <plug>(lsp-document-format)
        xmap <buffer> <leader>f <plug>(lsp-document-range-format)
    endif
endfunction
augroup lsp_install
  au!
  autocmd User lsp_buffer_enabled call s:on_lsp_buffer_enabled()
augroup END
" vim-lsp デバッグログの出力
command! LspDebug let lsp_log_verbose=1 | let lsp_log_file = expand('~/lsp.log')

" lightline.vim のステータスラインに vim-lsp diagnostics の数を表示する
" augroup LightLineOnLSP
"   autocmd!
"   autocmd User lsp_diagnostics_updated call lightline#update()
" augroup END

let g:lsp_diagnostics_echo_cursor = 1

Plug 'prabirshrestha/vim-lsp'

vim-lsp-settings

vim-lsp に各プログラミング言語の LSP サーバを設定するには多くの設定が必要です。これをシンプルにするため、 vim-lsp-settings 使用します。たとえば TypeScript ファイルを開いたら typescript-language-server のインストールをサジェストし、:LspInstallServer コマンドを実行するとこれを自動的にインストールして vim-lsp を設定してくれます。

vim-lsp-settings の設定方法は 2 つあります。vimrc 内で g:lsp_settings を使用する方法と、:LspSettingsGlobalEdit コマンド経由で JSON ファイルを編集する方法(通常は ~/.local/share/vim-lsp-settings/settings.json に保存)です。今回は前者を採用しています。

vimrc
Plug 'mattn/vim-lsp-settings'

efm-langserver

ESLint や Prettier など、LSP に準拠しない開発ツールも存在します。これらのツールを LSP のインタフェースに合わせて呼び出せるようにするのが efm-langserver です。efm-langserver は設定次第で任意のツールを非同期に呼び出すことができます。これによって ALESyntastic を必要とせず、vim-lsp だけでシンプルに完結できます。余談ですが、efm って何?と思った方は :h efm を参照してください。

efm-langserver は Golang で実装されたサーバ本体と Vim プラグインがセットになっています。まず、サーバをインストールします。

# go v1.16 以上の場合
go install github.com/mattn/efm-langserver@latest

efm-langserver の設定ファイルは通常 ~/.config/efm-langserver/config.yaml が使用されます。このファイルを次の内容で作成します。TypeScript 以外のツールの設定を行うには、GitHub の README に書かれているサンプルを参考にしてください。私はこの記事の執筆にも、efm-langserver 経由で textlint を使用しています。

log-file のパスは適宜修正してください。

version: 2
root-markers:
  - .git/
lint-debounce: 1s
log-file: /home/ubuntu/efm-langserver.log # FIXME
log-level: 1
tools:
  javascript-eslint: &javascript-eslint
    lint-command: './node_modules/.bin/eslint -f visualstudio --stdin --stdin-filename ${INPUT}'
    lint-ignore-exit-code: true
    lint-stdin: true
    lint-formats:
      - "%f(%l,%c): %tarning %m"
      - "%f(%l,%c): %rror %m"

  typescript-prettier: &typescript-prettier
    format-command: './node_modules/.bin/prettier ${--tab-width:tabWidth} ${--single-quote:singleQuote} --parser typescript --stdin-filepath ${INPUT}'
    format-stdin: true

languages:
  typescript:
    - <<: *javascript-eslint
    - <<: *typescript-prettier

vimrc には、vim-lsp-settings を使って efm-langserver を認識させる設定を入れます。
また、TypeScript ファイルの保存前に自動的に Prettier を呼び出すための autocmd も定義しています。

vimrc
" vim-lsp 経由でバッファの保存前にフォーマットをかける
" See: https://zenn.dev/yami_beta/articles/589c567199ea28
augroup MyLSPTypeScript
    autocmd!
    autocmd BufWritePre *.ts,*.tsx call execute('LspDocumentFormatSync --server=efm-langserver')
augroup END

let g:lsp_settings = {
    \ 'efm-langserver': {
        \ 'disabled': v:false
    \ },
\ }
Plug 'mattn/efm-langserver'

asyncomplete.vim

補完(オートコンプリート)には vim-lsp と同じ作者で相性の良い asyncomplete を使用します。このプラグインは依存関係として async.vim が必要であることと、vim-lsp と連携させるために asyncomplete-lsp.vim も一緒にインストールします。

vimrc
let g:asyncomplete_popup_delay = 200
Plug 'prabirshrestha/async.vim'
Plug 'prabirshrestha/asyncomplete.vim'
Plug 'prabirshrestha/asyncomplete-lsp.vim'

vim-vsnip

スニペットには vim-vsnip を使用します。VSCode のスニペットと互換性がり、Vim script のみで実装されているシンプルなプラグインです。これと vim-lsp を連携させるために vim-vsnip-integ と、スニペットライブラリの friendly-snippets もを合わせてインストールします。

vim-vsnip もデフォルトのキーマッピングを持ちません。ヘルプ :h vim-vsnip の例をそのまま使用しました。補完のポップアップが表示されているときに候補を確定してすぐに改行したいケースがあるため、<CR> のマッピングを追加しています。

vimrc
let g:vsnip_snippet_dir = expand($XDG_CONFIG_HOME . '/vsnip')
" 補完のポップアップメニュー表示中
"   <CR> で候補を確定
inoremap <expr><CR> pumvisible() ? "\<c-y>" : "\<cr>"
" :h vsnip-mapping
" Expand
imap <expr> <C-j>   vsnip#expandable()  ? '<Plug>(vsnip-expand)'         : '<C-j>'
smap <expr> <C-j>   vsnip#expandable()  ? '<Plug>(vsnip-expand)'         : '<C-j>'
" Expand or jump
imap <expr> <C-l>   vsnip#available(1)  ? '<Plug>(vsnip-expand-or-jump)' : '<C-l>'
smap <expr> <C-l>   vsnip#available(1)  ? '<Plug>(vsnip-expand-or-jump)' : '<C-l>'
" Jump forward or backward
imap <expr> <Tab>   vsnip#jumpable(1)   ? '<Plug>(vsnip-jump-next)'      : '<Tab>'
smap <expr> <Tab>   vsnip#jumpable(1)   ? '<Plug>(vsnip-jump-next)'      : '<Tab>'
imap <expr> <S-Tab> vsnip#jumpable(-1)  ? '<Plug>(vsnip-jump-prev)'      : '<S-Tab>'
smap <expr> <S-Tab> vsnip#jumpable(-1)  ? '<Plug>(vsnip-jump-prev)'      : '<S-Tab>'

Plug 'hrsh7th/vim-vsnip'
Plug 'hrsh7th/vim-vsnip-integ'
Plug 'rafamadriz/friendly-snippets'

editoconfig-vim

EditorConfig は、プロジェクト内でコードのフォーマット(インデントスタイルや改行コードなど)を統一するためのアプリケーションです。チーム開発でのトラブルを避けるため、Vim に限らずすべてのエディタで有効にしておくとよいでしょう。余談ですが、以前は Vim に +python が必要でしたが今は Vim script で実装された core がプラグインに含まれるようになったため外部依存がなくなり、Vim script のみで動作します。

vimrc
Plug 'editorconfig/editorconfig-vim'

vim-commentary

コメントアウト、アンコメントを行うプラグインです。gc などデフォルトのキーマッピングを持ちますので、:h commentary を参照してください。

vimrc
Plug 'tpope/vim-commentary'

lexima.vim

VSCode などでよくある、閉じ括弧良い感じに入力してくれるプラグインです。

vimrc
Plug 'cohama/lexima.vim'

完成形

全体の vimrc は以下の通りになります。

vimrc
scriptencoding utf-8

let g:mapleader = "\<Space>"

call plug#begin('~/.local/share/vim/bundle')

" vim-lsp がバッファで有効になったときに実行される関数
" バッファローカルなキーバインドやオプションを指定
" See: https://mattn.kaoriya.net/software/vim/20191231213507.htm
function! s:on_lsp_buffer_enabled() abort
    if &ft =~ 'ctrlp\|dirvish'
        return
    endif
    setlocal omnifunc=lsp#complete
    setlocal signcolumn=yes
    nmap <buffer> <leader>a <plug>(lsp-code-action)
    nmap <buffer> <leader>l <plug>(lsp-code-lens)
    nmap <buffer> <leader>L <plug>(lsp-document-diagnostics)
    " nmap <buffer> <leader>d <plug>(lsp-decralation)
    nmap <buffer> <leader>D <plug>(lsp-definition)
    nmap <buffer> <leader>y <plug>(lsp-type-definition)
    nmap <buffer> <leader>i <plug>(lsp-implementation)
    nmap <buffer> <leader>r <plug>(lsp-references)
    nmap <buffer> <leader>R <plug>(lsp-rename)
    nmap <buffer> <leader>S <plug>(lsp-document-symbol)
    nmap <silent><buffer> <C-j> <plug>(lsp-next-diagnostic)
    nmap <silent><buffer> <C-k> <plug>(lsp-previous-diagnostic)

    if &ft =~ 'typescript\|javascript'
        nmap <buffer> <leader>f :LspDocumentFormatSync --server=efm-langserver<CR>
        xmap <buffer> <leader>f :LspDocumentRangeFormatSync --server=efm-langserver<CR>
    else
        nmap <buffer> <leader>f <plug>(lsp-document-format)
        xmap <buffer> <leader>f <plug>(lsp-document-range-format)
    endif
endfunction
augroup lsp_install
  au!
  autocmd User lsp_buffer_enabled call s:on_lsp_buffer_enabled()
augroup END

" vim-lsp デバッグログの出力
command! LspDebug let lsp_log_verbose=1 | let lsp_log_file = expand('~/lsp.log')

" lightline.vim のステータスラインに vim-lsp diagnostics の数を表示する
" augroup LightLineOnLSP
"   autocmd!
"   autocmd User lsp_diagnostics_updated call lightline#update()
" augroup END

" vim-lsp 経由でバッファの保存前にフォーマットをかける
" See: https://zenn.dev/yami_beta/articles/589c567199ea28
augroup MyLSPTypeScript
    autocmd!
    autocmd BufWritePre *.ts,*.tsx call execute('LspDocumentFormatSync --server=efm-langserver')
augroup END

" asyncomplete.vim
let g:asyncomplete_popup_delay = 200

" vim-lsp
let g:lsp_diagnostics_echo_cursor = 1
let g:lsp_settings = {
    \ 'efm-langserver': {
        \ 'disabled': v:false
    \ },
\ }

" vim-vsnip
let g:vsnip_snippet_dir = expand($XDG_CONFIG_HOME . '/vsnip')
" 補完のポップアップメニュー表示中
"   <CR> で候補を確定
inoremap <expr><CR> pumvisible() ? "\<c-y>" : "\<cr>"
" :h vsnip-mapping
" Expand
imap <expr> <C-j>   vsnip#expandable()  ? '<Plug>(vsnip-expand)'         : '<C-j>'
smap <expr> <C-j>   vsnip#expandable()  ? '<Plug>(vsnip-expand)'         : '<C-j>'
" Expand or jump
imap <expr> <C-l>   vsnip#available(1)  ? '<Plug>(vsnip-expand-or-jump)' : '<C-l>'
smap <expr> <C-l>   vsnip#available(1)  ? '<Plug>(vsnip-expand-or-jump)' : '<C-l>'
" Jump forward or backward
imap <expr> <Tab>   vsnip#jumpable(1)   ? '<Plug>(vsnip-jump-next)'      : '<Tab>'
smap <expr> <Tab>   vsnip#jumpable(1)   ? '<Plug>(vsnip-jump-next)'      : '<Tab>'
imap <expr> <S-Tab> vsnip#jumpable(-1)  ? '<Plug>(vsnip-jump-prev)'      : '<S-Tab>'
smap <expr> <S-Tab> vsnip#jumpable(-1)  ? '<Plug>(vsnip-jump-prev)'      : '<S-Tab>'

Plug 'prabirshrestha/async.vim'
Plug 'prabirshrestha/asyncomplete.vim'
Plug 'prabirshrestha/asyncomplete-lsp.vim'
Plug 'prabirshrestha/vim-lsp'
Plug 'mattn/vim-lsp-settings'
Plug 'mattn/efm-langserver'
Plug 'hrsh7th/vim-vsnip'
Plug 'hrsh7th/vim-vsnip-integ'
Plug 'rafamadriz/friendly-snippets'

" editorconfig-vim
"   .editorconfig ファイルを参照してインデントや改行コードなどをプロジェクト内で統一
Plug 'editorconfig/editorconfig-vim'

" vim-commentary
"   `gc` でコメント、アンコメント
Plug 'tpope/vim-commentary'

" lexima.vim
"   閉じカッコを良い感じに補完
Plug 'cohama/lexima.vim'

call plug#end()

Amazon EC2 で Vim を設定して AWS CDK をデプロイしてみる

vimrc の動作検証のため、新しい Amazon EC2 インスタンスで Vim をインストールして AWS CDK を実装し、デプロイしてみます。手元の環境で実行する場合はパッケージのインストールなどの不要な部分を適宜読み飛ばしてください。

今回は Ubuntu 20.04 がインストールされた Amazon EC2 c5.large インスタンスを用意しました。

Vim と Golang のインストール

Golang は efm-langserver のインストールのために使用しました。なお、efm-langserver はバイナリ版も提供されているため、GitHub Release から直接インストールする場合 Golang のインストールは不要です。

$ sudo apt-get update
$ sudo apt-get install vim golang-go unzip -y
$ go version
go version go1.13.8 linux/amd64
$ vim --version
VIM - Vi IMproved 8.1 (2018 May 18, compiled Nov 08 2021 14:21:34)
(省略)

AWS CLI v2 のインストールと設定

こちらを参照してください。aws configure で適宜クレデンシャルを設定します。

curl "https://awscli.amazonaws.com/awscli-exe-linux-x86_64.zip" -o "awscliv2.zip"
unzip awscliv2.zip
sudo ./aws/install
aws --version
aws configure

vim-plug のインストール

最新の手順は GitHub を確認してください。

curl -fLo ~/.vim/autoload/plug.vim --create-dirs \
    https://raw.githubusercontent.com/junegunn/vim-plug/master/plug.vim

efm-langserver のインストール

~/.profile に GOPATH を定義して、パスを通します。

.profile
export GOPATH=~/go
export PATH=$PATH:$GOPATH/bin

efm-langserver をインストールします。Ubuntu 20.04 では Golang のバージョンが少し古いため go get を使用しましたが、 Go v1.16 以上の場合は go install を使用してください。

# go v1.16 未満の場合
go get github.com/mattn/efm-langserver

# go v1.16 以上の場合
go install github.com/mattn/efm-langserver@latest

次に設定ファイルを作成します。設定ファイルの内容は こちら からコピー&ペーストしてください。

mkdir -p ~/.config/efm-langserver
touch ~/.config/efm-langserver/config.yaml
vim ~/.config/efm-langserver/config.yaml

設定ファイルを作成したら、動作確認しておきます。エラーなく起動できることを確認したら、Ctrl-C で終了します。

efm-langserver

Node.js のインストール

nvm を使用して LTS 版の Node.js をインストールします。合わせて npm もアップデートしておきます。

curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.37.2/install.sh | bash
. ~/.nvm/nvm.sh
nvm ls-remote --lts
nvm install --lts=gallium
nvm use --lts=gallium
npm i -g npm
node --version
npm --version

AWS CDK プロジェクトの作成

こちらの手順を参考に hello world してみます。

mkdir hello-cdk
cd hello-cdk
git init .
git config user.name hoge
git config user.email hoge@example.com
git commit --allow-empty -m "initial commit"
npx -p aws-cdk cdk init --language typescript
git add -A
git commit -m "cdk init"
npm run build
npx cdk ls

ESLint, Prettier, EditorConfig の導入

ESLint と Prettier をインストールします。

npm i -D \
    @typescript-eslint/eslint-plugin \
    @typescript-eslint/parser \
    eslint \
    eslint-config-prettier \
    prettier

.eslintrc.json ファイルを以下の内容で作成します。

.eslintrc.json
{
    "extends": [
        "eslint:recommended",
        "plugin:@typescript-eslint/recommended",
        "prettier"
    ],
    "parser": "@typescript-eslint/parser",
    "parserOptions": {
        "project": "./tsconfig.json"
    },
    "plugins": [
        "@typescript-eslint"
    ],
    "rules": {
    }
}

.editorconfig ファイルを以下の内容で作成します。なお、前述の通り editorconfig-vim は EditorConfig Core を含むためプロジェクト単位で npm i editorconfig を実行する必要はありません。

root = true

# base settings
[*]
indent_style = space
indent_size = 2
end_of_line = lf
charset = utf-8
trim_trailing_whitespace = true
insert_final_newline = true

# by extensions
[*.sh]
end_of_line = lf
[*.bat]
end_of_line = crlf
[*.md]
trim_trailing_whitespace = false
indent_size = 4
[*.{yml,yaml}]
indent_size = 4
[{Makefile,go.mod,go.sum,*.go}]
indent_style = tab
indent_size = 4

Vim で編集

vimrc を配置します。内容はこちらをコピー&ペーストしてください。

mkdir -p ~/.vim
vim ~/.vim/vimrc

Vim をいったん終了し、再起動します。

vim

プラグインをインストールするために :PlugInstall を実行して、成功したら Vim を一度終了します。

vimrc
:PlugInstall

Vim で TypeScript ファイルを編集してみます。

vim lib/hello-cdk-stack.ts

qiita-aws-cdk-on-vim-1.jpg

:LspInstallServer を実行するようにサジェストされるため、これに従います。

vimrc
:LspInstallServer

あとは思う存分コードを編集していきましょう。記事の冒頭で紹介した Gif アニメによるデモのような編集が可能になっています。参考までに、作成していたコードは以下です。

hello-cdk-stack.ts
import { Stack, StackProps, aws_s3, aws_iam } from "aws-cdk-lib";
import { Construct } from "constructs";

export class HelloCdkStack extends Stack {
  constructor(scope: Construct, id: string, props?: StackProps) {
    super(scope, id, props);

    const myBucket = new s3.Bucket(this, "MyBucket", {
      blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL,
    });

    const myLambdaRole = new iam.Role(this, 'MyLambdaRole', {
      assumedBy: new iam.ServicePrincipal('lambda.amazonaws.com'),
    })

    myBucket.grantRead(myLambdaRole);
  }
}

LSP は非同期に起動されているため、Vim を起動した直後は補完やコードアクションが実行されない場合があります。通常は Vim を立ち上げたまま複数のファイルを編集するためあまり問題にはなりませんが、今回のデモのように 1 ファイルをさっと編集してすぐに Vim を終了するようなケースでは、補完が動き始めるまで数秒待機する必要があることもあります。

数秒待っても補完がうまくいかない場合は :LspStatus を実行して LSP サーバの実行状態を確認してください。また、:LspDebug を実行することで ~/lsp.log にログが出力されます。

ESLint は実行に時間がかかるため、typescript-language-server よりも遅れて結果が表示されます。

AWS CDK をデプロイ

npx cdk bootstrap
npx cdk synth
npx cdk deploy

終わりに

Vim でも VSCode などのモダンなエディタと遜色なく開発支援機能を活用できます。ここで紹介している通り、書かなければならない vimrc のコード量も非常に少なくなっています。Vim には慣れていても、なんとなくモダンなエディタや言語に壁を感じている方は、ぜひこの機会に試してみてください。Happy Coding!

13
6
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
13
6

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?