# 多页网站 WebPack5 配置

# 准备工作

  • 基本安装

    mkdir MultiPageWeb
    cd MultiPageWeb
    npm init -y
    npm install webpack webpack-cli -S
    
    1
    2
    3
    4
  • 页面结构 (opens new window)放到 src 文件夹中

  • 目录结构

  • 配置 webpack.config.js

    module.exports = {
      entry: {},
      module: {},
      plugins: [],
      output: {
        clean: true,
      }
    }
    
    1
    2
    3
    4
    5
    6
    7
    8
  • 配置 package.json

    {
      "name": "melon",
      "version": "1.0.0",
      "description": "多页面网站Webpack配置",
    - "main": "index.js",
    + "private": true,
      "scripts": {
    -   "test": "echo \"Error: no test specified\" && exit 1",
    +   "dev": "webpack --mode=development",
    +   "build": "webpack --mode=production"
      },
      "author": "Melon",
      "license": "ISC",
    }
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14

# 项目使用 webpack 的优化

webpack 是一个模块打包器 将任意资源文件 转换、打包或包裹 用于浏览器中使用

# html 资源处理

  • 安装 htmlWebpackPlugin (opens new window) 识别 html 文件

  • + const htmlWebpackPlugin = require('html-webpack-plugin');
    
    module.exports = {
      entry: {},
      module: {},
      plugins: [
    +   new htmlWebpackPlugin({
    +     template: "./src/html/index.html",
    +     filename: "index.html",
    +   }),
    +   new htmlWebpackPlugin({
    +     template: "./src/html/login.html",
    +     filename: "login.html",
    +   }),
      ],
      output: {
        clean: true,
      }
    }
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
  • 运行 npm run devdist 文件夹会生成 html 文件

  • 安装 html-loader (opens new window) 可以识别 html 文件里面的资源

    module: {
    +   rules: [
    +     {
    +       test: /\.html$/i,
    +       loader: "html-loader",
    +     },
    +   ],
      }
    
    1
    2
    3
    4
    5
    6
    7
    8
  • 运行 npm run devdist 文件夹生成对应的静态资源

# SCSS 资源处理

  • 安装 sass-loader (opens new window)css-loader (opens new window) 处理样式文件

    • 页面也不再直接引入 css 样式文件
    • 改为从 webpack.config.js 中引入 scss 样式文件
    const htmlWebpackPlugin = require('html-webpack-plugin');
    
    module.exports = {
      entry: {
    +   index: ["./src/scss/index.scss"],
    +   login: ["./src/scss/login.scss"]
      },
      module: {
        rules: [
          {
            test: /\.html$/i,
            loader: "html-loader",
          },
    +     {
    +       test: /\.(sc|sa|c)ss$/i,
    +       use: [
    +         'css-loader',
    +         'sass-loader',
    +       ],
    +     },
        ],
      },
      plugins: [
        new htmlWebpackPlugin({
          template: "./src/html/index.html",
          filename: "index.html",
        }),
        new htmlWebpackPlugin({
          template: "./src/html/login.html",
          filename: "login.html",
        }),
      ],
      output: {
        clean: true,
      }
    }
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
  • 运行 npm run dev思考几个问题

    • dist 文件夹里面的 html 文件的 head 引入变化
    • 自动生成 css 样式文件 可是要怎么使用
  • 使用 style-loader (opens new window) 将生成的样式文件作用到 html 文件中

    • dist 文件夹里面的 css 样式文件合并到页面对应的 js 文件中
    {
      test: /\.(sc|sa|c)ss$/i,
      use: [
    +   'style-loader',
        'css-loader',
        'sass-loader',
      ],
    },
    
    1
    2
    3
    4
    5
    6
    7
    8
  • 使用 MiniCssExtractPlugin (opens new window) 会将 CSS 提取到单独的文件

    • dist 文件夹里面会生成对应的 css 样式文件
    • 生成的 html 会自动引入样式文件 ⚠️观察引入的个数
      const htmlWebpackPlugin = require('html-webpack-plugin');
    + const MiniCssExtractPlugin = require("mini-css-extract-plugin");
    
      module.exports = {
        entry: {
          index: ["./src/scss/index.scss"],
          login: ["./src/scss/login.scss"]
        },
        module: {
          rules: [
            {
              test: /\.html$/i,
              loader: "html-loader",
            },
            {
              test: /\.(sc|sa|c)ss$/i,
              use: [
    -           'style-loader',
    +           MiniCssExtractPlugin.loader,
                'css-loader',
                'sass-loader',
              ],
            },
          ],
        },
        plugins: [
          new htmlWebpackPlugin({
            template: "./src/html/index.html",
            filename: "index.html",
          }),
          new htmlWebpackPlugin({
            template: "./src/html/login.html",
            filename: "login.html",
          }),
    +     new MiniCssExtractPlugin()
        ],
        output: {
          clean: true,
        }
      }
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
  • 由于现在是多入口文件 导致生成的每一个页面都会包含入口文件的内容 所以需要 指定页面的对应资源

    plugins: [
        new HtmlWebpackPlugin({
          template: "./src/html/index.html",
          filename: "index.html",
    +     chunks: ['index']
        }),
        new HtmlWebpackPlugin({
          template: "./src/html/login.html",
          filename: "login.html",
    +     chunks: ['login']
        }),
        new MiniCssExtractPlugin()
      ],
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13

