深入解析webpack的五個核心概念

2022-08-09 18:02:09
webpack 是一種前端資源構建工具,一個靜態模組打包器(module bundler);webpack有5大核心概念(入口、輸出、loader、外掛、模式) ,下面本篇文章就來帶大家深入瞭解一下,希望對大家有所幫助!

webpack 五個核心概念

1. Entry

入口(Entry)指示 webpack 以哪個檔案為入口起點開始打包,分解構建內部依賴圖。

2. Output

輸出(Output)指示 webpack 打包後的資源 bundles 輸出到哪裡去,以及如何命名。

3. Loader

webpack 只能理解 JavaScript 和 JSON 檔案,這是 webpack 開箱可用的自帶能力。loader 讓webpack 能夠去處理其他型別的檔案,並將它們轉換為有效 模組,以供應用程式使用,以及被新增到依賴圖中。

4. Plugins

外掛(Plugins)可以用於執行範圍更廣的任務。外掛的範圍包括,從打包優化和壓縮,一直到重新定義環境中的變數等。

5. Mode

模式(Mode)指示 webpack 使用相應模式的設定。

下面就來給大家詳細介紹一下webpack這五個核心概念。

1、入口(Entry)

entry 物件是用於 webpack 查詢啟動並構建 bundle。entry 是應用程式的起點入口,從這個起點開始,應用程式啟動執行。如果傳遞一個陣列,那麼陣列的每一項都會執行。入口起點(entry point) 指示 webpack 應該使用哪個模組,來作為構建其內部依賴圖(dependency graph) 的開始。進入入口起點後,webpack 會找出有哪些模組和庫是入口起點(直接和間接)依賴的。

簡單規則:每個 HTML 頁面都有一個入口起點。單頁應用(SPA):一個入口起點,多頁應用(MPA):多個入口起點。

預設值是 ./src/index.js,但你可以通過在 webpack configuration 中設定 entry 屬性,來指定一個(或多個)不同的入口起點。例如:

//單入口--字串
module.exports = {
  entry: './path/to/my/entry/file.js',
};
//多入口--陣列
module.exports = {
  entry: ['./src/index.js', './src/add.js']
};
//多入口--物件
module.exports = {
  entry: {
    home: './home.js',
    about: './about.js',
    contact: './contact.js'
  }
};

entry的值型別:

  • 字串:單入口,打包形成一個chunk,最終只會輸出一個bundle檔案,chunk 的名稱預設是 main

  • 陣列:多入口,所有的入口檔案最終也只會形成一個chunk,最終輸出一個 bundle 檔案,chunk 的名稱預設為 main。一般只用在 HMR 功能中讓html熱更新生效

  • 物件:多入口,有多少個 key 就會形成多少個chunk,也就輸出多少個 bundle 檔案,每個鍵(key)會是 chunk 的名稱。在物件型別中,每個key的值還可以是一個陣列,不僅僅是一個字串

2、輸出(output)

output 指示 webpack 如何去輸出、以及在哪裡輸出你的bundle、asset 和其他你所打包或使用 webpack 載入的任何內容。輸出的 bundle 的預設值是 ./dist/main.js,其他生成檔案預設放置在 ./dist 資料夾中。

你可以通過在設定中指定一個 output 欄位,來設定這些處理過程:

//webpack.config.js
const path = require('path');
module.exports = {
  entry: './path/to/my/entry/file.js',
  output: {
    path: path.resolve(__dirname, 'dist'),
    filename: 'my-first-webpack.bundle.js',
  },
};

我們可以通過 output.filenameoutput.path 屬性,來告訴 webpack bundle 的名稱,以及 bundle 生成到哪裡。

2.1、output.filename(檔名和目錄)

此選項決定了每個輸出 bundle 的目錄和名稱。這些 bundle 將寫入到 output.path 選項指定的目錄下。

對於單個入口起點,filename 會是一個靜態名稱。然而,當通過多個入口起點(entry point)、程式碼拆分(code splitting)或各種外掛(plugin)建立多個 bundle,應該使用其他方法來讓每個 bundle 都有一個唯一的名稱。

//單入口時:
module.exports = {
  //...
  output: {
    filename: 'js/bundle.js'
  }
};
//多入口--使用入口名稱:
module.exports = {
  //...
  output: {
    filename: '[name].bundle.js'
  }
};
//多入口--使用每次構建過程中,唯一的 hash 生成
module.exports = {
  //...
  output: {
    filename: '[name].[hash].bundle.js'
  }
};
...

