解锁webpack:对html、css、js及图片资源的抽离打包处理

面试被问到webpack,可别只知道说 HtmlWebpackPlugin 了哇。

前期准备

安装依赖

bash 复制代码
npm init -y
npm install webpack webpack-cli --save-dev

配置打包命令

bash 复制代码
// package.json
{
    "scripts": {
        // ... 其他配置信息
        "build": "webpack --mode production"
     }
}

基本配置

entry

通过entry指定项目打包时的入口文件,可以配置单入口或者多入口

  • 单入口文件
javascript 复制代码
module.exports = {
  entry: './path/to/my/entry/file.js',
};
  • 多入口文件
javascript 复制代码
module.exports = {
  entry: {
    index: './src/index.js',
    info: './src/information.js',
  },
};

output

通过output配置项目打包后的文件名以及文件的输出路径(位置)

  • filename:此选项决定了每个输出 bundle 的名称
    • 什么是bundle?

      根据入口文件,最后打包生成的、可以直接让浏览器解析的JS、CSS文件。

    • JS属于入口文件,那CSS为什么也可看作bundle?

      • 不对CSS做抽离,那CSS会被内联在JS中(必然算);
      • 对CSS做抽离,但CSS也是通过JS去做处理的(算做样式bundle);
    • html算不算?

      HTML文件从严格意义上讲不算做bundle,因为不包含直接打包的代码或资源;

      只是对打包好的资源做加载或展示;

    • CODE

      javascript 复制代码
      module.exports = {
        output: {
          // 为什么这样命名?方便应用浏览器的缓存机制
          filename: 'js/[name].[hash:8].js',
        }
      }

webpack官方:output->filename

webpack官方:缓存(输出哈希文件名)

  • path:打包时会将 bundle 写入到output.path选项指定的目录下
    • 官方给出:对应一个绝对路径,如何配置它的值?

      绝对路径不做解释,懂得都懂,不懂得自查;

      在node下提供了一个全局变量__dirname,表示的就是当前执行的脚本文件所在的绝对路径;

    • CODE

      javascript 复制代码
      module.exports = {
        output: {
          filename: 'js/[name].[hash:8].js',
          // 将打包后的资源输出到当前目录下的 dist 目录中
          path: path.resolve(__dirname, 'dist')
        }
      }
  • assetModuleFilename:与output.filename相同,不过应用于Asset Modules(字体or图标)

    • 支持的占位符:[name],[file],[query],[ext],[hash][path]
      • name:原始文件名
      • file:完整文件名(带拓展名)
      • query:URL查询参数部分------图的大中小到底请求哪个?(x.png?size=large),得到的是问号及后面的部分;
      • ext:文件拓展名
      • hash:根据文件内容生成的哈希值|区别与chunkhash和contenthash,是整个项目构建过程的哈希值,现在V5中叫fullhash。
      • path:文件的相对路径。
    • 同内容但更新了文件名?涉及到文件名及内容,避免延迟更新。
    • 注意:
      • 该配置项是全局的,全局优先级低于局部的;
      • 局部指的是后面通过asset系列方式对资源文件做解析时配置的generato.filename。
    • CODE
    javascript 复制代码
    module.exports = {
      output: {
        filename: 'js/[name].[hash:8].js',
        path: path.resolve(__dirname, 'dist'),
        // 将资源文件输出到 assets 目录下以hash码来命名
        assetModuleFilename: 'assets/[hash][ext]',
      }
    }
  • clean:控制是否在生成文件之前清空打包输出目录

    • 布尔值true(清空)、false(不清空)
    • 或以对象形式配置,使用keep来设置保留某个目录下的文件
    • CODE
    javascript 复制代码
    module.exports = {
      output: {
        filename: 'js/[name].[hash:8].js',
        path: path.resolve(__dirname, 'dist'),
        assetModuleFilename: 'assets/[hash][ext]',
        clean:true
      }
    }

optimization

