前端基礎知識總結(四)- webpack

Webpack幾個概念:javascript

  • module: 模塊,在webpack眼裏,任何能夠被導入導出的文件都是一個模塊。
  • chunk: chunk是webpack拆分出來的:css

    • entry chunk:每一個入口文件都是一個chunk
    • 經過 import、require 引入的代碼
    • children chunk:經過 splitChunks 拆分出來的代碼
    • commons chunk: 經過CommonsChunkPlugin建立出來的文件
  • bundle: webpack打包出來的文件,也能夠理解爲就是對chunk編譯壓縮打包等處理後的產出。

webpack 優化

問題分析:html

  • 核心問題:多頁應用打包後代碼冗餘,文件體積大。
  • 根本緣由:相同模塊在不一樣入口之間沒有獲得複用,bundle之間比較獨立。

解決思路:前端

  1. 解決代碼冗餘。把不一樣入口之間,共同引用的模塊,抽離出來,放到一個公共模塊中。這樣無論這個模塊被多少個入口引用,都只會在最終打包結果中出現一次。
  2. 減少文件體積。當把這些共同引用的模塊都堆在一個模塊中,這個文件可能異常巨大,也是不利於網絡請求和頁面加載的。因此咱們須要把這個公共模塊再按照必定規則進一步拆分紅幾個模塊文件。

如何拆分,方式因人而異,因項目而異。拆分原則有:vue

  • 業務代碼和第三方庫分離打包,實現代碼分割;
  • 業務代碼中的公共業務模塊提取打包到一個模塊;
  • 第三方庫最好也不要所有打包到一個文件中,由於第三方庫加起來一般會很大。能夠把特別大的庫獨立打包,剩下的加起來若是還很大,就把它按照必定大小切割成若干模塊。

提取公共模塊

經過將公共模塊拆出來,最終合成的文件在最開始的時候加載一次,便存到緩存中供後續使用。這個帶來速度上的提高,由於瀏覽器會迅速將公共的代碼從緩存中取出來,而不是每次訪問一個新頁面時,再去加載一個更大的文件。java

webpack提供了一個很是好的內置插件幫咱們實現這一需求: CommonsChunkPlugin。不過在 webpack4 中 CommonsChunkPlugin被刪除,取而代之的是 optimization.splitChunks

CommonsChunkPlugin

CommonsChunkPlugin 插件,是一個可選的用於創建一個獨立文件(又稱做 chunk)的功能,這個文件包括多個入口 chunk 的公共模塊。node

配置選項:
  • name:能夠是已經存在的chunk(通常指入口文件)對應的name,那麼就會把公共模塊代碼合併到這個chunk上;不然,會建立名字爲name的commons chunk進行合併
  • filename:指定commons chunk的文件名
  • chunks:指定source chunk,即指定從哪些chunk當中去找公共模塊,省略該選項的時候,默認就是entry chunks
  • childrenreact

    • 指定爲true的時候,就表明source chunks是經過entry chunks(入口文件)進行code split出來的children chunks
    • children和chunks不能同時設置,由於它們都是指定source chunks的
    • children 能夠用來把 entry chunk 建立的 children chunks 的共用模塊合併到自身,但這會致使初始加載時間較長
  • async:即解決children:true時合併到entry chunks自身時初始加載時間過長的問題。async設爲true時,commons chunk 將不會合併到自身,而是使用一個新的異步的commons chunk。當這個children chunk 被下載時,自動並行下載該commons chunk
  • minChunks:既能夠是數字,也能夠是函數,還能夠是Infinity, 默認值是2
minChunks含義:
數字:模塊被多少個chunk公共引用才被抽取出來成爲commons chunk
函數:接受 (module, count) 兩個參數,返回一個布爾值,你能夠在函數內進行你規定好的邏輯來決定某個模塊是否提取成爲commons chunk
Infinity: 只有當入口文件(entry chunks) >= 3 才生效,用來在第三方庫中分離自定義的公共模塊
基本使用

1. 分離出第三方庫、自定義公共模塊、webpack運行文件, 放在同一個文件中webpack

修改webpack.config.js新增一個入口文件vendor, 使用CommonsChunkPlugin插件進行公共模塊的提取:git

