webpack筆記

Webpack

功能包括css

  • 模塊打包器(Module bundler)
  • 模塊加載器(Loader)
  • 代碼拆分(Code Splitting)模塊增量加載,漸進式加載
  • 資源模塊(Asset Module)

打包工具解決的是前端總體的模塊化,並非單指JavaScript模塊化html

安裝,由於是基於npm的包,因此前端

  • yarn init --yes
  • yarn add webpack webpack-cli --dev (^4.40.2, ^3.3.9)
  • yarn webpack --version 查看版本
  • yarn webpack 開始打包,webpack自動從src/index.js開始打包

4.0之後webpack執行支持零配置打包,將src/index.js做爲打包入口 -> dist/main.js生成地址node

添加配置文件 webpack.config.js,運行在node環境的js文件,按CommonJs方式編寫代碼webpack

image.png

# webpack.config.js

const path = require('path')

module.exports = {
  entry:'./src/main.js', //項目打包入口文件路徑
  output:{               //輸出文件配置,是個對象
    filename:'bundle.js', //輸出文件名稱
    path:path.join(__dirname,'output')         //輸出文件所在目錄,必須是absolute path!
  },
  mode:'development' //webpack4以上 工做模式(不一樣環境的幾組預設配置)
  //默認是production,自動啓動優化,優化打包結果
  //development 優化打包速度,添加一些調試過程須要的輔助
  //none 原始打包不會進行額外處理
  // 使用yarn webpack --mode development執行
}

資源打包

非js代碼經過loader加載,Webpack內部的loader默認只能處理js,json文件
image.pnggit

yarn add css-loader --dev (^3.2.0)es6

yarn add style-loader --dev (^1.0.0)github

const path = require('path')

module.exports = {
  entry:'./src/main.css', //項目打包入口文件路徑
  output:{               //輸出文件配置,是個對象
    filename:'bundle.js', //輸出文件名稱
    path:path.join(__dirname,'dist')         //輸出文件所在目錄,必須是absolute path!
  },
  mode:'none',//production,development
  module: {
    rules:[ //除js外其餘資源模塊加載規則配置
      {
        test:/.css$/,
        use:['style-loader','css-loader'] //匹配到的文件使用的loader
      }
    ]
  }
}

loader內的rules,use從後向前執行,經過style標籤掛載到html上web

image.png

導入資源模塊

上面導入css經過修改entry入口文件,可是通常狀況下入口文件仍是js文件,打包的入口就至關於運行的入口,JS驅動整個前端應用業務,
經過入口文件引入 css的方式也能達到目的npm

# main.js

import createHeading from './heading.js'

import './main.css'

const heading = createHeading()

document.body.append(heading)

根據代碼須要動態導入資源,例如js中引入css

爲何不是js,css分離的方式?

js能夠比做驅動文件,而css和樣式是輔助js進行美化的,所以在js中引入css邏輯更合理,並且js確實須要這些資源文件

確保上線資源不缺失,都是必要的

文件資源加載器

文件圖片資源加載方式經過yarn add file-loader --dev (^4.2.0)

const path = require('path')

module.exports = {
  mode: 'none',
  entry: './src/main.js',
  output: {
    filename: 'bundle.js',
    path: path.join(__dirname, 'dist'),
    // publicPath: 'dist/' //默認值''網站的根目錄
  },
  module: {
    rules: [
      {
        test: /.css$/,
        use: [
          'style-loader',
          'css-loader'
        ]
      },
      {
        test: /.png$/,
        use: 'file-loader'
      }
    ]
  }
}

然而圖片資源並無被加載出來,查看下加載路徑

image.png

默認加載網站根目錄下的資源(應該加載dist下的資源),這是因爲index.html沒有生成到dist目錄,而是放在項目的根目錄下,因此把項目根目錄做爲網站根目錄,
而webpack會默認認爲全部打包結果都放在網站的根目錄下面,解決方法配置publicPath: 'dist/'

webpack打包時遇到圖片文件,根據配置文件配置,匹配到文件加載器,文件加載器開始工做,先將導入的文件copy到輸出目錄,而後將文件copy到輸出目錄事後的路徑,做爲當前模塊的返回值返回,這樣資源就被髮布出來了。不理解能夠看下bundle.js文件

URL加載器

