天天看點

webpack從入門及實戰(二)webpack核心概念

Loaders

說到webpack,自然離不開他的一大堆loaders,沒有loaders的webpack隻能打包js檔案,得益于衆多的loaders,我們可以打包各種類型的檔案。

loaders實際上可以了解為一種打包方案,webpack本身碰到除了js檔案之外的其他類型檔案時,就不知道該如何打包了,然後打包就會失敗,而loaders則是對應某種檔案類型的檔案一種單獨的打包方案,通過config配置了loaders之後,webpack就可以打包該類型的檔案了

打包圖檔

圖檔可以使用file-loader來進行打包,在項目目錄執行npm i file-loader -D之後進行一些簡單的配置就可以了

const path = require("path");

module.exports = {
  entry: "./src/index.js",
  output: {
    filename: "bundle.js",
    path: path.resolve(__dirname, "myDist"),
  },
  module: {
    rules: [
      {	
      	// 要打包的檔案類型
        test: /\.(png|jpg|gif)$/,
        use: [
          {	
          	// 使用file-loader打包
            loader: "file-loader",
            options: {
              // 生成的檔案名是 原檔案名_哈希值.原檔案擴充名
              name:'[name]_[hash].[ext]',
              // 輸出目錄是在打包目錄下的images檔案夾下
              outputPath:'images/'
            },
          },
        ],
      },
    ],
  },
};
           

也可以使用url-loader打包,url-loader會多一個limit配置項,低于該大小的檔案會直接被轉換成base64,不會再被打包

const path = require("path");

module.exports = {
  entry: "./src/index.js",
  output: {
    filename: "bundle.js",
    path: path.resolve(__dirname, "myDist"),
  },
  module: {
    rules: [
      {	
      	// 要打包的檔案類型
        test: /\.(png|jpg|gif)$/,
        use: [
          {	
          	// 使用file-loader打包
            loader: "file-loader",
            options: {
              // 生成的檔案名是 原檔案名_哈希值.原檔案擴充名
              name:'[name]_[hash].[ext]',
              // 輸出目錄是在打包目錄下的images檔案夾下
              outputPath:'images/',
              // 機關是位元組,這裡代表大于10kb的檔案正常打包,小于10kb的檔案轉成base64
              limit:10240
            },
          },
        ],
      },
    ],
  },
};
           

引入2張圖檔作為測試

// index.js
import sayHi from './sayHi'

// 小的圖檔大小為5kb
import smallImg from './images/small.png'
// 大的圖檔大小為15kb
import bigImg from './images/big.png'

// 将圖檔挂載至頁面
const root = document.getElementById('root');
const smallImage = new Image()
smallImage.src = smallImg
const bigImage = new Image()
bigImage.src = bigImg
root.append(smallImage)
root.append(bigImage)

sayHi()
           

可以看到打包後的images檔案夾下隻有1張大的圖檔

webpack從入門及實戰(二)webpack核心概念

但是頁面上2張圖檔均可以正常顯示

webpack從入門及實戰(二)webpack核心概念

打包樣式

普通的css需要通過style-loader和css-loader來打包

const path = require("path");

module.exports = {
  entry: "./src/index.js",
  output: {
    filename: "bundle.js",
    path: path.resolve(__dirname, "myDist"),
  },
  module: {
    rules: [
      {
        test:/\.css$/,
        use:[
          // style-loader将樣式挂載至dom節點
          'style-loader',
          // css-loader将css檔案整理為字元串
          'css-loader'
        ]
      }
    ],
  },
};
           

css預編譯語言需要額外使用對應語言的loader來多做一次解析,這裡以less為示例

const path = require("path");

module.exports = {
  entry: "./src/index.js",
  output: {
    filename: "bundle.js",
    path: path.resolve(__dirname, "myDist"),
  },
  module: {
    rules: [
      {
        test:/\.less$/,
        use:[
          'style-loader',
          {
            loader:'css-loader',
            options:{
              // 如果我們引入的less檔案裡通過@import引入了其他的less檔案,那引入的檔案預設會直接進入css-loader導緻出錯
              // 這裡是配置在進入css-loader之前要經過幾個loader,這裡前面隻有1個less-loader,故配置為1
              importLoaders: 1,
            }
          },
          'less-loader'
        ]
      }
    ],
  },
};
           

webpack打包的樣式預設是全局通用的,這也就導緻會出現樣式穿透的問題,我們可以通過modules配置來解決

const path = require("path");

module.exports = {
  entry: "./src/index.js",
  output: {
    filename: "bundle.js",
    path: path.resolve(__dirname, "myDist"),
  },
  module: {
    rules: [
      {
        test:/\.less$/,
        use:[
          'style-loader',
          {
            loader:'css-loader',
            options:{
              importLoaders: 1,
              // 開啟子產品化樣式
              modules:true
            }
          },
          'less-loader'
        ]
      }
    ],
  },
};
           

引入樣式時候就需要發生一些改變