优化:比如对代码进行压缩、分割代码、去除未使用的导出模块、去除空chunk等

  • minimize:告知 webpack 使用TerserPlugin或其它在optimization.minimizer定义的插件压缩 bundle;值为布尔类型。

    • CODE
    javascript 复制代码
    module.exports = {
      //...
      optimization: {
        minimize: true,
      },
    };
  • minimizer:允许开发者通过提供一个或多个定制过的TerserPlugin实例,覆盖默认的代码压缩工具,通过去除文件的换行、空格、注释等字符来缩小文件体积。
    • teser-webpack-plugin

      bash 复制代码
      npm install terser-webpack-plugin --save-dev
      javascript 复制代码
      const TerserPlugin = require('terser-webpack-plugin');
      
      module.exports = {
        optimization: {
          minimize: true,
          // 为啥是数组?你就用一个吗?接受的类型就是数组类型
          minimizer: [new TerserPlugin()],
        },
      };
    • css-minimizer-webpack-plugin

      • 该插件使用cssnano来压缩CSS(cssnano是一个第三方库)
      • webpack贡献,不过也需要安装使用
      • 注意:webpack只能识别处理Js文件,对CSS的压缩处理记得配置好它的加载器(下面说这个东西)。
      • webpack官方:css-minimizer-webpack-plugin
      • CODE
      bash 复制代码
      npm install css-minimizer-webpack-plugin --save-dev
      ## 安装加载器
      npm install mini-css-extract-plugin --save-dev
      javascript 复制代码
      const MiniCssExtractPlugin = require('mini-css-extract-plugin');
      const CssMinimizerPlugin = require('css-minimizer-webpack-plugin');
      
      module.exports = {
        // 当前的主角配置
        optimization: {
          minimize: true,
          minimizer: [
            new CssMinimizerPlugin(),
          ],
        },
        module: {
          rules: [
            {
              test: /.s?css$/,
              use: [MiniCssExtractPlugin.loader, 'css-loader'],
            },
          ],
        },
        plugins: [new MiniCssExtractPlugin()],
      };

加载器Loader

加载器在webpack这里有一个统称,叫loader,用于辅助webpack来加载非JS类型的文件,需要注意使用顺序。

webpack默认情况下只能处理Js文件,如果要处理其他文件,前提得让webpack能够加载其他文件,比如图片、CSS文件等。

配置在**module>rules** 中,rules是一个对象数组,对于某类文件的解析及配置写在对象中,其中test指定某类文件,use指定所需的加载器们。

style-loader

  • style-loader:将 CSS 代码以<style>标签的形式插入到 HTML 文档的<head>部分。

    • 当 Webpack 处理 CSS 文件时,通常和css-loader一起使用,css-loader会把 CSS 文件转换为 JavaScript 模块,style-loader则会提取这个模块中的 CSS 代码,并动态地创建<style>标签将其插入到 HTML 页面中。
    • CODE
    javascript 复制代码
    module.exports = {
      module: {
        rules: [
          {
            test: /\.css$/i,
            use: ['style-loader'],
          },
        ],
      },
    };

css-loader

  • css-loader:解析CSS文件,css-loader会对@importurl()进行处理,就像 js 解析import/require()一样

    • 启用/禁用url/image-set函数进行处理。 如果设置为falsecss-loader将不会解析url或者image-set中的任何路径。
    • webpack官方:css-loader
    • CODE
    javascript 复制代码
    module.exports = {
      module: {
        rules: [
          {
            test: /\.css$/i,
            use: ['style-loader',
              {
                 loader:"css-loader",
                 options:{
                    url: true // 解析 CSS 中的 url()------处理背景图时需要
                 }
              }
            ],
          },
        ],
      },
    };

MiniCssExtractPlugin.loader

  • MiniCssExtractPlugin.loader:会将 CSS 代码提取出来,然后由MiniCssExtractPlugin插件将这些 CSS 代码合并到一个或多个独立的 CSS 文件中,最后在 HTML 文件中通过<link>标签引入这些独立的 CSS 文件。

    • 区别于style-loader:
      • 将CSS抽离成单独的文件,而不是以style形式插入html;
      • 将抽离出来的css文件以link方式引入到html中。
    • CODE
    javascript 复制代码
    const MiniCssExtractPlugin = require('mini-css-extract-plugin');
    const CssMinimizerPlugin = require('css-minimizer-webpack-plugin');
    
    module.exports = {
      optimization: {
        minimize: true,
        minimizer: [
          new CssMinimizerPlugin(),
        ],
      },
      module: {
        rules: [
          // 当前的主角配置
          {
            test: /.s?css$/,
            use: [MiniCssExtractPlugin.loader, 'css-loader'],
          },
        ],
      },
        // 对应插件
        plugins: [new MiniCssExtractPlugin()],
    };

