webpack4介紹與總結

webpack是目前最火的打包工具,本文以最新的webpack4爲背景,從概念方面介紹webpack是什麼、能給咱們帶來什麼好處以及如何配置。css

因爲web應用擴展地得極其迅猛,前端技術也是突飛猛進,前端的苦不是有多難學,而是我剛學完,這東西就被淘汰了(手動哭臉)。框架方面咱們有vue、react、angular,咱們須要寫vue單文件,也須要寫jsx語法;js方面咱們有Typescript、es6(七、八、9),如今是每一年一個小版本的迭代,語法也是在不斷更新和淘汰;模塊方面咱們有es6的modlue、CommonJS、AMD;css方面咱們有sass、less、postcss。新技術層出不窮,瀏覽器實現跟不上,還要考慮到瀏覽器兼容性問題,是一個使人頭大的問題。可是webpack能夠輕鬆幫咱們解決這些問題,咱們能夠在webpack的世界中放心地使用上述提到的技術,以更方便、舒服的方式完成咱們開發。html

webpack是什麼

At its core, webpack is a static module bundler for modern JavaScript applications.這是官網的定義,webpack就是一個靜態模塊的打包工具。webpack能夠將工程中的靜態資源根據咱們聲明的依賴關係,打包出最後的正常運行的輸出文件。官網顯示的這幅圖很形象地描述了這個過程: 前端

打包過程

在webpack中,全部的靜態資源均可以被處理爲一個模塊,包括js、圖片、css、字體。模塊化是前端開發的主要模式,模塊化的好處有不少,我想到的有如下3種:vue

  • 更輕鬆地拆分代碼邏輯,對於大型工程尤爲重要
  • 更容易地去管理變量,模塊會自動生成一個局部變量環境,減小全局變量的污染
  • 顯示地聲明依賴關係,想一想以前在head中引入不少script標籤,因爲script順序錯誤而引發的bug

在es6以前,原生js是不支持模塊化開發。由於js設計之初,只是爲了實現表單驗證等簡單的交互,並無考慮到要用js來寫大型的web應用。隨着js承擔地職責愈來愈大,模塊化開發的需求愈來愈急迫。因而,js社區誕生出了CommonJS、AMD這樣的js模塊化標準,隨後在es6中,也終於加入了module的原生支持。如今最新版本的各大瀏覽器均已實現了對es6的module語法支持,但也僅限於最新的幾個版本,詳情能夠查看一下can i ues。咱們能夠把webpack當成是模塊化標準的實現方案,但webpack的功能不只限於此。node

webpack支持多種模塊使用方式,包括es6的module、CommonJS、AMD。推薦使用es6的module語法,一方面是由於它是標準,是之後模塊化語法的主要使用方式;另外一方面,是由於它是靜態的,webpack能夠依靠靜態分析,作一些優化,好比Treeshaking。webpack自帶js模塊處理功能,其餘類型的靜態資源咱們須要經過配置相應的loader去處理。react

概念和配置

webpack中最重要的概念有如下幾個:jquery

  • Entry, 工程的入口文件配置
  • Output, 打包的輸出的文件配置
  • Chunk, webpack處理和輸出的包
  • Loaders, 加載器,用於處理各類不一樣類型的模塊,可擴展
  • Plugins, 插件,在webpack打包過程當中不一樣時機執行一些任務,好比清除打包目錄、複製靜態文件、抽取css文件
  • Mode, 區分開發環境和生成環境

webpack通常根據配置文件去執行打包任務,咱們建立一個webpack.config.js文件來編寫咱們的打包配置。webpack

Entry

Entry,顧名思義就是工程的入口文件,Entry的配置寫法有三種:git

  • 對象,可配置多入口,可配置chunk名,靈活可擴展,最經常使用,一個屬性就是一個entry chunk
module.exports = {
  entry: {
    app: './src/app.js',
    vendors: './src/vendors.js'
  }
};
複製代碼
  • 字符串, 最簡單直接方式,單入口,chunk名默認爲main
