【轉】Webpack 快速上手(下)

因爲文章篇幅較長,爲了更好的閱讀體驗,本文分爲上、中、下三篇:javascript

  • 上篇介紹了什麼是 webpack,爲何須要 webpack,webpack 的文件輸入和輸出css

  • 中篇介紹了 webpack 在輸入和輸出這段中間所作的事情,也就是 loader 和 pluginshtml

  • 下篇介紹了 webpack 的優化,以及在開發環境和生產環境的不一樣用法前端

用過上面兩篇較長的篇幅,咱們終於瞭解了 webpack 的總體工做流程。在最後一篇,將會介紹一些零碎但也很重要的知識點。vue

tree-shaking

tree-shaking 這個技術在 webpack2 就已經被添加進來了,做用是在打包過程當中,將模塊內沒有使用的代碼刪除,從而減少打包後的文件體積。java

這個單詞表面的意思是,有一棵小樹,你去抖動這棵樹,那麼樹上多餘沒用的樹葉就會掉落,那在代碼中具體是什麼樣子呢。假設咱們如今將一些經常使用的方法都封裝在了 util.js 這個文件中:node

// util.js 
function add(...args) {
  return args.reduce((prev, currrent, index) => {
    return prev + currrent;
  }, 0);
}

function multiply(...args) {
  return args.reduce((prev, currrent, index) => {
    return prev * currrent;
  }, 1);
}

export {
  add,
  multiply
}

而後咱們在 index.js 中須要用到 add 這方法:jquery

// index.js
import { add } from './util.js';

add(1, 2);
add(1, 2, 3);

這樣打包後的代碼是不含有 multiply 這個函數的,這就是 tree-shaking 的做用。webpack

splitChunk

如今咱們再回顧一下 webpack 打包過程git

  1. 以 entry 爲起點,將所依賴的模塊組織成一個樹形的結構
  2. 經過不一樣的 loader 對不一樣的文件進行編譯
  3. 使用 plugins 對文件打包後的文件進行特定的操做
  4. 根據 output 將打包後的文件輸出到指定的位置

若是隻有一個入口文件,最終也只會打包出一個文件(下文用 chunk 表示,每打包出的一個文件就叫一個 chunk)(排除動態加載的狀況(import()))。這裏有一個很明顯的缺陷,就是將全部的模塊打包成一個文件,打包後的體積必定會很大。同時,若是咱們使用了 chunkhash 作文件緩存的話,每次項目修改的時候,不管修改哪一個文件,即便是修改了一個換行,chunkhash 的值都會發生改變,那麼每次改動上線以後,用戶都要從新加載這個巨大的文件,這樣用戶體驗很是糟糕。若是你說我不作文件緩存,那麼因爲瀏覽器緩存的緣由,用戶首次加載的文件會被緩存到本地,下次即便你更新了代碼,用戶執行的仍是首次加載的文件,這樣老闆會找你聊天的。

爲了解決這個問題,咱們能夠考慮設置多個入口文件,就像在介紹 entry 的例子代碼中那樣:

// webpack.config.js
module.exports = {
  entry: {
    login: 'src/login.js',
    logout: 'src/logout.js',
  }
}

經過這樣的配置,咱們就能夠將 login.js 和 logout.js 打包成兩個文件,並且修改其中一個文件不會影響到另外一個的 chunkhash。看起來好像已經解決了上面的問題,可是咱們再結合實際的項目深刻的分析一下,咱們一般會在項目中引入一些類庫,好比常見的 lodash ,假設 login.js 和 logout.js 中都用到了 lodash ,這就須要在這個兩個文件中顯式的 import _ from 'lodash'; 這樣一來,打包出來的兩個文件都包含了 lodash ,這就屬於重複引用了,另外若是咱們的項目是單頁應用,理應只有一個入口,在須要的時候再去加載 login.js 或 logout.js 的代碼。

因此咱們要解決咱們一開始的問題,應該從下面兩個點出發:

  1. 分離代碼中公共的部分,打包成一個或多個chunk
  2. 將不須要馬上執行的代碼分離出來,打包成多個 chunk ,而後經過動態加載的加載這些chunk

