动态

详情 返回 返回

【webpack系列】從基礎配置到掌握進階用法 - 动态 详情

前言

本篇文章將介紹一些webpack的進階用法,演示內容繼承自上一篇文章的內容,所以沒看過上一篇文章的建議先學習上一篇內容再閲讀此篇內容,會更有利於此篇的學習~

文件指紋

文件指紋指的是打包輸出的文件名後綴,一般用來做版本管理、緩存等

w1.png

webpack的指紋策略有三種:hashchunkhashcontenthash,它們之間最主要的區別就是每種hash影響的範圍不同。

佔位符

webpack提供佔位符用於將特定信息附加在打包輸出的文件上
名稱 含義
[ext] 資源後綴名
[id] 文件標識符
[name] 文件名稱
[path] 文件的相對路徑
[folder] 文件所在的文件夾
[hash] 模塊標識符的 hash,默認是 md5 生成
[chunkhash] chunk 內容的 hash,默認是 md5 生成
[contenthash] 文件內容的 hash,默認是 md5 生成
[query] 文件的 query,例如,文件名 ? 後面的字符串
[emoji] 一個隨機的指代文件內容的 emoji

我們可以使用特定的語法,對 hashchunkhashcontenthash 進行切片:[chunkhash:4],像 8c4cbfdb91ff93f3f3c5 這樣的哈希會最後會變為 8c4c

hash

與整個項目的構建有關,只要項目內文件有修改,整個項目構建的hash值就會改變

我們使用多入口打包來體驗一下:

// webpack.config.js
module.exports = {
  entry: { 
        main: './src/main.js',
        index: './src/index.js'
    },
    output: {
        filename: '[name].[hash:6].js',
        path: __dirname + '/dist',
        clean: true
    },
  // ...
}

此時我們使用了佔位符來設置文件指紋[name].[hash:6].js代表的是文件名+6位hash

此時我們執行npm run build,看打包出來的內容如下:

w2.png

此時兩個js文件的hash都是207495

我們修改一下index.js的內容,再打包一次

w3.png

我們會發現此時兩個js文件的hash都變成了9f0e2d

chunkhash

chunkhash 是和 webpack 打包的模塊相關,每一個 entry 作為一個模塊,會產生不同的 Chunkhash 值,文件改變時只會影響當前chunk組的hash值

我們再來看看chunkhash

// webpack.config.js
module.exports = {
  entry: { 
        main: './src/main.js',
        index: './src/index.js'
    },
    output: {
        filename: '[name].[chunkhash:6].js',
        path: __dirname + '/dist',
        clean: true
    },
  // ...
}

還是延用上面的例子,這次我們只修改main.js文件內容

修改前兩個文件的hash值如下:

w4.png

修改後:

w5.png

此時只有main.js的打包產物的hash發生了變化

contenthash

contenthash 是和根據文件內容相關,單個文件發生變化,只會引起此文件的hash值

這裏我們使用miniCssExtractPlugin將CSS內容提取成文件,併為它設置contenthash

// webpack.config.js
const miniCssExtractPlugin = require('mini-css-extract-plugin')
module.exports = {
  entry: { 
        main: './src/main.js',
        index: './src/index.js'
    },
    output: {
        filename: '[name].[contenthash:6].js',
        path: __dirname + '/dist',
        clean: true
    },
  mudole: {
    rules: [
      {
        test: /\.css$/,
        use: [miniCssExtractPlugin.loader, 'css-loader']
      },
      // ...
    ]
  },
  plugins: [
    // ...
    new miniCssExtractPlugin({
            filename: 'css/[name].[contenthash:6].css'
        }),
  ]
  // ...
}

然後打包看一下此時的hash:

w6.png

我們修改index.css內容再打包一次

w7.png

此時只有index.css的打包產物hash值發生了變化。

根據不同的文件類型一般選擇不同的文件指紋策略,通常情況下:

  • JS文件採用[chunkhash]文件指紋策略
  • CSS文件採用[contenthash]文件指紋策略
  • 圖片資源採用[hash]文件指紋策略

代碼壓縮

壓縮JS

