深入淺出的webpack構建工具---webpack基本配置(一)

龍恩0707發表於2018-07-29

深入淺出的webpack構建工具---webpack基本配置(一)

閱讀目錄

1. 安裝webpack到全域性

在學習構建之前,我們來在本地檔案新建一個存放專案的資料夾,比如叫demo1這個專案,然後進入demo1該專案的根目錄後,執行命令 npm init
執行下,一路回車(先簡單的來),就會生成一個package.json檔案。

在專案中直接執行如下命令,就可以把webpack安裝到全域性去;如下命令:

npm install -g webpack

2. 安裝webpack到本專案。

在本專案中,執行如下命令,就可以把webpack安裝到本地專案中,如下命令:

npm install --save-dev webpack

3. 如何使用webpack?

在編寫webpack程式碼之前,我們先搭建好目錄結構如下:

### 目錄結構如下:
demo1                                       # 工程名
|   |--- dist                               # 打包後生成的目錄檔案             
|   |--- node_modules                       # 所有的依賴包
|   |--- js                                 # 存放所有js檔案
|   | |-- demo1.js  
|   | |-- main.js                           # js入口檔案
|   |
|   |--- webpack.config.js                  # webpack配置檔案
|   |--- index.html                         # html檔案
|   |--- styles                             # 存放所有的css樣式檔案                              
|   |--- .gitignore  
|   |--- README.md
|   |--- package.json

index.html程式碼如下:

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <title>Title</title>
</head>
<body>
  <div id="app"></div>
  <script src="./dist/bundle.js"></script>
</body>
</html>

js/demo1.js 程式碼假如是如下:

function demo1Func() {
  console.log(11);
};
module.exports = demo1Func;

js/main.js 程式碼如下:

const demo1Func = require('./demo1.js');

demo1Func();

如上的簡單的程式碼,我們先從上面的簡單的程式碼來學起,然後逐漸會慢慢的深入的講解webpack知識點;我們在專案的根目錄中新建 webpack.config.js件;編寫配置檔案如下:

const path = require('path');

module.exports = {
  entry: './js/main.js',

  output: {
    // 將所有依賴的模組合併輸出到一個叫bundle.js檔案內
    filename: 'bundle.js',
    // 將輸出的檔案都放在dist目錄下
    path: path.resolve(__dirname, './dist')
  }
};

一切編寫檔案好了以後,我們一般情況下會在專案的根目錄下 執行webpack命令;但是當我們執行webpack後,會報錯如下資訊:

One CLI for webpack must be installed. These are recommended choices, delivered as separate packages:
 - webpack-cli (https://github.com/webpack/webpack-cli)
   The original webpack full-featured CLI.
 - webpack-command (https://github.com/webpack-contrib/webpack-command)
   A lightweight, opinionated webpack CLI.
We will use "npm" to install the CLI via "npm install -D".
Which one do you like to install (webpack-cli/webpack-command):

因此我們可以在專案的目錄下 安裝下 webpack-cli ,如下命令安裝:

npm install webpack-cli -g

安裝完成後,我們再執行webpack命令後,就會在專案中根目錄生成dist資料夾,該資料夾內會生成 bundle.js 檔案。這時候我們再開啟index.html, 執行後,會發現執行了依賴的demo1.js了。

3.1 理解配置檔案 entry(入口)和 出口 (output)

入口(entry) 是指示webpack應該使用哪個模組,來作為構建內部依賴js的開始,進入入口起點後,webpack會找出有哪些模組和庫是入口js依賴的。
出口(output) 是告訴webpack在什麼地方輸出它所建立的bundles,以及如何命名這些檔案,預設值為 './dist'.

4. 使用loader

webpack的構建是一個採用CommonJS規範的模組化專案,現在我們還是繼續上面的專案,我們再在main.js會引入一個css檔案。因此會在main.js程式碼修改如下:

require('../styles/main.css');

const demo1Func = require('./demo1.js');

demo1Func();

然後main.css程式碼如下:

#app {
  font-size: 18px;
}

如果我們現在直接去執行webpack的話,就會報錯,因此我們需要在webpack中加入Loader的機制。將webpack程式碼改成如下:

const path = require('path');

module.exports = {
  entry: './js/main.js',

  output: {
    // 將所有依賴的模組合併輸出到一個叫bundle.js檔案內
    filename: 'bundle.js',
    // 將輸出的檔案都放在dist目錄下
    path: path.resolve(__dirname, './dist')
  },
  module: {
    rules: [
      {
        // 用正則去匹配以 .css結尾的檔案,然後需要使用loader進行轉換
        test: /\.css$/,
        use: ['style-loader', 'css-loader?minimize']
      }
    ]
  }
};

如上程式碼,我們在webpack編譯之前需要安裝 style-loader 和 css-loader, 如下命令進行安裝:

npm install --save-dev style-loader css-loader

如上配置程式碼中的 module.rules 陣列配置了一組規則,是來告訴webpack在遇到哪些檔案時使用哪些loader去載入和轉換,比如上面的使用test正則去匹配
以 .css 結尾的檔案,會先使用 css-loader 讀取css檔案,然後使用 style-loader將css的內容注入到javascript裡面去。

注意:
1. use屬性的值是一個使用Loader名稱組成的陣列,Loader的執行順序是由後往前的。由於loader有順序的,因此我們在配置中不能如下編寫loader;
程式碼如下:

module: {
    rules: [
      {
        // 用正則去匹配以 .css結尾的檔案,然後需要使用loader進行轉換
        test: /\.css$/,
        use: ['css-loader?minimize', 'style-loader']
      }
    ]
  }

2. 每個Loader都可以通過 URL queryString 的方式傳入引數,比如 css-loader?minimize是告訴css-loader是開啟css壓縮。

現在我們可以在專案中的根目錄執行 webpack命令了,一切正常後,我們開啟index.html後,檢視程式碼,發現css被載入到style標籤內了;

style-loader的原理:是將css的內容使用javascript的字串儲存起來,在網頁執行javascript時通過DOM操作,動態地向HTML head標籤裡插入 HTML style標籤。

3. 配置loader的方式也可以使用Object來實現,比如如上的匹配css程式碼,可以改成如下:

use: [
    'style-loader', 
    {
      loader: 'css-loader',
      options: {
        minimize: true
      }
    }
] 

因此webpack所有的配置程式碼變成如下:

const path = require('path');
  module.exports = {
    entry: './js/main.js',
    output: {
      // 將所有依賴的模組合併輸出到一個叫bundle.js檔案內
      filename: 'bundle.js',
      // 將輸出的檔案都放在dist目錄下
      path: path.resolve(__dirname, './dist')
    },
    module: {
      rules: [
        {
          // 用正則去匹配以 .css結尾的檔案,然後需要使用loader進行轉換
          test: /\.css$/,
          use: [
            'style-loader', 
            {
              loader: 'css-loader',
              options: {
                minimize: true
              }
            }
          ]
        }
      ]
    }
  };

5. 使用外掛(Plugin)

loader的作用是被用於轉換某些型別的模組,而外掛則可以用於執行範圍更廣的任務,外掛的範圍包括,從打包優化和壓縮,一直到重新定義環節中的變數。
如果想要使用一個外掛,我們只需要require()它,然後把它新增到 plugins陣列中。我們可以在一個配置檔案中因為不同的目的多次使用用一個外掛,因此
我們可以使用new操作符來建立它的實列。

上面我們是通過loader載入了css檔案到js中去,下面我們通過plugin將注入的bundle.js檔案裡的css提取到單獨的檔案中,我們需要使用到
extract-text-webpack-plugin 外掛,配置程式碼如下:

const path = require('path');

// 提取css的外掛
const ExtractTextPlugin = require('extract-text-webpack-plugin');

module.exports = {
  entry: './js/main.js',

  output: {
    // 將所有依賴的模組合併輸出到一個叫bundle.js檔案內
    filename: 'bundle.js',
    // 將輸出的檔案都放在dist目錄下
    path: path.resolve(__dirname, './dist')
  },
  module: {
    rules: [
      {
        // 使用正則去匹配要用該loader轉換的css檔案
        test: /\.css$/,
        loaders: ExtractTextPlugin.extract({
          // 轉換 .css檔案需要使用的Loader
          use: ['css-loader']
        })
      }
    ]
  },
  plugins: [
    new ExtractTextPlugin({
      // 從js檔案中提取出來的 .css檔案的名稱
      filename: `main.css`
    })
  ]
};

要使用 extract-text-webpack-plugin 外掛的話,首先我們需要安裝該外掛,安裝外掛的命令如下:

npm install --save-dev extract-text-webpack-plugin

當安裝成功後,我們需要執行webpack命令後,發現命令列報錯了,報錯資訊如下:

(node:86210) DeprecationWarning: Tapable.plugin is deprecated. Use new API on `.hooks` instead
/usr/local/lib/node_modules/webpack/lib/Chunk.js:802

解決的辦法是 安裝 extract-text-webpack-plugin 時,需要如下安裝,安裝命令如下:

npm install extract-text-webpack-plugin@next

安裝成功後,再執行webpack就可以了,我們可以看到在dist資料夾內會多一個main.css, 因此我們可以在index.html中把 css檔案引入進去即可。

6. 使用DevServer

前面是使用webpack進行打包,但是在開發中我們還需要一個本地檔案的伺服器,並且當我們儲存程式碼的時候會自動進行打包,並且還支援 Source Map,
以方便程式碼除錯等功能,因此我們現在需要使用到 DevServer了。

首先我們需要安裝 webpack-dev-server, 如下安裝命令:

npm install --save-dev webpack-dev-server

當然我們還需要全域性安裝一下,安裝命令如下:

npm install webpack-dev-server -g

然後當我們在命令列輸入 webpack-dev-server 執行後,發現報錯了,報錯如下資訊:

The CLI moved into a separate package: webpack-cli.
Please install 'webpack-cli' in addition to webpack itself to use the CLI.
-> When using npm: npm install webpack-cli -D
-> When using yarn: yarn add webpack-cli -D
module.js:471

因此我們需要重新執行下如下命令:

npm install webpack-cli -D

當成功後,我們再次執行 webpack-dev-server 可以看到已經啟動了伺服器了,埠號預設是 8080, 然後我們訪問 http://localhost:8080/index.html  就可以訪問到我們專案中頁面了。

6.1 實時預覽
webpack在啟動時可以開啟監聽模式,預設是關閉的,開啟後webpack會監聽本地檔案系統的變化,在發生變化時候會重新構建出新的結果,在上面執行
webpack-dev-server 後,我們需要開啟一個新的命令列,在命令列中輸入如下命令來開啟監聽模式:

webpack --watch

當專案中入口檔案或入口依賴的檔案有改變的時候,它會自動重新構建,構建完成後會自動重新整理下頁面,但是如果修改的不是入口檔案或依賴的檔案
是不會有任何效果的,比如修改的是index.html 是不會重新構建的。但是要完成上面的實時預覽,html頁面的bundle.js 要直接引入即可:如下html頁面程式碼:

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <title>Title</title>
</head>
<body>
  <div id="app"></div>
  <script src="bundle.js"></script>
</body>
</html>

那為什麼 http://localhost:8080/bundle.js 這樣也能訪問的到呢?原因是DevServer會將webpack構建出的檔案儲存在記憶體中,DevServer不會理會
webpack.config.js裡配置的output.path屬性的。

6.2 模組熱替換

除了上面介紹的改動本地入口檔案或依賴檔案後,會自動打包,然後會自動重新整理瀏覽器即可看到更新效果外,我們還可以使用模組熱替換技術,

模組熱替換技術能做到在不重新載入整個網頁的情況下,通過將已更新的模組替換舊模組,它預設是關閉的,要開啟模組熱替換,我們只需在啟動DevServer時帶上 --inline 引數即可。如下命令:

webpack-dev-server --inline  或 webpack-dev-server --inline --hot

webpack-dev-server 有如下兩種啟動模式:

iFrame: 該模式下修改程式碼後會自動打包,但是瀏覽器不會自動重新整理。
inline: 內聯模式,該模式下修改程式碼,webpack將自動打包並重新整理瀏覽器。

6.3 支援Source Map
在瀏覽器中執行javascript程式碼都是編譯器輸出的程式碼,但是如果在程式碼中碰到一個bug的時候,我們不好調式,因此我們需要 Source Map來對映到原始碼上,Webpack支援生成 Source Map, 只需在啟動時帶上 --devtool source-map引數即可;如下命令:

webpack-dev-server --inline --hot --devtool source-map

注意:每次執行 如上命令,感覺非常長,因此我們可以在專案的根目錄的package.json檔案的scripts配置中新增如下配置:

"scripts": {
  "dev": "webpack-dev-server --devtool source-map --hot --inline"
}

加上如上配置後,我們只需要在命令列中 執行 npm run dev 即可;

其他配置常見的選項:
--quiet 控制檯中不輸出打包的資訊
--compress 開啟gzip的壓縮
--progress 顯示打包的進度

因此在專案中scripts經常會做如下配置:

"scripts": {
  "dev": "webpack-dev-server --progress --colors --devtool source-map --hot --inline",
  "build": "webpack --progress --colors"
}

這樣的話,打包的時候會顯示打包進度。

1. context

基礎目錄,絕對路徑,用於從配置中解析入口起點和載入器。什麼意思呢?比如如下程式碼配置:
context: path.resolve(__dirname, 'js'), 含義是使用當前目錄下的js檔案下查詢入口檔案。比如如下程式碼的配置也是可以的:

module.exports = {
  context: path.resolve(__dirname, 'js'),
  entry: './main.js'
}

含義是從當前專案目錄下的js檔案查詢main.js檔案作為入口檔案,如果在當前目錄沒有找到該入口檔案,就會報錯。
當然我們也可以如下寫配置程式碼也是可以的:如下程式碼配置:

module.exports = {
  context: path.resolve(__dirname, ''),
  entry: './js/main.js'
};

或者context配置項不要,它也是預設從當前目錄下查詢的,因此如果使用context的話,建議加上第二個引數,是從當前目錄下那個檔案內查詢的。或者直接不要這個配置項。

2. entry

應用程式的起點入口,從這個起點開始,應用程式啟動執行,如果傳遞一個陣列的話,那麼陣列的每一項都會執行。它的型別可以是String, array, 或 object;

2.1 string(型別為字串): 如下配置:

module.exports = {
  entry: './js/main.js',
  output: {
    // 將所有依賴的模組合併輸出到一個叫bundle.js檔案內
    filename: 'bundle.js',
    // 將輸出的檔案都放在dist目錄下
    path: path.resolve(__dirname, './dist')
  }
};

2.2 型別為陣列

module.exports = {
  entry: ['./js/main.js', './js/main2.js'],
  output: {
    // 將所有依賴的模組合併輸出到一個叫bundle.js檔案內
    filename: 'bundle.js',
    // 將輸出的檔案都放在dist目錄下
    path: path.resolve(__dirname, './dist')
  }
};

如果型別為陣列的話,將會建立多個主入口,並且把陣列中的js打包在一起到一個檔案裡面去。

2.3 型別為物件時

module.exports = {
  entry: {
    'main': './js/main.js',
    'main2': './js/main2.js'
  },
  output: {
    filename: '[name].js', // [name] 的值是entry的鍵值, 會輸出多個入口檔案
    // 將輸出的檔案都放在dist目錄下
    path: path.resolve(__dirname, './dist')
  }
};

3. Output

output配置是輸出最終想要的程式碼,它是一個object, 裡面包含很多配置項。

3.1 filename 和 path的理解
filename: 輸出檔案的名稱,為string型別.
1) 如果只有一個輸出檔案,可以將名稱寫死;如:filename: 'bundle.js';
path: 檔案被寫入硬碟的位置。必須是string型別的絕對路徑,一般通過Node.js的path模組獲取絕對路徑,如下程式碼:
path: path.resolve(__dirname, './dist')

