🙌

Viteでフロントエンド環境構築【html(ejs), sass, js】

2024/04/24に公開

概要

  • node 18
  • srcで記述してdistに吐き出される
  • ejsを使用して、headerやfooterをコンポーネント管理
  • sass使用可能
  • imageは圧縮しない

ディレクトリ構成

想定するディレクトリ構成は下記です。
[pages]はマルチページを想定。

root
  ├── dist
  ├── src
  │   ├── [pages]
  │   │    └── index.html
  │   ├── assets
  │   │   ├── scripts
  │   │   │   ├── pages
  │   │   │   │   └── [pages]
  │   │   │   │        └── index.js
  │   │   │   └── common.js
  │   │   └── styles
  │   │       ├── pages
  │   │       │   └── [pages]
  │   │       │        ├── _module.scss
  │   │       │        └── style.scss     // 同階層の'_*.scss'をimport   
  │   │       ├── components
  │   │       ├── reset
  │   │       ├── base
  │   │       └── main.scss
  │   ├── components
  │   │   ├── header.ejs
  │   │   ├── footer.ejs
  │   │   └── ...
  │   ├── public
  │   │   └── assets
  │   │       ├── images
  │   │       ├── scripts   // 圧縮したくないscripts
  │   │       └── styles    // 圧縮したくないstyles
  │   └── index.html
  ├── .stylelintrc.js
  ├── package.json
  └── vite.config.js

vite.config.js

vite.config.js
import { defineConfig } from 'vite';
import { ViteEjsPlugin } from "vite-plugin-ejs";
import sassGlobImports from 'vite-plugin-sass-glob-import';
import { globSync } from 'glob';
import path from 'node:path';
import { fileURLToPath } from 'node:url';

const jsFiles = Object.fromEntries(
  globSync('src/**/*.js', { ignore: ['node_modules/**','**/modules/**','**/dist/**']}).map(file => [
    path.relative(
      'src',
      file.slice(0, file.length - path.extname(file).length)
    ),
    fileURLToPath(new URL(file, import.meta.url))
  ])
);

const scssFiles = Object.fromEntries(
  globSync('src/assets/styles/pages/**/*.scss', { ignore: ['src/assets/styles/pages/**/_*.scss'] }).map(file => [
    path.relative(
      'src',
      file.slice(0, file.length - path.extname(file).length)
    ),
    fileURLToPath(new URL(file, import.meta.url))
  ])
);

const htmlFiles = Object.fromEntries(
  globSync('src/**/*.html', { ignore: ['node_modules/**', '**/dist/**'] }).map(file => [
    path.relative(
      'src',
      file.slice(0, file.length - path.extname(file).length)
    ),
    fileURLToPath(new URL(file, import.meta.url))
  ])
);

const inputObject = { ...scssFiles, ...jsFiles, ...htmlFiles };

export default defineConfig({
  root: './src',
  build: {
    outDir: '../dist',
    rollupOptions: {
      input: inputObject,
      output: {
        entryFileNames: `assets/[name].js`,
        chunkFileNames: `assets/[name].js`,
        assetFileNames: (assetInfo) => {
          if (/\.( gif|jpeg|jpg|png|svg|webp| )$/.test(assetInfo.name)) {
            return 'assets/[name].[ext]';
          }
          if (/\.css$/.test(assetInfo.name)) {
            return 'assets/styles/[name].[ext]';
          }
          return 'assets/[name].[ext]';
        },
      },
    },
  },
  plugins: [
    ViteEjsPlugin(),
    sassGlobImports()
  ],
  server: {
    port: 3000
  }
});

使い方

まずはyarn
node_moduleが生成。

yarn

yarn devで開発用。
localhsot:3000が立ち上がる。

yarn dev

yarn buildで納品用。
distが吐き出される。

yarn build

Viteインストール

まずはViteをインストール。

npm create vite@latest vite-app