module.exports = {
  entry: './path/to/my/entry/file.js'
};
複製代碼
  • 數組, 多入口,將多個入口文件打包爲一個chunk,chunk名默認爲main
module.exports = {
  entry: ['./path/to/my/entry/file.js', './path/to/my/entry/file1.js']
};
複製代碼

入口通常用對象寫法便可,其餘兩種寫法的可忽略。es6

Output

Output用於配置打包輸出的文件,包括輸出文件的文件名、輸出路徑、靜態資源地址,這裏列出最經常使用的4種:

module.exports = {
  entry: {
    app: './src/app.js',
    search: './src/search.js'
  },
  output: {
    filename: 'js/[name].js',
    chunkFilename: 'js/[name].js',
    path: __dirname + '/dist',
    publicPath: 'http://cdn.example.com/assets/[hash]/'
  }
};
複製代碼

配置項以下:

  • filename: 配置輸出文件名,可添加路徑配置(例子中js/),可以使用佔位符,佔位符有如下5種:

    • name: chunk名,在該例子中就是app和search
    • hash: 模塊標識符的hash值,跟工程內容相關
    • chunkhash: chunk內容的hash值,只和當前chunk內容相關,可用於緩存設置
    • id: 模塊標識符
    • query:模塊查詢參數,取文件名中?後面的內容
  • path: 文件的輸出路徑,必須是絕對地址

  • publicPath: 用於設置打包過程當中產生的靜態文件的最終引用地址,靜態文件的最終引用地址爲output.publicPath + output.filename,不少時候,你的靜態文件放置在CDN上,經過publicPath就能夠很方便地設置。若是你的靜態引用地址在運行時才能肯定,能夠在入口文件中設置__webpack_public_path__來決定publicPath的值:

    __webpack_public_path__ = myRuntimePublicPath;
    
    // rest of your application entry
    複製代碼
  • chunkFilename: 用於設置非entry入口的chunk的輸出文件名,非entry入口的chunk通常在動態引入和CommonsChunkPlugin中產生,這是一個Plugin,用於抽取公共代碼或者進行代碼分割等操做,該plugin已經在webpack4中廢除,由webpac4內置的optimization.splitChunks替代,後面會講到

output還有其餘不少配置,這4個是經常使用配置。

Loaders

Loaders能夠理解爲不一樣類型模塊的處理器,將這些類型的模塊處理爲瀏覽器可運行和識別的代碼。好比babel-loader將es6以上代碼轉換爲es5代碼;sass-loader將sass代碼解析爲css代碼;url-loader和file-loader能夠將圖片、字體等靜態文件解析爲base64碼或者靜態文件地址。Loaders給咱們提供了處理模塊的入口,在裏面可使用所有的js功能,從而使webpack具備了強大而靈活的能力。webpack及webpack社區提供了功能強大的loader供開發者使用,你也能夠本身編寫loader。下面介紹一下在工程中經常使用的loader。

js Loaders

babel-loader

使用babel將ES2015+的代碼轉碼爲ES5的代碼,babel的具體配置可參考babel官網

modlue: {
    rules: [
        {
             test: /\.js$/,
             exclude: /(node_modules|bower_components)/,
             use: 'babel-loader
        }
    ]
}
複製代碼

exclude表示不處理的目錄,通常node_modules中的第三方js文件不在咱們的處理範圍內。

script-loader

該loader使對應的js文件在全局環境中運行一次。好比咱們在工程中使用了jquery的插件,須要全局暴露,咱們就須要讓jquery文件在全局環境中運行,以便讓它把掛載到全局變量中,效果和在瀏覽器中加script標籤同樣。

import 'jquery';

module: {
    rules: [
        {
        test: /jquery$/,
        use: [ 'script-loader' ]
        }
    ]
}
複製代碼

