使用webpack4從零配置react項目

前言

以前做react項目的時候都是使用create-react-app腳手架初始化項目的,最近想自己從零配置webpack4來實現一個react項目的初始化。

源碼在這裏
配置實現的功能:

  • 打包css、js、img等資源
  • 支持jsx、es6等語法
  • 本地服務器
  • eslint
  • 按需加載
  • 構建分析
  • git hooks

最終的目錄結構
在這裏插入圖片描述

  • build放置打包文件
  • config放置webpack配置文件
  • src放置我們的代碼

配置過程

1.創建文件夾sty-react-template

2.初始化git倉庫、生成package.json文件

git init
yarn init

3.創建src目錄、index.html、index.js文件
在這裏插入圖片描述

接下來我們打包試試,雖然現在我們什麼都沒配置,但是webpack4有自己的默認配置。

#安裝依賴
yarn add webpack webpack-cli -D
#執行webpack命令
webpack index.js

此時發現報錯bash: webpack: command not found
因爲我們是本地安裝的webpack,全局的環境變量並沒有webpack命令,所以報錯,我們只能進入對應的目錄下執行命令

node_modules/.bin/webpack index.js
#或者   npx webpack index.js

npx會自動鏈接路徑具體可以參考這裏

在這裏插入圖片描述
此時項目底下多了一個打包文件dist,這是webpack默認配置打包的結果,我們在index.html引入打包的文件就可以了。在瀏覽器中打開index.html可以在控制檯看到打印的hello world。

接下來我們自己創建配置來打包,創建配置之前,我們先忽略node_modelues文件的改動,在根目錄下創建.gitignore

# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.

# dependencies
/node_modules
/.pnp
.pnp.js

# testing
/coverage

# production
/build

# misc
.DS_Store
.env.local
.env.development.local
.env.test.local
.env.production.local

npm-debug.log*
yarn-debug.log*
yarn-error.log*

創建config文件夾及配置文件後的目錄結構
在這裏插入圖片描述
生成和開發環境分開配置,通過webpack-merge合併共用配置

//webpack.common.js
const path = require('path')

// 從根目錄走
function resolve(dir) {
    return path.join(__dirname, '..', dir);
}

module.exports = {
    context: path.resolve(__dirname, '../'), // 入口起點根目錄
    entry: {
        app: './src/index.js'
    },
    output: {
        path: resolve('build'),
        filename: '[name].js',
    }
}

//webpack.dev.js
const merge = require('webpack-merge')
const common = require('./webpack.common')

module.exports = merge(common,{
    mode:'development'
})

//webpack.prod.js
const merge = require('webpack-merge')
const common = require('./webpack.common')

module.exports = merge(common, {
    mode: 'production'
})

webpack4提供了mode模式來優化webpack打包, mode爲production會自動壓縮js代碼。
通過package.json來使用webpack命令

//package.json
...
 "scripts": {
    "dev":"webpack --config ./config/webpack.dev.js",
    "build": "webpack --config ./config/webpack.prod.js"
  },
 ...
 

–config是告訴webpack通過什麼配置文件打包
執行yarn dev可以看到js被打包到build目錄下了,然後在html中引入打包的文件
在這裏插入圖片描述

我們可以使用插件來生成html文件,這樣就避免了html每次去手動引入js;使用插件來刪除上次打包的結果

#安裝依賴
yarn add -D html-webpack-plugin clean-webpack-plugin
//修改webpack.common.js文件
const HtmlWebpackPlugin = require('html-webpack-plugin');
const { CleanWebpackPlugin } = require('clean-webpack-plugin');

module.exports = {
	...
 	plugins:[
        new HtmlWebpackPlugin({template:'./src/index.html'}),
        new CleanWebpackPlugin()
    ]
    ...
}

現在打包會自動以src下的html爲模板生成新的html,新的html文件會自動引入打包的文件
在這裏插入圖片描述
每次修改完文件都要重新打包才能看到效果,我們可以使用webpack-dev-server來搭建一個本地服務器來實時更新。

