神族九帝's blog 神族九帝's blog
首页
  • 神卡套餐 (opens new window)
  • 神族九帝 (opens new window)
  • 网盘资源 (opens new window)
  • 今日热点 (opens new window)
  • 在线PS (opens new window)
  • IT工具 (opens new window)
  • FC游戏 (opens new window)
  • 在线壁纸 (opens new window)
  • 面试突击
  • 复习指导
  • HTML
  • CSS
  • JavaScript
  • 设计模式
  • 浏览器
  • 手写系列
  • Vue
  • Webpack
  • Http
  • 前端优化
  • 项目
  • 面试真题
  • 算法
  • 精选文章
  • 八股文
  • 前端工程化
  • 工作笔记
  • 前端基础建设与架构 30 讲
  • vue2源码学习
  • 剖析vuejs内部运行机制
  • TypeScript 入门实战笔记
  • vue3源码学习
  • 2周刷完100道前端优质面试真题
  • 思维导图
  • npm发包
  • 重学node
  • 前端性能优化方法与实战
  • webpack原理与实战
  • webGl
  • 前端优化
  • Web3
  • React
  • 更多
  • 未来要做的事
  • Stirling-PDF
  • ComfyUI
  • 宝塔面板+青龙面板
  • 安卓手机当服务器使用
  • 京东自动评价代码
  • 搭建x-ui免流服务器(已失效)
  • 海外联盟
  • 好玩的docker
  • 收藏夹
  • 更多
GitHub (opens new window)

神族九帝,永不言弃

首页
  • 神卡套餐 (opens new window)
  • 神族九帝 (opens new window)
  • 网盘资源 (opens new window)
  • 今日热点 (opens new window)
  • 在线PS (opens new window)
  • IT工具 (opens new window)
  • FC游戏 (opens new window)
  • 在线壁纸 (opens new window)
  • 面试突击
  • 复习指导
  • HTML
  • CSS
  • JavaScript
  • 设计模式
  • 浏览器
  • 手写系列
  • Vue
  • Webpack
  • Http
  • 前端优化
  • 项目
  • 面试真题
  • 算法
  • 精选文章
  • 八股文
  • 前端工程化
  • 工作笔记
  • 前端基础建设与架构 30 讲
  • vue2源码学习
  • 剖析vuejs内部运行机制
  • TypeScript 入门实战笔记
  • vue3源码学习
  • 2周刷完100道前端优质面试真题
  • 思维导图
  • npm发包
  • 重学node
  • 前端性能优化方法与实战
  • webpack原理与实战
  • webGl
  • 前端优化
  • Web3
  • React
  • 更多
  • 未来要做的事
  • Stirling-PDF
  • ComfyUI
  • 宝塔面板+青龙面板
  • 安卓手机当服务器使用
  • 京东自动评价代码
  • 搭建x-ui免流服务器(已失效)
  • 海外联盟
  • 好玩的docker
  • 收藏夹
  • 更多