postcss-loader

  • postcss-loader:让 Webpack 能够使用 PostCSS 对 CSS 文件进行处理。
    • 注意:

      • postcss-loader和postcss不一样,一个是loader一个是插件工具(下面讲)。
      • 使用postcss-loader还要下载postcss,要么铺的路没人走。
      • webpack官方:postcss-loader
      bash 复制代码
      npm install --save-dev postcss-loader postcss

html-loader

  • html-loader:解析HTML文件

    • 将HTML导出为字符串,还可以对html字符串进行压缩处理
    • CODE
    javascript 复制代码
    module.exports = {
      module: {
        rules: [
          {
            test: /\.html$/i,
            loader: 'html-loader',
            options: {
              // 开启压缩
              minimize: true,
              // 处理 HTML 中的资源引用
              sources: true, 
            },
          },
        ],
      },
    };

html-withimg-loader

  • html-withimg-loader:基于html-loader进行拓展,解决一些问题。
    • 可以对图片资源的输出进行配置,base64或是图片文件;
    • 可处理通过include引入的子页面,子页面中的图片资源也会处理。

asset module

资源模块(asset module)是一种模块类型,它允许使用资源文件(字体,图标等)而无需配置额外 loader。

在 webpack 5 之前,通常使用:

资源模块类型(asset module type),通过添加 4 种新的模块类型,来替换所有这些 loader:

  • asset/resource

    • 将资源文件发送到输出目录,并导出 URL(在代码中使用该资源时就会使用这个URL路径)。
    • 之前通过使用file-loader实现。
    • 对于较大的文件:图片或字体文件等,需要单独打包到输出目录,方便浏览器缓存管理。
    • CODE
    javascript 复制代码
    module.exports = {
        // ...其他配置~
        module: {
            rules: [
                {
                  test: /\.(png|jpeg|jpg|gif)$/i,
                  // 关键配置:仅匹配背景图路径(示例:src/css/imgs/ 目录)
                  include: path.resolve(__dirname, 'src/css/imgs'),
                  type: 'asset/resource',
                  generator: {
                    filename: 'bg-images/[name].[contenthash][ext]'
                  }
                },
                {
                  test: /\.(png|jpeg|jpg|gif)$/i,
                  // 关键配置:排除背景图路径,匹配其他图片(示例:src/assets/images/ 目录)
                  exclude: path.resolve(__dirname, 'src/css/imgs'),
                  type: 'asset/resource',
                  generator: {
                    // 为什么这里没有用哈希编码命名?因为资源名称涉及网络动态设定。
                    filename: 'images/[name][ext]'
                  }
                }
            ]
        }
    };
  • asset/inline

    • 导出一个资源的 data URI,也就是会将资源转换为Base64编码的数据。
    • 之前通过使用url-loader实现。
    • 将文件内容直接做编码化处理,避免额外的http请求。
  • asset/source

    • 导出资源的源代码,也就是会将资源的内容以字符串的形式引入到JS模块中。
    • 之前通过使用raw-loader实现。
    • 譬如针对txt、md等文件。
  • asset

    • 在导出一个 data URI 和发送一个单独的文件之间自动选择。之前通过使用url-loader,并且配置资源体积限制实现。
    • 默认情况下:小于8KB会使用asset/inline,否则使用asset/resource;可以使用parser.dataUrlCondition来更改临界值。
    • 资源模块 | webpack 中文文档
    • CODE
    javascript 复制代码
    module.exports = {
        // ...其他配置
        module: {
            rules: [
                {
                    test: /\.(png|jpg|jpeg|gif)$/i,
                    type: 'asset',
                    parser: {
                        dataUrlCondition: {
                            maxSize: 4 * 1024 // 调整阈值为 4KB
                        }
                    },
                    generator: {
                        filename: 'images/[name].[contenthash][ext]'
                    }
                }
            ]
        }
    };

其他

插件

html-webpack-plugin

