Note

3年後の自分のために書いています

rails, rack, unicorn or puma, nginx の関係についてちゃんと理解したい

だいぶメモ書きです。

表題の件、まだ曖昧にしか理解できてない。

ググっても浅い情報しか出てこないし、各ライブラリのソースコードを読んでみたけどどう繋がってるか、それぞれどんな役割を担っているか把握するのムズいなぁ…。

結論

色々調べてここまで分かった。

  • 「クライアント側 nginx <-> puma or unicorn <-> rack <-> rails DB 側」 的な感じ
  • rails(や sinatra)はつまるところ rack の仕様に沿って env(HTTPリクエスト)をもらって [status, headers, body](HTTP レスポンス)を返すだけのプログラムである

rack

HTTP server としてのインターフェースを提供する。 #call とか。

Rackとは何か - Qiita

rails

rack アプリケーション。 rack の仕様に沿った #call を実装する。

とりあえず actionpackAction Dispatch でリクエストをさばき、 Action Controller でレスポンスを生成していく感じなのかな?

Response#rack_response でレスポンス生成してる?

ちなみに以下のようなコードを差し込んだ時のログはこんな感じ。

class HogeController < ApplicationController
  def index
    caller.each{ |line| p line}
    ...
  end
end

ログ

Started GET "/" for 127.0.0.1 at 2019-09-17 11:10:00 +0900
Processing by WelcomeController#index as HTML
"/Users/daidoshota/training/perfect_rails_awesome_events/vendor/bundle/ruby/2.5.0/gems/actionpack-5.2.0/lib/action_controller/metal/basic_implicit_render.rb:6:in `send_action'"
"/Users/daidoshota/training/perfect_rails_awesome_events/vendor/bundle/ruby/2.5.0/gems/actionpack-5.2.0/lib/abstract_controller/base.rb:194:in `process_action'"
"/Users/daidoshota/training/perfect_rails_awesome_events/vendor/bundle/ruby/2.5.0/gems/actionpack-5.2.0/lib/action_controller/metal/rendering.rb:30:in `process_action'"
"/Users/daidoshota/training/perfect_rails_awesome_events/vendor/bundle/ruby/2.5.0/gems/actionpack-5.2.0/lib/abstract_controller/callbacks.rb:42:in `block in process_action'"
"/Users/daidoshota/training/perfect_rails_awesome_events/vendor/bundle/ruby/2.5.0/gems/activesupport-5.2.0/lib/active_support/callbacks.rb:132:in `run_callbacks'"
"/Users/daidoshota/training/perfect_rails_awesome_events/vendor/bundle/ruby/2.5.0/gems/actionpack-5.2.0/lib/abstract_controller/callbacks.rb:41:in `process_action'"
"/Users/daidoshota/training/perfect_rails_awesome_events/vendor/bundle/ruby/2.5.0/gems/actionpack-5.2.0/lib/action_controller/metal/rescue.rb:22:in `process_action'"
"/Users/daidoshota/training/perfect_rails_awesome_events/vendor/bundle/ruby/2.5.0/gems/actionpack-5.2.0/lib/action_controller/metal/instrumentation.rb:34:in `block in process_action'"
"/Users/daidoshota/training/perfect_rails_awesome_events/vendor/bundle/ruby/2.5.0/gems/activesupport-5.2.0/lib/active_support/notifications.rb:168:in `block in instrument'"
"/Users/daidoshota/training/perfect_rails_awesome_events/vendor/bundle/ruby/2.5.0/gems/activesupport-5.2.0/lib/active_support/notifications/instrumenter.rb:23:in `instrument'"
"/Users/daidoshota/training/perfect_rails_awesome_events/vendor/bundle/ruby/2.5.0/gems/activesupport-5.2.0/lib/active_support/notifications.rb:168:in `instrument'"
"/Users/daidoshota/training/perfect_rails_awesome_events/vendor/bundle/ruby/2.5.0/gems/actionpack-5.2.0/lib/action_controller/metal/instrumentation.rb:32:in `process_action'"
"/Users/daidoshota/training/perfect_rails_awesome_events/vendor/bundle/ruby/2.5.0/gems/actionpack-5.2.0/lib/action_controller/metal/params_wrapper.rb:256:in `process_action'"
"/Users/daidoshota/training/perfect_rails_awesome_events/vendor/bundle/ruby/2.5.0/gems/activerecord-5.2.0/lib/active_record/railties/controller_runtime.rb:24:in `process_action'"
"/Users/daidoshota/training/perfect_rails_awesome_events/vendor/bundle/ruby/2.5.0/gems/actionpack-5.2.0/lib/abstract_controller/base.rb:134:in `process'"
"/Users/daidoshota/training/perfect_rails_awesome_events/vendor/bundle/ruby/2.5.0/gems/actionview-5.2.0/lib/action_view/rendering.rb:32:in `process'"
"/Users/daidoshota/training/perfect_rails_awesome_events/vendor/bundle/ruby/2.5.0/gems/actionpack-5.2.0/lib/action_controller/metal.rb:191:in `dispatch'"
"/Users/daidoshota/training/perfect_rails_awesome_events/vendor/bundle/ruby/2.5.0/gems/actionpack-5.2.0/lib/action_controller/metal.rb:252:in `dispatch'"
"/Users/daidoshota/training/perfect_rails_awesome_events/vendor/bundle/ruby/2.5.0/gems/actionpack-5.2.0/lib/action_dispatch/routing/route_set.rb:52:in `dispatch'"
"/Users/daidoshota/training/perfect_rails_awesome_events/vendor/bundle/ruby/2.5.0/gems/actionpack-5.2.0/lib/action_dispatch/routing/route_set.rb:34:in `serve'"
"/Users/daidoshota/training/perfect_rails_awesome_events/vendor/bundle/ruby/2.5.0/gems/actionpack-5.2.0/lib/action_dispatch/journey/router.rb:52:in `block in serve'"
"/Users/daidoshota/training/perfect_rails_awesome_events/vendor/bundle/ruby/2.5.0/gems/actionpack-5.2.0/lib/action_dispatch/journey/router.rb:35:in `each'"
"/Users/daidoshota/training/perfect_rails_awesome_events/vendor/bundle/ruby/2.5.0/gems/actionpack-5.2.0/lib/action_dispatch/journey/router.rb:35:in `serve'"
"/Users/daidoshota/training/perfect_rails_awesome_events/vendor/bundle/ruby/2.5.0/gems/actionpack-5.2.0/lib/action_dispatch/routing/route_set.rb:840:in `call'"
"/Users/daidoshota/training/perfect_rails_awesome_events/vendor/bundle/ruby/2.5.0/gems/omniauth-1.8.1/lib/omniauth/strategy.rb:190:in `call!'"
"/Users/daidoshota/training/perfect_rails_awesome_events/vendor/bundle/ruby/2.5.0/gems/omniauth-1.8.1/lib/omniauth/strategy.rb:168:in `call'"
"/Users/daidoshota/training/perfect_rails_awesome_events/vendor/bundle/ruby/2.5.0/gems/omniauth-1.8.1/lib/omniauth/builder.rb:63:in `call'"
"/Users/daidoshota/training/perfect_rails_awesome_events/vendor/bundle/ruby/2.5.0/gems/rack-2.0.5/lib/rack/tempfile_reaper.rb:15:in `call'"
"/Users/daidoshota/training/perfect_rails_awesome_events/vendor/bundle/ruby/2.5.0/gems/rack-2.0.5/lib/rack/etag.rb:25:in `call'"
"/Users/daidoshota/training/perfect_rails_awesome_events/vendor/bundle/ruby/2.5.0/gems/rack-2.0.5/lib/rack/conditional_get.rb:25:in `call'"
"/Users/daidoshota/training/perfect_rails_awesome_events/vendor/bundle/ruby/2.5.0/gems/rack-2.0.5/lib/rack/head.rb:12:in `call'"
"/Users/daidoshota/training/perfect_rails_awesome_events/vendor/bundle/ruby/2.5.0/gems/actionpack-5.2.0/lib/action_dispatch/http/content_security_policy.rb:18:in `call'"
"/Users/daidoshota/training/perfect_rails_awesome_events/vendor/bundle/ruby/2.5.0/gems/rack-2.0.5/lib/rack/session/abstract/id.rb:232:in `context'"
"/Users/daidoshota/training/perfect_rails_awesome_events/vendor/bundle/ruby/2.5.0/gems/rack-2.0.5/lib/rack/session/abstract/id.rb:226:in `call'"
"/Users/daidoshota/training/perfect_rails_awesome_events/vendor/bundle/ruby/2.5.0/gems/actionpack-5.2.0/lib/action_dispatch/middleware/cookies.rb:670:in `call'"
"/Users/daidoshota/training/perfect_rails_awesome_events/vendor/bundle/ruby/2.5.0/gems/activerecord-5.2.0/lib/active_record/migration.rb:559:in `call'"
"/Users/daidoshota/training/perfect_rails_awesome_events/vendor/bundle/ruby/2.5.0/gems/actionpack-5.2.0/lib/action_dispatch/middleware/callbacks.rb:28:in `block in call'"
"/Users/daidoshota/training/perfect_rails_awesome_events/vendor/bundle/ruby/2.5.0/gems/activesupport-5.2.0/lib/active_support/callbacks.rb:98:in `run_callbacks'"
"/Users/daidoshota/training/perfect_rails_awesome_events/vendor/bundle/ruby/2.5.0/gems/actionpack-5.2.0/lib/action_dispatch/middleware/callbacks.rb:26:in `call'"
"/Users/daidoshota/training/perfect_rails_awesome_events/vendor/bundle/ruby/2.5.0/gems/actionpack-5.2.0/lib/action_dispatch/middleware/executor.rb:14:in `call'"
"/Users/daidoshota/training/perfect_rails_awesome_events/vendor/bundle/ruby/2.5.0/gems/actionpack-5.2.0/lib/action_dispatch/middleware/debug_exceptions.rb:61:in `call'"
"/Users/daidoshota/training/perfect_rails_awesome_events/vendor/bundle/ruby/2.5.0/gems/web-console-3.6.2/lib/web_console/middleware.rb:135:in `call_app'"
"/Users/daidoshota/training/perfect_rails_awesome_events/vendor/bundle/ruby/2.5.0/gems/web-console-3.6.2/lib/web_console/middleware.rb:30:in `block in call'"
"/Users/daidoshota/training/perfect_rails_awesome_events/vendor/bundle/ruby/2.5.0/gems/web-console-3.6.2/lib/web_console/middleware.rb:20:in `catch'"
"/Users/daidoshota/training/perfect_rails_awesome_events/vendor/bundle/ruby/2.5.0/gems/web-console-3.6.2/lib/web_console/middleware.rb:20:in `call'"
"/Users/daidoshota/training/perfect_rails_awesome_events/vendor/bundle/ruby/2.5.0/gems/actionpack-5.2.0/lib/action_dispatch/middleware/show_exceptions.rb:33:in `call'"
"/Users/daidoshota/training/perfect_rails_awesome_events/vendor/bundle/ruby/2.5.0/gems/railties-5.2.0/lib/rails/rack/logger.rb:38:in `call_app'"
"/Users/daidoshota/training/perfect_rails_awesome_events/vendor/bundle/ruby/2.5.0/gems/railties-5.2.0/lib/rails/rack/logger.rb:26:in `block in call'"
"/Users/daidoshota/training/perfect_rails_awesome_events/vendor/bundle/ruby/2.5.0/gems/activesupport-5.2.0/lib/active_support/tagged_logging.rb:71:in `block in tagged'"
"/Users/daidoshota/training/perfect_rails_awesome_events/vendor/bundle/ruby/2.5.0/gems/activesupport-5.2.0/lib/active_support/tagged_logging.rb:28:in `tagged'"
"/Users/daidoshota/training/perfect_rails_awesome_events/vendor/bundle/ruby/2.5.0/gems/activesupport-5.2.0/lib/active_support/tagged_logging.rb:71:in `tagged'"
"/Users/daidoshota/training/perfect_rails_awesome_events/vendor/bundle/ruby/2.5.0/gems/railties-5.2.0/lib/rails/rack/logger.rb:26:in `call'"
"/Users/daidoshota/training/perfect_rails_awesome_events/vendor/bundle/ruby/2.5.0/gems/sprockets-rails-3.2.1/lib/sprockets/rails/quiet_assets.rb:13:in `call'"
"/Users/daidoshota/training/perfect_rails_awesome_events/vendor/bundle/ruby/2.5.0/gems/actionpack-5.2.0/lib/action_dispatch/middleware/remote_ip.rb:81:in `call'"
"/Users/daidoshota/training/perfect_rails_awesome_events/vendor/bundle/ruby/2.5.0/gems/actionpack-5.2.0/lib/action_dispatch/middleware/request_id.rb:27:in `call'"
"/Users/daidoshota/training/perfect_rails_awesome_events/vendor/bundle/ruby/2.5.0/gems/rack-2.0.5/lib/rack/method_override.rb:22:in `call'"
"/Users/daidoshota/training/perfect_rails_awesome_events/vendor/bundle/ruby/2.5.0/gems/rack-2.0.5/lib/rack/runtime.rb:22:in `call'"
"/Users/daidoshota/training/perfect_rails_awesome_events/vendor/bundle/ruby/2.5.0/gems/activesupport-5.2.0/lib/active_support/cache/strategy/local_cache_middleware.rb:29:in `call'"
"/Users/daidoshota/training/perfect_rails_awesome_events/vendor/bundle/ruby/2.5.0/gems/actionpack-5.2.0/lib/action_dispatch/middleware/executor.rb:14:in `call'"
"/Users/daidoshota/training/perfect_rails_awesome_events/vendor/bundle/ruby/2.5.0/gems/actionpack-5.2.0/lib/action_dispatch/middleware/static.rb:127:in `call'"
"/Users/daidoshota/training/perfect_rails_awesome_events/vendor/bundle/ruby/2.5.0/gems/rack-2.0.5/lib/rack/sendfile.rb:111:in `call'"
"/Users/daidoshota/training/perfect_rails_awesome_events/vendor/bundle/ruby/2.5.0/gems/railties-5.2.0/lib/rails/engine.rb:524:in `call'"
"/Users/daidoshota/training/perfect_rails_awesome_events/vendor/bundle/ruby/2.5.0/gems/puma-3.11.4/lib/puma/configuration.rb:225:in `call'"
"/Users/daidoshota/training/perfect_rails_awesome_events/vendor/bundle/ruby/2.5.0/gems/puma-3.11.4/lib/puma/server.rb:632:in `handle_request'"
"/Users/daidoshota/training/perfect_rails_awesome_events/vendor/bundle/ruby/2.5.0/gems/puma-3.11.4/lib/puma/server.rb:446:in `process_client'"
"/Users/daidoshota/training/perfect_rails_awesome_events/vendor/bundle/ruby/2.5.0/gems/puma-3.11.4/lib/puma/server.rb:306:in `block in run'"
"/Users/daidoshota/training/perfect_rails_awesome_events/vendor/bundle/ruby/2.5.0/gems/puma-3.11.4/lib/puma/thread_pool.rb:120:in `block in spawn_thread'"
  Rendering welcome/index.html.erb within layouts/application
  Event Load (3.2ms)  SELECT "events".* FROM "events" WHERE (start_time > '2019-09-17 02:10:00.146381') ORDER BY "events"."start_time" ASC
  ↳ app/views/welcome/index.html.erb:6
  Rendered welcome/index.html.erb within layouts/application (17.7ms)
