webpack5についての勉強 (react + less + eslint + babel + 静的ファイル)
bundleとは?
プログラムのソースコードをコンパイル、圧縮、文法チェック、互換性処理をブラウザで実行可能の1つのファイルに最適な形でまとめること
webpackとは?
- webpackはbundleツールの一つです。
ほかに
Vite
やgulp
などもあります。ちなみに今はVue3
が使ってるVite
がWebpack
より性能が良いらしいけど、まだ不安定みたいです。
- webpackでは、フロントエンドのすべてのリソースファイル(js/json/css/img/less)がモジュールとして扱われます。
- モジュール内の全てのファイルの依存度関係に基づいて静的分析を行い、それぞれを1つのファイルにまとめます。
webpackの五つの重要な概念
-
Entry :
入口(entry point) webpackがどのモジュールを使用するべきかを示し、そこからその内部依存関係の構築をする。 -
Outout :
output属性はwebpackに対して、作成したbundlesをどこで出力しますかを示す。また、これらのファイルの名前はどうなりますかを指定する。デフォルトは./dist
-
Loader :
loaderを使うことでwebpackに非javascript/json
ファイルを処理させます(webpack自身はjavascript/json
を解析することしかできません) -
Pugins :
プラグインは、より広い範囲のタスクを実行するために使用できます。(bundle性能アップ、環境変数の再定義とか) -
Mode :
開発モード、本番用モード
ゼロから始めるwebpack
初期化プロジェクト
mkdir webpack-handson
cd webpack-handson
npm init
package.jsonを生成する
デフォルト設定で以下のもの生成しました。
{
"name": "webpack-handson",
"version": "1.0.0",
"description": "",
"main": "index.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"author": "",
"license": "ISC"
}
webpackをインストール
npm install webpack webpack-cli -g
npm install webpack webpack-cli -D
後でコマンドとしての実行するため、グローバルもインストールが必要
bundleするものを作成する
- jsファイルの作成
src/js/index.js
// js
import {sum} from './module1'
import {sub} from './module2'
import module3 from "./module3";
// json
import json from '../json/test';
console.log(sum(1,2));
console.log(sub(4,6));
console.log(module3.mul(5,6));
console.log(module3.div(10,5));
console.log(json,typeof json);
src/js/module1.js
export function sum(a,b){
return a+b;
}
src/js/module2.js
function sub(a,b) {
return a-b;
}
export {sub}
src/js/module3.js
export default {
mul(a,b) {
return a*b;
},
div(a,b) {
return a/b;
}
}
- jsonファイルの作成
src/json/test.json
{
"name": "kobe",
"age": 18
}
- index.htmlの作成
src/index.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>demo</title>
</head>
<body>
<script type="text/javascript" src="../dist/js/main.js"></script>
<h1>hello webpack</h1>
</body>
</html>
ここまでの構成
webpack
├─package.json
├─src
| ├─index.html
| ├─json
| | └test.json
| ├─js
| | ├─index.js
| | ├─module1.js
| | ├─module2.js
| | └─module3.js
webpackコマンドでソースコード(js, json)をbundleする
webpack ./src/js -o ./dist/js --mode=development
webpackはjsとjsonのファイルをbundleして、そしてes6モジュール文法をブラウザで識別できる文法に変換することもできます。
index.htmlを開きF12で以下のもの確認できたらオッケー
less-loaderで.lessをbundleする
- lessファイルの作成
src/css/index.less
.demo{
width: 400px;
height: 400px;
background-color: red;
}
- jsファイルの修正:lessのimport
src/js/index.less
// js
import {sum} from './module1'
import {sub} from './module2'
import module3 from "./module3";
// json
import json from '../json/test';
// less
import '../css/index.less'
console.log(sum(1,2));
console.log(sub(4,6));
console.log(module3.mul(5,6));
console.log(module3.div(10,5));
console.log(json,typeof json);
- htmlファイルの修正:lessを適用する
src/index.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>demo</title>
</head>
<body>
<script type="text/javascript" src="../dist/js/index.js"></script>
<h1>test</h1>
<div class="demo"></div>
</body>
</html>
このままでwebpack ./src/js -o ./dist/js --mode=development
を実行すると下記エラーが発生しちゃう
loaderの紹介でも話したがwebpack自体はjs/jsonしか扱えない。lessを適用するためにloaderの設定が要ります。
- とりあえず必要なloaderをインストール
npm i css-loader style-loader less-loader less -D
- webpack.config.jsを設定します。
src/webpack.config.js
let {resolve} = require('path');
// entry
module.exports = {
entry:'./src/js/index.js',
output:{
path:resolve(__dirname,'dist/js'),
filename: "index.js",
},
module: {
rules: [
// less-loader
{
test: /\.less$/, // すべてのlessファイルをmatch
use: [ // 配列内のloaderは実行順番が後ろから前 つまりless > css > styleの順番です
"style-loader", // 役割:HTMLファイルにstyleタグを作りスタイルを入れる
"css-loader", // 役割:cssをCommentJsモジュールに変換
"less-loader" // 役割:lessをコンパイルしcssに変換する、cssファイル生成せず、メモリ内に保存する
],
},
]
},
plugins: [],
mode:'production',
}
- コマンドでソースコードをbundleする
webpack
ここまでの構成
webpack-handson
├─package-lock.json
├─package.json
├─webpack.config.js
├─src
| ├─index.html
| ├─json
| | └test.json
| ├─js
| | ├─index.js
| | ├─module1.js
| | ├─module2.js
| | └module3.js
| ├─css
| | └index.less
├─dist
| ├─js
| | └index.js
この画面を確認できたらlessの部分は完了
eslint-loaderで静的解析の実装
- とりあえず必要なloaderをインストール
npm i eslint-loader eslint -D
- webpack.config.jsの設定を追加します。
src/webpack.config.js
let {resolve} = require('path');
// entry
module.exports = {
entry:'./src/js/index.js',
output:{
path:resolve(__dirname,'dist/js'),
filename: "index.js",
},
module: {
rules: [
// less-loader
{
test: /\.less$/, // すべてのlessファイルをmatch
use: [ // 配列内のloaderは実行順番が後ろから前 つまりless > css > styleの順番です
"style-loader", // 役割:HTMLファイルにstyleタグを作りスタイルを入れる
"css-loader", // 役割:cssをCommentJsモジュールに変換
"less-loader" // 役割:lessをコンパイルしcssに変換する、cssファイル生成せず、メモリ内に保存する
],
},
// eslint-loader
{
test:/\.js$/, // すべてのjsファイルをmatch
exclude:/node_modules/, // node_modulesフォルダ内のものはEslintでチェックしない
enforce:"pre", // webpackによるbundleする前に実施
use:{
loader:"eslint-loader",
},
},
]
},
plugins: [],
mode:'production',
}
- package.jsonの追加設定(eslint configの設定)
package.json
{
"name": "webpack-handson",
"version": "1.0.0",
"description": "",
"main": "index.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"author": "",
"license": "ISC",
"devDependencies": {
"css-loader": "^5.2.0",
"eslint": "^7.23.0",
"eslint-loader": "^4.0.2",
"less": "^4.1.1",
"less-loader": "^8.0.0",
"style-loader": "^2.0.0",
"webpack": "^5.30.0",
"webpack-cli": "^4.6.0"
},
"eslintConfig": {
"parserOptions": {
"ecmaVersion": 6,
"sourceType": "module"
},
"env": {
"browser": true,
"node": true
},
"globals": {
"$": "readonly",
"Promise": "readonly"
},
"rules": {
"no-console": 0,
"eqeqeq": 0,
"no-alert": 0
},
"extends": "eslint:recommended"
}
}
- コマンドでソースコードをbundleする。
webpack
文法に問題なければbundle行けるはず。
ps. eslint通らなかったの例
src/js/index.js
に適当に定数を作る。下記のようなエラーでbundleできないはず
babel-loaderで古いブラウザをサポートする
- とりあえず必要なloaderをインストール
npm i babel-loader @babel/core @babel/preset-env core-js -D
- webpack.config.jsの設定を追加します。
src/webpack.config.js
let {resolve} = require('path');
// entry
module.exports = {
entry:'./src/js/index.js',
output:{
path:resolve(__dirname,'dist/js'),
filename: "index.js",
},
module: {
rules: [
// less-loader
{
test: /\.less$/, // すべてのlessファイルをmatch
use: [ // 配列内のloaderは実行順番が後ろから前 つまりless > css > styleの順番です
"style-loader", // 役割:HTMLファイルにstyleタグを作りスタイルを入れる
"css-loader", // 役割:cssをCommentJsモジュールに変換
"less-loader" // 役割:lessをコンパイルしcssに変換する、cssファイル生成せず、メモリ内に保存する
],
},
// eslint-loader
{
test:/\.js$/, // すべてのjsファイルをmatch
exclude:/node_modules/, // node_modulesフォルダ内のものはEslintでチェックしない
enforce:"pre", // webpackによるbundleする前に実施
use:{
loader:"eslint-loader",
},
},
// es6 let/constなどを古いブラウザも処理させるように
{
test: /\.m?js$/,
exclude: /node_modules/,
use: {
loader: 'babel-loader',
options: {
// babelでどんな交換性処理を設定する
presets: [
['@babel/preset-env', { targets: "defaults" }]
]
}
}
},
// browser core promiseなどの対応
{
test: /\.js$/,
exclude: /node_modules/,
use: {
loader: 'babel-loader',
options: {
// babelでどんな交換性処理を設定する
presets: [
[
'@babel/preset-env',
{
useBuiltIns: 'usage',
corejs: {
version: 3
},
targets: {
chrome: '60', // chrome60バージョン以降のブラウザを対応する
firefox: '60',
ie: '11',
safari: '10',
edge: '17'
}
}
]
],
cacheDirectory:true,
}
}
},
]
},
plugins: [],
mode:'production',
}
url-loader, file-loaderで画像ファイルなどの静的ファイルをbundleする
- とりあえず必要なloaderをインストール
npm i file-loader url-loader -D
- スタイルを修正する(背景を画像にする)
src/css/index.less
先ずはsrc下にassetフォルダを作り適当に画像を入れる
次からlessの修正
.demo{
width: 400px;
height: 400px;
background-image: url("../asset/test_1.png");
}
- webpack.config.jsの設定を追加します。
src/webpack.config.js
let {resolve} = require('path');
// entry
module.exports = {
entry:'./src/js/index.js',
output:{
path:resolve(__dirname,'dist/js'),
filename: "index.js",
},
module: {
rules: [
// less-loader
{
test: /\.less$/, // すべてのlessファイルをmatch
use: [ // 配列内のloaderは実行順番が後ろから前 つまりless > css > styleの順番です
"style-loader", // 役割:HTMLファイルにstyleタグを作りスタイルを入れる
"css-loader", // 役割:cssをCommentJsモジュールに変換
"less-loader" // 役割:lessをコンパイルしcssに変換する、cssファイル生成せず、メモリ内に保存する
],
},
// eslint-loader
{
test:/\.js$/, // すべてのjsファイルをmatch
exclude:/node_modules/, // node_modulesフォルダ内のものはEslintでチェックしない
enforce:"pre", // webpackによるbundleする前に実施
use:{
loader:"eslint-loader",
},
},
// es6 let/constなどを古いブラウザも処理させるように
{
test: /\.m?js$/,
exclude: /node_modules/,
use: {
loader: 'babel-loader',
options: {
// babelでどんな交換性処理を設定する
presets: [
['@babel/preset-env', { targets: "defaults" }]
]
}
}
},
// browser core promiseなどの対応
{
test: /\.js$/,
exclude: /node_modules/,
use: {
loader: 'babel-loader',
options: {
// babelでどんな交換性処理を設定する
presets: [
[
'@babel/preset-env',
{
useBuiltIns: 'usage',
corejs: {
version: 3
},
targets: {
chrome: '60', // chrome60バージョン以降のブラウザを対応する
firefox: '60',
ie: '11',
safari: '10',
edge: '17'
}
}
]
],
cacheDirectory:true,
}
}
},
//url-loader 画像処理 file-loaderと比べると base64に変換するのが可能
{
test: /\.(png|jpg|gif)$/,
use: [
{
loader: 'url-loader',
options: {
limit:8192,
publicPath:'images/',
outputPath:'images',
name:'[hash:5].[ext]',
},
}
]
},
//file-loader
{
test:/\.(eot|svg|woff|woff2|ttf|mp3|mp4|avi)$/,
loader:'file-loader',
options: {
outputPath:'media',
name:'[hash:5].[ext]'
}
},
]
},
plugins: [],
mode:'production',
}
ここまで設定できたらwebpackの実行成功するけど、入れた画像は表示できない。
なぜならwebpackはhtmlをbundleできない。ここでPluginsを加入します。
HTMLWebpackPluginでhtmlファイルをbundleする
- 先ずはpluginをインストールする
npm i html-webpack-plugin -D
- webpack.config.jsの設定を追加します。
src/webpack.config.js
const {resolve} = require('path');
const HTMLWebpackPlugin = require('html-webpack-plugin');
module.exports = {
entry:'./src/js/index.js',
output:{
path:resolve(__dirname,'dist'),
filename: "js/index.js",
},
module: {
rules: [
// less-loader
{
test: /\.less$/, // すべてのlessファイルをmatch
use: [ // 配列内のloaderは実行順番が後ろから前 つまりless > css > styleの順番です
"style-loader", // 役割:HTMLファイルにstyleタグを作りスタイルを入れる
"css-loader", // 役割:cssをCommentJsモジュールに変換
"less-loader" // 役割:lessをコンパイルしcssに変換する、cssファイル生成せず、メモリ内に保存する
],
},
// eslint-loader
{
test:/\.js$/, // すべてのjsファイルをmatch
exclude:/node_modules/, // node_modulesフォルダ内のものはEslintでチェックしない
enforce:"pre", // webpackによるbundleする前に実施
use:{
loader:"eslint-loader",
},
},
// es6 let/constなどを古いブラウザも処理させるように
{
test: /\.m?js$/,
exclude: /node_modules/,
use: {
loader: 'babel-loader',
options: {
// babelでどんな交換性処理を設定する
presets: [
['@babel/preset-env', { targets: "defaults" }]
]
}
}
},
// browser core promiseなどの対応
{
test: /\.js$/,
exclude: /node_modules/,
use: {
loader: 'babel-loader',
options: {
// babelでどんな交換性処理を設定する
presets: [
[
'@babel/preset-env',
{
useBuiltIns: 'usage',
corejs: {
version: 3
},
targets: {
chrome: '60', // chrome60バージョン以降のブラウザを対応する
firefox: '60',
ie: '11',
safari: '10',
edge: '17'
}
}
]
],
cacheDirectory:true,
}
}
},
//url-loader 画像処理 file-loaderと比べると base64に変換するのが可能
{
test: /\.(png|jpg|gif)$/,
use: [
{
loader: 'url-loader',
options: {
limit:8192,
publicPath:'images/',
outputPath:'images',
name:'[hash:5].[ext]',
},
}
]
},
//file-loader
{
test:/\.(eot|svg|woff|woff2|ttf|mp3|mp4|avi)$/,
loader:'file-loader',
options: {
outputPath:'media',
name:'[hash:5].[ext]'
}
},
]
},
plugins: [
new HTMLWebpackPlugin({
template: "./src/index.html",
})
],
mode:'production',
}
修正した箇所:
const HTMLWebpackPlugin = require('html-webpack-plugin');
module.exports = {
...
output:{
path:resolve(__dirname,'dist'),
filename: "js/index.js",
},
...
plugins: [
new HTMLWebpackPlugin({
template: "./src/index.html", //このパスを基にhtmlファイルを生成しbundleしたものを自動的に入れる
})
],
...
}
- webpackはloaderが自動的にインポートされますが、pluginは自分でインポートしないと効かない。
importとrequireの違い
- requireはcommonJSつまりNodejsの環境で動作してくれる書き方です。
- importはes6の書き方
- outputを修正したのでbundleで生成したdistの構成も綺麗になった
webpackを実行して結果を確認しましょう
webpack
ここまでの構成
webpack-handson
├─package-lock.json
├─package.json
├─webpack.config.js
├─src
| ├─index.html
| ├─json
| | └test.json
| ├─js
| | ├─index.js
| | ├─module1.js
| | ├─module2.js
| | └module3.js
| ├─css
| | └index.less
| ├─asset
| | └test_1.png
├─dist
| ├─index.html
| ├─js
| | └index.js
| ├─images
| | └dd2d1.png
distフォルダ下のindex.htmlを開いて結果を確認しましょう
これ確認できたらオッケー
devServerで開発効率アップ
webpackコマンドで毎回毎回打ち込むのが面倒ので、コマンドを実行しなくてもブラウザで結果を確認したいなぁ。
- インストールから
npm i webpack-dev-server -D
- webpack.config.jsの設定を追加します。
src/webpack.config.js
const {resolve} = require('path');
const HTMLWebpackPlugin = require('html-webpack-plugin');
module.exports = {
entry:'./src/js/index.js',
output:{
path:resolve(__dirname,'dist'),
filename: "js/index.js",
},
module: {
rules: [
// less-loader
{
test: /\.less$/, // すべてのlessファイルをmatch
use: [ // 配列内のloaderは実行順番が後ろから前 つまりless > css > styleの順番です
"style-loader", // 役割:HTMLファイルにstyleタグを作りスタイルを入れる
"css-loader", // 役割:cssをCommentJsモジュールに変換
"less-loader" // 役割:lessをコンパイルしcssに変換する、cssファイル生成せず、メモリ内に保存する
],
},
// eslint-loader
{
test:/\.js$/, // すべてのjsファイルをmatch
exclude:/node_modules/, // node_modulesフォルダ内のものはEslintでチェックしない
enforce:"pre", // webpackによるbundleする前に実施
use:{
loader:"eslint-loader",
},
},
// es6 let/constなどを古いブラウザも処理させるように
{
test: /\.m?js$/,
exclude: /node_modules/,
use: {
loader: 'babel-loader',
options: {
// babelでどんな交換性処理を設定する
presets: [
['@babel/preset-env', { targets: "defaults" }]
]
}
}
},
// browser core promiseなどの対応
{
test: /\.js$/,
exclude: /node_modules/,
use: {
loader: 'babel-loader',
options: {
// babelでどんな交換性処理を設定する
presets: [
[
'@babel/preset-env',
{
useBuiltIns: 'usage',
corejs: {
version: 3
},
targets: {
chrome: '60', // chrome60バージョン以降のブラウザを対応する
firefox: '60',
ie: '11',
safari: '10',
edge: '17'
}
}
]
],
cacheDirectory:true,
}
}
},
//url-loader 画像処理 file-loaderと比べると base64に変換するのが可能
{
test: /\.(png|jpg|gif)$/,
use: [
{
loader: 'url-loader',
options: {
limit:8192,
publicPath:'images/',
outputPath:'images',
name:'[hash:5].[ext]',
},
}
]
},
//file-loader
{
test:/\.(eot|svg|woff|woff2|ttf|mp3|mp4|avi)$/,
loader:'file-loader',
options: {
outputPath:'media',
name:'[hash:5].[ext]'
}
},
]
},
plugins: [
new HTMLWebpackPlugin({
template: "./src/index.html",
})
],
devServer: {
open:true,
contentBase: './dist',
compress: true,
port:8080,
hot: true,
allowedHosts: [
'.amazonaws.com' //cloud9で開発する人
],
},
mode:'development',
devtool:'eval-cheap-source-map',
}
- package.jsonの設定を追加します。(スクリプトを追加する)
src/package.json
{
"name": "webpack-handson",
"version": "1.0.0",
"description": "",
"main": "index.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1",
"start": "npx webpack serve"
},
"author": "",
"license": "ISC",
"devDependencies": {
"@babel/core": "^7.13.14",
"@babel/preset-env": "^7.13.12",
"babel-loader": "^8.2.2",
"core-js": "^3.10.0",
"css-loader": "^5.2.0",
"eslint": "^7.23.0",
"eslint-loader": "^4.0.2",
"file-loader": "^6.2.0",
"html-webpack-plugin": "^5.3.1",
"less": "^4.1.1",
"less-loader": "^8.0.0",
"style-loader": "^2.0.0",
"url-loader": "^4.1.1",
"webpack": "^5.30.0",
"webpack-cli": "^4.6.0",
"webpack-dev-server": "^3.11.2"
},
"eslintConfig": {
"parserOptions": {
"ecmaVersion": 6,
"sourceType": "module"
},
"env": {
"browser": true,
"node": true
},
"globals": {
"$": "readonly",
"Promise": "readonly"
},
"rules": {
"no-console": 0,
"eqeqeq": 0,
"no-alert": 0
},
"extends": "eslint:recommended"
}
}
追加したスクリプトは
...
"scripts": {
...
"start": "npx webpack serve"
},
...
npm run startで試しましょう。
ちなみに、
start
のスクリプトはrun
付けなくても大丈夫
npm start
設定したポートで見られば大丈夫(自分は8080を設定しました)
React
- 先ずはインストール
npm i react react-dom
npm i @babel/preset-react -D
npm i babel-eslint eslint-plugin-react -D
npm i @material-ui/core
- babelの設定
src/babel.config.json
{
"presets": ["@babel/preset-env", "@babel/preset-react"]
}
- eslint configの設定変更
src/packge.json
{
"name": "webpack-handson",
"version": "1.0.0",
"description": "",
"main": "index.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1",
"start": "npx webpack serve"
},
"author": "",
"license": "ISC",
"devDependencies": {
"@babel/core": "^7.13.14",
"@babel/preset-env": "^7.13.12",
"@babel/preset-react": "^7.13.13",
"babel-eslint": "^10.1.0",
"babel-loader": "^8.2.2",
"core-js": "^3.10.0",
"css-loader": "^5.2.0",
"eslint": "^7.23.0",
"eslint-loader": "^4.0.2",
"eslint-plugin-react": "^7.23.1",
"file-loader": "^6.2.0",
"html-webpack-plugin": "^5.3.1",
"less": "^4.1.1",
"less-loader": "^8.0.0",
"style-loader": "^2.0.0",
"url-loader": "^4.1.1",
"webpack": "^5.30.0",
"webpack-cli": "^4.6.0",
"webpack-dev-server": "^3.11.2"
},
"eslintConfig": {
"parserOptions": {
"ecmaVersion": 6,
"sourceType": "module"
},
"ecmaFeatures": {
"experimentalObjectRestSpread": true
},
"plugins": [
"react"
],
"extends": "eslint:recommended",
"parser": "babel-eslint",
"env": {
"browser": true,
"node": true
},
"globals": {
"$": "readonly",
"Promise": "readonly"
},
"rules": {
"no-console": 0,
"eqeqeq": 0,
"no-alert": 0,
"no-unused-vars": 0
}
},
"dependencies": {
"react": "^17.0.2",
"react-dom": "^17.0.2"
}
}
- index.jsを編集 (react)
src/js/index.js
// js
import {sum} from './module1'
import {sub} from './module2'
import module3 from "./module3";
// json
import json from '../json/test';
// less
import '../css/index.less'
// react
import React, { useState } from "react";
import { render } from "react-dom";
function App() {
const [state, setState] = useState("CLICK ME");
return(
<div>
<Button variant="contained">Default</Button>
</div>
)
}
render(<App />, document.getElementById("root"));
console.log(sum(1,2));
console.log(sub(4,6));
console.log(module3.mul(5,6));
console.log(module3.div(10,5));
console.log(json,typeof json);
- index.htmlを編集
src/index.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>demo</title>
</head>
<body>
<script type="text/javascript" src="../dist/js/index.js"></script>
<h1>test</h1>
<!--react-->
<div id="root"></div>
<div class="demo"></div>
</body>
</html>
npm start
で起動この画面見れば大丈夫
おまけ
npx create-react-appのwebpack設定はこちらgithub facebook/create-react-app
Discussion