data urls是特殊的url協議,能夠用來直接表示一個文件,傳統url要求服務器有一個對應文件,而後咱們經過請求這個地址獲得這個對應文件。 而data url是一種當前url就能夠直接表示文件內容的方式,這種url中的文本包含文件內容

使用時不會發送任何http請求

data:text/html;charset=UTF-8,<h1>xxx</h1> //html內容,編碼utf-8

對於圖片,字體沒法直接經過文本去表示的二進制類型文件,經過將文件內容進行base64編碼,編號後的字符串表示文件內容

data:image/png;base64,iVBORw0KGgoAAAANSUhE... //png類型文件,編碼base64,編碼

webpack打包靜態資源模塊時,經過data urls咱們能夠用代碼形式表示任何類型文件了

yarn add url-loader --dev (^2.2.0)

module: {
    rules: [
      {
        test: /.css$/,
        use: [
          'style-loader',
          'css-loader'
        ]
      },
      {
        test: /.png$/,
        use: 'url-loader'//將其轉化爲 dataurl 形式
      }
    ]
  }

適合體積比較小的資源,小文件使用data urls減小請求次數,大資源會致使生成的文件體積過大,影響加載效率,所以大文件用file-loader單獨提取存放,提升加載速度 ,所以爲loader添加options

module: {
  rules: [
    {
      test: /.css$/,
      use: [
        'style-loader',
        'css-loader'
      ]
    },
    {
      test: /.png$/,
      // use: 'url-loader'//將其轉化爲 dataurl 形式
      use:{
        loader:'url-loader',
        options:{ //loader配置選項
          limit:10*1024 //10kb
        }
      }
    }
  ]
}

須要注意的若是你使用url-loader的話,必定要同時安裝file-loader,url-loader對於超出10kb的文件仍是會調用file-loader

經常使用加載器分類

  1. 編譯轉換類
  2. 文件操做類
  3. 代碼檢查類

對寫的代碼進行校驗的加載器,目的統一代碼風格,提升代碼質量,通常不會修改生產環境代碼

webpack與es2015

webpack由於模塊打包須要,因此處理了import和export,可是並不能轉化代碼中其餘的es6特性

yarn add babel-loader @babel/core @babel/preset-env --dev (^8.2.2, ^7.14.3, ^7.14.2)

module: {
  rules: [
    {
      test:/.js$/,
      use:{
        loader:'babel-loader',
        options:{
          presets:['@babel/preset-env']
        }
      }
    },
    {
      test: /.css$/,
      use: [
        'style-loader',
        'css-loader'
      ]
    },
    {
      test: /.png$/,
      // use: 'url-loader'//將其轉化爲 dataurl 形式
      use:{
        loader:'url-loader',
        options:{ //loader配置選項
          limit:10*1024 //10kb
        }
      }
    }
  ]
}

webpack只是打包工具,loader加載器能夠用來編譯轉換代碼

模塊加載方式

模塊加載的方式有遵循ES Modules標準的import聲明,遵循CommonJS 標準的require函數,遵循AMD標準的define函數和require函數,建議不要混着用,會下降項目可維護性

除了上面方式,觸發模塊加載方式還有css樣式代碼中的url函數和@import指令,html代碼中圖片標籤的src屬性

css樣式文件中 url 觸發模塊加載

# main.css

body {
  min-height: 100vh;
  background: #f4f8fb;
  background-image: url(background.png);
  /* url觸發了模塊加載,注意下url-loader版本 2.2.0,如今4不顯示 */
  background-size: cover;
}

@import url(reset.css)
# reset.css

* {
  margin:0;
  padding:0;
}
# main.js

import 'main.css'

html中的src屬性觸發模塊加載

yarn add html-loader --dev (^0.5.5)

# footer.html

<footer>
  <img class="lazy" referrerpolicy="no-referrer" data-src="better.png" alt="">
</footer>
# main.js

//html文件默認會將html代碼做爲字符串導出,還須要爲html模塊配置loader
import footerHtml from './footer.html' 
document.write(footerHtml)
# webpack.config.js

{
  test:/.html$/,
  use:{
    loader:'html-loader'
  }
}

可是html-loader只能處理html 下 img:src 屬性,其餘額外屬性經過attrs 配置

{
  test:/.html$/,
  use:{
    loader:'html-loader', //默認只能處理html img src屬性,其餘額外屬性經過attrs配置
    options:{ 
      attrs:['img:src','a:href']
    }
  }
}

