天天看點

萬字長文解密webpack-基本使用/進階用法/性能優化 你想要的全都有!!!前言1. 基本使用2 進階特性3 性能優化

創作不易 拒絕白嫖 點個贊呗

文章目錄

本質上,webpack 是一個用于現代 JavaScript 應用程式的 靜态子產品打包工具。當 webpack 處理應用程式時,它會在内部從一個或多個入口點建構一個 依賴圖(dependency graph),然後将你項目中所需的每一個子產品組合成一個或多個 bundles,它們均為靜态資源,用于展示你的内容。

主要概念

  1. 入口(entry)
  2. 輸出(output)
  3. 插件(plugin)
  4. 模式(mode)

npm init -y

yarn add -D webpack webpack-cli

yarn add -D webpack-merge

  • build\webpack.dev.js
  • build\webpack.prod.js

公共配置
const path = require('path')

module.exports ={
    entry:path.join(__dirname, '..', 'src/index')
}      

開發時
const webpackCommon = require('./webpack.common.js')
const { merge } = require('webpack-merge')

module.exports = merge(webpackCommon, {
    mode: 'development'
})      

打包時
const path = require('path')
const webpack = require('webpack')
const webpackCommon = require('./webpack.common.js')
const { merge } = require('webpack-merge')
const { CleanWebpackPlugin } = require('clean-webpack-plugin')

module.exports = merge(webpackCommon, {
    mode: 'production',
    output: {
        filename: '[name].[contenthash:8].js',
        path: path.join(__dirname, '..', 'dist'),
    },
    plugins: [new CleanWebpackPlugin()]
})      

"scripts": {
   // 本地服務
    "dev": "webpack-dev-server --config build/webpack.dev.js",
    "build": "webpack --config build/webpack.prod.js"
  },      

建立src/index

執行 yarn build

yarn add -D webpack-dev-server

devServer: {
        port: 8080,
        progress: true,  // 顯示打包的進度條
        contentBase:path.join(__dirname, '..', 'dist'),  // 根目錄
        open: true,  // 自動打開浏覽器
        compress: true,  // 啟動 gzip 壓縮

        // 設定代理
        proxy: {
            
        }
    }      

yarn dev

yarn add -D html-webpack-plugin

plugins:[
        new HtmlWebpackPlugin({
            template: path.join(__dirname, '..', 'src/index.html'),
            filename: 'index.html'
        })
    ]      

開發環境,會在記憶體中生成一個html檔案

打包環境,會在dist下生成一個html檔案

html檔案會自動引入main.js

yarn add -D babel-loader

yarn add -D @babel/core

yarn add -D @babel/preset-env

{
    "presets": ["@babel/preset-env"],
    "plugins": []
}      

module: {
        rules: [
            {
                test: /\.js$/,
                // loader: 'babel-loader', //loader 是單個加載器,use是加載器數組
                use: [
                    'babel-loader',
                ],
                include: path.join(__dirname, '..', 'src'),
                exclude: /node_modules/
            }
        ]
    },      

css插入到頁面的style标簽

yarn add -D style-loader

yarn add -D css-loader

自動添加字首

yarn add -D postcss-loader autoprefixer

{
    test: /\.css$/,
    // loader 的執行順序是:從後往前
    use: ['style-loader', 'css-loader', 'postcss-loader'] 
},      

postcss.config.js

module.exports = {
    plugins: [require('autoprefixer')]
}      

js檔案中通過 import imgSrc from ‘./photo’; img.src = imgSrc引入

在css檔案中作為背景圖引入

在html檔案中直接引入

yarn add -D file-loader

yarn add -D url-loader

build-base-conf\webpack.dev.js

// 直接引入圖檔 url
            {
                test: /\.(png|jpg|jpeg|gif)$/,
                use: 'file-loader'
            }      

build-base-conf\webpack.prod.js