html-webpack-plugin:根据html模板文件来将打包后的资源自动注入进该模版文件中,最后生成HTML文件。

  • 自动化注入资源 :在 Webpack 打包过程中,生成的 JavaScript 和 CSS 文件的文件名可能会包含哈希值,手动在 HTML 文件中更新这些引用会很繁琐。html-webpack-plugin可以自动完成这个任务,确保 HTML 文件引用的是最新的打包资源,以script或link引入相关资源。
  • 支持多页面应用:可以通过多次实例化该插件,为多页面应用的每个页面生成对应的 HTML 文件,并且为每个页面注入相应的资源。
  • 自定义模板:允许使用自定义的 HTML 模板,开发者可以在模板中使用 EJS 语法嵌入变量和逻辑,灵活控制生成的 HTML 内容。
  • chunks指定:多页面应用会有多个入口文件,每个页面可能需要不同的代码块,chunks可以为每个页面精确指定要引入哪些代码块。为什么一个html有引入多个css,但最后webpack知道在哪个html中关联哪几个css资源,哪些css属于html指定的chunks,那这些css最后就会link进html。
  • HtmlWebpackPlugin | webpack 中文文档
  • CODE
bash 复制代码
npm i html-webpack-plugin --save-dev
javascript 复制代码
const HtmlWebpackPlugin = require('html-webpack-plugin');

module.exports = {
    entry: {
        index: './src/js/index.js',
        detail: './src/js/info.js',
    },
    // ...其他配置
    plugins: [
        new HtmlWebpackPlugin({
            template: './public/index.html',
            filename: 'index.html',
            chunks: ['index'] // 只注入 index 代码块,也是因为这里的设置才让对应的css能够引入到html中,因为css属于chunk,chunk关联html
        }),
        new HtmlWebpackPlugin({
            template: './public/detail.html',
            filename: 'detail.html',
            chunks: ['detail'] // 只注入 detail 代码块
        })
    ]
};

mini-css-extract-plugin

MiniCssExtractPlugin:为每个包含 CSS 的 JS 文件创建一个 CSS 文件,并将CSS代码抽离到单独的文件中,而不会让CSS嵌入在Js中。

  • 官方建议同css-loader一起使用,哈哈要不然怎么处理css呢,净是没用的大实话
  • CODE
javascript 复制代码
const MiniCssExtractPlugin = require("mini-css-extract-plugin");

module.exports = {
  plugins: [
    new MiniCssExtractPlugin({filename: 'css/[name].[hash:8].css'})
  ],
  module: {
    rules: [
      {
        test: /\.css$/i,
        use: [MiniCssExtractPlugin.loader, "css-loader"],
      },
    ],
  },
};

css-minimizer-webpack-plugin

CssMinimizerPlugin:对每个CSS文件进行压缩处理。

  • 默认情况下只在生产环境下开启CSS压缩优化,如果想要在开发环境下也启用CSS优化,需要将optimization.minimize设置为true
  • CODE
javascript 复制代码
const MiniCssExtractPlugin = require('mini-css-extract-plugin');
const CssMinimizerPlugin = require('css-minimizer-webpack-plugin');

module.exports = {
  module: {
    rules: [
      {
        test: /.s?css$/,
        use: [MiniCssExtractPlugin.loader, 'css-loader'],
      },
    ],
  },
  optimization: {
    minimizer: [
      new CssMinimizerPlugin(),
    ],
  },
  plugins: [new MiniCssExtractPlugin()],
};

postcss

postcss :一个CSS处理引擎,需要结合postcss-loader来使用在webpack中,有autoprefixer、cssnano、postcss-preset-env等常用插件。

  • autoprefixer

    • 为解决浏览器兼容问题,为css相关样式添加前缀。

    • 需要安装postcss-loader、postcss、autoprefixer

      Autoprefixer CSS online

    • CODE

      bash 复制代码
      npm i postcss-loader postcss autoprefixer -D
      javascript 复制代码
      const MiniCssExtractPlugin = require("mini-css-extract-plugin");
      module.exports = {
        module: {
          rules: [
            {
              test: /\.css$/i,
              use: [
                MiniCssExtractPlugin.loader,
                'css-loader',
                'postcss-loader'
              ],
            },
          ],
        },
      };
  • 注意:

    • 添加前缀也是根据指定的浏览器列表来添加的,而浏览器列表正是package.json中的browserslist;
    • 创建`postcss.config.js`文件,内部引入autoprefixer,或在webpack中直接设置对应的配置项;
    javascript 复制代码
    // package.json
    {
      // 其他配置
      "browserslist": [
        "last 2 versions", 
        "ie >= 8", 
        "Chrome > 31", 
        "> 1%"
      ]
    }
    javascript 复制代码
    // postcss.config.js
    module.exports = {
      plugins: [require('autoprefixer')]
    }

