webpack 4 入門

導讀

寫這篇文章是爲了讓本身在自學 webpack 的過程當中有所產出,因而邊讀 webpack 中文文檔 邊寫下了這篇文章,裏面的不少實例都是直接挪用的文檔中的實例,但在一些概念的理解上我加入了本身的想法「未必精確」,因此讀的時候要抱着「懷疑的態度」。javascript

文章內容不只僅是簡單的「概念堆疊」,還有一些「重點」概念的「深刻理解」,不過篇幅有限我不但願這篇文章變成一份冗長的僞文檔,因此所有的內容都是圍繞 webpack 的 4個 核心概念延展開來的,每一個配置後面我都會盡可能跟上一個實例以更加形象的展現配置的具體做用。css

站在個人角度上,讀完這篇文章並不能讓你精通 webpack 可是理解 webpack 中的重要概念,本身編寫一個 webpack.config.js 配置文件仍是能夠的。html

webpack 簡介

本質上,webpack 是一個現代 JavaScript 應用程序的靜態模塊打包器(static module bundler)。在 webpack 處理應用程序時,它會在內部建立一個依賴圖(dependency graph),用於映射到項目須要的每一個模塊,而後將全部這些依賴生成到一個或多個bundle。前端

來自 webpack 中文文檔vue

目前都是使用一些成熟的 CLI 工具,通常都內置 webpack 因此我對 webpack 的認知一直比較少,只是大概的瞭解它是用來管理項目中的 .js 文件依賴,而後打包整個項目的。java

核心概念

1. 入口(entry)

對應屬性:entry 默認值:./src/index.jsnode

做用說明: 用來規定 webpack 應該使用哪一個模塊做爲構建內部依賴圖的起點。 webpack 會找出全部「入口模塊」(直接或間接)依賴的「模塊」和 [library]。webpack

代碼示例:git

// weboack.config.js
module.exports = {
  entry: './path/to/entry/file.js'
}
複製代碼

2. 出口(output)

對應屬性:output 主輸出文件默路徑:./dist/main.js 其餘文件默認路徑:./dist/<filename>github

做用說明: 用來規定 webpack 在那裏輸出 bundles 以及如何命名這些文件。

// weboack.config.js
const path = require('path') // Node.js 核心模塊,用於操做文件路徑

代碼示例:
module.exports = {
  entry: './path/to/entry/file.js',
  output: {
    path: path.resolve(__dirname, 'dist'),
    filename: '<WhateverYouLike>.js'
  }
}
複製代碼

3. 處理器(loader)

對應屬性:module->rules

做用說明: 做爲開箱即用的自帶特性,webpack 自身只支持處理 JavaScript 文件。而 loader 可以讓 webpack 處理那些非 JavaScript 文件,而且先將它們轉換爲有效「模塊」,而後添加到「依賴圖」中,提供給應用程序使用。

屬性特徵:

  1. test: 利用「正則表達式」規定 loader 用於哪些或哪一個文件。
  2. use: 規定運行時使用哪一個 loader

代碼示例:

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

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

代碼做用: 當運行包含 .txt 文件的 require() 或 import 語句時,在它打包以前,先使用 raw-loader 轉換。

4. 插件(plugins)

對應屬性:plugings

做用說明: 打包優化、資源管理和注入環境變量。

代碼實例:

// webpack.config.js
const HtmlWebpackPlugn = require('html-webpack-plugin') // 提早經過 npm 安裝
const webpack = require('webpack') //用於訪問內置插件

module.exports = {
  ...
  plugins: [
    new HtmlWebpackPlugin({template: './src/index.html'})
  ]
}
複製代碼

核心概念解析及拓展

1. 入口(entry)

單入口及其簡寫

// webpack.config.js
module.exports = {
  entry: {
    main: './path/to/entry/file.js'
  }
}

// 可簡寫爲以下形式
module.exports = {
  enrty: './path/to/enrty/file.js'
}

