webpack4の備忘録

webpackって使うことあるのかな?と思っていましたが、使うと便利です

2回目からはほぼコピペだし😅
いろいろ対応できるように、理解できている範囲でまとめておこうと思います

webpackでしていること
  • babelでJSのトランスパイル
  • scssのコンパイル
  • 画像な圧縮
  • ファイルの圧縮
  • モジュールの利用と依存関係の解決(読み込みの順番を気にしない)

*Node.jsインストール済み

目次
  1. npm
  2. 設定ファイル
    1. 基本の形
    2. 設置ファイルを分ける
  3. webpack.dev.js(開発用)
      1. 開発用サーバー
  4. webpack.common.js(共通)
    1. 出力したファイルを消す
      1. HTMLをどこに配置するか
        1. public直下に置く場合(手動)
        2. src直下に置く(webpackで管理)
          1. ブラウザキャッシュ対策(disk cache)
      2. 画像の出力
        1. background-image
        2. HTMLで読み込んでいる画像
      3. 画像の圧縮
    2. バンドルファイルを分割する場合は
    3. JavaScript
      1. Babelのインストール
      2. 対象ブラウザを指定する
      3. 設定ファイル(babel.config.js)
    4. SCSS
      1. CSSファイルに出力する
  5. webpack prod.js(本番用)
      1. CSSの圧縮と最適化
  6. 用語とよく使う正規表現
  7. 必要なファイルだけgulpで圧縮

npm

JavaScriptのパッケージを管理するツールで、依存するパッケージもまとめてインストールしてくれる

npm init -y で「package.json」を作成

オプション

--save
実行時に必要な依存パッケージの場合
package.json の dependencies に記録されます(もし誰かが npm install したときに依存パッケージとしてインストールされる)
--save-dev
開発用のパッケージの場合
package.jsonのdevDependencies に記録されます(されない)

*もし誰かがこのパッケージをインストールしたときに依存パッケージとしてインストールされるかどうかなので実質的には関係ない

webpackのインストールですが…

webpack webpack-cli

すべてインストールするのは面倒なので、基本のpackage.jsonから流用します😅
コピペしてnpm install

package.jsonファイル
{
  "name": "hogehoge",
  "scripts": {
    "start": "webpack-dev-server --config webpack.dev.js",
    "dev": "webpack --config webpack.dev.js",
    "build": "webpack --config webpack.prod.js"
  },
  "devDependencies": {
    "@babel/core": "^7.10.5",
    "@babel/preset-env": "^7.10.4",
    "autoprefixer": "^9.8.5",
    "babel-loader": "^8.1.0",
    "clean-webpack-plugin": "^3.0.0",
    "css-loader": "^3.5.3",
    "file-loader": "^6.0.0",
    "html-loader": "^1.1.0",
    "html-webpack-plugin": "^4.3.0",
    "image-webpack-loader": "^6.0.0",
    "mini-css-extract-plugin": "^0.9.0",
    "node-sass": "^4.14.1",
    "optimize-css-assets-webpack-plugin": "^5.0.3",
    "postcss-loader": "^3.0.0",
    "sass-loader": "^8.0.2",
    "terser-webpack-plugin": "^3.0.8",
    "webpack": "^4.43.0",
    "webpack-cli": "^3.3.11",
    "webpack-dev-server": "^3.11.0",
    "webpack-merge": "^5.0.9"
  },
  "dependencies": {
    "core-js": "^3.6.5",
    "regenerator-runtime": "^0.13.7",
  }
}

設定ファイル

webpack.config.js(設定ファイル)

( webpack – – mode development)などをまとめて
設定ファイルにしてwebpackコマンドのオプションとして渡す ( – – config ファイル名)

基本の形

//CommonJS(Node.js)
require(''); //インポート
module.exports = {} ; //エクスポート
outputのpathの値は絶対パスなのでpathモジュールを使う
src直下にエントリーポイントを作る、public直下にバンドルされる
*デフォルトのdist直下でいい場合は、outputのpathは不要
const path = require('path');
module.exports={
  mode: '',
  devtool: '',
  entry: './src/',
  output: { 
   path: path.resolve(__dirname, 'public'),
   filename: '', 
  },
  optimization: {
   },
  module: { //ローダーの設定
    rules: [],
  },
  plugins: [],
}
複数のエントリーポイントにする時はentryの値をオブジェクトにする
entry: {
app: './src/js/app.js',
hoge: './src/js/hoge.js',
},
 output: {
    path: path.resolve(__dirname, 'public'),
    filename: 'js/[name].js',
    chunkFilename: 'js/[name].js',
  },
outputのファイル名には[name]をつける(プレスホールダーになる)
[name]にentryのkeyが入る
chunkFilenameは、エントリーポイント以外(splitChunksプラグインなど)から出力されたファイルの名前を設定する

設置ファイルを分ける

webpack-merge

const output = merge(object1, object2, object3, ...)
  • webpack.common.js(共通)
  • webpack.dev.js(開発用)
  • webpack prod.js(本番用)
例:productionではミニファイされるようになっている(TerserPlugin)

webpack.dev.js(開発用)

