簡析 webpack 的打包優化

前言:關於前端優化始終是沒法跟構建工具相分離,下面由我從平常的前端的視角從文件的提取、合併、拆分等幾個方面來講一下 webpack 是如何進行打包配置的。
寫在前面:

前端是怎麼作到性能優化的? 帶着這個問題 咱們從幾個方面出發:javascript

  • 減小 HTTP 請求
  • 靜態資源使用 CDN
  • 將 CSS 放在文件頭部,JavaScript 文件放在底部
  • 使用字體圖標 iconfont 代替圖片圖標
  • 善用緩存,不重複加載相同的資源
  • 使用服務端渲染
  • ...

詳情能夠看:前端性能優化 24 條建議;大佬總結的很細緻,可是若是從 webpack 出發的話 咱們對以上的建議能夠作到幾條?css

答:均可以html


項目準備

以 react 爲例前端

webpack: 4.31.0
webpack-cli : 3.3.2

解釋一下 爲何用的是 webpack4x,目前組內大部分都是用的 webpack4 進行構建,後面也會單獨出一份關於 webpack5 的教程。java

目錄結構

image.png

如上圖所示,當前的項目放置一些靜態的文件便可。node

package.json

項目開始前的準備插件react

{
  "name": "webpack-demo",
  "version": "1.0.0",
  "description": "",
  "main": "index.js",
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1",
    "dev": "webpack-dev-server --open --config webpack.dev.js", // 本地運動
    "build": "webpack --config webpack.prod.js"  // 打包優化
  },
  "keywords": [],
  "author": "",
  "license": "ISC",
  "devDependencies": {
    "@babel/core": "^7.13.10",
    "@babel/preset-env": "^7.13.10",
    "@babel/preset-react": "^7.13.13",
    "babel-loader": "^8.2.2",
    "html-webpack-plugin": "^3.2.0",
    "react": "^17.0.2",
    "react-dom": "^17.0.2",
    "webpack": "^4.31.0",
    "webpack-cli": "^3.3.2"
  },
  "dependencies": {
    "webpack-dev-server": "^3.11.2"
  }
}
.babelrc
{
  "presets": ["@babel/preset-env", "@babel/preset-react"]
}

減小 http 請求

合併圖片資源webpack

npm i url-loader -D
webpack.prod.js
module: {
    rules: [
      ...
      {
        test: /.(png|jpg|gif|jpeg)$/,
        use: [
          {
            loader: 'url-loader',
            options: {
              limit: 102400, // 100k
            },
          },
        ],
      },
    ],
  },

場景:對於項目中有幾個的小圖標可使用 url-loader 進行優化處理,它默認會把文件轉爲 DataURL,若是文件小於 limit,那麼 url-loader 會調用file-loader進行處理。當前,圖標過多推薦使用iconfont進行加載。git

參考: url-loadergithub


減小文件的搜索範圍

webpack.prod.js
module: {
    rules: [
      ...
      {
        test: /.js$/,
        use: [
          {
            loader: 'babel-loader',
            exclude: /node_modules/,  // 不須要被解析的模塊
           // include: path.resolve('src')  // 不須要被解析的模塊
          },
        ],
      },
    ],
  },

靜態資源使用 CDN

配置publicPath

webpack.prod.js
output: {
    path: path.join(__dirname, 'dist'),
    filename: '[name].js', // 多個入口的狀況下 不知道對應的名稱、能夠用佔位符來指定[name]
    publicPath: 'https://cdn.example.com/assets/', // 配置CDN地址
  },
mode: 'production', // 生產環境

將 CSS 放在文件頭部,JavaScript 文件放在底部

配置H5的rem適配方案爲例:

npm i px2rem-loader raw-loader@0.5.1 lib-flexible  -D

新建 src/meta.html 添加公共的 mate 標籤

<!-- 公共的 meta信息-->
<meta charset="UTF-8" />
<meta name="viewport"
  content="width=device-width,initial-scale=1,maximum-scale=1, minimum-scale=1,user-scalable=no,viewport-fit=cover" />
<meta http-equiv="X-UA-Compatible" content="ie=edge" />

src/meta.html 配置:

<!DOCTYPE html>
<html lang="en">