// 非子產品化引用
import './index.less'
// 子產品化引用
import style from './index.less'
// 使用子產品化之後的樣式
el.className = style.xxx
           

還有諸多其他各種類型的loader,可以讓我們完成各種各樣的打包需求

附上文檔位址

在需要打包不同的檔案時,查閱文檔即可

Plugins

plugins(插件)可以在webpack運作到某些時刻時候,幫你做一些事情,比如clean-webpack-plugin可以在打包開始前幫我們清理掉上一次打包後的檔案,而html-webpack-plugin則可以在打包後自動生成一個引用了打包完成的js檔案的html檔案,并且支援指定模闆

const path = require("path");
// 引入插件
const { CleanWebpackPlugin } = require("clean-webpack-plugin");
const HtmlWebpackPlugin = require("html-webpack-plugin");

module.exports = {
  entry: "./src/index.js",
  output: {
    filename: "bundle.js",
    path: path.resolve(__dirname, "myDist"),
  },
  module: {
  	...
  },
  // 使用插件
  plugins: [
    new CleanWebpackPlugin(), 
    
    new HtmlWebpackPlugin({
      // 自定義title
      title:'html模闆',
      // 自定義檔案名
      filename:'index.html',
      // 自定義使用模闆的路徑
      template:'src/public/index.html'
    }),
  ],
};

           

devServer

webpack-dev-server 提供了一個簡單的 web 伺服器,并且能夠實時重新加載,安裝後在webpack.config中做簡單的配置即可使用

const path = require("path");
const { CleanWebpackPlugin } = require("clean-webpack-plugin");
const HtmlWebpackPlugin = require("html-webpack-plugin");

module.exports = {
  entry: "./src/index.js",
  devServer:{
  	// 告訴devServer在哪裡查找檔案
    contentBase: './myDist',
    // 啟動後自動浏覽器打開
    open:true,
    // 端口
    port:4396
  },
  output: {
    filename: "bundle.js",
    path: path.resolve(__dirname, "myDist"),
  },
  ...
};
           

但是這裡有一個坑,我用的是webpack5.x版本,運作後報錯了

webpack從入門及實戰(二)webpack核心概念

查閱了文檔的各種配置依舊沒有搞清楚問題所在,遂求助百度

原來是版本的問題

webpack、webpack-cli、webpack-dev-server是需要版本比對的

找到了一個可以運作的版本

“webpack”: “^4.43.0”,

“webpack-cli”: “^3.3.12”,

“webpack-dev-server”: “^3.11.0”

安裝代碼

更新版本之後,dev-server就可以正常使用了

但不得不說webpack的文檔實在是難以恭維…

子產品熱替換(HMR)

子產品熱替換(Hot Module Replacement 或 HMR)是 webpack 提供的最有用的功能之一,它允許在運作時更新各種子產品,而無需進行完全重新整理。

現在我們的dev-server在運作的時候,更改代碼雖然效果會更新,但是這個更新是直接重新整理頁面來更新的,而使用了HMR之後,就可以做到不重新整理頁面直接更新了

功能的啟用很簡單,插件也已經内置在webpack内部無需額外安裝

const path = require("path");
const { CleanWebpackPlugin } = require("clean-webpack-plugin");
const HtmlWebpackPlugin = require("html-webpack-plugin");
// 引入webpack
const webpack = require("webpack")

module.exports = {
  entry: "./src/index.js",
  devServer:{
    contentBase: './myDist',
    open:true,
    port:4396,
    // 啟用子產品熱更新
    hot:true
  },
  output: {
    filename: "bundle.js",
    path: path.resolve(__dirname, "myDist"),
  },
  plugins: [
    new CleanWebpackPlugin(),
    new HtmlWebpackPlugin({
      title:'html模闆',
      filename:'index.html',
      template:'src/public/index.html'
    }),
    // 使用插件
    new webpack.HotModuleReplacementPlugin()
  ],
};
           

這樣就可以實作HMR了,接下來修改樣式部分的代碼,頁面可以做到不重新整理直接更新,但js部分的代碼還是會重新整理的,如果想做到js代碼不重新整理,則需要通過HMR的api accept來手動配置相應的邏輯

首先要在配置檔案裡加多一個hotOnly配置

const path = require("path");
const { CleanWebpackPlugin } = require("clean-webpack-plugin");
const HtmlWebpackPlugin = require("html-webpack-plugin");
// 引入webpack
const webpack = require("webpack")

module.exports = {
  entry: "./src/index.js",
  devServer:{
    contentBase: './myDist',
    open:true,
    port:4396,
    // 啟用子產品熱更新
    hot:true,
    // 隻使用熱更新來更新頁面,不會自動重新整理頁面來更新
    hotOnly:true
  },
  output: {
    filename: "bundle.js",
    path: path.resolve(__dirname, "myDist"),
  },
  plugins: [
    new CleanWebpackPlugin(),
    new HtmlWebpackPlugin({
      title:'html模闆',
      filename:'index.html',
      template:'src/public/index.html'
    }),
    // 使用插件
    new webpack.HotModuleReplacementPlugin()
  ],
};
           

