拡張機能のバンドル

Visual Studio Code拡張機能をバンドルする最初の理由は、どのプラットフォームでVS Codeを使用しているユーザーにとっても動作するようにするためです。github.devvscode.devのようなVS Code for Web環境では、バンドルされた拡張機能のみが使用できます。VS Codeがブラウザで実行されている場合、拡張機能は1つのファイルしかロードできないため、拡張機能のコードは単一のWebフレンドリーなJavaScriptファイルにバンドルされる必要があります。これはノートブック出力レンダラーにも適用され、VS Codeはレンダラー拡張機能のために1つのファイルしかロードしません。

さらに、拡張機能はサイズと複雑さが増大することがあります。複数のソースファイルで作成されたり、npmのモジュールに依存したりする場合があります。分割と再利用は開発のベストプラクティスですが、拡張機能をインストールして実行する際にはコストがかかります。100個の小さなファイルをロードするよりも、1つの大きなファイルをロードする方がはるかに高速です。そのため、バンドルを推奨します。バンドルとは、複数の小さなソースファイルを1つのファイルに結合するプロセスです。

JavaScriptには、さまざまなバンドラーがあります。一般的なものとしては、rollup.jsParcelesbuild、およびwebpackがあります。

esbuildを使用する

esbuildは、設定が簡単な高速JavaScriptバンドラーです。esbuildを入手するには、ターミナルを開いて次のように入力します。

npm i --save-dev esbuild

esbuildを実行する

esbuildはコマンドラインから実行できますが、繰り返しを減らし、問題レポートを有効にするには、ビルドスクリプトesbuild.jsを使用すると便利です。

const esbuild = require('esbuild');

const production = process.argv.includes('--production');
const watch = process.argv.includes('--watch');

async function main() {
  const ctx = await esbuild.context({
    entryPoints: ['src/extension.ts'],
    bundle: true,
    format: 'cjs',
    minify: production,
    sourcemap: !production,
    sourcesContent: false,
    platform: 'node',
    outfile: 'dist/extension.js',
    external: ['vscode'],
    logLevel: 'warning',
    plugins: [
      /* add to the end of plugins array */
      esbuildProblemMatcherPlugin
    ]
  });
  if (watch) {
    await ctx.watch();
  } else {
    await ctx.rebuild();
    await ctx.dispose();
  }
}

/**
 * @type {import('esbuild').Plugin}
 */
const esbuildProblemMatcherPlugin = {
  name: 'esbuild-problem-matcher',

  setup(build) {
    build.onStart(() => {
      console.log('[watch] build started');
    });
    build.onEnd(result => {
      result.errors.forEach(({ text, location }) => {
        console.error(`✘ [ERROR] ${text}`);
        if (location == null) return;
        console.error(`    ${location.file}:${location.line}:${location.column}:`);
      });
      console.log('[watch] build finished');
    });
  }
};

main().catch(e => {
  console.error(e);
  process.exit(1);
});

ビルドスクリプトは以下を実行します。

  • esbuildでビルドコンテキストを作成します。コンテキストは次のように設定されています。
    • src/extension.tsのコードを単一のファイルdist/extension.jsにバンドルします。
    • --productionフラグが渡された場合、コードをミニファイします。
    • --productionフラグが渡されない限り、ソースマップを生成します。
    • 「vscode」モジュールをバンドルから除外します(VS Codeランタイムによって提供されるため)。
  • esbuildProblemMatcherPluginプラグインを使用して、バンドラーの完了を妨げたエラーを報告します。このプラグインは、拡張機能としてインストールする必要があるesbuild問題マッチャーによって検出される形式でエラーを出力します。
  • --watchフラグが渡された場合、ソースファイルの変更を監視し始め、変更が検出されるたびにバンドルを再構築します。

esbuildはTypeScriptファイルを直接扱うことができます。しかし、esbuildは型チェックを行わずにすべての型宣言を単に除去します。構文エラーのみが報告され、esbuildが失敗する可能性があります。

そのため、型チェックのためにTypeScriptコンパイラ(tsc)を別途実行しますが、コードは一切出力しません(--noEmitフラグ)。

package.jsonscriptsセクションは次のようになります。

