シンプルな Web サイト用の webpack スターターキット
これは何
仕事で必要だったので webpack のスターターキットをつくってみました。
その時に調べたことをメモしておきます。
ちな、いつもは Nuxt などを使っているので、ゼロから用意するのはほぼ初めてでした。何が言いたかというと、そのくらいの人が書いている記事だよということです 😇
必要だったこと:
- 複数の HTML ページ
- TypeScript
- JavaScript polyfills
- 各種 Lint
- Autoprefixer (PostCSS)
- Unit Testing
- 環境変数
- SPA は不要
使い方
メモ
以下、調べたことのメモです。
以下のバージョンを前提としています。
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
- 最終的に Babel がトランスパイルするので、
target
,module
,lib
はesnext
にしています baseUrl
,paths
に関しては パスの解決 で説明しますesModuleInterop
に関しては esModuleInterop オプションの必要性について - Qiita がわかりやすかったです。ありがとうございます。
{ "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 の設定
- 基本は ESLint の設定 (Nuxt TypeScript) - ryotah’s blog の「前提」と同じです
eslint-plugin-prettier
を利用して 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 の設定
- 特にこだわりはなかったのでルール設定には
stylelint-config-standard
を利用しました
.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 } }
ビルド時のファイル名をどうするか
- chunk 内容のハッシュ (
chunkhash
) にするのがキャッシュの点からよさそうです - https://webpack.js.org/configuration/output/#template-strings
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 にも同様の設定が必要になります。 (tsconfig
の baseUrl
と paths
)。
紛らわしいエイリアス名をつけてしまいましたが css ファイル内の @import '~normalize.css';
に使われている ~
とは別ものです。
https://webpack.js.org/loaders/css-loader/
To import styles from a
node_modules
path (includeresolve.modules
) and foralias
, 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), }), ], };
その他
- node.js - path.join vs path.resolve with __dirname - Stack Overflow
path.resolve, on the other hand, will resolve to an absolute path.
- webpack の
mode
について- https://webpack.js.org/configuration/mode/#mode-production
Providing the mode configuration option tells webpack to use its built-in optimizations accordingly.