1. 程式人生 > 實用技巧 >什麼是webpack?Webpack的核心概念

什麼是webpack?Webpack的核心概念

什麼是webpack?

一句話概括:webpack是一個模組打包工具(module bundler)。重點在於兩個關鍵詞“模組”和“打包”。什麼是模組呢?我們回顧一下曾經的前端開發方式,js檔案通過script標籤靜態引入,js檔案之間由於沒有強依賴關係,如果檔案1要用到檔案2的某些方法或變數,則必須將檔案1放到檔案2後面載入。隨著專案的增大,js檔案之間的依賴關係越發錯綜複雜,維護難度也越來越高。這樣的困境驅使著前端工程師們不斷探索新的開發模式,從後端、app的開發模式中我們獲得靈感,為什麼不能引入“模組”的概念讓js檔案之間可以相互引用呢?模組1要使用模組2的功能,只需要在該模組1中明確引用模組2就行了,而不用擔心它們的排列順序。基於這種理念,CommonJS和AMD規範被創造了出來,然後有了require.js、system.js這樣的前端模組載入工具和node的模組系統,直到現在流行的es6 module。

模組的引入解決了檔案之間依賴引用的問題,而打包則解決了檔案過多的問題。當專案規模增大,模組的數量數以千計,瀏覽器如果要載入如此多的檔案,頁面載入的速度勢必會受影響,而bundler可以把多個關聯的檔案打包到一起從而大量減少檔案的數量提高網頁載入效能。提供模組化的開發方式和編譯打包功能就是webpack的核心,其他很多功能都圍繞它們展開。

核心概念

Module(模組)

對於webpack,模組不僅僅是JavaScript模組,它包括了任何型別的原始檔,不管是圖片、字型、json檔案都是一個個模組。Webpack支援以下的方式引用模組:

  • ES2015import方法
  • CommonJsrequire()方法
  • AMDdefine和require語法
  • css/sass/less檔案中的@import語法
  • url(...)和 <img src=...>中的圖片路徑

Dependency Graph(依賴關係圖)

所謂的依賴關係圖是webpack根據每個模組之間的依賴關係遞迴生成的一張內部邏輯圖,有了這張依賴關係圖,webpack就能按圖索驥把所有需要模組打包成一個bundle檔案了。

Entry(入口)

繪製依賴關係圖的起始檔案被稱為entry。預設的entry為./src/index.js,或者我們可以在配置檔案中配置。entry可以為一個也可以為多個。

單個entry:

module.exports = {
  entry: './src/index.js'
}

或者

module.exports = {
  entry: {
    main: './src/index.js'
  }
};

多個entry,一個chunk

我們也可以指定多個獨立的檔案為entry,但將它們打包到一個chunk中,此種方法被稱為multi-main entry,我們需要傳入檔案路徑的陣列:

module.exports = {
  entry: ['./src/index.js', './src/index2.js', './src/index3.js']
}

但是改種方法的靈活性和擴充套件性有限,因此並不推薦使用。

多個entry,多個chunk

如果有多個entry,並且每個entry生成對應的chunk,我們需要傳入object:

module.exports = {
  entry: {
    app: './src/app.js',
    admin: './src/admin.js'
  }
};

這種寫法有最好的靈活性和擴充套件性,支援和其他的區域性配置(partial configuration)進行合併。比如將開發環境和生產的配置分離,並抽離出公共的配置,在不同的環境下執行時再將環境配置和公共配置進行合併。

Output(出口)

有了入口,對應的就有出口。顧名思義,出口就是webpack打包完成的輸出,output定義了輸出的路徑和檔名稱。Webpack的預設的輸出路徑為./dist/main.js。同樣,我們可以在配置檔案中配置output:

module.exports = {
  entry: './src/index.js',
  output: {
    path: __dirname + '/dist',
    filename: 'bundle.js'
  }
};

多個entry的情況

當有多個entry的時候,一個entry應該對應一個output,此時輸出的檔名需要使用替換符(substitutions)宣告以確保檔名的唯一性,例如使用入口模組的名稱:

module.exports = {
  entry: {
    app: './src/app.js',
    search: './src/search.js'
  },
  output: {
    filename: '[name].js',
    path: __dirname + '/dist'
  }
}

最終在./dist路徑下面會生成app.js和search.js兩個bundle檔案。

Loader(載入器)

Webpack自身只支援載入js和json模組,而webpack的理念是讓所有的檔案都能被引用和載入並生成依賴關係圖,所以loader出場了。Loader能讓webpack能夠去處理其他型別的檔案(比如圖片、字型檔案、xml)。我們可以在配置檔案中這樣定義一個loader:

webpack.config.js

module.exports = {
  module: {
    rules: [
      { 
        test: /\.txt$/, 
        use: 'raw-loader' 
      }
    ]
  }
};

