?调试React源代码是否优雅?别着急,先听一个故事:东东是一名前端工程师,主要使用React技术栈。用了很多年,想更深入一些,所以最近才开始阅读React源码。他下载了react和react-dom包,引入到项目中,开发服务启动后,他打开ChromeDevtools进行断点调试。就这样调试了一段时间后,他有些迷茫:这种调试是可以的,但是总感觉和源码有距离,因为调试的是react-dom.development.js和逻辑在源代码中分散在不同的包中,所以即使你懂逻辑,也不知道这些逻辑在哪些包里,只能通过搜索定位。于是他在想,有没有更好的调试方法,可以调试React的原始源码呢?于是,他来找我问我:光哥,你在调试React源码的时候有没有遇到这些问题?你是怎么调试的?我说,确实,我一开始也调试过react-dom.development.js,现在可以直接调试React的原始源码了,而且是在VSCode里调试的。点击调用堆栈可以直接打开对应的React源码文件,并定位到对应的行列号:哇,这就是我想要的调试效果,怎么做到的。要实现这样的调试效果,确实有点复杂。我们稍微看一下:首先,我们需要在VSCode中调试React项目,而不是在ChromeDevtools中,这样我们就可以直接打开对应的文件:DebuggingwithVSCodeReactproject我们使用create-react-app来创建一个react项目,然后运行??npmrunstart。这时候可以用ChromeDevtools调试浏览器访问:但是我们的目标是在VSCode中调试,所以需要添加一个VSCode调试器配置:在根目录下创建一个.vscode/launch.json文件,添加一个chrome类型调试配置,输入调试url。然后点击调试开始:这时候可以直接在VSCode中断点调试:用VSCode调试肯定比ChromeDevtools方便。但这不是我们的主要目的。调试还是react-dom.development.js:如何调试react的原始源码?这就涉及到了sourcemap的作用:sourcemapJS代码被编译生成目标代码,但同时也生成了sourcemap。sourcemap的作用是映射目标代码中的位置和源代码中的位置。例如源代码中第3行第5列的代码编译后对应的是第1行第10列的代码。像这样的映射有很多,编码后是这样的:在js文件最后一行添加一行注释关联sourcemap://#sourceMappingURL=http://example.com/path/to/your/sourcemap.map调试工具支持解析源映射以将调试代码位置映射到源代码中的位置。比如chromedevtools的Sources面板会提示sourcemapping来自哪个文件,点击链接跳转到映射前的文件:同样,VSCodeDebugger也支持sourcemap,有一个sourceMaps调试配置选项可以启用和禁用sourcemap功能,默认启用。那么我们只要把react-dom.development.js和sourcemap关联起来就可以debug原来的React源码了?理论上是这样的,但是现在下载的react和react-dom包是不包含sourcemap的,我们只好自己下载React源码构建:buildareactpackagewithsourcemap用npm下载的react包就像this:而我们需要的是带有sourcemap的代码,是这样的:这个是下载react源码,自己构建:gitclonehttps://github.com/facebook/react下载的代码执行npmrunbuild看到它构建产品:这里的build/node_modules下的react和react-dom包就是我们需要的。但是现在build生成的代码没有sourcemap,需要修改build过程。构建命令执行./scripts/rollup/build.js,打开这个文件,做一些修改。找到rollup的配置,添加一行sourcemap:true。这很容易理解。就是让rollup在构建的时候生成sourcemap:再次运行npmrunbuild,会报错:转换后的插件没有生成sourcemap。这是因为在构建过程中会进行多次转换,生成多个sourcemap,然后将sourcemap拼接成最终的sourcemap。如果中间有一个没有生成sourcemap的转换步骤,就会坏掉,sourcemaps不能串联起来。解决这个问题,只要找出并注释掉没有生成sourcemap的插件:在getPlugins方法中,注释掉这4个插件:这个是为了删除usestrict,可以去掉。这是生产环境压缩后的代码,也可以去掉。这是为了用prettier格式化代码,也可以去掉。这个就是加一些headercodes,比如Lisence等,没用的可以去掉。去掉这四个插件后,再次运行npmrunbuild,就可以正常进行构建了,然后生成的代码是withsourcemap:这样,我们就成功构建了reactpackagewithsourcemap了!最后一步是使用sourcemap直接调试React原始源码,应用sourcemap,调试React原始源码。我们用sourcemap构建了react和react-dom包,然后把这两个包拷贝到test项目的node_modules下,能不能直接debug原来的源码呢?或不。为什么?看下图:我们修改了构建过程,构建了react源码,用sourcemaps生成了react和react-dom包。这些包最终导出了react-xx.development.js。然后在项目中引入,通过webpack打包生成bundle.js和sourcemap。调试工具在后面运行代码的时候,会解析sourcemap,完成bundle.js到react-xxx.development.js的映射:但是不会做react-xx.development.js到原始source的映射再次反应的代码。也就是说,调试工具只会解析一次sourcemap。那我们该怎么办呢?react和react-dom这两个包不打包就可以了。如果没有webpack打包,就没有webpack生成的sourcemap,只会映射到React的原始源代码一次。那为什么不把这两个模块打包呢?webpack支持externals配置一些模块使用全局变量而不打包,这样我们就可以分别加载react和react-dom,然后配置它们导出的全局变量给externals。要更改webpack配置,请在create-react-app下执行npmruneject。然后项目下会多出config目录和public目录,分别包含webpack的配置和一些public文件。修改webpack配置,在externals下添加react和react-dom包对应的全局变量:然后将react.development.js和react-dom.development.js放在public下,在index.html中加载这两个文件:在这个way,再debug一下,会发现sourcemap映射到了React原来的源码:不再是react-dom.development.js下的代码,而是具体的react-xxx包下的。这样就达到了最初的目的,可以直接调试React的原始源码!还记得我们这样做的意思吗?只有能够调试原始源码,才能知道是哪个包里有哪一段逻辑,否则就得自己去寻找了。这样已经达到了我们的目的,但是如果我们想点击调用栈直接定位到gitclone下载的react项目的文件,还需要再做一步。关联react源码工程,看看我初步演示的效果。点击调用栈可以直接定位到react源码项目的文件:这是怎么做到的?其实只要sourcemap生效,并且映射的文件在当前工作空间,VSCode就会打开对应的文件。现在sourcemap已经生效,但是react工程不在workspace下。所以,如果想直接定位react源码工程,可以这样做:新建一个目录,将react源码工程和测试工程放在一个workspace下,这样再次调试时,映射的文件就可以了在工作区找到,对应的文件就会打开。只是现在sourcemap下的相对路径都是这样的相对路径,会导致映射错误的文件路径:所以再次修改react构建过程,在./script/rollup/build.js下添加一个sourcemap路径映射,并且把../../../packages映射到react项目的绝对路径/pcagges:这时候重新build,生成的sourcemap是绝对路径:复制新生成的sourcemap覆盖.在新的工作区调试,会发现路径映射是正确的:点击调用栈,直接打开react源码工程对应的文件!至此,我们就可以优雅地调试React的原始源码了。总结React用久了,自然会想调试源码更深入,但是常规的调试方式只能调试react-dom.development.js。虽然可以理清逻辑,但是不能对应源码中的哪些包哪些文件,总觉得和原来的源码还是有距离的。这个问题是有解决办法的,但是会有点复杂:首先下载react源码工程,修改build过程用sourcemap生成react和react-dom包,修改sourcemap映射的路径为绝对路径小路。然后将react和react-dom配置到webpack的externals中,不打包,而是在index.html中分别导入。因为sourcemap只会映射一次,而webpack已经生成了一次sourcemap,所以只有跳过这两个模块的打包,react和react-dom的sourcemap才能生效。使用VSCodeDebugger调试React项目后,可以映射到原始React源码。如果想点击调用堆栈直接打开React源码工程对应的文件,那么新建一个工作区,将测试工程和React源码工程包含进去。因为如果VSCode在工作空间下的sourcemap中找到文件,它会直接打开对应的文件。东东:最后调试效果很完美,就是过程有点复杂。I:确实,能调试原始源码,直接打开对应的react源码工程文件,还是挺麻烦的,不过还好,只需要配置一次,以后可以一直用,类似的源码调试方法也可以应用到其他源码调试中。毫不夸张的说,这应该是全网最优雅的React源码调试方式了。
