Viteで静的サイトの開発環境を構築する。

2024.08.28
TABLE OF CONTENTS

Viteを使用して簡易的な静的サイトの開発環境を構築します。

開発環境構築

1. インストール

開発環境を構築したいディレクトリに移動したのち、npm create vite@latest でViteをインストールします。※ nodeの解説はしません。

  1. Ok to proceed?(y)
     → インストールを続けるか問われる。 Yes を選択する。
  2. Project name
     → プロジェクトネームを問われる。今回は mpa-vite-static としました。
     ※ 画像ではmpa-vite-threejsになってます。
  3. Select a framework
     → フレームワークの使用が問われる。今回はフレームワークは使いませんので、VanillaをEnter Keyで選択する。
  4. JavaScriptかTypeScriptかを問われる。今回は下矢印キーで移動してJavaScriptを選択します。

完了すると下記のような結果になります。

ターミナルに書いてあるように下記のコードを順番に実行していきます。

cd mpa-vite-static
npm install
npm run dev

Hello World完了。

2. ファイル追加、削除

初期インストールされるファイルを削除します。

rm index.html javascript.svg main.js style.css counter.js

新規ディレクトリを追加します。

mkdir dist src src/assets src/assets/js src/assets/scss public/assets public/assets/img

新規ファイルを作成します。

touch src/index.html src/assets/js/index.js src/assets/scss/index.scss src/assets/scss/common.scss vite.config.js .env
.
└── mpa-vite-static/
    ├── dist
    ├── node_modules
    ├── public/
    │   └── assets/
    │       └── img
    ├── src/
    │   ├── assets/
    │   │   ├── js/
    │   │   │   ├── company-about.js
    │   │   │   ├── company.js
    │   │   │   └── index.js
    │   │   └── scss/
    │   │       ├── company-about.scss
    │   │       ├── comapny.scss
    │   │       ├── common.scss
    │   │       └── index.scss
    │   ├── company/
    │   │   ├── index.html
    │   │   └── about/
    │   │       └── index.html
    │   └── index.html
    ├── .env
    ├── .gitignore
    ├── package-lock.json
    ├── packaga.json
    └── vite.config.js

/src/company/about/index.html/src/assets/js/company-about.js等は任意で追加してください。
ページ展開した際に名前がJSやCSSのファイル名が被らないようにディレクトリを繋いだ名称にしています。(この思想前提で開発環境は進めていきます。)
ここら辺は思想的なものになってしまうと思いますので、適当に変えてください。

HTMLやSCSS、JSの中身は適当にいれてください。

3. パッケージをインストールする。

必要最低限インストールします。

npm i -D autoprefixer dotenv fs sass
  • autoprefixer:ベンダープレフィックスを自動的に付与する。
  • dotenv:.envファイルに定義された値を環境変数として使うことができる。
  • fs:ファイルを扱うことができる。(書き込み等)

4. vite.config.jsの設定をする。

自分がviteを使ってやりたいこととしては、src ディレクトリ内にある .html ファイルを取得して、その情報からJSやCSSを生成していくことです。

1|モジュールを読み込み、定数を作成する。

import { defineConfig } from "vite";
import { resolve, extname, dirname, basename, join, relative } from "path";
import fs from "fs";
import autoprefixer from "autoprefixer";
import * as dotenv from "dotenv";

// .envファイルを読み込み、その内容をprocess.envに設定します。
const result = dotenv.config();
if (result.error) throw result.error;

// ディレクトリを定数化します。
const DIR = {
  DIST: "dist",
  SRC: "src",
  PUBLIC: "public",
};

// ファイル名に日付をハッシュ値をつけようと思います。(viteのハッシュ値([hash])でもいいと思います。)
const HASH = `${new Date().getFullYear()}${
  new Date().getMonth() + 1
}${new Date().getDate()}`;

2|エントリーファイルを作成する。