# JavaScript 资源处理

  • 页面的脚本文件也在入口文件统一引入 而不需要在页面直接引入

    entry: {
    -   index: ["./src/scss/index.scss"],
    +   index: ["./src/scss/index.scss", "./src/js/index.js"],
    -   login: ["./src/scss/login.scss"]
    +   login: ["./src/scss/login.scss", "./src/js/login.js"]
      },
    
    1
    2
    3
    4
    5
    6

# 图片资源处理

​ 使用 htmlWebpackPlugin 会自动将图片资源文件移动到 dist 文件夹下 但是并没有对于图片有任何的优化处理

  • webpack4 时会使用 url-loader (opens new window) 将小图片转 base64 从而减少网络请求

    • 图片尺寸小于 20480B = 20KB 时 会自动将图片转成 base64
    • scss 文件引入的图片则会生成二进制图片 无法直接读取
    module: {
        rules: [
          {
            test: /\.html$/i,
            loader: "html-loader",
          },
          {
            test: /\.(sc|sa|c)ss$/i,
            use: [
              MiniCssExtractPlugin.loader,
              'css-loader',
              'sass-loader',
            ],
          },
    +     {
    +       test: /\.(png|jpg|gif)$/,
    +       use: {
    +         loader: 'url-loader',
    +         options: {
    +           name: '[name].[ext]',
    +           limit: 20480,
    +           outputPath: 'images',
    +         },
    +       }
    +     }
        ],
      },
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
  • webpack5 时 新增 资源模块 (opens new window) 进行静态资源的处理

    • 自动根据图片尺寸使用不同 loader 进行处理
    module: {
        rules: [
          {
            test: /\.html$/i,
            loader: "html-loader",
          },
          {
            test: /\.(sc|sa|c)ss$/i,
            use: [
              MiniCssExtractPlugin.loader,
              'css-loader',
              'sass-loader',
            ],
          },
    +     {
    +       test: /\.(png|jpg|gif)$/,
    +       type: 'asset',
    +       parser: {
    +         dataUrlCondition: {
    +           maxSize: 20 * 1024
    +         }
    +       },
    +       generator: {
    +         filename: 'images/[name][ext]'
    +       }
    +     },
    -     {
    -       test: /\.(png|jpg|gif)$/,
    -       use: {
    -         loader: 'url-loader',
    -         options: {
    -           name: '[name].[ext]',
    -           limit: 20480,
    -           outputPath: 'images',
    -         },
    -       }
    -     }
        ],
      },
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
  • 图片无损压缩 (opens new window)

      const HtmlWebpackPlugin = require('html-webpack-plugin');
      const MiniCssExtractPlugin = require("mini-css-extract-plugin");
    + const ImageMinimizerPlugin = require("image-minimizer-webpack-plugin");
    
      plugins: [
          new HtmlWebpackPlugin({
            template: "./src/html/index.html",
            filename: "index.html",
            chunks: ['index']
          }),
          new HtmlWebpackPlugin({
            template: "./src/html/login.html",
            filename: "login.html",
            chunks: ['login']
          }),
          new MiniCssExtractPlugin(),
    +     new ImageMinimizerPlugin({
    +       minimizerOptions: {
    +         plugins: [
    +           ["jpegtran", { progressive: true }],
    +           ["optipng", { optimizationLevel: 5 }],
    +         ],
    +       },
    +     })
        ]
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    • 运行 npm run build 观察压缩结果

    • ⚠️ 安装上面两个包时一定要用 国外 npm 不要用淘宝源或者 cnpm 源 否则会出现 下面的错误

    • 安装后 运行 npm audit fix 修复包的依赖才可以正常使用

  • 图片有损压缩 (opens new window)

    plugins: [
        new HtmlWebpackPlugin({
          template: "./src/html/index.html",
          filename: "index.html",
          chunks: ['index']
        }),
        new HtmlWebpackPlugin({
          template: "./src/html/login.html",
          filename: "login.html",
          chunks: ['login']
        }),
        new MiniCssExtractPlugin(),
    -   new ImageMinimizerPlugin({
    -     minimizerOptions: {
    -       plugins: [
    -         ["jpegtran", { progressive: true }],
    -         ["optipng", { optimizationLevel: 5 }],
    -       ],
    -     },
    -   }),
    + 	new ImageMinimizerPlugin({
    +     minimizerOptions: {
    +       plugins: [
    +         ["mozjpeg", { quality: 75 }],
    +         ["pngquant"]
    +       ],
    +     },
    +   }),
      ],
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    • 运行 npm run build 观察压缩结果

    • ⚠️ 安装上面两个包时一定要用 国外 npm 不要用淘宝源或者 cnpm 源 否则会出现 下面的错误

    • 安装后 运行 npm audit fix 修复包的依赖才可以正常使用

  • 图片换成 webp 格式

    • 使用 imagemin-webp (opens new window) 自动将 png 格式图片生成为 webp 格式
    • srcindex.html 引入 f2.png
    • 小尺寸png图片要转成 base64 不需要转 webp 格式
    plugins: [
        new HtmlWebpackPlugin({
          template: "./src/html/index.html",
          filename: "index.html",
          chunks: ['index']
        }),
        new HtmlWebpackPlugin({
          template: "./src/html/login.html",
          filename: "login.html",
          chunks: ['login']
        }),
        new MiniCssExtractPlugin(),
    -   new ImageMinimizerPlugin({
    -     minimizerOptions: {
    -       plugins: [
    -         ["mozjpeg", { quality: 75 }],
    -         ["pngquant"]
    -       ],
    -     },
    -   }),
    +   new ImageMinimizerPlugin({
    +     test: /\.(jpe?g)$/i,
    +     minimizerOptions: {
    +       plugins: [["mozjpeg", { quality: 75 }]],
    +     },
    +   }),
    +   new ImageMinimizerPlugin({
    +     test: /\.(png)$/i,
    +     minimizerOptions: {
    +       plugins: ["pngquant"],
    +     },
    +   }),
    +   new ImageMinimizerPlugin({
    +     test: /\.(png)$/i,
    +     deleteOriginalAssets: false,
    +     filename: "images/[hash].webp",
    +     filter: source => {
    +       return source.byteLength > 20480
    +     },
    +     minimizerOptions: {
    +       plugins: ["imagemin-webp"],
    +     },
    +   })
      ]
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    • 运行 npm run build 观察转化结果

  • 将图片上传到 CDN

    • 购买腾讯云的对象存储 (opens new window)

    • 使用 melon-cos-plugin (opens new window) 将图片上传到腾讯云

    • 自动将 htmlcss 里面引入的图片 png 类型变为 webp 类型

        const HtmlWebpackPlugin = require('html-webpack-plugin');
        const MiniCssExtractPlugin = require("mini-css-extract-plugin");
        const ImageMinimizerPlugin = require("image-minimizer-webpack-plugin");
      + const MelonCosPlugin = require("melon-cos-plugin");
      + const projectName = "multipageweb";
      + const dirName = new Date().toLocaleDateString();
      
        module.exports = {
          plugins: [
            new HtmlWebpackPlugin({
              template: "./src/html/index.html",
              filename: "index.html",
              chunks: ['index']
            }),
            new HtmlWebpackPlugin({
              template: "./src/html/login.html",
              filename: "login.html",
              chunks: ['login']
            }),
            new MiniCssExtractPlugin(),
            new ImageMinimizerPlugin({
              test: /\.(jpe?g)$/i,
              minimizerOptions: {
                plugins: [["mozjpeg", { quality: 75 }]],
              },
            }),
            new ImageMinimizerPlugin({
              test: /\.(png)$/i,
              minimizerOptions: {
                plugins: ["pngquant"],
              },
            }),
            new ImageMinimizerPlugin({
              test: /\.(png)$/i,
              deleteOriginalAssets: false,
              filename: "images/[hash].webp",
              filter: source => {
                return source.byteLength > 20480
              },
              minimizerOptions: {
                plugins: ["imagemin-webp"],
              },
            }),
      +     new MelonCosPlugin({
      +       projectName,
      +       dirName,
      +       SecretId: 'COS账号',
      +       SecretKey: 'COS密码',
      +       Bucket: '存储桶ID',
      +       Region: '存储桶地区'
      +     })
          ],
          output: {
            clean: true,
      +     publicPath: `https://cos.0melon0.cn/${projectName}/${dirName}/`
          }
        }
      
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      23
      24
      25
      26
      27
      28
      29
      30
      31
      32
      33
      34
      35
      36
      37
      38
      39
      40
      41
      42
      43
      44
      45
      46
      47
      48
      49
      50
      51
      52
      53
      54
      55
      56
      57