下記の様にframeworkvariantを聞かれますので選択。
今回はVanilla.jsJavaScriptで作成します。

✔ Select a framework: › Vanilla
✔ Select a variant: › JavaScript

プロジェクトディレクトリに移動して、開発環境を立ち上げます。

cd vite-app
yarn
yarn dev

vite.config.js作成

node_modules、public、index.html、package.jsonのみ残し、他を消し、srcディレクトリを作ります。
root直下にvite.config.jsを新しく作成します。

root
  ├── node_modules
  ├── src
  │   ├── public
  │   ├── index.html
  │   └── main.js
  ├── vite.config.js
  └── package.json

vite.config.jssrcがrootであることと、distを吐き出すことを設定します。

vite.config.js
import { defineConfig } from 'vite';

export default defineConfig({
  root: './src',
  build: {
    outDir: '../dist',
  }
});

マルチページに対応させる

jsのディレクトリをsrc/assets/scripts/[pages]としたい。
cssのディレクトリをsrc/assets/styles/[pages]としたい。
htmlのディレクトリをsrc/[pages]/index.htmlとしたい。

root
  ├── src
  │   ├── [pages]
  │   │    └── index.html
  │   ├── assets
  │   │   ├── scripts
  │   │   │   ├── pages
  │   │   │   │   └── [pages]
  │   │   │   │        └── index.js
  │   │   │   └── main.js
  │   │   └── styles
  │   │       ├── pages
  │   │       │   └── [pages]
  │   │       │        └── style.scss 
  │   │       └── main.scss
  │   ├── public
  │   └── index.html
  ├── package.json
  └── vite.config.js

index、aboutというページを作ったと仮定して、下記の様にvite.config.jsを追記します。

vite.config.js
import { defineConfig } from 'vite';

export default defineConfig({
  root: './src',
  build: {
    outDir: '../dist',
+    rollupOptions: {
+      input: {
+        'index': 'src/index.html',
+        'about': 'src/about/index.html',
+        'scripts/main': 'src/assets/scripts/main.js',
+        'scripts/pages/index/index': 'src/assets/scripts/pages/index/index.js',
+        'scripts/pages/about/index': 'src/assets/scripts/pages/about/index.js',
+        'styles/main': 'src/assets/styles/main.css',
+        'styles/pages/index/index': 'src/assets/styles/pages/index/style.css',
+        'styles/pages/about/index': 'src/assets/styles/pages/about/style.css',
+      },
+      output: {
+        entryFileNames: `assets/[name].js`,
+        chunkFileNames: `assets/[name].js`,
+        assetFileNames: `assets/[name].[ext]`
+      },
+   },
  }
});

(これではページが増えた際にいちいちvite.config.jsを更新しなければなりません。
のちのセクションで、自動的にディレクトリを取得して、吐き出すように更新します。
とりあえずはこのまま進めます。)

ejsを使用可能に(vite-plugin-ejs)

header、foorterなどをコンポーネント化するためにejsを導入します。

vite-plugin-ejsプラグインをインストール。

yarn add vite-plugin-ejs

vite.config.jsに追記。

vite.config.js
import { defineConfig } from 'vite';
+ import { ViteEjsPlugin } from "vite-plugin-ejs";

export default defineConfig({
  root: './src',
  build: {
    //
  },
+  plugins: [
+    ViteEjsPlugin(),
+  ],
});

componentsディレクトリを作成して、htmlで読み込みます。

root
  ├── src
  │   ├── [pages]
  │   ├── assets
  │   ├── components
  │   │   ├── header.ejs
  │   │   ├── footer.ejs
  │   │   └── ...
  │   ├── public
  │   └── index.html
  ├── package.json
  └── vite.config.js
<body>
  <%- include('./components/header.ejs') %>
  <main>
    //
  </main>
  <%- include('./components/footer.ejs') %>
</body>

https://www.npmjs.com/package/vite-plugin-ejs

sassを使用可能に(sass)