// 再帰的にディレクトリを探索して、全ての .html ファイルを取得する関数
const getAllHtmlFiles = (dir) => {
  let results = [];
  const list = fs.readdirSync(dir);
  list.forEach((file) => {
    const filePath = resolve(dir, file);
    const stat = fs.statSync(filePath);
    if (stat && stat.isDirectory()) {
      results = results.concat(getAllHtmlFiles(filePath));
    } else if (extname(file) === ".html") {
      results.push(filePath);
    }
  });
  return results;
};

const htmlFiles = getAllHtmlFiles(resolve(__dirname, DIR.SRC));

const input = {};
htmlFiles.forEach((file) => {
  const relativePath = file.replace(resolve(__dirname, DIR.SRC) + "/", "");

  let name = relativePath.replace(/\.html$/, "");

  const dirName = basename(dirname(name));

  if (basename(name) === "index") {
    name = dirName ? `${dirName}` : "index";
  } else {
    name = `${dirName ? dirName + "-" : ""}${basename(name)}`;
  }

  // 空白の場合は「index」を使用
  if (name === "." || name.includes(".") || name === "") name = "index";

  input[name] = file;
});

/*
console.log(input)
{
  about: '/Users/~~~/mpa-vite-threejs/src/company/about/index.html',
  company: '/Users/~~~/mpa-vite-threejs/src/company/index.html',
  index: '/Users/~~~/mpa-vite-threejs/src/index.html'
}
*/

3|defineConfigの設定

