VS Codeの拡張機能開発でTailwind CSSを導入してみる

## はじめに
初めまして。最近昼夜逆転してきた [yakiniku0220](https://twitter.com/yakiniku040220)です。

今回は前回書いた記事の続編で、実際にVS Codeの拡張機能の開発でも採用しているTailwind CSSの導入に関して執筆しようと思いました。
前回の記事を読んでいない方はぜひ読んでいただけると幸いです。

https://qiita.com/yakiniku0220/items/14b60a66530d70d261a5

VS CodeのセットアップやReact.jsの導入はこの記事では省きます。

## Tailwind CSSのSetup
今回はTailwind CLIを使用した手順を記載します。

### Tailwind CSSのInstall
まずはTailwind CSSをinstallして`tailwind.config.js`ファイルを生成します。

```terminal
npm install -D tailwindcss
npx tailwindcss init
```

### tailwind.config.jsにテンプレートファイルのパスを追記
先ほど生成した`tailwind.config.js`にテンプレートファイルのパスを追記ます。
今回はdist配下のファイルをcontentに指定します。

```js
/** @type {import('tailwindcss').Config} */
module.exports = {
  content: ["./dist/**/*.js"],
  theme: {
    extend: {},
  },
  plugins: [],
}
```

### CSSにTailwindディレクティブを追加
今回はsrc配下に`index.css`というファイルを作成し、以下のコードを記載します。
```css
@tailwind base;
@tailwind components;
@tailwind utilities;
```

## [Webpackの設定](#header2)
### 対象のパッケージをinstallする
まずは以下のパッケージをinstallします。

```terminal
yarn add -D css-minimizer-webpack-plugin mini-css-extract-plugin css-loader postcss-loader postcss autoprefixer
```

### postcss.config.jsファイルを作成する
src配下に`postcss.config.js`ファイルを作成して以下のコードを追記します。

```js
module.exports = {
  plugins: {
    tailwindcss: {},
    autoprefixer: {},
  },
};
```

### webpack.config.jsファイルを編集する
次に`webpack.config.js`ファイルに先ほどinstallしたpackageの設定を記載していきます。

```js
//@ts-check

"use strict";

const path = require("path");
const webpack = require("webpack");
const CssMinimizerPlugin = require("css-minimizer-webpack-plugin");
const MiniCssExtractPlugin = require("mini-css-extract-plugin");

//@ts-check
/** @typedef {import('webpack').Configuration} WebpackConfig **/

/** @type WebpackConfig */
const baseConfig = {
  target: "node", // VS Code extensions run in a Node.js-context 📖 -> https://webpack.js.org/configuration/node/
  mode: "none", // this leaves the source code as close as possible to the original (when packaging we set this to 'production')
  resolve: {
    // support reading TypeScript and JavaScript files, 📖 -> https://github.com/TypeStrong/ts-loader
    extensions: [".ts", ".js", ".tsx"],
  },
  plugins: [
    new webpack.DefinePlugin({
      "process.env.NODE_ENV": JSON.stringify(process.env.NODE_ENV),
    }),
    new MiniCssExtractPlugin({ // MiniCssExtractPluginを追記
      filename: "./src/index.css",
    }),
  ],
  module: {
    rules: [
      {
        test: /\.tsx?$/,
        exclude: /node_modules/,
        use: [
          {
            loader: "ts-loader",
          },
        ],
      },
      {
        test: /\.css$/i, // CSSファイルを別ファイルに抽出するために追加
        use: [MiniCssExtractPlugin.loader, "css-loader", "postcss-loader"],
      },
    ],
  },
  optimization: { // optimization.minimizerを追加
    minimizer: [new CssMinimizerPlugin()],
  },
  devtool: "nosources-source-map",
  infrastructureLogging: {
    level: "log", // enables logging required for problem matchers
  },
};

/** @type WebpackConfig */
const extensionConfig = {
  ...baseConfig,
  entry: "./src/extension.ts", // the entry point of this extension, 📖 -> https://webpack.js.org/configuration/entry-context/
  output: {
    // the bundle is stored in the 'dist' folder (check package.json), 📖 -> https://webpack.js.org/configuration/output/
    path: path.resolve(__dirname, "dist"),
    filename: "extension.js",
    libraryTarget: "commonjs2",
  },
  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.js.org/configuration/externals/
    // modules added here also need to be added in the .vscodeignore file
  },
};

/** @type WebpackConfig */
const webviewConfig = {
  ...baseConfig,
  target: ["web", "es2020"],
  entry: "./src/index.tsx",
  experiments: { outputModule: true },
  output: {
    path: path.resolve(__dirname, "dist"),
    filename: "webview.js",
    libraryTarget: "module",
    chunkFormat: "module",
  },
};

/** @type WebpackConfig */
const styleConfig = {　// index.cssをcompileするために処理を追加
  ...baseConfig,
  entry: "./src/index.css",
  output: {
    path: path.resolve(__dirname, "dist"),
    filename: "index.css",
  },
};

module.exports = [extensionConfig, webviewConfig, styleConfig];
```

## extension.tsファイルを編集する
最後に`extension.ts`ファイルを編集します。
compileした際にdistディレクトリに`index.css`ファイルが生成されるので、React.jsを導入したときに作成した`getUri`関数を使用します。

```ts
// The module 'vscode' contains the VS Code extensibility API
// Import the module and reference it with the alias vscode in your code below
import * as vscode from "vscode";

export function activate(context: vscode.ExtensionContext) {
  context.subscriptions.push(
    vscode.commands.registerCommand("react.start", () => {
      // Create and show panel
      const panel = vscode.window.createWebviewPanel(
        "react",
        "React Sample",
        vscode.ViewColumn.One,
        {
          localResourceRoots: [
            vscode.Uri.joinPath(context.extensionUri, "dist"),
          ],
          enableScripts: true,
        }
      );

      // And set its HTML content
      panel.webview.html = getWebviewContent(
        panel.webview,
        context.extensionUri
      );
    })
  );
}

// This method is called when your extension is deactivated
export function deactivate() {}

function getUri(
  webview: vscode.Webview,
  extensionUri: vscode.Uri,
  pathList: string[]
) {
  return webview.asWebviewUri(vscode.Uri.joinPath(extensionUri, ...pathList));
}

function getNonce() {
  let text = "";
  const possible =
    "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
  for (let i = 0; i < 32; i++) {
    text += possible.charAt(Math.floor(Math.random() * possible.length));
  }
  return text;
}

function getWebviewContent(webview: vscode.Webview, extensionUri: vscode.Uri) {
  const webviewUri = getUri(webview, extensionUri, ["dist", "webview.js"]);
  const styleUri = getUri(webview, extensionUri, ["dist", "src", "index.css"]);

  const nonce = getNonce();

  return `<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Cat Coding</title>
    <link href="${styleUri}" rel="stylesheet">
</head>
<body>
  <div id="app"></div>
  <script type="module" nonce="${nonce}" src="${webviewUri}"></script>
</body>
</html>`;
}
```

## 動作確認
`index.tsx`ファイルにTailwind CSSを書いて確認してみます。
![スクリーンショット 2024-06-26 6.50.26.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/271450/090ccf03-e956-2057-232d-2d1f0d6336ef.png)

compile後、ちゃんとTailwind CSSが適用されているのが確認できました。

## まとめ
今回VS Codeの拡張機能開発にTailwind CSSを入れてみました。
Tailwind CSSは個人開発でちょくちょく使っているので、個人的には開発体験は上がった気がします。
今回作ったサンプルコードは以下のリポジトリに作ってあります。
https://github.com/ebarakazuhiro/sample-vscode-extension

## 最後に
株式会社HRBrainでは新しいメンバーを募集中です。
興味がある方は下記のリンクから宜しくお願い致します。

https://www.hrbrain.co.jp/recruit