sassをインストール。

yarn add sass

src/assets/styles/cssを全てscssに変更。
伴ってvite.config.jscssを全てscssに変更。

vite.config.js
import { defineConfig } from 'vite';

export default defineConfig({
  root: './src',
  build: {
    outDir: '../dist',
    rollupOptions: {
      input: {
        'index': 'src/index.html',
        'about': 'src/about/index.html',
        'scripts/main': 'src/assets/scripts/main.js',
        'scripts/pages/index/index': 'src/assets/scripts/pages/index/index.js',
        'scripts/pages/about/index': 'src/assets/scripts/pages/about/index.js',
-        'styles/main': 'src/assets/styles/main.css',
+        'styles/main': 'src/assets/styles/main.scss',
-        'styles/pages/index/index': 'src/assets/styles/pages/index/style.css',
+        'styles/pages/index/index': 'src/assets/styles/pages/index/style.scss',
-        'styles/pages/about/index': 'src/assets/styles/pages/about/style.css',
+        'styles/pages/about/index': 'src/assets/styles/pages/about/style.scss',
      },
      output: {
        entryFileNames: `assets/[name].js`,
        chunkFileNames: `assets/[name].js`,
        assetFileNames: `assets/[name].[ext]`
      },
   },
  }
});

distにはcssが吐き出されます。

htmlにはscssのまま読み込みます。ビルドの際にcssに書き換わる。

index
  <link rel="stylesheet" href="/assets/styles/pages/index/style.scss" />

sassファイルをコンポーネント化して管理しやすく(vite-plugin-sass-glob-import)

src/assets/styles/pages/[pages]/style.scssにはページごとのscssが記述されることを想定していますが、style.scssに全て集約するのはあまり好ましくありません。

下記の様にstyle.scssの同階層のアンダースコア(_)から始まるscssファイルを全てstyle.scssに読み込み、scssをコンポーネント管理できる様にします。

root
  ├── src
  │   ├── assets
  │   │   └── styles
  │   │       ├── pages
  │   │       │   └── [pages]
  │   │       │        ├── _sec01.scss 
  │   │       │        ├── _sec02.scss 
  │   │       │        ├── _sec03.scss 
  │   │       │        ├── ...
  │   │       │        └── style.scss 
  │   │       └── main.scss
  │   └── index.html
  ├── package.json
  └── vite.config.js

vite-plugin-sass-glob-importというプラグインをインストールします。

yarn add vite-plugin-sass-glob-import

vite.config.jsに追記。

vite.config.js
import { defineConfig } from 'vite';
import { ViteEjsPlugin } from "vite-plugin-ejs";
+ import sassGlobImports from 'vite-plugin-sass-glob-import';

export default defineConfig({
  root: './src',
  build: {
    //
  },
  plugins: [
    ViteEjsPlugin(),
+    sassGlobImports()
  ],
});

これでsrc/assets/styles/pages/[pages]/style.scssには同階層のscssファイルのimport
だけ記述し、コンポーネント化してscssを管理できます。

src/assets/styles/pages/[pages]/style.scss
@charset 'utf-8';
@import "_*";

https://www.npmjs.com/package/vite-plugin-sass-glob-import

ディレクトリを自動取得するして吐き出す(glob)

このままでは、ページが増えた際にいちいちvite.config.jsinputを更新しなければなりません。
globを使用して、自動的にディレクトリを取得して、吐き出すように更新します。

yarn add glob

使い方は下記docsに書いてあります。
https://rollupjs.org/configuration-options/#output-globals

要するにファイル構造を保持しながらエントリーポイントをオブジェクトに変換することができますよ、みたいなことのようですね。

まずはdocs通りに記述してみます。

vite.config.js
...
+ import { globSync } from 'glob';
+ import path from 'node:path';
+ import { fileURLToPath } from 'node:url';

