Web production note

 【更新日 :

【簡易版】Viteでコーダーのコーディング環境(HTML(ejsライク:ハンドルバー化)・Sass・JS)を作る

Category:
開発環境

※本記事はejsを利用していません。

代わりにHTMLファイルをejsと同等の機能を追加する(ハンドルバー化する)プラグイン vite-plugin-handlebars を利用しています。
ejsは以下プラグイン vite-plugin-ejs を利用すると実装できるようです。

本記事は構築する手順とファイル構成だけをまとめた記事です。
各設定の詳細を知りたい場合は以下の【詳細版】をご参照ください。

Viteでコーダー向けの環境構築をしたサンプルです。
通常のコーディングと同じ感覚(HTML(ejsライク)・Sass・JS)でコーディングできる設定を目指しました。

Viteの環境構築にはターミナル(Mac)やコマンドプロンプト(Windows)を用います。
本記事はこれらの基礎知識があることを前提とした記事です。

Viteは「HTML、SCSS、JS」で1セットなので、特定のファイルのみビルドしたい場合は別のツールを利用してください。

node.js v16.16.0 で動作確認をしています。
不具合が出る場合は実行環境のバージョンを合わせてください。

動作確認はmacです。windowsでは未検証ですので予めご了承ください。

この記事のViteでできること

  • 複数のHTMLページ生成
  • HTMLファイルをejsのように扱う(HTMLをハンドルバー化してejsの代替とする)
  • ビルド後のHTMLファイルを整形(任意追加)
  • SCSSの書き出し(PostCSSによるオプション設定)
    • autoprefixer:ベンダープレフィックスの追加
    • postcss-sort-media-queries:メディアクエリをソートして1つにまとめる
    • css-declaration-sorter:プロパティ順のソート(smacss)
    • postcss-purgecss:CSSファイルから未使用のスタイルを削除する
    • postcss-normalize-charset:先頭にcharset追加
  • publicに内包したサブJS(特定のページのみ追加したいJS)の圧縮(任意追加)

この記事のViteでしない(できない)こと

画像の圧縮はしない

プロジェクトで利用する固定画像は個別にコントロールしたい場合もあるので、コーディング環境に一括設定はしない派です。

Viteでは vite-plugin-imagemin というプラグインが存在しているようですので、そちらを利用すれば設定可能だと思います。
本記事では扱いませんので必要な方は以下をご参照ください。

ベース環境の構築

ベース環境の構築には以下の記事を参考にさせていただきました。

Vite は Node.js >=14.6.0 のバージョンが必要ですので事前にインストールしておいてください。

プロジェクト作成

プロジェクトを作成したいディレクトリで以下を実行すると、①〜③の質問が始まるので順番に入力・選択してください。その後は④、⑤と続けて実行してください。

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」「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
       ┗ (各記法に合わせたディレクトリ構成)

各モジュールやプラグインのインストール

Sass

npm install -D sass

PostCSS + プラグイン (不要なものは適宜省いてください。)

npm install -D postcss autoprefixer postcss-sort-media-queries css-declaration-sorter @fullhuman/postcss-purgecss postcss-normalize-charset

HTMLファイルをejsのように扱う (ハンドルバー化する) vite-plugin-handlebars

npm install -D vite-plugin-handlebars

HTML整形プラグインjs-beautifyをインストール

npm install -D js-beautify

package.json

browserslistは環境に合わせて書き換えてください。

esbuildの実行コマンドは、対象となるsrc/public/assets/js/にJSファイルが存在しないとコンソール上でエラーが出ます。
Viteのビルドコマンドとは独立してるのでベースのビルドは問題なく動作しますが、
不要は場合はesbuildの実行コマンド自体を削除してください。

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

handlebars用のページ情報(pageData)はサンプルですので不要なら省いてください。

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', //開発ディレクトリ設定
  build: {
    outDir: '../dist', //出力場所の指定
    rollupOptions: { //ファイル出力設定
      output: {
        assetFileNames: (assetInfo) => {
          let extType = assetInfo.name.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

npm install時に省いたものは削除してください。

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

js-beautifyの設定ファイルです。各自の環境に合わせて設定してください。

.jsbeautifyrc

{
  "html": {
    "indent_size": 2,
    "unformatted": ["svg", "pre"]
  }
}

main.jsの中身を削除

CSSのimport設定やテストコードは全て不要なので削除してください。

main.js

//空にしてください。

index.htmlにlinkタグを追加・パスの修正

CSSを通常のコーディングと同じように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>

開発を開始する

以下のコマンドを実行すると開発サーバが起動するのでコーディングを開始できます。

npm run dev

開発したプロジェクトをビルドする

以下のコマンドを実行するとdist/ディレクトリに公開用のファイル一式が書き出されます。

npm run build

エラーが出た場合はコンソール上に詳細が書いてあるので、しっかり確認すると殆どの原因はすぐに特定できると思います。
よくありそうなエラーは、パスの記述ミス、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を混在させることができるのか検証しています。

関連リンク