/* * 當你須要爲只有一個入口的應用程序或工具(library)快速設置 webpack 配置時, * 簡寫會是個很不錯的選擇。然而,使用此語法在擴展配置時有失靈活性。 */
複製代碼

思考:當你向 entry 傳入一個數組時會發生什麼? 解釋:向 entry 傳入「文件路徑數組」將建立「多個主入口」。在你想要多個依賴文件一塊兒注入,而且將它們的依賴導向到一個 chunk 時,傳入數組的方式就頗有用。

對象語法

用法:entry: {<enrtyChunkName: String>: <Path: String | Array>}

// webpack.config.js
module.exports = {
  entry: {
    app: './src/app.js',
    vendors: './src/vendors.js'
  }
}

// 對象語法會比較繁瑣。然而,這是應用程序中定義入口的最可擴展的方式。
複製代碼

常見場景

1. 分離應用程序主體和第三方庫
// webpack.config.js
module.exports = {
  entry: {
    app: './src/app.js'
    vendors: './src/vendors.js'
  }
}

/* * webpack 從 app.js 和 vendors.js 開始建立依賴圖。 * 這些依賴圖是彼此徹底分離、互相獨立的(每一個 bundle 中都有一個 webpack 引導)。 * 這種方式比較常見於,只有一個入口起點(不包括 vendor)的單頁應用程序中。 * * 此設置容許你使用 CommonsChunkPlugin 從應用程序依賴圖中提取 vendor 到 vendor 依賴圖,並把引用 vendor 的部分替換爲 __webpack_require__() 調用。 * 若是應用程序依賴圖中沒有 vendor 代碼,那麼你能夠在 webpack 中實現被稱爲長效緩存的通用模式。 * 說實話,目前看不懂上面這段話,因此也不曉得怎麼通俗的表述。 */
複製代碼
2. 多頁面應用程序
// webpack.config.js
module.exports = {
  entry: {
    pageOne: './src/pageOne/index.js',
    pageTwo: './src/pageTwo/index.js',
    pageThree: './src/pageThree/index.js'
  }
}

/* * webpack 分離 3 個的依賴圖 * * 在多頁應用中,每當頁面跳轉時服務器將爲你獲取一個新的 HTML 文檔。 * 頁面從新加載新文檔,而且資源被從新下載。這給了咱們特殊的機會去作不少事: * 使用 CommonsChunkPlugin 使全部頁面的應用程序共享代碼建立依賴圖, * 入口增多,多頁應用可以複用不一樣入口的大量重複代碼/模塊。 */
複製代碼

2. 出口(output)

注意,即便能夠存在多個入口,但只配置一個出口設置。

用法

webpack 中配置 output 的最低要求是,將它的值是一個包括如下兩點的對象:

  1. filename: 輸出文件的文件名。
  2. path: 輸出目錄的絕對路徑。
// webpack.config.js
module.exports = {
  output: {
    filename: '<WhateverYouLike>.js',
    path: '/path/to/project'
  }
}

// 此配置將一個單獨的 .js 文件輸出到 /path/to/project 目錄中。
複製代碼

配合多個入口設置

若是配置建立了多個單獨的入口,則應該使用 佔位符 來確保每一個文件具備惟一的名稱。

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

// 寫入到硬盤:./dist/app.js, ./dist/search.js
複製代碼

經常使用佔位符

內部ID:[id]

入口名稱:[name]

基於構建的hash(每次構建都會改變):[hash]

基於內容的hash(文件內容改變纔會改變):[chunkhash]

高級進階

官網所謂高級進階其實就是利用哈希佔位符構建隨版本迭代的文件命名方式這裏不展現了。

比較有用的是如何動態設置 publicPath:

首先,何爲 publicPath,以及周邊概念
  1. output.publicPath: 全部資源的基礎路徑,它被稱爲公共路徑,以 / 結束,示例:
// webpack.config.js
module.exports = {
  ...
  output: {
    publicPath: '/assets/',
    chunkFilename: '[id].chunk.js'
  }
};

