webpackの設定で困ったこと集
webpack-dev-server
が立ち上がらない!!
Udemyの『最短で学ぶReactとReduxの基礎から実践まで』を学習していてwebpackのサーバーが立ち上がらなかったので備忘録。
そもそも講義で使っているwebpackのバージョンは3で、私が導入したバージョンは5だった。
講義と同様のバージョンにすれば解決すると思うが、どうしてもwebpack 5を使いたかったため色々ググった。
webpackのバージョンは以下の通り。
"dependencies": {
"webpack": "^5.65.0",
"webpack-cli": "^4.9.1",
"webpack-dev-server": "^4.7.2"
}
余計なオプションが入っていたことが原因
講義のdevServer
はhistoryApiFallback
とcontentBase
が設定されていた。
webpack 5ではこれだけでは足らずstatic
の設定も必要になる。
devServer: {
historyApiFallback: true,
contentBase: path.resolve(__dirname, 'public'),
},
static
を追加して実行!
devServer: {
historyApiFallback: true,
contentBase: path.resolve(__dirname, 'public'),
+ static: './public',
},
動かない…
devServer: {
- historyApiFallback: true,
- contentBase: path.resolve(__dirname, 'public'),
static: './public',
},
試しにhistoryApiFallback
とcontentBase
を消してみると…
動きました!
create-react-appせずにwebpack × React × TypeScriptの開発環境を構築する
まずは初期化
npm init
npm install --save-dev webpack webpack-cli babel-loader @babel/core @babel/preset-env typescript ts-loader
npm install react react-dom @types/react @types/react-dom
その他のライブラリをインストールする
npm install react-redux react-router-dom@5.3.0 @types/react-redux @types/react-router-dom
webpack.config.js
を書く
下記リンクの設定をそのまま写経
module.exports = {
mode: 'production',
entry: './src/index.jsx',
output: {
path: `${__dirname}/public`,
filename: 'main.js',
},
module: {
rules: [
{
test: /\.js$/,
use: [
{
loader: 'babel-loader',
options: { presets: ['@babel/preset-env', '@babel/react'] },
},
],
},
],
},
target: ['web', 'es5'],
devServer: {
port: 3000,
},
};
そして、package.json
にscrips
を登録
"build": "webpack"
そしてコマンド実行
npm run build
するとエラーが…
Invalid configuration object. Webpack has been initialized using a configuration object that does not match the API schema.
- configuration.module.rules[0].use[0] has an unknown property 'option'. These properties are valid:
object { ident?, loader?, options? }
単にタイポミスでした。
options
(複数形)のところがoption
(単数形)になっていました。
そして再度ビルド
assets by status 1.27 KiB [cached] 1 asset
./src/main.js 39 bytes [built] [code generated] [1 error]
ERROR in ./src/main.js
Module build failed (from ./node_modules/babel-loader/lib/index.js):
Error [ERR_MODULE_NOT_FOUND]: Cannot find package '@babel/preset-react' imported from /Users/hrkmtsmt/Projects/redux-todo/babel-virtual-resolve-base.js
at new NodeError (/Users/hrkmtsmt/Projects/redux-todo/node_modules/@babel/core/lib/vendor/import-meta-resolve.js:2552:5)
at packageResolve (/Users/hrkmtsmt/Projects/redux-todo/node_modules/@babel/core/lib/vendor/import-meta-resolve.js:3208:9)
at moduleResolve (/Users/hrkmtsmt/Projects/redux-todo/node_modules/@babel/core/lib/vendor/import-meta-resolve.js:3242:18)
at defaultResolve (/Users/hrkmtsmt/Projects/redux-todo/node_modules/@babel/core/lib/vendor/import-meta-resolve.js:3281:13)
at /Users/hrkmtsmt/Projects/redux-todo/node_modules/@babel/core/lib/vendor/import-meta-resolve.js:3304:14
at Generator.next (<anonymous>)
at asyncGeneratorStep (/Users/hrkmtsmt/Projects/redux-todo/node_modules/@babel/core/lib/vendor/import-meta-resolve.js:63:103)
at _next (/Users/hrkmtsmt/Projects/redux-todo/node_modules/@babel/core/lib/vendor/import-meta-resolve.js:65:194)
at /Users/hrkmtsmt/Projects/redux-todo/node_modules/@babel/core/lib/vendor/import-meta-resolve.js:65:364
at new Promise (<anonymous>)
webpack 5.69.1 compiled with 1 error in 850 ms
またエラーが…
何やねんこれ。
よくよく読むと「@babel/preset-react
が見つかりません!」ってなってる。
ので、インストール。
npm install --save-dev @babel/preset-react
ビルドコマンドを実行。
ちゃんとコンパイルされてビルドできました。
webpack-dev-server
を立ち上げるコマンドを登録する
なにはともあれ、webpack-dev-server
をインストール。
npm install --save-dev webpack-dev-server
package.json
のscripts
に以下のコマンドを登録。
"start": "run webpack-dev-server"
webpack.config.js
の設定
サーバーをpublic
を起点にして、ポートを3000番に指定。
devServer: {
contentBase: path.join(__dirname, 'public'),
port: 3000,
},
publicディレクトリにindex.htmlを用意する
<html lang="ja">
<head>
<meta charset="UTF-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Redux ToDo</title>
</head>
<body>
<div id="root" class="div"></div>
<script defer src="main.js"></script>
</body>
</html>
srcディレクトリにindex.jsxを用意して、適当なコンポーネントをインポートします。
import React from 'react';
import ReactDOM from 'react-dom';
import './index.css';
import App from './App';
ReactDOM.render(<App />, document.getElementById('root'));
そしてビルド。
するとエラーが…
ERROR in ./src/index.jsx 5:16
Module parse failed: Unexpected token (5:16)
You may need an appropriate loader to handle this file type, currently no loaders are configured to process this file. See https://webpack.js.org/concepts#loaders
| import App from './App';
|
> ReactDOM.render(<App />, document.getElementById('root'));
|
webpack 5.69.1 compiled with 1 error in 2273 ms
原因はmodule.rulesのtestの記述が問題だったようです。
そこにjsxも含めます。
module: {
rules: [
{
- test: /\.(js)$/,
+ test: /\.(js|jsx)$/,
},
],
}
babel-loaderとts-loaderを併用する
TypeScriptはbabel-loaderでコンパイルできる。
ただし、ブラウザによってbabel-loaderとts-loaderを使い分けたい場合は設定が必要。
WARNINGエラーを解決する
WARNING in asset size limit: The following asset(s) exceed the recommended size limit (244 KiB).
This can impact web performance.
Assets:
main.js (245 KiB)
WARNING in entrypoint size limit: The following entrypoint(s) combined asset size exceeds the recommended limit (244 KiB). This can impact web performance.
Entrypoints:
main (245 KiB)
main.js
WARNING in webpack performance recommendations:
You can limit the size of your bundles by using import() or require.ensure to lazy load some parts of your application.
For more info visit https://webpack.js.org/guides/code-splitting/
webpack-dev-serverを立ち上げてコンパイルは成功しているが404が返ってくる
結論、output.pathとdevServer.static.directoryのパスの指定がミスっていた。
単純ミス…
> redux-todo@1.0.0 start
> webpack serve --open
<i> [webpack-dev-server] Project is running at:
<i> [webpack-dev-server] Loopback: http://localhost:3000/
<i> [webpack-dev-server] On Your Network (IPv4): http://192.168.89.5:3000/
<i> [webpack-dev-server] On Your Network (IPv6): http://[fe80::1]:3000/
<i> [webpack-dev-server] Content not from webpack is served from '/Users/{ user }/Projects/redux-tododist' directory
<i> [webpack-dev-middleware] wait until bundle finished: /
asset main.js 245 KiB [emitted] [minimized] [big] (name: main) 1 related asset
runtime modules 27.2 KiB 12 modules
orphan modules 17.3 KiB [orphan] 8 modules
modules by path ./node_modules/ 397 KiB
modules by path ./node_modules/webpack-dev-server/client/ 63.1 KiB 5 modules
modules by path ./node_modules/webpack/hot/*.js 4.4 KiB 4 modules
modules by path ./node_modules/html-entities/lib/*.js 115 KiB 4 modules
modules by path ./node_modules/react/ 9.02 KiB 2 modules
modules by path ./node_modules/react-dom/ 177 KiB 2 modules
modules by path ./node_modules/scheduler/ 7.24 KiB 2 modules
+ 3 modules
./src/index.tsx + 1 modules 322 bytes [built] [code generated]
webpack 5.69.1 compiled successfully in 6715 ms
^C<i> [webpack-dev-server] Gracefully shutting down. To force exit, press ^C again. Please wait...
一見成功しているように見えるが、8行目にヒントが…
<i> [webpack-dev-server] Content not from webpack is served from '/Users/{ user }/Projects/redux-tododist' directory
本来redux-todo/distであるべきところがredux-tododistに…
原因は、パスの連結を+
でやっていたことだった。
webpack.config.jsに以下の内容を追加
+ const path = require('path')
// ...(省略)
- path: __dirname + 'dist'
+ path: path.join(__dirname, 'dist')
dist
を/dist
にしても良いですが、記述漏れがあるとまたエラーになるので、pathライブラリを使ったほうが堅実。
最初はpathライブラリを使っていたが、なんか同じようなエラーが出たので+
を使ったのだが、もう一度pathライブラリを使ったらエラーが消えた。
なんかの勘違いか幻想か…
教訓
- ログが成功っぽくても実はヒントが隠されている
- pathライブラリを使え
TypeScriptがコンパイルされない
ts-loader
が設定されていなかったのが原因だった…
その前にloaderを設定するuseの実行順を理解する
webpackのローダーを設定するuse
になっています。
この配列は後ろから順に実行されるようになっています。
下記の様な配列の場合、配列最後尾の5から順に実行され、左に5→4→3→2→1の順番で実行されます。
use: [1, 2, 3, 4, 5]
さて本題、ts-loaderをwebpackに設定する
エラーが出たときのwebpackの設定は下記の通り。
babel-loaderのみが設定されています。
use: [
{
loader: 'babel-loader',
options: { presets: ['@babel/preset-env', '@babel/react'] },
},
],
これに、ts-loaderを追加していきます。
今の所、特にオプションは必要ないので、ts-loader
とだけ記述します。
use: [
{
loader: 'babel-loader',
options: { presets: ['@babel/preset-env', '@babel/react'] },
},
+ 'ts-loader'
],
なぜts-loaderから先に実行しなければ行けないのか
それは、TypeScriptをJavaScriptにコンパイルしたあとに、BabelでES5のJacaScriptにトランスパイルする必要があるから。
Babelに素のJavaScriptを渡す必要があるから、ts-loaderを先に通す必要がるわけです。