Webpack 概念速通:从入门到掌握构建工具的精髓

Webpack基本概念

这里我们先简单熟悉下Webpack的基本概念,我们在搭建项目的时候都会要用到的!

这里我们分享的着重点是基本概念 而不是具体配置项和使用方法

  • 依赖图(dependency graph)
  • 模式(mode)
  • 入口(entry)
  • 输出(output)
  • 加载器(loader)
  • 插件(plugin)
  • 源映射(Source Maps)
  • 开发服务器(devServer)

依赖图(dependency graph)

依赖图是指所有模块及其相互之间的依赖关系的一个整体表示。

简单来说,当Webpack处理应用程序时,它会从一个或多个入口文件(entry point)开始递归地构建一个图表。这个图表描述了项目中所有模块是如何相互依赖的。每个文件、模块都是图中的一个节点,节点之间的连接线(边)代表了文件的引入关系或依赖关系。

举个例子,如果你的应用程序有一个入口文件 index.js,它依赖于另一个模块 moduleA.js,而 moduleA.js 又依赖于 moduleB.js,那么Webpack会生成一个包含这些文件的依赖图。通过这个依赖图,Webpack可以知道哪些模块需要被打包在一起,并以合适的顺序加载它们。

这种方式可以确保最终生成的打包文件是最优化的,只包含了实际使用的代码,并且依赖关系被正确解析,从而确保应用程序能够正常运行。

模式(mode)

模式(mode) 是用来指定打包时的环境,主要分为三种:developmentproductionnone。不同的模式会影响 webpack的打包方式和优化策略,以适应不同的开发需求。

这里我们主要是说开发和生产两种模式:

development 模式 production 模式
输出文件 非压缩,带有注释与调试信息 压缩,移除所有注释与调试信息
Source Map 默认启用eval-source-map 默认禁用或启用高效的source-map
压缩 不压缩 自动压缩代码并移除未使用的代码
性能优化 关闭性能提示 启用性能提示与限制
树摇优化(Tree Shaking) 不启用 启用,移除无用代码
环境变量 process.env.NODE_ENV 被设为 'development' process.env.NODE_ENV 被设为 'production'

通过上面的表格中的对比我们可以总结得出:

  • development:适合本地开发,提供调试信息,构建速度快
  • production:适合生产环境,自动压缩和优化代码,提高性能

none呢,它完全不应用任何优化,完全自定义,通常我们不使用这个模式...

入口(entry)

我们上面在说依赖图的时候实际上也提到了我们的核心概念------入口

入口起点(entry point) 指示webpack应该使用哪个模块,来作为构建其内部依赖图的开始。进入入口起点后webpack会找出有哪些模块和库是入口起点(直接和间接)依赖的。

我们有多种方法可以配置入口:

单个入口(简写)语法

  1. 字符串

    用法:entry: string

    webpack.config.js中:

    js 复制代码
    module.exports = {
      entry: './src/index.js',
      // ...其他配置
    }
  2. 数组

    用法:entry: [string]

    当你使用数组形式来定义入口时,Webpack会按照数组中文件的顺序依次加载它们,并且所有这些文件将会被编译进同一个bundle中。这种方式适合于需要按特定顺序加载多个文件的情况,即多页面应用程序。(注意:数组中所有文件会被合并到一个输出文件中)

    webpack.config.js中:

    js 复制代码
    module.exports = {
      entry: ['./src/polyfills.js', './src/index.js'],
      // ...其他配置
    }

    在这个例子中,Webpack会首先加载 polyfills.js 文件,然后加载 index.js 文件。这两个文件的内容会被合并到同一个输出文件中。

对象语法

用法:entry: { <entryChunkName> string | [string] } | {}

当你使用对象形式来定义入口时,每个键值对实际上是在定义一个独立的入口点。这意味着每个入口点都可能会有自己的输出文件(取决于 output 配置,我们这里简单说下,这里后面会详细说),并且它们之间是相互独立的。

webpack.config.js中:

js 复制代码
const path = require('path');
module.exports = {
  entry: {
    main: './src/index.js',
    vendor: './src/vendors.js'
  },
  output: {
    path: path.resolve(__dirname, 'dist'),
    filename: '[name].bundle.js'
  }
}

这里定义了两个入口点:mainvendor。每个入口点都指向了一个单独的文件,Webpack会分别为它们创建对应的输出文件。例如,如果 output.filename 被设置为 [name].bundle.js,那么最终的输出文件名将是 main.bundle.jsvendor.bundle.js(注意是两个文件)