const path = require("path");
const webpack = require("webpack");
const packageJson = require("./package.json");

module.exports = {
  entry: {
    first: './src/first.js',
    second: './src/second.js',
    // 新增一個入口文件vendor
    vendor: Object.keys(packageJson.dependencies)
  },
  output: {
    path: path.resolve(__dirname,'./dist'),
    filename: '[name].js'
  },
  plugins: [
    // 使用CommonsChunkPlugin插件進行公共模塊的提取
    new webpack.optimize.CommonsChunkPlugin({
      name: 'vendor',
      filename: '[name].js'
    }),
  ]
}

生成dist文件夾下文件有:first.js, second.js, vendor.js。

經過查看vendor.js文件,發現first.js和second.js文件中依賴的第三方庫和自定義公共模塊都被打包進vendor.js中,同時還有webpack的運行文件。

2. 單獨分離出第三方庫、自定義公共模塊、webpack運行文件

plugins: [
  // 抽離第三方庫與webpack運行文件
  new webpack.optimize.CommonsChunkPlugin({
    name: ['vendor','runtime'], 
    // 建立runtime.js進行webpack運行文件的抽離,其中source chunks是vendor.js
    filename: '[name].js',
    minChunks: Infinity
  }),
  // 抽離自定義公共模塊
  new webpack.optimize.CommonsChunkPlugin({
    name: 'common',
    filename: '[name].js',
    chunks: ['first','second'],// 從first.js和second.js中抽取commons chunk
  }),
]

生成dist文件夾下文件有:first.js, second.js, vendor.js, runtime.js, common.js

splitChunks

屬性
  • cacheGroups: cacheGroupssplitChunks配置的核心,在cacheGroups緩存組裏配置代碼的拆分規則。緩存組的每個屬性都是一個配置規則, 例如配置default屬性,屬性名能夠不叫default能夠本身定。屬性的值是一個對象。
  • name: 提取出來的公共模塊將會以這個來命名,能夠不配置,若是不配置,就會生成默認的文件名,大體格式是index/a.js這樣的。
  • chunks: 指定哪些類型的chunk參與拆分,值能夠是string能夠是函數。若是是string,能夠是這個三個值之一:all,async,initialall表明全部模塊,async表明異步加載的模塊, initial表明初始化時就能獲取的模塊。若是是函數,則能夠根據chunk參數的name等屬性進行更細緻的篩選。
  • minChunks:splitChunks是自帶默認配置的,而緩存組默認會繼承這些配置,其中有個minChunks屬性:

    1. 它控制的是每一個模塊何時被抽離出去:當模塊被不一樣entry引用的次數大於等於這個配置值時,纔會被抽離出去。
    2. 它的默認值是1。也就是任何模塊都會被抽離出去(入口模塊其實也會被webpack引入一次)。
  • minSize

    minSize設置生成文件的最小大小,單位是字節。若是一個模塊符合以前所說的拆分規則,可是若是提取出來最後生成文件大小比minSize要小,那它不會被提取出來。這個屬性能夠在每一個緩存組屬性中設置,也能夠在splitChunks屬性中設置,在每一個緩存組都會繼承這個配置。

  • priority

    priority設置拆分規則的優先級,屬性值爲數字,能夠爲負數。當某個模塊同時符合一個以上的規則時,經過優先級屬性priority來決定使用哪一個拆分規則。優先級高者執行。

  • test

    test設置緩存組選擇的模塊,與chunks屬性的做用有一點像,可是維度不同。test的值能夠是一個正則表達式,也能夠是一個函數。它能夠匹配模塊的絕對資源路徑或chunk名稱,匹配chunk名稱時,將選擇chunk中的全部模塊。

實例

1. 實現代碼分離:

//webpack.config.js
optimization: {
  splitChunks: {
    cacheGroups: {
      default: {
        name: 'common',
        chunks: 'initial',
        minChunks: 2, //模塊被引用2次以上的才抽離
      }
    }
  }
}

進入dist目錄查看:
common.js: 包含引用2次以上的全部模塊

2. 分離第三方庫與自定義組件庫