"scripts": {
    "compile": "npm run check-types && node esbuild.js",
    "check-types": "tsc --noEmit",
    "watch": "npm-run-all -p watch:*",
    "watch:esbuild": "node esbuild.js --watch",
    "watch:tsc": "tsc --noEmit --watch --project tsconfig.json",
    "vscode:prepublish": "npm run package",
    "package": "npm run check-types && node esbuild.js --production"
}

npm-run-allは、与えられたプレフィックスに一致する名前のスクリプトを並行して実行するNodeモジュールです。この場合、watch:esbuildスクリプトとwatch:tscスクリプトを実行します。npm-run-allpackage.jsondevDependenciesセクションに追加する必要があります。

compilewatchスクリプトは開発用であり、ソースマップ付きのバンドルファイルを生成します。packageスクリプトは、VS Codeのパッケージングおよび公開ツールであるvsceによって使用されるvscode:prepublishスクリプトによって使用され、拡張機能を公開する前に実行されます。esbuildスクリプトに--productionフラグを渡すと、コードが圧縮され、小さなバンドルが作成されますが、デバッグが困難になるため、開発中は他のフラグが使用されます。上記のスクリプトを実行するには、ターミナルを開いてnpm run watchと入力するか、コマンドパレットからタスク: タスクの実行を選択します(⇧⌘P (Windows, Linux Ctrl+Shift+P))。

.vscode/tasks.jsonを次のように設定すると、各ウォッチタスクに個別のターミナルが提供されます。

{
  "version": "2.0.0",
  "tasks": [
    {
      "label": "watch",
      "dependsOn": ["npm: watch:tsc", "npm: watch:esbuild"],
      "presentation": {
        "reveal": "never"
      },
      "group": {
        "kind": "build",
        "isDefault": true
      }
    },
    {
      "type": "npm",
      "script": "watch:esbuild",
      "group": "build",
      "problemMatcher": "$esbuild-watch",
      "isBackground": true,
      "label": "npm: watch:esbuild",
      "presentation": {
        "group": "watch",
        "reveal": "never"
      }
    },
    {
      "type": "npm",
      "script": "watch:tsc",
      "group": "build",
      "problemMatcher": "$tsc-watch",
      "isBackground": true,
      "label": "npm: watch:tsc",
      "presentation": {
        "group": "watch",
        "reveal": "never"
      }
    }
  ]
}

このウォッチタスクは、問題ビューで問題を報告するためにインストールする必要がある、問題マッチング用の拡張機能connor4312.esbuild-problem-matchersに依存します。起動を完了するには、この拡張機能がインストールされている必要があります。

それを忘れないために、ワークスペースに.vscode/extensions.jsonファイルを追加します。

{
  "recommendations": ["connor4312.esbuild-problem-matchers"]
}

最後に、コンパイルされたファイルが公開される拡張機能に含まれるように、.vscodeignoreファイルを更新する必要があります。詳細については、「公開」セクションを参照してください。

テストセクションに進んで読み進めてください。

webpackを使用する

Webpackは、npmから入手できる開発ツールです。webpackとそのコマンドラインインターフェースを入手するには、ターミナルを開いて次のように入力します。

npm i --save-dev webpack webpack-cli

これによりwebpackがインストールされ、拡張機能のpackage.jsonファイルが更新され、webpackがdevDependenciesに含まれるようになります。

WebpackはJavaScriptバンドラーですが、多くのVS Code拡張機能はTypeScriptで記述されており、JavaScriptにのみコンパイルされます。拡張機能がTypeScriptを使用している場合、webpackがTypeScriptを理解できるように、ローダーts-loaderを使用できます。ts-loaderをインストールするには、次を使用します。

npm i --save-dev ts-loader

すべてのファイルは、webpack-extensionサンプルで利用できます。

webpackを設定する

すべてのツールがインストールされたら、webpackを設定できるようになります。慣例として、webpack.config.jsファイルには、webpackに拡張機能をバンドルするように指示する設定が含まれています。以下のサンプル設定はVS Code拡張機能用であり、良い出発点となるはずです。

//@ts-check

'use strict';

const path = require('path');
const webpack = require('webpack');