css Loaders

  • style-loader:將css模塊以style標籤的形式加入到html中

  • css-loader:主要用來解析css中的靜態資源,將@import和url()解析爲import/require(),解析出的除css之外的靜態資源,通常交給url-loader和file-loader去處理

  • postcss-loader:能夠對css進行各類處理,功能強大,好比自動添加css前綴,也可自定義插件

  • sass-loader/less-loader:將sass/less代碼轉換爲css

解析一個sass文件,並不僅須要一個loader,它須要多個loader串行處理,webpack能夠配置多個loader串行處理:

module: {
    rules: [
        {
        test: /\.sass$/,
        use: [ 'style-loader', 'css-loader', 'postcss-loader', 'sass-loader' ]
        }
    ]
}
複製代碼

咱們能夠將use配置爲一個數組,loader從右往左依次執行,且前一個loader的結果是下一個loader的輸入。最後一個loader的輸出就是咱們最終要的結果。一個sass文件首先通過sass-loader處理,變成css文件,又通過postcss-loader處理,添加瀏覽器前綴等功能,接着交給css-loader去解析css文件引用的靜態變量,最後由style-loaderscript標籤的形式加入到html中。

Files Loaders

url-loaderfile-loader是一對用來處理圖片、svg、視頻、字體等靜態資源文件的loader。通常體積比較小的資源交給url-loader處理,編碼爲base64字符串,直接嵌入js文件中。體積較大的文件由file-loader處理,直接釋放爲了一個輸出文件。

{
    test: /\.(png|jpe?g|gif|svg)(\?.*)?$/,
    loader: 'url-loader',
    options: {
        limit: 10000,
        name: 'img/[name].[hash:7].[ext]'
    }
},
複製代碼

通常只配置url-loader便可,在資源超過limit的時候,url-loader會將資源自動交給file-loader處理,並將options內容也傳遞給file-loader。

Plugins

loaders用來轉換某種特定類型的module,plugins則用來在一些合適的時機執行一些特定的任務,好比代碼分割、靜態資源處理、環境變量的注入、將全部css的module抽取爲單個文件等。webpack自身也是用插件系統構建起來的。插件的目的是作任何loaders作不了的事情。

HtmlWebpackPlugin

HtmlWebpackPlugin插件能夠用來生成包含你全部打包文件(js和css)的html文件,特別是你在打包文件名配置了hash,就不得不用這個插件了。

module.exports = {
  plugins: [
      new HtmlWebpackPlugin({
          template: path.resolve(__dirname, 'src/index.html'),  
          filename: 'static/index.[hash].html',  
          inject: true, 
          minify: false, 
          chunks: ['app', 'vendor'] 
      })
  ]
};
複製代碼
  • template: HtmlWebpackPlugin生成html文件的模板,若是簡單的話能夠直接經過其餘配置項生成,沒必要單獨提供一個html文件模板,詳情可參考HtmlWebpackPlugin
  • filename: 輸出的html文件名,規則和output的filename相同
  • inject: 是否注入打包的文件,包括js和經過MiniCssExtractPlugin打包輸出的css文件,默認爲true
  • minify: 是否壓縮
  • chunks: 只注入某些特定chunk的輸出文件,在多文件場景下比較有用

MiniCssExtractPlugin

MiniCssExtractPlugin將一個chunk中的css抽取爲一個單獨的css文件,若是chunk中不包含css,則不生成文件。且支持按需加載和sourceMap。webpack4新增插件,在webpack4以前是使用ExtractTextWebpackPlugin來作這件事。官方文檔總結了MiniCssExtractPlugin相對ExtractTextWebpackPlugin的四個優點:

  • 按需異步加載
  • 沒有重複編譯(性能)
  • 使用更簡單
  • 專門爲css設計
const MiniCssExtractPlugin = require("mini-css-extract-plugin");
const devMode = process.env.NODE_ENV !== 'production'