其中test定義了需要被轉化的檔案或者檔案型別,use定義了對該檔案進行轉化的loader的型別。該條配置相當於告訴webpack當遇到一個txt檔案的引用時(使用require或者import進行引用),先用raw-loader轉換一下該檔案再把它打包進bundle。

還有其他各種型別的loader,比如載入css檔案的css-loader,載入圖片和字型檔案的file-loader,載入html檔案的html-loader,將最新JS語法轉換成ES5的babel-loader等等。完整列表請參考webpack loaders。

Plugin(外掛)

Plugin和loader是兩個比較混淆和模糊的概念。Loader是用來轉換和載入特定型別的檔案,所以loader的執行層面是單個的原始檔。而plugin可以實現的功能更強大,plugin可以監聽webpack處理過程中的關鍵事件,深度整合進webpack的編譯器,可以說plugin的執行層面是整個構建過程。Plugin系統是構成webpack的主幹,webpack自身也基於plugin系統搭建,webpack有豐富的內建外掛和外部外掛,並且允許使用者自定義外掛。官方列出的外掛有這些。

與loader不同,使用plugin我們必須先引用該外掛,例如:

const webpack = require('webpack'); // 用於引用webpack內建外掛
const HtmlWebpackPlugin = require('html-webpack-plugin'); // 外部外掛

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

實踐

瞭解webpack的基本概念之後,我們通過實踐來加深理解。接下來,我們使用webpack搭建一個簡易的react腳手架。

建立專案

首先建立react-webpack-starter專案並使用npminit初始化。

安裝依賴

安裝react

npmi react react-dom

安裝webpack相關

npmi -D webpack webpack-cli webpack-dev-server html-webpack-plugin style-loader css-loader

安裝webpack-cli後可以在命令列中執行webpack命令;webpack-dev-server提供了簡易的web伺服器,並且在修改檔案之後自動執行webpack的編譯操作並自動重新整理瀏覽器,省去了重複的手動操作;html-webpack-plugin用於自動生成index.html檔案,並且在index.html中自動新增對bundle檔案的引用;style-loader和css-loader用於載入css檔案。

安裝babel相關

由於react中使用了class,import這樣的es6的語法,為了提高網站的瀏覽器相容性,我們需要用babel轉換一下。

npmi -D@babel/core@babel/preset-env@babel/preset-react babel-loader

其中@babel/core是babel的核心模組,包含了babel的核心功能;@babel/preset-env支援轉換ES6以及更新的js語法,並且可根據需要相容的瀏覽器型別選擇載入的plugin從而精簡生成的程式碼;@babel/preset-react包含了babel轉換react所需要的plugin;babel-loader是webpack的babel載入器。

配置webpack

在專案根目錄下面新建webpack.config.js,內容如下:

webpack.config.js

const path = require('path');
const HtmlWebpackPlugin = require('html-webpack-plugin');

module.exports = {
  entry: './src/index.js',
  output: {
    path: path.resolve(__dirname, 'dist'),
    filename: 'bundle.js'
  },
  module: {
    rules: [
      {
        test: /\.js$/,
        exclude: /node_module/,
        use: 'babel-loader'
      },
      {
        test: /\.css$/,
        use: ['style-loader', 'css-loader'] // 注意排列順序,執行順序與排列順序相反
      }
    ]
  },
  plugins: [
    new HtmlWebpackPlugin({
      template: './src/index.html'
    })
  ]
}

其中HtmlWebpackPlugin使用自定義的模版來生成html 檔案,模版的內容如下:

./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>My React App</title>
</head>
<body>
  <div id="app"></div>
</body>
</html>

配置babel

在專案根目錄下面新建.babelrc檔案,配置我們安裝的兩個babel preset:

.babelrc

{
  "presets": [
    "@babel/preset-env",
    "@babel/preset-react"
  ]
}

生成react應用根節點

./src/index

import React from 'react';
import ReactDOM from 'react-dom';
import App from './components/App';

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

./src/component/App.js

import React, { Component } from 'react';
import './App.css';

export default class App extends Component {
  render() {
    return (
      <div>
        my react webpack starter
      </div>
    )
  }
}

./src/components/App.css

body{
  font-size: 60px;
  font-weight: bolder;
  color: red;
  text-transform: uppercase;
}

資源搜尋網站大全 https://www.renrenfan.com.cn 廣州VI設計公司https://www.houdianzi.com

配置package.json

最後,在package.json檔案裡面加上兩個scripts,用來執行開發伺服器和打包:

package.json

"scripts": {
  "start": "webpack-dev-server --mode development --open --hot",
  "build": "webpack --mode production"
}

注意,我們啟用了webpack-dev-server的模組熱更新功能(HMR),進一步提高我們的開發效率。