const path = require('path');
const { merge } = require('webpack-merge');
const commonConfig = require('./webpack.common.js');

module.exports = merge(commonConfig, {
  mode: 'development',
  watch: true,
  devtool: 'cheap-module-eval-source-map',
  devServer: {
    open: false,
    port: 9000,
    contentBase: path.resolve(__dirname, 'public'),
  }
});

開発用サーバー

webpack-dev-server

webpack-dev-server(npm scriptsで実行)

const path = require('path');

  devServer: {
    open: true,
    port: 9000,
    contentBase: path.resolve(__dirname, 'public'),
  }

*メモリを保持するだけで、ファイルに出力しない

webpack.common.js(共通)

const path = require('path');
const { CleanWebpackPlugin } = require('clean-webpack-plugin');
const HtmlWebpackPlugin = require('html-webpack-plugin');
const MiniCssExtractPlugin = require('mini-css-extract-plugin');

module.exports = {
  devtool: 'none',
  entry: './src/js/app.js',
  output: {
    path: path.resolve(__dirname, 'public'),
    filename: 'js/[name].js',
  },
  optimization: {
     splitChunks: {
      chunks: 'initial',
      name: 'vendor',
    }
  },
  module: {
    rules: [
      {
        test: /\.js$/,
        exclude: /node_modules/,
        loader: 'babel-loader',
      },
      {
        test: /\.scss$/,
        use: [
          MiniCssExtractPlugin.loader,
          'css-loader',
          'postcss-loader',
          'sass-loader',
        ],
      },
       {
        test: /\.(jpe?g|gif|png|svg)$/,
        use: [
          {
            loader: 'file-loader',
            options: {
              name: '[name].[contenthash].[ext]',
              outputPath: 'images',
              publicPath: '/images',
            },
          },
          'image-webpack-loader',
         ],
       },
       {
         test: /\.html$/,
         loader: 'html-loader',
       },
    ]
  },
  plugins: [
    new CleanWebpackPlugin(),
    new HtmlWebpackPlugin({
      template: './src/index.html',
      inject:'body'
    }),
    new MiniCssExtractPlugin({
    filename: './css/.[name].[contenthash].css',
    }),
  ],
};

出力したファイルを消す

clean-webpack-plugin

HTMLをどこに配置するか

バンドルしたファイルの読み込みを手動でするか、webpackで管理するか

public直下に置く場合(手動)
HTMLファイル以外の全てのファイルやディテクトリが削除対象
const { CleanWebpackPlugin } = require('clean-webpack-plugin');

 plugins: [
    new CleanWebpackPlugin({
    cleanOnceBeforeBuildPatterns:['**/*', '!**.html'], 
   }),  
  ],
src直下に置く(webpackで管理)

html-webpack-plugin

const HtmlWebpackPlugin = require('html-webpack-plugin');

 new HtmlWebpackPlugin({
      template: './src/index.html',
    }),

エントリーポイントが2つの場合

new HtmlWebpackPlugin({
      template: './src/index.html',
// appから出力されるjs を読み込んだ HTML を出力する
      chunks: ['app'],
    }),
    new HtmlWebpackPlugin({
      filename: 'hoge.html',
      template: './src/hoge.html',
      chunks: ['hoge'],
    }),
ブラウザキャッシュ対策(disk cache)

ファイル名を変更する

outputにハッシュ(例:[contenthash] )を追加

 output: {
    path: path.resolve(__dirname, 'public'),
    filename: 'js/[name].[contenthash].js',
  },
  • [contenthash]更新されたをコンテンツ毎
  • [hash]ビルド全体で同じハッシュを取得
  • [chunkhash]chunk単位で割り振られる

画像の出力

background-image

file-loader

      options: {
              name: '[name].[contenthash].[ext]', 
 //publicからの相対パス(格納先)
              outputPath: 'images', 
 //画像のパス(別のサーバーに画像がある場合など状況に応じて)
              publicPath: 'images',
              },
HTMLで読み込んでいる画像

html-loader

webpackgがテンプレートを解析するために必要

画像の圧縮

image-webpack-loader

バンドルファイルを分割する場合は

splitChunksプラグイン

  • 目的はキャッシュ活用のため、更新頻度が高いものと低いものを一緒にバンドルしないようにする
  • 「html webpackプラグイン」を使用していたら、自動で読み込み追加される
  • デフォルトの設定(chunksはasyncになっている)を上書きする(インストール不要)
上書きした例
async:ダイナミックインポートしたモジュール(デフォルト)
initial :npmでインストールしたモジュール
all:どちらも
複数に分割する時cacheGroups:{ }
optimization: {
 //ここ
}
splitChunks: {
      chunks: 'initial',
      cacheGroups: {
        hoge1: {
 //npmインストールしたモジュール
          test: /node_modules/,
          name: 'vendor',
        },
        hoge2: {
          test: /src[\\/]js[\\/]hoge/,
          name: 'hoge',
//分割対象ファイルの最小サイズ
          minSize: 0, 
//2つ以上のファイルで利用されている場合分割対象
          minChunks: 2, 
        },
      },
    },

JavaScript

Babelのインストール