目前最成熟的JavaScript代碼壓縮工具是UglifyJS,它能夠分析JavaScript語法樹,理解代碼含義,從而能做到諸如去掉無效代碼、去掉日誌輸出代碼、縮短變量名等優化。但很遺憾的是UglifyJS不再維護,並且它不支持 ES6 + 。

現在推薦使用的是Terser,它在 UglifyJS 基礎上增加了 ES6 語法支持,並重構代碼解析、壓縮算法,使得執行效率與壓縮率都有較大提升,並且Webpack5.0 後默認使用 Terser 作為 JavaScript 代碼壓縮器

簡單實用:

// webpack.config.js
module.exports = {
  //...
  optimization: {
    minimize: true
  }
}

需要注意的是在生產模式中構建時,Terser壓縮是默認開啓的

當然它也允許你通過提供一個或多個定製過的TerserPlugin實例,覆蓋默認的壓縮工具,實現更精細的壓縮功能

// webpack.config.js
const TerserPlugin = require("terser-webpack-plugin");
module.exports = {
  //...
  optimization: {
    minimize: true,
    minimizer: [
      new TerserPlugin({
        parallel: true,
        terserOptions: {
          // https://github.com/webpack-contrib/terser-webpack-plugin#terseroptions
        },
      })
    ]
  }
}

在Webpack4中 默認使用 uglifyjs-webpack-plugin壓縮代碼,也可以通過 minimizer 數組替換為 Terser 插件

壓縮CSS

CSS代碼同樣也可以使用webpack來進行壓縮,比較常見的CSS壓縮工具有:cssnanocss-minimizer-webpack-plugin

對於 webpack5 或更高版本,官方推薦使用 CssMinimizerWebpackPlugin,該插件是使用 cssnano 優化和壓縮 CSS,支持緩存和併發模式下運行。

安裝:

npm i css-minimizer-webpack-plugin

配置:

// webpack.config.js

const CssMinimizerPlugin = require("css-minimizer-webpack-plugin"); // 用壓縮css
const MiniCssExtractPlugin = require("mini-css-extract-plugin");  // 用來提取css成單獨的文件

module.exports = {
  //...
  module: {
    rules: [
      {
        test: /.css$/,
        // 注意,MiniCssExtractPlugin.loader 與 style-loader不能同時使用
        use: [MiniCssExtractPlugin.loader, "css-loader"],
      },
    ],
  },
  optimization: {
    minimize: true,
    minimizer: [
      // Webpack5 之後,約定使用 '...' 字面量保留默認 minimizer 配置
      "...",
      new CssMinimizerPlugin(),
    ],
  },
  plugins: [new MiniCssExtractPlugin()],
};

⚠️這裏需要注意的是需要使用 mini-css-extract-plugin 將 CSS 代碼抽取為單獨的 CSS 產物文件,這樣才能命中 css-minimizer-webpack-plugin 默認的 test 邏輯。

壓縮HTML

我們之前使用的html-webpack-plugin,它除了可以生成html模版,也可以用來對html進行壓縮。

htmlWebpackPlugin常見參數

  • template:模板的路徑,默認會去尋找 src/index.ejs 是否存在。
  • filename:輸出文件的名稱,默認為 index.html
  • inject:是否將資源注入到模版中,默認為 true
  • minify:壓縮參數。在生產模式下(production),默認為 true;否則,默認為false
// webpack.config.js

module.exports = {
  // ...
  plugins: [
    // ...
    new HtmlWebpackPlugin({
            template: './public/index.html',
            filename: 'index.html',
            minify: true
        }),
  ]
}

生成的 HTML 將使用 html-minifier-terser 和以下選項進行壓縮,所以它實際上的壓縮功能其實是html-minifier-terser來實現的,更多配置可以查看這個工具文檔

{
  collapseWhitespace: true,
  keepClosingSlash: true,
  removeComments: true,
  removeRedundantAttributes: true,
  removeScriptTypeAttributes: true,
  removeStyleLinkTypeAttributes: true,
  useShortDoctype: true
}

禁止生成LICENSE文件

經過上面這些配置後,我發現了一個奇怪的問題,那就是每個bundle產物都多了一個同名的LICENSE.txt文件,打開一看裏面都是一些註釋內容。

w-license.png

為什麼會生成這些文件,帶着疑惑我去翻了下官方文檔,Webpack5 默認壓縮代碼工具為terser-webpack-plugin,那就先從它入手吧。

