シンプルな Web サイト用の webpack スターターキット

これは何

仕事で必要だったので webpack のスターターキットをつくってみました。

GitHub - ryotah/webpack-starter-basic: A simple webpack starter kit. Babel, TypeScript, ESLint, PostCSS, Jest, Environment variables, Git hooks, etc.

その時に調べたことをメモしておきます。

ちな、いつもは Nuxt などを使っているので、ゼロから用意するのはほぼ初めてでした。何が言いたかというと、そのくらいの人が書いている記事だよということです 😇

必要だったこと:

  • 複数の HTML ページ
  • TypeScript
  • JavaScript polyfills
  • 各種 Lint
  • Autoprefixer (PostCSS)
  • Unit Testing
  • 環境変数
  • SPA は不要

使い方

リポジトリREADME を読んでください 🙇

メモ

以下、調べたことのメモです。

以下のバージョンを前提としています。
webpack 4.43.0, TypeScript 3.9.5, Babel 7.10.2, ESLint 7.2.0, Prettier 2.0.5, Jest 26.0.1, core-js 3.6.5

webpack で 複数 HTML ページ

  • webpack は HTML ファイルをエントリーに指定できません
  • HtmlWebpackPlugin を使いましょう
  • chunk の設定をする必要が (きっと) あるので、webpack 用語としての chunk を知っておくと良いかと思います
// https://github.com/jantimon/html-webpack-plugin#generating-multiple-html-files
{
  entry: 'index.js',
  output: {
    path: __dirname + '/dist',
    filename: 'index_bundle.js'
  },
  plugins: [
    new HtmlWebpackPlugin(), // Generates default index.html
    new HtmlWebpackPlugin({  // Also generate a test.html
      filename: 'test.html',
      template: 'src/assets/test.html'
    })
  ]
}

コミット時に Lint チェックしたい

  • 具体的には ESLint, stylelint, コミットメッセージのチェック
  • フックに husky、コミットメッセージのチェックには commitlint を利用しました

package.json

{ 
  // ...
  "husky": {
    "hooks": {
      "commit-msg": "commitlint -E HUSKY_GIT_PARAMS",
      "pre-commit": "lint-staged"
    }
  },
  "lint-staged": {
    "*.{js,ts}": [
      "eslint --fix"
    ],
    "*.css": [
      "stylelint --fix"
    ],
    "*.html": [
      "prettier --write"
    ]
  }
}

commitlint.config.js

module.exports = {
  extends: ['@commitlint/config-conventional'],
};

詳細はコミットログからどうぞ
chore: set up commitlint · ryotah/webpack-starter-basic@bd44ce3 · GitHub

Babel + TypeScript の設定

  • Babel を利用することで core-js の対応もしたい
  • ts-loader => babel-loader の順番で処理するようにしました
  • @babel/preset-env が便利ですね

webpack.config.js

module.exports = {
  // ...
  module: {
    rules: [
      {
        test: /\.ts$/,
        use: ['babel-loader', 'ts-loader'],
        exclude: /node_modules/,
      },
    ],
  },
};

babel.config.json

{
  "presets": [
    [
      "@babel/preset-env",
      {
        "useBuiltIns": "usage",
        "corejs": 3
      }
    ]
  ]
}

tsconfig.json

{
  "compilerOptions": {
    "target": "esnext",
    "module": "esnext",
    "moduleResolution": "node",
    "lib": [
      "esnext",
      "dom"
    ],
    "esModuleInterop": true,
    "strict": true,
    "baseUrl": ".",
    "paths": {
      "~/*": [
        "src/*"
      ]
    }
  }
}

browserslist

  • @babel/preset-env がデフォルトで browserslist を利用します
  • Autoprefixer も browserslist を利用するので対象ブラウザはここでしっかり設定しておきましょう

package.json

{
  // ...
 "browserslist": [
    "defaults"
  ]
}

ESLint + prettier の設定

$ npm i -D @typescript-eslint/eslint-plugin @typescript-eslint/parser eslint eslint-config-prettier eslint-plugin-prettier prettie

.eslintrc

{
  "root": true,
  "env": {
    "browser": true,
    "node": true
  },
  "extends": [
    "eslint:recommended",
    "plugin:@typescript-eslint/recommended",
    "plugin:prettier/recommended",
    "prettier/@typescript-eslint"
  ],
  "overrides": [
    {
      "files": ["**/*.js"],
      "rules": {
        "@typescript-eslint/no-var-requires": 0
      }
    }
  ]
}

.eslintignore

dist

.prettierrc

{
  "singleQuote": true
}

stylelint の設定

.stylelintrc

{
  "extends": "stylelint-config-standard"
}

Jest の設定

  • preset: 'ts-jest' って前からありましたっけ?簡単ですね。

jest.config.js

module.exports = {
  preset: 'ts-jest',
};

CSS 読み込み

  • サンプルではcss-loader => style-loader をよく見かけますが、style-loader は実行時に "Inject CSS into the DOM" するとのこと
  • 少なくともビルド時には別の loader にしたいので、今回は MiniCssExtractPlugin を利用してみました

webpack.config.js

module.exports = {
  // ...
  module: {
    rules: [
      {
        test: /\.css$/,
        use: [MiniCssExtractPlugin.loader, 'css-loader', 'postcss-loader'],
        exclude: /node_modules/,
      },
    ],
  },
  plugins: [
    // ...
    new MiniCssExtractPlugin({
      filename: '[name].[contenthash].css',
      chunkFilename: '[id].[contenthash].css',
    }),
  ],
};

Autoprefixer を適用させるために PostCSS も利用します。postcss-loader が内部で postcss-load-config を利用しているので今回は .postcssrc を利用することにしました。

.postcssrc

{
  "plugins": {
    "autoprefixer": true
  }
}

ビルド時のファイル名をどうするか

webpack.config.js

module.exports = merge(common, {
  mode: 'production',
  devtool: 'source-map',
  output: {
    filename: '[name].[chunkhash].js',
    path: buildPath,
  },
});

パスの解決

resolve.alias を利用することでエイリアスを利用できます
例えば以下のような設定をすることで import '~/assets/scripts/utils.ts'; のような作業ディレクトリをベースにした import が可能になります。

webpack.config.js

const path = require('path');
const basePath = path.resolve(__dirname, 'src');

module.exports = {
  //...
  resolve: {
    alias: {
      '~': basePath,
    },
  }
};

webpack のエイリアス設定をした場合、TypeScript にも同様の設定が必要になります。 (tsconfigbaseUrlpaths)。

紛らわしいエイリアス名をつけてしまいましたが css ファイル内の @import '~normalize.css'; に使われている ~ とは別ものです。

https://webpack.js.org/loaders/css-loader/

To import styles from a node_modules path (include resolve.modules) and for alias, prefix it with a ~:

環境変数

// Load environment variables
const result = require('dotenv').config({
  path: `.env.${process.env.APP_ENV}`,
});
if (result.error) {
  throw result.error;
}

module.exports = {
  // ...
  plugins: [
    new webpack.DefinePlugin({
      'process.env.GA_TRACKING_ID': JSON.stringify(process.env.GA_TRACKING_ID),
    }),
  ],
};

その他