Completed 200 OK in 96ms (Views: 70.1ms | ActiveRecord: 13.1ms)

ラストが actionpack/lib/action_controller/metal/basic_implicit_render.rb の send_action

最後まで追えてない気がするけど。

Railsはアクセスをどう処理しているのか(1)

module function Kernel.#caller (Ruby 2.6.0)

unicorn

rack アプリケーション用サーバ。 マルチプロセス。 HTTPリクエストを受け取って rack インタフェースにそったオブジェクトを rack アプリケーションに渡してくれるWebサーバ、だけど rack gem に依存はしていない。(開発環境のみ依存)

RackがよくわからなかったのでRackアプリケーションをUnicornで動かしてnginxからリクエストを転送してみた - woshidan's blog

puma

rack ハンドラ(rack アプリケーション用サーバと同義?)マルチプロセス&マルチスレッド。

unicorn を代替するために作られた? Heroku が推してる。

最近の Rack サーバ事情について - おもしろwebサービス開発日記

puma/architecture.md at master · puma/puma · GitHub

【Rails 5.2 版】スマートな rails new コマンドの叩き方

自分はいつもプライベートの開発ではこんな感じで rails new やってます、というのを備忘録もかねて載せておきます。

基本的に使うか分からないライブラリのファイルは生成しないようにしています。後から genarate コマンドで簡単に追加できるので。