針對第一點,咱們可使用 webpack 提供的 SplitChunksPlugin 插件,這個插件和上面介紹的 minimize 同樣,須要在 optimization.splitChunks 中配置。在 production 模式下 webpack 會默認作一下代碼分離的工做,可是沒多大的卵用,因此仍是須要咱們本身動手配置。

第一步先未來自 node_modules 中的包分離出來,由於這些都是項目所依賴的第三方庫,咱們是不會改動的(除非升級版本),這些能夠作經過 chunkhash 作長期緩存,咱們把這寫代碼打包爲 chunk-vendors

// webpack.config.js
module.exports = {
  entry,
  output: {
    path: './dist',
    filename: '[name].[chunkhash:8].js' // 只取chunkhash的前8位
  },
  optimization: {
    splitChunks: {
      cacheGroups: {
        vendors: {
          name: `chunk-vendors`,
          test: /[\\/]node_modules[\\/]/,
          priority: -10,
          chunks: 'initial'
        }
      }
    }
  }
}

在上面的配置中,咱們用 cacheGroups 將 node_modules 的代碼所有分離出來。 cacheGroups 直譯成中文就是緩存組,其實就是存放分離代碼塊規則的對象,第一個對象的 key 值是 vendors ,這個 key 值沒什麼用,主要仍是看對應的 val 。

  • name:分離後打包出的文件名稱。咱們設置爲 chunk-vendors ,那麼打包出來的文件就叫 chunk-vendors.js 。由於在 output.filename 設置了 chunkhash:8,因此最終打包出的文件名稱是 chunk-vendors.ac96737b.js 。後面的一串字符就是 chunkhash 的前8位。前面介紹過 chunkhash 是每個打包出來的文件的 hash ,只要文件的內容沒有改變,這個值就不會發生變化,因此只要不對咱們依賴的包進行版本升級,或者增長新的包,這個值就不會變更,所以能夠用這個辦法進行長期緩存。
  • test:用於匹配的文件位置,test: /[\\/]node_modules[\\/]/ 表示全部來自 node_modules 下面的代碼,能夠填寫具體的路徑
  • priority:權重,這個值仍是很重要的,webpack 會優先分離權重高的 cacheGroups 。
  • chunks:做用範圍,能夠設置爲 async 表示對異步模塊起做用, initial 表示對初始模塊起做用, all 則表示對全部模塊起做用。

若是打包出的 chunk-vendors 體積很大,並且包含一些常常升級的依賴,那麼咱們能夠繼續作拆分

// webpack.config.js
module.exports = {
  entry,
  output: {
    path: './dist',
    filename: '[name].[chunkhash:8].js'
  },
  optimization: {
    splitChunks: {
      cacheGroups: {
        vendors: {
          name: `chunk-vendors`,
          test: /[\\/]node_modules[\\/]/,
          priority: -10,
          chunks: 'initial'
        },
        vue: {
          name: 'chuank-vue',
          test: /[\\/]node_modules[\\/]vue[\\/]/,
          priority: 10,
          chunks: 'initial'
        }
      }
    }
  }
}

這樣我就將 vue 分離成單獨一個 chunk 了,不只減少了 chunk-vendors 的體積 ,當咱們升級 vue 版本的時候,也不會影響 chunk-vendors 的 chunkhash 。注意:不要忘了設置 priority 。

除了將 node_modules 中的類庫分離出來,咱們本身寫的代碼中也有些公共的部分,好比在講 tree-shaking 提到了 util.js ,做爲一個工具方法,跟定會在項目中好多處用到,那麼咱們也能夠將會這個公共代碼分離出來:

// webpack.config.js
module.exports = {
  entry,
  output: {
    path: './dist',
    filename: '[name].[chunkhash:8].js'
  },
  optimization: {
    splitChunks: {
      cacheGroups: {
        common: {
          name: `chunk-common`,
          minChunks: 2,
          priority: -20,
          chunks: 'initial',
          reuseExistingChunk: true
        }
      }
    }
  }
}

