コツコツと

[VS Code 拡張機能開発]Vue を使って Webview を構築してみる

December 15, 2020

Visual Studio Code(VS Code) 拡張機能の開発において、Vue を使って Webview を構築してみたので、本記事にて紹介します。
構築した Webview は、トップページに2つのリンクを配置しており、リンクを押すとそれぞれのページに切り替わるだけです(下の gif を参照下さい)。
また、構築した環境は Vue(SFC) + TypeScript の環境です。
Github にて公開しています。

サンプルとして構築したWebview

環境構築のポイント

VS Code の Webview は通常のブラウザと同様の動きをするので、 webpack でバンドルした js ファイルを生成することがポイントです。
あとは、その生成した js ファイルを HTML に読み込ませるだけです。

npm パッケージの依存性

VS Code 拡張機能開発のジェネレーター実行後($ yo code後)、下記のパッケージを追加しました($ yarn addしました)。

webpack
  • webpack
  • webpack-cli

公式のインストール手順に従いました。
webpack v4 以降は、webpack-cli のインストールも必要とのことです。

Loaders
  • ts-loader
  • vue-loader
  • vue-template-compiler
  • css-loader
  • style-loader

webpack は、Javascript と JSON ファイルしかデフォルトでは処理できないので、 Loader を追加して他の形式のファイルも処理できるよう設定します。
ts-loaderは TypeScript 用です。
vue-loaderは、 SFC(単一ファイルコンポーネント)で Vue コンポーネントを開発するために利用します。また、vue-template-compilerは、Vue Loader の公式サイトのManual Setupに従いインストールしています。vue-template-compiler は vue パッケージ とバージョンの同期をとる必要があるとのことです。
css-loaderstyle-loaderは css 用です。こちら(ics.media)が参考になりました。

Vue
  • vue
  • vue-class-component
  • vue-property-decorator
  • vue-router

vueは Vue を利用するためで、 vue-class-componentvue-property-decoratorは Vue で TypeScript 環境で開発するためにインストールしています。
そして、vue-routerは、シングルページアプリケーションを構築するためにインストールしています。

webpack の設定

webpack.config.js は次の通りです。

const VueLoaderPlugin = require("vue-loader/lib/plugin");

module.exports = {
  mode: "development",
  entry: `./media/main.ts`,
  output: {
    path: `${__dirname}/media/dist`,
    filename: "main.js",
  },
  module: {
    rules: [
      {
        test: /\.ts$/,
        use: [
          {
            loader: "ts-loader",
            options: {
              appendTsSuffixTo: [/\.vue$/],
            },
          },
        ],
      },
      {
        test: /\.css$/,
        use: [{ loader: "style-loader" }, { loader: "css-loader" }],
      },
      {
        test: /\.vue$/,
        use: [{ loader: "vue-loader" }],
      },
    ],
  },
  resolve: {
    extensions: [".js", ".vue", ".ts"],
    alias: {
      vue$: `vue/dist/vue.esm.js`,
    },
  },
  plugins: [new VueLoaderPlugin()],
};

webpack の設定は下記の情報を参考にしています。

webpack のエントリーファイル

エントリーファイルでは、Vue の生成と、VueRouter の設定をしています。
vue ファイルを import する際、//@ts-ignoreを利用していますが、 ts ファイルから vue ファイルの import が上手くいかないようなので、 //@ts-ignoreで警告を無視しています。 //@ts-ignoreに抵抗がある場合は、こちらに記載あるように、 定義ファイルを利用する方法もあります。

// media/main.ts

//@ts-ignore
import Top from "./Top";
//@ts-ignore
import Example from "./Example";
//@ts-ignore
import Example2 from "./Example2";

import Vue from "vue";
import VueRouter, { RouterOptions } from "vue-router";

const routerOption: RouterOptions = {
  routes: [
    { path: "/", component: Top },
    { path: "/example", component: Example },
    { path: "/example2", component: Example2 },
  ],
};
const router = new VueRouter(routerOption);
Vue.use(VueRouter);
router.push("/");
new Vue({
  el: `#entry`,
  template: `<router-view></router-view>`,
  router,
});

extension.ts

こちらは拡張機能のエントリーファイルです。
ここで、Webview のパネルを生成して、Webview の html を設定します。
そして、設定する html にて、webpack で生成した script ファイルの読み込みと、 Vueインスタンスをマウントする要素を定義しておくだけです。
Webview はセキュリティの観点上、ローカルファイルに直接アクセスできない仕様なので、script ファイルの読み込み方法についてはこちらを参照ください。

import * as vscode from "vscode";
import * as path from "path";

export function activate(context: vscode.ExtensionContext) {
  let disposable = vscode.commands.registerCommand(
    "extension-practice.vue-try",
    () => {
      const panel = vscode.window.createWebviewPanel(
        `vue-practice`,
        `Vue Practice`,
        vscode.ViewColumn.One,
        {
          enableScripts: true,
          // VS Codeのタブを切り替えるとTopページに強制的に戻るのを防ぐ。
          // ただし、メモリ消費が大きいとのこと。
          // https://code.visualstudio.com/api/extension-guides/webview#retaincontextwhenhidden
          retainContextWhenHidden: true,
        }
      );
      panel.webview.html = getWebviewContent(context, panel.webview);
    }
  );

  context.subscriptions.push(disposable);
}

function getWebviewContent(
  context: vscode.ExtensionContext,
  webview: vscode.Webview
): string {
  const scriptUri = webview.asWebviewUri(
    vscode.Uri.file(
      path.join(context.extensionPath, "media", "dist", "main.js")
    )
  );
  return `<!DOCTYPE html>
          <html lang="ja">
          <head>
              <meta charset="UTF-8">
              <meta name="viewport" content="width=device-width, initial-scale=1.0">
              <title>Hello</title>
          </head>
          <body>
              <div id="entry"></div>
              <script src=${scriptUri}></script>
          </body>
          </html>`;
}

export function deactivate() {}

拡張機能の実行

拡張機能はF5でデバック起動できますが、その前に$ webpackを実行して、 html に読み込ませる js ファイルを生成しておく必要があります。
サンプルで構築した環境は npm script を定義しておいたので、$ yarn develop$ npx webpack --watchが実行できるようにしています。


今回は、Vue を使って Webview を構築する方法について紹介しました。
Vue を使うぐらい凝ったページを作るなら、そもそも他のプラットフォーム用のアプリとして作った方がいい気はしますが、興味本位です。


© 2020 jiri3