通常の Rails アプリ

$ bundle exec rails new . -TMCGB -d postgresql --skip-turbolinks --skip-coffee --skip-active-storage

API 専用の Rails アプリ

--api つけると そもそも js 系のファイルが生成されないので、以下のオプションで OK。

$ bundle exec rails new . --api -TMCGB -d postgresql --skip-active-storage

オプション説明

  • -TMC
    • Test, Action Mailer, Action Cableの作成をスキップ
  • -G
    • .gitignore ファイルを上書きしない(あと自動で git init しない)
  • -B
    • rails new 時に bundle install しない( Gemfile を編集してから bundle install するので)
  • -d postgresql
  • あとはもろもろライブラリをスキップ

参考

$ bundle exec rails new -h

Options:
      [--skip-namespace], [--no-skip-namespace]            # Skip namespace (affects only isolated applications)
  -r, [--ruby=PATH]                                        # Path to the Ruby binary of your choice
                                                           # Default: /Users/daidoshota/.rbenv/versions/2.5.0/bin/ruby
  -m, [--template=TEMPLATE]                                # Path to some application template (can be a filesystem path or URL)
  -d, [--database=DATABASE]                                # Preconfigure for selected database (options: mysql/postgresql/sqlite3/oracle/frontbase/ibm_db/sqlserver/jdbcmysql/jdbcsqlite3/jdbcpostgresql/jdbc)
                                                           # Default: sqlite3
      [--skip-yarn], [--no-skip-yarn]                      # Don't use Yarn for managing JavaScript dependencies
      [--skip-gemfile], [--no-skip-gemfile]                # Don't create a Gemfile
  -G, [--skip-git], [--no-skip-git]                        # Skip .gitignore file
      [--skip-keeps], [--no-skip-keeps]                    # Skip source control .keep files
  -M, [--skip-action-mailer], [--no-skip-action-mailer]    # Skip Action Mailer files
  -O, [--skip-active-record], [--no-skip-active-record]    # Skip Active Record files
      [--skip-active-storage], [--no-skip-active-storage]  # Skip Active Storage files
  -P, [--skip-puma], [--no-skip-puma]                      # Skip Puma related files
  -C, [--skip-action-cable], [--no-skip-action-cable]      # Skip Action Cable files
  -S, [--skip-sprockets], [--no-skip-sprockets]            # Skip Sprockets files
      [--skip-spring], [--no-skip-spring]                  # Don't install Spring application preloader
      [--skip-listen], [--no-skip-listen]                  # Don't generate configuration that depends on the listen gem
      [--skip-coffee], [--no-skip-coffee]                  # Don't use CoffeeScript
  -J, [--skip-javascript], [--no-skip-javascript]          # Skip JavaScript files
      [--skip-turbolinks], [--no-skip-turbolinks]          # Skip turbolinks gem
  -T, [--skip-test], [--no-skip-test]                      # Skip test files
      [--skip-system-test], [--no-skip-system-test]        # Skip system test files
      [--skip-bootsnap], [--no-skip-bootsnap]              # Skip bootsnap gem
      [--dev], [--no-dev]                                  # Setup the application with Gemfile pointing to your Rails checkout
      [--edge], [--no-edge]                                # Setup the application with Gemfile pointing to Rails repository
      [--rc=RC]                                            # Path to file containing extra configuration options for rails command
      [--no-rc], [--no-no-rc]                              # Skip loading of extra configuration options from .railsrc file
      [--api], [--no-api]                                  # Preconfigure smaller stack for API only apps
  -B, [--skip-bundle], [--no-skip-bundle]                  # Don't run bundle install
      [--webpack=WEBPACK]                                  # Preconfigure for app-like JavaScript with Webpack (options: react/vue/angular/elm/stimulus)