這個文件能夠被處理了

# footer.html

<footer>
  <!-- <img src="better.png" alt=""> -->
  <a href="better.png">download png</a>
</footer>

核心工做原理

項目中散落各類各樣代碼及資源,webpack會根據咱們的配置,找到其中一個文件做爲打包的入口,通常狀況下這個文件都是.js文件,而後他會順着咱們入口文件中的代碼,根據代碼中出現的import或require之類的語句,解析推斷出這個文件所依賴的資源模塊,而後分別去解析每個資源模塊對應的依賴,最後造成了依賴關係的依賴樹,webpack會遞歸這個依賴樹,找到每一個節點對應的資源文件,最後根據配置文件中的rules屬性,去找到模塊對應的加載器,交給對應的加載器去加載這個模塊,最後將加載到的結果放入到bundle.js中,從而實現整個項目打包。

整個過程當中,loader機制事webpack的核心

開發一個loader

建立一個markdown-loader, 引入.md文件,輸出轉化後的html

先在根文件夾下建立配置和loader文件

image.png

每一個webpack loader都須要導出一個函數,這個函數就是loader對加載到的資源的處理過程

輸入就是加載到的資源文件的內容,輸出就是這次加工事後的結果

# markdonw-loader.js

module.exports = source => {

  // console.log(source);

  //webpack加載資源過程相似於工做管道,能夠在加載過程當中依次使用多個loader,可是最終結果必須是js代碼
  // return 'hello ~'   //x
  // return 'console.log("hello ~")'  //√


  //yarn add marked --dev (markdown解析模塊)
  const html = marked(source) //返回值是html字符串

  
  // return html //面臨上面一樣問題,正確作法把他變成js代碼

  // 方式1
  // html做爲模塊導出的字符串,經過module.exports = 這樣一個字符串,
  // 可是簡單拼接的話,html中存在的換行符或者引號拼接一塊兒會形成語法錯誤

  // return `module.exports = ${html}` //x

  // return `module.exports = ${JSON.stringify(html)}` //webpack會解析模板字符串中的js代碼

  // return `export default  ${JSON.stringify(html)}` //可使用esm語法

  //方式2
  // 返回html 字符串,而後再向管道中添加一個loader處理字符串
  // 交給下一個loader處理,須要安裝 html-loader (組合loader的形式)

  return html
}
# webpack.config.js

const path = require('path')

module.exports = {
  mode: 'none',
  entry: './src/main.js',
  output: {
    filename: 'bundle.js',
    path: path.join(__dirname, 'dist'),
    publicPath: 'dist/'
  },
  module:{
    rules:[
      {
        test:/.md$/,
        use:[  
          'html-loader',
          './markdown-loader' //模塊名稱或文件路徑,相似nodejs的require
        ]
      }
    ]
  }
}

固然也能夠把自定義loader發佈到npm包上

總結

loader負責資源文件從輸入到輸出的轉換,其實是一種管道概念,對同一資源能夠依次使用多個loader

插件機制

加強webpack自動化能力

loader專一實現資源模塊加載,plugin解決其餘自動化工做

e.g. 清除dist目錄

e.g. 拷貝靜態文件至輸出目錄

e.g. 壓縮輸出代碼
webpack+plugin 實現了絕大多數前端工程化工做

自動清除目錄插件

yarn add clean-webpack-plugin --dev第三方插件

# webpack.config.js

const {CleanWebpackPlugin} = require('clean-webpack-plugin')

plugins:[
  new CleanWebpackPlugin()
]

自動生成使用bundle.js的HTML

yarn add html-webpack-plugin --dev第三方插件(^3.2.0)

dist下生成html,解決之前根下html中的引入路徑發生改變須要硬編碼的問題(經過 publicpath解決的),如今經過動態注入。

# webpack.config.js

const HtmlWebpackPlugin = require('html-webpack-plugin')

plugins:[
    new HtmlWebpackPlugin()
]

會在dist/生成 index.html文件。注意要去掉publicpath,由於此時index在dist下,咱們默認把根目錄設置成了dist/ ,訪問時瀏覽器會把index.html所在的路徑設置爲根路徑,至此咱們就能夠刪除項目中的index.html了,經過webpack自動生成

改進html-webpack-plugin生成結果

默認生成在index.html,若是想自定義修改經過配置。例如修改html的title,自定義html基礎dom結構,html-webpack-plugin參數參考文章