在上面的配置中,咱們把被依賴超過兩次(minChunks: 2)的 chunk 都分離到了 chunk-common.f4786e34.js 中。

在解決了對公共代碼的分離,下一步即便處理動態加載的代碼,之一部分相對簡單一些,就像在介紹 babel 時提到的那樣,經過 import() 來切分動態加載的代碼。

webpack 在將咱們的代碼打包後,也會生成一些在運行時所必須的代碼,這些代碼默認會打包進主文件中,咱們也能夠將它分離出來單獨打包成一個文件,這須要在 optimization.runtimeChunk 中單獨配置:

// webpack.config.js
module.exports = {
  entry,
  output,
  optimization: {
    runtimeChunk: {
      name: 'manifest'
    }
  }
}

這樣就能夠將運行時的代碼也分裏出來,打包爲 manifest.js。

其實代碼拆分是須要反覆嘗試的,通常狀況下咱們只會將 node_modules 裏的包分離成一份(chunk-vendors.js), 業務中公共的代碼分離成一分(chunk-common.js),剩下的都放在了主模塊(main.js) 和動態加載的 chunk 中了。可是因爲項目的不一樣,這種方式未必是最好的,因此這須要咱們反覆的去嘗試一各類分離的方式,爲了讓咱們對打包後的代碼有更爲直觀的認識,咱們能夠藉助 webpack-bundle-analyzer 來幫咱們很直觀的看到打包後每個 chunk 的大小。

webpack-bundle-analyzer

webpack-dev-server

在上面的介紹中,都是面向打包的,也就是說咱們默認代碼是無誤能夠直接打包上線運行,固然這是不可能滴,實際開中須要配合 Google 和 fuck 來 debug 代碼,若是用上面的方法來 debug 我相信無論是誰,都會想砸電腦的,由於每次 debug 都要從新的打包,而後再想辦法再本地啓動一個web服務,用來託管咱們打包出的靜態文件。那麼 webpack 可不能夠幫我作到這兩點呢:

  1. 監聽文件變化,自動從新編譯
  2. 建立一個web服務,用來託管打包後的靜態文件,方便本地調試

爲了解決上面兩點,webpack 提供了 webpack-dev-server 這個包,它能夠輕鬆的幫助咱們實現上面兩功能,這個包須要單獨安裝一下

npm i webpack-dev-server -D

而後在 npm script 中添加一行 :

// package.json
{
  "scripts": {
    "dev": "webpack-dev-server --mode development",
    "build": "webpack --mode production"
  }
}

這時候在命令行中執行 npm run dev ,便會在本地啓動一個Web服務,當命令行中出現 Compiled successfully 便表示服務啓動成功,而後打開瀏覽器,輸入 localhost:8080 即可以直接訪問項目了。當源代碼發生變化時,便會自動從新編譯,而後刷新瀏覽器。

webpack-dev-server 一樣也提供了一些配置選項,能夠在配置文件的 devServer 中進行配置:

// webpack.config.js
module.exports = {
  entry,
  output,
  devServer: {
    port: 8080, // 設置端口爲8080,默認就是8080
    open: true, // 編譯完成後自動打開瀏覽器
    historyApiFallback: true, // 若是你的項目使用了 HTML5 history API ,開啓此項能夠將全部的跳轉將指向index.html
  }
}

這些配置也能夠以參數的形式添加在加命令行後面,可是有的配置只能以參數的形式使用,好比咱們想查看編譯的進度,就須要加上 --progress :

// package.json
{
  "scripts": {
    "dev": "webpack-dev-server --mode development --progress",
    "build": "webpack --mode production"
  }
}

學會了如何使用,在簡單的介紹一下 webpack-dev-server 的工做原理,webpack-dev-server 是一個基於 express 封裝的 Web 服務,當咱們在執行 webpack-dev-server 時候,雖然能夠看到打包以後的運行效果,可是實際上並無生成打包後的文件,這是由於 webpack-dev-server 將打包後的內容放在了內存中,當某一個源代碼文件發生變動的時候,它也不會從新的再將全部的文件打包一遍,而是隻更新了一部分文件,這樣的好處是能夠加快從新編譯的速度,加大程度的減小了開發模式下的編譯時間。