Runtime options:
  -f, [--force]                    # Overwrite files that already exist
  -p, [--pretend], [--no-pretend]  # Run but do not make any changes
  -q, [--quiet], [--no-quiet]      # Suppress status output
  -s, [--skip], [--no-skip]        # Skip files that already exist


# Options, Runtime options 以外は省略

React Native × Firebase Authentication での自動ログイン機能に AsyncStorage は使わなくていい

タイトルのまんまですが、 Web に落ちてる記事の中にはわざわざログイン状態を React Native の AsyncStorage を使って保存してる実装のものもあったので、勘違いしないようにメモ。

Firebase Authentication では デフォルトで 明示的にログアウトするまでセッションを維持してくれます。

Authentication State Persistence  |  Firebase

'local': ブラウザ ウィンドウを閉じたり React Native でアクティビティが破棄されたりした場合でも、状態が維持されることを示します。この状態をクリアするには、明示的なログアウトが必要です。Firebase Auth のウェブ セッションは単一のホストを生成元とするため、単一のドメインでのみ永続化されることに注意してください。

...

ウェブブラウザと React Native アプリのデフォルトは local (ブラウザがこのストレージ メカニズムをサポートしている場合、たとえばサードパーティCookie / データが有効な場合など)で、Node.js バックエンドアプリでは none になります。

