ViteでJavaScriptとSCSSをビルド

更新:2025-01-10
JavaScript

概要

JavaScriptとSCSSのビルドにwebpackを使用しているプロジェクトを、Viteに移行できるかどうか試す。具体的には、以下のような構成を目指す。

/project-dir/
├── public // この中にビルドする
│ └── assets
│ ├── css
│ │ ├── styles1.css // バンドルCSSのみそれぞれビルドする
│ │ └── styles2.css
│ └── js
│ ├── sample1.js // JSをそれぞれビルドする
│ └── sample2.js
├── src
│ ├── js
│ │ ├── sample
│ │ │ └── sample2.js
│ │ └── sample1.js
│ └── scss
│ ├── _sample1.scss
│ ├── sample
│ │ └── _sample2.scss
│ ├── styles1.scss
│ └── styles2.scss
└── vite.config.js

プロジェクトの作成

プロジェクトディレクトリを用意し、中に展開。

/project-dir
npm init vite@latest .

今回はフレームワークやTypeScriptは使用しない。そういうプロジェクトに導入するので。

/project-dir
Select a framework: Vanilla
Select a variant: JavaScript

インストールして、開発サーバが起動できれば完了。

/project-dir
npm install
npm run dev
# http://localhost:5173/

開発環境の整備

モジュールの追加

/project-dir
npm i -D sass

設定ファイルの用意

試行錯誤の結果こうなった。

/project-dir/vite.config.js
import { defineConfig } from 'vite';
import fs from 'fs';
import path from 'path';
/**
* 任意ディレクトリの配下にある任意ファイルのリストを作成する。
*
* @param {string} directoryPath - 処理するディレクトリ名
* @param {string[]} extensionList - 対象とするファイルの拡張子(例:['.scss'])
* @return {void}
*/
const createFilePathList = (directoryPath, extensionList) => {
const filePathList = [];
// 処理を関数にまとめて、ディレクトリの場合に再帰できるようにする
const main = (directoryPath, extensionList) => {
if (!fs.existsSync(directoryPath)) return;
const allDirents = fs.readdirSync(directoryPath, { withFileTypes: true });
for (const dirent of allDirents) {
if (dirent.isDirectory()) {
const retryPath = path.join(directoryPath, dirent.name)
main(retryPath, extensionList)
} else if (dirent.isFile() && extensionList.includes(path.extname(dirent.name))) {
// パーシャルファイルは無視する
if (dirent.name[0] === '_') continue;
filePathList.push(path.join(directoryPath, dirent.name));
}
}
}
main(directoryPath, extensionList);
return filePathList;
}
// SCSSとJSのパス一覧を作成
const scssPathList = createFilePathList('src/scss', ['.scss']);
const jsPathList = createFilePathList('src/js', ['.js']);
const inputPathList = [...scssPathList, ...jsPathList];
// 任意のディレクトリにビルドする
const outDir = 'public';
export default defineConfig({
// outDirに「public」を指定する場合は、publicDirを無効にしておかないと警告が出るみたい
publicDir: outDir === 'public' ? false : true,
build: {
outDir: outDir,
rollupOptions: {
input: inputPathList,
output: {
assetFileNames: (file) => {
// CSSのビルド
const cssRegex = /\.css$/i;
if (cssRegex.test(file.names[0])) {
return `assets/css/[name].css`;
} else {
return;
}
},
// JSのビルド
entryFileNames: (_file) => {
return `assets/js/[name].js`;
}
},
plugins: [
// 各JSファイルを即時関数でラップする
// https://stackoverflow.com/questions/73713323/how-to-wrap-vite-build-in-iife-and-still-have-all-the-dependencies-bundled-into
{
name: 'wrap-in-iife',
generateBundle(outputOptions, bundle) {
Object.keys(bundle).forEach((fileName) => {
const file = bundle[fileName]
if (fileName.slice(-3) === '.js' && 'code' in file) {
file.code = `(() => {\n${file.code}})();`;
}
})
}
}
]
},
// ひとつのCSSにまとめる場合
// cssCodeSplit: false,
// CSS、JavaScriptを圧縮したくない場合
// minify: false
}
});

rollupOptions.inputにはワイルドカードを指定できるような記事もあったがうまくいかず、自前でパスの一覧を作成した。

SCSSのパーシャルファイルは、バンドル用のSCSSから@useで読み込んでいる想定のため、CSSファイルをビルドしないよう除外した。

即時関数でラップするプラグインは、コメントにも書いたとおり以下を参考にした。
https://stackoverflow.com/questions/73713323/how-to-wrap-vite-build-in-iife-and-still-have-all-the-dependencies-bundled-into
即時関数でラップすることで、別々のJSを同時に読み込んだ時に関数名が重複するという問題を回避できる。

よく分からない点として、styles1.scssとstyles2.scssの内容がまったく同じだった場合(基本そんなケースはないが..)は、styles1.cssしかビルドされない。

成果物

https://github.com/namakurajin/vite-build-js-and-scss

続く...かもね