Intro

In my project, users can create multiple types of cards, and the title and content of the cards are configurable by users. The different types of cards look basically the same. The problem is that as the project code gets bigger and bigger, it becomes more expensive to find the source code.

I believe that many front-end development students will encounter this problem. Imagine that a user reports an urgent online problem and you open the code repository with VSCode. How can you quickly find the source file for the component in question because it happens to be a module that you didn’t develop?

This is what I used to do: find key information (Element, class, Attributes, ID, etc.) by looking at some relatively fixed text, and then search globally in the code. To be honest, this is not a very friendly way to experience, often found a dozen code, in order to determine the location of the code, usually need to add debugger/console to confirm, wasted a lot of time.

So I wonder if I can use the ability of dev-server to improve the efficiency of source location — to achieve clicking on a page element, automatically open the corresponding source code of VSCode, just recently in contact with vite, can practice the development of vite plug-in.

At the time I was excited about the idea, but the next thing Google noticed was that there was already a related NPM library called React-dev-Inspector.

As shown in the GIF above, after pressing the shortcut key to turn on the switch and clicking on the page element, VSCode will automatically open the corresponding code location. There’s no need to reinvent the wheel now that it’s already been implemented. In the next section, we’ll look into the react-dev-Inspector source code to see how the tool works (with the Vite plugin).

Deep into the react – dev – inspector

How to use it?

To use the React-dev-Inspector in a Vite project, you first need to install the NPM package.

npm i react-dev-inspector -D
Copy the code

The next step is to introduce the inspectorServer plug-in in the viet.config. js file.

import { inspectorServer } from 'react-dev-inspector/plugins/vite'


export default defineConfig({
 / /...
 plugins: [
   react(),
   inspectorServer()
 ],
 / /...
})
Copy the code

Then you need to wrap the entire App with the Inspector component exported from the package. Since this function is not required in the formal environment, you can use process.env.NODE_ENV to render the App as a Fragment component in the formal environment.

import { Inspector } from 'react-dev-inspector'

const InspectorWrapper =
 process.env.NODE_ENV === 'development' ? Inspector : React.Fragment

<InspectorWrapper
 keys={['shift'.'command'.'i']}
 disableLaunchEditor={false}
>
 <App />
</InspectorWrapper>
Copy the code

When finished, press the shortcut key on the page to turn on the switch. Clicking on the page element editor will open the source code automatically. There are not many codes in the whole package, which are mainly divided into front-end React component and Node middleware. Since the Node middleware code is relatively simple, the code of Node end will be studied first.

1. The Node middleware

Look at the inspectorServer method code, which defines two middleware, queryParserMiddleware and launchEditorMiddleware, using the configureServer hook provided by Vite.

QueryParserMiddleware and launchEditorMiddleware

export const inspectorServer = (): Plugin= > ({
 name: 'inspector-server-plugin'.configureServer(server) {
    server.middlewares.use(queryParserMiddleware)
    server.middlewares.use(launchEditorMiddleware)
 },
})
Copy the code

QueryParserMiddleware simply converts the parameters of the request URL into an object that can be mounted to req.Query, which is simpler and not expanded here. LaunchEditorMiddleware, however, uses the errorOverlayMiddleware provided by React-dev-utils without the compatibility code.

import createReactLaunchEditorMiddleware from 'react-dev-utils/errorOverlayMiddleware'
import launchEditorEndpoint from 'react-dev-utils/launchEditorEndpoint'

const reactLaunchEditorMiddleware: RequestHandler = createReactLaunchEditorMiddleware()

export const launchEditorMiddleware: RequestHandler = (req, res, next) = > {
 // Determine whether it is a request for a special tag
 if (req.url.startsWith(launchEditorEndpoint)) {
     reactLaunchEditorMiddleware(req, res, next)
 } else {
     next()
 }
}
Copy the code

Wait, what is react-dev-utils?

react-dev-utils

React dev-utils is a library of tools designed specifically for Create React App. React projects created using the Create React App are integrated by default and do not require additional installation. This library provides many tools that are convenient for development environments, such as openBrowser trying to reuse Chrome’s existing tabs, choosePort trying to find a port to listen on, and so on.

ErrorOverlayMiddleware is used to call the launchEditor method in response to a specific request (the URL starting with launchEditorEndpoint). As the name suggests, this method is the caller that actually opens the editor, Inside did many different operating systems, common editor compatible work, try to find and open the editor.