// 圖檔 - 考慮 base64 編碼的情況
{
  test: /\.(png|jpg|jpeg|gif)$/,
    use: {
      loader: 'url-loader',
        options: {
          // 小于 5kb 的圖檔用 base64 格式産出
          // 否則,依然延用 file-loader 的形式,産出 url 格式
          limit: 5 * 1024,

            // 打包到 img 目錄下
            outputPath: '/img1/',

              // 設定圖檔的 cdn 位址(也可以統一在外面的 output 中設定,那将作用于所有靜态資源)
              // publicPath: 'http://cdn.abc.com'
        }
        }
    },      

entry: {
        index: path.join(srcPath, 'index.js'),
        other: path.join(srcPath, 'other.js')
    },      
這裡的index與other就是chunk名稱

output: {
        filename: 'bundle.[contentHash:8].js',  // 打包代碼時,加上 hash 戳
        path: distPath,
        // publicPath: 'http://cdn.abc.com'  // 修改所有靜态檔案 url 的字首(如 cdn 域名),這裡暫時用不到
    },      

注意這裡隻需要在prod裡面加入配置,

dev不需要

new HtmlWebpackPlugin({
            template: path.join(__dirname, '..', 'src/index.html'),
            filename: 'index.html',
            // chunks 表示該頁面要引用哪些 chunk (即上面的 index 和 other),預設全部引用
            chunks: ['index']  // 隻引用 index.js
        }),
        // 多入口 - 生成 other.html
        new HtmlWebpackPlugin({
            template: path.join(__dirname, '..', 'src/other.html'),
            filename: 'other.html',
            chunks: ['other']  // 隻引用 other.js
        })      

yarn add -D mini-css-extract-plugin

// 抽離 css
{
  test: /\.css$/,
    use: [
      MiniCssExtractPlugin.loader,  // 注意,這裡不再用 style-loader
      'css-loader',
      'postcss-loader'
    ]
},      

new MiniCssExtractPlugin({
        filename: 'css/main.[contenthash:8].css'
    })      

yarn add optimize-css-assets-webpack-plugin

build-min-extract-css\webpack.prod.js

optimization: {
        // 壓縮 css
        minimizer: [ new OptimizeCSSAssetsPlugin({})],
    }      

抽離公共代碼

我們在開發多個頁面的項目的時候,有時候會在幾個頁面中引用某些公共的子產品,這些公共子產品多次被下載下傳會造成資源浪費,如果把這些公共子產品抽離出來隻需下載下傳一次之後便緩存起來了,這樣就可以避免因重複下載下傳而浪費資源,那麼怎麼在webpack中抽離出公共部分呢?方法如下:

舉例:

項目中分别有a.js, b.js, page1.js, page2.js這四個JS檔案,

page1.js 和 page2.js中同時都引用了a.js, b.js,

這時候想把a.js, b.js抽離出來合并成一個公共的js,然後在page1, page2中自動引入這個公共的js,

splitChunks: {
   cacheGroups: {
     //公用子產品抽離
     common: {
       chunks: 'initial',
         minSize: 0, //大于0個位元組
           minChunks: 2 //抽離公共代碼時,這個代碼塊最小被引用的次數
     }
   }
 }      

頁面中有時會引入第三方子產品,比如import $ from ‘jquery’; page1中需要引用,page2中也需要引用,這時候就可以用vendor把jquery抽離出來,方法如下:

optimization: {
        // 分割代碼塊
        splitChunks: {
            chunks: 'all',
            /**
             * initial 入口 chunk,對于異步導入的檔案不處理
                async 異步 chunk,隻對異步導入的檔案處理
                all 全部 chunk
             */

            // 緩存分組
            cacheGroups: {
                // 第三方子產品
                vendor: {
                    name: 'vendor', // chunk 名稱
                    priority: 1, // 權限更高,優先抽離,重要!!!
                    test: /node_modules/,
                    minSize: 0,  // 大小限制
                    minChunks: 1  // 最少複用過幾次
                },

                // 公共的子產品
                common: {
                    name: 'common', // chunk 名稱
                    priority: 0, // 優先級
                    minSize: 0,  // 公共子產品的大小限制
                    minChunks: 2  // 公共子產品最少複用過幾次
                }
            }
        }

    }      

