[dev: webpack] webpack - Frontend module bundler. #webpack #html #js #css #sass
webpack/webpack - github.com
webpack.js.orgwebpack は WebApp に必要なリソースの依存関係を解決し、アセット(配布物)を生成するビルドツール(要するにコンパイラ)です。JavaScript だけでなく、CoffeeScript や TypeScript、CSS 系、画像ファイルなどを扱うことができます。- webpackでイマドキフロント開発
ざっくりと ./src/index.js
をエントリーポイントとして、依存パッケージやアセットをバンドルし、最終的に ./dist/main.js
へコンパイルする frontend ビルドツール。
webpack.config.js
という設定ファイルで鬼カスタマイズできる利用者が多い分、古い・誤った情報も多い。ドキュメント嫁。
require()とは何か?何が便利なのか
ES6のexportについて モダンなJSの話──importとexport
[意訳]初学者のためのJavaScriptモジュール講座 Part1
webpack は分割されたモジュール群の結合 = バンドルのためのツール。よって ↓ のような JavaScript のモジュール分割機能について十分に理解してから触れないと「よくわからん」ってなる。
export
/ import
require
Getting Started - webpack.js.org
Configuration > Mode - webpack.js.org
# 自分であれこれ入れるより init の対話で依存入れてもらうのが楽そう
# https://github.com/webpack/webpack-cli/blob/master/packages/generators/INIT.md
#
$ npx webpack-cli init
# 手動で typescript で最小構成にするんだったら多分こう
#
$ npm i -D webpack webpack-cli typescript ts-loader
webpack-cli init
で作った webpack.config.js
について、モードの切り替えをやってるところだけ ↓ のように変えると良い。通常 webpack --mode=production
とかでビルドかけるので NODE_ENV
参照じゃなくて module.exports
に渡す即時関数の第2引数 (↓ の argv
) で mode
見る感じ。
// webpack.config.js
//
const path = require("path");
const config = { /* ... */ };
// ここ
module.exports = (_, argv) => {
config.mode = argv.mode;
if (config.mode === 'development') {
config.devtool = 'inline-source-map';
}
return config;
};
webpack v5がリリースされたので、現状をまとめてみた
webpack@5の主な変更点まとめ To v5 from v4 - webpack.js.org
2020.10 に v5 がリリースされた。結構大きく変わっているので注意。
@types/webpack
が不要になったfile-loader
とか要らなくなった
target: 'browserslist'
で fallback 先は web
<script type="module">
で出力する output module 機能が追加された
target: ['es5']
が必要target: 'node'
で node api の fallback (transpile) を無効化してくれるみたい
use
loader
の違いが厳格に (エラーになるように) なった
file:///
http(s)://
プロトコルのサポートが追加された
http(s)://
プロトコルは別途プラグインが必要みたい# webpack 本体 & CLI をインストール
# 2021.2 現在 v5 がどれくらい安定してるかわからないので v4 指定してる
#
$ npm i -D webpack@4 webpack-cli
# webpack 設定ファイルを生成 (デフォルトで使うことはほぼない)
$ npx webpack init
より詳細なコンパイル設定は package.json
と同階層に設置した webpack.config.js
で行う。開発用パッケージのロード・設定、エントリーポイント・出力先、モジュール対象ファイル・処理ローダー ( パッケージ )、開発用サーバー設定など。webpack は JavaScript だけでなく、設定次第で CSS や画像やフォントファイルなどもバンドルできるため、CSS フレームワークを webpack 経由でバンドルしたりと、フロントエンド開発に必要なタスクは大体こなせる。
Watch and WatchOptions webpack は
--watch
による Watch モード時に監視対象ファイルの変更を検知して差分ビルドを行ってくれる
// webpack.config.js
module.exports = {
watchOptions: {
ignored: ['files/**/*.js', 'node_modules'],
aggregateTimeout: 300,
poll: 1000
},
};
// webpack-cli の init で作った例
const isProduction = process.env.NODE_ENV == "production";
const config = {/* ... */};
module.exports = () => {
if (isProduction) {
config.mode = "production";
} else {
config.mode = "development";
config.devtool = 'inline-source-map';
}
return config;
};
Entry Points
isaacs/node-glob
webpackのentryファイルを複数指定、globパッケージの使い方
webpack は複数のエントリーポイントを設けられるが ./**/*.js
みたいなことはできない。これについて node-glob パッケージと掛け合わせてエントリーポイントを複数にして import
を減らせるね!やった!!という試み。但し、エントリーポイントは 1 つでそこから他を import
する方が設定は楽、webpack で柔軟なバンドルをやりすぎると設定やキメゴトが複雑になるのでほどほどに。
$ npm i -D glob
// webpack.config.js
const glob = require('glob');
module.exports = [
{
entry: {
bundle: [
'./src/index.js', // ど頭で Polyfill 読んだりする index.js
...glob.sync('./src/bases/**/*.js', { ignored: './**/_*.js' }),
...glob.sync('./src/components/**/*.js', { ignored: './**/_*.js' }),
...glob.sync('./src/middlewares/**/*.js', { ignored: './**/_*.js' }),
...glob.sync('./src/modules/**/*.js', { ignored: './**/_*.js' }),
],
},
}
];
https://www.npmjs.com/package/node-sass-glob-importer
Glob online tester
JavaScript と違って CSS / Sass は @import
の実行順序が大事。よって webpack 側でまとめるより上記のようなプラグインを使って index.scss
みたいなエントリーポイントで順番を指定しながら @import 'hoge/**/*.scss';
とかした方が楽。逆に JavaScript の場合は import 時の変数衝突があるためこの手法は微妙。
$ npm i -D node-sass-glob-importer
// index.scss
// Bootstrap 変数カスタム用 _variables.scss を先頭でインポート
@import 'bases/variables';
// node_modules 配下 Bootstrap のインポート
@import '~bootstrap/scss/bootstrap';
// 以下おれおれモジュール群を glob パターンでインポート
// glob パターンの場合は拡張子指定が必要なので注意
// .{css,scss,sass} とかも一応可能
@import 'bases/**/!(_*).scss';
@import 'components/**/!(_*).scss';
デフォルトでは webpack.config.js
の存在するアプリルート上の node_modules
が import
時のルート /
としてセットされているみたい。React のコンポーネントなど自作 Class の import / export
を 相対パスでなく絶対パスで 指定したい場合は、パス解決のためのエイリアスを resolve.alias
で追加できる。
module.exports = {
//...
resolve: {
modules: ['node_modules'], // デフォ
extensions: ['.wasm', '.mjs', '.js', '.json', '.jsx'], // デフォ + .jsx
alias: {
// ここやで
bases: path.resolve(__dirname, './src/bases'),
components: path.resolve(__dirname, './src/components'),
middlewares: path.resolve(__dirname, './src/middlewares'),
modules: path.resolve(__dirname, './src/modules'),
}
}
};
// これ ( クソかったるい相対パスで指定していた import ) を ...
import MyComponent from '../../components/my-component';
// こうじゃ ... !
import MyComponent from 'components/my-component';
eslint ちゃんは webpack で alias 設定したって読んでくれないから .tsconfig.json
にも同じこと書かんとおこ。
https://stackoverflow.com/questions/40443806/webpack-resolve-alias-does-not-work-with-typescript
{
"compilerOptions": {
"paths": {
"bases/*": ["./src/bases/*"],
// ... 略
}
Loaders - webpack.js.org
babeljs.io babel(バベル)を使ってすべてのブラウザで動作するようにする
ES2015(ES6)+webpack+babel-loaderで開発環境を構築 Babel@7で構築するReact開発環境
モジュール分割用の import / export
なども含め、ES6 以降コードの多くはまだブラウザ間で対応差異がある。これらを解決するために webpack 利用時にあわせて Babel を導入し、ブラウザ間で対応・実行差異のあるシンタックスや API を「サポート対象にしたいブラウザバージョン」に合わせてトランスパイルするのが一般的。つい最近 8 月ごろに最新バージョンの Babel 7 がリリースされており、パッケージ名が @babel/***
になっている。検索の際は 6 系情報と混同しないように注意。
# Install dev dependencies.
$ npm i -D @babel/core @babel/preset-env babel-loader @babel/preset-react
トランスパイルの設定は .babelrc
と呼ばれる設定ファイルで定義するか webpack.config.js
や package.json
に記載する。実際のトランスパイルは webpack の Loader 機能によって実現される。
// .babelrc の記載例
{
presets: [
['@babel/preset-env',
{
// ブラウザバージョンやシェアは https://browserl.ist を参照
targets: "> 0.5% in JP, last 2 versions, Firefox ESR, maintained node versions",
modules: false,
},
],
['@babel/preset-react'],
],
}
module.exports = [
//...
module: {
rules: {
{
test: /\.js$|\.jsx$/,
exclude: /node_modules/,
use: 'babel-loader', // babel-loader を指定
// ちなみに babel-loader?cacheDirectory でキャッシュ有効化
},
},
},
//...
];
@babel/preset-env では PostCSS x Autoprefixer の項で後述する Browserslist のクエリを targets に利用できる。また、既に package.json
や .browserslistrc
で記載されているクエリを採用する場合は以下のように記述する。
// webpack.config.js
// package.json の "browserslist" や .browserslistrc を参照する
module.exports = {
module: {
rules: {
{
test: /\.js$|\.jsx$/,
exclude: /node_modules/,
use: {
loader: 'babel-loader',
options: {
presets: [
['@babel/preset-env', { useBuiltIns: 'entry', corejs: 3, modules: false }],
],
},
},
}
}
}
};
@babel/polyfill
babel-polyfillとbabel-runtimeの使い分けに迷ったので調べた
babel-polyfill / babel-runtime の代わりにcore-jsを直接使うのはアリかナシか
# Install dependencies.
$ npm i -S @babel/polyfill
// index.js
import '@babel/polyfill'; // polyfill 関係は先頭で一度切り読み込む
// webpack.config.jp
module.exports = {
//...
entry: {
js: [
// 複数エントリー時 Polyfill 類は必ず先頭 JS で一度切り読み込む
'./src/index.js',
// または ['@babel/polyfill', './src/index.js'] のようにして
// パッケージを先頭で結合するようなエントリー構造にしてもよい
'./src/another_entry1.js',
'./src/another_entry2.js',
'./src/another_entry3.js',
],
},
//...
};
webpackでbabel際はnode_modulesをexcludeし忘れるべからず
webpackでnode_modulesをbabel-loaderの対象から除外すると、ES2015+で書かれたnpmパッケージのモジュールがBabelで変換されない問題一般的に、npmパッケージのモジュールはES5で書かれている。(ソースがES2015+であってもES5にコンパイルされている)。だが稀にES2015+で書かれたモジュールもある。... 次のように、ES2015+で書かれたモジュールをbabel-loaderの除外対象から除外することで、この問題を回避できる。
module.exports = {
module: {
rules: [
{
test: /\.js$/,
exclude: {
// node_modules 中を除外対象に
include: /node_modules/,
// foo パッケージのモジュールを除外対象から除外
// パターンの末尾に \/ がないと全ての foo が
// 除外対象から除外されてしまうので注意
exclude: /node_modules\/foo\//,
},
use: 'babel-loader',
},
],
},
};
postcss.org
postcss/autoprefixer
最新版で学ぶwebpack 4入門 – スタイルシート(CSSやSass)を取り込む方法
// webpack.config.js の例
const cssnano = require('cssnano')
const autoprefixer = require('autoprefixer')
module.exports = {
module: {
rules: [
{
test: /\.(sa|sc|c)ss$/,
use: [
{ loader: MiniCssExtractPlugin.loader },
{
loader: 'css-loader',
options: {
// Deny fetching resource by url().
url: false,
importLoaders: 2, // For PostCSS + Sass.
sourceMap: (process.env.npm_lifecycle_event === 'build'),
},
},
{
loader: 'postcss-loader',
options: {
plugins: [
// cssnano({ autoprefixer: false }),
autoprefixer(),
].concat(
// concat() に空配列を渡すと結合元配列のまま変わらないことを利用して
// build ( 本番向けビルド ) 時のみ cssnano による minify が実行されるように
(process.env.npm_lifecycle_event === 'build')
? [cssnano({ autoprefixer: false })]
: []
),
sourceMap: (process.env.npm_lifecycle_event === 'build'),
},
},
{
loader: 'sass-loader',
options: {
importer: globImporter(),
sourceMap: (process.env.npm_lifecycle_event === 'build'),
},
},
],
},
],
},
};
browserl.ist -
.browserslistrc
のテストアプリ
browserslist/browserslist - github.com
Autoprefixerの対象ブラウザの選び方
Autoprefixer は Can I Use のブラウザ・サポート情報と StatCounter の全世界のブラウザ利用状況データを参照して Browserslist の記述に当てはまるブラウザを抽出します。
PostCSS の Autoprefixer プラグインや Babel などの サポート対象 ( トランスパイル時の考慮対象 ) として .browserslistrc
を package.json
と同階層に設置し、サポート対象ブラウザを指定する仕様になっている。JSON 形式で package.json
に書いても良いみたい。
記載方法と対象ブラウザの確認は browserl.ist より。とりま > 0.5% in JP
しとけば日本の 90 % 程度はカバーできる。デフォルトは > 1%, last 2 versions, Firefox ESR
みたい。
# .browserslistrc
> 0.5% in JP
last 2 versions
Firefox ESR
maintained node versions
{
"browserslist": [
"> 0.5% in JP",
"last 2 versions",
"Firefox ESR",
"maintained node versions"
]
}
バンドルしてコンパイルした CSS / Scss / Sass を bundle.js におまとめせず、別ファイル ( style.css ) などに切り出して出力するための整形プラグイン。かつての extract-text-webpack-plugin が担っていたシゴトだが、webpack 4 系からこちらにシフトしたみたい。これに伴い style-loader も不要になった。
$ npm i -D mini-css-extract-plugin
// webpack.config.js
const MiniCssExtractPlugin = require('mini-css-extract-plugin');
module.exports = {
entry: {
bundle: [
'./src/index.js',
'./src/index.scss',
],
},
output: {
path: path.join(__dirname, 'public/js'),
filename: '[name].js',
},
module: {
rules: [
{
test: /\.(sa|sc|c)ss$/,
use: [
{ loader: MiniCssExtractPlugin.loader },
'css-loader',
'postcss-loader'
'sass-loader',
],
},
],
},
plugins: [
new MiniCssExtractPlugin({
// パス起点が module.exports.output.path なので注意
filename: '../css/style.css',
}),
],
};
Browsersync - browsersync.io
Va1/browser-sync-webpack-plugin - github.com
webpack-dev-serverからbrowsersyncへ
はじめてのbrowserSync
Browsersync で快適なブラウザチェック環境を作る
# 追記
browser-sync-webpack-plugin の更新古いし webpack-dev-server 抜いて、並列で browsersync 単独で立てた方が管理上も楽な気がしてきた。
webpack は development モードで立ち上げた際に自動的に Watch モードになりバンドル対象ファイルの更新を監視 → 更新時に再ビルドするようになる。一般的な構成ではここに webpack-dev-server を導入し、再ビルド時にブラウザをライブリロードしたりする。
Browsersync はこの webpack-dev-server と同じ趣旨のツールで、ローカルに開発用プロキシサーバを作り別途設定した監視対象の「更新時ライブリロード」や「リモートデバッグ ( 実機テスト・デバッグ ) 」などを提供する。webpack と BrowserSync の連携には BrowserSyncPlugin が必要。設定は webpack.config.js に記載する。
$ npm i -D browser-sync-webpack-plugin
new BrowserSyncPlugin({
host: 'localhost',
port: 3000,
cors: true,
open: false,
proxy: {
target: 'https://192.168.99.100',
cookies: { stripeDomain: false },
ws: false, // アプリが WebSocket を利用している際は true へ
},
reloadDelay: 0, // 適宜調整
watchOptions: {
awaitWriteFinish : true, // JS 更新時にビルドを待ってリロード
},
files: [
// 【注意】ここに存在しないパスを書くと正常に動かないよ!!
'app/**/*.rb',
'app/**/*.erb',
'config/**/*.rb',
'test/**/*.rb',
'src/**/*.js',
'public/css/*.css',
]
},
{
// CSS / Sass 更新を Inject するため
reload: false,
injectCss: true,
})
この設定では BrowserSync はただのプロキシサーバなので CSS Inject されない JavaScript や、WEB サーバサイドでキャッシュされているテンプレートエンジンファイルの更新など、ブラウザいつも通りキャッシュの影響を受けてしまうことを忘れない。 よって Chrome の DevTools で Network の Disable Cache を true にするなど、キャッシュ対策は別途行うこと。
ローカルの Static な HTML/CSS/JS だけで完結する静的 WEB ページや、SPA などを開発する際には proxy
で VM の IP を指定せず、ローカルのディレクトリおよび index を server
に指定してやれば、指定したディレクトリを起点とした開発用 WEB サーバをたててくれる。
server: {
baseDir: '.',
index: 'index.html',
}
Browsersync は localhost に開発用のプロキシサーバを立てる際に、ホスト端末の「最優先なプライベートネットワークカード」を自動検出し、この同一 LAN へ向けた「プロキシサーバのポート解放」を行う。この External URL ( = プライベートネットワーク向け解放アドレス ) に同一 LAN 上からアクセスすることで Charles のように「実機など外部端末 → ホスト端末プロキシサーバ → ゲスト VM 上 WEB サーバ」へ接続できる。
[Browsersync] Proxying: http://192.168.33.10
[Browsersync] Access URLs:
----------------------------------
Local: http://localhost:3000 # ローカル開発マシンからのアクセス
External: http://10.0.1.7:3000 # 同一ネットワーク上からのアクセス
----------------------------------
UI: http://localhost:3001 # Browsersync 管理画面へのアクセス
UI External: http://10.0.1.7:3001 # 管理画面へのネットワーク上からのアクセス
----------------------------------
[Browsersync] Watching files...
稀に External のアドレスが異常な値になっていることがあるが、大体は localhost
の設定ミスか、仮想 VM 用のネットワークカードの優先順位がイーサネットに勝ってしまっていることが原因。ネットワークカードの優先順位は メトリック値 の設定で変更できる。
react-hot-loader - github.com / react-hot-loader - npmjs.com
BrowserSync/recipes
webpack, React Hot Loader + Browsersync でクロスブラウジング+ホットリロード開発
index.html
から JS を読む SPA 構成のアプリ開発時はこういう構成もできるみたい。Proxy として利用している場合のナレッジはないのかな ... 。
Error: no parsers registered for: "]a(r)"
みたいなこといってコケるときは ignored
と files
の指定がおかしい。
new BrowserSyncPlugin({
// ...
watchOptions: {
// ignored: ['vendor'], // 理由は不明だがこれだと NG
ignored: ['laravel/vendor'], // こっちなら大丈夫 ... なぜ
},
files: [
'laravel/**/*.php',
],
})
BrowserSync を利用している際に、稀に Chrome でプロキシを介さないリクエストは問題ないのに、プロキシを介した localhost
なアクセスの POST ( GET 以外の ) リクエストがうんともすんとも言わなくなるケースがある。プロジェクト間を行き来した際に「さっきまで見てた localhost
と今の localhost
が別物」なときに起きる印象。
スーパーリロード や chrome://restart
や Cookie / サイトデータ削除なども効果がなく、Chrome のリクエスト処理が内部で延々と終わらずにハングアップしているようにも見受けられる。
プロキシを介さずに一度何かしらの POST 処理 ( ログインなど ) を行い、そののちに localhost
( BrowserSync ) へアクセスすると改善されることがある。 なぜかは不明。
moment.js のロケールファイルなど、プロダクション環境で全く利用しないものも一緒にバンドルされてしまうとファイルサイズが重たくなってしまう。IgnorePlugin でパッケージの特定ディレクトリを無視するような設定が可能。
plugins: [
// moment.js では多くの場合デフォルトの en 以外要らない
new webpack.IgnorePlugin(/^\.\/locale$/, /moment$/),
]
// 必要になった場合はまた別途 index.js 以下で呼べばよろしい
//
const moment = require('moment');
require('moment/locale/ja');
moment.locale('ja');
いつまでimport Reactしているつもり? Webpack-ProviderPlugin
暗黙で全てのjsファイルにモジュールをimportする
クラスのオートロード。よく使うやつは予め変数に代入しておくと各ファイルで import
ぶっ叩かなくて済む。
// webpack.config.js
// import ClassName from 'package-name';
// ↓
// ClassName: 'package-name'
// import { ClassName } from 'package-name';
// ↓
// ClassName: ['package-name', 'ClassName']
plugins: [
new webpack.ProvidePlugin({
$: 'jquery',
jQuery: 'jquery',
'window.$': 'jquery,
'window.jQuery': 'jquery,
moment: 'moment',
React: 'react',
ReactDOM: 'react-dom',
PropTypes: 'prop-types',
Component: ['react', 'Component'],
})
]
Node.js ランタイムの process.env
に .env
の Key-value 入れてくれるやつ。環境変数の使い所は Twelve-Factor App あたり読んでよく考えてね。
$ npm i -D dotenv
// webpack.config.js
require('dotenv').config(); // config() 実行で .env ファイルを読込
console.log(process.env.HOGE_ENV); // 以降は process.env から参照可能
node-sass
利用プロジェクトでは、ビルドしたマシンのアーキテクチャやら環境やらが違うとリビルドしろって怒られる。
$ npm rebuild node-sass
上記で Python2 がないよ、みたいなエラーが出る場合は、恐らくプロジェクトの node-sass
が古すぎる ( 実行環境の node.js が新しすぎる ) ため必要なビルドパッケージ群を取得しきれずリビルドに失敗している。Windows 環境なら windows-build-tools をぶち込むとリビルドが通るかも。
$ npm i -g windows-build-tools # 2 ~ 30 分くらいかかるかも
$ npm rebuild node-sass # ビルドに必要なパッケージを死ぬほどビルドしまくって無理くり通す
FontAwesome などの WEB フォントを含むものや、CSS フレームワーク内部に画像バイナリを抱えているような奴は url-loader
や file-loader
を通してやればエンドポイントのメイン CSS をエントリーポイントで import
するだけで全部ぶち丸めて JS 化してくれる ( 怖 ) 。
// webpack.config.js
{
test: /\.css$/,
include: /node_modules/,
use: [
{ loader: 'style-loader' },
{ loader: 'css-loader' },
],
},
{
test: /\.(png|jpe?g|gif|svg)(\?.*)?$/,
use: 'url-loader',
},
{
test: /\.(woff2?|eot|ttf|otf)(\?.*)?$/,
use: 'url-loader',
},
// index.js
import 'font-awesome/css/font-awesome.min.css'
module.exports = [
{
//...
performance: {
hints: false
},
//...
}
]
Error: EPERM: operation not permitted, stat 'D:\git\my_app\app\vendor\bin\apigen'
Composer で apigen を利用しているプロジェクトで上記のようなパーミッションエラーが発生して watch がコケていた。Windows のファイルシステムが上記ファイルをロックしていて ( プロセスがゾンビだった? ) watch できなかったよう。端末によっては同環境でも watch できた。
解決策として、メモリキャッシュ npm cache clear -f
やら Composer による vendor の再インストールやら npm で node_modules の再インストールやら試したが、結局 BrowserSync の watch 対象から当該ファイルを外すことで解決した。