module.exports = {
  plugins: [
    new MiniCssExtractPlugin({
      // Options similar to the same options in webpackOptions.output
      // both options are optional
      filename: "[name].css",
      chunkFilename: "[id].css"
    })
  ],
  module: {
    rules: [
      {
        test: /\.css$/,
        use: [
          devMode ? 'style-loader' : {  // MiniCssExtractPlugin目前尚未HMR功能,因此最好只在生成環境使用
            loader: MiniCssExtractPlugin.loader,
            options: {
              publicPath: '../' // 可單獨配置publicPath,默認採用output中的publicPath
            }
          },
          "css-loader"
        ]
      }
    ]
  }
}
複製代碼

CopyWebpackPlugin

CopyWebpackPlugin用來處理靜態文件,能夠將文件或者文件夾原封不動地移動到打包目錄。

const CopyWebpackPlugin = require('copy-webpack-plugin')

const config = {
  plugins: [
    new CopyWebpackPlugin([
        { from: 'source', to: 'dest' },
        { from: 'source', to: 'dest', toType: 'dir|file|template' }, // 手動設置to的類型,好比設置成dir,即便to設置爲a.js,最後也會生成a.js文件夾
        { from: 'source', to: 'dest', context: '/app' }, // 基準目錄,from相對於context解析
        { from: 'source', to: 'dest', ignore: ['*.js'] }, // ignore, 忽略匹配的文件
        'source' // 只有from,to默認爲output的path
     ], {
         context: '/app', // 同上
         ignore: ['*.js'], // 同上
     })
  ]
}
複製代碼

CleanWebpackPlugin

CleanWebpackPlugin用來清除打包目錄,主要用於每次從新生成的時候,清除殘留文件。在文件有hash值的狀況下,是必要的。

const CleanWebpackPlugin = require('clean-webpack-plugin');
const config = {
  plugins: [
    new CleanWebpackPlugin(['dist', 'bulid/*.js'], {
        watch: false, // 是否在--watch模式下也清除files而後從新編譯,默認爲false
        exclude: [ 'files', 'svg', 'css' ] // 不刪除的子目錄和文件
    }),
  ]
}
複製代碼

DefinePlugin

DefinePlugin用來定義webpack編譯期間的全局變量。咱們能夠根據這些變量,來作不一樣的動做。最典型的就是能夠區分開發環境和生產環境,好比在開發環境打印各類警告、錯誤,在生產環境去掉這些跟業務無關的代碼。

DefinePlugin的參數是一個對象,鍵名是一個標識符,或者用.隔開的多級標識符,參數遵循如下規則

  • 若是參數值是一個字符串,則被當作代碼塊
  • 若是參數值不是字符串,則會自動轉換爲字符串
  • 若是參數值是一個對象,則對象的每一個值都按照上述規則被定義爲全局變量
  • 若是鍵名前面有typeof,則只是定義typeof的調用
new webpack.DefinePlugin({
 'process.env.NODE_ENV': JSON.stringify('production'),
  PRODUCTION: JSON.stringify(true),
  VERSION: JSON.stringify('5fa3b9'),
  BROWSER_SUPPORTS_HTML5: true,
  TWO: '1+1',
  'typeof window': JSON.stringify('object')
});
複製代碼

index.js

console.log('PRODUCTION', PRODUCTION);
console.log('VERSION', VERSION);
console.log('BROWSER_SUPPORTS_HTML5', BROWSER_SUPPORTS_HTML5);
console.log('TWO', TWO);
console.log('Object', typeof window);
複製代碼

被編譯爲

console.log('PRODUCTION', true);
console.log('VERSION', "5fa3b9");
console.log('BROWSER_SUPPORTS_HTML5', true);
console.log('TWO', 1+1);
console.log('Object',  false ? undefined : _typeof(window));    // 不太明白
複製代碼

DefinePlugin的原理很簡單,只是在編譯過程當中遇到這些定義好的鍵名,就用鍵值作簡單的文本替換。因此,你若是想給全局變量賦一個字符串,須要這樣寫'"production"',通常使用JSON.stringify來轉一下。