2.2、output.path(檔案目錄)

output.path 指定所有輸出檔案的目錄,即將來所有資源輸出的公共目錄。path 必須是絕對路徑。

module.exports = {
  //...
  output: {
    path: path.resolve(__dirname, 'dist/assets')
  }
};

2.3、output.publicPath(參照資源的路徑字首)

publicPath 指定的是 html 檔案中的所有資源引入的公共路徑字首。它並不會對生成檔案的路徑造成影響,而是在 html 檔案引入各種資源時,將 publicPath 作為字首加到引入資源的路徑前面。

範例:

在 vue-cli 生成的 webpack 設定中,生產環境下 publicPath 的值預設是 '/',即當前目錄的根目錄。

打包過後,我們開啟 html 檔案,可以看到 html 檔案中引入的資源路徑為:

可以看到,都在路徑前面加了 / 符號。當我們開啟瀏覽器存取生成的 html 檔案時,會發現報錯,資源存取不到,報404,此時資源的存取類似如下:

在伺服器上可能會是如下,但存取一樣可能會有問題。

我們可以將 publicPath 修改為相對路徑,或者直接把它註釋掉也行。

2.3.1、path和publicPath的區別

  • path 指定的是打包後檔案在硬碟中的儲存位置,是webpack所有檔案的輸出的路徑,必須是絕對路徑。比如:輸出的js、圖片,HtmlWebpackPlugin生成的html檔案等,都會存放在以path為基礎的目錄下。
  • publicPath 並不會對生成檔案的路徑造成影響,主要是對你的頁面裡面引入的資源的路徑做對應的補全。

2.4、output.chunkFilename(非入口chunk的名稱)

output.chunkFilename 決定了非入口(non-entry) chunk 檔案的名稱。也就是除了入口檔案生成的chunk外,其他檔案生成的chunk檔案命名。

module.exports = {
  //...
  output: {
    chunkFilename: 'js/[name]_chunk.js'   //非入口chunk的名稱
  }
};

3、loader

webpack 本身只能打包 JavaScript 和 JSON 檔案(webpack3+和webpack2+內建可處理JSON檔案,但webpack1+並不支援,需要引入json-loader),這是 webpack 開箱可用的自帶能力。webpack 本身不支援打包其他型別檔案,比如 css、vue 等,但我們可以通過各種 loader 來讓 webpack 去處理這些型別的檔案。loader 可以將檔案從不同的語言(如 TypeScript)轉換為 JavaScript 或將內聯影象轉換為 data URL,loader 甚至允許你直接在 JavaScript 模組中 import CSS檔案!

通過使用不同的loaderwebpack有能力呼叫外部的指令碼或工具,實現對不同格式的檔案的處理,比如說分析轉換 scss為css,或者把下一代的JS檔案(ES6,ES7)轉換為現代瀏覽器相容的JS檔案。對React的開發而言,合適的Loaders可以把React的中用到的JSX檔案轉換為JS檔案。

在 webpack 的設定中,loader 有兩個屬性:

  • test 屬性,識別出哪些檔案會被轉換。

  • use 屬性,定義出在進行轉換時,應該使用哪個 loader。

  • include/exclude(可選):手動新增必須處理的檔案(資料夾)或遮蔽不需要處理的檔案(資料夾)

  • query(可選):為loaders提供額外的設定選項

//範例:webpack.config.js
const path = require('path');
module.exports = {
  output: {
    filename: 'my-first-webpack.bundle.js',
  },
  module: {
    rules: [
        { test: /\.txt$/, loader: 'raw-loader' },
        { test: /\.css$/, use: ['style-loader', 'css-loader'] }   //使用多個loader的話應該用 use
    ], 
  },
};

以上設定中,對一個單獨的 module 物件定義了 rules 屬性,裡面包含兩個必須屬性:testuse。這相當於告訴 webpack 編譯器在碰到 require()/import 語句中被解析為 '.txt' 的路徑時,在對它打包之前,先使用raw-loader 轉換一下。

使用多個loader的話應該用 use,use 陣列中的 loader 執行順序:從右到左,依次執行。比如上面的 css 檔案,首先 css-loader 會將 css 檔案編譯成 JS 載入到 JS檔案中,然後再由 style-loader 建立 style 標籤,將 JS 中的樣式資源插入到 head 標籤中。

3.1、CSS-loader