/* * HTML loader 輸入出:<link href="/assets/spinner.gif" /> * CSS:background-image: url(/assets/spinner.gif); * 靜態資源最終訪問路徑 = output.publicPath + loader 或插件等配置路徑 */
複製代碼
  1. devServer.publicPath: 肯定從哪裏提供 bundle

假設服務器運行在 http://localhost:8080 而且 output.filename 被設置爲 bundle.js。默認 publicPath/,因此你的包能夠經過 http://localhost:8080/bundle.js 訪問。

能夠修改經過 devServer.publicPath 來修改請求資源時的服務器前綴,示例:

// webpack.config.js
module.exports = {
  ...
  devServer: {
    publicPath: '/assets/'
  }
};

/* * 如今能夠經過 http://localhost:8080/assets/bundle.js 訪問 bundle。 * 確保 publicPath 老是以斜槓(/)開頭和結尾。 * devServer.publicPath 也能夠是一個完整的 URL。 * 通常狀況下都要保證 devServer.publicPath 與 output.publicPath 保持一致。 */
複製代碼
  1. devServer.contentBase: 告訴服務器從哪裏提供內容,只有在提供靜態文件時才須要

默認狀況下,將使用當前工做目錄做爲提供內容的目錄,可是你能夠修改成其餘目錄,示例:

// webpack.config.js
module.exports = {
  ...
  devServer: {
    // 推薦使用絕對路徑。
    contentBase: path.join(__dirname, 'public')
  }
};

// 也能夠從多個目錄提供內容
module.exports = {
  ...
  devServer: {
    contentBase: [path.join(__dirname, 'public'), path.join(__dirname, 'assets')]
  }
};

// 具體做用不詳,官網並無給出說明也懶得查了
複製代碼
其次,如何動態設置 publicPath
// webpack.config.js
...
const BASE_URL = process.env.NODE_ENV === 'production'
  ? '/'
  : '/'

module.exports = {
  ...
  publicPath: BASE_URL,
  ...
}
// 方法來自 iview-admin vue.config.js
// 我不知道我理解的動態設置對不對,不過官網給的 __webpack_public_path__ 我沒看明白
複製代碼

3. 處理器(loader)

loader 用於對模塊的源代碼進行轉換,可使你在「載入」模塊時預處理文件。

loader 相似於其餘構建工具中「任務(task)」,提供了處理前端構建步驟的方法。

loader 能夠將文件從不一樣的語言(如 TypeScript)轉換爲 JavaScript,或將內聯圖像轉換爲 data URL。容許你直接在 JavaScript 模塊中 import CSS 文件。

示例

配置 loader 使 webpack 加載 CSS 文件,或者將 TypeScript 轉爲 JavaScript

首先安裝相對應的 loader

npm install --save-dev css-loader
npm install --save-dev ts-loader
複製代碼

而後配置 webpack 對每一個 .css 使用 css-loader,全部 .ts 文件使用 ts-loader

// webpack.config.js
module.exports = {
  ...
  module: {
    rules: [
      { test: /\.css$/, use: 'css-loader' },
      { test: /\.ts$/, use: 'ts-loader' }
    ]
  }
}
複製代碼

使用 loader 的三種方式

  1. 配置:在 webpack.config.js 文件中指定 loader。(推薦)

前面展現過了,這裏就不重複了。

  1. 內聯:在每一個 import 語句中顯式指定 loader

能夠在 import 語句或任何等效於 import 的方式中指定 loader。使用 ! 將資源中的 loader 分開。分開的每一個部分都相對於當前目錄解析,示例:

import Styles from 'style-loader!css-loader?modules!./styles.css';
複製代碼
  1. CLI:利用 shell 命令指定 loader
webpack --module-bind jade-loader --module-bind 'css=style-loader!css-loader'
複製代碼