ログインしてるかどうかは firebase.auth().onAuthStateChanged() などで取得できます。

雑記

Firebase Authentication では明示的にログアウトしなくても他のユーザとして再度ログインかサインアップすればログインアカウントが切り替わるというのも(当たり前なんですが)挙動確認できたので、メモしておきます。

Expo で "This version of the Expo app is out of date. Uninstall the app and run again to upgrade." のエラーが出た時の対処法

iOS Simulator にインストールされている Expo アプリのバージョンが古いとのこと。

iOS Simulator の Hardware > Erase All Content and Settings してから再度 $ expo start -i すれば最新版の Expo アプリが install されます。

参考

javascript - expo is out of date uninstall and run again to upgrade - Stack Overflow

React Native にゆかりのあるスタートアップが集う会#5 に行ってきた

普段は Rails でアプリケーション開発してる自分ですが、プライベートで以前から触っていた React Native のイベントに行ってきました。

r-n.connpass.com

詳細なレポートは以下がとてもよくまとまってると思います。

【第5回】 「ReactNativeにゆかりのあるスタートアップが集う会」に参加しました|tkow|note

この記事では自分の気になった発表を中心にまとめていきます。

ReactNative はじめのいっぽ

まとめ

  • expo init してからも実装環境周りで迷うことが多い
  • 基本方針は UI を無理に作り込まずツールや OSS を活用する

