使用source map进行代码调试

我们都经历过这样的情况:连续 10 多个小时开发新功能,一切进展顺利。构建项目并将代码推送到生产环境。突然,一个生产错误警报响起!所有人都开始寻找责任人。找到了,原来是你。但你的所有测试套件都没有报错,代码本身看起来也没有问题。

你查看日志,发现错误是:

arduino 复制代码
Uncaught Error: Cannot read property 'xyz' of undefined at app.min.js:1:45678

你心想,app.min.js:1:45678 到底是什么意思?整个源代码中根本没有这样的文件?你的文件叫 app.js,而且它有 45678 个字符长!这根本无法调试!尽管如此,你还是尝试打开文件。整个文件充满了你无法理解的乱码。你该怎么办?

现在,source map登场了。source map允许你将生产环境中的压缩代码(也就是你刚刚看到的乱码)映射到实际的源代码,从而在源代码中精确定位并有效调试。

在这篇博客中,我们将详细介绍source map是什么、为什么要创建它们以及如何创建,还会提供一些有效调试代码的技巧。让我们开始吧!

为什么源代码会被压缩?

在深入source map之前,我们先来解释源代码为何变得面目全非。

答案很简单:压缩。

压缩是将源代码转换为生产环境代码的过程,且不改变其任何功能。这通常由你使用的打包工具(如 Webpack)完成。

简单来说,打包工具通过去除空格、注释和冗余代码,来优化源代码。这使得代码更加高效,体积也小得多。

为什么会这样?

  • 提高加载时间:较小的文件带来更好的网站加载时间。
  • 混淆:虽然不会使代码完全无法阅读,但确实增加了普通用户理解代码的难度。
  • 浏览器性能:代码被修改成浏览器引擎易于解析的形式。

以下是一个压缩后的 React 应用程序代码示例:

null

什么是source map?

source map是指文件名以 .map 结尾的文件,它们将压缩后的代码映射到实际的源代码。例如,这样的文件 example.min.js.mapstyles.css.map。它们由 Webpack、Vite、Rollup、Parcel 等构建工具生成。由于源映射仅在调试时需要,这些工具通常默认不生成源映射。例如,在 Webpack 中启用它,可以在 package.json 文件中添加以下内容:

js 复制代码
// 将此添加到你的 package.json 文件中
"scripts": {
  "build:dev": "webpack --mode development --devtool source-map",
  }

或者在 webpack.config.js 文件中添加:

js 复制代码
module.exports = {
  devtool: 'source-map',
  // ...其余配置
}

source map文件包含有映射的关键信息,包括实际源文件名、包含的内容、源代码中的各种变量名以及压缩后代码文件的名称等。

以下是典型源映射文件的格式:

json 复制代码
{
  "mappings": "AAAA,SAAQA,MAAMA,QAAQ,OAAO;AAC7B,SAAQC...",
    "sources": ["src/index.js"],
    "sourcesContent": [
    "import React from 'react';\nimport { createRoot } from 'react-dom/..."
  ],
    "names": ["React", "createRoot", "App", "count", "setCount", "useState", ...],
    "version": 3,
    "file": "bundle.js.map"
}

其中最重要的部分是 mappings。它使用一种称为 VLQ 64 编码的特殊编码方式,将行映射到编译后的文件及其对应的原始文件。

可视化source map

"好吧,太棒了!" "这到底有什么帮助呢?我还是无法阅读source map并手动解码映射。"

这是一个很好的问题!这引出了本文的主要亮点------源映射可视化工具。这些工具允许你以查看source map,从而有效地定位和调试问题。市场上有许多source map可视化工具,但今天我们将重点介绍 Sokra & Paulirish 的source map可视化工具。你可以在他们的GitHub 存储库上找到此工具的源代码。

以下是一个代码示例的比较。然而,可视化工具的编码映射通过悬停帮助我们将这两种代码对应起来。

null

实践示例

让我们创建一个简单的 React 应用程序

  1. 创建项目目录:
js 复制代码
mkdir my-project
cd my-project
  1. 初始化新项目:
js 复制代码
npm init -y
  1. 将以下依赖项添加到 package.json 中:
json 复制代码
{
  "name": "react-counter-app",
  "version": "1.0.0",
  "description": "简单 React 计数器应用程序",
  "main": "index.js",
  "scripts": {
    "start": "webpack serve --mode development",
    "build": "webpack --mode production",
    "test": "echo "Error: no test specified" && exit 1"
  },
  "dependencies": {
    "react": "^18.2.0",
    "react-dom": "^18.2.0"
  },
  "devDependencies": {
    "@babel/core": "^7.23.0",
    "@babel/preset-env": "^7.23.0",
    "@babel/preset-react": "^7.22.15",
    "babel-loader": "^9.1.3",
    "css-loader": "^6.8.1",
    "html-webpack-plugin": "^5.5.3",
    "style-loader": "^3.3.3",
    "webpack": "^5.88.2",
    "webpack-cli": "^5.1.4",
    "webpack-dev-server": "^4.15.1"
  }
}
  1. 创建一个 src/index.js 文件,其中包含以下 React 代码:
js 复制代码
// src/index.js
import React from 'react';
import { createRoot } from 'react-dom/client';
import './styles.css';

function App() {
  const [count, setCount] = React.useState(0);

  const increment = () => {
    setCount(count + 1);
  };

  const decrement = () => {
    setCount(count - 1);
  };

  return (
    <div className="app">
    <h1>计数器: {count}</h1>
    <button onClick={increment}>增加</button>
    <button onClick={decrement}>减少</button>
    </div>
  );
}

