ViteでJavaScriptとSCSSをビルド

更新:2025-01-10
JavaScript
概要
JavaScriptとSCSSのビルドにwebpackを使用しているプロジェクトを、Viteに移行できるかどうか試す。具体的には、以下のような構成を目指す。
├── 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
プロジェクトの作成
プロジェクトディレクトリを用意し、中に展開。
npm init vite@latest .
今回はフレームワークやTypeScriptは使用しない。そういうプロジェクトに導入するので。
✔ Select a framework: › Vanilla✔ Select a variant: › JavaScript
インストールして、開発サーバが起動できれば完了。
npm installnpm run dev# http://localhost:5173/
開発環境の整備
モジュールの追加
npm i -D sass
設定ファイルの用意
試行錯誤の結果こうなった。
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しかビルドされない。
成果物
続く...かもね