用于描述入口对象的常用属性有以下几种:

  1. dependOn:指定当前入口所依赖的入口
  2. filename:用于覆盖默认的文件名生成规则(优先级高)
  3. import:启动时需要提前加载的模块

看下面这段代码来简述这三个属性的使用:

js 复制代码
module.exports = {
  entry: {
      one: './a',
      two: {
        import: './b',
        dependOn: 'one',
        filename: 'B.bundle.js'
      }
  },
  output: {
    filename: '[name].bundle.js'
  }
}

它设定了两个入口点,其中two入口点依赖于one入口点,one文件中的代码应该在two之前执行;one的输出文件名为one.bundle.js,而two由于设定了优先级更高的filename属性,输出文件名为B.bundle.js

输出(output)

输出(output)告知webpack在向硬盘写入编译后(打包后)的文件的具体路径名称 ,值得一提的是,output配置并不像entry一样可以有多个,它只可以被唯一指定

在说输出之前,我们先来说一下输出中处处会用到的占位符

我们来说几个常见的占位符:

  • [name]:入口文件的名称
  • [id]:模块的id
  • [ext]:输出文件的扩展名(通常在最后)
  • [hash]:根据编译的版本计算的哈希值,文件内容变化时会变,适合做缓存
  • [chunkhash]:根据chunk内容生成的哈希值,每个chunk不同。(chunk是块,它在webpack中指打包过程中生成的一组模块 ,简单说,chunk是在构建过程中生成的代码片段,它的目的就是将代码分割成多个块,这样可以显著的优化加载和提高性能)
  • [contenthash]:根据文件内容生成的哈希值(通常是静态文件)
chunkhash contenthash
基于 Chunk 的内容(多个模块的集合) 单个文件的内容
常用场景 JavaScript 文件(尤其是代码分割后的) CSS 文件、静态资源文件(图片、字体)
缓存控制 较为宽泛,依赖整个chunk的变化 更精确,依赖单个文件内容变化
变化条件 当chunk内的任一模块内容变化时改变 当文件内容变化时改变

占位符是固定的,不可以随便更改名字的哦~

