📁

Viteのビルドで単一htmlを出力する「vite-plugin-singlefile」の紹介!あと、fileプロトコルとCORSエラーの話

2024/06/03に公開

初投稿です。初心者です。Viteのプラグインに良さげなやつがあった+日本語で検索しても単品での紹介記事が無かったので書いてみました。

書いてある内容や用語の使い方に誤りがあれば、ガンガン指摘して頂けると幸いです。CORS関係の部分があまり自信ないです。

  • 対象読者:Viteを使ってJavaScriptのWebアプリを作っている人。
  • 記事概要:Viteのビルドで単一htmlの出力を可能にする「vite-plugin-singlefile」の紹介。Vite標準のビルドで出力したhtmlをブラウザから直接開く(fileプロトコルでアクセス)するとエラーが出る理由(CORS関係)。

「vite-plugin-singlefile」とは

https://www.npmjs.com/package/vite-plugin-singlefile
「vite-plugin-singlefile」とは、Viteのビルドで「全部のJavaScriptとCSSがインライン化されている、単一のhtmlファイル」の出力を可能にするプラグインです。

出力された単一のhtmlファイルは、ローカル環境のブラウザで直接開く(fileプロトコルでアクセスする)だけで、Webアプリとして動作させることが可能です! ランタイムをインストールしてターミナルでコマンドを打ったり、httpサーバーを準備してhttpプロトコルでアクセスしたりするといった、実行環境に関する事前準備が必要ないというメリットがあります!

こんな時に役立ちます!
・身内向けの簡単なWebアプリを、単一htmlファイルとしてお気軽に配布したい時
・httpサーバーを準備しなくても、ローカルのブラウザで動くWebアプリを作りたい時
・関連記事にあるGASのように、インライン化された単一htmlがデプロイに必要な時

※画像:ViteのデフォルトのVanilla+JavaScriptのプロジェクトでビルドした例

vite-plugin-singlefileの使用方法

0:必要な条件と注意事項

READMEの通りです。「vite-plugin-singlefile」のv2.0.1での情報です。
https://www.npmjs.com/package/vite-plugin-singlefile/v/2.0.1

条件
https://www.npmjs.com/package/vite-plugin-singlefile#what-doesnt-work

  • エントリポイントとなるhtmlファイルが一つであること
    複数のhtmlファイルを使用するWebアプリは対象外
  • Web History API、Cookie、WebXR Immersive Modeを使用していないこと
     

注意点
https://www.npmjs.com/package/vite-plugin-singlefile#caveats

  • public内に配置した静的リソース(faviconなど)は、インライン化されない。(Vite本体の仕様)
  • SVG画像のインライン化はVite本体でサポートされていないため、vite-svg-loaderを利用する必要がある。
     

1:プラグインのインストール

READMEの通りです。
https://www.npmjs.com/package/vite-plugin-singlefile#installation

  • npmやyarnを使ってViteのプロジェクトにインストールします
  • ビルド以外には使わないのでdevDependenciesにインストールすればOK
npm install vite-plugin-singlefile --save-dev
yarn add vite-plugin-singlefile --dev

 

2:ビルドの設定

vite.config.jsvite.config.tsに、プラグイン使用の記述を追加します。
READMEにVue+TypeScriptの場合のサンプルがあります。
https://www.npmjs.com/package/vite-plugin-singlefile#how-do-i-use-it

Vanilla+JavaScriptの場合だと、このようになります(動作確認済み)。

vite.config.js
import { defineConfig } from "vite";
//インストールした「vite-plugin-singlefile」をインポート
import { viteSingleFile } from "vite-plugin-singlefile";

export default defineConfig({
  //pluginsに記述することでJSとCSSがインライン化された単一htmlを出力するように
  plugins: [viteSingleFile()],
});

 

3:ビルドを実行

npm run buildvite buildでビルドを実行します。初期設定のままの場合、./distのディレクトリに、全部のJavaScriptとCSSがインライン化された単一のindex.htmlが出力されます。SVG画像のようにインライン化できないファイルがあると、それも出力されます。
 

