您现在的位置是:首页 >学无止境 >【原理分析】React 通过页面直接打开vscode并定位到具体源代码网站首页学无止境
【原理分析】React 通过页面直接打开vscode并定位到具体源代码
简介【原理分析】React 通过页面直接打开vscode并定位到具体源代码
效果如下
原理
低于react19以下react官方会在每个fiber节点都生成一个ReactFilerXxxx属性
可以看到里面属性有debugSource就会定位到具体文件 我们只需要使用vscode的协议open即可
例如vscode://file/xxx/xxx
上图可以看到还有_debugOwner也就是上级元素,结构也是个fiberNode,我们就可以一直往上层找将所有的父级也一并找出来。
伪代码如下
const findParentNodeFileAttr = (node: HTMLElement) => {
const list: FiberNode[] = [];
let _node: HTMLElement | null = node;
let attr = findReactFiberAttr(_node);
while (
!attr &&
_node &&
_node.parentElement !== body &&
_node !== body &&
!rootDom.contains(_node) &&
rootDom !== _node
) {
_node = _node.parentElement;
if (_node) {
attr = findReactFiberAttr(_node);
}
}
if (attr) list.push(attr);
while (attr?._debugOwner) {
list.push(attr._debugOwner);
attr = attr._debugOwner;
}
(window as any).react_find_attr_list = list;
return { list, attr, _node, currentNode: node };
};
const findParentNodeFileAttr = (node: HTMLElement) => {
const list: FiberNode[] = [];
let _node: HTMLElement | null = node;
let attr = findReactFiberAttr(_node);
while (
!attr &&
_node &&
_node.parentElement !== body
) {
_node = _node.parentElement;
if (_node) {
attr = findReactFiberAttr(_node);
}
}
if (attr) list.push(attr);
while (attr?._debugOwner) {
list.push(attr._debugOwner);
attr = attr._debugOwner;
}
(window as any).react_find_attr_list = list;
return { list, attr, _node, currentNode: node };
};
react19.x中_debugSource属性被删除了
issue如下:
https://github.com/facebook/react/issues/31981
迫使开发者需要使用插件的方式来对每个dom进行源码位置定位,我们需要借助babel来生成ast,并且在每个元素加入一个源码路径的属性例如source-file-path
例如vite插件的写法如下
import type { Plugin } from 'vite';
import { NodePath, transformSync, types } from '@babel/core';
import type { JSXOpeningElement } from '@babel/types';
export default function reactSourcePlugin(): Plugin {
return {
enforce: 'pre',
name: 'vite-plugin-react-source',
transform(code: string, id: string) {
if (!id.match(/.[jt]sx$/)) return null;
const result = transformSync(code, {
filename: id,
presets: [['@babel/preset-react', { runtime: 'automatic' }], '@babel/preset-typescript'],
plugins: [
function sourceAttributePlugin() {
return {
name: 'source-attribute',
visitor: {
JSXOpeningElement(path: NodePath<JSXOpeningElement>) {
const loc = path.node.loc;
if (!loc) return;
path.node.attributes.push(
types.jsxAttribute(
types.jsxIdentifier('source-file-path'),
types.stringLiteral(`${id}:${loc.start.line}`),
),
);
},
},
};
},
],
ast: true,
sourceMaps: true,
configFile: false,
babelrc: false,
});
if (!result?.code) return null;
return {
code: result.code,
map: result.map,
};
},
};
}
webpack则可以使用loader的方式,代码类似
import type { LoaderContext } from 'webpack';
import { transformSync, types } from '@babel/core';
import type { NodePath } from '@babel/core';
import type { JSXOpeningElement } from '@babel/types';
import type { RawSourceMap } from 'source-map';
function transformCode(code: string, id: string) {
const result = transformSync(code, {
filename: id,
presets: [['@babel/preset-react', { runtime: 'automatic' }], '@babel/preset-typescript'],
plugins: [
function sourceAttributePlugin() {
return {
name: 'source-attribute',
visitor: {
JSXOpeningElement(path: NodePath<JSXOpeningElement>) {
const loc = path.node.loc;
if (!loc) return;
path.node.attributes.push(
types.jsxAttribute(
types.jsxIdentifier('source-file-path'),
types.stringLiteral(`${id}:${loc.start.line}`),
),
);
},
},
};
},
],
ast: true,
sourceMaps: true,
configFile: false,
babelrc: false,
});
if (!result?.code) return null;
return {
code: result.code,
map: result.map as RawSourceMap | undefined,
};
}
export default function loader(this: LoaderContext<any>, source: string) {
const callback = this.async();
const filename = this.resourcePath;
try {
const result = transformCode(source, filename);
if (!result) {
callback(null, source);
return;
}
callback(null, result.code, result.map);
} catch (err) {
callback(err as Error);
}
}
使用方法如下
{
test: /.(ts|tsx|js|jsx)$/,
exclude: /node_modules/,
use: [
{
loader: 'babel-loader',
options: {
presets: [
'@babel/preset-env',
'@babel/preset-react',
'@babel/preset-typescript'
]
}
},{
loader: require.resolve('react-find/webpack/webpack-react-source-loader')
}
]
}
最后
欢迎使用npm包,支持react17.x 18.x 19.x,nextjs,欢迎多多交流。
风语者!平时喜欢研究各种技术,目前在从事后端开发工作,热爱生活、热爱工作。