講到這裏,你可能也意識到了,若是是開發模式下,有許多事情都不須要作。好比不須要設置 output ,不須要對代碼壓縮,不須要分離 css 和 js 等等,若是省去這些工做,首次編譯的速度又會有大幅度的提高,這是一個優化點,會在後面講到。

HMR

HMR (hot module replace) 模塊熱替換,在不刷新頁面的狀況下更新代碼。

在引入了 webpack-dev-server 以後,咱們能夠作到監聽源代碼變化,而後刷新瀏覽器及時看到修改效果。可是在前端開發中,每一步操做每每都伴隨着狀態和 dom 的變化,好比咱們開發一個定外賣的網站,此時正在調試購物車功能,先加了一份煲仔飯,爲了滿減,再加一份荷包蛋,可是這時候後出現了bug,加了荷包蛋仍是沒有滿減,原來是計算滿減的方法寫錯了,修復這個bug以後,咱們發現頁面刷新了,回到最開始的樣子,因而又要從選擇店鋪開始在走一遍流程。那可不能夠在修復計算滿減的方法以後,不要刷新頁面也能看到正確的效果呢?這就是 HMR 實現的功能了。

開啓 HMR 須要將 devServer.hot 設置爲 true ,而後在 plugins 中添加 HotModuleReplacementPlugin 插件,該插件是 webpack 自帶的一個插件:

// webpack.config.js
module.exports = {
  entry,
  output,
  plugins: [
    new webpack.HotModuleReplacementPlugin()
  ]
  devServer: {
    hot: true,
    /* 其餘配置 */
    /* ... */
  }
}

還有一種更簡便的方法來開啓 HRM ,那就是在命令行中添加參數 --hot ,而後在執行 npm run dev 的時候也會自動添加 HotModuleReplacementPlugin 插件。

如今咱們在 webpack 中開啓了 HMR 功能,webpack 能夠將老模塊替換爲編譯後的新模塊,可是從瀏覽器層面考慮,瀏覽拿到新模塊以後,並不知道要作什麼處理,就像咱們前面舉的例子中提到,在修改計算滿減方法以後,咱們但願從新執行一遍這個方法,很明顯這個需求不太現實,瀏覽名沒那麼聰明。全部這就須要咱們顯式的用代碼來告訴瀏覽器來作哪些事情。

咱們能夠在項目代碼中經過 module.hot 來判斷是否啓用了 HMR ,經過 module.hot.accept 來處理模塊更新後的要作的事情,如今假設咱們的項目入口文件是 index.js ,還有一個 util.js 裏面封裝了 add 方法:

project
├── src 
│   ├── index.js
│   ├── util.js
│   └── index.html
└── webpack.config.js
// util.js
function add(...args) {
  return args.reduce((prev, currrent, index) => {
    return prev + currrent;
  }, 0);
}

export { add }

而後咱們在 index.js 中導入 add 方法,而且將計算結果顯示在頁面上:

// index.js
import { add } from './util.js';

const h2 = document.createElement('h2');
h2.innerHTML = add('1', '2');

document.body.appendChild(h2);

將項目跑起來以後,發現 add 方法計算的結果錯了,經排查發現原來 add 方法忽略了對 string 類型的轉換,只要修改一下 util.js 中的 add 函數就行了:

// util.js
function add(...args) {
  return args.reduce((prev, currrent, index) => {
    return prev + currrent * 1;
  }, 0);
}

export { add }

這時候能夠發現,頁面中雖然顯示了正確的結果,可是頁面刷新了,而咱們但願的是在頁面不刷新的狀況下顯示正確結果,這時候就要在 index.js 添加熱更新後須要執行的代碼了:

// index.js
import { add } from './util.js';