Looking only at the VSCode editor on the MAC, a call to the launchEditor will eventually trigger another child process to execute the command via child_process.spawn.

child_process.spawn(editor, args, { stdio: 'inherit' });
Copy the code

To print the editor and args separately, we call the code -g –goto command to pass the line information in the specific file code. [:character]>

// editor
code

// args
[
 '-g'.'/Users/xxx/projects/ad/src/components/situation/index.tsx:156:19'
]
Copy the code

As you can see, the code on the Node side is very simple. It intercepts the request and passes the request parameters (searchParams) to VSCode. The request parameters include the specific file to open, the rows, and the columns. It’s where the information comes from that matters, and look at the front-end components.

2. Front-end components

Use hotKeys to turn the switch on and hover over the DOM element to see its box model, HTML tag, and component name (this is also implemented by the React-dev-inspector).

Gets the current mouse position of DOM elements, use DocumentOrShadowRoot. ElementFromPoint () API, this method returns the given points under the top element of the element. If the specified coordinate point is outside the visual range of the document, or if both coordinates are negative, null is returned. (You can use elementsFromPoint if you want to return multiple elements at specific coordinates.)

var element = document.elementFromPoint(x, y);
Copy the code

After retrieving the DOM element, iterate through the DOM node object to find a key starting with __reactInternalInstance$(React <= v16.13.1) or __reactFiber$. Here is a reference to the DOM node corresponding to the Fiber node inside React.

export const getElementFiber = (element: FiberHTMLElement): Fiber | undefined= > {
 const fiberKey = Object.keys(element).find(key= > (
   // React <= v16.13.1
   key.startsWith('__reactInternalInstance$')
       || key.startsWith('__reactFiber$')))return element[fiberKey] as Fiber
}
Copy the code

The precacheFiberNode code in the React source code is here.

If you get the Fiber node, you can read the _debugSource information, which is exactly the absolute path of the file that the node is in, which is the line, which is the column.

Where does the _debugSource come from? The @babel/plugin-transform-react-jsx-source plugin is used as a default for @babel/preset-react. This plugin injects __source information into JSX when the development environment parses JSX.

// input
<sometag />

// output
<sometag __source={ { fileName: 'this/file.js', lineNumber: 10.columnNumber: 1 } } />
Copy the code

__source is the special props, which is removed from the props when ReactElement is created and mounted to the _debugSource on the Fiber node. Also reserved are props, ref, and __self.

With this information in hand, all you need to do is make an HTTP request to Node dev-server.

 const launchParams = {
  fileName,
  lineNumber,
  colNumber
 }
 const apiRoute = launchEditorEndpoint

 fetch(`${apiRoute}?${queryString.stringify(launchParams)}`)
Copy the code

LaunchEditorEndpoint appears again, and launchEditorMiddleware in Node also uses the URL. StartsWith (launchEditorEndpoint) to determine whether to open an editor. It’s really just a normal string defined in react-dev-utils.

module.exports = '/__open-stack-frame-in-editor';
Copy the code

Open Chrome Dev Tools and look at the Networks Kanban, and you can see that when you click on a page element, you actually make a request.

At this point, the logic behind the implementation is clear. Review the complete link again.

  1. In a local development environment, a user can press a hotkey to openInspectorMode;
  2. useelementFromPointGet the top-level DOM node where the user mouse is hover.
  3. Traverses the properties on DOM node, corresponding Fiber node;
  4. fiberNode._debugSourcStores the data injected by the Babel plug-in__sourceInformation, splicing HTTP requests, sent to the Node end;
  5. The Node middleware intercepts the request to determine if it is a launchEditor request.
  6. LaunchEditor tries to find the editor that is allowing the project;
  7. Calls the child process to tell the editor to open row NTH, column MTH of the specified file;
  8. END

conclusion

This article analyzes the implementation principle of the react-dev-Inspector small but beautiful package, and after understanding the implementation logic behind it, I have learned a lot of new knowledge and tools, which may be used next time I have a new idea.

Space is limited, and only the implementation of the Vite plug-in and trunk is combed out, leaving the rest of the build tools and compatibility code to be explored by the reader. All in all, this package does improve development efficiency, and if you feel good about it, you can introduce/send a portal to Star ⭐️⭐️ port