在它的配置中找到了extractComments參數,默認值為true,表示將註釋剝離到單獨的文件中。

w-ext.png

如果我們不想要,直接關掉該配置就行了

module.exports = {
  // ...
  optimization: {
        minimize: true,
        minimizer: [
            new cssMinimizerPlugin(),
            new terserPlugin({
                extractComments: false,  // 關閉註釋剝離功能
            }),
            '...'
        ]
    },
}

CSS增強(autoprefixer)

前端最頭疼的問題莫過於處理兼容性,因為前端的運行環境並不固定,可以在各種瀏覽器以及各種webview中運行,並且每個瀏覽器廠商對CSS的寫法也各不相同,這就勢必會導致出現一些問題。

比如為了兼容各種瀏覽器內核,圓角屬性應該這樣寫:

.container {
  -moz-border-radius: 16px;
  -webkit-border-radius: 16px;
  -o-border-radius: 16px;
  border-radius: 16px;
}

試想一下如果在開發中需要你這樣寫,那是不是太不合理了?

我們一般都會通過webpack配置插件來幫我們解決這個問題,處理CSS我們首先會想到postcss,沒錯webpack也有使用postcss處理CSS的loader --- postcss-loader,然後我們還需要使用postcss的插件autoprefixer來幫我們自動添加瀏覽器前綴。

安裝:

npm i postcss postcss-loader autoprefixer

修改配置:

// webpack.config.js
module.exports = {
  // ...
  module: {
    rules: [
      //...
      {
        test: /\.css$/,
        use: [miniCssExtractPlugin.loader, 
              'css-loader', 
              {
                loader: 'postcss-loader',
                options: {
                  postcssOptions: {
                    plugins: ['autoprefixer']
                  }

                }
              }]
      },
    ]
  }
  //...
}

⚠️這裏需要注意的是,如果你想自定義轉換的規則,最好是將 autoprefixer 的 browsers選項替換為 browserslist 配置。在 package.json 或。Browserslistrc 文件。使用 browsers選項可能導致錯誤,並且browserslist 配置可以用於 babel、 autoprefixer、 postcss-norize 等工具。

比如package.json中配置browserslist:

// package.json
{
  //...
  "browserslist": [
    "last 10 Chrome versions",
    "last 5 Firefox versions",
    "Safari >= 6", 
    "ie> 8"
  ] 
}

此時我們打包的CSS的產物就會自動添加瀏覽器前綴

w8.png

靜態資源拷貝

假如我們需要在html中引用一些不需要打包處理的資源,比如下面這種情況

w9.png

index.html中引入了一些日誌的工具函數,這時候我們直接跑起來會發現這個文件直接404了,這是怎麼回事?

首先我們寫的路徑肯定是沒問題的,問題在於我們打包後這個utils文件肯定是不在這個位置了,所以會報404

w10.png

所以這裏我們需要使用copy-webpack-plugin將文件拷貝至dist目錄下

// webpack.config.js
const copyWebpackPlugin = require('copy-webpack-plugin')
module.exports = {
  // ...
  plugins: [
    new copyWebpackPlugin({
      patterns: [
        {from: 'module', to: __dirname + '/dist/module/'}
      ]
    }),
  ]
}

此時再打包,我們會發現dist目錄下已經有了module/utils.js,並且頁面也不會再報404了

w11.png

sourcemap

SourceMap 就是一個信息文件,裏面儲存着代碼的位置信息。這種文件主要用於開發調試,現在代碼都會經過壓縮混淆,這樣報錯提示會很難定位代碼。通過 SourceMap 能快速定位到源代碼,並進行調試。

比如我們沒有開啓sourcemap,然後開發過程中報錯了,它的報錯信息是這樣的:

w12.gif

定位過去是打包後的內容,這樣的話對我們排查報錯非常不方便。

當我們開啓sourcemap,再來看看這個同樣的報錯是怎樣的:

// webpack.config.js
module.exports = {
  // ...
  devtool: 'eval-cheap-module-source-map',
}

w13.gif

此時的報錯指向就非常清晰了~

關鍵字

devtool的值有20多種,並且都是由以下七種關鍵字的一個或多個組成

  • eval 關鍵字