const h2 = document.createElement('h2');
h2.innerHTML = add('1', '2');

document.body.appendChild(h2);

if (module.hot) {
  module.hot.accept('./util.js', () => {
    h2.innerHTML = add('1', '2');
  });
}

這樣再去修改 add 方法的時候,h2 顯示的內容會發生變化,可是頁面卻不會刷新,這纔是咱們想要的熱更新。

講到這裏你可能已經發現,實現一個完美的熱更新,難點不是在 webpack 的配置,而是在咱們的項目代碼中,咱們要針對全部須要熱更新的模塊加上熱更新以後的回調( module.hot.accept ),不過社區中已經提供了一些 loader 使 HMR 與各類框架平滑地進行交互 https://webpack.js.org/guides/hot-module-replacement/#other-code-and-frameworks ,

若是須要樣式熱更新的話,咱們須要判斷當前的環境變量是否爲 development ,而後將 MiniCssExtractPlugin.loader 換成 style-loader ,由於 MiniCssExtractPlugin 還不支持 HMR :

// webpack.config.js
const styleLoader = process.env.NODE_ENV === 'development' ? 'style-loader' : MiniCssExtractPlugin.loader;

module.exports = {
  entry,
  output,
  module: {
    rules: [{
      test: /\.css$/,
      use: [styleLoader, 'css-loader']
    }]
  }
}

content-base

在 plugins 這一章節中,提到了 copy-webpack-plugin 這個插件,它是用來將一些靜態資源拷貝到打包後的目錄,可是在開發環境下,咱們是經過 webpack-dev-server 建立一個 Web 服務,它的根目錄默認是配置文件所在的目錄,因此在開發模式下,若是須要請求一些靜態資源,那麼咱們就須要設置一下 contentBase

假設咱們的靜態資源放在了項目根目錄下的 static 文件夾下面,並且配置文件 webpack.config.js 也放在了項目根目錄下,那麼我麼就能夠將 devServer.contentBase 設置爲 static

// webpack.config.js
module.exports = {
  entry,
  output,
  devServer: {
    contentBase: 'static'
  }
}

假設 static 下面有一個圖片 logo.png ,咱們就能夠經過 localhost:8080/logo.png 來訪問這張圖片了。

public-path

在介紹 output.publicPath 的時候提到,這個值並不會影響打包後輸出的文件路徑,他只是設置在線上運行的時候,所請求的資源路徑,當咱們在 webpack-dev-server 這個 Web 服務下調試咱們的代碼的時候,可能也會出現和類型的狀況,這時候就須要設置一下 devServer.publicPath 了。它 output.publicPath 的區別的是 一個做用於線上環境,一個做用於咱們調試的開發環境。

proxy

在開發過程,常常須要調用後端提供的接口,通常狀況會把接口部署在測試環境,好比 http://test.api.com 而後咱們在項目中經過 ajax 的方式去調用。因爲同源策略,咱們在開發的時候經過 webpack-dev-server 啓動的 Web 服務的域是 localhost:8080 ,很明顯跨域了,接口沒法調用了。這個時候有兩種辦法解決,一是在測試上環境上配置 cors ,將咱們的 localhost 加入容許跨域的名單;二是咱們在本地利用 node 去請求這個接口,而後再將請求內容發送給前端,在整個過程當中 node 扮演的角色就是一個可靠的跑腿子,你去把請求交給它,它把請求送給測試環境,測試環境把響應交給它,它再把響應送到你這邊。

proxy示意圖

