Day6 深入理解Webpack配置:流程类与工具类配置的重要性与应用 | 青训营

118 阅读18分钟

Webpack是什么

主要用于将多个静态资源打包并转换为可用于生产环境的优化文件,实现模块化开发。 主要功能包括:

  1. 模块打包
  2. 依赖管理
  3. 打包优化
  4. 开发环境支持

当配置webpack时,主要使用两种主要的配置方式:流程类配置和工具类配置

流程类配置

输入

输入配置:

  1. 入口点entry
  2. 上下文context,上下文配置向指定了webpack应该从哪个目录开始解析入口点和其他相对路径默认情况下是webpack.config.is目录下为根目录。我们可以通过这样设置其根目录路径module.exports={context: path.resolve(__dirname,'../')}

模块处理

webpack根据文件的类型(通过正则表达式匹配)应用相应的加载器(Loaders)对模块进行转换,加载器可以将模块转化为webpack理解的格式,例如ES6——>ES5. module模块化处理有两个重要的配置:

  1. 模块解析resolve:该配置项用于告诉webpack如何解析模块的导入请求。在代码中使用import或者require语句倒入模块时,webpack需要知道他的位置,resolve字段用于配置webpack模块解析规则
    1. extensions:用于指定在导入模块时可以省略的扩展名。例如,如果指定了.js和.jsx,则可以在导入模块时省略这些扩展名。
    2. alias:用于创建模块导入的别名。通过别名,可以将长路径映射为短路径,方便引用模块。
    3. modules:用于指定Webpack解析模块时要搜索的目录。默认情况下,Webpack会从当前上下文目录开始向上逐级搜索node_modules目录,但可以通过modules字段来指定其他目录。
    4. fallback:在解析模块时,如果在modules配置项中指定的目录中找不到模块,Webpack将回退到这些目录进行搜索。
  2. 外部模块externals :用于告诉webpack哪些模块时外部引入,即不需要被webpack打包的;比如全局变量、CDN ...

使用模块解析(resolve)和外部模块(externals)的配置项,可以对模块的导入和处理进行更精确的控制。模块解析配置项帮助Webpack解析模块的路径和文件扩展名,而外部模块配置项允许将某些模块标记为外部引入,从而避免对它们进行打包处理。

后处理

  1. 优化optimization
    1. optimization:
    2. mode:用来指定现在是什么开发模式,develop / production / none;不同的模式将启用不同的优化策略和插件。
      1. 在production模式下,Webpack会自动启用各种优化策略,如代码压缩、作用域提升、去除未使用的代码等。
      2. 在development模式下,Webpack会启用更多的调试工具和输出信息。
      3. 在none模式下,不会进行任何优化。
  2. 目标target ,用来处理后处理的目标环境
    1. web:这是Webpack的默认目标。它用于将代码打包为在浏览器中运行的前端应用程序。打包后的文件通常会包含可以在浏览器中执行的JavaScript代码。
    2. node:该目标用于将代码打包为在Node.js环境中运行的应用程序。打包后的文件通常会包含适用于Node.js的模块化代码。
    3. async-node:类似于node目标,但在构建时注重异步加载的支持。适用于构建服务器端应用程序,其中模块可以按需动态加载。
    4. webworker:该目标用于将代码打包为在Web Worker中运行的应用程序。Web Worker是在后台运行的JavaScript脚本,可用于在浏览器中执行计算密集型任务。
    5. electron-main:用于将代码打包为Electron桌面应用程序的主进程。打包后的文件包含Electron主进程所需的代码。electron-renderer:用于将代码打包为Electron桌面应用程序的渲染进程。打包后的文件包含Electron渲染进程所需的代码。

输出