BabelはJavaScriptのトランスパイラ、core-jsというPolyfillを利用します

babel-loader(ローダー) 
babel/preset-env(babelPluginsの集まり) 
babel/core(本体)
regenerator-runtime (async/await)

余談 React : @babel/preset-react
 module: {
    rules: [
      {
        test: /\.js$/,
        exclude: /node_modules/,
        loader: 'babel-loader',
      },
 ]
}

対象ブラウザを指定する

ファイルサイズを減らすため、必要なポリフィルのみ取り込む

.browserslistrc ファイルを作成(共通利用できる)
または、package.jsonに
"browserslist":["ie 11"]を追加しても良い
ie 11

設定ファイル(babel.config.js)

設定ファイル(babel.config.js)
module.exports = {
  presets: [
    [
      '@babel/preset-env',
      {
//必要なポリフィルだけ
        useBuiltIns: 'usage', 
//babelのバージョンが上がると変わる可能性あり
        corejs: 3, 
      },
    ],
  ],
};

SCSS

エントリーポイントにscssファイルをimportする

node-sass sass-loader css-loader

css-loaderでJSに読み込んでstyle-loaderでHTMLに出力する
個別のCSSでファイルで出力するのでstyle-loaderは不要

CSSファイルに出力する

mini-css-extract-plugin

ベンダープレフィクスを自動でつける
autoprefixerはpostcss製のプラグイン

autoprefixer postcss-loader

設定ファイル(postcss.config.js)
対応ブラウザは.browserslistrc
module.exports = {
  plugins: [require('autoprefixer')],
};

webpack prod.js(本番用)

const { merge } = require('webpack-merge');
const commonConfig = require('./webpack.common.js');
const TerserPlugin = require('terser-webpack-plugin');
const OptimizeCSSAssetsPlugin = require('optimize-css-assets-webpack-plugin');

module.exports = merge(commonConfig, {
  mode: 'production',
  optimization: {
    minimizer: [
      new TerserPlugin({
//ライセンスコメント
        extractComments: false,
//console.logを消す
        terserOptions: {
          compress: {
            drop_console: true,
          },
        },
      }),
      new OptimizeCSSAssetsPlugin({}),
    ],
  },
 });

CSSの圧縮と最適化

optimize-css-assets-webpack-plugin terser-webpack-plugin

postcssローダーを使っている場合
postcss.config.jsに追加
 //ここを追加
    require('cssnano')({
      preset: 'default',
    }),
postcssローダーを使っていない場合
 minimizer: [  new TerserPlugin({ }), new OptimizeCSSAssetsPlugin({}),   ],

注意 optimizationのminimizerに追加する時は、new TerserPlugin({ })を追加する
new TerserPlugin({ })がないとJSは圧縮されない

用語とよく使う正規表現

エントリーポイント
最初に見に行くファイル
ここにモジュールをimportする(起点)
モジュール
部品(jsファイルやsassファイルなど)
バンドル
モジュールをまとめたファイル
JSのトランスパイル
ES6以降のJavaScriptをそれ以前のものに変換する
scssのコンパイル
CSSにする(コンピュータ用にする)
loader(ローダー)
ファイルを加工して別の状態にする役割
webpackはJavaScriptファイルのみ、そのまま使える
plugins(プラグイン)
webpackの拡張機能
Polyfill
最近の機能をサポートしていない古いブラウザーで、その機能を使えるようにするためのコード
PostCSS
JavaScriptでCSSを変換するためのプラグインを作るツール
よく使う正規表現 / /で囲む
  • \ メタ文字を普通の文字で使う時(エスケープ)
  • .  任意の1文字
  • * 直前の文字の0回以上の繰り返し
  • + 直前の文字の1回以上の繰り返し
  • ?  直前の文字が全くないか、1回だけある
  • [0-9]  任意の数字1文字
  • [a-z]  任意のアルファベット1文字(小文字)
  • |  複数のアイテムのいずれか
  • ()  グループ
  • ^  次の文字で始まる
  • $  前の文字で終わる
  • [\\/] は/\

必要なファイルだけgulpで圧縮

src直下のファイルたちを、削除した悲しすぎる経験から💧

$ npm install --save-dev gulp gulp-zip
gulpfile.jsを作る
gulpで実行
const gulp = require('gulp');
const zip = require('gulp-zip');

const paths = {
	package: {
    src: [
      "**/*",
      "!.vscode",
      "!node_modules/**",
      "!packaged/**",
      "!src/**",
      "!babel.config.js",
      "!.gitignore",
      "!gulpfile.js",
      "!package.json",
      "!package-lock.json",
	  "!postcss.config.js",
	  "!.browserslistrc",
	  "!webpack.common.js",
	  "!webpack.dev.js",
	  "!webpack.prod.js"
    ],
    dest: "packaged",
  },
}

exports.default = () => (
	gulp.src(paths.package.src)
		.pipe(zip('圧縮フォルダの名前.zip'))
		.pipe(gulp.dest(paths.package.dest))
);
gulp cliがグローバルインストールされていない場合は
npm scripts(package.json)を使う

{
"scripts": {
//追加
"gulp": "gulp"
}
}