webpack提供兩個工具處理樣式表,css-loaderstyle-loader,二者處理的任務不同。css-loader使你能夠使用類似import的方法來引入 css 檔案,style-loader將所有的計算後的樣式加入頁面中,二者組合在一起使你能夠把樣式表嵌入webpack打包後的JS檔案中,由此就可以在JS檔案中引入css檔案了。

//安裝
npm install --save-dev style-loader css-loader  //css-loader版本太高編譯可能會出錯,建議降低版本比如 [email protected] 可用
//使用
module.exports = {
   ...
    module: {
        rules: [
            {
                test: /(\.jsx|\.js)$/,
                use: {
                    loader: "babel-loader"
                },
                exclude: /node_modules/
            },
            {
                test: /\.css$/,  //對同一個檔案引入多個loader的方法。loader的作用順序是後面的loader先開始作用
                use: [
                    {
                        loader: "style-loader"
                    }, {
                        loader: "css-loader"
                    }
                ]
            }
        ]
    }
};

假設有一個 main.css 檔案:

body {
  backgroud: green;
}

為了讓webpack能找到」main.css「檔案,我們把它匯入」main.js 「中,如下:

//main.js
import React from 'react';
import {render} from 'react-dom';
import Greeter from './Greeter';
import './main.css';//使用require匯入css檔案
 
render(<Greeter />, document.getElementById('root'));

通常情況下,css會和js打包到同一個檔案中,並不會打包為一個單獨的css檔案。不過通過合適的設定webpack也可以把css打包為單獨的檔案的。

4、外掛(plugin)

loader 用於轉換某些型別的模組,而外掛則可以用於執行範圍更廣的任務,包括:打包優化、壓縮、資源管理、注入環境變數等。外掛目的在於解決 loader 無法實現的其他事。

要使用某個外掛,我們需要通過npm安裝它,然後在 plugins 屬性下新增該外掛的一個範例。由於外掛可以攜帶引數/選項,你必須在 webpack 設定中,向 plugins 屬性傳入 new 範例。多數外掛可以通過選項自定義,你也可以在一個組態檔中因為不同目的而多次使用同一個外掛。

//webpack.config.js
const HtmlWebpackPlugin = require('html-webpack-plugin'); // 通過 npm 安裝
const webpack = require('webpack'); // 用於存取內建外掛
module.exports = {
  module: {
    rules: [{ test: /\.txt$/, use: 'raw-loader' }],
  },
  plugins: [new HtmlWebpackPlugin({ template: './src/index.html' })],
};

在上面的範例中,html-webpack-plugin 為應用程式生成一個 HTML 檔案,並自動注入所有生成的 bundle。

4.1、BannerPlugin外掛(新增版權說明)

下面我們新增了一個給打包後程式碼新增版權宣告的外掛。該外掛是webpack中的內建外掛不用安裝。

const webpack = require('webpack');
module.exports = {
...
    module: {
        rules: [
            {
                test: /(\.jsx|\.js)$/,
                use: {
                    loader: "babel-loader"
                },
                exclude: /node_modules/
            },
            {
                test: /\.css$/,
                use: [
                    {
                        loader: "style-loader"
                    }, {
                        loader: "css-loader",
                        options: {
                            modules: true
                        }
                    }, {
                        loader: "postcss-loader"
                    }
                ]
            }
        ]
    },
    plugins: [
        new webpack.BannerPlugin('wenxuehai版權所有,翻版必究')
    ],
};

4.2、Hot Module Replacement 外掛(熱載入)

Hot Module Replacement(HMR)是webpack裡很有用的一個外掛,它允許你在修改元件程式碼後,自動重新整理實時預覽修改後的效果。熱載入和webpack-dev-server不同,熱替換在應用執行時,無需重新整理頁面,便能檢視程式碼更新後的效果 ,就跟直接在瀏覽器上修改dom樣式一樣,而webpack-dev-server是要重新整理頁面的。

(1)在webpack組態檔中新增HMR外掛;

(2)在Webpack Dev Server中新增「hot」引數;

4.2.1、react實現熱載入

React模組可以使用Babel實現功能熱載入。Babel有一個叫做react-transform-hrm的外掛,可以在不對React模組進行額外的設定的前提下讓HMR正常工作;

安裝react-transform-hmr

