この記事は「はてなエンジニア Advent Calendar 2022」の10日目の記事です。9日目は@dekokunさんの「みんなで会議をよくしていこう!と思ってやってるTips」でした。
はじめに
Webページはさまざまなファイルから成り立っており、それぞれのファイルはソースファイルをビルドすることで作られています。従って、ソースファイルの更新をブラウザから確認するためには、単純に考えればソースファイルをビルドしてファイルを配置してからWebページをブラウザから更新する必要があるように思えます。しかし、開発中これらの作業を都度するのは大変なので、開発サーバーを立ち上げてビルドの工程を自動化します。すなわち、ソースファイルの更新に応じてブラウザのリロードなしにWebページの表示にその変更を反映させるようにします。この開発体験をLive Reloadと呼びます。
今回はフロントエンドビルドツールのViteに備わる開発サーバー立ち上げ機能を使ってCSSのライブリロードを実現します。またViteを既存の開発サーバーと連携させる方法をまとめます。
Viteについて
Viteは便利なビルドツールで、デフォルトの設定でもHTML, JavaScript, CSSからなる開発サーバーを立ち上げることができます。
Viteの触り心地を試すために軽くHTML, TypeScript, Sassから成り立つ開発環境を立ててみましょう。
Viteの触り心地を試す編
今回はトップページにTypeScript, Sassを読み込ませてみます。
インストール
必要なライブラリをYarn経由でインストールします。Viteのデフォルト設定でもJavaScriptやCSSのバンドルを自動でやってくれますが、TypeScriptやSassなどを使うためにはそれぞれdevDependenciesに追加する必要があります。
yarn add -D vite sass typescript water.css
water.cssはNo-classなCSSフレームワークで、読み込むだけでシュッとしたスタイルが適用されます。これは見た目を整えるために入れています。なんたって見た目をカッコ良くするとやる気が出ます!従ってインストールは完全なオプションです。
参考: No-Class CSS フレームワークをいろいろ比較するサイトを作った | blog.ojisan.io
なお、執筆時のpackage.jsonのライブラリのバージョンは以下の通りです。
package.json
{
"name": "vite",
"devDependencies": {
"sass": "^1.56.2",
"typescript": "^4.9.4",
"vite": "^3.2.5",
"water.css": "^2.1.1"
}
}
今回、src/以下ソースコードを書いていきましょう。そのために、Viteの設定ファイルたるvite.config.jsをpackage.jsonと同じディレクトリに以下の内容で追加します。なお、Viteはデフォルトで全ての.htmlファイルをビルドの対象にします。
module.exports = {
root: 'src'
}
今回、TypeScriptの設定のtsconfig.jsonにはこだわりがないので次のコマンドで雛形を用意してもらいます。なお、tscはtypescriptをdevDependenciesに追加していれば使えます。
yarn tsc --init
ただし、Viteを使う上で、tsconfig.jsonのcompilerOptionsの設定にはいくつか制限があり、"isolatedModules": trueにする必要があります。
ソースコードを書いてみる
簡単にindex.htmlを用意します
<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="UTF-8">
<title>Title</title>
<link rel="stylesheet" href="scss/hoge.scss">
<script type="module" src="button.ts"></script>
</head>
<body>
<content>
<h1>テスト</h1>
<p class="hoge">hogehoge</p>
<p class="piyo">piyopiyo</p>
<button id="hello">Hello</button>
</content>
</body>
</html>
注目したいポイントは次の2行で、TypeScriptソースを使いたいところは.tsのままSassならば.scssのまま、index.htmlからの相対パスで記載している点です。
<link rel="stylesheet" href="scss/hoge.scss">
<script type="module" src="button.ts"></script>
Viteの開発サーバーでindex.htmlを読み込んでいるならば、TypeScriptやSassのファイルはViteによって管理されているので、ファイルの変更に応じて自動的に表示も更新されます。開発サーバーを立ち上げる前にhoge.scssの内容とbutton.tsの内容も作ってしまいましょう。
@import "water.css";
@import "fuga/_piyo.scss";
.hoge {
color: tomato;
}
.piyo {
color: blue;
}
const element = document.getElementById("hello");
if (element) {
element.addEventListener("click", (e) => {
alert(`hello: ${element.innerText}`)
});
}
ここまでのディレクトリ構成は以下のようになっています。
ディレクトリ構成
.
├── .DS_Store
├── .tool-versions
├── index.html
├── package.json
├── src
│ ├── button.ts
│ ├── index.html
│ └── scss
│ ├── fuga
│ │ └── _piyo.scss
│ └── hoge.scss
├── tsconfig.json
├── vite.config.js
└── yarn.lock
開発サーバーを立ち上げる
次のコマンドでViteはvite.config.jsの内容をもとに開発サーバーを立ち上げます。
yarn vite
出力は次のようになり、この時点でhttp://localhost:5173/にブラウザからアクセスすることでindex.htmlを見ることができます。
❯❯❯ yarn vite
yarn run v1.22.19
warning package.json: No license field
$ ********************
VITE v4.0.0 ready in 254 ms
➜ Local: http://localhost:5173/
➜ Network: use --host to expose
➜ press h to show help
開発サーバーを立ち上げたままソースファイルを編集してみましょう。ブラウザを閉じることなく、変更が表示に反映されることが確認できるはずです。
ビルド
開発サーバーでは.tsファイルや.scssを読み込んでいますが、開発サーバーを経由しない本番用のファイルを作るためには次のコマンドを使ってビルドします。
yarn vite build
ディレクトリ構成
├── src
│ ├── button.ts
│ ├── dist
│ │ ├── assets
│ │ │ ├── index-5a956950.css
│ │ │ └── index-7f113ec2.js
│ │ └── index.html
│ ├── hoge.scss
│ └── index.html
これによって本番用のindex.htmlとcss, jsがsrc/dist/以下に作成されます。このとき、cssとjsはミニファイされて、配信に最適な形になっています。
ビルド結果をsrc配下にしたくない場合は、vite.config.jsのbuild≫outDirを設定することで変更できます。例えば、今回は次のような設定にするとpackage.jsonと同じディレクトリに出力することができます。
module.exports = {
root: 'src',
build: {
outDir: '../dist'
}
}
ビルド結果のディレクトリ構成
.
├── .tool-versions
├── dist
│ ├── assets
│ │ ├── index-3c0c8df7.css
│ │ └── index-7f113ec2.js
│ └── index.html
├── index.html
├── package.json
├── src
│ ├── button.ts
│ ├── index.html
│ └── scss
│ ├── fuga
│ │ └── _piyo.scss
│ └── hoge.scss
├── tsconfig.json
├── vite.config.js
└── yarn.lock
Viteとバックエンドを連携する
基本的なアイデア
このように、全てをViteに任せるのであれば簡単に開発サーバーを利用できます。
今回想定する開発環境ではViteではない開発サーバーがあって、その開発サーバーからViteの開発サーバーを呼び出し、CSSのLive Reloadを実現してみます。
そのために、Viteの開発サーバーのクライアントを使います。
開発サーバーのクライアントの使い方
これまで作ってきたものに加えて、開発サーバーのクライアントの挙動を試すために、Viteで管理しないhtmlファイルを作ります。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
<script type="module" src="http://localhost:5173/@vite/client"></script>
<link rel="stylesheet" href="http://localhost:5173/scss/hoge.scss">
</head>
<body>
<content>
<h1>テスト</h1>
<p class="hoge">hogehoge</p>
<p class="piyo">piyopiyo</p>
</content>
</body>
</html>
重要な行は次の行で、scriptタグでViteの開発サーバーのクライアントを読み込み、使いたいscssファイルをhttp://localhost:5173/(Viteの設定ファイルのrootからの相対パス)で呼び出す点です。
<script type="module" src="http://localhost:5173/@vite/client"></script>
<link rel="stylesheet" href="http://localhost:5173/scss/hoge.scss">
そしてViteの開発サーバーを立ち上げます。
yarn vite
今作ったhtmlファイルをブラウザにドラッグ&ドロップして開いてみると、開発サーバーが立ち上がっている限り、ブラウザーでLive Reloadが機能していることが確認できます。
今回はhtmlファイルを直に開いて確認していますが、Viteの開発サーバーのクライアントを使いたい開発サーバーをhttpsスキームで立ち上げている時、Viteの開発サーバーもまたhttpsなlocalhostで立ち上げる必要があります。Viteの開発サーバは、サーバーオプションに有効な証明書を渡してやることで、httpsで立ち上げられます。次の記事が参考になりました。
参考:
ビルド
次のやり方はVite 4では使えません。ビルドはできるものの、出力されるディレクトリが変わります。
今回SassからCSSへのビルドは次のようにしてみます。
-
src/scss/以下の.scssを全てビルドする - ただし、
_から始まるファイルはビルドしない- なお
_をインポートして使うことはできる
- なお
- 最終的に
dist/cssにディレクトリ構造とファイル名を保ったま出力する- ただし、
.cssに拡張子を変える
- ただし、
先に完成形をお見せすると、vite.config.jsを次のようにします。
import glob from 'fast-glob';
module.exports = {
root: 'src',
build: {
outDir: '../dist',
rollupOptions: {
/**
* scssのビルド
* - scss/以下の.scssをビルドする
* - ただし,_から始まるファイルはビルドしない
*/
input: glob.sync(['src/scss/**/*.scss', '!**/_*']),
output: {
assetFileNames: ({name}) => `${name?.replace('scss/', 'css/')}`,
}
}
}
}
個別に解説します。
src/scss/以下の.scssを全てビルドする
.scssをビルドの対象にするには、全ての.scssファイルを列挙し、それらをビルドの対象にすることで実現できます。
globのように複数ファイルを列挙するにはnpmパッケージのfast-globが便利です。
yarn add -D fast-glob
Viteは内部でrollup.jsを使っているので、ビルドの対象を変更するには、vite.config.jsでrollup.jsの設定を渡せるbuild»rollupOptionsを設定します。
import glob from 'fast-glob';
module.exports = {
root: 'src',
build: {
outDir: '../dist',
rollupOptions: {
/**
* scssのビルド
* - scss/以下の.scssをビルドする
*/
input: glob.sync(['src/scss/**/*.scss']),
}
}
}
ここまでの設定でビルドをすると出力ディレクトリは以下のようになります。
yarn vite build
.
├── dist
│ └── assets
│ ├── _piyo.167b97ea.css
│ └── hoge.3c0c8df7.css
├── index.html
├── package.json
├── src
│ ├── button.ts
│ └── scss
│ ├── fuga
│ │ └── _piyo.scss
│ └── hoge.scss
├── tsconfig.json
├── vite.config.js
└── yarn.lock
ここで注目したいのは、デフォルトの設定では出力ファイルはassets配下になり、かつ、ハッシュ値がつけられており、元のファイルとの対応を取る必要がありそうだという点です。
この解決策はViteのドキュメントに書かれています。具体的には、出力ファイルと元ファイルの対応表であるmanifest.jsonをmanifest: trueオプションを設定することで作成するこ、manifest.jsonから対応を引いてくる方法が書かれています。
今回は、distディレクトリにディレクトリ構造とファイル名を保ったま出力してみましょう。
dist/cssにディレクトリ構造とファイル名を保ったま出力する
これを実現するには、出力先オプションを設定すればオッケーです!
import glob from 'fast-glob';
module.exports = {
root: 'src',
build: {
outDir: '../dist',
rollupOptions: {
/**
* scssのビルド
* - scss/以下の.scssをビルドする
* - ただし,_から始まるファイルはビルドしない
*/
input: glob.sync(['src/scss/**/*.scss']),
output: {
assetFileNames: ({name}) => `${name?.replace('scss/', 'css/')}`,
}
}
}
}
これでビルド結果は次のようになります。
├── dist
│ └── css
│ ├── fuga
│ │ └── _piyo.css
│ └── hoge.css
assetFileNamesはcssの出力先を変更できるオプションで引数で渡ってくるオブジェクトの中に元のファイル名であるnameフィールドがあるので、nameをそのまま返してやればハッシュ値がつきません。また、この時にディレクトリを変えておくとscssディレクトリに.cssファイルが吐き出されるというねじれた状態から、cssディレクトリに.cssファイルを吐き出すといったことが実現できます。
ところで、assetとはなんでしょう?この記事ではassetの説明は省いています。ここまで見てきたように、Viteは内部でrollup.jsを使っており、assetの説明をするためにはrollup.jsの解説も必要になってきます。この記事を読むにあたってはrollup.jsにはchunk,entry,assetというグループがあり、cssはassetに含まれるもので、cssの出力はデフォルトではassets/に出力されれ、cssのファイル名を変更するにはassetFileNamesというオプションを使えば良さそうということを覚えておけば大丈夫です。
Vite 4系ではこのオプションを設定していても次のように出力されます。ハッシュは外せますが、ディレクトリは入っていないといった状態。
├── dist
│ ├── _piyo.css
│ └── hoge.css
_から始まるファイルはビルドしない
これは簡単に実現できて、globで_始まりを弾いてやればよく、ここまですると冒頭で示した完成系と一致します。
import glob from 'fast-glob';
module.exports = {
root: 'src',
build: {
outDir: '../dist',
rollupOptions: {
/**
* scssのビルド
* - scss/以下の.scssをビルドする
* - ただし,_から始まるファイルはビルドしない
*/
input: glob.sync(['src/scss/**/*.scss', '!**/_*']),
output: {
assetFileNames: ({name}) => `${name?.replace('scss/', 'css/')}`,
}
}
}
}
本番では
本番環境ではViteのクライアントを呼び出さないようにして、cssファイルを読み込むようにしたら完成です!お疲れ様でした!
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
<link rel="stylesheet" href="dist/css/hoge.css">
</head>
<body>
<content>
<h1>テスト</h1>
<p class="hoge">hogehoge</p>
<p class="piyo">piyopiyo</p>
</content>
</body>
</html>
執筆後記
アドベントカレンダーのネタを温めておいて、いざ書こうとしたらVite 4が出たんですよ(執筆の昨日)!そしたら破壊的変更の影響受けてしまっておおわらわしている。