输出(output) 很重要,但是却相对简单,下面我们来说一下output的常见配置选项:

  1. path:指定打包文件的输出目录。

    此配置通常与Node.js的path模块结合使用(这应该不需要我过多解释吧...),来看一段代码:

    js 复制代码
    const path = require('path')
    module.exports = {
      output: {
        // 输出文件夹为项目根目录下的 dist 文件夹
        path: path.resolve(__dirname, 'dist'), 
      },
    }
  2. filename:指定输出文件的文件名,与入口相关

    此属性是output属性的最低要求 ,这意味着你必须要在output属性中添加此配置。属性允许我们动态命名文件,也就是使用我们上面说过的占位符

    来看一个小栗子:

    js 复制代码
    const path = require('path')
    module.exports = {
      entry: {
        main: './src/index.js',
        admin: './src/admin.js'
      },
      output: {
        path: path.resolve(__dirname, 'dist'),
        filename: '[name].[hash].bundle.js', 
    }

    我们定义了两个入口文件,同时指定了输出目录(path),在这里我们使用了两个占位符[name][hash],输出的文件名会是main.[hash].bundle.jsadmin.[hash].bundle.js

  3. chunkFilename:用于指定非入口chunk文件的名称,这些chunk通常由动态导入或代码分割生成。

    此配置也允许我们使用占位符动态命名文件:

    js 复制代码
    module.exports = {
      // ...
      output: {
        // ...
        chunkFilename: '[id].[chunkhash].js', 
        // ...
      },
    }
  4. library:可以让你控制你的库如何被其他代码导入或引用。当你构建一个库而不是应用程序时,这个选项变得尤为重要。

    来看一段library实际使用的代码段:

    js 复制代码
    const path = require('path')
    module.exports = {
      entry: './src/index.js',
      output: {
        filename: 'my-library.js',
        path: path.resolve(__dirname, 'dist'),
        library: {
          name: 'MyLib',
          type: 'umd', // 支持CommonJS2, CommonJS, AMD, 和全局变量
          // 如果需要,还可以指定exportName
          export: 'default',
        },
      },
      mode: 'production', // 后面再解释
    }

    在控制台输入npx webpack来进行库的打包,上述代码打包后,你会在dist文件夹中看到一个my-library.js文件。

    现在你可以将打包好的库部署到某个地方:

    1. 如果你发布到了一个可访问的URL(例如:https://example.com/dist/my-library.js ),那么我们就可以在HTML页面中通过<script>标签来进行引入:

      html 复制代码
      <script src="https://example.com/dist/my-library.js"></script>
    2. 如果你将库发布到了NPM,那么你可以通过npm install来安装它,像我们熟知的模块一样导入使用:

      js 复制代码
      import { add, sub } from 'myLibrary' // 假设我们定义了这两种方法...
  5. publicPath:用于配置项目中所有资源(如 JavaScript、CSS、图片等)的公共 URL 前缀

    它主要影响的是打包之后HTML中引用这些资源的路径,可以理解为输出目录的"虚拟"路径或URL前缀。我们来看一个例子就会瞬间理解publicPath

    现在我们假设有一个项目要部署到https://cdn.example.com/上,那么我们可以这样进行配置:

    js 复制代码
    const path = require('path')
    module.exports = {
      entry: './src/index.js',
      output: {
        path: path.resolve(__dirname, 'dist'), // 打包输出的物理路径
        filename: '[name].[hash].bundle.js',  // 输出文件名
        publicPath: 'https://cdn.example.com/assets/'  // 静态资源的公共URL前缀
      }
    }

    在我们打包后生成的HTML文件中,引用的脚本的形式可能为:<script src="https://cdn.example.com/assets/main.a1b2c3.bundle.js"></script>

    如果没有设置publicPath的话,webpack会默认使用相对路径(./)的形式来引用生成的资源文件。

    默认的相对路径可以满足简单的项目结构或者是静态的网站部署,但是如果针对特定复杂的项目(类似CDN)来说肯定是不合理的,所以是否需要配置publicPath还请君细细斟酌~

  6. assetModuleFilename:用于确定静态资源文件的输出路径和名称。

    直接上例子:

    js 复制代码
    const path = require('path')
    module.exports = {
      output: {
        assetModuleFilename: 'assets/[hash].[contenthash][ext]',
      },
    }

    假设你在代码中引入了一个logo.png,那么输出路径类似于:assets/logo.[contenthash].png

  7. clean:用于清除输出目录(webpack5新增配置)。

    它对于确保构建目录只包含当前构建生成的文件是非常有用的,特别是在构建过程中可能会出现旧文件残留的情况。为true时为开启。

加载器(loader)

加载器(loader) 是webpack中处理和转换文件内容的工具。它允许webpack处理各种类型的非JavaScript文件(如CSS、图片、字体等)。使用loader,可以将这些文件转换为webpack认识的模块。

在webpack中通常使用配置文件中的 module.rules 中配置loader,每个规则包括:

  1. test:一个正则表达式,用于匹配需要处理的文件类型。

  2. exclude:排除某些文件或目录,以避免不必要的处理。

  3. include:指定包含的文件或目录。

  4. use:指定使用的loader配置

  • loader: 指定使用的加载器(如果没有其他配置的话默认为loader)

  • options: 传递给loader的配置选项

  1. type: 用于指定如何打包和处理静态文件资源(专门处理图片资源的属性,相当于use,webpack5新加属性)

加载器最常见的使用场景就是针对js、css和静态资源文件,这里我们着重说一下打包静态资源时使用的type属性的几个值:

'asset/resource' 'asset/inline' 'asset' 'asset/source'
作用 将静态资源打包到output并返回资源的URL 将静态资源作为Base64编码的字符串嵌入到打包后的js文件中 webpack自动判断是将文件作为resource还是inline处理 将文件的内容作为字符串导入到js中
说明 文件单独生成,打包时会将文件复制到dist目录中 适用于文件较小的情况,减少HTTP请求 是混合处理方式,文件小于8KB 会使用inline,反之会使用resource 通常在倒入txt文件时采用,使用场景较少

插件(plugin)

插件(plugin) 是用来扩展webpack功能的。

插件在整个构建过程中执行各种任务。它与loader不同,loader只是转换文件,而插件的功能更为强大,几乎能够完成你想要的任何任务。包括打包优化,资源管理,注入环境变量等一系列的任务。

可以说插件是基于webpack之外的工具库,它可以让你集成其他工具来强化、充盈你的webpack

源映射(Source Maps)

在生产环境中,代码通常会被打包和压缩,这使得调试变得非常困难,因为生成的代码是混淆和压缩过的。而有了Source Maps,浏览器开发者工具可以帮助你查看原始的源代码,而并非是打包、压缩或编译后的,这会让调试更加直观和方便。

假设你有一个源文件app.js,在构建过程中,Webpack会生成一个压缩后的文件(如app.bundle.js),同时也会生成一个app.bundle.js.map文件。这个.map文件包含了原始代码的行号、列号和文件的对应关系。

Source Maps仅需要手动开启一下即可:

js 复制代码
module.exports = {
  devtool: 'source-map', // 生成完整的 Source Maps
}

开发服务器(devServer)

开发服务器(devServer) 专为开发环境设置。它提供了一系列的功能,帮助开发者在本地快速开发、调试和预览项目。它的主要作用是提高开发效率,通过一些高效的配置项优化开发体验。

这里有一个常见的误区,就是被开发服务器映射到浏览器的代码其实并不是你编辑器中的代码(包括打包后和打包前的),实际被映射的代码是在内存中的。

webpack的开发服务器在开发模式下所提供的打包文件都是存在在内存中的,而不是从物理硬盘上直接读取的文件。

我们来说一些常见的配置项以及相应的作用:

  1. static:定义哪些文件夹中的内容将作为静态资源提供

    • directory:通常由此属性指定静态文件目录。这些静态资源不会经过webpack的打包处理,而是直接提供给浏览器访问。这通常用于服务不需要通过webpack处理的文件,如图片、字体等,或某些独立的HTML文件。

    常见使用方式为:

    js 复制代码
    module.exports = {
      devServer: {
        static: {
          directory: path.resolve(__dirname, 'public')
        }
      }
    }

    这段代码的意思为,webpack会直接提供public文件夹中的文件,它们不会经过任何打包处理,即使没有使用webpack的入口文件,也能直接通过URL访问这些资源。

    同样也可以传递dist值,但是正如上面误区所提示的,在开发模式 下我们实际提供的打包文件都是在内存中的,所以即使你编辑器中的dist文件夹下内容为空,也可以正常访问项目。那么我们来看一下publicdist的区别:

    public dist
    用途 通常用来存放不需要经过webpack打包处理的静态文件,这些文件可以是HTML文件、图片、Font文件、独立的js文件等,直接供浏览器访问 用于在开发服务器中模拟生产环境的静态文件
    表现 开发服务器会直接提供public(存放静态资源文件的文件夹)文件夹下的所有文件,它们可以直接通过url访问 开发服务器不会从物理的dist目录打包文件,而是从内存中读取它生成的文件
    使用场景 图像、字体等不会修改的静态资源 查看已经打包完成的文件结构(但在开发模式下,文件其实是从内存提供的)
  2. port:指定开发服务器运行的端口

  3. open:是否在启动开发服务器时自动打开服务器(默认值为false

  4. hot:启用HMR(热模块更新),使得在修改代码时页面不刷新而只替换修改的模块(默认值为false

  5. historyApiFallback:支持H5历史模式路由时,重定向404相应到index.html(默认值为false

  6. proxy:将请求代理到另外一个服务器中,常用语开发环境下的API请求

    • target: 代理的目标服务器
    • changeOrigin:修改请求的来源为目标服务器,解决跨域问题(默认值为false
    • pathRewrite:设置api路径配置
  7. compress:启用Gzip压缩,提高资源的加载速度

  8. client:用于配制客户端设置

后续会继续持续更新补充文章中的概念相关的问题,同时也会继续分享一些使用场景来帮助大家巩固webpack的使用。

如果文章有哪里写错劳烦各位在评论区里指正,我会感激不尽!

如果你看到这里很啦希望你可以点个赞鼓励一下作者,非常感谢!

相关推荐
brief of gali8 分钟前
记录一个奇怪的前端布局现象
前端
Json_181790144801 小时前
电商拍立淘按图搜索API接口系列,文档说明参考
前端·数据库
风尚云网1 小时前
风尚云网前端学习:一个简易前端新手友好的HTML5页面布局与样式设计
前端·css·学习·html·html5·风尚云网
木子02042 小时前
前端VUE项目启动方式
前端·javascript·vue.js
GISer_Jing2 小时前
React核心功能详解(一)
前端·react.js·前端框架
捂月2 小时前
Spring Boot 深度解析:快速构建高效、现代化的 Web 应用程序
前端·spring boot·后端
深度混淆2 小时前
实用功能,觊觎(Edge)浏览器的内置截(长)图功能
前端·edge
Smartdaili China2 小时前
如何在 Microsoft Edge 中设置代理: 快速而简单的方法
前端·爬虫·安全·microsoft·edge·社交·动态住宅代理
秦老师Q2 小时前
「Chromeg谷歌浏览器/Edge浏览器」篡改猴Tempermongkey插件的安装与使用
前端·chrome·edge
滴水可藏海2 小时前
Chrome离线安装包下载
前端·chrome