devtool 值包含 eval 時,生成的模塊代碼會被包裹進一段 eval 函數中,且模塊的 Sourcemap 信息通過 //# sourceURL 直接掛載在模塊代碼內

  • source-map 關鍵字

devtool 包含 source-map 時,Webpack 才會生成 Sourcemap 內容

  • cheap 關鍵字

devtool 包含 cheap 時,生成的 Sourcemap 內容會拋棄維度的信息,這就意味着瀏覽器只能映射到代碼行維度

  • module 關鍵字

module 關鍵字只在 cheap 場景下生效,例如 cheap-module-source-mapeval-cheap-module-source-map。當 devtool 包含 cheap 時,Webpack 根據 module 關鍵字判斷按 loader 聯調處理結果作為 source,還是按處理之前的代碼作為 source

  • nosources 關鍵字

devtool 包含 nosources 時,生成的 Sourcemap 內容中不包含源碼內容 —— 即 sourcesContent 字段

  • inline 關鍵字

devtool 包含 inline 時,Webpack 會將 Sourcemap 內容編碼為 Base64 DataURL,直接追加到產物文件中

  • hidden 關鍵字

通常,產物中必須攜帶 //# sourceMappingURL= 指令,瀏覽器才能正確找到 Sourcemap 文件,當 devtool 包含 hidden 時,編譯產物中不包含 //# sourceMappingURL= 指令

devtool的值以及各自的功能可以在webpack文檔上查看

如何選擇

  • 對於開發環境,適合使用:

    • eval:速度極快,但只能看到原始文件結構,看不到打包前的代碼內容;
    • cheap-eval-source-map:速度比較快,可以看到打包前的代碼內容,但看不到 loader 處理之前的源碼;
    • cheap-module-eval-source-map:速度比較快,可以看到 loader 處理之前的源碼,不過定位不到列級別;
    • eval-source-map:初次編譯較慢,但定位精度最高;
  • 對於生產環境,則適合使用:

    • source-map:信息最完整,但安全性最低,外部用户可輕易獲取到壓縮、混淆之前的源碼,慎重使用;
    • hidden-source-map:信息較完整,安全性較低,外部用户獲取到 .map 文件地址時依然可以拿到源碼,慎重使用;
    • nosources-source-map:源碼信息缺失,但安全性較高,需要配合 Sentry 等工具實現完整的 Sourcemap 映射。

解決跨域

在開發過程中,我們勢必會遇到跨域問題,對於本地開發我們一般可以通過配置代理來解決

我們先來簡單寫一個接口:

const express = require('express')
const app = express()
app.get('/api/getInfo', (req, res) => {
    res.json({
        code: 200,
        data: {
            name: 'nanjiu',
            age: 18
        }
    })
})

app.listen(3000, () => {
    console.log('服務已啓動~')
})

然後把服務跑起來,再到vue項目中去調用

const getInfo = async () => {
    try {
        const res = await axios.get('http://localhost:3000/api/getInfo')
        console.log(res)
    } catch(err) {
        console.log(err)
    }
}

這時候你會發現接口調用跨域了

w14.png

配置代理

接着我們再來通過webpack配置代理解決跨域問題,由於我們本地使用了webpack-dev-server,所以我們可以直接通過它來配置

// webpack.config.js
module.exports = {
  // ...
  devServer: {
    hot: true,
    open: true,
    proxy: {
      '/api': 'http://localhost:3000'
    }
  }
}

這個時候我們的接口請求就正常了

w15.png

由於篇幅問題,這篇文章就介紹到這裏了,後面會接着更新webpack更多高級用法。

如果這篇文章有幫助到你,❤️關注+點贊❤️鼓勵一下作者,文章公眾號首發,關注 前端南玖 第一時間獲取最新文章~

user avatar congjunhua 头像 wanglinfeng 头像 morimanong 头像 mi2nagemao 头像 tonyyoung 头像 minnanitkong 头像 hyfhao 头像 fsjohnhuang 头像 asmallwhitecat 头像 beiniaonanyou 头像 tianzhich 头像 shenyongweiwudemaozi 头像
点赞 14 用户, 点赞了这篇动态!
点赞

Add a new 评论

Some HTML is okay.