webpack4 的30個步驟打造優化到極致的 react 開發環境,如約而至

張不慫發表於2019-06-10

上一篇記錄了一下webpack4使用的一些基礎使用小技巧,確實沒有想到能收穫這麼大的反響,還是非常感謝各位的錯愛,沒有看過的關於webpack4的14個知識點,童叟無欺

這一篇文章將react和webpack4進行結合,集webpack的優勢於一身,從0開始構建一個強大的react開發環境

本篇所有程式碼線上程式碼react-webpack4-cook,翻譯過來叫:webpack4和react的亂燉,可以跟著程式碼進行配置,之前有很多坑,線上程式碼都已經被解決了 。如果對您有幫助,不妨給個star.點贊關注不迷路

前言

一篇文章不寫前言總感覺不太正式,大概介紹下我是怎麼完成一個總的知識點的概括的把。其實很多人都有一看就會,一做就廢的特點(當然也包括我在內),這個時候,你需要制定一個略微詳細的計劃,就比如我這篇會首先列出知識點,列出大的方向,制定思維導圖,然後根據思維導圖編寫程式碼,計劃明確,就會事半功倍,因此,希望你可以跟著本篇循序漸進的跟著程式碼走一遍,不管是真實開發,還是面試,都有的扯。好了,不扯了,可以先看下目錄。現在開始

一、基礎配置

1、init專案

mkdir react-webpack4-cook
cd react-webpack4-cook
mkdir src
mkdir dist
npm init -y
複製程式碼

2、安裝webpack

yarn add webpack webpack-cli webpack-dev-server -D //webpack4把webpack拆分了
touch webpack.config.js
// webpack.config.js初始化內容
module.exports = {
    mode: "development",
    entry: ["./src/index.js"],
    output: {
        // 輸出目錄
        path: path.join(__dirname, "dist"),
        // 檔名稱
        filename: "bundle.js"
    },
    module:{},
    plugins:[],
    devServer:{}
}
複製程式碼

3、安裝react並編寫程式碼

這部分程式碼篇幅過多,就是一些簡單的react和react-router的一些程式碼編寫,可以去github上檢視,這裡只闡述基本功能

cd src 
cnpm i react react-router-dom -S
// 建立如下的檔案目錄,並編寫安裝react和react-router並編寫react程式碼如下
|-src
│      index.js 主檔案
├───pages
│      Count.jsx -- 實現了一個計數器的功能,點選按鈕,會讓數字增加,按鈕會實時顯示在頁面上
│      Home.jsx -- 一個簡單的文字展示
└───router
       index.js -- 路由配置檔案,兩個頁面分別對應兩個路由 count和 home
複製程式碼

4、babel編譯ES6,JSX等

// @babel/core-babel核心模組    @babel/preset-env-編譯ES6等 @babel/preset-react-轉換JSX
cnpm i babel-loader @babel/core @babel/preset-env  @babel/plugin-transform-runtime   @babel/preset-react -D
// @babel/plugin-transform-runtime: 避免 polyfill 汙染全域性變數,減小打包體積
// @babel/polyfill: ES6 內建方法和函式轉化墊片
cnpm i @babel/polyfill @babel/runtime
 {
        test: /\.jsx?$/,
        exclude: /node_modules/,
        use: [
          {
            loader: "babel-loader"
          }
        ]
      }
 
複製程式碼

新建.babelrc檔案

{
  "presets": ["@babel/preset-env","@babel/preset-react"],
  "plugins": ["@babel/plugin-transform-runtime"]
}
 
複製程式碼

5、按需引入polyfill

在src下的index.js中全域性引入 @babel/polyfill 並寫入 ES6 語法 ,但是這樣有一個缺點:

  1. 全域性引入 @babel/polyfill 的這種方式可能會匯入程式碼中不需要的 polyfill,從而使打包體積更大

更改 .babelrc,只轉譯我們使用到的

npm install core-js@2 @babel/runtime-corejs2 -S
 
{
  "presets": ["@babel/preset-env",
              { "useBuiltIns": "usage" },
              "@babel/preset-react"],
  "plugins": ["@babel/plugin-transform-runtime"]
}
 