npm install --save-dev babel-plugin-react-transform react-transform-hmr
const webpack = require('webpack');
module.exports = {
    entry: __dirname + "/app/main.js",//已多次提及的唯一入口檔案
    output: {
        path: __dirname + "/public",
        filename: "bundle.js"
    },
    devtool: 'eval-source-map',
    devServer: {
        contentBase: "./public",//本地伺服器所載入的頁面所在的目錄
        historyApiFallback: true,//不跳轉
        inline: true,
        hot: true
    },
    module: {
        rules: [
            {
                test: /(\.jsx|\.js)$/,
                use: {
                    loader: "babel-loader"
                },
                exclude: /node_modules/
            },
            {
                test: /\.css$/,
                use: [
                    {
                        loader: "style-loader"
                    }, {
                        loader: "css-loader",
                        options: {
                            modules: true
                        }
                    }, {
                        loader: "postcss-loader"
                    }
                ]
            }
        ]
    },
    plugins: [
        new webpack.BannerPlugin('版權所有,翻版必究'),
     new webpack.HotModuleReplacementPlugin()  //熱載入外掛
    ],
};

設定Babel

// .babelrc
{
  "presets": ["react", "env"],
  "env": {
    "development": {
    "plugins": [["react-transform", {
       "transforms": [{
         "transform": "react-transform-hmr",         
         "imports": ["react"],       
         "locals": ["module"]
       }]
     }]]
    }
  }
}
//Greeter,js
import React, {
  Component
} from 'react'
import styles from './main.css'
 
class Greeter extends Component {
  render() {
    return ( 
        < div>
          <h1>
            aaaf
          </h1>
        </div>
    );
  }
}
export default Greeter
//main.js
import React from 'react';
import {
  render
} from 'react-dom';
import Greeter from './greeter.js';
 
render( < Greeter / > , document.getElementById('root'));

現在如果我們就可以實現熱載入模組了,每次儲存就能在瀏覽器上直接看到更新內容,瀏覽器不必重新整理也不會自動重新整理。

(有時候沒有效果可能是版本問題)

4.3、ExtractTextWebpackPlugin外掛(抽離css)

在預設情況下,webpack 不會將 css 樣式作為一個獨立檔案,而是會將 css 也打包到 js 檔案中,打包生成的 js 檔案在渲染時會通過 js 語法來將樣式通過 style 標籤的形式來插入到頁面中。但是這樣的話,打包出來的 bundle 檔案可能會過大,此時我們可以通過 ExtractTextWebpackPlugin 外掛來將 css 樣式獨立成 css 檔案。

ExtractTextWebpackPlugin 外掛會將入口 chunk 中參照到的 *.css(包括引入的css檔案和vue檔案中style所寫的樣式),移動到一個獨立分離的 CSS 檔案中。ExtractTextPlugin 對每個入口 chunk 都會生成一個對應的 css檔案,也就是說一個入口對應著一個 css 檔案,多個入口的話就會分別生成多個對應的 css 檔案。

通過 ExtractTextWebpackPlugin 外掛,你的樣式將不再內嵌到 JS bundle 中,而是會放到一個單獨的 CSS 檔案(即 styles.css)當中。 如果你的樣式檔案大小較大,這會做更快提前載入,因為 CSS bundle 會跟 JS bundle 並行載入。

const ExtractTextPlugin = require("extract-text-webpack-plugin");
module.exports = {
  module: {
    rules: [
      {
        test: /\.css$/,
        use: ExtractTextPlugin.extract({
          fallback: "style-loader",
          use: "css-loader"
        })
      }
    ]
  },
  plugins: [
    new ExtractTextPlugin({
      filename: utils.assetsPath('css/[name].[contenthash].css'),   //ExtractTextPlugin 對每個入口 chunk 都生成一個對應的檔案,所以當你設定多個入口 chunk 的時候,必須使用 [name], [id] 或 [contenthash]
      // allChunks: true,   //當使用 `CommonsChunkPlugin` 並且在公共 chunk 中有提取的 chunk(來自`ExtractTextPlugin.extract`)時,`allChunks` **必須設定為 `true`。
    }),
  ]
}

4.3.1、allChunks選項(是否也將非同步載入的樣式一起提取出來)

ExtractTextWebpackPlugin 外掛的 allChunks 選項的預設值為 false。

allChunks 選項的意思是是否需要將非同步載入的樣式一起提取出來。因為在預設情況下,就算使用了 ExtractTextWebpackPlugin 外掛,如果該樣式或者樣式檔案是非同步載入的話,那麼這些樣式是不會被提取到獨立的 css 檔案中的,而是仍然會打包到 js 檔案中。