注意:這裡需要配置權重 priority,因為抽離的時候會執行第一個common配置,入口處看到jquery也被公用了就一起抽離了,不會再執行wendor的配置了,是以加了權重之後會先抽離第三方子產品,然後再抽離公共common的,這樣就實作了第三方和公用的都被抽離了。

Loader處理檔案的轉換操作是很耗時的,是以需要讓盡可能少的檔案被Loader處理

{
    test: /\.js$/,
    use: [
        'babel-loader?cacheDirectory',//開啟轉換結果緩存
    ],
    include: path.resolve(__dirname, 'src'),//隻對src目錄中檔案采用babel-loader
    exclude: path.resolve(__dirname,' ./node_modules'),//排除node_modules目錄下的檔案
},      

yarn add -D happypack

Plugins

new HappyPack({
            // 用唯一ID來代表目前HappyPack是用來處理一類特定檔案的,與rules中的use對應
            id: "babel",
            loaders: ["babel-loader?cacheDirectory"], //預設設定loader處理
            threads: 5, //使用共享池處理
        }),
        new HappyPack({
            id: 'styles',
            loaders: ['css-loader', 'postcss-loader'],
            threads: 5, //代表開啟幾個子程序去處理這一類型的檔案
            verbose: true //是否允許輸出日子
        }),      

rules

{
                test: /\.(js|jsx)$/,
                use: [MiniCssExtractPlugin.loader, "HappyPack/loader?id=babel"],
                exclude: path.resolve(__dirname, " ./node_modules"),
            },
            {
                test: /\.css$/,
                use: 'happypack/loader?id=styles',
                include: path.join(__dirname, '..', 'src')

            },      

webpack預設提供了UglifyJS插件來壓縮JS代碼,但是它使用的是單線程壓縮代碼,也就是說多個js檔案需要被壓縮,它需要一個個檔案進行壓縮。

是以說在正式環境打包壓縮代碼速度非常慢(因為壓縮JS代碼需要先把代碼解析成用Object抽象表示的AST文法樹,再去應用各種規則分析和處理AST,導緻這個過程耗時非常大)。

yarn add -D webpack-parallel-uglify-plugin

new ParallelUglifyPlugin({
      // 傳遞給 UglifyJS的參數如下:
      uglifyJS: {
        output: {
          /*
           是否輸出可讀性較強的代碼,即會保留白格和制表符,預設為輸出,為了達到更好的壓縮效果,
           可以設定為false
          */
          beautify: false,
          /*
           是否保留代碼中的注釋,預設為保留,為了達到更好的壓縮效果,可以設定為false
          */
          comments: false
        },
        compress: {

          /*
           是否删除代碼中所有的console語句,預設為不删除,開啟後,會删除所有的console語句
          */
          drop_console: true,

          /*
           是否内嵌雖然已經定義了,但是隻用到一次的變量,比如将 var x = 1; y = x, 轉換成 y = 5, 預設為不
           轉換,為了達到更好的壓縮效果,可以設定為false
          */
          collapse_vars: true,

          /*
           是否提取出現了多次但是沒有定義成變量去引用的靜态值,比如将 x = 'xxx'; y = 'xxx'  轉換成
           var a = 'xxxx'; x = a; y = a; 預設為不轉換,為了達到更好的壓縮效果,可以設定為false
          */
          reduce_vars: true
        }
      }
    }
 )      

借助自動化的手段,在監聽到本地源碼檔案發生變化時,自動重新建構出可運作的代碼後再控制浏覽器重新整理。Webpack将這些功能都内置了,并且提供了多種方案供我們選擇。      