將將全域性引入這段程式碼註釋掉
// import '@babel/polyfill'
複製程式碼

這就配置好了按需引入。配置了按需引入 polyfill 後,用到es6以上的函式,babel會自動匯入相關的polyfill,這樣能大大減少 打包編譯後的體積

5、外掛 CleanWebpackPlugin

你經過多次打包後會發現,每次打包都會在dist目錄下邊生成一堆檔案,但是上一次的打包的檔案還在,我們需要每次打包時清除 dist 目錄下舊版本檔案

cnpm install  clean-webpack-plugin -D
// 注意這個引入的坑,最新版的需要這樣引入,而不是直接const CleanWebpackPlugin
const { CleanWebpackPlugin } = require('clean-webpack-plugin');
 
plugins: [
    new CleanWebpackPlugin() 
]
複製程式碼

6、使用外掛 HtmlWebpackPlugin

經過上一步的操作,index.html 也被清除了。因此我們將使用 HtmlWebpackPlugin外掛,來生成 html, 並將每次打包的js自動插入到你的 index.html 裡面去,而且它還可以基於你的某個 html 模板來建立最終的 index.html,也就是說可以指定模板哦

cnpm install html-webpack-plugin -D
// 建立template.html
cd src
touch template.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>react-webpack4-cook</title>
</head>
<body>
  <div id="root"></div>
</body>
</html>
 
// webpack.config.js做出更改
const HtmlWebpackPlugin = require('html-webpack-plugin');
plugins: [
    new CleanWebpackPlugin(),
    new HtmlWebpackPlugin({
      filename: 'index.html', // 最終建立的檔名
      template: path.join(__dirname, 'src/template.html') // 指定模板路徑
    })
  ]
複製程式碼

7、使用 source-map,對devtool進行優化

webpack中devtool選項用來控制是否生成,以及如何生成 source map。簡言之,source map就是幫助我們定位到錯誤資訊位置的檔案。正確的配置source map,能夠提高開發效率,更快的定位到錯誤位置。

在webpack.config.js中選項mode下加上如下這句話:

devtool:"cheap-module-eval-source-map",// 開發環境配置
devtool:"cheap-module-source-map",   // 線上生成配置
複製程式碼

8、使用 WebpackDevServer

webpack-dev-server就是在本地為搭建了一個小型的靜態檔案伺服器,有實時重載入的功能,為將打包生成的資源提供了web服務

  devServer: {
    hot: true,
    contentBase: path.join(__dirname, "./dist"),
    host: "0.0.0.0", // 可以使用手機訪問
    port: 8080,
    historyApiFallback: true, // 該選項的作用所有的404都連線到index.html
    proxy: {
      // 代理到後端的服務地址,會攔截所有以api開頭的請求地址
      "/api": "http://localhost:3000"
    }
  }
複製程式碼

9、使用 HotModuleReplacement (熱模組替換HMR)

建立了開發環境本地伺服器 後,當修改內容後,網頁會同步重新整理,我們現在進入toCOunt頁面

  1. 點選按鈕,將數字加到一個不為0的數,比如加到6

  2. 然後你可以在程式碼中改變按鈕的文字,隨便改點東西,會發現,頁面重新整理後,數字重新變為0

這顯然不是我們想要的,想要的是,能不能把頁面的狀態儲存了,也就是更改了程式碼後,頁面還是儲存了數字為6的狀態,也就是實現區域性更改,首先需要用到:HotModuleReplacementPlugin外掛

devServer: {
    hot: true
},
 
plugins: [
    new webpack.HotModuleReplacementPlugin()
],
複製程式碼

完事之後,繼續更上邊的操作,點選按鈕,數字增加,然後更改內容,發現還是沒有儲存狀態。。。what?怎麼辦

對@!這還沒完呢,接著往下看,我們還需要react-hot-loader這個外掛

10、react-hot-loader記錄react頁面留存狀態state

我們繼續接著上邊的進行操作,分一下四步

cnpm i react-hot-loader -D
 
// 在主檔案裡這樣寫
 