單個入口配置程式碼如下:

{
  entry: './js/main.js',
  output: {
    filename: 'bundle.js',
    path: path.resolve(__dirname, './dist')
  }
}

多個入口

如果有多個chunk要輸出時,就需要藉助[name]變數了,webpack會為每個chunk取一個名稱,因此我們根據chunk的名稱來區分輸出的檔名。如下:
filename: '[name].js'

多個入口配置程式碼如下:

module.exports = {
  entry: {
    'main': './js/main.js',
    'main2': './js/main2.js'
  },
  output: {
    filename: '[name].js', // [name] 的值是entry的鍵值, 會輸出多個入口檔案
    // 將輸出的檔案都放在dist目錄下
    path: path.resolve(__dirname, './dist')
  }
};

如上配置,會輸出 main.js 和 main2.js。
內建變數除了包括name,還包括,id, hash, chunkhash。

id: Chunk的唯一標識,從0開始。
hash: Chunk的唯一標識的Hash值。比如[hash:8] 代表8位的hash值。預設是20位。
chunkhash: Chunk內容的Hash值。

如下hash配置程式碼:

entry: {
  'main': './js/main.js',
  'main2': './js/main2.js'
},
output: {
  filename: '[name]_[hash:8].js', // [name] 的值是entry的鍵值, 會輸出多個入口檔案
  // 將輸出的檔案都放在dist目錄下
  path: path.resolve(__dirname, './dist')
}

