Viteでフロントエンド環境構築【html(ejs), sass, js】
概要
- 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
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
下記の様にframework
、variant
を聞かれますので選択。
今回はVanilla.js
、JavaScript
で作成します。
✔ 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.js
にsrc
がrootであることと、dist
を吐き出すことを設定します。
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
を追記します。
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
に追記。
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>
sassを使用可能に(sass)
sass
をインストール。
yarn add sass
src/assets/styles/
のcss
を全てscss
に変更。
伴ってvite.config.js
のcss
を全てscss
に変更。
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に書き換わる。
<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
に追記。
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を管理できます。
@charset 'utf-8';
@import "_*";
ディレクトリを自動取得するして吐き出す(glob)
このままでは、ページが増えた際にいちいちvite.config.js
のinput
を更新しなければなりません。
globを使用して、自動的にディレクトリを取得して、吐き出すように更新します。
yarn add glob
使い方は下記docsに書いてあります。
要するにファイル構造を保持しながらエントリーポイントをオブジェクトに変換することができますよ、みたいなことのようですね。
まずはdocs通りに記述してみます。
...
+ 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に渡すようにしてみます。
...
+ 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_module
やdist
以下のファイルはignoreします。
ホストの指定
localhost:3000
を指定します。
...
export default defineConfig({
...
+ server: {
+ port: 3000
+ }
});
Discussion