import React from "react";
import ReactDOM from "react-dom";
import { AppContainer } from "react-hot-loader";-------------------1、首先引入AppContainre
import { BrowserRouter } from "react-router-dom";
import Router from "./router";
 
/*初始化*/
renderWithHotReload(Router);-------------------2、初始化
 
/*熱更新*/
if (module.hot) {-------------------3、熱更新操作
  module.hot.accept("./router/index.js", () => {
    const Router = require("./router/index.js").default;
    renderWithHotReload(Router);
  });
}
 
 
function renderWithHotReload(Router) {-------------------4、定義渲染函式
  ReactDOM.render(
    <AppContainer>
      <BrowserRouter>
        <Router />
      </BrowserRouter>
    </AppContainer>,
    document.getElementById("app")
  );
}
 
複製程式碼

好了,現在你再試試

11、編譯css和scss

cnpm install css-loader style-loader sass-loader node-sass -D
 
{
  test: /\.scss$/,
    use: [
      "style-loader", // 建立style標籤,並將css新增進去
      "css-loader", // 編譯css
      "sass-loader" // 編譯scss
    ]
}
複製程式碼

12、整合postcss

最關心的還是這有啥用啊?自動增加字首, postcss-cssnext允許你使用未來的css特性,並做一些相容處理

cnpm install  postcss-loader postcss-cssnext -D
 
{
    test: /\.scss$/,
        use: [
            "style-loader", // 建立style標籤,並將css新增進去
            "css-loader", // 編譯css
            "postcss-loader",
            "sass-loader" // 編譯scss
        ]
}
// 在剛才的home.scss 加上 transform: scale(2);
通過控制檯檢視,已經自動加上了字首
複製程式碼

13、處理圖片

cnpm i file-loader url-loader -D
 
file-loader 解決css等檔案中引入圖片路徑的問題
url-loader 當圖片較小的時候會把圖片BASE64編碼,大於limit引數的時候還是使用file-loader 進行拷貝
{
    test: /\.(png|jpg|jpeg|gif|svg)/,
    use: {
      loader: 'url-loader',
      options: {
        outputPath: 'images/', // 圖片輸出的路徑
        limit: 10 * 1024
      }
    }
}
複製程式碼

14、處理字型

{
        test: /\.(eot|woff2?|ttf|svg)$/,
        use: [
          {
            loader: 'url-loader',
            options: {
              name: '[name]-[hash:5].min.[ext]',
              limit: 5000, // fonts file size <= 5KB, use 'base64'; else, output svg file
              publicPath: 'fonts/',
              outputPath: 'fonts/'
            }
          }
        ]
      }
複製程式碼

二、webpack優化

1、alias對檔案路徑優化

  1. extension: 指定extension之後可以不用在require或是import的時候加副檔名,會依次嘗試新增副檔名進行匹配
  2. alias: 配置別名可以加快webpack查詢模組的速度
  resolve: {
    extension: ["", ".js", ".jsx"],
    alias: {
      "@": path.join(__dirname, "src"),
      pages: path.join(__dirname, "src/pages"),
      router: path.join(__dirname, "src/router")
    }
  },
複製程式碼

14、使用靜態資源路徑publicPath(CDN)

CDN通過將資源部署到世界各地,使得使用者可以就近訪問資源,加快訪問速度。要接入CDN,需要把網頁的靜態資源上傳到CDN服務上,在訪問這些資源時,使用CDN服務提供的URL。

output:{
 publicPatch: '//【cdn】.com', //指定存放JS檔案的CDN地址
}
複製程式碼

2、MiniCssExtractPlugin ,抽取 css 檔案

如果不做配置,我們的css是直接打包進js裡面的,我們希望能單獨生成css檔案。 因為單獨生成css,css可以和js並行下載,提高頁面載入效率

cnpm install mini-css-extract-plugin -D
 
const MiniCssExtractPlugin = require("mini-css-extract-plugin");
 
 {
        test: /\.scss$/,
        use: [
          // "style-loader", // b不再需要style-loader要已經分離處理
          MiniCssExtractPlugin.loader,
          "css-loader", // 編譯css
          "postcss-loader",
          "sass-loader" // 編譯scss
        ]
      },
 
 plugins: [
    new MiniCssExtractPlugin({
      filename: "[name].css",
      chunkFilename: "[id].css"
    })
  ]