<head>
  ${require('raw-loader!./meta.html')}
  <title>Document</title>
  <script>${ require('raw-loader!babel-loader!../node_modules/lib-flexible/flexible.js') }</script>
</head>

<body>
  <div id="root"></div>
</body>

</html>

場景:使用raw-loader能夠動態的配置模板的佔位符,達到渲染的位置。須要注意的是raw-loader@0.5.1比較穩定,其餘版本有些許問題。

參考: raw-loader


抽離資源文件

npm i html-webpack-externals-plugin -D
webpack.prod.js
const HtmlWebpackExternalsPlugin = require('html-webpack-externals-plugin');
...
plugins:[
     new HtmlWebpackExternalsPlugin({
      // 提取公共資源
      externals: [
        {
          module: 'react',
          entry: 'https://unpkg.com/react@16/umd/react.production.min.js',
          global: 'React',  // 全局注入
        },
        {
          module: 'react-dom',
          entry: 'https://unpkg.com/react-dom@16/umd/react-dom.production.min.js',
          global: 'ReactDOM', // 全局注入
        },
        {
          module: 'google-roboto',
          entry: {
            path: '//at.alicdn.com/t/font_460072_qm96unh8hja.css',
            type: 'css',
          },
        },
      ],
      files: ['index.html']
    })
]

優化結果:

優化前:
image.png

優化後:
image.png

場景:對於項目中存在不少的插件或者 UI 組件庫等、能夠在 HTML 插入外鏈的 CDN 方式進行打包優化,若是有加載順序渲染的限制也可使用raw-loader進行打包設置。

參考: html-webpack-externals-plugin


分離公共方法

使用 webpack 內置的SplitChunksPlugin,它的強大之處是能夠在項目中分離公共方法的引入次數。

例子: 在 src/ 建立common/index.js

export function common() {
  return '我是公共的JavaScript';
}

分別在:src/index/index.jssrc/search/index.js

import { common } from '../common';
...

let result = common();
console.log(result);
webpack.prod.js
plugins: [
    new HtmlWebpackPlugin({
      template: path.join(__dirname, 'src/index.html'),
      chunks: ['commons', 'index'],  // 引入當前的名稱 commons
    }),
  ],
...
 optimization: {
    splitChunks: {
      minSize: 0, // 引入的模塊的大小,設置爲0 有引入就會打包成模塊
      cacheGroups: {
        commons: {
          minChunks: 1, // 最少引入的次數
          name: 'commons',  // 命名chunks_name
          chunks: 'all',
        },
      },
    },
  },

場景: 能夠根據項目引入的次數進行公共方法 chunk 的抽離,不用在每次文件構建中反覆構建。

參考: SplitChunksPlugin


善用緩存構建

yarn add  hard-source-webpack-plugin -D
// or
npm i  hard-source-webpack-plugin -D
webpack.prod.js
...
plugins: [
     new HardSourceWebpackPlugin(),
  ],

第一次構建:

image.png

第二次構建:
image.png

場景:當項目在本地構建的時候須要的依賴較多,能夠增長爲模塊提供中間緩存的方式進行構建,構建的速度能夠達到80%左右。 詳細的配置也能夠參考文檔

參考: hard-source-webpack-plugin


文件指紋

yarn add mini-css-extract-plugin -D
// or
npm i  mini-css-extract-plugin -D
Hash :和整個項目的構建相關,只要項目文件有修改,整個項目構建的`hash`值就會隨之更改
Chunkhash:和`webpack`打包的`chunk`有關,不一樣的`entry`會申城不一樣的`chunkhash`值
Contenthash : 根據文件內容來定義`hash`,文件內容不變,則`contenthash`不變
webpack.prod.js
"use strict";

const path = require("path");
const MiniCssExtractplugin = require("mini-css-extract-plugin"); // 提取css單獨一個文件