module.export = {
  watch: true,
  watchOptions: {
    // 不監聽的檔案或檔案夾
    ignored: /node_modules/,
    // 監聽到變化發生後會等300ms再去執行動作,防止檔案更新太快導緻重新編譯頻率太高  
    aggregateTimeout: 300,  
    // 判斷檔案是否發生變化是通過不停的去詢問系統指定檔案有沒有變化實作的
    poll: 1000
  }
}      

  • 在 Webpack 中監聽一個檔案發生變化的原理是定時(可在watchOptions.poll中設定)的去擷取這個檔案的最後編輯時間,每次都存下最新的最後編輯時間,如果發現目前擷取的和最後一次儲存的最後編輯時間不一緻,就認為該檔案發生了變化
  • 當發現某個檔案發生了變化時,并不會立刻告訴監聽者,而是先緩存起來,收集一段時間(可在watchOptions.aggregateTimeout中設定)的變化後,再一次性告訴監聽者。防止在編輯代碼的過程中可能會高頻的輸入文字導緻檔案變化的事件高頻的發生

_ _DevServer 還支援一種叫做子產品熱替換( Hot Module Replacement )的技術可在不重新整理整個網頁的情況下做到超靈敏實時預覽。原理是在一個源碼發生變化時,隻需重新編譯發生變化的子產品,再用新輸出的子產品替換掉浏覽器中對應的老子產品 。子產品熱替換技術在很大程度上提升了開發效率和體驗 。

項目中子產品熱替換的配置:

const HotModuleReplacementPlugin = require('webpack/lib/HotModuleReplacementPlugin');

 devServer:{
        host:'localhost',
        port:'8080',
        open:true//自動拉起浏覽器
        // 隻設定hot隻有cssHMR生效
        hot:true,//熱加載
        //hotOnly:true
    },
    plugins:[
    //熱更新插件
        new webpack.HotModuleReplacementPlugin()
    ]

HotModuleReplacementPlugin生成一個mainifest(一個json結構描述了發生變化的modules清單)
和update file(一個js檔案包含修改後的代碼内容)      

JS檔案中

if (module.hot) {
    module.hot.accept('./print.js', function() { //告訴 webpack 接受熱替換的子產品
        console.log('Accepting the updated printMe module!');
        printMe();
    })
}
// 對Js生效       

]

// config/webpack.common.js
const { BundleAnalyzerPlugin } = require('webpack-bundle-analyzer');

const commonConfig = {
  // ...
  plugins: [
    new BundleAnalyzerPlugin({
      analyzerPort: 8889, // 指定端口号
      openAnalyzer: false,
    }),
  ]
  // ...
}      

// config/webpack.common.js
const SpeedMeasurePlugin = require("speed-measure-webpack-plugin");
const smp = new SpeedMeasurePlugin();
// ...
module.exports = (production) => {
  if (production) {
    const endProdConfig = merge(commonConfig, prodConfig);
    return smp.wrap(endProdConfig);
  } else {
    const endDevConfig = merge(commonConfig, devConfig);
    return smp.wrap(endDevConfig);
  }
};      

2020-10-10 webpack 5.0.0 釋出了,但這并不意味着它已經完成了,沒有 bug,甚至功能完整。 就像 webpack 4

一樣,我們通過修複問題以及增加新特性來延續開發。 在接下來的日子裡,可能會有很多 bug 修複。新特性可能也會出現。

嘗試用持久性緩存來提高建構性能。 嘗試用更好的算法和預設值來改進長期緩存。 嘗試用更好的 Tree Shaking 和代碼生成來改善包大小。

嘗試改善與網絡平台的相容性。 嘗試在不引入任何破壞性變化的情況下,清理那些在實作 v4 功能時處于奇怪狀态的内部結構。

試圖通過現在引入突破性的變化來為未來的功能做準備,使其能夠盡可能長時間地保持在 v5 版本上。 遷移指南

繼續閱讀