//webpack.config.js
optimization: {
  splitChunks: {
    minSize: 300,  //提取出的chunk的最小大小
    cacheGroups: {
      default: {
        name: 'common',
        chunks: 'initial',
        minChunks: 2, //模塊被引用2次以上的才抽離
        priority: -20,
      },
      // 拆分第三方庫(經過npm|yarn安裝的庫)
      vendors: {
        test: /[\\/]node_modules[\\/]/,
        name: 'vendors',
        chunks: 'initial',
        priority: -10,
      },
      // 拆分指定文件
      locallib: {
         test: /(src\/locallib\.js)$/,
         name: 'locallib',
         chunks: 'initial',
         priority: -9
       }
    }
  }
}

進入dist目錄查看:
common.jsvendor.js包含第三方庫代碼,locallib.js包含locallib模塊的代碼。

分離動態庫

什麼是DLL

DLL(Dynamic Link Library)文件爲動態連接庫文件。在Windows中,許多應用程序並非一個完整的可執行文件,它們被分割成一些相對獨立的動態連接庫,即DLL文件,放置於系統中。當咱們執行某一個程序時,相應的DLL文件就會被調用。

爲何使用DLL

一般來講,咱們的代碼均可以致少簡單區分紅業務代碼和第三方庫。若是不作處理,每次構建時都須要把全部的代碼從新構建一次,耗費大量的時間。而後大部分狀況下,不少第三方庫的代碼並不會發生變動(除非是版本升級),這時就能夠用到dll:把複用性較高的第三方模塊打包到動態連接庫中,在不升級這些庫的狀況下,動態庫不須要從新打包,每次構建只從新打包業務代碼。

如何使用

DllPluginDllReferencePlugin 用某種方法實現了拆分 bundles,大幅度提高了構建的速度。使用DLL時,能夠把構建過程分紅dll構建過程和主構建過程,因此須要兩個構建配置文件,例如叫作webpack.config.jswebpack.dll.config.js

1. 使用DLLPlugin打包須要分離到動態庫的模塊