module.exports = {
  entry: {
    // 入口文件能夠用對象的形式來寫
    index: "./src/index.js",
    search: "./src/search.js",
  },
  output: {
    path: path.join(__dirname, "dist"),
    filename: "[name]_[chunkhash:8].js", // chunkhash 8位的長度
  },
  mode: "production",
  module: {
    rules: [
      {
        test: /.js$/,
        use: "babel-loader",
        exclude: /node_modules/,
      },
      {
        test: /.css$/, // 配置css的後綴名
        exclude: /node_modules/,
        use: [MiniCssExtractplugin.loader, "css-loader"], //tips:執行的順序是右到左的
      },
      {
        test: /.less$/, // 配置less的後綴名
        exclude: /node_modules/,
        use: [MiniCssExtractplugin.loader, "css-loader", "less-loader"], //tips:執行的順序是右到左的
      },
      {
        test: /.(png|jpg|gif|jpeg)$/,
        exclude: /node_modules/,
        use: [
          {
            loader: "file-loader",
            options: {
              name: "[name]_[hash:8].[ext]",
            },
          },
        ],
      },
      {
        test: /.(woff|woff2|eot|ttf|otf)$/,
        exclude: /node_modules/,
        use: [
          {
            loader: "file-loader",
            options: {
              name: "[name]_[hash:8].[ext]",
            },
          },
        ],
      },
    ],
  },
  plugins: [
    new MiniCssExtractplugin({
      filename: "[name]_[contenthash:8].css",
    }),
  ],
};

場景:

用做版本管理時,若是一個項目須要發佈,只須要發佈修改過的文件指紋;對於沒有修改過的文件,用戶在訪問的時候,依舊可使用瀏覽器緩存好的,無需二次加載,加速頁面訪問。

參考:

MiniCssExtractPlugin


代碼壓縮

  1. uglifyjs-webpack-plugin // js 壓縮 [內置]
  2. optimize-css-assets-webpack-plugin // css 壓縮
  3. html-webpack-plugin // html 壓縮
yarn add html-webpack-plugin optimize-css-assets-webpack-plugin cssnano -D
// or
npm i html-webpack-plugin optimize-css-assets-webpack-plugin cssnano  -D
webpack.prod.js
const OptimizeCssAssetsWebpackPlugin = require('optimize-css-assets-webpack-plugin');
const HtmlWebpackPlugin = require('html-webpack-plugin');

plugins:[
    ...

    new OptimizeCssAssetsWebpackPlugin({
      assetNameRegExp: /.css$/g, // 匹配的正則的名稱後綴、跟loader配置一致
      cssProcessor: require('cssnano'), // 用於最小化的css處理器,默認是cssnano
    }),
    new HtmlWebpackPlugin({
      template: path.join(__dirname, 'src/search.html'),
      filename: 'search.html',
      chunks: ['search'],
      /**
       * inject : true || 'head' || 'body' || false
       * body : 全部javascript資源將被放置在body元素的底部。
       * head : 把腳本放置在head元素中.
       * true : script標籤位於html文件的 body 底部 [默認]
       * false: 不插入生成的 js 文件,只是單純的生成一個 html 文件
       *  */
      inject: true,
      minify: {
        collapseWhitespace: true, // 清理html中的空格、換行符。 默認值:false
        minifyCSS: true, // 壓縮html內的樣式。默認值:false
        minifyJS: true, // 壓縮html內的js。 默認值:false
        removeComments: false, // 清理html中的註釋。 默認值:false
      },
    }),
]

參考:

optimize-css-assets-webpack-plugin
cssnano
html-webpack-plugin

Tips:_關於詳細的html-webpack-pluginminify能夠詳細的參考_


寫在最後

在目前的前端的性能優化中,構建工具必不可少,怎麼作才能使當前的項目更快、性能更好是前端業界的一個老生暢談的問題、只有熟練的掌握構建工具的配置才能在性能渲染獨領風騷。

參考文獻

https://segmentfault.com/a/11...
https://segmentfault.com/a/11...
https://segmentfault.com/a/11...
https://juejin.cn/post/684490...

其餘

插播一條招聘信息,LeapFE 招聘前端工程師

若是你對 用戶體驗、交互操做流程及用戶需求 "有一些" 追求若是你對 web 、小程序 、Electron 技術 "有一些" 認識若是你 很擅長前端新技術的學習和分享 👏 歡迎加入好將來,歡迎加入 LeapFE 一塊兒作一些有意思的事情

image.png

相關文章
相關標籤/搜索