GitHub (opens new window)
  • 工作笔记

  • 前端基础建设与架构 30 讲

  • vue2源码学习

  • 剖析vuejs内部运行机制

  • TypeScript 入门实战笔记

  • vue3源码学习

  • 2周刷完100道前端优质面试真题

  • 思维导图

  • npm发包

  • 重学node

  • 前端性能优化方法与实战

  • webpack原理与实战

    • 开篇词:Webpack 现代化前端应用的基石
    • 第01讲:Webpack 究竟解决了什么问题?
    • 第02讲:如何使用 Webpack 实现模块化打包?
    • 第03讲:如何通过 Loader 实现特殊资源加载?
    • 第04讲:如何利用插件机制横向扩展 Webpack 的构建能力?
    • 第05讲:探索 Webpack 运行机制与核心工作原理
    • 第06讲:如何使用 Dev Server 提高你的本地开发效率?
    • 第07讲:如何配置 Webpack SourceMap 的最佳实践?
      • 第08讲:如何让你的模块支持热替换(HMR)机制?
      • 第09讲:Tree Shaking 和 sideEffects
      • 第10讲:Code Splitting(分块打包)
      • 第11讲:如何优化Webpack的构建速度和打包结果
      • 第12讲:如何选择打包工具:Rollup 和 Webpack ?
      • 第13讲:如何使用 Parcel 零配置完成打包任务?
      • 加餐:Vue3 到底带来了哪些变化
    • webGl

    • 前端优化

    • Web3

    • React

    • 更多

    • 笔记
    • webpack原理与实战
    wu529778790
    2021-11-09

    第07讲:如何配置 Webpack SourceMap 的最佳实践?

    Source Map 简介

    目前很多第三方库在发布的文件中都会同时提供一个 .map 后缀的 Source Map 文件。例如 jQuery。我们可以打开它的 Source Map 文件看一下,如下图所示:

    20211109211634

    这是一个 JSON 格式的文件,为了更容易阅读,我提前对该文件进行了格式化。这个 JSON 里面记录的就是转换后和转换前代码之间的映射关系,主要存在以下几个属性:

    • version 是指定所使用的 Source Map 标准版本;
    • sources 中记录的是转换前的源文件名称,因为有可能出现多个文件打包转换为一个文件的情况,所以这里是一个数组;
    • names 是源代码中使用的一些成员名称,我们都知道一般压缩代码时会将我们开发阶段编写的有意义的变量名替换为一些简短的字符,这个属性中记录的就是原始的名称;
    • mappings 属性,这个属性最为关键,它是一个叫作 base64-VLQ 编码的字符串,里面记录的信息就是转换后代码中的字符与转换前代码中的字符之间的映射关系,具体如下图所示:

    20211109211711

    一般我们会在转换后的代码中通过添加一行注释的方式来去引入 Source Map 文件。不过这个特性只是用于开发调试的,所以最新版本的 jQuery 已经去除了引入 Source Map 的注释,我们需要手动添加回来,这里我们在最后一行添加 //# sourceMappingURL=jquery-3.4.1.min.map,具体效果如下:

    20211109211740

    这样我们在 Chrome 浏览器中如果打开了开发人员工具,它就会自动请求这个文件,然后根据这个文件的内容逆向解析出来源代码,以便于调试。同时因为有了映射关系,所以代码中如果出现了错误,也就能自动定位找到源代码中的位置了。

    我们回到浏览器中,打开开发人员工具,找到 Source 面板,这里我们就能看到转换前的 jQuery 源代码了,具体效果如下图所示:

    20211109211816

    我们还可以添加一个断点,然后刷新页面,进行单步调试,此时调试过程中使用的就是源代码而不是压缩过后的代码,具体效果如下图所示:

    20211109211841

    Webpack 中配置 Source Map

    20211109211916

    然后打开命令行终端,运行 Webpack 打包。打包完成过后,我们打开 dist 目录,此时这个目录中就会生成我们 bundle.js 的 Source Map 文件,与此同时 bundle.js 中也会通过注释引入这个 Source Map 文件,具体如下图所示:

    20211109211935

    我们再回到命令行,通过 serve 工具把打包结果运行起来,然后打开浏览器,再打开开发人员工具,此时我们就可以直接定位到错误所在的位置了。当然如果需要调试,这里也可以直接调试源代码。

    CgqCHl65LS6AQ3RLAA8Qi5GdMKM734

    Webpack 中的 devtool 配置,除了可以使用 source-map 这个值,它还支持很多其他的选项,具体的我们可以参考文档中的不同模式的对比表。

    20211109212045

    上表分别从初次构建速度、监视模式重新构建速度、是否适合生成环境使用,以及 Source Map 的质量,这四个维度去横向对比了不同的 Source Map 模式之间的差异。

    Eval 模式

    首先来看 eval 模式。在去具体了解 Webpack eval 模式的 Source Map 之前,我们需要先了解一下 JavaScript 中 eval 的一些特点。

    eval 其实指的是 JavaScript 中的一个函数,可以用来运行字符串中的 JavaScript 代码。例如下面这段代码,字符串中的 console.log("foo~") 就会作为一段 JavaScript 代码被执行:

    20211109212126

    在默认情况下,这段代码运行在一个临时的虚拟机环境中,我们在控制台中就能够看到:

    20211109212150

    其实我们可以通过 sourceURL 来声明这段代码所属文件路径,接下来我们再来尝试在执行的 JavaScript 字符串中添加一个 sourceURL 的声明,具体操作如下:

    20211109212215

    具体就是在 eval 函数执行的字符串代码中添加一个注释,注释的格式:# sourceURL=./path/to/file.js,这样的话这段代码就会执行在指定路径下。

    在了解了 eval 函数可以通过 sourceURL 指定代码所属文件路径这个特点过后,我们再来尝试使用这个叫作 eval 模式的 Source Map。

    我们回到 Webpack 的配置文件中,将 devtool 属性设置为 eval,具体如下:

    20211109212317

    然后我们回到命令行终端再次运行打包,打包过后,找到生成的 bundle.js 文件,你会发现每个模块中的代码都被包裹到了一个 eval 函数中,而且每段模块代码的最后都会通过 sourceURL 的方式声明这个模块对应的源文件路径,具体如下:

    20211109212344

    那此时如果我们回到浏览器运行这里的 bundle.js,一旦出现错误,浏览器的控制台就可以定位到具体是哪个模块中的代码,具体效果如下:

    20211109212413

    但是当你点击控制台中的文件名打开这个文件后,看到的却是打包后的模块代码,而并非我们真正的源代码,具体如下:

    20211109212429

    综上所述,在 eval 模式下,Webpack 会将每个模块转换后的代码都放到 eval 函数中执行,并且通过 sourceURL 声明对应的文件路径,这样浏览器就能知道某一行代码到底是在源代码的哪个文件中。

    因为在 eval 模式下并不会生成 Source Map 文件,所以它的构建速度最快,但是缺点同样明显:它只能定位源代码的文件路径,无法知道具体的行列信息。

    案例准备工作

    为了可以更好地对比不同模式的 Source Map 之间的差异,这里我们使用一个新项目,同时创建出不同模式下的打包结果,通过具体实验来横向对比它们之间的差异。

    在这个案例中,项目中只有两个 JS 模块,在 main.js 中,我故意加入了一个运行时错误,具体项目结构和部分代码如下:

    20211109212523

    然后我们打开 Webpack 的配置文件,在这个文件中定义一个数组,数组中每一个成员都是 devtool 配置取值的一种,具体代码如下:

    20211109212539

    在上一课时中我们也提到过,Webpack 的配置文件除了可以导出一个配置对象,还可以导出一个数组,数组中每一个元素就是一个单独的打包配置,那这样就可以在一次打包过程中同时执行多个打包任务。

    例如,我们这里导出一个数组,然后在这个数组中添加两个打包配置,它们的 entry 都是 src 中的 main.js,不过它们输出的文件名不同,具体代码如下:

    20211109212611

    这么配置的话,再次打包就会有两个打包子任务工作,我们的 dist 中生成的结果也就是两个文件,具体结果如下:

    20211109212629

    了解了 Webpack 这种配置用法过后,我们再次回到配置文件中,遍历刚刚定义的数组,为每一个模式单独创建一个打包配置,这样就可以一次性生成所有模式下的不同结果,这比我们一个一个去试验的效率更高,而且对比起来也更明显。

    具体配置代码如下:

    20211109212710

    这里简单解释一下这个配置中的部分配置用意:

    1. 定义 devtool 属性,它就是当前所遍历的模式名称;
    2. 将 mode 设置为 none,确保 Webpack 内部不做额外处理;
    3. 设置打包入口和输出文件名称,打包入口都是 src/main.js,输出文件名称我们就放在 js 目录中,以模式名称命名,至于为什么放在单独目录中,你可以在接下来的内容中找到答案;
    4. 为 js 文件配置一个 babel-loader,配置 babel-loader 的目的是稍后能够辨别其中一类模式的差异。
    5. 配置一个 html-webpack-plugin,也就是为每个打包任务生成一个 HTML 文件,通过前面的内容,我们知道 html-webpack-plugin 可以生成使用打包结果的 HTML,接下来我们就是通过这些 HTML 在浏览器中进行尝试。

    配置完成以后,我们再次回到命令行终端运行打包,那此时这个打包过程就自动生成了不同模式下的打包结果,具体结果如下图所示:

    20211109212750

    然后我们通过 serve 把结果运行起来,打开浏览器,此时我们能够在页面中看到每一个使用不同模式 Source Map 的 HTML 文件,具体如下图:

    20211109212816

    那如果刚刚没有把 JS 文件输出到单独目录中,这里的文件就会非常多,导致 HTML 文件寻找起来特别麻烦。

    不同模式的对比

    有了不同模式下生成的结果过后,我们就可以仔细去对比不同 Source Map 模式之间的具体差异了。其实也没必要真的一个一个去看,这里我先带你看几个比较典型的模式,然后找出它们的规律,这样你就再也不用头大了。

    首先 eval 模式,这个模式刚刚已经单独看过了,它就是将模块代码放到 eval 函数中执行,并且通过 sourceURL 标注所属文件路径,在这种模式下没有 Source Map 文件,所以只能定位是哪个文件出错,具体效果如下图:

    20211109212854

    然后我们再来看一个叫作 eval-source-map 的模式,这个模式也是使用 eval 函数执行模块代码,不过这里有所不同的是,eval-source-map 模式除了定位文件,还可以定位具体的行列信息。相比于 eval 模式,它能够生成 Source Map 文件,可以反推出源代码,具体效果如下:

    Ciqc1F65LV2ABnPSAAOQByCZ1X8805

    紧接着我们再来看一个叫作 cheap-eval-source-map 的模式。根据这个模式的名字就能推断出一些信息,它就是在 eval-source-map 基础上添加了一个 cheap,也就是便宜的,或者叫廉价的。用计算机行业的常用说法,就是阉割版的 eval-source-map,因为它虽然也生成了 Source Map 文件,但是这种模式下的 Source Map 只能定位到行,而定位不到列,所以在效果上差了一点点,但是构建速度会提升很多,具体效果如下图:

    CgqCHl65LXKAL1X9AAQvU6NQre0545

    接下来再看一个叫作 cheap-module-eval-source-map 的模式。慢慢地我们就发现 Webpack 中这些模式的名字不是随意的,好像都有某种规律。这里就是在 cheap-eval-source-map 的基础上多了一个 module,具体效果如下图:

    Ciqc1F65LYKAb35fAAWpO16gIpE536

    这种模式同样也只能定位到行,它的特点相比于 cheap-eval-source-map 并不明显 ,如果你没有发现差异,可以再去看看上一种模式,仔细做一个对比,相信对比之后你会发现,cheap-module-eval-source-map 中定位的源代码与我们编写的源代码是一模一样的,而 cheap-eval-source-map 模式中定位的源代码是经过 ES6 转换后的结果,具体对比如下(左图是 cheap-eval-source-map):

    20211109213106

    这也是为什么之前我要给 JS 文件配置 Loader 的原因:因为这种名字中带有 module 的模式,解析出来的源代码是没有经过 Loader 加工的,而名字中不带 module 的模式,解析出来的源代码是经过 Loader 加工后的结果。也就是说如果我们想要还原一模一样的源代码,就需要选择 cheap-module-eval-source-map 模式。

    了解了这些过后,你基本上就算通盘了解了 Webpack 中所有 Source Map 模式之间的差异,因为其它的模式无外乎就是这几个特点的排列组合罢了。

    例如,我们再来看一个 cheap-source-map 模式,这个模式的名字中没有 eval,意味着它没用 eval 执行代码,而名字中没有 module,意味着 Source Map 反推出来的是 Loader 处理后的代码,有 cheap 表示只能定位源代码的行号。

    那以上就是我们在日常开发过程中经常用到的几种 Source Map 模式,你在尝试的时候一定要注意:找规律很重要。

    除此之外,还有几个特殊一点的模式,我们单独介绍一下:

    • inline-source-map 模式

    它跟普通的 source-map 效果相同,只不过这种模式下 Source Map 文件不是以物理文件存在,而是以 data URLs 的方式出现在代码中。我们前面遇到的 eval-source-map 也是这种 inline 的方式。

    • hidden-source-map 模式

    在这个模式下,我们在开发工具中看不到 Source Map 的效果,但是它也确实生成了 Source Map 文件,这就跟 jQuery 一样,虽然生成了 Source Map 文件,但是代码中并没有引用对应的 Source Map 文件,开发者可以自己选择使用。

    • nosources-source-map 模式:

    在这个模式下,我们能看到错误出现的位置(包含行列位置),但是点进去却看不到源代码。这是为了保护源代码在生产环境中不暴露。

    写在最后

    这里再分享一下我个人开发时的选择,供你参考。

    首先开发过程中(开发环境),我会选择 cheap-module-eval-source-map,原因有以下三点:

    • 我使用框架的情况会比较多,以 React 和 Vue.js 为例,无论是 JSX 还是 vue 单文件组件,Loader 转换后差别都很大,我需要调试 Loader 转换前的源代码。
    • 一般情况下,我编写的代码每行不会超过 80 个字符,对我而言能够定位到行到位置就够了,而且省略列信息还可以提升构建速度。
    • 虽然在这种模式下启动打包会比较慢,但大多数时间内我使用的 webpack-dev-server 都是在监视模式下重新打包,它重新打包的速度非常快。

    综上所述,开发环境下我会选择 cheap-module-eval-source-map。

    至于发布前的打包,也就是生产环境的打包,我选择 none,它不会生成 Source Map。原因很简单:

    • 首先,Source Map 会暴露我的源代码到生产环境。如果没有控制 Source Map 文件访问权限的话,但凡是有点技术的人都可以很容易的复原项目中涉及的绝大多数源代码,这非常不合理也不安全,我想很多人可能都忽略了这个问题。
    • 其次,调试应该是开发阶段的事情,你应该在开发阶段就尽可能找到所有问题和隐患,而不是到了生产环境中再去全民公测。如果你对自己的代码实在没有信心,我建议你选择 nosources-source-map 模式,这样出现错误可以定位到源码位置,也不至于暴露源码。

    除此之外,我还要强调一点,Source Map 并不是 Webpack 特有的功能,它们两者的关系只是:Webpack 支持 Source Map。大多数的构建或者编译工具也都支持 Source Map。希望你不要把它们二者捆绑到一起,混为一谈。


    # 精选评论

    # Sept

    官网没有 cheap-module-eval-source-map,有 eval-cheap-module-source-map,是后来改了吗,还是文中写错了?

    #     讲师回复:

        官方考虑之前的选项比较乱,现在开始慢慢规整了,不过仍然还可以这么使用,没有区别,建议遵循最新的官方文档使用,避免后期调整

    编辑 (opens new window)
    上次更新: 2025/03/17, 12:21:00
    第06讲:如何使用 Dev Server 提高你的本地开发效率?
    第08讲:如何让你的模块支持热替换(HMR)机制?

    ← 第06讲:如何使用 Dev Server 提高你的本地开发效率? 第08讲:如何让你的模块支持热替换(HMR)机制?→

    最近更新
    01
    Code Review
    10-14
    02
    ComfyUI
    10-11
    03
    vscode插件开发
    08-24
    更多文章>
    Power by vuepress | Copyright © 2015-2025 神族九帝
    • 跟随系统
    • 浅色模式
    • 深色模式
    • 阅读模式
    ×