所以allChunks:false為預設值,預設是從 entry 的入口提取程式碼,但是不會提取非同步載入的程式碼;allChunks:true則是提取所有模組的程式碼(包括非同步載入的模組)到一個檔案裡面。如果使用了非同步載入樣式,但是 allChunks 又設為了 false,那麼我們就需要設定 ExtractTextPlugin.extract 的 fallback, fallback是在非同步程式碼載入的 css 程式碼沒有被提取的情況下, 以style-loader的情況去載入非同步元件的樣式。

可參考:

https://github.com/sevenCon/blog-github/blob/master/articles/webpack學習筆記(2)-ExtractTextWebpackPlugin的使用.md

https://blog.csdn.net/weixin_41134409/article/details/88416356

5、模式(mode)

通過選擇 development, productionnone 之中的一個,來設定 mode 引數,你可以啟用 webpack 內建在相應環境下的優化。其預設值為 production

module.exports = {
  mode: 'production',
};

在組態檔中直接設定 mode 選項將告知 webpack 使用相應模式的內建優化,mode選項有development、production、none。

development : 開發模式,打包的程式碼不會被壓縮,開啟程式碼偵錯,
production : 生產模式,則正好反之。

將 mode 設為development或者production,webpack會自動同時也設定 process.env.NODE_ENV 的值,我們可以在任何資料夾中直接拿到該值。但如果只設定 NODE_ENV,則不會自動設定 mode。(在node中,全域性變數 process 表示的是當前的node程序。process.env 屬性包含著使用者環境的資訊。process.env 本身並不存在NODE_ENV這個屬性,我們一般會自己去定義 NODE_ENV 屬性,用它來判斷是生產環境還是開發環境)

(請注意:mode選項是webpack4新增的,在4之前都是用DefinePlugin外掛設定,webpack4把DefinePlugin刪除了)

5.1、vue-cli專案mode設定詳解

在 webpack 中,一般都會在組態檔中設定 NODE_ENV 的值。在使用 vue-cli 預設生成的 vue 專案中,NODE_ENV 設定情況如下:

//webpack.dev.conf.js 檔案下,引入了 dev.env.js 檔案
new webpack.DefinePlugin({
      'process.env': require('../config/dev.env')
}),
//dev.env.js 檔案中
module.exports = merge(prodEnv, {
  NODE_ENV: '"development"'
})
//webpack.prod.conf.js 檔案下,引入了 prod.env.js 檔案
const env = require('../config/prod.env')
new webpack.DefinePlugin({
      'process.env': env
}),
//prod.env.js 檔案中
module.exports = {
  NODE_ENV: '"production"'
}

從上面可以知道,在開發環境下,組態檔將 NODE_ENV 設定成了 'development';在生產環境下,組態檔將 NODE_ENV 設定成了 'production'。

我們在執行專案時,會執行 npm run dev 或者 npm run build,這兩個命令時使用了開發環境或者生產環境的組態檔來生成執行專案,由此也對應著設定了對應的 NODE_ENV 的值,我們也就能夠在專案的任一檔案中(組態檔不一定,因為要看設定了 NODE_ENV 的值的組態檔有沒有生效了才行)獲取到對應的 NODE_ENV 的值。

5.2、process.env.NODE_ENV設定

process 是 node 的全域性變數,並且 process 有 env 這個屬性,但是沒有 NODE_ENV 這個屬性。NODE_ENV 變數並不是 process.env 直接就有的,而是通過設定得到的,但是 NODE_ENV 變數通常約定用於定義環境型別。這個變數的作用是:我們可以通過判斷這個變數區分開發環境或生產環境。

(1)可以通過webpack的內建外掛 DefinePlugin 來設定全域性變數值:

new webpack.DefinePlugin({
      'process.env.NODE_ENV': JSON.stringify('production')
}),

設定完後在執行指令碼上可以取到該值,比如:

// main.js
console.log(process.env.NODE_ENV);    //production

但是在webpack的組態檔 webpack.config.js 中取不到該值。

(2)通過 cross-env 包設定

先下載 cross-env 包:

cnpm i cross-env -D

設定 package.json 檔案:

"build": "cross-env NODE_ENV=test webpack --config webpack.config.js"

此時在組態檔中可以取到該值(process.env.NODE_ENV),但是在可執行指令碼中取不到,需要配合DefinePlugin 外掛使用

更多程式設計相關知識,請存取:!!

以上就是深入解析webpack的五個核心概念的詳細內容,更多請關注TW511.COM其它相關文章!