/**@type {import('webpack').Configuration}*/
const config = {
  target: 'webworker', // vscode extensions run in webworker context for VS Code web 📖 -> https://webpack.dokyumento.jp/configuration/target/#target

  entry: './src/extension.ts', // the entry point of this extension, 📖 -> https://webpack.dokyumento.jp/configuration/entry-context/
  output: {
    // the bundle is stored in the 'dist' folder (check package.json), 📖 -> https://webpack.dokyumento.jp/configuration/output/
    path: path.resolve(__dirname, 'dist'),
    filename: 'extension.js',
    libraryTarget: 'commonjs2',
    devtoolModuleFilenameTemplate: '../[resource-path]'
  },
  devtool: 'source-map',
  externals: {
    vscode: 'commonjs vscode' // the vscode-module is created on-the-fly and must be excluded. Add other modules that cannot be webpack'ed, 📖 -> https://webpack.dokyumento.jp/configuration/externals/
  },
  resolve: {
    // support reading TypeScript and JavaScript files, 📖 -> https://github.com/TypeStrong/ts-loader
    mainFields: ['browser', 'module', 'main'], // look for `browser` entry point in imported node modules
    extensions: ['.ts', '.js'],
    alias: {
      // provides alternate implementation for node module and source files
    },
    fallback: {
      // Webpack 5 no longer polyfills Node.js core modules automatically.
      // see https://webpack.dokyumento.jp/configuration/resolve/#resolvefallback
      // for the list of Node.js core module polyfills.
    }
  },
  module: {
    rules: [
      {
        test: /\.ts$/,
        exclude: /node_modules/,
        use: [
          {
            loader: 'ts-loader'
          }
        ]
      }
    ]
  }
};
module.exports = config;

このファイルは、webpack-extensionサンプルの一部として利用可能です。Webpack設定ファイルは、設定オブジェクトをエクスポートする必要がある通常のJavaScriptモジュールです。

上記のサンプルでは、以下が定義されています。

  • targetは、拡張機能が実行されるコンテキストを示します。拡張機能がWeb用VS Codeとデスクトップ版VS Codeの両方で動作するように、webworkerを使用することをお勧めします。
  • webpackが使用すべきエントリーポイント。これはpackage.jsonmainプロパティに似ていますが、"出力"エントリーポイントではなく、通常src/extension.tsのような"ソース"エントリーポイントをwebpackに提供します。webpackバンドラーはTypeScriptを理解するため、別のTypeScriptコンパイルステップは冗長です。
  • output設定は、生成されたバンドルファイルをどこに配置するかをwebpackに伝えます。慣例として、それはdistフォルダです。このサンプルでは、webpackはdist/extension.jsファイルを生成します。
  • resolveおよびmodule/rules設定は、TypeScriptおよびJavaScript入力ファイルをサポートするために存在します。
  • externals設定は、バンドルに含めるべきではないファイルやモジュールなどの除外を宣言するために使用されます。vscodeモジュールはディスク上に存在せず、必要に応じてVS Codeによってオンザフライで作成されるため、バンドルすべきではありません。拡張機能が使用するNodeモジュールによっては、さらに多くの除外が必要になる場合があります。

最後に、コンパイルされたファイルが公開される拡張機能に含まれるように、.vscodeignoreファイルを更新する必要があります。詳細については、「公開」セクションを参照してください。

webpackを実行する

webpack.config.jsファイルが作成されたら、webpackを呼び出すことができます。webpackはコマンドラインから実行できますが、繰り返しを減らすには、npmスクリプトを使用すると便利です。

これらのエントリをpackage.jsonscriptsセクションに結合します。

"scripts": {
    "compile": "webpack --mode development",
    "watch": "webpack --mode development --watch",
    "vscode:prepublish": "npm run package",
    "package": "webpack --mode production --devtool hidden-source-map",
},

compilewatchスクリプトは開発用であり、バンドルファイルを生成します。vscode:prepublishは、VS Codeのパッケージングおよび公開ツールであるvsceによって使用され、拡張機能を公開する前に実行されます。違いはモードにあり、これが最適化のレベルを制御します。productionを使用すると最小のバンドルが得られますが、時間も長くかかるため、それ以外の場合はdevelopmentが使用されます。上記のスクリプトを実行するには、ターミナルを開いてnpm run compileと入力するか、コマンドパレットからタスク: タスクの実行を選択します(⇧⌘P (Windows, Linux Ctrl+Shift+P))。