terser-webpack-plugin

TerserWebpackPlugin:虽然由社区成员维护的第三方包,基于terser来对Javascript进行压缩处理,在webpack中开箱即用,安装配置即可。

  • CODE
javascript 复制代码
const TerserPlugin = require('terser-webpack-plugin')
module.exports = {
  optimization: {
    minimize: true,
    minimizer: [new TerserPlugin()]
  }
}

补充问题

批量导入图片

javascript 复制代码
// 自动导入 ../images 目录下所有扩展名为 .png、.jpg、.jpeg 或 .gif 的图片文件
require.context('../images', false, /\.(png|jpe?g|gif)$/)
// 由于这些图片可能在多个html中都会用到,所以可以创建一个入口文件写入该代码,配置在entry中,这样webpack就可以知道对这些图片做模块打包了。
  • '../images' :这是第一个参数,表示要搜索的目录路径。在这个例子中,Webpack 会从当前文件所在目录的上一级的images目录开始搜索符合条件的文件。
  • false :第二个参数是一个布尔值,用于指定是否要递归搜索子目录。false表示不进行递归搜索,即只在../images目录下查找文件,而不会深入到其子目录中。如果设置为true,则会递归搜索该目录下的所有子目录。
  • /.(png|jpe?g|gif)$/ :第三个参数是一个正则表达式,用于筛选符合条件的文件。这个正则表达式表示只匹配扩展名为.png.jpg.jpeg.gif的文件。

样式正常,报错找不到CSS

由于使用了html-webpack-plugin,可以自动对css和js文件通过link或script插入到html模板中,所以检查是否模板并非模版而是原始的html文件;或者html模板中有引入相对路径下的源码文件。

html 复制代码
<!-- 删除以下行(插件会自动注入) -->
<!-- <link rel="stylesheet" href="./css/index.css"> -->
<!--  删除手动引入的 CSS 链接,HtmlWebpackPlugin 会自动注入正确的路径 -->

哈希缓存

文件哈希+浏览器缓存

譬如资源的304状态码,其实是由于浏览器中缓存了该资源,且本次请求的该资源同缓存的资源一致,没有变化,直接使用缓存的资源。

响应头中的ETag

就是处理文件读取的关键点,是服务器响应资源时返回的一个唯一标识符,用于标识资源的具体版本。它通常基于资源内容生成(如哈希值、文件大小、修改时间等),类似于文件的 "指纹"。

  • 浏览器再次请求同一资源时,会先检查本地缓存是否存在。若存在,会向服务器发送条件请求 ,通过If-None-Match 头携带缓存中的Etag,询问服务器资源是否更新:
    • 若资源未更新

      服务器返回304 Not Modified,告知浏览器可直接使用本地缓存,不返回资源内容,减少带宽消耗。

    • 若资源已更新

      服务器返回新资源内容和新的Etag,浏览器更新缓存。

与 Cache-Control/Expires 的配合
  • Cache-Control :控制缓存的 "有效期"(如max-age=31536000表示缓存 1 年)。
  • Etag :在缓存过期后,作为精确验证手段 ,确保浏览器获取到最新资源。
    • Cache-Control未过期,浏览器直接使用缓存,不触发 Etag 验证;
    • Cache-Control过期,浏览器发送带If-None-Match的请求,通过 Etag 验证资源是否真的需要更新。
  • 若同时使用Last-ModifiedEtag,服务器会优先验证EtagIf-None-Match优先级高于If-Modified-Since)。
相关推荐
izx88821 小时前
HTML5敲击乐 PART--1
css
i听风逝夜21 小时前
Web 3D地球实时统计访问来源
前端·后端
iMonster21 小时前
React 组件的组合模式之道 (Composition Pattern)
前端
呐呐呐呐呢21 小时前
antd渐变色边框按钮
前端
元直数字电路验证1 天前
Jakarta EE Web 聊天室技术梳理
前端
wadesir1 天前
Nginx配置文件CPU优化(从零开始提升Web服务器性能)
服务器·前端·nginx
牧码岛1 天前
Web前端之canvas实现图片融合与清晰度介绍、合并
前端·javascript·css·html·web·canvas·web前端
灵犀坠1 天前
前端面试八股复习心得
开发语言·前端·javascript
9***Y481 天前
前端动画性能优化
前端
网络点点滴1 天前
Vue3嵌套路由
前端·javascript·vue.js