# 完整版配置文件(区分环境开发)

  • 项目环境配置 package.json

    {
      "name": "multi-page-web-webpack",
      "version": "1.0.0",
      "description": "",
      "private": true,
      "scripts": {
        "dev": "webpack --config=webpack.dev.js",
        "start": "webpack serve --open --config=webpack.dev.js",
        "build": "webpack --name=melon --config=webpack.prod.js"
      },
      "keywords": [],
      "author": "",
      "license": "ISC",
      "devDependencies": {
        "cos-nodejs-sdk-v5": "^2.10.5",
        "css-loader": "^6.4.0",
        "fontmin-webpack": "^3.1.0",
        "html-loader": "^2.1.2",
        "html-webpack-plugin": "^5.4.0",
        "image-minimizer-webpack-plugin": "^2.2.0",
        "imagemin-mozjpeg": "^9.0.0",
        "imagemin-pngquant": "^9.0.2",
        "imagemin-webp": "^6.0.0",
        "melon-cos-plugin": "^1.0.4",
        "melon-min-font-plugin": "^1.0.5",
        "mini-css-extract-plugin": "^2.4.2",
        "sass": "^1.43.2",
        "sass-loader": "^12.2.0",
        "style-loader": "^3.3.0",
        "webpack": "^5.58.2",
        "webpack-cli": "^4.9.1",
        "webpack-dev-server": "^4.3.1",
        "webpack-sources": "^3.2.1"
      }
    }
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
  • 页面配置文件 webpack.config.js

    const HtmlWebpackPlugin = require('html-webpack-plugin');
    
    module.exports = {
      entry: {
        index: ['./src/scss/index.scss', './src/js/index.js'],
        login: ['./src/scss/login.scss', './src/js/login.js']
      },
      plugins: [
        new HtmlWebpackPlugin({
          template: "./src/html/index.html",
          filename: "index.html",
          chunks: ["index"]
        }),
        new HtmlWebpackPlugin({
          template: "./src/html/login.html",
          filename: "login.html",
          chunks: ["login"]
        }),
      ]
    }
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
  • 开发环境配置 webpack.dev.js

    const BaseConfig = require("./webpack.config");
    
    module.exports = {
      entry: BaseConfig.entry,
      module: {
        rules: [
          {
            test: /\.html$/i,
            loader: "html-loader",
          },
          {
            test: /\.(sc|sa|c)ss$/i,
            use: [
              "style-loader",
              'css-loader',
              'sass-loader',
            ],
          },
          {
            test: /\.(png|jpg|gif)$/,
            type: 'asset',
            parser: {
              dataUrlCondition: {
                maxSize: 20 * 1024
              }
            },
            generator: {
              filename: 'images/[name][ext]'
            }
          },
        ],
      },
      plugins: BaseConfig.plugins,
      devServer: {
        static: './dist',
        watchFiles: ['./src/**/*'],
        proxy: {
          context: "/api",
          changeOrigin: true,
        },
      },
      devtool: 'inline-source-map',
      mode: "development",
      output: {
        clean: true,
      },
      stats: {
        children: true,
        errorDetails: true
      }
    }
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
  • 上线配置 webpack.prod.js

    const BaseConfig = require("./webpack.config");
    const MiniCssExtractPlugin = require("mini-css-extract-plugin");
    const ImageMinimizerPlugin = require("image-minimizer-webpack-plugin");
    const MelonCosPlugin = require("melon-cos-plugin");
    const MelonMinFontPlugin = require("melon-min-font-plugin");
    
    module.exports = (env, argv) => {
      try {
        let projectName = argv.name || 'temp';
        let dirName = new Date().toLocaleDateString().replace(/\//g, "");
        return {
          entry: BaseConfig.entry,
          module: {
            rules: [
              {
                test: /\.html$/i,
                loader: "html-loader",
              },
              {
                test: /\.(sc|sa|c)ss$/i,
                use: [
                  MiniCssExtractPlugin.loader,
                  'css-loader',
                  'sass-loader',
                ]
              },
              {
                test: /\.(woff|woff2|eot|ttf|otf)$/i,
                type: 'asset/resource',
                generator: {
                  filename: 'fonts/[name][ext]'
                }
              },
              {
                test: /\.(png|jpg|gif)$/,
                type: 'asset',
                parser: {
                  dataUrlCondition: {
                    maxSize: 20 * 1024
                  }
                },
                generator: {
                  filename: 'images/[name][ext]'
                }
              },
            ],
          },
          plugins: [
            ...BaseConfig.plugins,
            new MiniCssExtractPlugin(),
            new ImageMinimizerPlugin({
              test: /\.(jpe?g)$/i,
              minimizerOptions: {
                plugins: [["mozjpeg", { quality: 75 }]],
              },
            }),
            new ImageMinimizerPlugin({
              test: /\.(png)$/i,
              minimizerOptions: {
                plugins: ["pngquant"],
              },
            }),
            new ImageMinimizerPlugin({
              test: /\.(png)$/i,
              filename: "images/[name].webp",
              filter: source => {
                return source.byteLength > 20480
              },
              minimizerOptions: {
                plugins: ["imagemin-webp"],
              },
            }),
            new MelonMinFontPlugin({
              exclude: ['iconfont.ttf']
            }),
            new MelonCosPlugin({
              projectName,
              dirName,
              SecretId: '账号',
              SecretKey: '密钥',
              Bucket: '存储桶',
              Region: '地区'
            })
          ],
          mode: "production",
          output: {
            clean: true,
            publicPath: `https://cos域名/${projectName}/${dirName}/`
          }
        }
      } catch (error) {
        console.log("全局try捕获");
        console.log(error);
      }
    }
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    91
    92
    93
    94
    95

# 疑惑点

  • 图片上传到 COS 后 前端页面可以判断是否支持 Webp 格式 但是 不支持的情况下如何请求回原来的图片格式呢?

    (function (doc) {
      if (!sessionStorage.getItem(isWebp)) {
        let image = new Image();
        image.onload = function () {
          if (image.width == 1) {
            sessionStorage.setItem(isWebp, true);
          } else {
            // 不支持Webp格式时的处理
          }
        };
        image.src = 'data:image/webp;base64,UklGRkoAAABXRUJQVlA4WAoAAAAQAAAAAAAAAAAAQUxQSAwAAAARBxAR/Q9ERP8DAABWUDggGAAAABQBAJ0BKgEAAQAAAP4AAA3AAP7mtQAAAA==';
      }
    }(document));
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
  • 使用 image-minimizer-webpack-plugin 可以将 htmlscss 里面引入的图片压缩、转换 Webp 格式 可是 在不同文件中引入同一张图片时 会重复处理图片资源导致 Multiple assets emit different content to the same filename 错误

  • 因为 HTTP 的限制 最多支持六个并发请求 我们现在除了页面外 其他一切资源上传到 COS 所以如何设置多域名来解决域名并发上限