webpack 知識梳理

易名發表於2018-04-19

webpack 是什麼?我們為什麼要用它?

首先貼出官方解釋:

本質上,webpack 是一個現代 JavaScript 應用程式的靜態模組打包器(module bundler)。當 webpack 處理應用程式時,它會遞迴地構建一個依賴關係圖(dependency graph),其中包含應用程式需要的每個模組,然後將所有這些模組打包成一個或多個 bundle。

通俗的講,webpack 是一個程式碼加工機器,我們把自己的程式碼丟給他,它經過加工之後再還給我們。
以上所說的加工,包括但不限於以下幾點:

  • 程式碼打包壓縮
  • 程式碼編譯(ES6轉ES5、ts 轉 js 、less\sass 轉 css 等)
  • 程式碼優化,提取公共模組

webpack 還可以為我們提供前端靜態服務,極大地方便了我們日常的開發除錯。

綜上,我們之所以使用 webpack ,主要是為了提高開發效率,優化自身程式碼。

webpack 核心概念

當我們把自身程式碼交給 webpack 加工時,必然會需要一個入口,而加工過後的程式碼需要還給我們,肯定也會需要出口,而在加工的過程中 webpack 需要進行一系列的工序。如同麵粉變成麵包,不只是烘烤這麼簡單。
由上,我們來理解 webpack 核心的四個概念:

  • entry : 檔案入口
  • output : 檔案出口
  • module : 工序一 (通過配置,對不同的檔案型別做處理,完成模組程式碼的轉換)
  • plugins : 工序二 (通過外掛,來獲取更加強大的加工處理能力,完成更復雜的構建任務)

到這裡,我們便可以寫出一份簡單的配置檔案骨架:

module.exports = {
  entry: {
    ··· 檔案入口
  },
  output: {
    ··· 檔案出口
  },
  module: {
    rules: [
        ··· 一些列的 loader 
    ],
  },
  plugins: [
    ··· 一系列的外掛
  ]
}
複製程式碼

webpack 的基本配置使用

在 webpack 4.0以上版本中為我們提供了 --mode這個命令配置項,mode 分為 development 和 production 兩個選項,預設為production。它為我們提供了一些簡單的基本配置,對於簡單的專案,我們不再需要配置檔案,mode 預設入口檔案為 src 資料夾 下的 index.js,出口為 dist 資料夾,打包時,我們只需要選擇 mode 的引數即可。

webpack --mode development/production
複製程式碼

在 mode 的預設配置中,已經為我們處理了一些常見的用法。但是,這些預設配置,無法處理非 js 檔案內容。

下面這段話來自知乎:

development預設值會給你最好的開發體驗,它注重:  
瀏覽器除錯工具
快速開發週期中的快速增量編譯
在執行過程中提供有效的錯誤資訊


而production預設值會給你提供一系列有效的預設值以便部署你的應用,它注重:
小的輸出體積
執行快速的程式碼
忽略僅在開發時需要的程式碼
不暴露原始碼和檔案路徑
易於使用的輸出產物
複製程式碼

實際上,就目前看來,在專案開發中,我們仍需配置自己的配置檔案。
在日常的開發中,我們一般需要 webpack 為我們解決下面這些問題:

  • 構建我們釋出需要的 HTML、CSS、JS 檔案
  • 使用 CSS 前處理器來編寫樣式
  • 處理和壓縮圖片
  • 使用 Babel 來支援 ES 新特性
  • 本地提供靜態服務以方便開發除錯

根據以上需求,我們可以這樣配置自己 webpack.config.js :

const HtmlWebpackPlugin = require('html-webpack-plugin'); 
const CleanWebpackPlugin = require('clean-webpack-plugin');
const ExtractTextPlugin = require('extract-text-webpack-plugin'); // 注意版本號 webpack 4 以上版本請下載 @next 版本
const path = require('path');

module.exports = {
  entry: {
    index: './src/index.js',
  },
  output: {
    path: path.resolve(__dirname, 'dist'),
    filename: '[name].min.js'
  },
  module:{
    rules: [
      {
        test: /\.jsx?$/,// 匹配檔案路徑的正規表示式,通常我們都是匹配檔案型別字尾
        exclude: /(node_modules|bower_components)/, // 過濾掉不需要處理的檔案
        use: { loader: 'babel-loader' } // 指定使用的 loader
      },
      {
        test: /\.less/,
        use: ExtractTextPlugin.extract({ // 使用外掛抽離 css ,生成單獨的 css 檔案
          fallback: 'style-loader',
          use: ['css-loader', 'less-loader']
        })
      },
      {
        test: /\.html$/,
        use: [ 
          {
            loader: 'html-loader',
            options: { // 壓縮 html
              minimize: true
            }
          }
        ]
      },
      {
        test: /\.(png|jpg|gif)$/,
        use: [
          {
            loader: 'file-loader',
            options: {
              // 自定義配置,圖片壓縮、處理等
            } 
          }
        ]
      }
    ]  
  },

  // 提供靜態服務
  devServer:{ 
    port: 9999,
    headers: { // 新增頭部資訊
      "X-Custom-Foo": "bar"
    },
    proxy: { // 請求代理
      "/api": {
        target: "http://localhost:3000"
      }
    }
  },
  plugins: [
    // 每次打包前清除 dist 下的檔案
    new CleanWebpackPlugin('dist'),

    // 提取樣式,生成單獨檔案
    new ExtractTextPlugin("styles.css"),
    
    // 生成新的 html 檔案
    new HtmlWebpackPlugin({ 
      filename: 'index.html', // 如果檔名不是 index , 開發時要在 url 處新增檔名
      template: path.resolve(__dirname + '/src/index.html'), // 注意路徑,
    }) 
  ]
}
複製程式碼