Mode

webpack4新增了mode配置。webpack會根據mode值自動幫你作一個不一樣的優化:

  • production(默認值)

    • DefinePlugin中將process.env.NODE_ENV設置爲production
    • 默認啓用了以下插件:FlagDependencyUsagePlugin, FlagIncludedChunksPlugin, ModuleConcatenationPlugin, NoEmitOnErrorsPlugin, OccurrenceOrderPlugin, SideEffectsFlagPlugin and UglifyJsPlugin
  • development:

    • DefinePlugin中將process.env.NODE_ENV設置爲development
    • 默認啓用了以下插件:NamedChunksPlugin, NamedModulesPlugin
  • none: 什麼都不作

mode的兩種使用方式

  • 配置:
module.exports = {
  mode: 'production'
};
複製代碼
  • 命令行:
webpack --mode=production
複製代碼

代碼分割

代碼分割能夠把代碼按照必定的邏輯分割,用來作按需加載或者並行加載,以減小加載時間。在webpack中,主要有如下3中方式,實現代碼分割:

  • Entry Points: 手動在entry入口配置處配置多個入口
  • SplitChunks: webpack4默認帶這個優化插件,用於抽取不一樣chunk的公共部分,或者直接代碼分割
  • Dynamic Imports: 動態加載,在函數中使用import()語法,webpack使用SplitChunksimport()加載的module分割爲一個單獨的chunk,並在函數執行時加載該chunk對應的js文件。

第一種沒什麼好說的,主要說一下後兩種。

SplitChunks

在webpack4以前的版本,都是使用CommonsChunkPlugin來抽取公共chunk,在webpack4中廢棄了CommonsChunkPlugin,轉而支持內置的optimization.splitChunks

splitChunks默認只對按需加載的chunk起做用,會自動將import()引入的module分割爲單獨chunk,可是須要知足如下條件:

  • 新chunk中的模塊被共享或者來自node_modules
  • 新chunk必須大於30kb(before min+gz)
  • 按需加載的chunks並行加載的數量要小於等於5
  • 入口文件的並行加載的請求應該小於等於3

在執行後兩個原則的時候,體積大的chunk被優先生成。

splitChunks的默認配置以下,咱們能夠看出正好是和上面4個條件對應的:

module.exports = {
  optimization: {
    splitChunks: {
      chunks: 'async',
      minSize: 30000, // chunk只有超過這個大小纔會被分割
      maxSize: 0, // 大於這個體積的chunk會被自動分割爲更小的chunk
      minChunks: 1, // 一個模塊被共享的chunk數量大於minChunks時,纔會被分割出來
      maxAsyncRequests: 5, // 按需加載最大的並行數
      maxInitialRequests: 3, // 初始加載最大的並行數
      automaticNameDelimiter: '~', // name爲true時,新chunk的文件名由cacheGroups的key加上chunks屬性的一些信息生成,automaticNameDelimiter是分隔符
      name: true,
      cacheGroups: {  // 配置拆分規則,會繼承splitChunks全部的配置項,全部splitChunks配置項均可以在這裏重寫覆蓋,test、prioprity、reuseExistingChunk是cacheGroups獨有的屬性
        vendors: {
          test: /[\\/]node_modules[\\/]/, // 模塊匹配規則,能夠是正則表達式或者函數,不寫默認選擇全部模塊
          priority: -10 // 優先級,當同一個模塊同時包含在不一樣cacheGroup中,該模塊將被劃分到優先級高的組中
        },
        default: {
          minChunks: 2,
          priority: -20,
          reuseExistingChunk: true  // 若是該chunk包含的modules都已經另外一個被分割的chunk中存在,那麼直接引用已存在的chunk,不會再從新產生一個
        }
      }
    }
  }
};
複製代碼

chunks

splitChunks的chunks屬性表示做用的chunk範圍。chunks能夠是一個函數,徹底由開發者控制;也能夠是一個字符串,字符串一共有3個值:

  • initial,表示只做用於入口chunk
  • async,表示只做用於動態引入的異步chunk
  • all,全部chunk