loader 特性

  • loader 支持鏈式傳遞。loader 鏈中每一個 loader,都對前一個 loader 處理後的資源進行轉換。loader 鏈會按照相反的順序執行。第一個 loader 將(應用轉換後的資源做爲)返回結果傳遞給下一個 loader,依次這樣執行下去。最終,在鏈中最後一個 loader,返回 webpack 所預期的 JavaScript
  • loader 能夠是同步的,也能夠是異步的。
  • loader 運行在 Node.js 中,而且可以執行任何可能的操做。
  • loader 接收查詢參數,用於對 loader 傳遞配置。
  • loader 也可以使用 options 對象進行配置。
  • 除了使用 package.json 常見的 main 屬性,還能夠將普通的 npm 模塊導出爲 loader,作法是在 package.json 裏定義一個 loader 字段。
  • 插件能夠爲 loader 帶來更多特性。
  • loader 可以產生額外的任意文件。

解析 loader

loader 遵循標準的 模塊解析。多數狀況下,loader 將從模塊路徑(一般將模塊路徑認爲是 node_modules)解析。

loader 模塊須要導出爲一個函數,而且使用 Node.js 兼容的 JavaScript 編寫。一般使用 npm 進行管理,可是也能夠將自定義 loader 做爲應用程序中的文件。按照約定,loader 一般被命名爲 xxx-loader(例如 json-loader)。有關詳細信息,請查看 如何編寫 loader?

4. 插件(plugins)

插件是 webpack 的支柱功能。webpack 自身也構建於插件系統之上。

插件目的在於解決 loader 沒法實現的其餘事。

剖析

webpack 插件是一個具備 apply 方法的 JavaScript 對象。apply 屬性會被 webpack compiler 調用,而且 compiler 對象可在整個編譯生命週期訪問。

// ConsoleLogOnBuildWebpackPlugin.js
const pluginName = 'ConsoleLogOnBuildWebpackPlugin';

class ConsoleLogOnBuildWebpackPlugin {
  apply(compiler) {
    // compiler hook 的 tap 方法的第一個參數,應該是駝峯式命名的插件名稱。
    // 建議爲此使用一個常量,以便它能夠在全部 hook 中複用。
    compiler.hooks.run.tap(pluginName, compilation => {
      console.log('webpack 構建過程開始!');
    });
  }
}
// 插件編寫屬於比較深刻的內容,這裏不過多探討,目前僅須要知道實現原理便可
複製代碼

用法

因爲插件能夠攜帶參數/選項,你必須在 webpack 配置中,向 plugins 屬性傳入 new 實例。

配置寫法

// webpack.config.js
const HtmlWebpackPlugin = require('html-webpack-plugin') //經過 npm 安裝
const webpack = require('webpack') //訪問內置的插件
const path = require('path')

module.exports = {
  entry: './path/to/my/entry/file.js',
  output: {
    filename: 'my-first-webpack.bundle.js',
    path: path.resolve(__dirname, 'dist')
  },
  module: {
    rules: [
      {
        test: /\.(js|jsx)$/,
        use: 'babel-loader'
      }
    ]
  },
  plugins: [
    new HtmlWebpackPlugin({template: './src/index.html'})
  ]
}
複製代碼

瞭解更多

1. 模式(mode)

對應屬性:mode | String

做用說明: 經過將 mode 參數設置爲 development, productionnone,能夠啓用對應環境下 webpack 內置的優化。默認值爲 production

用法

  1. 在配置文件中設置
// webpack.config.js
module.exports = {
  ...
  mode: 'production'
};
複製代碼
  1. 經過 CLI 參數設置
webpack --mode=production
複製代碼

支持模式

選項 描述
development 會將 process.env.NODE_ENV 的值設爲 development。啓用 NamedChunksPlugin 和 NamedModulesPlugin。
production 會將 process.env.NODE_ENV 的值設爲 production。啓用 FlagDependencyUsagePlugin, FlagIncludedChunksPlugin, ModuleConcatenationPlugin, NoEmitOnErrorsPlugin, OccurrenceOrderPlugin, SideEffectsFlagPlugin 和 UglifyJsPlugin.
None 不選用任何默認優化選項