就會生成 main_xxxx.js 和 main2_xxxx.js 其中xxxx是hash值的隨機八位。

3.2 chunkFilename的理解
chunkFilename 和 filename非常類似,但是chunkFilename是未被列在entry中的,但是又需要被打包出來的檔案命名配置,什麼場景需要這樣的呢?
比如 非同步按需載入模組的時候,一般這樣的檔案沒有被列在entry中,比如如下專案在js檔案內再新建plugins資料夾,存放比如js外掛使用的,目錄
結構如下:

### 目錄結構如下:
demo1                                       # 工程名
|   |--- dist                               # 打包後生成的目錄檔案             
|   |--- node_modules                       # 所有的依賴包
|   |--- js                                 # 存放所有js檔案
|   | |-- demo1.js  
|   | |-- main.js                           # js入口檔案
|   | |-- main2.js                          # js的多個入口檔案
|   | |-- plugins                           # plugins資料夾,存放js外掛類的
|   | | |--- a.js
|   |
|   |--- webpack.config.js                  # webpack配置檔案
|   |--- index.html                         # html檔案
|   |--- styles                             # 存放所有的css樣式檔案                              
|   |--- .gitignore  
|   |--- README.md
|   |--- package.json

如上目錄結構 在js資料夾內,再新建plugins資料夾,裡面包含一個a.js檔案;程式碼如下:

function a() {
  console.log('a.js');
}
module.exports = a;

然後我們在main2.js入口檔案如下編寫程式碼;使用非同步方式引用a.js; 程式碼如下:

require.ensure(['./plugins/a.js'], function(require) {
  var aModule = require('./plugins/a.js');
}, 'a');

然後在webpack的output配置新增 chunkFilename 配置如下:

module.exports = {
  context: path.resolve(__dirname, ''),
  entry: {
    'main': './js/main.js',
    'main2': './js/main2.js'
  },
  output: {
    filename: '[name]_[hash:8].js', // [name] 的值是entry的鍵值, 會輸出多個入口檔案
    chunkFilename: '[name].min.js', 
    // 將輸出的檔案都放在dist目錄下
    path: path.resolve(__dirname, './dist')
  }
};

其中上面的 require.ensure() API的第三個引數是給這個模組命名的,因此在webpack配置完成後,繼續打包下,會在dist資料夾下打出 a.min.js 檔案
了。所以這就是 chunkFilename 的用途場景了。

3.3 publicPath的理解

output.path 是指所有輸出檔案的本地檔案目錄(絕對路徑)。比如配置如下:

output: {
  filename: 'bundle.js',
  // 將輸出的檔案都放在dist目錄下
  path: path.resolve(__dirname, 'dist')
}

那麼webpack會將所有檔案輸出到 dist/下。也就是說path是存放打包後的檔案的輸出目錄。

正式環境下publicPath的理解:
publicPath正式環境可以理解為改變相對目錄下的靜態資原始檔的路徑為正確的路徑。比如圖片引入的是相對路徑,可以配置publicPath成為正確的路徑。
它是指定資原始檔引用的目錄(相對於伺服器的根目錄來講)。