# webpack.config.js

const HtmlWebpackPlugin = require('html-webpack-plugin')

plugins:[
  new HtmlWebpackPlugin({
    title:'Webpack Plugin Sample',
    meta:{  //設置頁面中元數據標籤
      viewport:'width=device-width'
    },
    template:'./src/index.html'//對於大量的配置經過建立模板文件,根據模板生成頁面
  })
]

模板文件

# ./src/index.html

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <meta http-equiv="X-UA-Compatible" content="ie=edge">
  <title>Webpack</title>
</head>
<body>
  <div class="container">
    <!-- 訪問插件配置數據 -->
    <h1><%= htmlWebpackPlugin.options.title %></h1>
  </div>
</body>
</html>

同時輸出多個頁面文件

除非是單頁面應用,不然須要輸出多個html,經過添加多個實例對象到plugins中

# webpack.config.js

  plugins:[
    //用於生成index.html
    new HtmlWebpackPlugin({
      title:'Webpack Plugin Sample',
      meta:{ 
        viewport:'width=device-width'
      },
      template:'./src/index.html'
    }),

    //用於生成about.html
    new HtmlWebpackPlugin({
      filename:'about.html' //默認的filename是index.html
    })
  ]

靜態文件拷貝插件 copy-webpack-plugin

yarn add copy-webpack-plugin --dev (^5.0.4)

拷貝根目錄下 public/xx.ico靜態文件至dist

# webpack.config.js

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

plugins:[
  //參數數組,指定拷貝的文件路徑(通配符,目錄,相對路徑)
  new CopyWebpackPlugin([ 
    // 'public/**'
    'public'
  ])
]

總結

每一個項目用到的,常見的plugin

  • clean-webpack-plugin
  • html-webpack-plugin
  • copy-webpack-plugin

經過github查看特性,作到心中有數。其餘插件經過github特殊使用時特殊查找,例如imagemin-webpack-plugin 圖片壓縮

插件機制工做原理

相比於loader(加載模塊時候),plugin(涉及webpack工做每一個環節)擁有更寬的能力範圍

webpack plugin機制就是軟件開發中最多見的鉤子機制,經過鉤子機制實現。

鉤子機制相似於web中的事件。在webpack工做過程當中會有不少環節,爲了便於插件的擴展,webpack幾乎給每個環節都埋下了鉤子,這樣咱們在開發插件時,能夠經過往這些不一樣的節點上去掛載不一樣任務,就能夠輕鬆擴展webpack的能力。具體有哪些預先定義好的鉤子參考文檔

image.png

webpack開發一個插件

定義一個插件往鉤子上掛載任務,這個插件用於清除bundle.js中無用的註釋//

webpack要求插件必須是一個函數或者是一個包含apply方法的對象,
通常咱們都會把插件定義爲一個類型,而後再這個類型中定義一個apply方法
使用時經過類型構建實例去使用

# webpack.config.js

class MyPlugin{

  apply (compiler){ // 此方法在webpack啓動時自動被調用,compile配置對象,配置信息

    console.log('MyPlugin 啓動');

    // 經過hooks屬性訪問鉤子emit 
    // 參考:https://webpack.docschina.org/api/compiler-hooks/
    // tap方法註冊鉤子函數(參數1:插件名稱,參數2:掛載到鉤子上的函數)

    compiler.hooks.emit.tap('MyPlugin',compilation=>{

      // compilation 能夠理解爲這次打包的上下文,全部打包過程產生的結果都會放到這個對象中
      // compilation.assets屬性是個對象,用於獲取即將寫入目錄當中的資源文件信息

      for(const name in compilation.assets) 
      {

        // console.log("文件名稱:",name); 如圖
        // console.log("文件內容:",compilation.assets[name].source());
        
        if(name.endsWith(".js")){

          //獲取內容
          const contents = compilation.assets[name].source();

          //替換內容
          const withoutComments = contents.replace(/\/\*\*+\*\//g,'')

          //覆蓋老內容
          compilation.assets[name] = {

            source:()=> withoutComments,
            size:()=> withoutComments.length //返回內容的大小,webpack要求必須加
          }
        }
      }
    }) 
  }
}

// 使用自定義插件
plugins:[
  new MyPlugin()
]

從上面的類咱們瞭解了插件是經過在生命週期的鉤子中掛載函數擴展

相關文章
相關標籤/搜索