DllPluginwebpack內置的插件,不須要額外安裝,直接配置webpack.dll.config.js文件。此插件用於在單獨的 webpack 配置中建立一個 dll-only-bundle,會生成一個名爲 manifest.json 的文件,這個文件是用於讓 DllReferencePlugin 可以映射到相應的依賴上。

  • context(可選): manifest 文件中請求的 context (默認值爲 webpack 的 context)
  • format (boolean = false):若是爲 true,則 manifest json 文件 (輸出文件) 將被格式化。
  • name:暴露出的 DLL 的函數名(TemplatePaths[fullhash] & [name]
  • path:manifest.json 文件的 絕對路徑(輸出文件)
  • entryOnly (boolean = true):若是爲 true,則僅暴露入口
  • type:dll bundle 的類型
咱們建議 DllPlugin 只在 entryOnly: true 時使用,不然 DLL 中的 tree shaking 將沒法工做,由於全部 exports 都可使用。
// webpack.dll.config.js

module.exports = {
  entry: {
    // 第三方庫
    react: ['react', 'react-dom', 'react-redux']
  },
  output: {
    // 輸出的動態連接庫的文件名稱,[name] 表明當前動態連接庫的名稱,
    filename: '[name].dll.js',
    path: resolve('dist/dll'),
    // library必須和後面dllplugin中的name一致
    library: '[name]_dll_[hash]'
  },
  plugins: [
    new webpack.DllPlugin({
      // 動態連接庫的全局變量名稱,須要和 output.library 中保持一致
      // 該字段的值也就是輸出的 manifest.json 文件 中 name 字段的值
      name: '[name]_dll_[hash]',
      // 描述動態連接庫的 manifest.json 文件輸出時的文件名稱
      path: path.join(__dirname, 'dist/dll', '[name].manifest.json')
    }),
  ]
}

2. 在主構建配置文件使用DllReferencePlugin引用動態庫文件

webpack.config.js中使用dll要用到DllReferencePlugin, 此插件會把 dll-only-bundles 引用到須要的預編譯的依賴中。

  • context:(絕對路徑) manifest (或者是內容屬性)中請求的上下文
  • extensions:用於解析 dll bundle 中模塊的擴展名 (僅在使用 'scope' 時使用)。
  • manifest :包含 contentname 的對象,或者是一個字符串 —— 編譯時用於加載 JSON manifest 的絕對路徑
  • content (可選): 請求到模塊 id 的映射(默認值爲 manifest.content
  • name (可選):dll 暴露地方的名稱(默認值爲 manifest.name)(可參考externals
  • scope (可選):dll 中內容的前綴
  • sourceType (可選):dll 是如何暴露的 (libraryTarget)

經過引用 dll 的 manifest 文件來把依賴的名稱映射到模塊的 id 上,以後再在須要的時候經過內置的 __webpack_require__ 函數來 require 對應的模塊。

new webpack.DllReferencePlugin({
  context: __dirname,
  manifest: require('./dist/dll/react.manifest.json')
}),

第一步產出的manifest文件就用在這裏,給主構建流程做爲查找dll的依據:DllReferencePlugin去 manifest.json 文件讀取 name 字段的值,把值的內容做爲在從全局變量中獲取動態連接庫中內容時的全局變量名,所以:在 webpack.dll.config.js 文件中,DllPlugin 中的 name 參數必須和 output.library 中保持一致。

3. 在入口文件引入dll文件

生成的dll暴露出的是全局函數,所以還須要在入口文件裏面引入對應的dll文件。

<body>
  <div id="app"></div>
  <!--引用dll文件-->
  <script src="../../dist/dll/react.dll.js"></script>
</body>

使用DLL做用

1.分離代碼,業務代碼和第三方模塊能夠被打包到不一樣的文件裏,這個有幾個好處:

  • 避免打包出單個文件的大小太大,不利於調試
  • 將單個大文件拆成多個小文件以後,必定狀況下有利於加載(不超出瀏覽器一次性請求的文件數狀況下,並行下載確定比串行快)

2.提高構建速度。第三方庫沒有變動時,因爲咱們只構建業務相關代碼,相比所有從新構建天然要快的多。

移除沒必要要的文件

moment.js日期處理庫,佔用很大的體積, 由於全部的locale文件都被引入,而這些文件在整個庫的體積中佔了大部分,所以當webpack打包時移除這部份內容會讓打包文件的體積有所減少。

webpack自帶的兩個庫能夠實現這個功能:

  • IgnorePlugin
  • ContextReplacementPlugin

IgnorePlugin的使用方法以下:

// 插件配置
plugins: [
  // 忽略moment.js中全部的locale文件
  new webpack.IgnorePlugin(/^\.\/locale$/, /moment$/),
],
  
// 使用方式
const moment = require('moment');
// 引入zh-cn locale文件
require('moment/locale/zh-cn');
moment.locale('zh-cn');

ContextReplacementPlugin的使用方法以下:

// 插件配置
plugins: [
  // 只加載locale zh-cn文件
  new webpack.ContextReplacementPlugin(/moment[\/\\]locale$/, /zh-cn/),
],
  
// 使用方式
const moment = require('moment');
moment.locale('zh-cn');

模塊化引入

在項目中使用了lodash這個很經常使用的工具庫,然而在使用這類工具庫的時候每每只使用到了其中的不多的一部分功能,但卻把整個庫都引入了。所以這裏也能夠進一步優化,只引用須要的部分。

import { chain, cloneDeep } from 'lodash';
// 或者
import chain from 'lodash/chain';
import cloneDeep from 'lodash/cloneDeep';

壓縮混淆代碼

咱們日常也會對代碼進行壓縮混淆,能夠經過UglifyJS等工具來對js代碼進行壓縮,同時能夠去掉沒必要要的空格、註釋、console信息等,也能夠有效的減少代碼體積。

webpack 基本功能

webpack hash區別

hash通常是結合CDN緩存來使用,經過webpack構建以後,生成對應文件名自動帶上對應的MD5值。若是文件內容改變的話,那麼對應文件哈希值也會改變,對應的HTML引用的URL地址也會改變,觸發CDN服務器從源服務器上拉取對應數據,進而更新本地緩存。

  • hash

hash是跟整個項目的構建相關,只要項目裏有文件更改,整個項目構建的hash值都會更改。同一次構建過程當中生成的哈希都是同樣的。

output:{
  path:path.join(__dirname, '/dist'),
  filename: 'bundle.[name].[hash].js',
}
  • chunkhash

根據不一樣的入口文件(Entry)進行依賴文件解析、構建對應的chunk,生成對應的哈希值。把一些公共庫和程序入口文件區分開,單獨打包構建,接着咱們採用chunkhash的方式生成哈希值,那麼只要咱們不改動公共庫的代碼,就能夠保證其哈希值不會受影響。

output:{
  path:path.join(__dirname, '/dist/js'),
  filename: 'bundle.[name].[chunkhash].js',
}

採用chunkhash,項目主入口文件Index.js及其對應的依賴文件Index.css因爲被打包在同一個模塊,共用相同的chunkhash。因爲公共庫是不一樣的模塊,有單獨的chunkhash。因此Index文件的更改不會影響公共庫。若是index.js更改了代碼,css未改變,因爲該模塊發生了改變,致使css文件會重複構建。

  • contenthash

根據文件內容建立出惟一 hash。當文件內容發生變化時,[contenthash] 纔會發生變化。

output: {
  filename: '[name].[contenthash].js',
  chunkFilename: '[name].[contenthash].js',
  path: path.resolve(__dirname, '../dist'),
}

模塊熱更新

模塊熱替換(hot module replacement 或 HMR)是 webpack 提供的最有用的功能之一。它容許在運行時更新全部類型的模塊,而無需徹底刷新。

實現

// webpack.config.js
const HtmlWebpackPlugin = require('html-webpack-plugin');

{
  devServer: {
    contentBase: './dist',
    hot: true, // DevServer開啓模塊熱替換模式
  },
  plugins: [
    new CleanWebpackPlugin(),
    new HtmlWebpackPlugin({
      title: 'Hot Module Replacement',
    }),
  ],
}

filename 和 chunkFilename 的區別

filename列在 entry 中,打包後輸出的文件的名稱。

chunkFilename未列在 entry 中,卻又須要被打包出來的文件的名稱。默認使用 [id].js 或從 output.filename 中推斷出的值([name] 會被預先替換爲 [id] 或 [id].)。

// webpack.config.js

module.exports =  {
  entry: './src/index.js',
  output: {
    filename: '[name].bundle.js',
    path: path.resolve(__dirname, 'dist'),
    chunkFileName: '[name].bundle.js'
  }
}

CSS Modules 模塊化

現狀

由於 css 不是編程語言,因此不能聲明變量、函數,不能作判斷、循環和計算,也不能嵌套。爲了解決這個問題,衍生了兩種拓展語言 lesssass,它們兼容 css,而且拓展了編程的功能,主要是帶來了如下的特性:

  • 能夠聲明變量、函數,能夠進行一些簡單的計算、判斷、循環;
  • 能夠嵌套選擇器,這樣節省了書寫的內容,也更具閱讀性;
  • @import 避免重複導入問題,所以能夠放心大膽的導入其餘文件。

從模塊化的角度來說,lesssass 只是擴充了 css 的功能,但並無在語言的層面作模塊化,由於全局命名衝突的問題依然還在。

實現模塊化

想要讓 css 具有模塊化功能,暫時還不能從語言的層面來考慮,因此只能從工具的角度來實現。目前比較好的方式是使用 js 來加載 css 文件,並將 css 的內容導出爲一個對象,使用 js 來渲染整個 dom 樹和匹配相應的樣式到對應的元素。

css文件建議遵循以下原則

  • 不使用選擇器與id,只使用 class 名來定義樣式(由於只有 .class 才能導出爲對象的屬性)
  • 不層疊多個 class,只使用一個 class 把全部樣式定義好
  • 全部樣式經過 composes 組合來實現複用
  • 不嵌套
  • 推薦用 .className 書寫,而非 .class-name(前者能夠經過 styles.className 訪問,後者須要經過 styles['class-name'] 才能訪問)。

實例

/* dialog.css */
.root {}
.confirm {}
.disabledConfirm {}

js文件引入dialog.css, 使用 classnames 庫來操做 class 名:

/* dialog.jsx */
import classNames from 'classnames';
import styles from './dialog.css';

export default class Dialog extends React.Component {
  render() {
    const cx = classNames({
      [styles.confirm]: !this.state.disabled,
      [styles.disabledConfirm]: this.state.disabled
    });

    return <div className={styles.root}>
      <a className={cx}>Confirm</a>
      ...
    </div>
  }
}
若是你不想頻繁的輸入 styles.**,能夠試一下 react-css-modules,它經過高階函數的形式來避免重複輸入 styles.**

依賴webpack: css-loader

這個功能須要構建工具的支持,若是使用 webpack ,可使用 css-loader,並設置 options.modulestrue, 即可使用模塊化的功能了。
css-loader 解析@import和 url() ,會 import/require() 後再解析(resolve)它們。
css-loader配置項:

名稱 類型 默認值 描述
root String root值將被添加到 URL 前面,而後再進行轉譯。由於對於以 / 開頭的 URL,默認行爲是不轉譯。
url Boolean true 啓用/禁用解析 url()
alias Object {} 給url建立別名。用別名重寫你的 URL,在難以改變輸入文件的url 路徑時,這會頗有幫助。
import Boolean true 啓用/禁用 @import 處理
minimize Boolean\ Object false 啓用/禁用 壓縮
sourceMap Boolean false 啓用/禁用 Sourcemap
importLoaders Number 0 在 css-loader 前應用的 loader 的數量
modules Boolean false 啓用/禁用 CSS 模塊
camelCase Boolean\ String false 是否以駝峯化式命名導出類名
localIdentName String [hash:base64] 配置生成的類名標識符(ident)
module.exports = {
  module: {
    rules: [
      {
        test: /\.css$/i,
        loader: "css-loader",
        options: {
          modules: true,
        },
      },
    ],
  },
};

loader 和 plugin 的區別

loader

是一個轉換器,將A文件進行編譯成B文件,好比:將A.less轉換爲A.css,單純的文件轉換過程。
是一個導出爲function的node模塊。能夠將匹配到的文件進行一次轉換,同時loader能夠鏈式傳遞。

plugin

是一個擴展器,經過鉤子能夠涉及整個構建流程,能夠作一些在構建範圍內的事情。
它並不直接操做文件,而是基於事件機制工做,會監聽webpack打包過程當中的某些節點,執行普遍的任務。

經常使用loader

  • 樣式:style-loader、css-loader、less-loader、sass-loader等
  • 文件:raw-loader、file-loader 、url-loader等
  • 編譯:babel-loader、coffee-loader 、ts-loader等
  • 校驗測試:mocha-loader、jshint-loader 、eslint-loader等

sass-loader轉化sass爲css文件,而且包一層module.exports成爲一個js module。

css-loader解析@import和 url() 。

style-loader將建立一個style標籤將css文件嵌入到html中。

vue-loader、coffee-loader、babel-loader等能夠將特定文件格式轉成js模塊、將其餘語言轉化爲js語言和編譯下一代js語言。

file-loader能夠處理資源,file-loader能夠複製和放置資源位置,並能夠指定文件名模板,用hash命名更好利用緩存。

url-loader能夠處理資源, 將小於配置limit大小的文件轉換成內斂Data Url的方式,減小請求。

raw-loader能夠將文件以字符串的形式返回

imports-loader、exports-loader能夠向模塊注入變量或者提供導出模塊功能。

經常使用Plugin

  • webpack內置UglifyJsPlugin,壓縮和混淆代碼。
  • webpack內置CommonsChunkPlugin,將指定的模塊或公用模塊打包出來,減小主bundle文件的體積,配合緩存策略,加快應用訪問速度。
  • webpack 內置DllPluginDllReferencePlugin相互配合,前置第三方包的構建,只構建業務代碼,同時能解決Externals屢次引用問題。DllReferencePlugin引用DllPlugin配置生成的manifest.json文件,manifest.json包含了依賴模塊和module id的映射關係
  • html-webpack-plugin能夠根據模板自動生成html代碼,並自動引用css和js文件
  • extract-text-webpack-plugin 將js文件中引用的樣式單獨抽離成css文件
  • HotModuleReplacementPlugin 熱更新
  • optimize-css-assets-webpack-plugin 不一樣組件中重複的css能夠快速去重
  • webpack-bundle-analyzer 一個webpack的bundle文件分析工具,將bundle文件以可交互縮放的treemap的形式展現。
  • compression-webpack-plugin 生產環境可採用gzip壓縮JS和CSS
  • happypack:經過多進程模型,來加速代碼構建
  • clean-wenpack-plugin 清理每次打包後沒有使用的文件

webpack文件分離思想

現狀

爲何要分離第三方庫?

第三方庫是比較穩定,不會輕易改變的,利用瀏覽器緩存後,用戶再次加載頁面會減小服務器請求,提升速度優化體驗。提取多個應用(入口)公共模塊的做用和他相似,公共部分會被緩存,全部應用均可以利用緩存內容從而提升性能。

分離第三方庫就能利用瀏覽器換緩存了麼?

答案是否認的。致使沒法利用緩存的因素有不少,好比每次分離的庫文件從新打包都會獲得不一樣的名稱,後臺的同事給js文件設置的緩存過時時間爲0,只要文件是徹底不變的,包括修改時間,文件內容等,依然會利用緩存。

瀏覽器緩存機制是什麼樣的?

HTTP1.1給的策略是使用Cache-control配合Etag。
Apache中,ETag的值默認是對文件的索引節(INode),大小(Size)和最後修改時間(MTime)進行Hash後獲得的。若是Etag相同,依然不會請求新資源,而會使用之前的文件。

文件分離插件

CommonsChunkPlugin與SplitChunksPlugin

做用

將公共模塊抽離。每次打包的時候都會從新打包,仍是會去處理一些第三方依賴庫,只是它能把第三方庫文件和咱們的代碼分開掉,生成一個獨立的 js 文件。可是它仍是不能提升打包的速度

自 webpack 4.0 上線以後,CommonsChunkPlugin 已被替換成 SplitChunksPlugin,旨在優化 chunk 的拆分。

CommonsChunkPlugin

設計思路:知足 minChunks 的引用次數時,都會將對應的模塊抽離如一個新的 chunk 文件中,這個文件爲全部的業務文件的父級。

這種設計思路帶來了會形成模塊打包冗餘。總的來講會形成這麼幾個問題:

  • 產出的 chunk 在引入時,會包含重複的代碼;
  • 沒法優化異步 chunk;
  • 高優的 chunk 產出須要的 minchunks 配置比較複雜。

SplitChunksPlugin

SplitChunksPlugin 優化了 webpack 的打包策略,使用自動重複算法,會自動計算出各頁面公共的包引用以及部分頁面公共的包引用,固然,對於那些部分共有可是閾值太小的文件其不會建立單獨的輸出文件,由於其大小不值得去新開一個請求。(緩存策略配置在 cacheGroup 中)

SplitChunksPlugin 默認的分包策略基於如下 4 個條件:

  1. 新代碼塊能夠被共享引用,或這些模塊都是來自 node_modules;
  2. 新產出的 vendor-chunk 的大小得大於 30kb;
  3. 按需加載的代碼塊(vendor-chunk)並行請求的數量很少於 5 次;
  4. 初始加載的代碼塊,並行請求的數量很少於 3 次。
  • SplitChunksPlugin 配合使用 RuntimeChunk 對運行時的 hash 變更作優化(至關於 CommonsChunkPlugin 的兩次使用)
  • 減小 maxInitial/AsyncRequest 會加大 module 的冗餘,可是會進一步的減小請求。
DllPlugin與DllReferencePlugin

使用

DLLPlugin 這個插件是在一個額外獨立的 webpack 設置中建立一個只有 dll 的 bundle,也就是說,除了 webpack.config.js,項目中還會新建一個 webpack.dll.config.js 文件來配置 dll 的打包。webpack.dll.config.js 做用是把全部的第三方庫依賴打包到一個 bundle 的 dll 文件裏面,還會生成一個名爲 manifest.json 文件。該 manifest.json 的做用是用來讓 DllReferencePlugin 映射到相關的依賴上去的。(可類比 CommonsChunkPlugin 的兩次打包或者 RuntimeChunk 的運行包配置)

設計思路

DLLPlugin 是提早將公共的包構建出來,使得在 build 時過濾掉這些構建過的包,使得在正是構建時的速度縮短。因此其相對來講打包速度會更快

推薦使用策略

  • 若是是單頁應用,只用DllPlugin打包庫文件便可,業務代碼一個包搞定。
  • 若是是多頁應用,DllPlugin打包庫文件,若是有不少公共的業務代碼並且可能隨時變更,就須要使用CommonsChunkPlugin提取公共業務代碼。在頁面間切換時,公共部分仍是會被緩存的。

參考:
webpack 文件分離思想

webpack運行機制

webpack的運行過程能夠簡單概述爲以下流程:

初始化配置參數 -> 綁定事件鉤子回調 -> 肯定Entry逐一遍歷 -> 使用loader編譯文件 -> 輸出文件

webpack事件流

什麼是webpack事件流?

Webpack 就像一條生產線,要通過一系列處理流程後才能將源文件轉換成輸出結果。 這條生產線上的每一個處理流程的職責都是單一的,多個流程之間有存在依賴關係,只有完成當前處理後才能交給下一個流程去處理。 插件就像是一個插入到生產線中的一個功能,在特定的時機對生產線上的資源作處理。Webpack 經過 Tapable 來組織這條複雜的生產線。 Webpack 在運行過程當中會廣播事件,插件只須要監聽它所關心的事件,就能加入到這條生產線中,去改變生產線的運做。 Webpack 的事件流機制保證了插件的有序性,使得整個系統擴展性很好。 -- 吳浩麟《深刻淺出webpack》

咱們將webpack事件流理解爲webpack構建過程當中的一系列事件,他們分別表示着不一樣的構建週期和狀態,咱們能夠像在瀏覽器上監聽click事件同樣監聽事件流上的事件,而且爲它們掛載事件回調。咱們也能夠自定義事件並在合適時機進行廣播,這一切都是使用了webpack自帶的模塊 Tapable 進行管理的。咱們不須要自行安裝 Tapable ,在webpack被安裝的同時它也會一併被安裝,如需使用,咱們只須要在文件裏直接 require 便可。

Tapable的原理

Tapable的原理其實就是咱們在前端進階過程當中都會經歷的EventEmit,經過發佈者-訂閱者模式實現,它的部分核心代碼能夠歸納成下面這樣:

class SyncHook{
    constructor(){
        this.hooks = [];
    }

    // 訂閱事件
    tap(name, fn){
        this.hooks.push(fn);
    }

    // 發佈
    call(){
        this.hooks.forEach(hook => hook(...arguments));
    }
}

webpack運行流程詳解

  • 首先,webpack會讀取你在命令行傳入的配置以及項目裏的 webpack.config.js 文件,初始化本次構建的配置參數,而且執行配置文件中的插件實例化語句,生成Compiler傳入plugin的apply方法,爲webpack事件流掛上自定義鉤子。
  • 接下來到了entryOption階段,webpack開始讀取配置的Entries,遞歸遍歷全部的入口文件
  • Webpack進入其中一個入口文件,開始compilation過程。先使用用戶配置好的loader對文件內容進行編譯(buildModule),咱們能夠從傳入事件回調的compilation上拿到module的resource(資源路徑)、loaders(通過的loaders)等信息;以後,再將編譯好的文件內容使用acorn解析生成AST靜態語法樹(normalModuleLoader),分析文件的依賴關係逐個拉取依賴模塊並重覆上述過程,最後將全部模塊中的require語法替換成__webpack_require__來模擬模塊化操做。
  • emit階段,全部文件的編譯及轉化都已經完成,包含了最終輸出的資源,咱們能夠在傳入事件回調的compilation.assets 上拿到所需數據,其中包括即將輸出的資源、代碼塊Chunk等等信息。

參考:
Webpack揭祕——走向高階前端的必經之路

相關文章
相關標籤/搜索