9
6

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 1 year has passed since last update.

はてなエンジニアAdvent Calendar 2022

Day 10

CSSのLiveReloadする!開発環境でのCSSのビルドをViteに任せてバックエンドとViteの開発サーバーを連携させる

Last updated at Posted at 2022-12-10

この記事は「はてなエンジニア 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のデフォルト設定でもJavaScriptCSSのバンドルを自動でやってくれますが、TypeScriptSassなどを使うためにはそれぞれdevDependenciesに追加する必要があります。

terminal
yarn add -D vite sass typescript water.css

water.cssはNo-classなCSSフレームワークで、読み込むだけでシュッとしたスタイルが適用されます。これは見た目を整えるために入れています。なんたって見た目をカッコ良くするとやる気が出ます!従ってインストールは完全なオプションです。
参考: No-Class CSS フレームワークをいろいろ比較するサイトを作った | blog.ojisan.io

なお、執筆時のpackage.jsonのライブラリのバージョンは以下の通りです。

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.jspackage.jsonと同じディレクトリに以下の内容で追加します。なお、Viteはデフォルトで全ての.htmlファイルをビルドの対象にします。

vite.config.js
module.exports = {
  root: 'src'
}

今回、TypeScriptの設定のtsconfig.jsonにはこだわりがないので次のコマンドで雛形を用意してもらいます。なお、tsctypescriptdevDependenciesに追加していれば使えます。

terminal
yarn tsc --init

ただし、Viteを使う上で、tsconfig.jsoncompilerOptionsの設定にはいくつか制限があり、"isolatedModules": trueにする必要があります。

Features#Type Script | Vite

ソースコードを書いてみる

簡単にindex.htmlを用意します

src/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を読み込んでいるならば、TypeScriptSassのファイルはViteによって管理されているので、ファイルの変更に応じて自動的に表示も更新されます。開発サーバーを立ち上げる前にhoge.scssの内容とbutton.tsの内容も作ってしまいましょう。

src/scss/hoge.scss
@import "water.css";
@import "fuga/_piyo.scss";

.hoge {
  color: tomato;
}
src/scss/fuga/_piyo.scss
.piyo {
  color: blue;
}
src/button.ts
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


開発サーバーを立ち上げる

次のコマンドでVitevite.config.jsの内容をもとに開発サーバーを立ち上げます。

terminal
yarn vite

出力は次のようになり、この時点でhttp://localhost:5173/にブラウザからアクセスすることでindex.htmlを見ることができます。

terminal
❯❯❯ 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

スクリーンショット 2022-12-10 20.02.09.png

開発サーバーを立ち上げたままソースファイルを編集してみましょう。ブラウザを閉じることなく、変更が表示に反映されることが確認できるはずです。

ビルド

開発サーバーでは.tsファイルや.scssを読み込んでいますが、開発サーバーを経由しない本番用のファイルを作るためには次のコマンドを使ってビルドします。

yarn vite build
ディレクトリ構成
├── src
│   ├── button.ts
│   ├── dist
│   │   ├── assets
│   │   │   ├── index-5a956950.css
│   │   │   └── index-7f113ec2.js
│   │   └── index.html
│   ├── hoge.scss
│   └── index.html

これによって本番用のindex.htmlcss, jssrc/dist/以下に作成されます。このとき、cssjsはミニファイされて、配信に最適な形になっています。

ビルド結果をsrc配下にしたくない場合は、vite.config.jsbuild≫outDirを設定することで変更できます。例えば、今回は次のような設定にするとpackage.jsonと同じディレクトリに出力することができます。

vite.config.js
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の開発サーバーを呼び出し、CSSLive Reloadを実現してみます。

そのために、Viteの開発サーバーのクライアントを使います。

Backend Integration | Vite

開発サーバーのクライアントの使い方

これまで作ってきたものに加えて、開発サーバーのクライアントの挙動を試すために、Viteで管理しないhtmlファイルを作ります。

index.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の開発サーバーを立ち上げます。

terminal
yarn vite

今作ったhtmlファイルをブラウザにドラッグ&ドロップして開いてみると、開発サーバーが立ち上がっている限り、ブラウザーでLive Reloadが機能していることが確認できます。

スクリーンショット 2022-12-10 20.29.18.png

今回はhtmlファイルを直に開いて確認していますが、Viteの開発サーバーのクライアントを使いたい開発サーバーをhttpsスキームで立ち上げている時、Viteの開発サーバーもまたhttpslocalhostで立ち上げる必要があります。Viteの開発サーバは、サーバーオプションに有効な証明書を渡してやることで、httpsで立ち上げられます。次の記事が参考になりました。

参考:

ビルド

次のやり方はVite 4では使えません。ビルドはできるものの、出力されるディレクトリが変わります。

今回SassからCSSへのビルドは次のようにしてみます。

  • src/scss/以下の.scssを全てビルドする
  • ただし、_から始まるファイルはビルドしない
    • なお_をインポートして使うことはできる
  • 最終的にdist/cssにディレクトリ構造とファイル名を保ったま出力する
    • ただし、.cssに拡張子を変える

先に完成形をお見せすると、vite.config.jsを次のようにします。

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.jsrollup.jsの設定を渡せるbuild»rollupOptionsを設定します。

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']),
	}
  }
}

ここまでの設定でビルドをすると出力ディレクトリは以下のようになります。

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.jsonmanifest: trueオプションを設定することで作成するこ、manifest.jsonから対応を引いてくる方法が書かれています。

Backend Integration | Vite

今回は、distディレクトリにディレクトリ構造とファイル名を保ったま出力してみましょう。

dist/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/')}`,
	  }
	}
  }
}

これでビルド結果は次のようになります。

├── dist
│   └── css
│       ├── fuga
│       │   └── _piyo.css
│       └── hoge.css

assetFileNamescssの出力先を変更できるオプションで引数で渡ってくるオブジェクトの中に元のファイル名であるnameフィールドがあるので、nameをそのまま返してやればハッシュ値がつきません。また、この時にディレクトリを変えておくとscssディレクトリに.cssファイルが吐き出されるというねじれた状態から、cssディレクトリに.cssファイルを吐き出すといったことが実現できます。

ところで、assetとはなんでしょう?この記事ではassetの説明は省いています。ここまで見てきたように、Viteは内部でrollup.jsを使っており、assetの説明をするためにはrollup.jsの解説も必要になってきます。この記事を読むにあたってはrollup.jsにはchunk,entry,assetというグループがあり、cssassetに含まれるもので、cssの出力はデフォルトではassets/に出力されれ、cssのファイル名を変更するにはassetFileNamesというオプションを使えば良さそうということを覚えておけば大丈夫です。

Vite 4系ではこのオプションを設定していても次のように出力されます。ハッシュは外せますが、ディレクトリは入っていないといった状態。

├── dist
│   ├── _piyo.css
│   └── hoge.css

_から始まるファイルはビルドしない

これは簡単に実現できて、glob_始まりを弾いてやればよく、ここまですると冒頭で示した完成系と一致します。

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/')}`,
	  }
	}
  }
}

本番では

本番環境ではViteのクライアントを呼び出さないようにして、cssファイルを読み込むようにしたら完成です!お疲れ様でした!

index.html
<!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が出たんですよ(執筆の昨日)!そしたら破壊的変更の影響受けてしまっておおわらわしている。

9
6
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
9
6

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?