複製程式碼

3、程式碼分割按需載入、提取公共程式碼

為什麼要實現按需載入?

我們現在看到,打包完後,所有頁面只生成了一個bundle.js,當我們首屏載入的時候,就會很慢。因為他也下載了別的頁面的js,也就是說,執行完畢之前,頁面是 完!全!空!白!的!。 如果每個頁面單獨打包自己的js,就可以在進入頁面時候再載入自己 的js,首屏載入就可以快很多

  optimization: {
    splitChunks: {
      chunks: "all", // 所有的 chunks 程式碼公共的部分分離出來成為一個單獨的檔案
    },
  },
複製程式碼

5、檔案壓縮

webpack4只要在生產模式下, 程式碼就會自動壓縮

mode:productioin
複製程式碼

6、暴露全域性變數

可以直接在全域性使用$變數

 new webpack.ProvidePlugin({
      $: 'jquery', // npm
      jQuery: 'jQuery' // 本地Js檔案
    })
複製程式碼

8、指定環境,定義環境變數

plugins: [
    new webpack.DefinePlugin({
      'process.env': {
        VUEP_BASE_URL: JSON.stringify('http://localhost:9000')
      }
    }),
]
複製程式碼

9、css Tree Shaking

npm i glob-all purify-css purifycss-webpack --save-dev
 
const PurifyCSS = require('purifycss-webpack')
const glob = require('glob-all')
plugins:[
    // 清除無用 css
    new PurifyCSS({
      paths: glob.sync([
        // 要做 CSS Tree Shaking 的路徑檔案
        path.resolve(__dirname, './src/*.html'), // 請注意,我們同樣需要對 html 檔案進行 tree shaking
        path.resolve(__dirname, './src/*.js')
      ])
    })
]
複製程式碼

10、js Tree Shaking

清除到程式碼中無用的js程式碼,只支援import方式引入,不支援commonjs的方式引入

只要mode是production就會生效,develpoment的tree shaking是不生效的,因為webpack為了方便你的除錯

  optimization: {
    usedExports:true,
  }
複製程式碼

11、DllPlugin外掛打包第三方類庫

專案中引入了很多第三方庫,這些庫在很長的一段時間內,基本不會更新,打包的時候分開打包來提升打包速度,而DllPlugin動態連結庫外掛,其原理就是把網頁依賴的基礎模組抽離出來打包到dll檔案中,當需要匯入的模組存在於某個dll中時,這個模組不再被打包,而是去dll中獲取。

安裝jquery,並在入口檔案引入。新建webpack.dll.config.js檔案

/*
* @desc 靜態公共資源打包配置
*/


const path = require('path')
const webpack = require('webpack')

const src = path.resolve(process.cwd(), 'src'); // 原始碼目錄
const evn = process.env.NODE_ENV == "production" ? "production" : "development";

module.exports = {
    mode: 'production',
    entry: {
        // 定義程式中打包公共檔案的入口檔案vendor.js
        jquery: ['jquery']
    },

    output: {
        path: path.resolve(__dirname, '..', 'dll'),
        filename: '[name].dll.js',
        library: '[name]_[hash]',
        libraryTarget: 'this'
    },

    plugins: [
        new webpack.DllPlugin({
            // 定義程式中打包公共檔案的入口檔案vendor.js
            context: process.cwd(),

            // manifest.json檔案的輸出位置
            path: path.resolve(__dirname, '..', 'dll/[name]-manifest.json'),

            // 定義打包的公共vendor檔案對外暴露的函式名
            name: '[name]_[hash]'
        })
    ]
}
複製程式碼

在package.json中新增

        "build:dll": "webpack --config ./build/webpack.dll.config.js",
複製程式碼

執行

npm run build:dll
複製程式碼

你會發現多了一個dll資料夾,裡邊有dll.js檔案,這樣我們就把我們的jquery這些已經單獨打包了,接下來怎麼使用呢?