然後在引用了其他子產品的代碼裡加上對應處理的邏輯

這裡sayHi添加了一個id為hi的dom節點

// index.js
import sayHi from "./sayHi";

if (module.hot) {
  module.hot.accept(
    "./sayHi", // 監聽具體哪個子產品的改變
    function () {
      //手動的删除原來saiHi添加的dom節點,然後重新執行saiHi方法
      console.log('這樣就可以不重新整理頁面重新加載js檔案了')
      document.getElementById('root').removeChild(document.getElementById("hi"));
      sayHi();
    } // 用于在子產品更新後觸發的函數
  );
}
sayHi();
           

但是如果引入的子產品比較複雜,這裡的配置也會随之複雜很多,所有具體的情況還是要看業務的需求,重新整理一下頁面其實也問題不大…

Babel

通常我們會配置babel來幫我們将es6的文法轉換成es5的文法,來避免一些奇奇怪怪的問題(ie說的就是你)

首先需要安裝babel以及它的loader

npm install --save-dev babel-loader @babel/core @babel/preset-env
           

在index.js裡引入babel/polyfill

// 注入低版本浏覽器不支援的一些es6的新方法,諸如promise,map等
import "@babel/polyfill"
           

然後在loader裡面進行簡單的配置即可

module:{
  rules:[
      {
        test: /\.js$/,
        exclude: /node_modules/,
        loader: "babel-loader",
        options: {
          presets: ["@babel/preset-env"],
        },
      },
  ]
}
           

這時候再進行打包,就可以看到打包後代碼裡es6的文法已經轉換成es5的文法了

也可以做更多的一些适配

module:{
  rules:[
      {
        test: /\.js$/,
        exclude: /node_modules/,
        loader: "babel-loader",
        options: {
          presets: [["@babel/preset-env",{
            // 隻将用到的es6文法整合進去,沒有用到的不整合,減少打包後的大小
            useBuiltIns:'usage'
          }]],
        },
      },
  ]
}
           

這樣配置之後就可以把引入的babel/polyfill去掉了,他會在需要的時候被自動引入

更多的配置可以參考babel的文檔

SourceMap

SourceMap是一種映射關系,可以幫助我們在代碼出現錯誤的時候定位到錯誤的位置,他的配置對應的是config中的devtool項

先将它設定為none

const path = require("path");
const { CleanWebpackPlugin } = require("clean-webpack-plugin");
const HtmlWebpackPlugin = require("html-webpack-plugin");

module.exports = {
  entry: "./src/index.js",
  devtool:false,
  output: {
    filename: "bundle.js",
    path: path.resolve(__dirname, "myDist"),
  },
  ...
};
           
// index.js
// 在代碼裡添加一句錯誤的語句
console.llog('錯啦')
           

打包是可以正常執行的,但是我們打開頁面後控制台會報錯

webpack從入門及實戰(二)webpack核心概念

點選檢視source後會發現對應的地方是打包之後的代碼位置,而不是我們源代碼的位置,這對于我們定位錯誤沒有太大幫助,而想要讓他定位到我們源代碼的位置,就需要配置SourceMap

webpack從入門及實戰(二)webpack核心概念
const path = require("path");
const { CleanWebpackPlugin } = require("clean-webpack-plugin");
const HtmlWebpackPlugin = require("html-webpack-plugin");

module.exports = {
  entry: "./src/index.js",
  devtool:'source-map',
  output: {
    filename: "bundle.js",
    path: path.resolve(__dirname, "myDist"),
  },
  ...
};
           

更改devtool配置再次打包後,點選檢視source就會幫我們定位到源碼的位置了,打包的檔案也會多一個map檔案

webpack從入門及實戰(二)webpack核心概念

官方的source-map有很多的配置

inline代表不生成map檔案,将map檔案以base64格式打包至js檔案内

cheap代表是否定位錯誤出現在代碼的第幾行和第幾個字元,帶上cheap代表不定位出現在第幾個字元,隻定位第幾行

module代表是否定位一些子產品的錯誤

eval代表以js的eval形式來執行代碼,不過這種方式在代碼量較大的環境下可能無法準确定位錯誤

開啟SourceMap會造成性能方面的影響,是以通常我們的做法是在開發環境下開啟source-map,正式上線時候關閉

我個人會在開發環境下使用’cheap-module-eval-source-map’這種方式提示的錯誤比較全面,同時打包的速度也比較快

線上環境通常是直接設定為false不開啟,如果出了問題需要迅速定位錯誤的話,使用’cheap-module-source-map’可以幫助我們能準确定位錯誤

webpack從入門及實戰(二)webpack核心概念

關于webpack一些核心的理念基本已經介紹完畢,後續的博文會介紹webpack進階的一些進階概念