output拥有以下常见属性:

  1. filename:指定输出文件的名称。可以使用占位符(placeholder)来生成唯一的文件名。例如,可以使用[name]占位符表示入口文件的名称,使用[hash]占位符表示文件内容的哈希值。
  2. path:指定输出文件的存储路径。可以使用绝对路径或相对于Webpack配置文件的相对路径。publicPath:指定在浏览器中引用生成文件的公共路径。可以是一个绝对路径或相对路径,用于指定打包后的文件在网页中的加载路径
  3. chunkFilename:用于指定非入口文件(chunk)的输出文件名。通常在使用代码分割(code splitting)时使用
  4. library:如果你正在开发一个库(library),可以使用该属性指定库的名称。这样,在使用该库时,可以通过全局变量或模块导入的方式引用库
  5. libraryTarget:与library属性一起使用,用于指定库的导出方式。可以选择var、commonjs、commonjs2、amd、umd等选项。

工具类配置

工具类配置是webpack4以及更高版本中引入的新型配置方式,它使用js模块导出的函数动态生成webpack配置。

webpack常见的工具类我在这里把他分成三大类,分别是工具开发类性能优化类日志类

工具开发类

  1. watch 文件监听模式

    webpack --watch

    在文件监听模式下,Webpack会持续运行,并监听文件的变化。在终端中使用 Ctrl + C 来停止监听模式。 这里就不得不提一下HMR,他和HMR的区别就在于,HMR相对来说更高级,不需要手动去刷新浏览器就可实现实时刷新。使用 watch 模式和 Hot Module Replacement (HMR) 是可以配合使用的,但并不是必须一起使用的。

    首先,让我们澄清一下它们分别起的作用:

    1. watch 模式是 Webpack 的文件监听模式,用于在开发过程中监听文件的变化。当文件发生变化时,Webpack 会自动重新构建整个应用程序。

    2. HMR 是一种高级机制,它允许在应用程序运行时,只替换发生变化的模块,而不需要完全刷新页面。这意味着在进行代码修改时,只有修改的模块会被动态加载和更新,而其他模块保持不变。

      当你使用 watch 模式时,Webpack会监视文件的变化并重新构建整个应用程序,但它并不提供模块级别的热更新功能。这意味着每次文件变化后,整个应用程序会被重新加载,有时可能会导致页面刷新。这对于某些场景来说可能不够理想,因为它会中断应用程序的运行状态和用户体验。

      为了提供更好的开发体验,可以结合使用 watch 模式和 HMR。通过配置相应的插件和模块加载器,Webpack可以将 HMR 功能集成到开发服务器中,实现模块级别的热更新。这样,在进行代码修改时,只有修改的模块会被重新加载,而其他模块保持不变,从而加快了开发反馈速度,并保持应用程序的状态。

  2. devTool

    devtool 允许我们配置一种称为 "Source Map" 的技术,它会在编译后的代码中生成一个映射文件,将它与原始源代码关联起来。这个映射文件会告诉开发工具如何将编译后的代码映射回原始源代码,以便我们可以方便地进行调试;对于开发环境,我们通常希望有较好的调试体验,因此可以选择生成较高质量的 Source Map,并将其嵌入到最终的打包文件中。这样,在浏览器的开发工具中,我们就可以看到与原始源代码相对应的行号和变量名,从而更容易地找到问题所在。 这个可以对标到实际应用中,我不希望别人看到我写的辣鸡代码时,通常有两种解决思路:

  • 代码混淆,将源代码中的变量名、函数名和空格等不必要的字符进行缩减和替换的过程。常用的 JavaScript 压缩工具如 UglifyJS、Terser 等可以帮助您实现代码压缩。这些工具可以删除注释、缩短变量名、删除空格等操作,从而使代码更加难以被(抓到把柄)理解和被强迫修改修改
  • 代码压缩,代码混淆是一种更进一步的代码保护技术,它通过将代码中的标识符(如变量、函数和类名)进行重命名和转换,使其变得晦涩难懂,增加了代码的复杂性。混淆工具会对代码进行各种转换,如重命名变量名、函数名,转换控制流添加无用代码等。这些转换操作使代码的逻辑变得难以理解,从而增加了代码的保护性。一些常用的 JavaScript 代码混淆工具包括 UglifyJS、JavaScript Obfuscator 等。
  1. devServer 以下是一些 devServer 的主要功能和常用配置选项:
  • 自动刷新(Hot Reloading)devServer 支持自动刷新功能,当您对源代码进行更改时,它会自动重新加载页面,以便您可以立即看到更改的效果,而无需手动刷新浏览器。这样可以提高开发效率,节省时间。
  • 代理请求(Proxy) :在开发过程中,您可能需要与后端 API 进行交互。devServer 允许您配置代理,将特定的 API 请求转发到后端服务器,以避免跨域问题。这样,您可以在开发环境中与后端进行无缝集成,而无需手动处理跨域请求。
  • 本地服务器(Local Server)devServer 通过在本地启动一个服务器,将应用程序的文件提供给浏览器。它会为您提供一个本地的 URL 地址,您可以在浏览器中打开该地址以访问应用程序。这样,您可以在开发环境中进行实时预览和调试。
  • 热模块替换(Hot Module Replacement) :除了自动刷新功能之外,devServer 还支持热模块替换。这意味着当您修改一个模块时,它会只重新编译该模块,然后将新模块的代码注入到运行中的应用程序中,而不会刷新整个页面。这样可以提供更快的开发体验,避免重新加载整个应用程序。
  • 自定义配置(Custom Configuration) :除了上述功能之外,devServer 还提供了许多其他配置选项,以满足您的特定需求。您可以配置服务器的主机、端口、SSL 加密等选项。您还可以设置自定义的路由规则、中间件等。这样,您可以根据项目的具体情况进行灵活的配置。