name

name屬性表示最後生成chunk的文件名,有如下3中類型取值:

  • boolean,設置爲true,則會根據cacheGroup的key和chunks屬性的信息自動生成
  • string,若是設置在splitChunks下,那全部chunk設置爲相同的名稱,這會形成不一樣的chunk合成爲一個chunk,固然你能夠設置在cacheGroup
  • function,徹底由開發者控制

Tree Shaking

Tree Shaking此次詞很形象,搖樹,把爛掉的樹葉搖下來。咱們工程中的爛樹葉就是那些咱們導出了,可是沒有用到的代碼,Tree Shaking能夠幫助咱們去除無效代碼,減少打包體積,有其在大工程中,效果明顯。

Tree Shaking的使用三部曲:

靜態的模塊語法

使用Tree Shaking的第一個前提條件就是必須使用ES2015的模塊語法,import,export。由於ES2015的模塊語法是靜態加載的,而CommonJS和AMD都是動態加載靜態加載是指在編譯階段你就能肯定導出和加載的內容。ES2015在語法上規定:你只能在頂層模塊做用域進行導入導出操做,不容許在條件語句中導入導出,也不容許導入和導出的內容有變量。這意味着你只分析源碼就能夠肯定導入和導出的內容,而不是等到運行時才能肯定。好比說下面CommonJS的語法,在ES6中就是不能使用的:

var my_lib;
if (Math.random()) {
    my_lib = require('foo');
} else {
    my_lib = require('bar');
}
複製代碼

你只有在運行的時候,才知道到底加載是的foo仍是bar。ES6強制模塊語法靜態化,失去了必定的靈活性,可是帶來了更多的好處,其中之一就是咱們能夠經過靜態分析去實現Tree Shaking。

sideEffects

在徹底的Es6模塊世界中,代碼是沒有反作用的,可是如今咱們可能用到的各類地方庫會有反作用。反作用是指代碼除了導入和導出,還作了一些其餘影響了其餘代碼的行爲,好比定義了全局變量。最典型的例子就是polyfills,好比Promise的polyfills就定義了Promise全局變量。這時候若是咱們分析到Promise有未使用的導出代碼,則不能刪除,不然可能會影響Promise的使用。哪些是有反作用的代碼,須要你識別,而且告訴webpack,方式就是經過設置sideEffects,能夠設置在package.json文件中,也能夠設置的module.rules中。

{
  "name": "your-project",
  "sideEffects": [
    "./src/some-side-effectful-file.js",
    "*.css"
  ]
}
複製代碼

UglifyJSPlugin

webpack會經過靜態分析找到冗餘代碼,並打上標記,咱們看一下官網例子:

math.js

export function square(x) {
  return x * x;
}

export function cube(x) {
  return x * x * x;
}
複製代碼

index.js

import { cube } from './math.js';

  function component() {
   var element = document.createElement('pre');

   element.innerHTML = [
     'Hello webpack!',
     '5 cubed is equal to ' + cube(5)
   ].join('\n\n');

    return element;
  }

  document.body.appendChild(component());
複製代碼

development模式下打包,內容以下:

/* 1 */
/***/ (function(module, __webpack_exports__, __webpack_require__) {
 'use strict';
  /* unused harmony export square */
  /* harmony export (immutable) */ __webpack_exports__['a'] = cube;
  function square(x) {
    return x * x;
  }

  function cube(x) {
    return x * x * x;
  }
});
複製代碼

咱們能夠看到代碼中並未用到math.js中的square方法,webpack輸出文件中表示它未被使用。若是想去掉未被使用的代碼,則須要用到UglifyJSPlugin插件,它是用來壓縮js文件的,自動啓用了去除冗餘代碼的功能。咱們能夠在production模式下打包,會發現square的代碼已經被去除。

博客地址

相關文章
相關標籤/搜索