fileプロトコル(file:///)とCORS

標準ビルドで出力したhtmlをfileプロトコルで開くとエラーが発生する理由

Viteの標準のビルドで出力されるhtmlファイルは、クロスオリジン要求を行いassetsのディレクトリに配置したJavaScript(ESM)やCSSを読み込むようなものです。ES Modules機能を使ったJavaScriptファイル(type="module")は、必ずCORS利用で読み込まれます。

通常のように、httpサーバーを立ち上げてhttpプロトコルでアクセスするなら、特に問題なくアプリとして動作させることができます。

例:Viteのサンプルで標準のビルドを行った時に出力されたhtmlの一部。

index.html
<script type="module" crossorigin src="/assets/index-Bd-pKGJy.js"></script>
<link rel="stylesheet" crossorigin href="/assets/index-Cz4zGhbH.css">

https://qiita.com/asmsuechan/items/70c4b6f56139034738c2
https://qiita.com/il-m-yamagishi/items/119cd99197cd3e9b79c2#常に-cors-で通信される
 
しかし、Viteのドキュメントのトラブルシューティングに記載されている通りで、ローカル環境のブラウザで直接開き、fileプロトコル(file:///)でアクセスすると、JavaScriptやCSSの読み込みで、CORSに関するエラーが発生してしまい、正常にアプリとして動作させることができません。
https://ja.vitejs.dev/guide/troubleshooting#ビルド

  • Google Chrome(v125.0.6422.113)のコンソールでのエラーメッセージ例:fileプロトコルではクロスオリジン要求がサポートされていないため、CORSポリシー違反でエラーになります。公式の説明探したけど見つからなかった…。
Access to script at
'file:///C:/ViteProject/dist/assets/index-Cdp2L-o9.js'
from origin 'null' has been blocked by CORS policy:
Cross origin requests are only supported for protocol schemes:
http, data, isolated-app, chrome-extension, chrome, https, chrome-untrusted.

Access to CSS stylesheet at
'file:///C:/ViteProject/dist/assets/index-BAADmztE.css'
from origin 'null' has been blocked by CORS policy:
Cross origin requests are only supported for protocol schemes:
http, data, isolated-app, chrome-extension, chrome, https, chrome-untrusted.
  • Firefox(v126.0.1)のコンソールでのエラーメッセージ例:クロスオリジン要求を行ったことによる、同一生成元ポリシー(SOP)違反でエラーになります。Firefoxの場合、fileプロトコルでのアクセス時の生成元(オリジン)は、そのhtmlファイルのみになります(MDN参照)。
    https://developer.mozilla.org/ja/docs/Web/HTTP/CORS/Errors/CORSRequestNotHttp
クロスオリジン要求をブロックしました:
同一生成元ポリシーにより、
file:///C:/ViteProject/dist/assets/index-Cdp2L-o9.js にある
リモートリソースの読み込みは拒否されます (理由: CORS 要求が http でない)。

クロスオリジン要求をブロックしました:
同一生成元ポリシーにより、
file:///C:/ViteProject/dist/assets/index-BAADmztE.css にある
リモートリソースの読み込みは拒否されます (理由: CORS 要求が http でない)。

vite-plugin-singlefileでビルドしたhtmlがfileプロトコルで問題なく開ける理由

「vite-plugin-singlefile」によるビルドで生成した単一のhtmlファイルは、全部のJavaScriptやCSSが単一のhtmlファイルにインライン化されており、クロスオリジン要求による外部ファイルの読み込みを行うことがないため、ChromeのCORSポリシー・Firefoxの同一生成元ポリシー(SOP)に違反することも無いです。

従って、ローカル環境のブラウザからhtmlを直接開いて、fileプロトコルでアクセスしても、特に問題なく動作させることができるというわけです。
 

★参考にした&関連する記事

CORSや同一生成元ポリシー等

  • 「vite-plugin-singlefile」は、この記事で述べられている「HTMLにファイルの内容を埋め込む (外部ファイル化を諦める。全ブラウザ対応)」を、Viteのビルドで実行するようなものです。

https://qiita.com/nissuk/items/1ede2953a8661dc59214#htmlにファイルの内容を埋め込む-外部ファイル化を諦める全ブラウザ対応
 

「vite-plugin-singlefile」の使用例

  • Svelteで使用している事例
    「vite-svg-loader」を利用したSVG画像の埋め込みも行っています。

https://deep.tacoskingdom.com/blog/194
 

  • GASにClaspでデプロイする関係で使用している事例
    インライン化された単一htmlが必要になるようです。

https://engineer.retty.me/entry/2022/12/22/150035

https://qiita.com/takatama/items/7253d89e52d816bee739

https://prelude.hatenablog.jp/entry/2023/04/16/112107

https://zenn.dev/6mile/articles/359f52afbc3709
 

★関連するライブラリ

「vite-plugin-make-offline」

同様にViteで単一htmlをビルドするプラグインです。
https://www.npmjs.com/package/vite-plugin-make-offline
 

「html-webpack-inline-source-plugin」

webpackで単一htmlをビルドするプラグインです。
https://qiita.com/aknm21/items/616ac96475d282d28ff2

Discussion