先看styles/main.css程式碼如下:

#app {
  font-size: 18px;
  width: 200px;
  height: 200px;
  backround: url('../images/1.jpg') no-repeat;
}

iamges資料夾內有一張圖片為1.jpg, 因此上面是css的引入路徑。然後手動建立 index.html程式碼如下:

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <title>Title</title>
  <link href="dist/main.css" rel="stylesheet" type="text/css" />
</head>
<body>
  <div id="app"></div>
  <script src="dist/bundle.js"></script>
</body>
</html>

現在我們執行 npm run build 進行打包後,開啟index.html發現css中圖片的引用的路徑為:backround: url(1.jpg) no-repeat; 很明顯圖片的引用路徑不對,它引入是根目錄下的圖片,因此如果我們在打包的時候加上 publicPath,配置如下:

output: {
  filename: 'bundle.js',
  // 將輸出的檔案都放在dist目錄下
  path: path.resolve(__dirname, 'dist'),
  publicPath: '/dist/'
}

重新打包下,再看下css引入路徑為 backround: url(/dist/1.jpg) no-repeat;說明引入是對的。

開發環境下publicPath的理解:

output的配置如下:

output: {
  filename: 'bundle.js',
  // 將輸出的檔案都放在dist目錄下
  path: path.resolve(__dirname, 'dist'),
  // publicPath: '/dist/'
}

然後把dist目錄刪除掉,執行 npm run dev後,開發環境打包後並沒有生成dist目錄,而我們的index.html引入了 dist/bundle.js 和 dist/main.css, 因此頁面會發現找不到js和css檔案。那麼打包後檔案放在那裡去了呢?我們可以看如下圖:

啟動了webpack-dev-server, 然後整個專案執行在localhost:8080下面,也就是伺服器地址是localhost:8080, 當我們在瀏覽器中輸入伺服器地址,伺服器會籤就會到伺服器請求js 檔案,js的地址是dist/bundle.js, 沒有,報錯了。
看如上截圖的:wepback output is served from / , 這裡指明瞭webpack 打包後檔案放到了/ 目錄下,也就是根目錄下,webpack-dev-server 進行打包時
它預設把css和js及圖片打包到根目錄下。
現在把output配置改成如下:

output: {
  filename: 'bundle.js',
  // 將輸出的檔案都放在dist目錄下
  path: path.resolve(__dirname, 'dist'),
  publicPath: '/dist/'
};

再進行執行下 npm run dev 後看到如下所示:

wepback output is served from /dist/ 這裡指明瞭webpack 打包後檔案放到了/dist/ 目錄下了,因為js和css檔案可以重新訪問了。

3.4 crossOriginLoading

Webpack輸出的部分程式碼有可能需要非同步載入,而非同步載入是通過JSON方式實現的。JSONP的原理是動態地向HTML中插入一個<script src="url">
</script>,crossOriginLoading則是用於配置這個非同步插入的標籤的 crossorigin的值。
script標籤的crossorigin屬性可以取以下的值:
1. anonymous(預設),啟用跨域載入,在載入此指令碼資源時不會帶上使用者的Cookies; 即傳送不帶憑據的 credential的請求。
2. use-credentials 啟用跨域載入,在載入此指令碼資源時會帶上使用者的Cookies. 傳送帶憑據的credential的請求。
3. false 禁用跨域載入。

3.5 libraryTarget 和 library

這兩個屬性大家可能比較陌生,一般專案中不需要關注這兩個屬性,但是當我們開發類庫,使用webpack去構建一個可以被其他模組匯入使用的庫時會使用到。一般情況下,webpack對js模組進行打包,即多個js模組和一個入口模組,打包成一個bundle檔案,可以直接被瀏覽器或者其他javascript引擎執行,

相當於直接編譯生成一個完成的可執行檔案。但是當我們需要釋出一個javascript庫的時候,比如在npm社群中釋出自己的庫,這個時候我們的webpack就需要相應的配置.

libraryTarget 是配置以何種方式匯出庫。可以是 var, commonjs, 'commonjs2', amd, this,umd模式等。
library 配置匯出庫的名稱。
他們一般都組合使用的。

我們下面來編寫一個簡單的庫,假如名字叫 demo1.js,程式碼如下:

function sayHello() {
  console.log('Hello');
}

function sayFunc() {
  console.log('say');
}

export default {
  sayHello: sayHello,
  sayFunc: sayFunc
}

然後我們在main.js程式碼引入該檔案

import foo from './demo1.js';

console.log(foo);
foo.sayHello(); // 可以執行

webpack打包配置如下: 

entry: './js/main.js',
output: {
  filename: 'bundle.js',
  // 將輸出的檔案都放在dist目錄下
  path: path.resolve(__dirname, 'dist')
}

這樣會輸出一個立即執行的函式,bundle程式碼大致結構如下:

(function(modules) { // webpackBootstrap
  var installedModules = {};
  function __webpack_require__(moduleId) {
    // ....
  }
  return __webpack_require__(__webpack_require__.s = "./js/main.js");
})
({
  "./js/demo1.js":
  (function(module, __webpack_exports__, __webpack_require__) {
      "use strict";
      eval("__webpack_require__.r(__webpack_exports__);\nfunction sayHello() {\n  console.log('Hello');\n}\n\nfunction sayFunc() {\n  console.log('say');\n}\n\n/* harmony default export */ __webpack_exports__[\"default\"] = ({\n  sayHello: sayHello,\n  sayFunc: sayFunc\n});\n\n//# sourceURL=webpack:///./js/demo1.js?");
  }),
  "./js/main.js":
  (function(module, __webpack_exports__, __webpack_require__) {
      "use strict";
      eval("__webpack_require__.r(__webpack_exports__);\n/* harmony import */ var _demo1_js__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ./demo1.js */ \"./js/demo1.js\");\n\n\nconsole.log(_demo1_js__WEBPACK_IMPORTED_MODULE_0__[\"default\"]);\n_demo1_js__WEBPACK_IMPORTED_MODULE_0__[\"default\"].sayHello();\n\n\n\n//# sourceURL=webpack:///./js/main.js?");
  })
});

3.5.1 commonjs2 模式
如果我們需要將打包返回值交給編譯後的檔案 module.export, 因此我們這邊可以使用 libraryTarget 和 library, webpack配置如下:

entry: './js/main.js',
output: {
  filename: 'bundle.js',
  // 將輸出的檔案都放在dist目錄下
  path: path.resolve(__dirname, 'dist'),
  libraryTarget: 'commonjs2',
  library: 'util'
}

main.js 程式碼如下:

import demo1 from './demo1.js';

demo1.js 程式碼如下:

function sayHello() {
  console.log('Hello');
}

function sayFunc() {
  console.log('say');
}

export default {
  sayHello: sayHello,
  sayFunc: sayFunc
}

打包後的檔案是如下格式程式碼:

module.exports = (function(modules) { // webpackBootstrap
  var installedModules = {};
  function __webpack_require__(moduleId) {
    // ....
  }
  return __webpack_require__(__webpack_require__.s = "./js/main.js");
})
({
  "./js/demo1.js":
  (function(module, __webpack_exports__, __webpack_require__) {
      "use strict";
      eval("__webpack_require__.r(__webpack_exports__);\nfunction sayHello() {\n  console.log('Hello');\n}\n\nfunction sayFunc() {\n  console.log('say');\n}\n\n/* harmony default export */ __webpack_exports__[\"default\"] = ({\n  sayHello: sayHello,\n  sayFunc: sayFunc\n});\n\n//# sourceURL=webpack:///./js/demo1.js?");
  }),
  "./js/main.js":
  (function(module, __webpack_exports__, __webpack_require__) {
      "use strict";
      eval("__webpack_require__.r(__webpack_exports__);\n/* harmony import */ var _demo1_js__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ./demo1.js */ \"./js/demo1.js\");\n\n\nconsole.log(_demo1_js__WEBPACK_IMPORTED_MODULE_0__[\"default\"]);\n_demo1_js__WEBPACK_IMPORTED_MODULE_0__[\"default\"].sayHello();\n\n\n\n//# sourceURL=webpack:///./js/main.js?");
  })
});

那麼從npm社群下載庫後,使用庫的方法是如下:

const util1 = require('library-name-in-npm');
util1.xxx(); // xxx就是 某個方法名稱

3.5.2 commonjs
編寫的庫將通過commonjs匯出。
webpack 配置程式碼如下:

entry: './js/main.js',
output: {
  filename: 'bundle.js',
  // 將輸出的檔案都放在dist目錄下
  path: path.resolve(__dirname, 'dist'),
  libraryTarget: 'commonjs',
  library: 'util'
}

打包後的檔案變成如下程式碼:

exports["util"] =
  (function(modules) { // webpackBootstrap
    var installedModules = {};
    function __webpack_require__(moduleId) {
      // ....
    }
    return __webpack_require__(__webpack_require__.s = "./js/main.js");
  })
  ({
    "./js/demo1.js":
   (function(module, __webpack_exports__, __webpack_require__) {
    "use strict";
    eval("__webpack_require__.r(__webpack_exports__);\nfunction sayHello() {\n  console.log('Hello');\n}\n\nfunction sayFunc() {\n  console.log('say');\n}\n\n/* harmony default export */ __webpack_exports__[\"default\"] = ({\n  sayHello: sayHello,\n  sayFunc: sayFunc\n});\n\n//# sourceURL=webpack://util/./js/demo1.js?");
  }),
    "./js/main.js":
    (function(module, __webpack_exports__, __webpack_require__) {
      "use strict";
      eval("__webpack_require__.r(__webpack_exports__);\n/* harmony import */ var _demo1_js__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ./demo1.js */ \"./js/demo1.js\");\n\n\n\n\n\n//# sourceURL=webpack://util/./js/main.js?");

    })
});

如上程式碼:配置了 output.library = 'LibraryName'; webpack就會輸出程式碼格式為:
exports['LibraryName'] = lib_code;

使用庫的方法程式碼如下:

require('library-name-in-npm')['LibraryName'].doSomething();

注意:lib_code 為所有的webpack打包的程式碼;library-name-in-npm 是指模組被髮布到npm程式碼倉庫的名稱。

