webpackを使ってhtmlファイルのheadタグにインラインスタイルのCSSを出力する方法

## 1. はじめに
本記事では特にhtml-webpack-plugin[^1]とmini-css-extract-plugin[^2]を使って、entryに追加した外部cssをバンドル後のhtmlファイルのheadタグにインラインスタイルで出力する方法をご紹介します。

ユースケースとしては、以下のような環境でしょう。
- JavaScriptが実行できない環境
	- style-loader[^3]が使用できない
- 外部ファイルとしてcssをimportできない環境(htmlメールなど)
	- mini-css-extract-pluginだけでは解決できない

## 2. 各構成
はじめに実行環境やフォルダ構成、本記事で主に使用するファイルの内容を提示します。
以下の構成や中身をそのままコピーして実行いただくと、本記事に示した内容を実現できます。

### 2.1 実行環境
- node: 16.14.2
- npm: 8.5.0

### 2.2 フォルダ構成

```
output/
  index.html
src/
  assets/
    styles/
      common.css
  app.html
gitignore
package-lock.json
package.json
webpack.config.js
```

### 2.3 各ファイル内容

```json:package.json
{
	// ...
	"scripts": {
	    "build": "NODE_ENV=production webpack --mode production"
	  },
	"devDependencies": {
	  "css-loader": "^6.7.1",
	  "html-webpack-plugin": "^5.5.0",
	  "mini-css-extract-plugin": "^2.6.1",
	  "webpack": "^5.74.0",
	  "webpack-cli": "^4.10.0"
	}
}
```

```html:app.html
<!DOCTYPE html>
<html lang="ja">
  <head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title><%= htmlWebpackPlugin.options.title %></title>
    <style>
      <%= compilation.getAsset(htmlWebpackPlugin.files.css[0]).source.source() %>
      </style>
  </head>
  <body>
    <main>
      <p>メインコンテンツ</p>
    </main>
  </body>
</html>
```

```css:common.css
p {
    background-color: #333;
    color: #fff;
}
```

```js:webpack.config.js
const path = require("path")
const MinCssExtraPlugin = require("mini-css-extract-plugin")
const HtmlWebpackPlugin = require("html-webpack-plugin")

const projectBasePath = path.resolve(__dirname, "src")
const publicBasePath = path.resolve(__dirname, "output")

const filePath = {
    css: "./src/assets/styles/common.css",
    js: "./src/assets/js/[name].js",
    template: `${projectBasePath}/app.html`,
    html: `${publicBasePath}/index.html`
}

module.exports = {
    entry: {
        css: filePath.css
    },
    output: {
        filename: filePath.js,
        path: publicBasePath
    },
    plugins: [
        new MinCssExtraPlugin({ filename: filePath.css }),
        new HtmlWebpackPlugin({
            template: filePath.template,
            filename: filePath.html,
            minify: false,
            inject: false
        }),
    ],
    module: {
        rules: [
            {
                test: /\.css$/,
                use: [
                    MinCssExtraPlugin.loader,
                    "css-loader"
                ]
            },
        ]
    }
}
```

## 3. 説明

html-webpack-pluginの公式ドキュメントによると、`template`オプションで指定したhtmlファイルから`templateParameters`を参照することができます。

`templateParameters`の型定義部分を引用すると以下のような内容です[^4]。
```js
 interface TemplateParameter {
    compilation: any;
    htmlWebpackPlugin: {
      tags: {
        headTags: HtmlTagObject[];
        bodyTags: HtmlTagObject[];
      };
      files: {
        publicPath: string;
        js: Array<string>;
        css: Array<string>;
        manifest?: string;
        favicon?: string;
      };
      options: Options;
    };
    webpackConfig: any;
  }
```

上記の型情報によると、`htmlWebpackPlugin.files.css` でcssのファイルパスを取得することができます[^5]。

```jsx
[ './src/assets/styles/common.css' ]
```

また、compilation[^6]はwebpackの`compilation`オブジェクトを指します。

今回はgetAssetメソッド[^7]を使用します。`getAsset`メソッドは`entry`に指定したファイル名からファイル情報を取得するため、例えば `./src/assets/styles/common.css` を指定すると以下のような情報を取得することができます。

```js

 {
  name: './src/assets/styles/common.css',
  source: CachedSource {
    _source: [ConcatSource],
    _cachedSourceType: undefined,
    _cachedSource: undefined,
    _cachedBuffer: undefined,
    _cachedSize: undefined,
    _cachedMaps: Map(0) {},
    _cachedHashUpdate: undefined
  },
  info: {}
}
```

そして、`source`プロパティにある`source()`を実行することで、cssを取得することができます。

最終的なcssを取得する指定方法は以下のようになります。
```js
compilation.getAsset(htmlWebpackPlugin.files.css[0]).source.source()
```

build後、生成されたhtmlファイルでcssの内容が表示されていれば成功です。

```html:index.html
<!DOCTYPE html>
<html lang="ja">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Webpack App</title>
    <style>
      p {
        background-color: #333;
        color: #fff;
      }
    </style>
  </head>
  <body>
     <main>
	<p>メインコンテンツ</p>
     </main>
  </body>
</html>
```

## 4. 制限

html-loader[^8]を導入している場合はJSPで記述したコードがそのまま出力されてしまい、html内で`templateParameters`の内容に参照することができません。

## 5. おわりに
本記事ではwebpackを使ったcssのインライン出力の一例をご紹介しました。
すごくニッチな内容ですが、お役に立てれば幸いです。


[^1]: https://webpack.js.org/plugins/html-webpack-plugin/
[^2]: https://webpack.js.org/plugins/mini-css-extract-plugin/
[^3]: https://webpack.js.org/loaders/style-loader/
[^4]: https://github.com/jantimon/html-webpack-plugin/blob/main/typings.d.ts#L186-L203
[^5]: https://github.com/jantimon/html-webpack-plugin#writing-your-own-templates
[^6]: https://webpack.js.org/api/compilation-object/
[^7]: https://webpack.js.org/api/compilation-object/#getasset
[^8]: https://webpack.js.org/loaders/html-loader/