以下ピクスタでの環境。

  • 開発支援ツールは expo 一択
  • エミュレーターiOS: XcodeAndroid: Genymotion
  • デバッガは React Native Debugger(長時間開いてるとメモリ食うので再起動必要)
  • 型チェックは TypeScript
  • State 管理は Redux(学習コスト大きいけど、書ける人多いから採用において有利)
  • ディレクトリ構成は Ducks(小規模なアプリなら見通しが良い)
  • ルーティングは React Navigation(Redux との相性良い)
  • 非同期処理は redux-saga
  • UI コンポーネントは React Native Elements

一言感想

UI コンポーネントに Native Base でなくて React Native Elements を選んだ理由が聞きたかった…

React Native のルーティングばなし

まとめ

  • React Navigation では今何枚目問題やバージョンアップによる破壊的変更がつらい
  • シンプルな zen-router を作ってみた
  • 今何枚目問題を解決する screenLength を実装した

GitHub - jshosomichi/react-native-zen-router

一言感想

zen-router 使ってみたい。

React Nativeの開発環境を分けよう(iOS編)

まとめ

一言感想

デバッグ方法わからなさすぎたのでものすごくためになりました!

React Native の E2E テストフレームワーク Detoxの紹介

まとめ

  • React Native 用 E2E テストツール Detox の紹介
  • テストデータ作るのが難しいので、 get 系のテストを中心に書いてる

GitHub - wix/Detox: Gray Box End-to-End Testing and Automation Framework for Mobile Apps

一言感想

テスト大事(でもプライベートでの開発では一旦 E2E テストは良いかな…)

パネルディスカッション

Hooks 便利そう(React Native 0.59 から使える)

VSCode の One Dark Pro のサイドバーの色を調整する

デフォルトだと選択されているファイルやフォーカスされているファイルが見づらすぎてキツイので。

// settings.json
{
  "workbench.colorCustomizations": {
    // 現在選択されている(開かれている)ファイルの背景
    "list.activeSelectionBackground": "#777",
    // フォーカスされているファイルの背景
    "list.focusBackground": "#555"
  },
  "workbench.colorTheme": "One Dark Pro"
}

参考

https://code.visualstudio.com/api/references/theme-color#lists-and-trees

初めての OSS コントリビュート

内容は超ショボいんですが、一応初めての記念として。

自分自身が普段使っている vim-snippets の React の部分を直したいなーと思って、 PR を送りました。

英語力をもっとつけなれけば、と強く思いました…。

github.com