性能优化类

  • Tree Shaking(摇树优化):Tree Shaking 是一种通过静态分析来消除未使用的代码的技术。它通过识别和移除应用程序中未被使用的模块和代码块,从而减小打包后的文件体积。要启用 Tree Shaking,您需要使用 ES6 模块语法,并确保在 Webpack 配置中启用了相应的选项,如 mode 设置为 "production"。

  • 代码分割(Code Splitting):代码分割是一种将应用程序拆分成多个较小的包的技术。通过将代码分割成按需加载的模块,可以减小初始加载的文件大小,并提高应用程序的加载速度。Webpack 提供了多种代码分割的方法,如使用动态导入(dynamic import)、使用 SplitChunksPlugin 插件等。

  • 懒加载(Lazy Loading):懒加载是一种延迟加载模块的技术,只有在需要时才会加载相应的模块。这可以帮助减小初始加载的文件大小,并提高应用程序的响应速度。您可以使用动态导入语法或使用类似 React.lazy 的库来实现懒加载。

    提到懒加载就不得不再提一嘴他和预加载的区别和联系了,懒加载(Lazy Loading)和预加载(Preloading)是两种优化网页加载性能的技术,它们可以提高用户体验和减少页面加载时间。下面是它们的简要解释:

    懒加载(Lazy Loading) :懒加载是一种延迟加载内容的策略。它适用于网页上的大量或较大的资源,例如图片、视频或其他媒体文件。通常,在页面初始加载时,并不会一次性加载所有的资源,而是只加载首屏可见区域的内容。当用户滚动页面时,懒加载会动态地加载进入视野的部分内容,而不加载未进入视野的内容。这样可以减少初始加载时间和数据传输量,提高页面的加载速度和响应性。

    懒加载的实现方式通常是使用 JavaScript 监听滚动事件,当元素进入视野时,动态地加载相应的内容。也可以借助第三方库如 Intersection Observer 来实现懒加载的效果,它提供了更简单和高效的方式来检测元素是否可见。

    预加载(Preloading) :预加载是一种提前加载资源的策略,它用于加载将来可能需要的资源,以便在用户请求时能够快速获取。预加载适用于关键资源,例如下一个页面所需的 CSS、JavaScript 文件或其他静态资源。通过在当前页面中预加载这些资源,可以减少未来页面的加载时间,并提供更流畅的用户体验。

    预加载通常使用 <link rel="preload"> 标签来实现,通过指定资源的 URL 和类型,浏览器会在页面加载过程中提前请求并加载这些资源。预加载资源的优先级可以使用 as 属性进行调整,以确保关键资源优先加载。

    以下是一个实现预加载和懒加载的例子,我们在npm install intersection-observer下载完依赖之后:

<template>
  <div>
    <div class="image-container" v-for="image in images" :key="image.id">
      <!-- 预加载部分 -->
      <img :src="image.src" style="display: none;" @load="onImagePreloaded" />
      <!-- 懒加载部分 -->
      <img :src="placeholderImage" :data-src="image.src" alt="Lazy-loaded Image" class="lazy-image" ref="lazyImages" />
    </div>
  </div>
</template>

<script>
import { ref, onMounted } from 'vue';

export default {
  setup() {
    const images = [
      { id: 1, src: 'path/to/image1.jpg' },
      { id: 2, src: 'path/to/image2.jpg' },
      { id: 3, src: 'path/to/image3.jpg' },
      //根据需要添加更多我需要的imgs
    ];

    const lazyImages = ref([]);

    onMounted(() => {
      const observer = new IntersectionObserver((entries) => {
        entries.forEach((entry) => {
          if (entry.isIntersecting) {
            const lazyImage = entry.target;
            lazyImage.src = lazyImage.dataset.src;
            observer.unobserve(lazyImage);
          }
        });
      });

      lazyImages.value.forEach((image) => {
        // 懒加载部分
        observer.observe(image);
      });
    });

    const placeholderImage = 'path/to/placeholder.jpg';//placeholderImage 是一个占位图

    const onImagePreloaded = (image) => {
      // 预加载部分完成后的逻辑处理
      // 可以在这里执行一些操作,如显示预加载的图片等,在这里我们可以简单的把他显示出来
        image.style.display = 'block';
    };

    return {
      images,
      lazyImages,
      placeholderImage,
      onImagePreloaded,
    };
  },
};
</script>