package.json (請注意版本號,因為版本更新會造成不可預期的錯誤)

{
  "name": "webpack-share",
  "version": "1.0.0",
  "description": "",
  "main": "index.js",
  "scripts": {
    "dev": "webpack-dev-server --open --inline --mode development --progress",
    "build": "webpack --mode production --progress"
  },
  "author": "",
  "license": "ISC",
  "devDependencies": {
    "babel": "^6.23.0",
    "babel-core": "^6.26.0",
    "babel-loader": "^7.1.4",
    "babel-preset-env": "^1.6.1",
    "clean-webpack-plugin": "^0.1.19",
    "css-loader": "^0.28.11",
    "extract-text-webpack-plugin": "^3.0.2",
    "file-loader": "^1.1.11",
    "html-loader": "^0.5.5",
    "html-webpack-plugin": "^3.2.0",
    "less": "^3.0.1",
    "less-loader": "^4.1.0",
    "path": "^0.12.7",
    "style-loader": "^0.20.3",
    "webpack": "^4.5.0",
    "webpack-cli": "^2.0.14",
    "webpack-dev-server": "^3.1.2"
  }
}

複製程式碼

這樣,一個簡單的前端開發環境就已經配置好了,這份配置檔案解決了之前我們所提出的問題:

1、構建我們釋出需要的 HTML、CSS、JS 檔案

  • 使用 html-loader 、 html-webpack-plugin 處理 html 檔案,引入處理後的 css , js 檔案,使用 extract-text-webpack-plugin 抽離單獨的 css 檔案(注意版本號,在 webpack 4 中需要下載 @next 版本)

2、使用 CSS 前處理器來編寫樣式

  • 使用 less-loader、 css-loader 、style-loader 處理樣式檔案

3、處理和壓縮圖片

  • 使用 file-loader 處理圖片資源

4、使用 Babel 來支援 ES 新特性

  • 使用 babel-loader 對 js 編譯

5、本地提供靜態服務以方便開發除錯

  • 使用 devServer 提供靜態服務,配置請求代理,避免產生跨域安全問題

如果在專案中引入 react ,我們要做一下簡單修改:

{
        test: /\.jsx?$/,// 匹配檔案路徑的正規表示式,通常我們都是匹配檔案型別字尾
        exclude: /(node_modules|bower_components)/, // 過濾掉不需要處理的檔案
        use: { // 指定使用的 loader
          loader: 'babel-loader',
          options: {
            // presets: ['es2015','react'] webpack 3
            presets: ['@babel/preset-react','@babel/preset-es2015'] // webpack 4
          }
        } 
      },
複製程式碼

package.json :

"@babel/preset-es2015": "^7.0.0-beta.44",
"@babel/preset-react": "^7.0.0-beta.44",
複製程式碼

如果要引入 antd , 在 less-loader 中還需要做一下修改:

{
    test: /\.less/,
    use: ExtractTextPlugin.extract({ // 使用外掛抽離 css ,生成單獨的 css 檔案
      fallback: 'style-loader',
      use: [
        {
          loader: 'css-loader'
        },
        {
          loader:  'less-loader',
          options: { // 引入 js
            javascriptEnabled: true 
          } 
        }
      ]
    })
  }
複製程式碼

一些常用的其他配置

resolve

這裡只做簡單介紹,更多詳情,請看官網
在 webpack 中,和模組路徑解析相關的配置都在 resolve 欄位下,我們可以通過配置 resolve 來提高自身開發的體驗,例如常用的 alias 別名設定,簡化了我們引入檔案的路徑。 首先我們來了解一下在 webpack 中的解析規則:

  • 解析相對路徑

1、如果是檔案,直接載入
2、如果是資料夾,則查詢資料夾下 package.json 檔案

  • 找到 package.json: 則一般情況會對照 main 屬性,獲取檔案路徑來解析,如果找不到 main 欄位,一般情況話會尋找資料夾下的 index.js
  • 找不到 package.json : 會尋找 index.js
  • 解析絕對路徑

直接查詢檔案

  • 解析模組

由下自上查詢 node_modules 中的模組

alias 別名

alias 可以為一段路徑建立一個別名,使一些較為常用的冗長路徑得到簡化:
題外話:

path.join([path1][, path2][, ...])用於連線路徑。該方法的主要用途在於,會正確使用當前系統的路徑分隔符,Unix系統是/,Windows系統是\。

path.resolve([from ...], to) 將 to 引數解析為絕對路徑。



resolve: {
  alias: {
    Js: path.resolve(__dirname + '/src/js'),
    Less: path.resolve(__dirname, './src/css'), // 模糊匹配: 引用時,只要匹配到 Less 都會被替換
    Css$: path.resolve(__dirname, './src/css/index.css') // 精確匹配:引用時,只能 improt 'Css'
  }
},
複製程式碼

這樣,我們在引入 css 和 js 時,只需要在別名下查詢相應檔案就好:

// 設定別名前
import '../css/a.less';

// 設定別名後
import 'Less/a.less';
improt 'Css'
複製程式碼

extensions 自動解析確定的擴充套件

resolve: {
    // 預設配置
    // extensions: [".js", ".json"]
    extensions: ['.js','.jsx','.less','.css']
},

複製程式碼

設定補全副檔名陣列,引用檔案時,可以不加字尾,wepack 會從陣列中自動補全。

resolve 其他配置

resolve: {
    // 當目錄下有 package.json 檔案時,預設查詢欄位
    // 配置 target === "web" 或者 target === "webworker" 時 mainFields 預設值是:
    mainFields: ['browser', 'module', 'main'],
    // target 的值為其他時,mainFields 預設值為:
    mainFields: ["module", "main"],
    
    // 當目錄下沒有 package.json 檔案時,預設查詢檔案
    mainFiles: ["index"],
    
    modules: [
      path.resolve(__dirname, 'my_modules'), //告訴 webpack 解析模組時應該搜尋的目錄,可以自定義一些自己的模組路徑。
      'node_modules',
    ]
}
複製程式碼

devtool

devtool: 'source-map',
複製程式碼

此選項控制是否生成,以及如何生成 source map。在開發環境時使用,便於準確定位程式碼位置。

webpack-dev-serve

devServer:{ 
  contentBase: path.resolve(__dirname, "dist"), // 未經 webpack 處理的靜態檔案訪問路徑
  port: 9999,
  publicPath: '/', // 確定應該從哪裡提供 bundle,並且此選項優先。建議將 devServer.publicPath 和 output.publicPath 的值保持一致。
  overlay:{ //當有編譯錯誤或者警告的時候顯示一個全屏 overlay
    errors:true,
    warnings:true,
  },
  headers: { // 新增頭部資訊
    "X-Custom-Foo": "bar"
  },
  proxy: { // 請求代理
    "/api": {
      target: "http://localhost:3000",
    }
  },
},
複製程式碼

開發環境和生產環境

基於開發環境和生產環境的需求差異,我們一般需要使用兩套不同的配置檔案。
首先,我們來看一下開發環境和生產環境環境需求點的異同:

相同點

1、共同的入口
2、共同的程式碼處理
3、同樣的解析配置
3、共同的出口
複製程式碼

不同點

開發環境

1、模組熱更新 // 提高開發效率
2、介面代理  // 方便介面呼叫
3、devtool  // 準確定位程式碼位置


生產環境

1、提取公共程式碼    
2、壓縮混淆   
3、去除無用程式碼  
4、檔案壓縮  // 減小程式碼體積
複製程式碼

我們發現,在 wenpack 4 中,mode 的兩個引數已經能很好地解決我們大部分的需求,但是仍有些個性配置需要我們手動建立。我們需要根據不同的業務需求對配置檔案進行拆分,然後利用 webpack-merge 對不同的配置檔案進行合併。
通常情況下,我們會拆分出三個配置檔案:

webpack.base.conf.js    // 公共配置
webpack.prod.conf.js    // 生產環境配置
webpack.dev.conf.js    // 開發環境配置
複製程式碼

你可以把兩份差異配置引入公共配置,然後判斷傳入引數來決定使用那份配置:

webpack-dev-server --config ./webpack.base.conf.js --mode development/production 或 --env development/production

const HtmlWebpackPlugin = require('html-webpack-plugin'); 
const CleanWebpackPlugin = require('clean-webpack-plugin');
const ExtractTextPlugin = require('extract-text-webpack-plugin'); // 注意版本號 webpack 4 以上版本請下載 @next 版本
const merge = require('webpack-merge')
const webpack = require('webpack');
const path = require('path');

const prodConf = require('./webpack.prod.conf');
const devConf = require('./webpack.dev.conf');

module.exports = (env, argv) => {
  console.log(env,'==================', argv.mode)

  const baseConf = {
    entry: {
      index:  path.resolve(__dirname, '../src/index.js'),
    },
    output: {
      path: path.resolve(__dirname, '../dist'),
      filename: 'js/[name].min.js'
    },
    ····
  }
  const config = env === 'dev' ? devConf : prodConf;
  return merge(baseConf,devConf)
}

複製程式碼

也可以將共同配置分別引入差異配置中,在啟動命令上指定所使用的配置檔案:

webpack-dev-server --config 配置檔案
複製程式碼

優化

相關文章