export default defineConfig({
  root: './src',
  build: {
    outDir: '../dist',
    rollupOptions: {
-      input: {
-        'index': 'src/index.html',
-        'about': 'src/about/index.html',
-        'scripts/main': 'src/assets/scripts/main.js',
-        'scripts/pages/index/index': 'src/assets/scripts/pages/index/index.js',
-        'scripts/pages/about/index': 'src/assets/scripts/pages/about/index.js',
-        'styles/main': 'src/assets/styles/main.css',
-        'styles/pages/index/index': 'src/assets/styles/pages/index/style.css',
-        'styles/pages/about/index': 'src/assets/styles/pages/about/style.css',
-      },
+      input: Object.fromEntries(
+        globSync('src/**/*.js').map(file => [
+          // This remove `src/` as well as the file extension from each
+          // file, so e.g. src/nested/foo.js becomes nested/foo
+          path.relative(
+            'src',
+            file.slice(0, file.length - path.extname(file).length)
+          ),
+          // This expands the relative paths to absolute paths, so e.g.
+          // src/nested/foo becomes /project/src/nested/foo.js
+          fileURLToPath(new URL(file, import.meta.url))
+        ])
+      ),
      output: {
        entryFileNames: `assets/[name].js`,
        chunkFileNames: `assets/[name].js`,
        assetFileNames: `assets/[name].[ext]`
      },
   },
  }
  ...
});

yarn buildすると、jsファイルだけがdistに出力されたと思います。

jsファイル、scssファイル、htmlファイルを取得して、一つのオブジェクトにして、inputに渡すようにしてみます。

vite.config.js
...

+ const jsFiles = Object.fromEntries(
+   globSync('src/**/*.js', { ignore: ['node_modules/**','**/modules/**','**/dist/**']}).map(file => [
+     path.relative(
+       'src',
+       file.slice(0, file.length - path.extname(file).length)
+     ),
+     fileURLToPath(new URL(file, import.meta.url))
+   ])
+ );
+ 
+ const scssFiles = Object.fromEntries(
+   globSync('src/assets/styles/pages/**/*.scss', { ignore: ['src/assets/styles/pages/**/_*.scss'] }).map(file => [
+     path.relative(
+       'src',
+       file.slice(0, file.length - path.extname(file).length)
+     ),
+     fileURLToPath(new URL(file, import.meta.url))
+   ])
+ );
+ 
+ const htmlFiles = Object.fromEntries(
+   globSync('src/**/*.html', { ignore: ['node_modules/**', '**/dist/**'] }).map(file => [
+     path.relative(
+       'src',
+       file.slice(0, file.length - path.extname(file).length)
+     ),
+     fileURLToPath(new URL(file, import.meta.url))
+   ])
+ );
+ 
+ const inputObject = { ...scssFiles, ...jsFiles, ...htmlFiles };

export default defineConfig({
  root: './src',
  build: {
    outDir: '../dist',
    rollupOptions: {
-      input: Object.fromEntries(
-        globSync('src/**/*.js').map(file => [
-          // This remove `src/` as well as the file extension from each
-          // file, so e.g. src/nested/foo.js becomes nested/foo
-          path.relative(
-            'src',
-            file.slice(0, file.length - path.extname(file).length)
-          ),
-          // This expands the relative paths to absolute paths, so e.g.
-          // src/nested/foo becomes /project/src/nested/foo.js
-          fileURLToPath(new URL(file, import.meta.url))
-        ])
-      ),
+      input: inputObject,
      output: {
        entryFileNames: `assets/[name].js`,
        chunkFileNames: `assets/[name].js`,
        assetFileNames: `assets/[name].[ext]`
      },
   },
  }
  ...
});

globSyncの第二引数内ののignoreには取得したくないファイルを記述します。
今回はnode_moduledist以下のファイルはignoreします。

ホストの指定

localhost:3000を指定します。

vite.config.js
...

export default defineConfig({
  ...
+   server: {
+     port: 3000
+   }
});

Discussion