#安裝依賴
yarn add -D webpack-dev-server
//webpack.dev.js
const merge = require('webpack-merge')
const common = require('./webpack.common')

module.exports = merge(common, {
    mode: 'development',
    devServer: {
        contentBase: './index.html',
        hot: true,
        port: 3000
    }
})

//修改package.json的dev
 "scripts": {
    "dev": "webpack-dev-server --open --config ./config/webpack.dev.js",  
	...
  },

執行yarn dev會就可以直接在http://localhost:3000/實時預覽了

至此webpack的雛形已經完成了,上面一些配置的細節可以參考官方文檔
接下來我們就去實現jxs語法了。

#安裝依賴,這裏不需要加-D參數
yarn add react react-dom
//修改src下的index.js文件
import React from 'react'
import ReactDOM from 'react-dom'

const App = () => (
	<div>
		hello react
	</div>
)

ReactDOM.render(<App />, document.getElementById('app'))

運行yarn dev報錯
在這裏插入圖片描述
很明顯不支持jsx語法,我們需要配置loader了
babel的官網,看看如何配置jsx語法loader
在這裏插入圖片描述

yarn add -D @babel/preset-react babel-loader @babel/core
//webpack.common.js
module.exports = {
	...
 	module: {
	    rules: [
	      {
	        test: /\.(js|jsx|ts|tsx)$/,
	        exclude: /(node_modules|bower_components)/,
	        use: [
	          {
	            loader: 'babel-loader',
	            options: {
	              presets: ['@babel/preset-react']
	            }
	          }
	        ]
	      },
	    ]
 	 }
}

運行yarn dev啓動成功
在這裏插入圖片描述
接下來配置css的loader
在src下創建css文件,然後在index中引入並使用
在這裏插入圖片描述

yarn add -D style-loader css-loader

//webpack.common.js
   ...
   module:{
	rules:[
		...
		{
	      test: /\.css$/,
	      use: ['style-loader', 'css-loader']
      	}
	]
   }

//index.css
...
.red {
  color: red;
}

運行yarn dev
在這裏插入圖片描述
可以看到樣式被插進了head中。當項目變大時樣式直接插入head中的方式並不好,我們需要將樣式分離
webpack4的版本中建議使用mini-css-extract-plugin插件(以前是ExtractTextWebpackPlugin插件)

yarn add -D mini-css-extract-plugin
//webpack.common.js
...
const MiniCssExtractPlugin = require('mini-css-extract-plugin');

	rules:[
		...
		{
	      test: /\.css$/,
	      use: [
          {
            loader: MiniCssExtractPlugin.loader,
            options: {
              // you can specify a publicPath here
              // by default it uses publicPath in webpackOptions.output
              publicPath: '../',
              hmr: process.env.NODE_ENV === 'development',
             },
            },
           'css-loader',
          ],
      	}
	]

plugins:[
	...
	 new MiniCssExtractPlugin({
      filename: 'static/css/[name].css',   //打包到static的css目錄下
      ignoreOrder: false, // Enable to remove warnings about conflicting order
    })
]

在這裏插入圖片描述
可以看到css被分離出去了

接下來配置img、font、mp3等資源的loader。
url-loader和file-loader的區別在於url-loader在文件小於一定大小時可以直接打包成base64的格式嵌入html中,避免一次http請求,所以這裏我們用url-loader

yarn add url-loader -D
//webpack.common.js
module:{
	rules:[
	   ...
	    {
        test: /\.(png|jpe?g|svg|gif)(\?.*)?$/,
        loader: 'url-loader',
        options: {
          limit: 10000,
          name: 'static/img/[name].[hash:7].[ext]'
        }
      },
      {
        test: /\.(woff2?|eot|ttf|otf)(\?.*)?$/,
        loader: 'url-loader',
        options: {
          limit: 10000,
          name: 'static/font/[name].[hash:7].[ext]'
        }
      },
      {
        test: /\.(mp4|webm|ogg|mp3|wav|flac|aac)(\?.*)?$/,
        loader: 'url-loader',
        options: {
          limit: 10000,
          name: 'static/media/[name].[hash:7].[ext]'
        }
      }
	]
}

