はじめに
Vue TypeScript Vuetifyという環境でWebpackを使用してみたので、構築手順やWebpackの説明などを残しておきます。
必要なパッケージをインストール
webpackと、webpackで使用するloaderをインストールします。
npm install -D webpack webpack-cli css-loader style-loader ts-loader vue-loader @vue/compiler-sfc
loader(ローダー)とは、Webpack が .js 以外のファイルを理解・変換できるようにする仕組みのことです。
✅ わかりやすく言うと…
Webpack はもともと JavaScript ファイルしか扱えません。
でも、現代のWeb開発では .vue、.ts、.scss、.png など様々な形式のファイルを扱います。
Webpack にこう伝える必要があります:
「.vue ファイルはこう処理してね!」
「.ts は TypeScript だから JavaScript に変換してね!」
この「処理方法を教える機能」が loader(ローダー) です。
Vueコンポーネントを型として認識させるファイルを作成
src/shims-vue.d.ts
declare module '*.vue' {
import { DefineComponent } from 'vue';
const component: DefineComponent<{}, {}, any>;
export default component;
}
shims-vue.d.ts は、**TypeScript に Vue コンポーネント(.vue ファイル)を型として認識させるための宣言ファイル(型定義)**です。
✅ なぜ必要なのか?
TypeScript は .vue ファイルの中身がどんな構造になっているかをデフォルトでは理解できません。
例えば、これがあると:
ts
コピーする
編集する
import MyComponent from './components/MyComponent.vue';
TypeScript は「.vue って何? モジュールじゃないでしょ?」とエラーを出します。
✅ shims-vue.d.ts がやってること
ts
コピーする
編集する
declare module '*.vue' {
import { DefineComponent } from 'vue';
const component: DefineComponent<{}, {}, any>;
export default component;
}
この宣言は:
「.vue ファイルをインポートしたら、それは Vue コンポーネント(DefineComponent)として扱っていいよ」
と TypeScript に教えています。
tsconfigに設定を追記
{
// 追加
"include": ["src/**/*.ts", "src/**/*.vue"]
}
webpack.config.jsを追加
packaga.jsonと同じフォルダに以下のファイルを作成する
import path from 'path';
import { fileURLToPath } from 'url';
import { VueLoaderPlugin } from 'vue-loader';
const __filename = fileURLToPath(import.meta.url);
const __dirname = path.dirname(__filename);
export default {
entry: './src/main.ts',
output: {
filename: 'bundle.js',
path: path.resolve(__dirname, 'dist'),
},
resolve: {
extensions: ['.ts', '.js', '.vue'],
alias: {
vue$: 'vue/dist/vue.esm-bundler.js',
},
},
module: {
rules: [
{
test: /\.vue$/,
use: 'vue-loader',
},
{
test: /\.ts$/,
use: {
loader: 'ts-loader',
options: {
appendTsSuffixTo: [/\.vue$/],
},
},
exclude: /node_modules/,
},
{
test: /\.css$/,
use: ['style-loader', 'css-loader'],
},
],
},
plugins: [new VueLoaderPlugin()],
mode: 'development', // or 'production'
};
パラメータ解説
entry
最初にビルドするファイル
ビルドしてファイルをまとめていくことをバンドル処理といい、その最初の開始ファイルをここで設定する
「バンドル処理」とは、複数のファイルやモジュール(JS・CSS・画像など)を1つまたは少数のファイルにまとめる処理のことです。Webpack の主な役割がこの「バンドル」です。
entryにはどのファイルを指定したらええねんとなりましたが、
Vue プロジェクトでいうと 下記のように createApp() を呼び出してアプリを起動するファイルにします。
import { createApp } from 'vue';
import App from './App.vue';
import { createVuetify } from 'vuetify';
import * as components from 'vuetify/components';
import * as directives from 'vuetify/directives';
import '@mdi/font/css/materialdesignicons.css';
import 'vuetify/lib/styles/main.css';
const vuetify = createVuetify({
components,
directives,
});
const app = createApp(App);
app.use(vuetify);
app.mount('#app');
entryには複数ファイル指定できる
その場合、以下のように設定すると、app.bundle.jsとadmin.bundle.jsが作成される
entry: {
app: './src/app.js',
admin: './src/admin.js'
},
output: {
filename: '[name].bundle.js',
path: path.resolve(__dirname, 'dist'),
},
resolve
Webpack における モジュール解決のルールを定義する設定
簡単に言うと、「import や require が書かれたときに、どのようにファイルを探すか?」を指定します。
✅ コードの解説
resolve: {
extensions: ['.ts', '.js', '.vue'],
alias: {
vue$: 'vue/dist/vue.esm-bundler.js',
},
}
この中に 2 つの重要な設定があります。
-
extensions: ['.ts', '.js', '.vue']
これは、拡張子を省略したときにどの順で探すかを指定します。
import Hello from './components/Hello';
このとき Webpack は、次の順でファイルを探します- ./components/Hello.ts
- ./components/Hello.js
- ./components/Hello.vue
✅ 拡張子が .vue のファイルを読み込むには、.vue をここに含める必要があります。
-
alias: { vue$: 'vue/dist/vue.esm-bundler.js' }
これは、import 'vue' と書かれたときに、どのファイルを読み込むか指定するものです。
Vue は複数のビルド形式がありますが、.vue ファイルを扱うには バンドル対応かつコンパイル付きの ESM ビルドを使う必要があります。
vue → デフォルトはランタイムオンリー
vue/dist/vue.esm-bundler.js → template をコンパイルできる完全版
✅ .vue ファイルを扱うなら vue/dist/vue.esm-bundler.js を明示的に指定する必要があります。
また便利な使い方としては下記のように相対パスを省略するといったことも可能です。
下記のように設定すると
alias: {
"@components": path.resolve(__dirname, "src/main/components"),
"@pages": path.resolve(__dirname, "src/main/pages"),
}
インポートする側のファイルでは下記のように書けます
// 相対パスが長い書き方
// import MyButton from '../../../main/components/MyButton.vue';
// エイリアスを使った書き方
import MyButton from '@components/MyButton.vue';
import HomePage from '@pages/HomePage.vue';
modules
Webpack に「どのファイルをどう処理するか(変換・読み込みなど)」を教える設定を書く
✅ ざっくり言うと:
「このファイルの拡張子にはこのローダーを使ってね」とルールを書く場所です。
✅ 基本構造
module: {
rules: [
{
test: /\.拡張子$/, // 対象となるファイル(正規表現)
use: 'ローダー名' // 使用するローダー(または use: [...] で複数)
}
]
}
plugins
plugins は、Webpack の「追加機能」を拡張するための仕組みです。
module.rules が「ファイル変換のルール」だとしたら、plugins は「ビルドの過程でやりたいことを追加する場所」です。
✅ plugins には何を書く?
new プラグイン名(設定) の形で、使いたいプラグインを plugins: [] の中に書きます。
✅ よく使うプラグイン例(Vue + TypeScript想定)
const { VueLoaderPlugin } = require('vue-loader');
const HtmlWebpackPlugin = require('html-webpack-plugin');
const path = require('path');
module.exports = {
// ...entry, output, module.rulesなど
plugins: [
new VueLoaderPlugin(), // ← Vueファイルを正しく処理するために必要
new HtmlWebpackPlugin({
template: './dist/index.html', // ← HTMLテンプレートにJSを注入する
}),
],
};
✅ 各プラグインの役割
| プラグイン名 | 主な役割 |
|---|---|
| VueLoaderPlugin |
.vue ファイルを使うために必須(vue-loader とセット) |
| HtmlWebpackPlugin | HTML ファイルを自動生成し、<script src="bundle.js"> を挿入 |
| DefinePlugin | 環境変数を埋め込む(例:process.env.NODE_ENV) |
| MiniCssExtractPlugin | CSS を別ファイルとして出力する(style-loader の代替) |
| CleanWebpackPlugin |
dist フォルダをビルド前に自動で削除する |
mode
mode には、ビルドの目的に応じて Webpack に最適な動作をさせるモードを指定します。
✅ mode に指定できる値
- 'development'
開発用モード。デバッグしやすくなる ソースマップ有効、圧縮なし、watch有効 - 'production'
本番用モード。パフォーマンス重視 自動で最小化(JS/CSS圧縮)、console削除など - 'none'
最適化なしの素の状態でビルド 自動最適化なし(必要なら細かく設定)
✅ 書き方(webpack.config.js)
module.exports = {
mode: 'development', // or 'production' or 'none'
entry: './src/index.ts',
output: {
filename: 'bundle.js',
path: path.resolve(__dirname, 'dist'),
},
// ...
};
✅ よくある使い分け
- 開発中 → 'development'
- 本番リリース → 'production'
- カスタム最適化や自前ビルド管理 → 'none'
index.htmlを作成
package.jsonと同じ階層にdist/index.htmlを作成します
後述しますが、HtmlWebpackPluginを使って自動で生成することもできます
<script src="bundle.js"></script>のbundle.jsは、webpack.config.jsのoutputのfilenameと合わせます。
<!doctype html>
<html>
<body>
<div id="app"></div>
<script src="bundle.js"></script>
</body>
</html>
build
ここまで設定したらnpx webpackでビルドできます。
ビルドすると、distフォルダにファイルが出力されます
serve
webpack-dev-server使うと、localhostで、Webサーバーとして起動できます
npm install -D webpack-dev-server
インストールできたら、webpack.config.jsも編集します
devServerを追加します。
export default {
+ devServer: {
+ static: 'dist',
+ open: true,
+ },
追加したら、npx webpack serveを実行すると、サーバーが起動します
サーバーを起動すると、ホットリロードが効きますので、tsやVueのファイルを編集すると即座に変更が反映されます
Ctrl+cで終了できます
serveではファイルは出力されない
深く理解したい方はお読みください
サーバーを起動していたら終了して、npx webpackを実行した方は、distフォルダ内のindex.html以外を削除してみてください
そして、もう一度、npx webpack serveを実行し、サーバーを立ち上げます
その後、distフォルダを確認してください
何も出力されてないはずです
私はここが引っかかりました
なぜなら、配置したindex.htmlでは、<script src="bundle.js"></script>でbundle.jsを読み込んでいるが、bundle.jsは出力されていない
なぜ正常に動くのか?
✅ 結論 Webpack Dev Server がメモリ上に bundle.js を作って提供しているから
🔧 背景
Webpack Dev Server(webpack-dev-server)は、開発中に以下のような動作をします
- bundle.js をファイルとして書き出さない(開発中はディスクに保存されない)
- bundle.js を メモリ上で生成(内部的にメモリでバンドルし、HTTP 経由で提供する)
- index.html が
<script src="bundle.js">を参照(Web サーバーが bundle.js を仮想的に提供するので正常に動く)
つまり、実際のファイルシステムには存在しなくても、Webpack Dev Server は bundle.js を仮想的に提供しているので、ブラウザは問題なくそれを読み込めます。
✅ 本番環境では注意
開発中はメモリ上でOKですが、本番ビルド時にはnpx webpack --mode productionなどで dist/bundle.js を実ファイルとして出力する必要があります。そうしないと index.html から参照されている bundle.js が読み込めず、404 エラーになります
index.htmlも自動で作成する
手動で、dist/index.htmlを作成していましたが、これも自動で作成するようにします
html-webpack-pluginをインストール
自動作成するプラグインである html-webpack-pluginをインストールします
npm install -D html-webpack-plugin
テンプレートファイルを作成する
html-webpack-pluginでindex.htmlを作成するのですが、テンプレートとなるファイルを作成します
src/index.htmlを作成する
<!doctype html>
<html>
<body>
<div id="app"></div>
<!--これは自動で挿入されるので必要なし-->
<!--<script src="bundle.js"></script>-->
</body>
</html>
テンプレートファイルはなくても、デフォルトのファイルを作成してくれるらしいのですが、以下のように最小限のhtmlになるため、Vueで<div id="app"></div>を使いたい場合はテンプレートが必要になるようです。
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Webpack App</title>
</head>
<body>
<script src="bundle.js"></script>
</body>
</html>
webpack.config.jsにpluginの設定を追加する
import path from 'path';
import { fileURLToPath } from 'url';
import { VueLoaderPlugin } from 'vue-loader';
+import HtmlWebpackPlugin from 'html-webpack-plugin';
const __filename = fileURLToPath(import.meta.url);
const __dirname = path.dirname(__filename);
export default {
devServer: {
static: 'dist',
open: true,
},
...
plugins: [
new VueLoaderPlugin(),
+ new HtmlWebpackPlugin({
+ template: './src/index.html', // ← テンプレートとして使うHTML
+ filename: 'index.html', // outputのpathのフォルダに出力される
+ inject: 'body',
+ }),
],
mode: 'development', // or 'production'
};
追記したら、npx webpack serveで起動します。
bundle.jsと同じく、npx webpack serveで起動した場合、dist/index.htmlは出力されず、メモリ上に展開されます。
生成したindex.htmlに<script>タグが挿入されない場合
私の環境だけかもしれませんが、npx webpack serveで立ち上げると画面が真っ白になりました。
開発者ツールで確認する<script>タグが挿入されておらず、bundle.jsが読み込めてないみたいでした。
色々試行錯誤したのですが、src/index.htmlを下記に変更して、npx webpackでビルドしてみると、生成物に<script>タグ挿入されるようになりました。
そして、distフォルダの中身を全て消して、npx webpack serveを実行すると正しく起動しました。
一度挿入されると、元のファイルに戻しても、正しく挿入されるので原因わからずです。
<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="UTF-8">
<title>Webpack Vue App</title>
</head>
<body>
<div id="app"></div>
</body>
</html>
devServeのstatic: 'dist'は不要
今回のサンプルでは、npx webpack serveで起動した場合、必要なファイル(index.htmlや、bundle.js)が全て、メモリ上に展開されるため、distには何も出力されないです。
したがって、devServeのstatic: 'dist'は不要となります。
devServer.static: 'dist' 通常のVueアプリ開発では 不要
使いたいケース robots.txt や favicon.ico など Webpackで処理しない静的ファイルを別に配置したいとき
通常は使わずOK。静的ファイルが必要なら public/ フォルダなどを使うのが一般的みたいです