この記事は「はてなエンジニア 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
が出たんですよ(執筆の昨日)!そしたら破壊的変更の影響受けてしまっておおわらわしている。