文件名加上hash是避免緩存的影響,webpack幾種hash值得區別可以參考這裏

基本上面完成了大部分配置了。
不過有一個問題,將所有的js打包成一個文件導致文件體積很大,我們需要將js拆分。通過import()語法我們可以動態加載js,webpack會自動將js拆分。我們只需要配置拆分文件的name即可,下面我們來創建一個組件動態引入。

修改src目錄下的文件
在這裏插入圖片描述

//  src/index.js
import React from 'react'
import ReactDOM from 'react-dom'
import App from './app'

ReactDOM.render(
	<App/>,
	document.getElementById('app')
)

// app.js
import React from 'react'
import Test from './components/Test'

function App() {
  return (
    <div>
      <Test />
    </div>
  )
}

export default App;

// src/components/Test.js
import React, { Component } from 'react';

class Test extends Component {
  state = {  }
  render() { 
    return ( 
      <div>
        test
      </div>
     );
  }
}
 
export default Test;

啓動項目報錯
在這裏插入圖片描述
現在還不支持類實例屬性簡寫,實例屬性只能寫在constructor中,不過我們可以通過配置babel來支持這種寫法

yarn add -D @babel/plugin-syntax-class-properties
//webpack.common.js
...
 rules: [
      {
        test: /\.(js|jsx|ts|tsx)$/,
        exclude: /(node_modules|bower_components)/,
        use: [
          {
            loader: 'babel-loader',
            options: {
              presets: ['@babel/preset-react'],
              plugins: ['@babel/plugin-proposal-class-properties']
            }
          }
        ]
      },
      ...
 ]

接下來使用按需加載,按需加載只需要配置chunkFilename即可,然後使用動態import()方法,webpack會自動拆分

我使用react提供的懶加載方法來拆分代碼

// src/app.js
import React from 'react'
// import Test from './components/Test'

const Test = React.lazy(() => import('@/components/Test'))

function App() {
  return (
    <div>
      <React.Suspense fallback={<div>Loading...</div>}>
        <Test />
      </React.Suspense>
    </div>
  )
}

export default App;

啓動報錯
在這裏插入圖片描述
不支持異步導入。添加babel插件解決

yarn add  -D @babel/plugin-syntax-dynamic-import
// webpack.common.js
 use: [
          {
            loader: 'babel-loader',
            options: {
              presets: ['@babel/preset-react'],
              plugins: ['@babel/plugin-proposal-class-properties', '@babel/plugin-syntax-dynamic-import']
            }
          },
          'eslint-loader'
        ]

執行yarn build
在這裏插入圖片描述
不用懶加載打包的情況如下:
在這裏插入圖片描述

通過對比發現多了一個1.js。這裏我們需要配置拆分後的js目錄,通過chunkFilename即可

//webpack.common.js
output: {
  	...
    chunkFilename: 'static/js/[name].[contenthash:8].chunk.js',
  },

在這裏插入圖片描述
可以看到打包的入口文件在最外層,拆分的js放到了static的js目錄下了。

接下來是壓縮css代碼。mode爲production只會壓縮js,css壓縮我們通過插件optimize-css-assets-webpack-plugin

yarn add -D optimize-css-assets-webpack-plugin
//webpack.common.js
module.exports = {
	...
	optimization: {
    // splitChunks: {
    //   chunks: 'all'
    // },
   	    minimizer: [new TerserJSPlugin({}), new OptimizeCSSAssetsPlugin({})]
  },

}

optimization是webpack提供的優化方式,webpack有默認的壓縮方式,我們可以自己配置minimizer,TerserJSPlugin是webpack4默認的js壓縮方法,這裏我們再寫一遍是防止自定義覆蓋了。

接下來我們進行構建分析,通過webpack-bundle-analyzer我們可以分析我們打包的代碼,webpack-bundle-analyzer的使用可以參考官網或這裏

yarn add -D webpack-bundle-analyzer
//webpack.common.js
...
const BundleAnalyzerPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPlugin;

plugins:[
	...
	new BundleAnalyzerPlugin({
      analyzerMode: 'disabled',   //避免每次啓動都打開分析網站
      generateStatsFile: true
    })
]