// React 18 新的 createRoot API
const container = document.getElementById('root');
const root = createRoot(container);
root.render(<App />);
  1. 通过添加 src/styles.css 文件来添加样式:
css 复制代码
/* src/styles.css */
.app {
  font-family: Arial, sans-serif;
  max-width: 500px;
  margin: 0 auto;
  padding: 20px;
  text-align: center;
}

button {
  background-color: #4CAF50;
  border: none;
  color: white;
  padding: 10px 20px;
  text-align: center;
  text-decoration: none;
  display: inline-block;
  font-size: 16px;
  margin: 10px;
  cursor: pointer;
  border-radius: 4px;
}

button:hover {
  background-color: #45a049;
}
  1. 现在通过在根文件夹中创建 webpack.config.js 文件来定义 Webpack 配置:
js 复制代码
// webpack.config.js
const path = require('path');
const HtmlWebpackPlugin = require('html-webpack-plugin');

module.exports = {
  entry: './src/index.js',
  output: {
    path: path.resolve(__dirname, 'dist'),
    filename: 'bundle.js',
  },
  module: {
    rules: [
      {
        test: /.(js|jsx)$/,
        exclude: /node_modules/,
        use: {
          loader: 'babel-loader',
          options: {
            presets: ['@babel/preset-env', '@babel/preset-react']
          }
        }
      },
      {
        test: /.css$/,
        use: ['style-loader', 'css-loader']
      }
    ]
  },
  plugins: [
    new HtmlWebpackPlugin({
      template: './public/index.html'
    })
  ],
  devServer: {
    static: {
      directory: path.join(__dirname, 'public'),
    },
    port: 3000,
    open: true
  },
  resolve: {
    extensions: ['.js', '.jsx']
  }
};
  1. 现在你可以通过运行以下命令来启动应用程序:
js 复制代码
webpack serve --mode development

应用应该如下所示(虽然很简单,但你知道的):

null

你可以使用浏览器的开发者工具找到source map。格式可能因浏览器而异。这里我使用 Zen,但所有浏览器的格式应该类似。

你可以通过右键单击页面上的任意位置并选择"检查元素"来找到源映射。然后,在浏览器中转到"源代码"部分并找到源文件。

现在,你可以在source-map-visualization 中加载它。它看起来会是这样的:

null

在右侧,你可以跳过所有 React 代码,直接转到代码的部分。将鼠标悬停在代码的各个部分上,你会看到压缩代码的哪个部分!

一开始看起来可能有点混乱,但尝试在 UI 上悬停在各个元素上,你会发现它实际上非常直观。例如,在这个示例代码中,

js 复制代码
React.useState(0) ----> t().createElement("h1",null,"Counter: ",n) .... // 依此类推

将鼠标悬停在 React.useState 上,会发现它映射到压缩代码中的 createElement。因此,我们的打包工具 Webpack 在这种情况下通过将状态直接转换为 JavaScript 元素并在后续代码中直接修改它来优化代码。

在创建示例应用程序时,你可能已经注意到我们需要在 Webpack 运行命令中显式添加 --mode development 标志。这是因为源映射仅用于调试目的,在生产环境中可能会导致安全问题,包括:

问题 描述 缓解措施
暴露源代码 源映射会暴露原始代码,包括注释和逻辑 在生产环境中使用 hidden-source-mapnosources-source-map
知识产权保护 完整的源映射可能会暴露知识产权 将源映射部署到安全、需要身份验证的位置
文件大小 源映射可能很大,影响下载性能 仅在开发环境中生成映射,或单独提供
服务器配置 CORS 问题可能阻止源映射加载 配置正确的 Access-Control-Allow-Origin 标头

还有一些工具,如 SentryRollbar,它们使用你的源映射来提供更好的错误报告,同时不会引发任何安全问题。

结论

source map是一个令人惊叹的功能,它允许你将源代码映射到压缩代码。我们探讨了如何使用此功能轻松调试代码,并使用源映射可视化工具辅助这一过程。

引用

原文:dev.to/chiragagg5k...

相关推荐
CodeCraft Studio5 分钟前
数据透视表控件DHTMLX Pivot v2.1发布,新增HTML 模板、增强样式等多个功能
前端·javascript·ui·甘特图
一把年纪学编程7 分钟前
【牛马技巧】word统计每一段的字数接近“字数统计”
前端·数据库·word
llc的足迹16 分钟前
el-menu 折叠后小箭头不会消失
前端·javascript·vue.js
九月TTS1 小时前
TTS-Web-Vue系列:移动端侧边栏与响应式布局深度优化
前端·javascript·vue.js
Johnstons1 小时前
AnaTraf:深度解析网络性能分析(NPM)
前端·网络·安全·web安全·npm·网络流量监控·网络流量分析
whatever who cares1 小时前
CSS3 伪元素(Pseudo-elements)大全
前端·css·css3
若愚67921 小时前
前端取经路——性能优化:唐僧的九道心经
前端·性能优化
Bl_a_ck2 小时前
开发环境(Development Environment)
开发语言·前端·javascript·typescript·ecmascript
田本初2 小时前
使用vite重构vue-cli的vue3项目
前端·vue.js·重构
ai产品老杨2 小时前
AI赋能安全生产,推进数智化转型的智慧油站开源了。
前端·javascript·vue.js·人工智能·ecmascript