export default defineConfig(({ mode }) => {
  return {
    base: "./",
    root: DIR.SRC,
    publicDir: resolve(__dirname, DIR.PUBLIC),
    resolve: {
      alias: {
        "@scss": resolve(__dirname, "src/assets/scss"), // 'src/assets/scss'ディレクトリへのエイリアスを作成する
      },
    },
    css: {
      devSourcemap: true,
      postcss: {
        plugins: [autoprefixer({ grid: true })],
      },
      preprocessorOptions: {
        scss: {
          additionalData: `@import "@scss/common.scss";`, // 共通のスタイルを自動的にインポートする
        },
      },
    },
    esbuild: {
      drop: ["console", "debugger"], // build時にログを削除する
    },
    build: {
      outDir: resolve(__dirname, DIR.DIST),
      emptyOutDir: true,
      polyfillModulePreload: false,
      chunkSizeWarningLimit: 100000000, // チャンクファイルのメモリサイズを拡張する(デフォルトは500kb)
      rollupOptions: {
        input: input, // 動的に取得した.htmlファイルをエントリーポイントとして設定する
        output: {
          entryFileNames: (chunkInfo) => {
            // ベースとなるパスと、絶対パスから現在の相対パスを取得する。
            const relativePath = relative(
              join(process.cwd()),
              chunkInfo.facadeModuleId
            );
            // ファイル名を作成する
            let filename = relativePath
              .replace("src/", "")
              .replace("index.html", "")
              .replace(/\//g, "-")
              .replace(/-$/, "");
            if (filename === "") filename = "index";
            filename = filename || chunkInfo.name || "index";
            return `assets/js/${filename}.${HASH}.js`;
          },
          chunkFileNames: (chunkInfo) => {
            // チャンクファイルは /_chunk/ にまとめる。
            const name = chunkInfo.name || "index";
            return `assets/js/_chunk/${name}.${HASH}.js`;
          },
          assetFileNames: (assetInfo) => {
            // entryFileNames の設定と同様に情報からファイル名を作成する
            let filename = assetInfo.originalFileName;
            if (filename != null) {
              filename = assetInfo.originalFileName;
              filename = filename
                .replace("index.html", "")
                .replace(/\//g, "-")
                .replace(/-$/, "");
              if (filename === "") filename = "index";
            }
            return filename != null
              ? `assets/[ext]/${filename}.${HASH}[extname]`
              : `assets/[ext]/[name].${HASH}[extname]`;
          },
          manualChunks: undefined, // コード分割を無効化する
        },
      },
    },
    // ローカルサーバの設定
    server: {
      open: true,
      port: process.env.PORT,
      host: true,
    },
  };
});

4|.envの設定

今回はポートのみの設定です。
自由に拡張してください。

PORT=3150

なんとなく3150(最高)にしてます。

5| 全体コード

import { defineConfig } from "vite";
import { resolve, extname, dirname, basename, join, relative } from "path";
import fs from "fs";
import autoprefixer from "autoprefixer";
import * as dotenv from "dotenv";

const result = dotenv.config();
if (result.error) throw result.error;

const DIR = {
  DIST: "dist",
  SRC: "src",
  PUBLIC: "public",
};

const HASH = `${new Date().getFullYear()}${
  new Date().getMonth() + 1
}${new Date().getDate()}`;

const getAllHtmlFiles = (dir) => {
  let results = [];
  const list = fs.readdirSync(dir);
  list.forEach((file) => {
    const filePath = resolve(dir, file);
    const stat = fs.statSync(filePath);
    if (stat && stat.isDirectory()) {
      results = results.concat(getAllHtmlFiles(filePath));
    } else if (extname(file) === ".html") {
      results.push(filePath);
    }
  });
  return results;
};

const htmlFiles = getAllHtmlFiles(resolve(__dirname, DIR.SRC));

const input = {};
htmlFiles.forEach((file) => {
  const relativePath = file.replace(resolve(__dirname, DIR.SRC) + "/", "");

  let name = relativePath.replace(/\.html$/, "");

  const dirName = basename(dirname(name));

  if (basename(name) === "index") {
    name = dirName ? `${dirName}` : "index";
  } else {
    name = `${dirName ? dirName + "-" : ""}${basename(name)}`;
  }

  if (name === "." || name.includes(".") || name === "") name = "index";

  input[name] = file;
});

export default defineConfig(({ mode }) => {
  return {
    base: "./",
    root: DIR.SRC,
    publicDir: resolve(__dirname, DIR.PUBLIC),
    resolve: {
      alias: {
        "@scss": resolve(__dirname, "src/assets/scss"),
      },
    },
    css: {
      devSourcemap: true,
      postcss: {
        plugins: [autoprefixer({ grid: true })],
      },
      preprocessorOptions: {
        scss: {
          additionalData: `@import "@scss/common.scss";`,
        },
      },
    },
    esbuild: {
      drop: ["console", "debugger"],
    },
    build: {
      outDir: resolve(__dirname, DIR.DIST),
      emptyOutDir: true,
      polyfillModulePreload: false,
      chunkSizeWarningLimit: 100000000,
      rollupOptions: {
        input: input,
        output: {
          entryFileNames: (chunkInfo) => {
            const relativePath = relative(
              join(process.cwd()),
              chunkInfo.facadeModuleId
            );
            let filename = relativePath
              .replace("src/", "")
              .replace("index.html", "")
              .replace(/\//g, "-")
              .replace(/-$/, "");
            if (filename === "") filename = "index";
            filename = filename || chunkInfo.name || "index";
            return `assets/js/${filename}.${HASH}.js`;
          },
          chunkFileNames: (chunkInfo) => {
            const name = chunkInfo.name || "index";
            return `assets/js/_chunk/${name}.${HASH}.js`;
          },
          assetFileNames: (assetInfo) => {
            let filename = assetInfo.originalFileName;
            if (filename != null) {
              filename = assetInfo.originalFileName;
              filename = filename
                .replace("index.html", "")
                .replace(/\//g, "-")
                .replace(/-$/, "");
              if (filename === "") filename = "index";
            }
            return filename != null
              ? `assets/[ext]/${filename}.${HASH}[extname]`
              : `assets/[ext]/[name].${HASH}[extname]`;
          },
          manualChunks: undefined,
        },
      },
    },
    server: {
      open: true,
      port: process.env.PORT,
      host: true,
    },
  };
});

5. 立ち上げてみる

npm run dev

http://localhost:3150/ がちゃんとエラーなく立ち上がっていれば完了です。

buildするときは npm run build 、buildしたものを確認したいときはnpm run previewで確認できます。

まとめ

今回のリポジトリはGitHubにUpしています。
自己責任でご自由に使ってください。

参考

PICKUP ARTWORK