【更新日 : 】
【詳細版】Viteでコーダーのコーディング環境(HTML(ejsライク:ハンドルバー化)・Sass・JS)を作る
- Category:
- 開発環境
※本記事はejsを利用していません。
代わりにHTMLファイルをejsと同等の機能を追加する(ハンドルバー化する)プラグイン vite-plugin-handlebars を利用しています。ejsは以下プラグイン vite-plugin-ejs を利用すると実装できるようです。
2025.01.17 更新箇所
ビルド時のファイル名調整に用いるコードで参照している「assetInfo.name」が非推奨になったため「assetInfo.names[0]」に修正しました。
本記事は各設定の詳細を記載しています。
構築する手順とファイル構成だけが知りたい場合は以下の【簡易版】をご参照ください。
Viteでコーダー向けの環境構築をしたサンプルです。
通常のコーディングと同じ感覚(HTML(ejsライク)・Sass・JS)でコーディングできる設定を目指しました。
Viteの環境構築にはターミナル(Mac)やコマンドプロンプト(Windows)を用います。
本記事はこれらの基礎知識があることを前提とした記事です。
Viteは「HTML、SCSS、JS」で1セットなので、特定のファイルのみビルドしたい場合は別のツールを利用してください。
node.js v16.16.0 で動作確認をしています。
不具合が出る場合は実行環境のバージョンを合わせてください。
動作確認はmacです。windowsでは未検証ですので予めご了承ください。
Viteについて
Vite(ヴィート)は高速な開発環境を構築することができるフロントエンドのビルドツールです。
公式サイトが日本語化されており、ドキュメントも日本語で読むことができます。
Viteは「バンドルする」ということがコンセプトなツールのようですので、種々のファイルを分割して扱っている通常のWebサイトコーディングでの利用は想定していなさそうな印象ですが、検証したところ次項のような構成は組むことができたのでサンプルを作成しました。
この記事のViteでできること
- 複数のHTMLページ生成
- HTMLファイルをejsのように扱う(HTMLをハンドルバー化してejsの代替とする)
- ビルド後のHTMLファイルを整形(任意追加)
- SCSSの書き出し(PostCSSによるオプション設定)
- autoprefixer:ベンダープレフィックスの追加
- css-declaration-sorter:プロパティ順のソート(smacss)
- postcss-sort-media-queries:メディアクエリをソートして1つにまとめる
- postcss-purgecss:CSSファイルから未使用のスタイルを削除する
- postcss-normalize-charset:先頭にcharset追加
- publicに内包したサブJS(特定のページのみ追加したいJS)の圧縮(任意追加)
この記事のViteでできないこと
画像の圧縮はしない
プロジェクトで利用する固定画像は個別にコントロールしたい場合もあるので、コーディング環境に一括設定はしない派です。
Viteでは vite-plugin-imagemin というプラグインが存在しているようですので、そちらを利用すれば設定可能だと思います。
本記事では扱いませんので必要な方は以下をご参照ください。
【2024.08.06更新】Viteの外で利用できる画像圧縮ツールとしてsharpの実装記事を公開しました。
buildコマンド実行後に圧縮処理を組み合わせるなど、工夫次第でViteと共存させることも可能かと思います。
ベース環境の構築
ベース環境の構築には以下の記事を参考にさせていただきました。
Vite は Node.js >=14.6.0 のバージョンが必要ですので事前にインストールしておいてください。
プロジェクト作成
構築したいディレクトリへ移動
作業を始める前に設定したいプロジェクトのあるディレクトリまで移動してください。
Windowsでの移動
cd C:¥Users¥.....¥Viteを構築したいプロジェクトディレクトリ
Macでの移動
cd /Users/....../Viteを構築したいプロジェクトディレクトリ
macなら該当フォルダを右クリック→「フォルダに新規ターミナルタブ」を選択してターミナルを立ち上げるとスムーズです。
Viteをインストール
プロジェクトを作成したいディレクトリで以下を実行すると、①〜③の質問が始まるので順番に入力・選択してください。その後は④、⑤と続けて実行してください。
npm init vite@latest
①プロジェクト名(構築するプロジェクトの名称)を入力
? Project name: › vite-project
②利用するフレームワークを選択:vanillaを選択
? Select a framework: › - Use arrow-keys. Return to submit.
❯ vanilla
vue
react
preact
lit
svelte
③テンプレートのバリエーションを選択:vanillaを選択
? Select a variant: › - Use arrow-keys. Return to submit.
❯ vanilla
vanilla-ts
④作成したプロジェクトへ移動
cd vite-project
⑤作成したプロジェクトに初期インストール
npm install
動作チェック(省略可能)
ここまで完了するとベースの構築は完了です。
「npm run dev」で開発サーバが起動するようになります。
※あくまでもテスト起動なのでこの工程は省いても問題ありません。
※一度立ち上げたタスクは control + c で終了できます。
npm run dev
ファイルのビルド(省略可能)
「npm run build」でdist/ディレクトリに公開用のファイル一式が書き出されます。
npm run build
想定しているディレクトリ構成
初期インストールを実行するとViteのサンプルファイル一式が出力さていますが不要なものは全て削除します。
本記事では開発ファイルを src/で管理する構成です。
存在していないファイルは以降の手順を進めながら新規作成してください。
■プロジェクトディレクトリ
┣ dist (ビルドしたファイルが出力される場所)
┣ package.json (プロジェクトのjsonファイル)
┣ postcss.config.cjs (PostCSSの設定ファイル)
┣ vite.config.js (viteの設定ファイル)
┣ .jsbeautifyrc (任意:HTML整形プラグインjs-beautifyの設定ファイル)
┃
┣ node_modules (編集不要:自動生成されるコアファイル格納場所)
┣ package-lock.json (編集不要:インストールしたパッケージ情報などが記載されている)
┃
┗ src
┣ index.html
┣ xxx.html (複数ページを追加する場合)
┃
┣ components (HTMLのコンポーネントパーツを格納)
┃ ┗ header.html
┃ xxx.html ...
┃
┣ js (メインのモジュールJSファイルを格納)
┃ ┗ main.js
┃
┣ public (Viteの変換対象外のディレクトリ。distに中身がそのままコピーされます。)
┃ ┗ assets (そのまま移動させたいファイルを必要に応じて格納していく)
┃ ┣ fonts
┃ ┃ ┗ xxx.woff2 ...
┃ ┣ js
┃ ┃ ┗ xxx.js ...
┃ ┗ images
┃ ┗ xxx.jpg ...
┃
┗ scss
┣ style.scss
┗ (各記法に合わせたディレクトリ構成)
vite.config.jsを作成
ディレクトリの変更などViteの各設定をカスタマイズするためのvite.config.jsをルートに作成します。
■プロジェクトディレクトリ
┣ vite.config.js (※新規作成)
┃
┣ package-lock.json
┣ package.json
┗ src
vite.config.js
import { defineConfig } from 'vite';
export default defineConfig({
root: './src', //開発ディレクトリ設定
build: {
outDir: '../dist', //出力場所の指定
},
});
Viteの初期設定ではnpm run buildするとファイルの出力結果が以下のような構成になります。
■dist
┣ index.html
┗ assets
┣ index.js
┗ index.css
この構成のまま納品することはまずないと思いますので、「assets/js/main.js」「assets/css/style.css」の構成になるようvite.config.jsのbuildプロパティに rollupOptions の設定を追加します。
vite.config.js
import { defineConfig } from 'vite';
export default defineConfig({
root: './src', //開発ディレクトリ設定
build: {
outDir: '../dist', //出力場所の指定
rollupOptions: { //ファイル出力設定
output: {
assetFileNames: (assetInfo) => {
let extType = assetInfo.names[0].split('.')[1];
//Webフォントファイルの振り分け
if (/ttf|otf|eot|woff|woff2/i.test(extType)) {
extType = 'fonts';
}
if (/png|jpe?g|svg|gif|tiff|bmp|ico/i.test(extType)) {
extType = 'images';
}
//ビルド時のCSS名を明記してコントロールする
if(extType === 'css') {
return `assets/css/style.css`;
}
return `assets/${extType}/[name][extname]`;
},
chunkFileNames: 'assets/js/[name].js',
entryFileNames: 'assets/js/[name].js',
},
},
},
});
これで以下のような構成で出力されるようになります。
■dist
┣ index.html
┗ assets
┣ fonts (Webフォントが存在する場合)
┃ ┗ xxx.woff2
┣ js
┃ ┗ index.js
┗ css
┗ style.css
rollupOptionsの設定は以下を参考にさせていただきました。
rollupOptionsの詳細は以下の公式リファレンスをご参照ください。
Sass(SCSS)を使う
Sassモジュールのインストール
sassモジュールをインストールするとSassファイルを扱えるようになるので、以下のコマンドでインストールします。
npm install -D sass
Sassファイルの設定
Viteは特に設定を追記せずとも SCSSファイルを認識してくれるので、モジュールをインストールした後は既存のCSSをSCSSに置き換えるだけです。
- src/のcssディレクトリをscssに変更
- style.cssをstyle.scssに変更
■プロジェクトディレクトリ
┗ src
┣ index.html
┣ js
┃ ┗ main.js
┗ scss
┗ style.scss
cssファイルの読み込み方法
Viteの初期設定ではCSSは main.js 内で読み込んでいますが、
通常のコーディングではHTMLでCSSを読み込む方が一般的なので、HTMLで読み込むよう変更します。
main.js(未編集)
import './style.css'
〜省略〜
main.jsの中身を削除
CSSのimport設定やテストコードは全て不要なので削除してください。
main.js
//空にしてください。
index.htmlにlinkタグを追加
通常のコーディングと同じようにlinkタグで読み込ませます。
相対パスでSCSSのまま設定します。(buildすると自動的にCSSへ置き換わります。)
※lang属性はjaに変更し、初期に入っているfavicon設定は不要なので削除しています。
※main.jsも同じように相対パスを修正してください。
index.html
<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Vite App</title>
<!-- CSSの読み込みを追加 -->
<link rel="stylesheet" href="./scss/style.scss">
</head>
<body>
<div id="app"></div>
<!-- 相対パスを修正 -->
<script type="module" src="./js/main.js"></script>
</body>
</html>
SASSの警告(The legacy JS API〜)に対する対応
Dart Sass 2.0.0 で古いコンパイラー(legacy JS API)が削除されるに当たって下記の警告が出るようになりました。
Deprecation Warning: The legacy JS API is deprecated and will be removed in Dart Sass 2.0.0.
More info: https://sass-lang.com/d/legacy-js-api
警告の詳細については上記More infoに記載されている https://sass-lang.com/d/legacy-js-api をご参照ください。
新しいAPIを利用して警告を消す
Viteはデフォルト設定がレガシーAPIのためこの警告が出てしまいますが、明示的に新しいAPI「modern-compiler」を利用するようvite.config.js に css.preprocessorOptions の設定を追記することで対処が可能です。
vite.config.js
import { defineConfig } from 'vite';
export default defineConfig({
root: './src', //開発ディレクトリ設定
css: {
preprocessorOptions: {
scss: {
api: 'modern-compiler'
}
}
},
build: {
〜省略〜
},
});
css.preprocessorOptionsの詳細については以下をご参照ください。
CSSビルド時にPostCSSによるオプション設定を追加する(任意)
ViteはPostCSSを扱えるので、ベンダープレフィックスを付与したり様々なオプションを追加することが可能です。
PostCSSのインストール
以下のコマンドでベースとなるPostCSSをインストールします。
npm install -D postcss
PostCSSの利用方法
PostCSSを利用するにはプロジェクトのルートディレクトリに「postcss.config.cjs」を作成し、プラグインを呼び出すコードを記述する必要があります。
■プロジェクトディレクトリ
┣ postcss.config.cjs (※新規作成)
┃
┣ package-lock.json
┣ package.json
┗ src
postcss.config.cjs
module.exports = {
plugins: {
//呼び出すプラグインを記述していく
},
}
本記事のプラグインを一括でインストールする
本記事のプラグインを一括でインストールする場合は以下のコードを実行してください。
精査したい場合は以降を参照しそれぞれ個別にインストールしてください。
npm install -D postcss autoprefixer postcss-sort-media-queries css-declaration-sorter @fullhuman/postcss-purgecss postcss-normalize-charset
autoprefixerの追加
autoprefixerはベンダープレフィックスを自動付与してくれるPostCSSのプラグインです。
以下のコマンドでautoprefixerをインストールします。
npm install -D autoprefixer
browserslistの設定
package.jsonに対象となるブラウザ範囲「browserslist」を追記してください。
package.json
{
〜省略〜
"devDependencies": {
〜省略〜
},
"browserslist": [
"last 3 versions",
"> 5%",
"Firefox ESR",
"not dead"
]
}
browserslistは環境に合わせて適宜変更してください。
設定の詳細は以下をご参照ください。
autoprefixerを有効化する
postcss.config.cjsに追記してください。
postcss.config.cjs
module.exports = {
plugins: {
autoprefixer: {},
},
}
特別対応が必要でなければオプションなしで十分だと思います。
細かいオプション設定が必要な場合は環境に合わせて適宜追加してください。autoprefixerのオプション設定の詳細は以下をご参照ください。
postcss-sort-media-queries
メディアクエリをソートして1つにまとめてくれるPostCSSのプラグインです。
npm install -D postcss-sort-media-queries
postcss.config.cjs
module.exports = {
plugins: {
autoprefixer: {},
'postcss-sort-media-queries': {},
},
}
css-declaration-sorter
CSSプロパティの順番をソートするPostCSSのプラグインです。
npm install -D css-declaration-sorter
postcss.config.cjs
module.exports = {
plugins: {
autoprefixer: {},
'postcss-sort-media-queries': {},
'css-declaration-sorter':{order:'smacss'},
},
}
orderの設定値は「alphabetical」「concentric-css」の3種類です。
詳しくは以下をご参照ください。
postcss-purgecss
CSSファイルから未使用のスタイルを削除するPostCSSのプラグインです。
案件によっては利用しない方が良いこともありますので、本当に必要かどうか必ず検討してください。
npm install -D @fullhuman/postcss-purgecss
postcss.config.cjs
module.exports = {
plugins: {
autoprefixer: {},
'postcss-sort-media-queries': {},
'css-declaration-sorter':{order:'smacss'},
'@fullhuman/postcss-purgecss': {
content: ['./src/**/*.html','./src/js/**/*.js'],
//除外設定 https://purgecss.com/safelisting.html
safelist: ['hoge']
},
}
}
postcss-normalize-charset
CSSファイルの先頭にcharset追加するPostCSSのプラグインです。
昔文字化けした経験があって明記しておきたい派なので個人的に導入していますが、最近はブラウザがよしなに判定してくれるようなので多くの場合は不要だと思います。
npm install -D postcss-normalize-charset
postcss.config.cjs
module.exports = {
plugins: {
'postcss-normalize-charset': {},
autoprefixer: {},
'postcss-sort-media-queries': {},
'css-declaration-sorter':{order:'smacss'},
'@fullhuman/postcss-purgecss': {
content: ['./src/**/*.html','./src/js/**/*.js'],
//除外設定 https://purgecss.com/safelisting.html
safelist: ['hoge']
},
}
}
HTMLを複数出力したい時の設定
Viteの初期設定では「npm run build」するとHTMLファイルは1つにまとめられてしまうので、HTMLを複数出力したい時には、vite.config.jsに出力するページを追記していく必要があります。
出力したいHTMLファイル
今回はindex.htmlに加えてlist.htmlを出力する想定とします。
■プロジェクトディレクトリ
┗ src
┣ index.html
┗ list.html
複数出力の詳細は公式リファレンスのマルチページアプリの項をご参照ください。
vite.config.jsの設定
冒頭のimport設定と、先程追加したrollupOptionsの中にinputの設定を追加します。
vite.config.js
import { defineConfig } from 'vite';
//import設定を追記
import { resolve } from 'path';
export default defineConfig({
root: './src', //開発ディレクトリ設定
css: {
preprocessorOptions: {
scss: {
api: 'modern-compiler'
}
}
},
build: {
outDir: '../dist', //出力場所の指定
rollupOptions: { //ファイル出力設定
output: {
〜省略〜
},
input: {
index: resolve(__dirname, './src/index.html'),
/*
複数HTMLページを出力したい時にここへ追記していく
xxx: resolve(__dirname, './src/xxx.html'),
*/
list: resolve(__dirname, './src/list.html'),
},
},
},
});
HTMLの複数出力を自動化したい時の設定
複数ページの出力を自動化する方法です。
vite.config.jsに./src配下にあるhtmlファイル一式を取得し自動出力する記述を追記します。
vite.config.js
import { defineConfig } from 'vite';
//import設定を追記
import { resolve } from 'path';
// HTMLの複数出力を自動化する
//./src配下のファイル一式を取得
import fs from 'fs';
import path from 'path';
const files = [];
function readDirectory(dirPath) {
const items = fs.readdirSync(dirPath);
for (const item of items) {
const itemPath = path.join(dirPath, item);
if (fs.statSync(itemPath).isDirectory()) {
// componentsディレクトリを除外する
if (item === 'components') {
continue;
}
readDirectory(itemPath);
} else {
// htmlファイル以外を除外する
if (path.extname(itemPath) !== '.html') {
continue;
}
// nameを決定する
let name;
if (dirPath === path.resolve(__dirname, 'src')) {
name = path.parse(itemPath).name;
} else {
const relativePath = path.relative(path.resolve(__dirname, 'src'), dirPath);
const dirName = relativePath.replace(/\//g, '_');
name = `${dirName}_${path.parse(itemPath).name}`;
}
// pathを決定する
const relativePath = path.relative(path.resolve(__dirname, 'src'), itemPath);
const filePath = `/${relativePath}`;
files.push({ name, path: filePath });
}
}
}
readDirectory(path.resolve(__dirname, 'src'));
const inputFiles = {};
for (let i = 0; i < files.length; i++) {
const file = files[i];
inputFiles[file.name] = resolve(__dirname, './src' + file.path );
}
/*
この形を自動的に作る
input:{
index: resolve(__dirname, './src/index.html'),
list: resolve(__dirname, './src/list.html')
}
*/
export default defineConfig({
root: './src', //開発ディレクトリ設定
css: {
preprocessorOptions: {
scss: {
api: 'modern-compiler'
}
}
},
build: {
outDir: '../dist', //出力場所の指定
rollupOptions: { //ファイル出力設定
output: {
〜省略〜
},
//生成オブジェクトを渡す
input: inputFiles,
},
},
});
HTMLファイルをejsのように扱う (ハンドルバー化する)
Viteでejsを利用する代わりにHTMLファイルをejsのように扱うことができる(ハンドルバー化する)プラグイン vite-plugin-handlebars を利用します。
vite-plugin-handlebarsのインストール
npm install -D vite-plugin-handlebars
コンポーネントを管理するディレクトリをsrc/に追加
各ページで利用するコンポーネントファイル用のディレクトリ「components」を src/ 内に作成し、テスト用のheader.htmlを用意します。
■プロジェクトディレクトリ
┗ src
┣ index.html
┗ components
┗ header.html
vite.config.jsの設定
vite-plugin-handlebars を読み込む設定を追記します。
プラグインを有効化するとHTML内でif文や変数の出力ができるようになるので、ページごとに設定したい情報をまとめたオブジェクトも作成します。
(vite.config.jsに書かずに直接テンプレート側で変数を定義する方法もすぐ下に記載しています。)
vite.config.js
import { defineConfig } from 'vite';
import { resolve } from 'path';
〜省略〜
//import設定を追記
import handlebars from 'vite-plugin-handlebars';
//HTML上で出し分けたい各ページごとの情報
const pageData = {
'/index.html': {
isHome: true,
title: 'Main Page',
},
'/list.html': {
isHome: false,
title: 'List Page',
},
};
export default defineConfig({
root: './src', //開発ディレクトリ設定
css: {
preprocessorOptions: {
scss: {
api: 'modern-compiler'
}
}
},
build: {
outDir: '../dist', //出力場所の指定
rollupOptions: { //ファイル出力設定
〜省略〜
},
},
/*
プラグインの設定を追加
*/
plugins: [
handlebars({
//コンポーネントの格納ディレクトリを指定
partialDirectory: resolve(__dirname, './src/components'),
//各ページ情報の読み込み
context(pagePath) {
return pageData[pagePath];
},
}),
],
});
変数の出力、コンポーネントの読み込み
- プロパティの出力:
{{プロパティ名}}
(出力など基本構文のリファレンス) - コンポーネントの呼び出し:
{{> ファイル名(拡張子なし)}}
(コンポーネントのリファレンス) - 条件分岐:
{{#if hoge}} {{else}} {{/if}}
(if文のリファレンス) - コメントアウト:
{{!コメント}}
(コメントアウトのリファレンス)
index.html
<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<!-- pageDataのtitle情報を出力 -->
<title>{{title}}</title>
<!-- CSSの読み込みを追加 -->
<link rel="stylesheet" href="./scss/style.scss">
</head>
<body>
<!-- header.htmlの読み込み -->
{{> header}}
<div id="app"></div>
<!-- 相対パスを修正 -->
<script type="module" src="./js/main.js"></script>
</body>
</html>
header.html
{{!if文でテキストの出し分け}}
<header>
ヘッダー
{{#if isHome}}
トップページ
{{else}}
リストページ
{{/if}}
</header>
テンプレート上で変数を渡す
vite.config.jsに記載せずともコンポーネントの呼び出し内に変数を追記していくと、テンプレート上で直接値を渡すことができます。
index.html
{{> header isHome=true hoge="文字情報を渡す"}}
その他のできること
このプラグインはハンドルバーというテンプレート言語を利用できるようにするものです。
上記機能以外の検証確認はしていませんが、繰り返し文など一通り機能は揃っていそうです。
もっとできることが知りたい方は以下の公式リファレンスをご参照ください。
js-beautifyでビルドしたHTMLを整形する(任意)
Viteのプラグインなどではなく「npm run build」後にnpm scriptsでビルドされたHTMLに対してHTMLを整形する設定です。
環境に合わせて適宜導入を検討してください。
この項はViteの機能とは独立しており導入必須ではありません。
有名な整形ツールとしてprettierがありますが、本記事ではjs-beautifyを用いてHTMLを整形します。
js-beautifyはprettierと違って、整形しないタグを設定(除外設定)できたり、不用意な改行が入るといったことがないため、個人的にHTML整形においてはprettierより使いやすと思っています。
HTML整形プラグインjs-beautifyをインストール
npm install -D js-beautify
.jsbeautifyrcのファイルを作成(js-beautifyの設定)
開発ディレクトリに.jsbeautifyrcを作成し、JSON形式でHTML整形のオプション設定を追加します。
.jsbeautifyrc
{
"html": {
"indent_size": 2,
"unformatted": ["svg", "pre"]
}
}
オプション設定は環境に合わせて適宜変更してください。
詳細は以下をご参照ください。
.jsbeautifyrcに追記する書き方は以下が参考になります。
package.jsonへコマンドを追加
package.jsonの「build」コマンドにjs-beautifyのコマンドを追記します。
package.json
{
〜省略〜
"scripts": {
"build": "vite build && html-beautify dist/**/*.html",
〜省略〜
},
〜省略〜
}
実行方法
「npm run build」を実行すると自動的に dist/ にあるHTMLファイルが整形されるようになります。
変換しないファイルを格納する public/ディレクトリの設定
「npm run build」時に変換対象にしたくないファイル(そのまま使いたいファイル)は、メインのHTMLファイルと同じ階層に public/ ディレクトリを作成しそこへ内包します。
■プロジェクトディレクトリ
┗ src
┣ index.html
┣ js
┃ ┗ main.js
┣ scss
┃ ┗ style.scss
┃
┗ public (※新規作成)
┗ assets
┣ js
┗ images
publicのファイルを参照する時はpublicを除いてパスを記述します。
<img src="./assets/images/hoge.jpg" alt="">
<script src="./assets/js/fuga.js"></script>
「npm run build」を実行すると変換ファイルと一緒に出力されます。
■dist
┣ index.html
┣ list.html
┗ assets
┣ js
┃ ┣ main.js
┃ ┗ fuga.js
┃
┣ images
┃ ┗ hoge.jpg
┃
┗ css
┗ style.css
public/ディレクトリ内のJSファイルを圧縮する(任意)
Viteに備わっているesbuildを利用して特定のページのみに追加したJSファイルを圧縮します。
prettierと同様に「npm run build」後にnpm scriptsでesbuildを実行する方法です。
この項のesbuildコマンドは対象のJSファイルが存在しない場合、コンソール上でエラーが出るので機能自体が不要な場合は追記しないでください。
(一応Viteのビルドコマンドとは独立してるので、エラーが出てもベースのビルド自体は問題なく動作します。)
esbuildの実行コマンドをpackage.jsonに追記
package.jsonの “scripts” に記載されている “build” コマンドにesbuildの実行コマンドを追加します。
&&でesbuild src/public/assets/js/*.js –bundle –minify –outdir=dist/assets/js/を繋ぎます。
package.json
{
〜省略〜
"scripts": {
"dev": "vite",
"build": "vite build && html-beautify dist/**/*.html && esbuild src/public/assets/js/*.js --bundle --minify --outdir=dist/assets/js/",
"preview": "vite preview"
},
〜省略〜
}
実行方法
「npm run build」を実行すると自動的に src/public/assets/js/ のjsファイルが圧縮されるようになります。
モジュール main.jsの書き方
本記事ではモジュールJS(main.js)に対して特に設定を追加しません。
元々Vite自体にJSをバンドルする機能が備わっていますので、外部JSファイルをimportして扱っても、webpackを利用している時と同じようにビルド時には自動的にバンドルしてくれます。
main.js
//特に何か設定せずともビルド時にはimport内容を自動的にバンドルしてくれます。
import { hoge } from './hoge';
hoge();
モジュールJSはimportが使えるというだけで、importの記述を強制するものではありません。
importを使わない普通のコードのみでも動作します。
モジュール main.jsの扱いについて
この項はmain.jsからモジュール機能(type=”module”属性)を外したいと思った方へ向けた内容です。
結論だけ先に申し上げると、JSファイルにモジュール設定が残っていても特に問題ないので、気にならない方はこの項は飛ばしていただいて大丈夫です。
基本的に普通のコーディングしかしたことがないので「モジュール機能は利用しないし外したいな」と最初に引っかかって色々調べました。
もしかすると同じように思う方がいるかもしれないので詳細を記載しておきます。
main.jsのtype=”module”属性は削除できない(でも問題ない)
Viteはフロントエンドのビルドツールで、基本的にバンドルすることを主目的としているためか、main.jsからtype=”module”属性を外すことはできません。(ファイル名を変えることはできます。)
「モダン開発でもないしモジュール機能自体がそもそも必要ない」という場合でも、type=”module”属性を残しておかなければなりません。
使わない機能は外したくなるかもしれませんが、type=”module”属性はimportをブラウザ上で利用する時に追加する属性で、通常のscriptに+αするものですので、これ自体が何か阻害したり制限になったりもしません。
通常利用の時と同じように書いたものがそのまま動きますので、main.jsはいつもと同じように「全ページに共通して読み込まれる普通のJSファイル」という考え方で扱っても問題ないと思います。
src/index.html
<!DOCTYPE html>
<html lang="ja">
<head>
〜省略〜
</head>
<body>
<div id="app"></div>
<!-- type="module"が追加されているmain.jsのscriptタグ -->
<script type="module" src="./js/main.js"></script>
<!-- public内のJSを読み込む時はtype属性を省く普通の方法で書く -->
<script src="./assets/js/hoge.js"></script>
</body>
</html>
ビルド後のmain.jsの出力位置(変更できないが問題ない)
「npm run build」で出力したHTMLファイルを確認するとmain.jsはheadタグの中(CSSの直前)に出力されます。
dist/index.html
<!DOCTYPE html>
<html lang="en">
<head>
〜省略〜
<!-- 必ずCSSの上に出力される -->
<script type="module" crossorigin src="/assets/js/main.js"></script>
<link rel="stylesheet" href="/assets/css/style.css">
</head>
<body>
〜省略〜
</body>
</html>
この仕様は普段JSファイルを</body>の直前で読み込ませている方は結構気持ち悪さを感じるかもしれません。
しかし、type=”module”属性が付いたscriptは、defer属性をつけたscriptと同じ仕様で読み込み・実行されるので、</body>の直前に書いた時と同じような処理になるため速度面でも特に問題ありません。
defer属性は、HTMLの読み込みを阻害せず、HTMLの解析が完了した後にソースコード上に記載された順番でJSコードを実行する機能です。
詳細は以下の「モジュールの通常のスクリプトのその他の違い」項などをご参照ください。
モジュール main.jsの名称について
「npm run build」で出力したモジュールJSの名称は各設定の影響を受けることがあります。
単一ページの時 (モジュールJSの名称を変える方法)
rollupOptionsにinput設定がない単一ページの時に「npm run build」で出力したJSファイルの名称は index.jsになります。
これをコントロールしたい場合は、「chunkFileNames」「entryFileNames」に設定したい名称を明記します。
vite.config.js (rollupOptions.input設定がない時:jsのファイル名を明記した例)
〜省略〜
export default defineConfig({
root: './src', //開発ディレクトリ設定
css: {
preprocessorOptions: {
scss: {
api: 'modern-compiler'
}
}
},
build: {
outDir: '../dist', //出力場所の指定
rollupOptions: { //ファイル出力設定
output: {
assetFileNames: (assetInfo) => {
let extType = assetInfo.names[0].split('.')[1];
//Webフォントファイルの振り分け
if (/ttf|otf|eot|woff|woff2/i.test(extType)) {
extType = 'fonts';
}
if (/png|jpe?g|svg|gif|tiff|bmp|ico/i.test(extType)) {
extType = 'images';
}
//ビルド時のCSS名を明記してコントロールする
if(extType === 'css') {
return `assets/css/style.css`;
}
return `assets/${extType}/[name][extname]`;
},
//単一ページの時は設定したいファイル名を明記するとコントロールできる
chunkFileNames: 'assets/js/main.js',
entryFileNames: 'assets/js/main.js',
},
},
〜省略〜
});
複数ページの時 (モジュールJSの名称に数字が入る時の回避策)
rollupOptionsにinput設定がある複数ページの場合は「npm run build」で出力すると、input設定のプロパティ名にモジュールJSと同じ名称(main)が存在する場合は main2.jsのように名称に数字が入ることがあります。
vite.config.js (inputのプロパティ名とモジュールjsの名称が一致している例)
〜省略〜
input: {
//プロパティ名: resolve(__dirname, './src/index.html'),
main: resolve(__dirname, './src/index.html'),
/*
↑モジュールJSと同じ名称のプロパティ名が存在すると
ビルド結果のファイル名に数字が入ってしまう
dist/asetts/js/main2.js
*/
list: resolve(__dirname, './src/list.html'),
},
〜省略〜
これはinput設定のプロパティ名を変更することで回避できます。
以下のように、モジュールのJSと異なる名称にすることで数字がつかなくなります。
vite.config.js
〜省略〜
input: {
//プロパティ名: resolve(__dirname, './src/index.html'),
index: resolve(__dirname, './src/index.html'),
/*
↑モジュールJSと異なる名称にすると数字がつかなくなる
*/
list: resolve(__dirname, './src/list.html'),
},
〜省略〜
相対パスでビルドしたい場合
通常「npm run build」するとルートパスで出力されますが vite.config.js の defineConfig に base 設定を追加するとビルド時のパスをコントロールできるようになります。
vite.config.js
〜省略〜
export default defineConfig({
base: './', //相対パスでビルドする
root: './src', //開発ディレクトリ設定
css: {
preprocessorOptions: {
scss: {
api: 'modern-compiler'
}
}
},
build: {
〜省略〜
},
〜省略〜
});
詳しくは以下をご参照ください。
CSS内の画像を相対パスで参照する
ビルド後のディレクトリ環境で一旦ルートに戻ってからファイルを参照するように書くと上手くいくようです。
ビルド後のファイルの位置関係
■dist
┗ assets
┣ images
┃ ┗ hoge.jpg
┃
┗ css
┗ style.css
style.scss (assets/images/hoge.jpgを参照する場合)
.piyo {
background-image: url(../../assets/images/hoge.jpg);
}
Network設定を有効化してIPアドレスを発行する
vite.config.js
〜省略〜
export default defineConfig({
server: {
host: true //IPアドレスを有効化
},
base: './', //相対パスでビルドする
root: './src', //開発ディレクトリ設定
css: {
preprocessorOptions: {
scss: {
api: 'modern-compiler'
}
}
},
build: {
〜省略〜
},
〜省略〜
});
package.json
{
〜省略〜
"scripts": {
"dev": "vite --host",
〜省略〜
},
〜省略〜
「npm run dev」するとIPアドレスが発行されるようになります。
詳しくは以下をご参照ください。
CSSとJSファイルに更新パラメータを自動で追加する
CSSとJSファイルに更新パラメータを自動で追加する方法です。
Viteに用意されたHTMLファイルを変換するための専用フック transformIndexHtml を用いて、ファイルの末尾に更新パラメータを追加します。
vite.config.js にパラメータを生成するhtmlPlugin関数を追加し、plugins内で読み込ませます。
更新パラメータは日時の合計値とすることで値が被らないようにしています。
vite.config.js
〜省略〜
//CSSとJSファイルに更新パラメータを追加
const htmlPlugin = () => {
return {
name: 'html-transform',
transformIndexHtml(html) {
// npm run build のときのみ動作させる
if(process.env.NODE_ENV !== 'production') {
return;
}
//更新パラメータ作成
const date = new Date();
const param = date.getFullYear() + date.getMonth() + date.getDate() + date.getHours() + date.getMinutes() + date.getSeconds();
// CSSファイルにパラメータを追加(httpsから始まる外部リンクは除外)
let setParamHtml = html.replace(/(?=.*<link)(?=.*css)(?!.*https).*$/gm, match => {
return match.replace(/\.css/, '.css?' + param);
});
// JSファイルにパラメータを追加して変更内容を返す(httpsから始まる外部リンクは除外)
return setParamHtml.replace(/(?=.*<script)(?=.*js)(?!.*https).*$/gm, match => {
return match.replace(/\.js/, '.js?' + param);
});
}
}
}
export default defineConfig({
〜省略〜
build: {
〜省略〜
},
〜省略〜
plugins: [
handlebars({
〜省略〜
}),
htmlPlugin()
],
});
※パラメータ更新以外にも独自ルールでhtmlの内容を書き換えることが可能です。
詳しくは以下をご参照ください。
完成した構成ファイルのまとめ
ここまでの内容を構築したファイル構成のまとめです。
ディレクトリ構成
■プロジェクトディレクトリ
┣ dist (ビルドしたファイルが出力される場所)
┣ package.json (プロジェクトのjsonファイル)
┣ postcss.config.cjs (PostCSSの設定ファイル)
┣ vite.config.js (viteの設定ファイル)
┣ .prettierrc (prettierの設定ファイル)
┃
┣ node_modules (編集不要:自動生成されるコアファイル格納場所)
┣ package-lock.json (編集不要:インストールしたパッケージ情報などが記載されている)
┃
┗ src
┣ index.html
┣ list.html (複数ページを追加する場合)
┃
┣ components (HTMLのコンポーネントパーツを格納)
┃ ┗ header.html
┃ xxx.html ...
┃
┣ js (メインのモジュールJSファイルを格納)
┃ ┗ main.js
┃
┣ public (Viteの変換対象外のディレクトリ。distに中身がそのままコピーされます。)
┃ ┗ assets (そのまま移動させたいファイルを必要に応じて格納していく)
┃ ┣ fonts
┃ ┃ ┗ xxx.woff2 ...
┃ ┣ js
┃ ┃ ┗ xxx.js ...
┃ ┗ images
┃ ┗ xxx.jpg ...
┃
┗ scss
┣ style.scss
┗ (各記法に合わせたディレクトリ構成)
package.json
browserslistは環境に合わせて書き換えてください。
devDependencies”の各バージョンは記事執筆時のものですので、package.jsonをコピペで利用する場合は更新がないか確認をしてください。
package.json
{
"name": "プロジェクト名",
"private": true,
"version": "0.0.0",
"type": "module",
"scripts": {
"dev": "vite",
"build": "vite build && html-beautify dist/**/*.html && esbuild src/public/assets/js/*.js --bundle --minify --outdir=dist/assets/js/",
"preview": "vite preview"
},
"devDependencies": {
"@fullhuman/postcss-purgecss": "^5.0.0",
"autoprefixer": "^10.4.13",
"css-declaration-sorter": "^6.3.1",
"js-beautify": "^1.14.7",
"postcss": "^8.4.21",
"postcss-normalize-charset": "^5.1.0",
"postcss-sort-media-queries": "^4.3.0",
"sass": "^1.57.1",
"vite": "^4.0.0",
"vite-plugin-handlebars": "^1.6.0"
},
"browserslist": [
"last 3 versions",
"> 5%",
"Firefox ESR",
"not dead"
]
}
vite.config.js
vite.config.js
import { defineConfig } from 'vite';
import { resolve } from 'path';
//handlebarsプラグインimport
import handlebars from 'vite-plugin-handlebars';
// HTMLの複数出力を自動化する
//./src配下のファイル一式を取得
import fs from 'fs';
import path from 'path';
const files = [];
function readDirectory(dirPath) {
const items = fs.readdirSync(dirPath);
for (const item of items) {
const itemPath = path.join(dirPath, item);
if (fs.statSync(itemPath).isDirectory()) {
// componentsディレクトリを除外する
if (item === 'components') {
continue;
}
readDirectory(itemPath);
} else {
// htmlファイル以外を除外する
if (path.extname(itemPath) !== '.html') {
continue;
}
// nameを決定する
let name;
if (dirPath === path.resolve(__dirname, 'src')) {
name = path.parse(itemPath).name;
} else {
const relativePath = path.relative(path.resolve(__dirname, 'src'), dirPath);
const dirName = relativePath.replace(/\//g, '_');
name = `${dirName}_${path.parse(itemPath).name}`;
}
// pathを決定する
const relativePath = path.relative(path.resolve(__dirname, 'src'), itemPath);
const filePath = `/${relativePath}`;
files.push({ name, path: filePath });
}
}
}
readDirectory(path.resolve(__dirname, 'src'));
const inputFiles = {};
for (let i = 0; i < files.length; i++) {
const file = files[i];
inputFiles[file.name] = resolve(__dirname, './src' + file.path );
}
/*
この形を自動的に作る
input:{
index: resolve(__dirname, './src/index.html'),
list: resolve(__dirname, './src/list.html')
}
*/
//HTML上で出し分けたい各ページごとの情報
const pageData = {
'/index.html': {
isHome: true,
title: 'Main Page',
},
'/list.html': {
isHome: false,
title: 'List Page',
},
};
//CSSとJSファイルに更新パラメータを追加
const htmlPlugin = () => {
return {
name: 'html-transform',
transformIndexHtml(html) {
// npm run build のときのみ動作させる
if(process.env.NODE_ENV !== 'production') {
return;
}
//更新パラメータ作成
const date = new Date();
const param = date.getFullYear() + date.getMonth() + date.getDate() + date.getHours() + date.getMinutes() + date.getSeconds();
// CSSファイルにパラメータを追加(httpsから始まる外部リンクは除外)
let setParamHtml = html.replace(/(?=.*<link)(?=.*css)(?!.*https).*$/gm, match => {
return match.replace(/\.css/, '.css?' + param);
});
// JSファイルにパラメータを追加して変更内容を返す(httpsから始まる外部リンクは除外)
return setParamHtml.replace(/(?=.*<script)(?=.*js)(?!.*https).*$/gm, match => {
return match.replace(/\.js/, '.js?' + param);
});
}
}
}
export default defineConfig({
server: {
host: true //IPアドレスを有効化
},
base: './', //相対パスでビルドする
root: './src', //開発ディレクトリ設定
css: {
preprocessorOptions: {
scss: {
api: 'modern-compiler'
}
}
},
build: {
outDir: '../dist', //出力場所の指定
rollupOptions: { //ファイル出力設定
output: {
assetFileNames: (assetInfo) => {
let extType = assetInfo.names[0].split('.')[1];
//Webフォントファイルの振り分け
if (/ttf|otf|eot|woff|woff2/i.test(extType)) {
extType = 'fonts';
}
if (/png|jpe?g|svg|gif|tiff|bmp|ico/i.test(extType)) {
extType = 'images';
}
//ビルド時のCSS名を明記してコントロールする
if(extType === 'css') {
return `assets/css/style.css`;
}
return `assets/${extType}/[name][extname]`;
},
chunkFileNames: 'assets/js/[name].js',
entryFileNames: 'assets/js/[name].js',
},
input:
//生成オブジェクトを渡す
input: inputFiles,
},
},
/*
プラグインの設定を追加
*/
plugins: [
handlebars({
//コンポーネントの格納ディレクトリを指定
partialDirectory: resolve(__dirname, './src/components'),
//各ページ情報の読み込み
context(pagePath) {
return pageData[pagePath];
},
}),
htmlPlugin()
],
});
postcss.config.cjs
postcss.config.cjs
module.exports = {
plugins: {
autoprefixer: {},
'postcss-sort-media-queries': {},
'css-declaration-sorter':{order:'smacss'},
'@fullhuman/postcss-purgecss': {
content: ['./src/**/*.html','./src/js/**/*.js'],
//除外設定 https://purgecss.com/safelisting.html
safelist: ['hoge']
},
}
}
.jsbeautifyrc
.jsbeautifyrc
{
"html": {
"indent_size": 2,
"unformatted": ["svg", "pre"]
}
}
開発を開始する
以下のコマンドを実行すると開発サーバが起動するのでコーディングを開始できます。
npm run dev
エラーが出た場合はコンソール上に詳細が書いてあるので、しっかり確認すると殆どの原因はすぐに特定できると思います。
よくありそうなエラーは、パスの記述ミス、vite.config.jsやpostcss.config.cjsファイルなどの構文エラー、存在しないファイルを参照しているなどが考えられます。
CSSでfont-familyに日本語を設定する場合の注意点
Viteに内蔵された機能でCSSを圧縮すると日本語文字列をUnicodeエスケープシーケンスに変換(例:\65e5\672c\8a9e)しますが、変換されるとWindows環境などでfont-family設定が効かなくなる場合があり、これを回避するには別途設定が必要です。
詳細は以下の記事にまとめましたので、必要に応じて追加で設定をしてください。
Tailwind CSSを導入する
ViteでTailwind CSSを導入する方法を以下の記事にまとめました。
Tailwind CSSの最適化(リセットCSSの削除、変数の削減、独自拡張)や、最適化した独自styleを混在させることができるのか検証しています。
UnoCSSを導入する
ViteでUnoCSSを導入する方法を以下の記事にまとめました。
Tailwind CSSとの互換性があり、拡張性にも優れていて高速に動作するツールです。なるべくTailwind CSSと同じ使用感で導入することを目指しました。