1. 程式人生 > 其它 >詳解webpack打包

詳解webpack打包

技術標籤:javascriptwebpack

模組化打包工具的由來

  • ES Modules存在環境相容問題
  • 模組檔案過多,網路請求頻繁
  • 所有的前端資源都需要模組化
  • 毋庸置疑,模組化是必要的

解決問題

  • 將es6等新功能變成es5等瀏覽器能識別的語言
  • 將很多個模組打包成一個bundle.js(在開發模式下用模組化開發 在生產模式下打包成一個bundle.js檔案)

模組化打包工具概述

  • webpack 模組化打包工具
    • 模組載入器(Loader)
    • 程式碼拆分(Code splitting)
    • 資源模組(Asset Module)

上面的到爆工具解決的是前端整體的模組化並不單指Javascript模組化

webpack快速上手

  • 初始化 yarn init -y
  • webpack資料工具模組需要安裝 yarn add webpack webpack-cli --dev
  • 配置webpack.config.js檔案(執行在nodejs的檔案所以要符合commjs)
const path = require('path');
module.exports={
    entry:'./src/main.js',  //檔案入口
    output:{   //輸出目錄
        filname:'bundle.js',  //輸出檔名稱
        path:path.join(_dirname,'output'
) //在outuput檔案裡面生成bundle.js檔案 //指定輸出檔案的目錄(需要絕對路徑) } }

webpack模式

  • none 最原始狀態打包
  • production
  • development

webpack資源模組載入

藉助loader可以載入任何形式的資源(loader對於同一個源可以依次使用多個loader,通過多個loader完成同一個功能。例如style-loader,css-loader)
dataUrl 可以用base64編碼進行表示任何檔案 url-loader

  • 解析css 模組 安裝
    • yarn add style-loader --dev
    • yarn add css-loader --dev
  • 檔案資源載入器 例如有些內容是不能轉化成js的例如圖片、字型 就需要使用file-loader
    • yarn add file-loader --dev
    • main.js中
  • 使用dataUrl載入任何檔案
    • yarn add url-loader
    • 適合體積比較小的檔案,因為這樣減少請求次數,如果檔案過大會影響打包速度 url-
    • 較大的檔案使用file-loader單個檔案存放,提高載入速度
    • 超過10kb檔案單獨提取存放
    • 小於10kb轉換為dataUrl嵌入程式碼中
  • 使用babel-loader轉化新特性
    • yarn add babel-loader @babel/core @babel/preset-env --dev
    • babel只是一個轉化程式碼的平臺,具體轉化需要轉化工具進行配置
    import icon from './icon.png'
    const img = new Image();
    img.src = icon;
    document.body.append(img);
    
  • 解析html(使用html-loader)
    • yarn add html-loader --dev
    <!-- footer.html -->
    
    <footer>
    <a href="better.png">download</a>
    </footer>
    
    <!-- main.js -->
    import footerHtml from './footer.html'
    documnet.write(footerHtml)
    
// 使用module:
module:{
    rules:[
        {
            test:'/.js$/',
            use: {
            loader:'babel-loader',
            options:{
                presets:['@babel/preset-env']
            }
            },
            
        },
        { //將css轉換成js的模組
            test:'/.css$/',
            //這裡需要注意 陣列中的執行順序是從右往左執行的 必須先解析css在載入style-loader
            use:['style-loader',css-loader']
        },
        {
           test:'/.png$',
          // use:'file-loader'url
          // use:'url-loader'
            use:{
                loader:'url-loader',
                options:{
                    limit:10*1024  //10kb
                }
            }
        },
        {
            test:'/.html$/',
            loader:'html-loader'
            //html中img src屬性觸發打包如果想讓別的也觸發可以進行如下配置
            options:{
                attrs:['img:src','a:href']
            }
        }
        
    ]
}

需要注意如果我們配置了url-loader 的大小限制,小於走url-loader否則走file-loader 但是需要注意的是解除安裝掉file-loader會報錯(可不配置file-loader但是不能解除安裝)

webpack只是一個打包工具,如果你想處理程式碼編譯轉化的功能需要使用對應的loader進行轉化

常用的載入器

* 編譯轉化載入類(css-loader)
* 檔案操作型別載入器(file-loader)
* 程式碼檢查載入器

webpack匯入資源模組

  • 在使用的js中使用import ‘./main.css’

需要注意我們平時寫的專案要求前後端分離 webpack建議我們根據程式碼的需要動態匯入資源,真正需要資源的不是應用,而是程式碼,程式碼什麼時候需要就要載入,程式碼更新不需要資源這個時候就不需要載入,webpack驅動了整個前端的應用。

  • 邏輯合理,js需要這些資原始檔
  • 確保上線資源不缺失,都是必要的

webpack中模組的載入方式

  • 遵循ESmodules 標準的import
  • 支援commonJS標準的require函式 但需要注意如果載入一個ESmodule的defalut匯出 需要使用
require('./header.js').default;
  • 支援AMD標準的define函式和require函式
  • 需要注意的是隻建議在一個專案中使用一種引用方式,不然會增加維護成本
  • 樣式程式碼中的@import指令和url函式
  • html程式碼中圖片標籤的src屬性

webpack打包的工作原理

專案當中都會散落著各種各樣的程式碼和資原始檔,weboack會根據配置找到一個主入口檔案一般都是.js的檔案根據程式碼import export等依賴的模組和整個的模組依賴,形成依賴數,webpack會遞迴遍歷依賴數,找到每個節點的所對應的資原始檔,根據模組對應的ruls找到模組對應的載入器,然後交給對應的載入器載入模組,載入到的結果放到bundle.js檔案中,實現專案的打包。loader是webpack的核心。

webpack loader的工作原理

自己開發一個markdown-loader,注意你可以使用多個loader但是最終返回的結果是一個javascript的程式碼

  • 安裝一個 marked的解析模組
    • yarn add marked --dev
    • 匯出的檔案支援module.exports 和esModule的格式 export.defaule
    • 如果選擇第二種方式需要安裝處理html的loader add html-loader
markdown-loader.js

const marked = required('marked');
module.exports = source =>{
    const html = marked(source);
    
    // 第一種匯出將 匯出內容直接轉化成javascript的格式
    //以下兩種格式都支援匯出
    <!--return `module.exports = ${JSON.stringify(html)}`
    return `export.defaule = ${JSON.stringify(html)}`
    -->
    
    //第二種格式 返回的html字串直接交給下一個loader來處理
    return html;
    <!--console.log(source)-->
    <!--return source;-->
}

配置webpack.json
//返回javascript的格式
{
    test:'./md$/',
    use:'./markdown-loader'  //不僅可以使用模組還可以使用路徑地址
}

//返回html的格式
{
    test:'./md$/',
    use:['html-loader','./markdown-loader']  //再次強調陣列的執行順序是從後面像前面執行的
}

webpack外掛功能

loader處理資源 plugin處理那些非資源的內容實現了大多的功能

  • 自動清除目錄的外掛 clean-webpack-plugin
    • yarn add clean-webpack-plugin --dev
  • 自動生成html檔案(如果不輸出html檔案 那麼我就就需要將html引入的script路徑手動指向打包的dist目錄下相應用的路徑地址如果打包的js檔名有所變化那我們就需要手動更改。)
    • yarn add html-webpack-plugin --dev
  • webpack動態生成html檔案
    • html中可以自己建立 用loadsh的語法輸出動態的內容
    <h1><%= htmlwebpackPlugin.options.title %></h1>
    
  • webpack多頁面輸出
  • webpack c處理不參與打包的靜態檔案需要複製到對應目錄
    • yarn add copy-webpack-plugin --dev
const {CleanWebpackPlugin} = require('clean-webpack-plugin')  
const HtmlWebpackPlugin =  require('html-webpack-plugin')  //預設匯出的外掛型別
const CopyWebpackPlugin =  require('copy-webpack-plugin') 
plugins:[
    new CleanWebpackPlugin()
    <!--new HtmlWebpackPlugin()-->
    new HtmlWebpackPlugin({  //生成index.html
        title:'webpack',
        meta:{
            viewport:'width=device-width'
        },
        template:'./src/index.html'
    })
    //新增多個例項輸出多個檔案
    new HtmlWebpackPlugin({
        filename:'about.html'
    }),
    //接受的是一個數組,可以寫路徑,目錄,萬用字元等
    new CopyWebpackPlugin([
    'public'
    //public/**
    ])
]

webpack開發一個外掛

定義外掛一個函式或者是一個包含apply方法物件

// 清除bundle.js中沒用註釋 emit在webpack輸出檔案時候執行
class MyPlugin {
    apply(compiler) {
        console.log('Myplugin');
        compiler.hooks.emit.tap('MyPlugin', compilation => {
            // compilation => 可以理解為此次打包的上下文
            for (const name in compilation.assets) {
                //檔案的名稱
                // console.log(compilation.assets[name].source())  //內容
                if (name.endsWith('.js')) {
                    const contents = compilation.assets[name].source();
                    console.log(contents);
                    const withoutComments = contents.replace(/\/\*\*+\*\//g, '');
                    // /******/   \/\*\*+\*\/
                    compilation.assets[name] = {  .//webpack必須要暴露出來的兩個方法
                        source: () => withoutComments,
                        size: () => withoutComments.length
                    }
                }
            }
        })
    }
}

在plugin中new MyPlugin();

webpack增強開發體驗 實現自動編譯

  • webpack dev server 高度整合工具 自動編譯,自動重新整理瀏覽器
yarn add webpack-dev-server --dev 
// 通過yarn執行cli
yarn webpack-dev-server 執行命令會直接打包應用

*處理靜態資源 webpack-dev-server

// 註釋:上面程式碼中new CopyWebpackPlugin 可以實現資源的copy,但是一般new CopyWebpackPlugin都留在上線前的打包使用而已,因為在開發過程中每次打包都copy會影響速度。
module.exports = {
    devServer:{
        contentBase:['./public']  //指定靜態資源
    }
}

webpack-dev-server 支援代理服務

devServer:{
   contentBase:'./public',
   proxy:{ 
   // 請求字首
       '/api':{
            // http://localhost:8080/api/users -> https:api.geithub.com/api/users
           target:"https:api.github.com", //代理目標
           pathRewrite:{
               '^/api':''  //代理路徑重寫
           },
           //不能使用localhost:8080 作為請求GitHub的主機名
           changeOrign:true
       }
   }
}

source Map

  • 執行程式碼和原始碼之間有很大差異。
  • 幫助開發者除錯和定位錯誤
  • sourceMappingUrl = jquery-3.4.1.min.map 根據對映關係定位到原始碼
  • 解決了原始碼與執行程式碼不一致所導致的問題

如何配置source Map

devtool 開發過程中的輔助工具

  • 目前source Map支援12種
  • 每種方式的功效和效率都不相同

webpack配置source Map (eval模式下的source Map)

  • eval(‘console.log(123)’)
  • eval(‘console.log(123) //# sourceURL=./foo/’)
  • 將webpack.config.js中的屬性設定為 devtool:‘eval’
  • 使用eval 構建速度最快 但是不知道錯誤的行內資訊

模式

  • eval 將模組程式碼放到eval中但是不會生成對應的sourvcemao
  • eval-source-map 生成sourceMAP
  • cheap-eval-source-map 生成速度較快可以定義行錯誤資訊不能定義到列,加工過後的結果,loader處理過後的結果
  • cheap-module-eval-source-map 能夠定義到行 沒有經過加工手寫的原始碼
  • inline-source-map和普通的相同inline-source-map以dataurl嵌入進來和eval相同 這個不常用因為體積較大
  • hidden-source-map在這個模式下程式碼並沒有看到引用,但是確實是已經生成了
  • nosources-source-map 能看到錯誤資訊,但是沒有原始碼開發過程中看不到原始碼,為了在生產環境中不爆露原始碼

webpack選擇Source Map模式

  • 開發環境打包cheap-eval-source-map
原因:
個人編寫程式碼風格每一行不會超過80個字元,所以定義到行就可以了
使用框架比較多,loader轉化之後和之前都有很多差別,所以要選擇有module
首次打包啟動會慢,但是重寫打包速度較快
  • none或者生產環境使用nosources-source-map
原因:
會暴露原始碼
除錯是開發階段的事情,開發階段儘量把所有的問題都找出來解決

## webpack dev server

webpack dev server重新整理會對樣式有影響,每次構建都需要重新整理頁面

HRM

  • 開啟HRM 整合在webpack-dev-server 中
第一種
## webpack-dev-server --host

第二種
##devServer:{
    hot:true
    // 如果出錯可以報錯不會自動重新整理 hot:only
}

const webpack = require('webpack');
plugins:[
new webpack.HotModuleReplacementPlugin();
]

//啟動 yarn webpack-dev-server --open

HMR疑惑

  • webpack中的HRM需要手動配置
  • 樣式檔案可以自動處理,js指令碼不可以

HMR API

module.hot.accept('./editor',()=>{
    
})

webpack 不同環境下的配置

  • 配置檔案根據環境不同匯出不同配置
module.exports = (env,argv){
    //env cli傳過來的引數
    //argv 除了env之後的所有引數
    if(env ==='production'){
        config.mode = 'production'
        config.devtool = false
        config.plugins = [
         ...config.plugins,
         new CleanWebpackPlugin(),
         new CopywebpackPlugin(['public'])
        ]
    }
    return config
}

//執行 yarn webpack --env production
  • 一個環境對應一個配置檔案
// 一般有三個檔案 一個線上一個開發 一個公共檔案

// ## webpack.prod.js
const common = require(./webpack.common)
const merge = require('webpack-merge')
const { CleanWebpackPlugin} = require('clean-webpack-plugin')
const CopyWebpackPlugin = require('copy-webpack-plugin')

module.exports = merge(common,{
    mode:'production',
    plugins:[
    new CleanWebpackPlugin(),
    new CopyWebpackPlugin(['public'])
    ]
})

//單獨執行 yarn webpack --config webpack.prod.js

// ## webpack.dv.js

// ## webpack.common.js
// 公共配置
const HtmlWebpackPlugin = require('html-webpack-plugin')

module.exports = {
    entry:'./src/main.js',
    output:{
        filename:'js/bundle.js'
    },
    module:{
        rules:[
            {
                test:'/.js$/',
                use: {
                loader:'babel-loader',
                options:{
                    presets:['@babel/preset-env']
                }
                },
                
            },
            { //將css轉換成js的模組
                test:'/.css$/',
                //這裡需要注意 陣列中的執行順序是從右往左執行的 必須先解析css在載入style-loader
                use:['style-loader',css-loader']
            },
            {
               test:'/.png$',
              // use:'file-loader'url
              // use:'url-loader'
                use:{
                    loader:'url-loader',
                    options:{
                        limit:10*1024  //10kb
                    }
                }
            },
            {
                test:'/.html$/',
                loader:'html-loader'
                //html中img src屬性觸發打包如果想讓別的也觸發可以進行如下配置
                options:{
                    attrs:['img:src','a:href']
                }
            }
            
        ]
    }
}

DefinePlugin

  • 為程式碼注入全域性成員 預設啟動 預設為全域性註冊了 process.env.NODE_ENV
const webpack = require('webpack');
PLUGINS :[
new webpack.DefinePlugin({
API_BASE_URL:'"https://api.example.com"'
//JSON.stringigy 如果生產和開發的值不一樣可以通過這個來注入
})
]

tree-shaking

在生產模式下自動開啟

集中配置webpack的優化功能

module.exports = [
    mode :'none',
    entry:'./src/index.js',
    output:{
        filename:'bundle.js'
    },
    optimization:{  //優化
    usedExports:true, //只標記使用的程式碼,
    minimize:true, //把這些無用的搖掉
        
    }
]

webpack 模組合併

optimization:{  //優化
   concatenatModules:true 
   //註釋:平時打包會有很多模組函式,這樣合併成一個,儘可能將所有的模組合併一個模組被稱為 Scope Hoisting  作用域的提升,既提升了執行效率,又減少了程式碼體積
}

Tree-shaking & Babel

Tree-shaking前提是ES Modles,由webpack打包的程式碼必須使用ESM,babel-loader處理程式碼時有可能會把esmodule轉化成了comminjs 所以 tree-shaking就不會生效 在最新版本中babel支援了tree-shaking原因是 原始碼中並沒有轉化esmodule

//自己配置 如果使用commonjs這個時候就不會執行tree-shaking
{
    test:/\.js$/,
    use:{
        loader:'babel-loader',
        options:{
            presets:[['@babel/preset-env',{modules:'commonjs'}]
            //{modules:'commonjs'} 就不會執行 將modules:false就會禁止轉化esmodule 所以會執行tree-shking
        }
    }
}

sideEffects 副作用

模組執行除了匯出成員之外所做的其他的事情 一般用於npm包標記是否有副作用

  • 使用場景 在多模組開發中,加入我們將多個元件都放到index.js中引用 但是 我們在外部呼叫的時候只應用了其中的button元件 但是這個元件匯入的是index.js這個時候打包會把所有的元件都打包進去這個時候我們就需要使用 optimization中的sideEffects
optimization:{
    sideEffects:true,  //打包看是否有標識看是否有副作用 如果沒有則別移除
}

//在package.json中增加
"sideEffects":false  //標識當前專案沒有副作用
  • 注意你的程式碼真的沒有副作用 css都屬於副作用
解決方法:
在package.json中
"sideEffects":['./extend.js','./extnd.css'] 陣列進行配置

程式碼分割 Code Splitting

並不是每個模組在啟動的時候都需要載入 要不然bundle的檔案過大

  • 根據不同需求 可分為多入口打包
  • 動態匯入按需載入

多頁面打包 Multi Entry (多入口打包)

應用於多應用程式 一個頁面提供一個打包入口 公共的可以提出

## 在entry 中進行配置多頁面 記住是物件
entry:{
    index:'./src/index.js',
    album:'./src/album.js'
}

## 出口自動設定相應的檔案
output:[name].bundle.js

## html中 new html-webpack-plgins中設定chunks:['index']另一個設定 chunks:['album']

公共模組的提取

多入口打包會存在公共的模組
例如index.js中和album.js中公共的模組使用了global.css的模組

optimization:{
    splitChunks:{
        chunks:'all'
    }
}

MiniCssExtractPlugin提取css單位件

  • 安裝外掛 yarn add mini-css-extract-plugin
const MiniCssExtractPlugin = require('mini-css-extract-plugin')

//style-loader //將樣式是通過style標籤注入
MiniCssExtractPlugin.loader,//如果檔案小於150kb可以不用單獨提出來
css-loader //css 解析

壓縮樣式檔案

  • 安裝 yarn add optimize-css-assets-webpack-plugin --dev
const OptimizeCssAssetsWebpackPlugin = require('optimize-css-assets-webpack-plugin');

plugins:[
    new OptimizeCssAssetsWebpackPlugin(); //配資到plugins中 都可以使用
]

optimization:{
    minimizer:[
    new OptimizeCssAssetsWebpackPlugin();  //配置到這裡 在沒有開啟壓縮的時候外掛就不會工作
    //需要注意的是配置了minimizer 這個時候打包預設手動填寫,需要配置js打包壓縮不然js無法壓縮
    new TerserWebpackPlugin()  //需要安裝和引用
    ]
}

const TerserWebpackPlugin = ('terser-webpack-plugin')

輸出檔名Hash

在生產模式下,檔名使用hash

output:{
    //針對專案,有一個發生改變就會重新生成
    filename:'[name]-[hash].bundle.js'
    // 打包過程中同一類,改動哪裡那個類js.css都會改變
    filename:'[name]-[chunkhash].bundle.js'
    //解決快取最好的方法 每個檔案都有hashcontenthash:8 也可以指定位數 預設20
    filename:'[name]-[contenthash].bundle.js'
}

註釋

  • output 輸出的必須是絕對路徑

  • webpack載入資源的方式:

    • 遵循ES Modules標準的import宣告
    • 遵循CommonJS標準的require函式
    • 遵循AMD標準的define函式和require函式
    • 樣式程式碼中的@import指令和url函式
    • HTML程式碼中圖片標籤的src屬性
  • webpack本身只能打包js檔案,但對於其他資源例如css,圖片,或者其他的語法集比如jsx,是沒有辦法載入的,這就需要對應的loader將資源轉化,載入進來

  • TreeShaking在生產模式會自動開啟,他是一組功能搭配使用後的效果,起鬨usedExports負責標記(枯樹葉),sideEffects一般標記npm包標記是否有副作用

vs code摺疊快捷鍵 control+k control+0