こんにちはjunです。前回の記事ちゃんと理解するWebpack5。1:webpack基礎とSass・jsのバンドルの続きの記事です。前回はjsファイルのバンドル、scssのコンパイルを行いました。今回はそこから
以上を解説したいと思います。これらができればひとまず意図通りのwebコンテンツが作れるようになります。コードは前回のものから発展させて使用します。それではまず画像パスの解決から行っていきます。
sassでは背景画像などで画像のパスが必要となることがあります。/src
にimgs
ディレクトリを作成します。
.
├── dist
│ ├── bundle.js
│ ├── index.html
│ └── style.css
├── package-lock.json
├── package.json
├── src
│ ├── imgs // new!
│ ├── js
│ │ ├── functions.js
│ │ └── main.js
│ └── sass
│ ├── compnent.scss
│ ├── style.scss
│ └── variable.scss
├── node_modules
└── webpack.config.js
img
ディレクトリにに画像を配置していきます。適当な画像sample.jpgをおいておき、sassにも適当なbackground-image
を設定します。
.image-box{
width: 100px;
height: 100px;
background-image: url('~/imgs/sample.jpg');
}
url('../imgs/sample.jpg');
のような相対パスでなくurl('~/imgs/sample.jpg');
としたのは運用上のテクニックです。後でこの説明をしますので、ひとまずこんなパスにしておきます。まだ設定していませんが、試しにビルドしてみます。
.image-box {
width: 100px;
height: 100px;
background-image: url("~/imgs/sample.jpg"); }
component.scss
で定義した通りのURLとなりましたが、もちろん不正なので404となります。
...
<body>
<main>
<div id='app'>
</div>
<input type="text" value="" id="inputs">
<button id="submmit" >追加する</button>
<div class='box'></div>
<div class='image-box'></div>
</main>
</body>
...
<!-- Failed to load resource: net::ERR_FILE_NOT_FOUND /~/imgs/sample.jpg-->
相対リンクでやってもdist配下ににimgsディレクトリと画像そのものがないので、404となります。そのためwebpackを用いて/src/imgs
と配下の画像をdistに移動し、url('~/imgs/sample.jpg');
のようなパスを変換させてあげる必要があります。
webpack4では画像の処理にurl-loader
、raw-loader
、file-loader
を使用していましたが、webpack5ではすでにwebpackに搭載されているAsset Modulesで行います。ただバージョン4の書き方もまだ主流なので、今回は4と5の方法を紹介し、最終的には5の方法で実装しようと思います。
なお、目指す形としては/dist
配下に/imgs
というディレクトリが作られ、そこに配下の画像が移動され、sassの画像パスが正しくなっているように設定します。
それでは従来の方法での説明をまずは行います。最初に必要なloaderをインストールします。
npm i -D url-loader file-loader
この2つを用いてパスの解決とファイルの移動を行うことができます。そしてwebpack.config.js
を以下のように変更します。
const MiniCssExtractPlugin = require('mini-css-extract-plugin');
const path = require('path'); // 追加
module.exports = {
//バンドル対象のファイル
entry: ./src/js/main.js',
mode:"development",
module: {
rules: [
{
test: /\.(sa|sc|c)ss$/,
exclude: /node_modules/,
use: [
MiniCssExtractPlugin.loader,
{
loader: 'css-loader',
options: { url: true } // trueに変更
},
'sass-loader',
]
},
{ //追加
test: /\.(png|jpg|gif)$/i,
use: [
{
loader: 'url-loader',
options: {
limit: 8192,
name:'./imgs/[name].[ext]'
}
},
],
type: 'javascript/auto' // 大切
},
],
},
resolve:{ //追加
alias: {
'~': path.resolve(__dirname, 'src')
}
},
// ファイルの出力設定
output: {
// 出力ファイルのディレクトリ名
path: `${__dirname}/dist`,
// 出力ファイル名
filename: "bundle.js"
},
plugins: [
new MiniCssExtractPlugin({
filename: 'style.css'
})
]
};
それでは細かい説明をします。
file-loaderではjpg,png,gifの拡張子を見つけた時の処理を記述します。
{
test: /\.(png|jpg|gif)$/i,
use: [
{
loader: 'url-loader',
options: {
limit: 8192,
name:'./imgs/[name].[ext]'
}
},
],
type: 'javascript/auto' //大切
},
loaderにはurl-loaderを指定してファイルをURIに変換できるようにします。
distでは画像はimgsディレクトリ配下に格納させますので、name:'./imgs/[name].[ext]'
としておきます。sass,jsの画像ファイルの参照もこのディレクトリ構成にあったパスに変更してくれます。
webpack5で従来の方法を使う場合、type: 'javascript/auto'
を忘れないようにしてください。公式ドキュメントの記述はこちら。 webpack5にはAsset Moduleというものがすでにあり、それがfile-loader達と似たような働きを行います。もし先ほどの記述がないと二重で処理が入るなどがしておかしくなります。
{
resolve:{ //追加
alias: {
'~': path.resolve(__dirname, 'src')
}
},
},
resolve
ではwebpackがデフォルトで持っている、名前解決に新しい設定を追加したり、変更することができます。ここでは先ほど~/img/test.png
みたいな書き方をしたパスを正しいパスに変換する処理を書いています。
~
はエイリアスとして設定しました。'~': path.resolve(__dirname, 'src')
とすることで、~
を見つけたらpath.resolve(__dirname, 'src')
というに変換するんだなとwebpackが読み取ります。ちなみにpath.resolve(__dirname, 'src')
というのはこのファイル(今回のwebpack.config.js)が置かれているOS上のパスのことです。macのあるディレクトリで構築していると以下の様に変換されます。
/Users/jun/Desktop/my_apps/webpack_practice/src
もう少しざっくり説明するとこのsrc
ディレクトリを指しています。
.
├── dist
│ ├── bundle.js
│ ├── index.html
│ └── style.css
├── package-lock.json
├── package.json
├── src //←ここ!! = path.resolve(__dirname, 'src')
│ ├── imgs
│ ├── js
│ │ ├── functions.js
│ │ └── main.js
│ └── sass
│ ├── compnent.scss
│ ├── style.scss
│ └── variable.scss
├── node_modules
└── webpack.config.js
つまり、sassで~/imgs/test.png
とすれば~
を自動に変換してsrc
配下のimgs
の画像にバインドされます。このように特定ディレクトリやそのパスを別名で呼ぶことをエイリアス(alias)といいます。
ではなぜエイリアスで呼ぶことがいいのでしょうか?それは階層が深くなるほど、相対パスの指定では苦労するからです。他にも相対パスで指定すると、imgsディレクトリの位置を変えたいとなった時に全ての相対パスを変更する必要があります。srcディレクトリをエイリアスにすることで、相対パスでしていなくても良くなります。画像を使いたい場合は
background-image('~/imgs/test.png')
import img from '~/imgs/test.png';
console.log(img);
と指定すればよくなります。ディレクトリ構成の変更にも柔軟に対応できます。
src
直下ですが、src/imgs
を指す画像用エイリアスなんかも作成できます。プロジェクトに合わせて設定しましょう。
これで画像ファイルが利用できる様になります。次はwebpack5の方法でやってみましょう。
webpack5の方法では結構簡単になりました。webpack4でインストールしたraw-loader
,url-loader
,file-loader
は必要ありません。
const MiniCssExtractPlugin = require('mini-css-extract-plugin');
const path = require('path');
module.exports = {
//バンドル対象のファイル
entry: ./src/js/main.js',
mode:"development",
module: {
rules: [
{
test: /\.(sa|sc|c)ss$/,
exclude: /node_modules/,
use: [
MiniCssExtractPlugin.loader,
{
loader: 'css-loader',
options: { url: true }
},
'sass-loader',
]
},
{
test: /\.(png|jpg|gif)$/i,
// ここから変更。useがなくなり。typeが変更されている。
generator: {
filename: 'imgs/[name][ext][query]'
},
type: 'asset/resource'
},
],
},
resolve:{
alias: {
'~': path.resolve(__dirname, 'src')
}
},
// ファイルの出力設定
output: {
// 出力ファイルのディレクトリ名
path: `${__dirname}/dist`,
// 出力ファイル名
filename: "bundle.js"
},
plugins: [
new MiniCssExtractPlugin({
filename: 'style.css'
})
]
};
変わった箇所はpng,jpg,gifのrules
の設定です。4の設定ではloaderを使用するためにuse
で設定しました。しかし5ではtype: 'asset/resource'
を使用すること4で実装していた動きを実装できます。
エイリアスなどの設定は変わりません。webpackにおける画像ファイルの設定以上となります。
これらの設定があればひとまずsassとjsを用いた作成ができそうです。ですがwebエンジニアを悩ませるこれの対策をしなくてはいけません。
Interner Explorer(以後IE)です。特にJSが影響を受けます。JSにはES5、ES6という2種類の記述方法があります。今回はその違いの説明は省きますが、ES6はES5より効率的な書き方ができます。しかしIEはES5の書き方しか受け付けず、ES6の書き方は構文エラーを起こして実行できないというクソ仕様です。
そのためES6のJSを使用するにはES5の記述に変換する必要があります。その変換をしてくれるのがBabelです。webpackにはbabel-loader
というものがあるので、それを利用してバンドルと同時に変換(トランスコンパイル)を行いましょう。
試しにIEでは使用できないアロー関数と定数宣言を書いておきます。
import $ from 'jquery';
import funcs from './functions';
import '~/sass/style.scss';
$('#submmit').on('click',()=>{
return funcs.addNewText('#app','#inputs');
})
const message = "use in IE";
()=>{
console.log(message)
}
ちなみにbundle.jsは以下の通りに書かれていました。ES6の書き方がで出力されています。
//...
// const message = \"use in IE\";\n()=>{\n console.log(message)
//...
まずはloaderをインストールします。
npm install -D babel-loader @babel/core @babel/preset-env
そしてドキュメントのままですがJSファイルに対してのrule
を追加し、babel-loaderを適用させる様にします。
// ...
module: {
rules: [
// 追加
{
test: /\.m?js$/,
exclude: /(node_modules|bower_components)/,
use: {
loader: 'babel-loader',
options: {
presets: ['@babel/preset-env']
}
}
},
{
test: /\.(sa|sc|c)ss$/,
exclude: /node_modules/,
use: [
MiniCssExtractPlugin.loader,
{
loader: 'css-loader',
options: { url: true }
},
'sass-loader',
]
},
// ...
],
},
// ...
これでbabelが有効になり、トランスコンパイルされます。実際に動かしてみてbundle.jsをみてみると
var message = \"use in IE\";\n\n(function () {\n console.log(message);\n})
このようにES5の書き方に直してくれました。
ちなみに今回は数行のコードなので十分ですが、本番では大量のファイルと記述を変換するので時間がかかったりメモリを食います。npm run watch
でもそこそこメモリを食う様になります。対策としては環境変数を用いて本番ビルドの時だけトランスコンパイルさせる様にします。簡単な例としてまずpackge.json
でnode.jsの変数をコマンド上で定義します。
"scripts": {
"build": "es5=true npx webpack-cli build",
"watch": "npx webpack-cli watch",
"test": "echo \"Error: no test specified\" && exit 1"
},
今回の場合、npm run build
した時にes5=true
という変数が定義されます。そしてwebpack.config.js
のrulesをこんな風に変更してみます。
// ...
// rulesを外で定義しておく。
let rules = [
{
test: /\.(sa|sc|c)ss$/,
exclude: /node_modules/,
use: [
MiniCssExtractPlugin.loader,
{
loader: 'css-loader',
options: { url: true }
},
'sass-loader',
]
},
{
test: /\.(png|jpg|gif)$/i,
generator: {
filename: 'imgs/[name][ext][query]'
},
type: 'asset/resource'
}
]
// es5がtrueならばバベルを適用
if(process.env.es5){
rules.push(
{
test: /\.m?js$/,
exclude: /(node_modules|bower_components)/,
use: {
loader: 'babel-loader',
options: {
presets: ['@babel/preset-env']
}
}
}
)
}
module.exports = {
//バンドル対象のファイル
entry: './src/js/main.js',
mode:"development",
module: {
rules: rules
},
// ...
}
rules
をmodule.exports
の外に出しておいて、process.env.es5
の値によってrules
を変更できる様にします。これでrules
の分岐ができました。build
の時だけBabelが使用され、watch
の時はBabelが無しになります。この辺はプロジェクトごとに好きに設定してみてください。
最後に複数のバンドルファイルを出力する方法を解説します。今は参照されているアセットファイルを全てbundle.js、bundle.cssにしています。しかしプロジェクトによっては
など複数パターンのファイルを出力したい時があります。例えば私はよく管理画面のUIはbootstrapとvueを使って構築してしまいます。そして一般画面はせいぜいjqueryを使用して200行にも満たないこともあります。管理画面はbootstrap合わせていろんなライブラリを使うため本番ビルドしてもかなりファイル容量を食います。一方、一般画面はそれほど大きくなりません。そんな時に全て一つのbundle.js/css
にまとめては一般画面に重いファイルを配ってしまいますし、場合によっては管理画面の構築コードが漏れてしまうのでよろしくありません。
このような状況もよくあるので複数のバンドルファイルを出力できる様にしましょう。上記の様な状況として管理画面のadmin.js
,admin.css
とmain.js
,main.css
が必要になったとしましょう。
.
├── dist
│ ├── admin.css // 作成目標
│ ├── admin.js // 作成目標
│ ├── imgs
│ │ └── test.png
│ ├── index.html
│ ├── main.css // 作成目標
│ └── main.js // 作成目標
├── package-lock.json
├── package.json
├── node_modules
├── src
│ ├── imgs
│ │ ├── sample.png
│ │ └── test.png
│ ├── js
│ │ ├── admin.js // 作成
│ │ ├── functions.js
│ │ └── main.js
│ └── sass
│ ├── admin.scss // 作成
│ ├── compnent.scss
│ ├── style.scss
│ ├── utility
│ └── variable.scss
└── webpack.config.js
srcにエントリーとなるadmin.js
,admin.scss
を作成します。中身は適当にadminのみで使われている記述にしてみてください。ここでは省きます。
そしてwebpack.config.js
のentry
,output
,plugins
を以下の様に変更します。
module.exports = {
entry:{
main:'./src/js/main.js',
admin:'./src/js/admin.js'
},
// 省略
output: {
// 出力ファイルのディレクトリ名
path: `${__dirname}/dist`,
// 出力ファイル名
filename: "[name].js"
},
plugins: [
new MiniCssExtractPlugin({
filename: '[name].css'
})
]
};
注目して欲しいのはentry
です。
// 変更前
entry: './src/js/main.js',
// 変更後
entry:{
main:'./src/js/main.js',
admin:'./src/js/admin.js'
},
今まではそれぞれのファイルを直接配列で指定してましたが、変更後ではオブジェクトにしています。オブジェクトのキーは[name]
として利用できます。例えば、output
でこの様に利用します。
output: {
// 出力ファイルのディレクトリ名
path: `${__dirname}/dist`,
// 出力ファイル名
filename: "[name].js"
},
plugins: [
new MiniCssExtractPlugin({
filename: '[name].css'
})
]
変更前は全てbundle.js
,style.css
と一定の名前でしたが、こうするとオブジェクトのキー名に応じて、main.js
、admin.js
など作成されます。実際にビルドをしてみると、main.js
、admin.js
、main.css
、admin.css
が作成されました。
以上が今回の内容です。画像・Babelそして複数ファイルパターンができればもうプロジェクトで十分利用可能です。次回はhtmlをsrc配下で利用できるようにします。src配下のみで作業してコマンド打って完成したファイルがdistに出せる様にします。そしてまとめとしてこれらの構成とpugを使用したプロトタイプページの作成をしてみます。
コメント
コメント読み込み中..