Viteで純粋なPugを使う
ViteでPugを使おうとすると少し厄介だったので、環境構築の手順をまとめました。
「vite pug」等で検索するとVueやReact、HTMLを噛ませる方法はヒットするのですが、Pugを単体で使う方法はなかなかヒットしません。なのでViteで純粋なPugを使えるようにしようというのが本記事の趣旨です。
環境構築
まずは普通に環境構築をします。
Viteプロジェクトの作成
公式ガイドの手順に従ってViteの環境構築を行います。
本記事ではyarn
を使いますが、npm
でも問題ないと思います。
$ yarn create vite
#...
✔ Project name: … vite-pug-project
✔ Select a framework: › vanilla
✔ Select a variant: › vanilla-ts
#...
プロジェクト名はvite-pug-project
、フレームワークはなし(vanilla
)でTypeScript(vanilla-ts
)を選択しました。
$ cd vite-pug-project # プロジェクトディレクトリに移動
$ yarn # yarn install でモジュールをインストール
$ yarn dev # 開発サーバーの立ち上げ
指示された手順に従っているだけですが、これで開発サーバーが立ち上がります。
非常に簡単ですね。
Hello Vite!
Documentation
という文字がブラウザに表示されていれば問題ありません。
環境のカスタマイズ
現場のディクレクトリ構造は以下のようになっていると思います。
vite-pug-project
├── node_modules - ...略
├── favicon.svg
├── index.html
├── package.json
├── src
│ ├── main.ts
│ ├── style.css
│ └── vite-env.d.ts
├── tsconfig.json
└── yarn.lock
このままでもいいのですが、index.html
がルートにあるとMulti-Page Appでは扱いづらいのでsrc
フォルダの中に移動します(ついでにfaviconもいらないので削除してください)。
それに伴って、Viteの設定ファイルをいじります。
プロジェクトのルートにvite.config.js
を作成して、下記のように記述してください。
import { resolve } from "path";
import { defineConfig } from "vite";
export default defineConfig({
root: "src",
build: {
outDir: resolve(__dirname, "dist"),
},
});
root(index.html
が置かれる場所)をsrc
に設定して、ビルドするディレクトリをdist
に指定しています。
場所を移動したのでindex.html
も書き換えます。
<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="UTF-8" />
- <link rel="icon" type="image/svg+xml" href="favicon.svg" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Vite App</title>
</head>
<body>
<div id="app"></div>
- <script type="module" src="/src/main.ts"></script>
+ <script type="module" src="/main.ts"></script>
</body>
</html>
一度yarn dev
で開発サーバー立ち上げてみて、問題なければyarn build
コマンドでビルドしてください。
vite-pug-project
├── node_modules - ...略
├── dist
│ ├── assets
│ │ ├── index.06d14ce2.css
│ │ └── index.ad4f7fa4.js
│ └── index.html
├── package.json
├── src
│ ├── index.html
│ ├── main.ts
│ ├── style.css
│ └── vite-env.d.ts
├── tsconfig.json
├── vite.config.js
└── yarn.lock
無事ビルドができれば上記のようなディレクトリ構造になると思います。
Pugを使えるようにする
ここまできたらPugを使うための準備をします。
まずはPugに必要なモジュールと型定義ファイルをインストールしましょう。
$ yarn add --dev pug @types/pug @types/node
次にindex.html
を削除して、index.pug
を追加しましょう。
以下は単純にindex.html
内容をindex.pug
に書き換えただけのものになります(変化がわかるようにtitle
だけ変更)。
doctype html
html(lang="ja")
head
meta(charset="UTF-8")
meta(name="viewport", content="width=device-width, initial-scale=1.0")
//- index.pugからビルドされたものだとわかるようにtitleだけ変更する
title Vite Pug App
body
#app
script(src="/main.ts" type="module")
ビルド用プラグインの作成
Pugをビルドするためにプラグインを自作します。
プラグインを自作する方法は以下に載っています。
ちゃんと作ろうとするとrollup.js
の知識も必要となります(なお、私はVite
もrollup.js
も初めて学びました)。
ルートにplugins
ディレクトリを作成して、その中にプラグインを作成しましょう。
import fs from "fs";
import type { Plugin } from "vite";
import { compileFile } from "pug";
export const vitePluginPugBuild = (): Plugin => {
const pathMap: Record<string, string> = {};
return {
// Vite専用プラグインの命名には「vite-plugin-」のプレフィックスをつけるらしい
name: "vite-plugin-pug-build",
enforce: "pre",
// ビルド時のみ
apply: "build",
// カスタムリゾルバーを定義できる
// エントリーポイントの加工をできる
resolveId(source: string) {
if (source.endsWith(".pug")) {
// xxxx.pug へのリクエストを
// xxxx.html へのリクエストに偽る
const dummy = `${
source.slice(0, Math.max(0, source.lastIndexOf("."))) || source
}.html`;
// xxxx.pug と xxxx.html 対応表を作る
pathMap[dummy] = source;
// xxxx.html を返す
return dummy;
}
},
// ローダーを定義できる
// ここでファイルの中身を読み込む
load(id: string) {
if (id.endsWith(".html")) {
// xxxx.html へのリクエストがあった時
if (pathMap[id]) {
// もとのファイルが xxxx.pug の時は pug をコンパイルして返す
const html = compileFile(pathMap[id])();
return html;
}
// もとのファイルも xxxx.html の時は xxxx.html の中身をそのまま返す
return fs.readFileSync(id, "utf-8");
}
},
};
};
上記で何をやっているか簡単に説明すると、xxxx.pug
に来たリクエストをxxxx.html
と偽って(?)、xxxx.pug
をコンパイルした結果を返すということをしています。
resolveId
やload
はrollup.js
に存在するビルドフックで、ビルド時のそれぞれのタイミングで呼び出される関数です。
詳しくは以下のドキュメントをご参照ください。
後ほどもう1つプラグインを作成するので、vite-plugin-pug.ts
を作成しラップしてexport
します。
import { vitePluginPugBuild } from "./vite-plugin-pug-build";
const vitePluginPug = () => {
return [vitePluginPugBuild()];
};
export default vitePluginPug;
プラグインの読み込みとindex.pug
をエントリーポイントとして明示的に指定するためにvite.config.js
を次のように書き換えます。
import { resolve } from "path";
import { defineConfig } from "vite";
import vitePluginPug from "./plugins/vite-plugin-pug";
export default defineConfig({
root: "src",
build: {
outDir: resolve(__dirname, "dist"),
rollupOptions: {
input: {
main: resolve(__dirname, "src", "index.pug"),
},
},
},
plugins: [vitePluginPug()],
});
現状のディクトリ構造は下記のようになります。
vite-pug-project
├── dist
│ ├── assets
│ │ ├── index.06d14ce2.css
│ │ └── index.ad4f7fa4.js
│ └── index.html
├── package.json
├── plugins
│ ├── vite-plugin-pug-build.ts
│ └── vite-plugin-pug.ts
├── src
│ ├── index.pug
│ ├── main.ts
│ ├── style.css
│ └── vite-env.d.ts
├── tsconfig.json
├── vite.config.js
└── yarn.lock
ここまできたら一旦yarn build
してみます。
<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<!-- Vite Pug App なので index.pug がビルドされている -->
<title>Vite Pug App</title>
<script type="module" crossorigin src="/assets/main.94834e27.js"></script>
<link rel="stylesheet" href="/assets/main.b16cdc1a.css" />
</head>
<body>
<div id="app"></div>
</body>
</html>
dist
ディレクトリにindex.html
がビルドされていることがわかります!
開発サーバー用のプラグインの作成
ビルドはできましたが、この状態でyarn dev
をして開発サーバーを立ち上げても、index.pug
ファイルを確認することはできません。
この問題を解決するためには開発サーバー用のプラグインも作成する必要があります。
vite-plugin-pug-serve.ts
というファイルを作成して、下記のように記述します。
import fs from "fs";
import { send } from "vite";
import type { ViteDevServer, Plugin } from "vite";
import { compileFile } from "pug";
const transformPugToHtml = (server: ViteDevServer, path: string) => {
const compliled = compileFile(path)();
return server.transformIndexHtml(path, compliled);
};
export const vitePluginPugServe = (): Plugin => {
return {
name: "vite-plugin-pug-serve",
enforce: "pre",
// 開発サーバー時のみ
apply: "serve",
handleHotUpdate(context) {
// ファイルが保存された時にホットリロードする
// この記述がないと xxxx.pug を保存した時にリロードされない
context.server.ws.send({
type: "full-reload",
});
return [];
},
configureServer(server) {
server.middlewares.use(async (req, res, next) => {
const root = server.config.root;
let fullReqPath = root + req.url;
if(fullReqPath.endsWith("/")){
fullReqPath += "index.html"
}
if (fullReqPath.endsWith(".html")) {
// xxxx.html にリクエストがきた時
if (fs.existsSync(fullReqPath)) {
// xxxx.html が存在するならそのまま次の処理へ
return next();
}
// xxxx.htmlが存在しないときは xxxx.pug があるか確認する
const pugPath = `${
fullReqPath.slice(0, Math.max(0, fullReqPath.lastIndexOf("."))) ||
fullReqPath
}.pug`;
if(!fs.existsSync(pugPath)){
// xxxx.pug が存在しないなら 404 を返す
return send(req, res, "404 Not Found", "html", {});
}
// xxxx.pug が存在するときは xxxx.pug をコンパイルした結果を返す
const html = await transformPugToHtml(server, pugPath);
return send(req, res, html, "html", {});
} else {
// xxxx.html 以外へのリクエストはそのまま次の処理へ
return next();
}
});
},
};
};
上記では開発サーバーを立ち上げた状態でxxxx.html
へのリクエストがきたらxxxx.pug
をコンパイルして結果を返すという処理を記述しています(xxxx.html
ファイルが存在するときはそれをそのまま返す)。
send()
メソッドについては公式ドキュメントに記載があったわけではないのですが、他の方のプラグインで使っていたので存在を知りました。
上記を見る限りだと、拡張子の種類によって適切なヘッダーを付与してレスポンスを返してくれるメソッドのようです。
最後にvite-plugin-pug-build.ts
と同様にvite-plugin-pug.ts
からまとめてexport
します。
import { vitePluginPugBuild } from "./vite-plugin-pug-build";
import { vitePluginPugServe } from "./vite-plugin-pug-serve";
const vitePluginPug = () => {
return [vitePluginPugBuild(), vitePluginPugServe()];
};
export default vitePluginPug;
ここまで書いたらyarn dev
して開発サーバーを立ち上げてください。
Hello Vite!
Documentation
が表示されていれば完璧です。
ボイラープレート
ここまでの作業をボイラープレートとしてまとめてあります。
全体の完成コードを確認したい方は上記をご参照ください。
パフォーマンスについて
本記事の内容は必ずしもViteに最適化された(想定された)方法ではない可能性があります。
一旦細かなパフォーマンスのことは置いておいて、従来の方法でPugを使えるようしようというのが趣旨になります。
(!) Could not auto-determine entry point from rollupOptions or html files and there are no explicit optimizeDeps.include patterns. Skipping dependency pre-bundling.
例えば本記事の内容で開発サーバーを立ち上げるとターミナルに上記のような文言が表示されます。
vite.config.js
で指定したエントリーポイントがxxxx.pug
なので、(おそらく想定している拡張子とは違って?)うまくパスの解決ができていないようです。
それによりdependency pre-bundling
がされないようなのですが、パフォーマンスへの影響がどのくらいあるのかは未検証です(というか詳しい方いたら教えたください)。
dependency pre-bundling
については以下に載っています。
まとめ
ViteでPug単体を使う方法でした。正攻法ではないかもしれませんが、webpackや他のビルドツールに変わるオプションの1つとして備えておくのは十分ありだと思います。
今回はじめてVite(またrollup.js)を触ったのですが、自作プラグインの作り方やビルドフックについて理解が進んだので、違う機会でも活かせそうだなと感じました。ぜひ皆さんもいろいろ触ってみてください。
参考
Author And Source
この問題について(Viteで純粋なPugを使う), 我々は、より多くの情報をここで見つけました https://zenn.dev/yend724/articles/20220408-tfq16buha8ctdzp7著者帰属:元の著者の情報は、元のURLに含まれています。著作権は原作者に属する。
Collection and Share based on the CC protocol