在 webpack-dev-server 集成了中間件 (http-proxy-middleware)[https://github.com/chimurai/http-proxy-middleware] 能夠很輕鬆的完成接口轉發,好比咱們想將全部的以 /api 開頭的請求都轉發到 http://test.api.com 只要在 devServer.proxy 像下面這樣配置便可:

// webpack.config.js
module.exports = {
  entry,
  output,
  devServer: {
    proxy: {
      '/api': 'http://test.api.com'
    }
  }
}

devServer.proxy 暴露出的配置項和 (http-proxy-middleware)[https://github.com/chimurai/http-proxy-middleware] 的配置項徹底同樣,具體能夠點擊連接查看。

mock

後端已經寫好的接口咱們咱們能夠用轉發的方式調用,而對於尚未寫好的接口咱們能夠經過 mock 的方式來調用,這樣能夠解決由於接口調用通而致使咱們開發不順暢的問題。由於 webpacl-dev-server 是基於 express 封裝了,而且將 express 的實例暴露在了 devServer.beforedevServer.after 這兩個配置項下面,因此咱們徹底能夠將後端沒有寫好的接口在 devServer.before 經過 express 去 mock 。假設咱們如今須要調用 /api/user/creation 這個接口來建立用戶,咱們能夠這樣 mock

// webpack.config.js
module.exports = {
  entry,
  output,
  devServer: {
    befor(app) {
      app.post('/api/user/creation', (req, res) => {
        // some code
        res.json({success: true});
      });
    }
  }
}

若是你須要 mock 的接口後端,那你徹底能夠像寫 express 那樣去寫接口,固然有些經常使用的中間件須要咱們本身去安裝。

其它

source-maps

webpack 打包壓縮以後的代碼可讀性幾乎爲零,同時也不方便調試,這時候能夠經過設置 devtool 選項來幫助咱們在開發環境調試,具體效果是:在 chrome 中(其它高級瀏覽器一樣支持)打開控制檯,咱們能夠在 Sources 中看到一個以 webpack:// 開頭的資源,裏面的內容和咱們編寫代碼大體相同(這取決於 devtool 的值)。

source maps

因爲 devtool 會影響打包的速度和打包後的代碼質量,因此在生產環境的構建中,不建議開啓此項(默認爲none),只要在開環境設置爲 eval-source-map 便可。其它配置和打包速度能夠參考 官網

alias

當項目的目錄結構愈來愈深,模塊變得愈來愈多的時候,模塊間的引用會變得很混亂,時常會看到下面這樣的代碼:

import ComponentA from '../../../../../components/a.component.js';
import ServiceA from '../../../../../service/a.service.js';

有沒有想罵人的衝動?這時候可使用 webpack 的 alias 選項來解決這個問題,配置文件的內容以下:

// webpack.config.js
module.exports = {
  entry,
  output,
  resolve: {
    alias: {
      '@': path.resolve(__dirname, "src"),
      'components': path.resolve(__dirname, "src/components"),
      'services': path.resolve(__dirname, "src/services"),
    }
  }
}

上面的配置表示爲 srcsrc/componentssrc/services 分別設置一個別名,咱們就能夠在代碼中用 @ 表示相對路徑 src 而沒必要再使用 ../../ 一層一層的向上查找了。假設咱們如今的項目結構是下面這樣子:

project
├── src
│   ├── components
│   └── services
└── webpack.config.js

這樣咱們能夠在任意文件夾下的代碼內使用 @ 來表示根目錄 src/,使用 components 來表示路徑 src/components/ ,因此上面例子中的代碼能夠在簡化爲:

import ComponentA from '@/components/a.component.js';
import ServiceA from 'services/a.service.js';

這樣配置以後,webpack 在打包編譯的時候能識別簡化以後的路徑,可是編輯器卻未必能識別,這又給咱們開發帶來了一些困擾,若是你是 vscode 用戶的話,這個問題能夠很好的解決。只要在項目的根目錄添加一份配置文件 jsconfig.json 便可,配置文件的內容以下:

{
  "compilerOptions": {
    "baseUrl": ".",   // 根目錄
    "paths": {
      "@/*": [ "./src/*" ],
      "components/*": [ "./src/components/*" ],
      "services/*": [ "./src/services/*" ],
    }
  }
}

這個配置文件和 webpack 是沒有關係的,它是給 vscode 用的,想請能夠查看這裏:https://code.visualstudio.com/docs/languages/jsconfig

extensions

在原生的 JavaScript 中,使用 import 加載一個模塊是能夠不用寫文件的擴展名的,nodejs 中的 require 也是同樣,就像這樣:import ModuleA from 'a' ,如今有了 loader 咱們也但願 import 其它類型文件的時候也不寫擴展名,好比

import styles from '@/styles/common';
import html from '@/tpl/login';

只需在 webpack 中配置 extensions 便可,具體代碼以下:

// webpack.config.js
module.exports = {
  entry,
  output,
  resolve: {
    extensions: ['.js', '.json', '.css', '.html']
  }
}

該選項的值是一個數組,默認值爲 ['.js', '.json'] ,當咱們手動配置以後,默認值會被覆蓋,因此爲了避免影響以前的寫法,要在配置中將 .js.json 也加上。

我的建議不要配置此項,儘可能把文件的擴展名寫全,這樣不只能夠知道引入的文件是什麼類型,並且在打包的時候速度也相對快一些。

externals

開發一個 Web 項目確定會用到第三方的類庫好比 jQuerylodash 等,有人會選從 npm 下載,有人會選擇從 cdn 加載。這兩種方式使用起來都很簡單:

  • 從 npm 下載的包只需在用到的時候 import 就好了:import _ from 'lodash'
  • 從 cdn 加載類庫只要在 html 頁面經過 script 引入以後(注意引用順序),即可以在任何地方使用

可是從 cdn 引入的資源在開發過程有一個很很差的地方:既然已是模塊化開發了,忽然冒出一個全局變量會讓人以爲很莫名其妙,並且這個變量也不能類型提示。

那可不能夠這樣子呢:

  1. 從 cdn 加載第三方類庫(速度快)
  2. 在代碼中依然使用 import 的方式來引入資源(代碼模塊清晰)
  3. 打包的時候排除從cdn加載的資源(減少打包後的代碼體積)

答案是能夠的,配置一下 externals 就能夠輕鬆實現,以 jQuery 爲例,具體代碼以下:

// webpack.config.js
module.exports = {
  entry,
  output,
  externals: {
    jquery: 'jQuery'
  }
}

在代碼中就能夠這樣使用 jQuery :

import $ from 'jquery';

$(() => {
  console.log('hello jQuery');
});

並且打包的時候會自動的把 jquery 排除掉。

從上面的配置中能夠看出,externals 是一個對象,它的 key (jquery) 對應的是代碼中引入的包名,也就是 from 後面的字符串,它的 val (jQuery) 就是暴露在全局的變量名,jQuery 暴露在全局的變量名爲 jQuery$ ,因此這裏換成 $ 一樣是能夠的。

因此上面的代碼能夠理解爲是下面這種寫法:

const $ = window.jQuery;

$(() => {
  console.log('hello jQuery');
});

若是想對這個選項有更多的理解,能夠參考這裏:https://github.com/zhengweikeng/blog/issues/10

分離配置文件

上面的配置中,有的是適用於生產環境的,有的是適用於開發環境的,因此咱們要將配置文件作一下分離。在項目中建立 build 文件夾,用來存放咱們的構建腳本,在 build 中建立 webpack.common.js 咱們能夠將一些通用的配置寫在這裏面,好比 entry、output、loader 等等。而後咱們在建立 webpack.prod.jswebpack.dev.js 兩份配置文件,分別用來編寫打包和開發是的腳本,已經在webpack.common.js 中寫好的配置,就不須要在寫了。而後咱們利用 webpack-merge 將通用的配置分別和 dev、prod 的配置合併:

// build/webpack.prod.js
const merge = require('webpack-merge'); 

const webpackCommonConfig = require('./webpack.common');

module.exports = merge(webpackCommonConfig, {

/** 針對打包到生產環境的配置 */

});

最後再利用 npm script 設置不一樣的腳本

{
  "scripts": {
    "dev": "webpack-dev-server --mode development --color --progress --config  build/webpack.dev.js",
    "build": "node build/build.js"
  }
}

這裏我已經寫好一份能夠直接使用的配置,你們能夠參考一下 webpack-workbench https://github.com/onlymisaky/webpack-workbench

相關文章
相關標籤/搜索