需要注意的是,懒加载和预加载是根据具体的优化需求和场景来选择使用的。懒加载适用于优化大量或较大的资源的加载,而预加载适用于提前加载关键资源以提高未来页面的加载性能。综合使用这两种技术可以更有效地优化网页的加载性能。

  • 缓存和长效缓存(Caching and Long-Term Caching):通过使用文件名哈希或内容哈希,可以实现文件的缓存和长效缓存。这样,当文件内容发生更改时,会生成一个新的文件名或哈希值,从而确保客户端能够正确地获取最新的文件。这有助于浏览器在后续访问时重用缓存的文件,减少不必要的网络请求。 缓存和长效缓存可以通过以下几种方式来实现:

    1. 文件名哈希(File Name Hashing) :通过为每个生成的文件添加哈希值作为文件名的一部分,可以实现缓存和长效缓存。当文件内容发生更改时,哈希值也会相应地改变,从而确保客户端能够获取到最新的文件。您可以使用 Webpack 插件如 MiniCssExtractPlugin 和 HashedModuleIdsPlugin 来生成带有哈希值的文件名。
    2. 输出文件名持久化(Persistent Output File Names) :将输出的文件名设置为基于内容的哈希值,而不是使用自增的数字或随机生成的字符串。这样可以确保文件名与其内容的关联性,从而实现长效缓存。可以使用 Webpack 的 [contenthash] 或 [chunkhash] 变量,在配置中为输出文件名添加内容哈希。通俗的说(个人理解):

    当我们构建一个应用程序时,通常会生成一些静态资源文件,例如 JavaScript 文件、CSS 文件和图片文件等。这些文件在用户访问网页时需要被下载到客户端进行加载和显示。缓存是一种技术,它可以将这些文件保存在用户的浏览器中,以便下次访问页面时可以直接从缓存中加载,而不需要重新下载。

    但是,当我们对这些文件进行更新时,浏览器可能会继续使用之前缓存的旧版本文件,而不是获取最新的文件。这就导致了用户看到的是旧版本的应用程序,而不是我们期望的新版本。

    为了解决这个问题,可以使用输出文件名持久化的方法。持久化意味着每当我们对文件进行更新时,都会为文件生成一个唯一的标识符,例如哈希值。然后,我们将这个标识符添加到文件名中。这样,当浏览器检测到文件名发生变化时,它就会认为文件是一个新的版本,而不是使用之前的缓存。

    通过持久化输出文件名,我们可以确保用户在更新应用程序后能够获取到最新的文件,而不会被旧版本的缓存所影响。这样可以提供更好的用户体验,同时确保应用程序的更新能够及时生效。

    1. 版本号管理(Versioning) :添加版本号到文件的 URL 中,以强制客户端重新下载更新后的文件。您可以将版本号作为查询参数或路径的一部分添加到文件 URL 中。每当文件内容发生更改时,更新版本号,从而强制客户端加载最新的文件。这种方式适用于不使用文件名哈希的情况。

      Semantic Versioning 的版本号格式为 MAJOR.MINOR.PATCH。每个部分具有以下含义:

      • MAJOR:主要版本号,表示不向后兼容的更新。当进行重大的功能改变或破坏性的 API 更改时,增加主要版本号。
      • MINOR:次要版本号,表示向后兼容的新功能。当添加新功能或功能改进时,增加次要版本号。
      • PATCH:补丁版本号,表示向后兼容的错误修复和细微改动。当进行错误修复或其他细小改动时,增加补丁版本号。
    2. 服务端配置缓存策略(Server-Side Configuration for Caching) :在服务器端设置适当的响应头,如 Cache-Control 和 Expires,来指示浏览器对特定的资源进行缓存。这样,浏览器将根据响应头中的缓存策略来决定是否从缓存中加载文件,而不是每次都向服务器发送请求。

    3. CDN 缓存(CDN Caching) :如果您使用内容分发网络(CDN)来分发静态文件,可以配置 CDN 的缓存策略,以便在 CDN 边缘节点上缓存文件。这样,当用户请求文件时,CDN 可以直接返回缓存的文件,减少对源服务器的请求。

  • 并行处理(Parallel Processing):Webpack 5 引入了并行处理功能,可以同时处理多个模块的构建。这可以显著提高构建速度,尤其是在大型项目中。您可以通过设置 parallel 选项为 true 来启用并行处理。

  • 缩小搜索范围(Narrowing the Search Scope):通过明确指定入口文件、减少模块搜索范围和排除不必要的文件,可以缩小 Webpack 的搜索范围。这有助于减少构建时间和提高性能。可以使用resolve>module指定目录位置,也就是我们在项目中常用的@/xxx的写法来源

module.exports = {
  // ...
  resolve: {
    alias: {
      react: 'preact/compat', // 为React创建别名
      '@components': path.resolve(__dirname, 'src/components') // 为src/components目录创建别名
    }
  }
};
  • 优化插件(Optimization Plugins):Webpack 提供了一些优化插件,如 TerserPlugin、OptimizeCSSAssetsPlugin 等,用于压缩和优化 JavaScript 和 CSS 代码。这些插件可以减小生成的包的体积,并提高应用程序的加载速度。

日志类

举几个常用例子:

  1. stats选项:
module.exports = {
  // ...
  stats: {
    assets: true,    // 是否显示构建生成的资源信息,如输出的文件名、文件大小等
    modules: false,  // 是否显示模块信息,包括模块名称、依赖关系等
    colors: true,    // 是否使用彩色输出,用于增强可读性
    // 更多配置选项...
  }
};

以上即是本人对webpack的两大类型配置(流程类、工具类)的浅薄理解。