// package.json
"scripts": {
   "analyz": "webpack-bundle-analyzer --port 8001 ./build/stats.json"
	...
},

每次執行yarn devyarn build都會在build文件下生成stats.json文件,然後執行yarn analyz會自動在8001端口打開分析頁面
在這裏插入圖片描述
可以看到項目中node_modules文件在打包中的體積是最大的,如何優化?

我們可以將這類庫不打包,然後直接通過CDN引入。

// webpack.common.js
module.exports = {
	...
	externals: {
	   react: 'React',
	   'react-dom': 'ReactDOM'
  	},
}

執行yarn devyarn analyz
在這裏插入圖片描述
可以看到react和react-dom都沒有打包進去,體積減小了很多。但是這裏我們的頁面報錯了,因爲react沒有打包,導致頁面找不到React變量。我們需要手動在html裏引入react的庫

<!-- src/index.html -->
<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <meta http-equiv="X-UA-Compatible" content="ie=edge">
  <title>第一個模板</title>
  <!-- 有很多cdn,選擇一個即可 -->
  <script type="text/javascript" src="https://unpkg.com/[email protected]/umd/react.production.min.js"></script>
  <script type="text/javascript" src="https://unpkg.com/[email protected]/umd/react-dom.production.min.js"></script>
</head>
<body>
  <div id="app"></div>
  
</body>
</html>

關於如何配置externals請參考文檔

爲了保持項目代碼風格我們需要使用eslint進行檢查

yarn add -D eslint

npx eslint --init

在這裏插入圖片描述
這是我的選擇,還有最後一步是安裝依賴,選擇y即可。
命令會自動在項目根目錄生成.eslintrc.js文件,代碼風格我用的是standard
我們可以擴展或覆蓋代碼風格配置,比如語句必須分號結尾,更多的配置規則查看文檔

module.exports = {
	...
	rules: {
    	semi: ["error", "always"],  //語句必須用;
  	}
}

運行npx eslint src(檢測src目錄代碼風格)
在這裏插入圖片描述
可以看到很多錯誤,我們按照對應的規則修改即可。eslint結合編輯器可以很好的使用,vscode安裝eslint插件後可以直接看到提示
在這裏插入圖片描述
上面就是缺少分號,可以配置保存自動修復。
在這裏插入圖片描述
這裏有個錯誤,沒有使用React變量。但是我們使用了jsx語法就必須引入react。清除警告的方法就是使用"plugin:react/recommended"

// .eslintrc.js
module.exports = {
 extends: [
 	...
    "plugin:react/recommended"
  ],
}

使用eslint-loader可以直接在瀏覽器的控制檯中看到警告信息

yarn add -D eslint-loader
// webpack.common.js

...
module:{
	rules:[
	 ...
	 {
        test: /\.(js|jsx|ts|tsx)$/,
        exclude: /(node_modules|bower_components)/,
        use: [
          {
            loader: 'babel-loader',
            options: {
              presets: ['@babel/preset-react'],
              plugins: ['@babel/plugin-proposal-class-properties', '@babel/plugin-syntax-dynamic-import'],
            }
          },
          'eslint-loader'
        ]
      },
	]
}


在這裏插入圖片描述

我們每次在代碼提交時,能不能自動檢測eslint,當有代碼風格錯誤時阻止提交代碼?
可以使用husky來實現這個功能

# 用yarn的安裝husky@next   用npm的安裝husky
yarn add -D husky@next
//package.json
...
 "scripts": {
   	...
    "lint": "eslint src"
  },
   "husky": {
    "hooks": {
      "pre-commit": "yarn lint",
      "pre-push": "yarn lint"
    }
  },

在代碼commit或push的時候都會自動執行yarn linit當檢測不通過時會阻止代碼提交
比如有個文件有語句沒加分號時,我們來提交代碼試試

git add .
git commit -m"test"

在這裏插入圖片描述
可以看到代碼提交失敗,當修改完後再提交就可以了

以上就是這次webpack配置的全部內容了。源碼放在了github

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章