拡張機能を実行する

拡張機能を実行する前に、package.jsonmainプロパティがバンドルを指している必要があります。上記の構成の場合、それは"./dist/extension"です。この変更により、拡張機能は実行およびテストできるようになります。

テスト

拡張機能の作成者は、拡張機能のソースコードに対して単体テストを記述することがよくあります。拡張機能のソースコードがテストに依存しないという正しいアーキテクチャレイヤリングがあれば、webpackやesbuildによって生成されたバンドルにはテストコードが含まれないはずです。単体テストを実行するには、単純なコンパイルのみが必要です。

これらのエントリをpackage.jsonscriptsセクションに結合します。

"scripts": {
    "compile-tests": "tsc -p . --outDir out",
    "pretest": "npm run compile-tests",
    "test": "vscode-test"
}

compile-testsスクリプトは、TypeScriptコンパイラを使用して拡張機能をoutフォルダにコンパイルします。その中間JavaScriptが利用可能であれば、launch.jsonの以下のスニペットでテストを実行するのに十分です。

{
  "name": "Extension Tests",
  "type": "extensionHost",
  "request": "launch",
  "runtimeExecutable": "${execPath}",
  "args": [
    "--extensionDevelopmentPath=${workspaceFolder}",
    "--extensionTestsPath=${workspaceFolder}/out/test"
  ],
  "outFiles": ["${workspaceFolder}/out/test/**/*.js"],
  "preLaunchTask": "npm: compile-tests"
}

テストを実行するためのこの設定は、バンドルされていない拡張機能と同じです。単体テストは拡張機能の公開部分ではないため、バンドルする必要はありません。

公開

公開する前に、.vscodeignoreファイルを更新する必要があります。dist/extension.jsファイルにバンドルされたものはすべて除外できます。通常、outフォルダ(まだ削除していない場合)と、最も重要なnode_modulesフォルダです。

典型的な.vscodeignoreファイルは次のようになります。

.vscode
node_modules
out/
src/
tsconfig.json
webpack.config.js
esbuild.js

既存の拡張機能を移行する

既存の拡張機能をesbuildまたはwebpackを使用するように移行するのは簡単で、上記の入門ガイドと似ています。webpackを採用した実際のサンプルとしては、このプルリクエストを介したVS Codeの参照ビューがあります。

そこでは、以下を確認できます。

  • esbuild、またはwebpackwebpack-cli、およびts-loaderdevDependenciesとして追加します。
  • npmスクリプトを上記のようにバンドラーを使用するように更新します。
  • タスク設定ファイルtasks.jsonを更新します。
  • esbuild.jsまたはwebpack.config.jsビルドファイルを追加し、調整します。
  • .vscodeignoreを更新して、node_modulesと中間出力ファイルを除外します。
  • はるかに高速にインストールおよびロードされる拡張機能をお楽しみください!

トラブルシューティング

ミニファイ

productionモードでのバンドルは、コードのミニファイも実行します。ミニファイは、空白やコメントを削除し、変数名や関数名を醜いが短いものに変更することで、ソースコードをコンパクトにします。Function.prototype.nameを使用するソースコードは動作が異なるため、ミニファイを無効にする必要があるかもしれません。

webpackのクリティカルな依存関係

webpackを実行しているときに、Critical dependencies: the request of a dependency is an expressionのような警告に遭遇するかもしれません。このような警告は真剣に受け止める必要があり、バンドルが動作しない可能性が高いです。このメッセージは、webpackが一部の依存関係を静的にバンドルする方法を決定できないことを意味します。これは通常、require(someDynamicVariable)のような動的なrequireステートメントによって引き起こされます。

この警告に対処するには、次のいずれかを実行する必要があります。

  • 依存関係を静的にして、バンドルできるようにしてみてください。
  • externals設定を介してその依存関係を除外します。また、それらのJavaScriptファイルがパッケージされた拡張機能から除外されないように、.vscodeignoreで否定されたグロブパターンを使用してください。例:!node_modules/mySpecialModule

次のステップ

© . This site is unofficial and not affiliated with Microsoft.