需要再安裝一個依賴 npm i add-asset-html-webpack-plugin,它會將我們打包後的 dll.js 檔案注入到我們生成的 index.html 中.在 webpack.base.config.js 檔案中進行更改。

 new AddAssetHtmlWebpackPlugin({
 filepath: path.resolve(__dirname, '../dll/jquery.dll.js') // 對應的 dll 檔案路徑
 }),
 new webpack.DllReferencePlugin({
 manifest: path.resolve(__dirname, '..', 'dll/jquery-manifest.json')
 })
複製程式碼

好了,你可有吧new webpack.DllReferencePlugin這個外掛註釋掉,打包試下,在放開打包試一下,我測試結果,註釋錢5689,註釋後,5302ms,才差了300ms?注意,我這裡只有一個jquery包作為演示,要是你把很多個都抽離了出來呢???那豈不是很恐怖了。如果你看的有點迷迷糊糊,那推薦去線上看一下我的程式碼吧,一看便知

12、使用happypack併發執行任務

執行在 Node.之上的Webpack是單執行緒模型的,也就是說Webpack需要一個一個地處理任務,不能同時處理多個任務。 Happy Pack 就能讓Webpack做到這一點,它將任務分解給多個子程式去併發執行,子程式處理完後再將結果傳送給主程式。

cnpm i -D happypack
 
// webpack.config.js
 rules: [
     {
        // cnpm i babel-loader @babel/core @babel/preset-env -D
        test: /\.jsx?$/,
        exclude: /node_modules/,
        use: [
          {
            // 一個loader對應一個id
            loader: "happypack/loader?id=busongBabel"
          }
        ]
      }
      ]
 
//在plugins中增加
plugins:[
      new HappyPack({
      // 用唯一的識別符號id,來代表當前的HappyPack是用來處理一類特定的檔案
      id:'busongBabel',
      // 如何處理.js檔案,用法和Loader配置中一樣
      loaders:['babel-loader?cacheDirectory'],
      threadPool: HappyPackThreadPool,
  })
]
 
複製程式碼

13、PWA優化策略

簡言之:在你第一次訪問一個網站的時候,如果成功,做一個快取,當伺服器掛了之後,你依然能夠訪問這個網頁 ,這就是PWA。那相信你也已經知道了,這個只需要在生產環境,才需要做PWA的處理,以防不測。

 cnpm i workbox-webpack-plugin -D

const WorkboxPlugin = require('workbox-webpack-plugin') // 引入 PWA 外掛
const prodConfig = {
  plugins: [
    // 配置 PWA
    new WorkboxPlugin.GenerateSW({
      clientsClaim: true,
      skipWaiting: true
    })
  ]
}

在入口檔案加上
// 判斷該瀏覽器支不支援 serviceWorker
if ('serviceWorker' in navigator) {
  window.addEventListener('load', () => {
    navigator.serviceWorker
      .register('/service-worker.js')
      .then(registration => {
        console.log('service-worker registed')
      })
      .catch(error => {
        console.log('service-worker registed error')
      })
  })
}
複製程式碼

配置完後,你可以打包到dist目錄下,在dist目錄下啟動一個靜態伺服器,訪問首頁,然後關閉這個伺服器,你會驚訝的發現:網站竟然還能夠訪問,哈哈,是不是很神奇?

15、合併提取webpack公共配置

 開發環境與生產環境以及webpack配置檔案的分離,具體需要用到webpack-merge,用來 合併 webpack配置
複製程式碼

16、最終分離配置檔案(打完收工)

由於時間和篇幅的限制,基本到這裡就結束了。以上,不管是提到的未提到的,或者還有一些細枝末節,github上的原始碼基本都已經全部包括在內了,如果有需要可以去github參照配置檔案,自己跟著配一份出來,會更加事半功倍

願世間再無webpack配置工程師

都到這裡了,您不妨點個,給個star,加個關注。本篇所有程式碼線上程式碼react-webpack4-cook,翻譯過來叫:webpack4和react的亂燉,可以跟著程式碼進行配置,之前有很多坑,線上程式碼都已經被解決了 。如果對您有幫助,不妨給個star.點贊關注不迷路

相關文章