根據 mode 改變編譯行爲

// webpack.config.js
var config = {
  entry: './app.js'
  ...
}

module.exports = (env, argv) => {

  if (argv.mode === 'development') {
    config.devtool = 'source-map';
  }

  if (argv.mode === 'production') {
    ...
  }

  return config
}
複製代碼

2. 模塊(modules)

在模塊化編程中,開發者將程序分解成離散功能塊,並稱之爲「模塊」。

每一個模塊具備比完整程序更小的接觸面,使得校驗、調試、測試垂手可得。 精心編寫的「模塊」提供了可靠的抽象和封裝界限,使得應用程序中每一個模塊都具備條理清楚的設計和明確的目的。

webpack 將「模塊」的概念應用於項目中的任何文件。

什麼是 webpack 模塊

對比 Node.js 模塊,webpack 「模塊」可以以各類方式表達它們的依賴關係,幾個例子以下:

樣式:(url(...)) ES2015: import CommonJS: require() HTML: <img src=...> AMD: define | require css/sass/less: @import

支持的模塊類型

webpack 經過 loader 能夠支持各類語言和預處理器編寫模塊。loader 描述了 webpack 如何處理「非 JavaScript(non-JavaScript) 模塊」,而且在 bundle 中引入這些「依賴」。

目前 webpack 已經但不限於支持如下語言的 loader:

3. 模塊解析(module resolution)

resolver 是一個庫,用於幫助找到模塊的絕對路徑。 它幫助 webpack 從每一個如 require/import 語句中,找到須要引入到 bundle 中的模塊代碼。 當打包模塊時,webpack 使用 enhanced-resolve 來解析文件路徑。

webpack 中的解析規則

使用 enhanced-resolvewebpack 可以解析三種文件路徑:

1. 絕對路徑
// 已經取得文件的絕對路徑,所以不須要進一步再作解析。
import '/home/me/file';
import 'C:\\Users\\me\\file';
複製代碼
2. 相對路徑
// 在這種狀況下,使用 import 或 require 的資源文件所在的目錄,被認爲是上下文目錄。
// 在 import/require 中給定的相對路徑,會拼接此上下文路徑,以產生模塊的絕對路徑。
import '../src/file1';
import './file2';
複製代碼
3. 模塊路徑
import 'module';
import 'module/lib/file';
// 解釋很囉嗦,感興趣能夠本身去看一下文檔
複製代碼

緩存

每次文件系統訪問都會被緩存,以便更快觸發對同一文件的多個並行或串行請求。在 觀察模式下,只有修改過的文件會從緩存中摘出。若是關閉觀察模式,會在每次編譯前清理緩存。

4. 依賴圖(dependency graph)

任什麼時候候,一個文件依賴於另外一個文件,webpack 就把此視爲文件之間有「依賴關係」。這使得 webpack 能夠接收非代碼資源(例如 imagesweb fonts),而且能夠把它們做爲「依賴」提供給你的應用程序。

webpack 從命令行或配置文件中定義的「入口」開始,遞歸地構建一個依賴圖,這個依賴圖包含着應用程序所需的每一個模塊,而後將全部這些模塊打包爲少許可由瀏覽器加載的 bundle(一般只有一個)。

5. 瀏覽器兼容性

webpack 支持全部 ES5 兼容(IE8 及如下不提供支持)的瀏覽器。webpackimport()require.ensure() 須要環境中有 Promise。若是你想要支持舊版本瀏覽器,你應該在使用這些 webpack 提供的表達式以前,先 加載一個 polyfill

總結

經過整理這篇文檔我已經對 webpack 有了一個初步的認識和了解了。

固然若是你要真正的在項目中投入使用 webpack 僅僅閱讀這一篇文章是不夠的,你還須要去深刻地閱讀了解文檔裏的各類配置參數和其餘經常使用的前端構建工具或預處理器配合 webpack 進行調試使用。

前路漫漫,與君共勉。

參考

相關文章
相關標籤/搜索