3.5.3 this
編寫的庫將通過this被賦值給library指定的名稱。

webpack配置程式碼如下:

entry: './js/main.js',
output: {
  filename: 'bundle.js',
  // 將輸出的檔案都放在dist目錄下
  path: path.resolve(__dirname, 'dist'),
  libraryTarget: 'this',
  library: 'util'
}

打包後的js檔案如下格式:

this["util"] =
  (function(modules) { // webpackBootstrap
    var installedModules = {};
    function __webpack_require__(moduleId) {
      // ....
    }
    return __webpack_require__(__webpack_require__.s = "./js/main.js");
  })
  ({
    "./js/demo1.js":
   (function(module, __webpack_exports__, __webpack_require__) {
    "use strict";
    eval("__webpack_require__.r(__webpack_exports__);\nfunction sayHello() {\n  console.log('Hello');\n}\n\nfunction sayFunc() {\n  console.log('say');\n}\n\n/* harmony default export */ __webpack_exports__[\"default\"] = ({\n  sayHello: sayHello,\n  sayFunc: sayFunc\n});\n\n//# sourceURL=webpack://util/./js/demo1.js?");
  }),
    "./js/main.js":
    (function(module, __webpack_exports__, __webpack_require__) {
      "use strict";
      eval("__webpack_require__.r(__webpack_exports__);\n/* harmony import */ var _demo1_js__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ./demo1.js */ \"./js/demo1.js\");\n\n\n\n\n\n//# sourceURL=webpack://util/./js/main.js?");

    })
});

使用庫的方法如下:

this.LibraryName.doSomething();

3.5.4 window
編寫的庫將通過window賦值給library指定的名稱,輸出的程式碼格式如下:
window['LibraryName'] = lib_code;

webpack的配置如下:

entry: './js/main.js',
output: {
  filename: 'bundle.js',
  // 將輸出的檔案都放在dist目錄下
  path: path.resolve(__dirname, 'dist'),
  libraryTarget: 'window',
  library: 'util'
}

打包後的js程式碼如下:

window["util"] =
  (function(modules) { // webpackBootstrap
    var installedModules = {};
    function __webpack_require__(moduleId) {
      // ....
    }
    return __webpack_require__(__webpack_require__.s = "./js/main.js");
  })
  ({
    "./js/demo1.js":
   (function(module, __webpack_exports__, __webpack_require__) {
    "use strict";
    eval("__webpack_require__.r(__webpack_exports__);\nfunction sayHello() {\n  console.log('Hello');\n}\n\nfunction sayFunc() {\n  console.log('say');\n}\n\n/* harmony default export */ __webpack_exports__[\"default\"] = ({\n  sayHello: sayHello,\n  sayFunc: sayFunc\n});\n\n//# sourceURL=webpack://util/./js/demo1.js?");
  }),
    "./js/main.js":
    (function(module, __webpack_exports__, __webpack_require__) {
      "use strict";
      eval("__webpack_require__.r(__webpack_exports__);\n/* harmony import */ var _demo1_js__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ./demo1.js */ \"./js/demo1.js\");\n\n\n\n\n\n//# sourceURL=webpack://util/./js/main.js?");

    })
});

使用庫的方法如下程式碼:

window.LibraryName.doSomething();

3.5.5 global
編寫的庫將通過global賦值給通過library指定的名稱,即把庫掛載到global上,輸出格式的程式碼如下:
global['LibraryName'] = lib_code;

和上面的window是一樣的,無非就是把window改成global;

使用庫的方法如下所示:

global.LibraryName.doSomething();

也可以打包成 'amd' 或 'umd' 模式結構程式碼, 在此介紹省略哦~

4. 模式(mode)

提供mode配置項,告訴webpack使用對應的模式。

在webpack配置中提供mode選項。如下程式碼:

module.exports = {
  mode: 'production' // 或開發環境下 'development'
};

注意:使用 development模式程式碼不會被壓縮,使用 production 程式碼會被壓縮。

也可以在CLI引數中傳遞,比如如下程式碼:
webpack --mode=production

5.理解使用Loader

我們都知道webpack是適用於資源進行打包的,裡面的所有資源都是模組,內部實現了對模組資源進行載入機制,但是webpack只能處理js模組,如果要處理其他型別的檔案,就需要使用loader進行轉換。Loader可以理解為是模組和資源的轉換器,它本身也是一個函式,接收原始檔作為引數,返回轉換的結果。

配置loader,需要使用rules模組來讀取和解析規則,它是一個陣列,陣列裡面中的每一項描述瞭如何處理部分檔案。
1. 條件匹配: 通過配置test,include,exclude三個配置項來選中loader需要應用規則的檔案。
2. 應用規則: 對選中的檔案通過use配置項來應用loader,可以只應用一個loader或者按照從右往左的順序應用一組loader(切記:loader使用順序是從右往左的),也可以向loader傳入引數。
3. 重置順序: Loader執行的順序預設是從右到左執行的,但是我們可以通過enforce選項可以將其中一個Loader的執行順序放到最前或最後。

具體的配置程式碼可以參考如下:

module.exports = {
  module: {
    rules: [
      {
        // 正則匹配 以 js結尾的
        test: /\.js$/,
        // babel 轉換js檔案,?cacheDirectory 用於快取babel的編譯結果,加快重新編譯的速度
        use: ['babel-loader?cacheDirectory'],
        // include只包含src目錄下的js檔案,加快查詢速度
        include: path.resolve(__dirname, 'src')
      },
      {
        // 正則匹配以 styl結尾的檔案
        test: /\.styl$/,
        /* 
          use使用順序從右到左執行,先使用stylus-loader外掛去解析以styl結尾的檔案成css檔案,
          再使用css-loader外掛去讀取css檔案,最後由 style-loader 將css的內容注入到javascript裡面。
        */
        use: ['style-loader', 'css-loader', 'stylus-loader'],

        // 排除node_modules目錄下的檔案
        exclude: path.resolve(__dirname, 'node_modules')
      },
      {
        // 對非文字檔案採用file-loader去載入
        test: /\.(gif|png|jpe?g|eot|woff|ttf|svg|pdf)$/,
        use: ['file-loader']
      }
    ]
  }
}

注意:在loader需要傳入多個引數時,我們可以通過一個Object來描述,如下面是babel-loader配置:

use: [
  {
    loader: 'babel-loader',
    options: {
      cacheDirectory: true
    },
    /*
      enforce: 'post' 的含義是將Loader執行的順序放到最後
      enforce: 'pre' 的含義是將Loader執行順序放到最前面
    */
    enforce: 'post'
  }
]

當然,上面的test,include,exclude配置也可以傳入多個,因此他們也支援陣列的方式來傳遞了。比如如下配置:

{
  test: [
    /\.js$/,
    /\.jsx$/
  ],
  include: [
    path.resolve(__dirname, 'src'),
    path.resolve(__dirname, 'src2')
  ],
  exclude: [
    path.resolve(__dirname, 'node_modules'),
    path.resolve(__dirname, 'node_modules2')
  ]
}

6.理解noParse

該配置項可以讓webpack忽略對部分未採用模組化檔案的遞迴解析和處理,該忽略的檔案不能包含import,require, define等模組化語句。
那麼這樣做的好處是可以提高構建效能,比如像一些庫jquery等就沒有必要去使用webpack去遞迴解析和處理了。
使用配置程式碼如下:

module.exports = {
  module: {
    noParse: /jquery|xxjs/
  }
}

也可以使用函式,但是webpack需要從3.0版本才支援;如下配置程式碼:

module.exports = {
  module: {
    noParse: (content) => {
      // content代表一個模組的檔案路徑
      return /jquery|xxjs/.test(content);
    }
  }
}

7. 理解alias

resolve.alias 是通過別名來將原匯入路徑對映成一個新的匯入路徑。比如如下配置:

module.exports = {
  entry: './js/main.js',
  output: {
    filename: 'bundle.js',
    // 將輸出的檔案都放在dist目錄下
    path: path.resolve(__dirname, 'dist')
  },
  resolve: {
    alias: {
      components: './src/components'
    }
  }
}

如上程式碼配置,當我通過 import xxx from 'components/xxx' 匯入時,實際上被alias替換成了
import xxx from './src/components/xxx';

8.理解extensions

在使用 import 匯入檔案時,有時候沒有帶入檔案的字尾名,webpack會自動帶上字尾去訪問檔案是否存在,那麼預設的字尾名為
['.js', '.json']; 即:
extensions: ['.js', '.json']; 如果我們想自己配置.vue字尾名,防止在匯入檔案時候不需要加上字尾;我們可以使用webpack做如下配置:

module.exports = {
  entry: './js/main.js',
  output: {
    filename: 'bundle.js',
    // 將輸出的檔案都放在dist目錄下
    path: path.resolve(__dirname, 'dist')
  },
  resolve: {
    alias: {
      components: './src/components'
    },
    extensions: ['.js', '.vue'];
  }
}

9.理解Externals

Externals 用來告訴webpack在構建程式碼時使用了不處理應用的某些依賴庫,依然可以在程式碼中通過AMD,CMD或window全域性方式訪問。
什麼意思呢?就是說我們在html頁面中引入了jquery原始檔後,比如如下程式碼:

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <title>Title</title>
  <script src="http://code.jquery.com/jquery-1.12.0.min.js"></script>
  <link href="dist/main.css" rel="stylesheet" type="text/css" />
</head>
<body>
  <div id="app"></div>
  <script src="dist/bundle.js"></script>
</body>
</html>

但是我們想在模組化的原始碼裡匯入和使用jquery,首先我們肯定需要安裝下 jquery依賴包,npm install --save jquery, 然後我們編寫如下程式碼進行使用:

const $ = require('jquery');
console.log($);

構建打包後我們會發現輸出的檔案裡面包含jquery的內容,這導致了jquery庫引入了兩次,並且導致了bundle.js檔案變得更大,浪費載入流量。因此 Externals 配置項就是來解決這個問題的。

通過externals 可以告訴webpack在javascript執行環境中已經內建了哪些全域性變數,不用將這些程式碼打包到程式碼裡面去。
因此我們可以做如下配置:

module.export = {
  externals: {
    jquery: 'jQuery'
  }
};

相關文章