Skip to content

本文由 简悦 SimpRead 转码, 原文地址 mp.weixin.qq.com

在开发过程中,尤其是当我们面对一个陌生的模块或需要修改他人编写的项目时,往往会遇到理解代码逻辑困难、定位问题耗时等挑战,那今天推荐的这个工具可以帮助我们高效应对,短时间内快速定位到关键代码,辅助我们迅速搞清楚代码的结构与流程,从而节省我们在理解代码和定位问题上的时间。

工具介绍

code-inspector-plugin 是一款开源的基于 webpack/vite/rspack/nextjs/nuxt/umijs plugin 的提升开发效率的工具,点击页面上的 DOM,它能够自动打开你的 IDE 并将光标定位到 DOM 对应的源代码位置。

优点

开发提效

只需点击页面上的 DOM 元素,IDE 自动打开并精准定位到对应的源码位置,让开发、调试效率成倍提升!

简单易用

无需侵入源码,仅需引入打包工具即可轻松启用,接入过程快如闪电。

适配性强

  • 全面兼容构建工具:支持 webpack、vite、rspack、rsbuild、esbuild、farm、nextjs、nuxt、umijs 等主流工具。

  • 多框架支持:完美适配 vue、react、preact、solid、qwik、svelte、astro 等前端框架。

  • 智能 IDE 识别:兼容 vscode、webstorm、atom、hbuilderX、PhpStorm、Pycharm、IntelliJ IDEA 等多款 IDE。

  • 环境自动识别:仅在开发环境中生效,不影响生产环境,安全又高效。

用它,开发工作从此快人一步!

使用

安装

npm install code-inspector-plugin -D

yarn add code-inspector-plugin -D

pnpm add code-inspector-plugin -D

配置

以 webpack 项目为例

const { codeInspectorPlugin } = require('code-inspector-plugin');module.exports = () => ({  plugins: [    codeInspectorPlugin({      bundler: 'webpack',    }),  ],});

使用

目前使用 DOM 源码定位功能的方式有两种:

方式一(推荐)

按住快捷键(Mac 默认 Option + Shift,Windows Alt + Shift )可以查看控制台提示:

  1. 鼠标悬停时页面元素会被高亮遮罩,并显示信息。

  2. 点击元素,IDE 自动打开并定位到对应代码位置。

方式二

启用页面上的 代码审查开关按钮(需配置 showSwitch: true):

点击按钮切换模式:

  • 彩色开关:代码审查模式开启,操作同方式一。

  • 黑白开关:模式关闭

详见官网:https://inspector.fe-dev.cn/guide/start.html

实现原理

实现思路

  1. 参与源码编译:
  • 在使用如 Webpack、Vite 或 Rspack 等打包工具进行编译时,code-inspector-plugin 插件会加入编译流程。

  • 插件会对 Vue 或 JSX 代码进行 AST 分析,从中提取出 DOM 元素的源代码信息(包括文件路径、行号和列号),并将这些信息作为额外的属性添加到 DOM 元素中。

  1. 前端交互逻辑
  • 一旦编译完成,插件会将一些交互功能嵌入网页,监听用户点击事件。

  • 当用户点击某个 DOM 元素时,插件会从该元素的属性中提取文件路径、行号和列号,并通过 HTTP 请求将这些信息发送到后端服务器。

  1. 后台服务处理请求
  • 后端启动一个 Node.js 服务,接收前端发送的 HTTP 请求。

  • 接收到请求后,服务会根据文件路径、行号和列号打开相关的 IDE,并将光标定位到正确的位置。

  1. 自动打开 IDE 并定位源代码
  • 后端服务利用 Node.js 的 spawn 或 exec 方法,启动 IDE,并将其定位到相应的源代码位置,帮助开发者快速修复或查看代码。

源码解析

接下来,根据作者的实现思路,来看看代码是如何实现的吧!

参与源码编译

入口

根据 bundler配置的打包工具参数(Vite、Webpack、Rspack、Esbuild)加载相应插件,并通过 .env.local 文件和 CODE_INSPECTOR 环境变量控制是否启用。

import { ViteCodeInspectorPlugin } from 'vite-code-inspector-plugin';import WebpackCodeInspectorPlugin from 'webpack-code-inspector-plugin';import { EsbuildCodeInspectorPlugin } from 'esbuild-code-inspector-plugin';export function CodeInspectorPlugin(options: CodeInspectorPluginOptions): any {  // 没有 bundler 参数,报错  if (!options?.bundler) {    console.log(      chalk.red(        'Please specify the bundler in the options of code-inspector-plugin.'      )    );    return;  }    ...    if (options.bundler === 'webpack' || options.bundler === 'rspack') {    // 使用 webpack 插件    return new WebpackCodeInspectorPlugin(params);  } else if (options.bundler === 'esbuild') {    return EsbuildCodeInspectorPlugin(params);  } else {    // 使用 vite 插件    return ViteCodeInspectorPlugin(params);  }}export const codeInspectorPlugin = CodeInspectorPlugin;

插件内部逻辑

以 webpack-plugin 为例,在 Webpack 构建中通过自定义 loader.jsinject-loader.js 来注入源代码信息:

applyLoader:添加自定义 loader 和 inject-loader 到 Webpack 配置。

WebpackCodeInspectorPlugin:检查是否处于开发环境,在开发模式下注册 loader, 这里判断了仅在开发环境下生效,有效降低用户接入的心智。

const applyLoader = (options: LoaderOptions, compiler: any) => {      if (!isFirstLoad) {        return;      }      isFirstLoad = false;      // 适配 webpack 各个版本      const _compiler = compiler?.compiler || compiler;      const module = _compiler?.options?.module;      const rules = module?.rules || module?.loaders || [];      rules.push(        {          test: options?.match ?? /.(vue|jsx|tsx|js|ts|mjs|mts)$/,          exclude: /node_modules/,          use: [            {              loader: path.resolve(compatibleDirname, `./loader.js`),              options,            },          ],          ...(options.enforcePre === false ? {} : { enforce: 'pre' }), // 默认指定当前插件优先执行        },        {          ...(options?.injectTo            ? { resource: options?.injectTo }            : {                test: /.(jsx|tsx|js|ts|mjs|mts)$/,                exclude: /node_modules/,              }),          use: [            {              loader: path.resolve(compatibleDirname, `./inject-loader.js`),              options,            },          ],          enforce: 'post',        }      );    }    class WebpackCodeInspectorPlugin {      options: Options;      constructor(options: Options) {        this.options = options;      }      apply(compiler) {              // 自定义 dev 环境判断        let isDev: boolean;        if (typeof this.options?.dev === 'function') {          isDev = this.options?.dev();        } else {          isDev = this.options?.dev;        }        if (isDev === false) {          return;        }        // 仅在开发环境下使用        if (          !isDev &&          compiler?.options?.mode !== 'development' &&          process.env.NODE_ENV !== 'development'        ) {          return;        }        ...        applyLoader({ ...this.options, record }, compiler);      }    }    export default WebpackCodeInspectorPlugin;

两个 loader 内部逻辑

第一个 loader

处理不同类型的文件(Vue、JSX、Svelte),使用 transformCode 方法根据文件类型对内容进行转换。transformCode 会调用不同的转换函数(transformVuetransformJsxtransformSvelte)处理不同的语法。

export default async function WebpackCodeInspectorLoader(content: string) {  // jsx 语法  const isJSX =    isJsTypeFile(filePath) ||    (filePath.endsWith('.vue') &&      jsxParamList.some((param) => params.get(param) !== null));  if (isJSX) {    return transformCode({ content, filePath, fileType: 'jsx', escapeTags });  }  // vue jsx  const isJsxWithScript =    filePath.endsWith('.vue') &&    (params.get('lang') === 'tsx' || params.get('lang') === 'jsx');  if (isJsxWithScript) {    const { descriptor } = parseSFC(content, {      sourceMap: false,    });    // 处理 <script> 标签内容    // 注意:.vue 允许同时存在 <script> 和 <script setup>    const scripts = [      descriptor.script?.content,      descriptor.scriptSetup?.content,    ];    for (const script of scripts) {      if (!script) continue;      const newScript = transformCode({        content: script,        filePath,        fileType: 'jsx',        escapeTags,      });      content = content.replace(script, newScript);    }    return content;  }  // vue  const isVue =    filePath.endsWith('.vue') &&    params.get('type') !== 'style' &&    params.get('type') !== 'script' &&    params.get('raw') === null;  if (isVue) {    return transformCode({ content, filePath, fileType: 'vue', escapeTags });  }  // svelte  const isSvelte = filePath.endsWith('.svelte');  if (isSvelte) {    return transformCode({ content, filePath, fileType: 'svelte', escapeTags });  }  return content;}

transformCode 函数根据 fileType 调用对应的转换函数 (transformVue, transformJsx, transformSvelte)。

import { transformJsx } from './transform-jsx';import { transformSvelte } from './transform-svelte';import { transformVue } from './transform-vue';export function transformCode(params: TransformCodeParams) {  const { content, filePath, fileType, escapeTags = [] } = params;  const finalEscapeTags = [    ...CodeInspectorEscapeTags,    ...escapeTags,  ];  try {    if (fileType === 'vue') {      return transformVue(content, filePath, finalEscapeTags);    } else if (fileType === 'jsx') {      return transformJsx(content, filePath, finalEscapeTags);    } else if (fileType === 'svelte') {       return transformSvelte(content, filePath, finalEscapeTags);    } else {      return content;    }  } catch (error) {    return content;  }}

以编译 vue 语法为例(transformVue 方法) 使用 vue 内置的包 @vue/compiler-dom的 parse 将模板转换为 AST, transform 是在 AST 上进行加工,以及通过 magic-string 包来向 AST 中注入:路径名称 (data-insp-path) =  路径: 行: 列: html 标签。

import MagicString from 'magic-string';import type {  TemplateChildNode,  NodeTransform,  ElementNode,} from '@vue/compiler-dom';import { parse, transform } from '@vue/compiler-dom';export function transformVue(  content: string,  filePath: string,  escapeTags: EscapeTags) {  const s = new MagicString(content);  // parse 方法解析vue 模版为ast 对象  const ast = parse(content, {    comments: true,  });    ...  if (pugMap.has(filePath) && templateNode) {    ...  } else {    transform(ast, {      nodeTransforms: [        ((node: TemplateChildNode) => {          if (            !node.loc.source.includes(PathName) &&            node.type === VueElementType &&            !isEscapeTags(escapeTags, node.tag)          ) {            // 向 dom 上添加一个带有 filepath/row/column 的属性            const insertPosition = node.loc.start.offset + node.tag.length + 1;            const { line, column } = node.loc.start;            // 规则: 注入的路径名称data-insp-path = 路径:行:列:html标签            const addition = ` ${PathName}="${filePath}:${line}:${column}:${              node.tag            }"${node.props.length ? ' ' : ''}`;            s.prependLeft(insertPosition, addition);          }        }) as NodeTransform,      ],    });  }  return s.toString();}

上面 vue/jsx 编译完成后,其实相当于在源代码基础上为每个 dom 注入了一个 data-insp-path 属性,最终元素到页面上,对应的 dom 就会添加一个这样的属性,如下图所示:

第二个 loader(inject-loader)

启用 node 服务监听打开 vscode,把页面交互代码注入到入口文件里面。

通过该 loader,使用 web component 组件在开发环境注入到页面中,简化用户的使用,不需要用户手动向页面中添加交互逻辑的组件。

import { normalizePath, getCodeWithWebComponent } from 'code-inspector-core';export default async function WebpackCodeInjectLoader(      content: string,      source: any,      meta: any    ) {      this.async();      this.cacheable && this.cacheable(true);      const filePath = normalizePath(this.resourcePath); // 当前文件的绝对路径      const options = this.query;      // start server and inject client code to entry file      content = await getCodeWithWebComponent(options, filePath, content, options.record);      this.callback(null, content, source, meta);    }

getCodeWithWebComponent 方法内部核心调用了 getWebComponentCode,将打包的自定义组件代码 client.umd.js 注入到入口 js 文件中,在加载入口 js 时立即执行,注入到页面中。

import { startServer } from './server';let compatibleDirname = '';if (typeof __dirname !== 'undefined') {  compatibleDirname = __dirname;} else {  compatibleDirname = dirname(fileURLToPath(import.meta.url));}// 这个路径是根据打包后来的,client.umd.js 是啥,后面说export const clientJsPath = path.resolve(compatibleDirname, './client.umd.js');const jsClientCode = fs.readFileSync(clientJsPath, 'utf-8');export function getInjectedCode(options: CodeOptions, port: number) {  let code = `'use client';`;  code += getEliminateWarningCode();  if (options?.hideDomPathAttr) {    code += getHidePathAttrCode();  }  code += getWebComponentCode(options, port);  return `/* eslint-disable */\n` + code.replace(/\n/g, '');}export function getWebComponentCode(options: CodeOptions, port: number) {  const {    hotKeys = ['shiftKey', 'altKey'],    showSwitch = false,    hideConsole = false,    autoToggle = true,    behavior = {},    ip = false,  } = options || ({} as CodeOptions);  const { locate = true, copy = false } = behavior;  return `;(function (){  if (typeof window !== 'undefined') {    if (!document.documentElement.querySelector('code-inspector-component')) {      var script = document.createElement('script');      script.setAttribute('type', 'text/javascript');      script.textContent = ${`${jsClientCode}`};        var inspector = document.createElement('code-inspector-component');      inspector.port = ${port};      inspector.hotKeys = '${(hotKeys ? hotKeys : [])?.join(',')}';      inspector.showSwitch = !!${showSwitch};      inspector.autoToggle = !!${autoToggle};      inspector.hideConsole = !!${hideConsole};      inspector.locate = !!${locate};      inspector.copy = ${typeof copy === 'string' ? `'${copy}'` : !!copy};      inspector.ip = '${getIP(ip)}';      document.documentElement.append(inspector); // 将自定义组件插入到页面上    }  }})();`;}export async function getCodeWithWebComponent(  options: CodeOptions,  file: string,  code: string,  record: RecordInfo) {  // start server  await startServer(options, record);  recordEntry(record, file);  // 判断js文件,且是入口js文件,注入代码  if (    (isJsTypeFile(file) && getFilePathWithoutExt(file) === record.entry) ||    file === AstroToolbarFile  ) {      ...    } else {      code = `${injectCode};${code}`;    }  }  return code;}

经过这一步,把自定义组件代码 注入到了 app.js 中,在页面加载之后,我们可以看到页面上 被注入了 shadow dom 自定义组件

client.umd.js 是谁打包后的产物呢?

通过这个 vite 配置看出,它指定了入口文件为 src/client/index.ts,输出文件名为 client,并将库的全局变量名设置为 vueInspectorClient

import { defineConfig } from 'vite';import { terser } from 'rollup-plugin-terser';// https://vitejs.dev/config/export default defineConfig({  build: {    lib: {      entry: ['src/client/index.ts'],      formats: ['umd'],      fileName: 'client',      name: 'vueInspectorClient',    },    minify: true,    emptyOutDir: false,    target: ['node8', 'es2015'],  },  plugins: [    // @ts-ignore    terser({      format: {        comments: false      }    })  ]});

接下来我们再来看src/client/index.ts的代码逻辑。

前端交互逻辑

code-inspector-plugin 插件的交互功能主要包含监听两部分:

  • 监听组合键按住时,鼠标在 dom 上移动时会出现 DOM 遮罩层信息

  • 点击遮罩层会获取 DOM attribute 上的源代码信息,向后台发送一个请求

自定义组件

实现了一个基于 LitElement 的开发者工具组件,主要功能是通过快捷键和鼠标交互快速定位 DOM 元素,并支持在代码编辑器(如 VS Code)中打开对应的源文件。核心功能包括:

  • 热键功能:组件监听用户按下特定的热键(如 shiftKey, altKey),并在按下热键时启用代码检查功能。

  • 元素定位:当鼠标悬停在页面元素上时,组件会显示该元素的详细信息,如路径、行号、列号等。如果用户点击该元素,会打开 VSCode 或者复制代码路径到剪贴板。

  • UI 控制:有一个开关按钮,用来开启和关闭功能。开关的位置支持拖动,用户可以自由移动它。

  • 样式和事件处理:组件在显示时会加入遮罩层,阻止元素选择(userSelect: 'none'),并通过 HTTP 请求或者 img 请求方式来通知本地服务端打开 VSCode。

import { LitElement, css, html } from 'lit';export class CodeInspectorComponent extends LitElement {  @property()  hotKeys: string = 'shiftKey,altKey';  @property()  port: number = DefaultPort;  ...  isTracking = (e: any) => {    return (      this.hotKeys && this.hotKeys.split(',').every((key) => e[key.trim()])    );  };  // 渲染遮罩层  renderCover = (target: HTMLElement) => {    // 设置 target 的位置    const { top, right, bottom, left } = target.getBoundingClientRect();    this.position = {      top,      right,      bottom,      left,      ...    };    ...        // 增加鼠标光标样式    this.addGlobalCursorStyle();    // 防止 select    if (!this.preUserSelect) {      this.preUserSelect = getComputedStyle(document.body).userSelect;    }    document.body.style.userSelect = 'none';    // 获取元素信息    let paths = target.getAttribute(PathName) || (target as CodeInspectorHtmlElement)[PathName] || '';       if (!paths && target.getAttribute('data-astro-source-file')) {      paths = `${target.getAttribute(        'data-astro-source-file'      )}:${target.getAttribute(        'data-astro-source-loc'      )}:${target.tagName.toLowerCase()}`;    }    const segments = paths.split(':');    const name = segments[segments.length - 1];    const column = Number(segments[segments.length - 2]);    const line = Number(segments[segments.length - 3]);    const path = segments.slice(0, segments.length - 3).join(':');    this.element = { name, path, line, column };    this.show = true;  };  removeCover = () => {    ...  };  sendXHR = () => {    const file = encodeURIComponent(this.element.path);    const url = `http://${this.ip}:${this.port}/?file=${file}&line=${this.element.line}&column=${this.element.column}`;    const xhr = new XMLHttpRequest();    xhr.open('GET', url, true);    xhr.send();    xhr.addEventListener('error', () => {      this.sendType = 'img';      this.sendImg();    });  };  // 通过img方式发送请求,防止类似企业微信侧边栏等内置浏览器拦截逻辑  sendImg = () => {    const file = encodeURIComponent(this.element.path);    const url = `http://${this.ip}:${this.port}/?file=${file}&line=${this.element.line}&column=${this.element.column}`;    const img = document.createElement('img');    img.src = url;  };  // 请求本地服务端,打开vscode  trackCode = () => {    if (this.locate) {      if (this.sendType === 'xhr') {        this.sendXHR();      } else {        this.sendImg();      }    }    if (this.copy) {      ...    }  };  copyToClipboard(text: string) {    ...  }  // 移动按钮  moveSwitch = (e: MouseEvent) => {    ...  };  handleMouseup = () => {    this.hoverSwitch = false;  };  // 鼠标移动渲染遮罩层位置  handleMouseMove = (e: MouseEvent) => {    ...  };  // 鼠标点击唤醒遮罩层  handleMouseClick = (e: any) => {    if (this.isTracking(e) || this.open) {      if (this.show) {        e.stopPropagation();        e.preventDefault();        // 唤醒 vscode        this.trackCode();        // 清除遮罩层        this.removeCover();        if (this.autoToggle) {          this.open = false;        }      }    }  };  // disabled 无法触发 click 事件  handlePointerDown = (e: any) => {    ...  };  // 监听键盘抬起,清除遮罩层  handleKeyUp = (e: any) => {    if (!this.isTracking(e) && !this.open) {      this.removeCover();    }  };  // 记录鼠标按下时初始位置  recordMousePosition = (e: MouseEvent) => {    this.mousePosition = {      baseX: this.inspectorSwitchRef.offsetLeft,      baseY: this.inspectorSwitchRef.offsetTop,      moveX: e.pageX,      moveY: e.pageY,    };    this.dragging = true;    e.preventDefault();  };  // 结束拖拽  handleMouseUp = () => {    this.dragging = false;  };  // 切换开关  switch = (e: Event) => {    if (!this.moved) {      this.open = !this.open;      e.preventDefault();      e.stopPropagation();    }    this.moved = false;  };  protected firstUpdated(): void {    if (!this.hideConsole) {      this.printTip();    }        ...    window.addEventListener('click', this.handleMouseClick, true);    window.addEventListener('pointerdown', this.handlePointerDown, true);    document.addEventListener('keyup', this.handleKeyUp);    document.addEventListener('mouseleave', this.removeCover);    document.addEventListener('mouseup', this.handleMouseUp);    this.inspectorSwitchRef.addEventListener(      'mousedown',      this.recordMousePosition    );    this.inspectorSwitchRef.addEventListener('click', this.switch);  }  disconnectedCallback(): void {    window.removeEventListener('mousemove', this.handleMouseMove);    window.removeEventListener('mousemove', this.moveSwitch);    ...  }  render() {    const containerPosition = {      display: this.show ? 'block' : 'none',      top: `${this.position.top - this.position.margin.top}px`,      left: `${this.position.left - this.position.margin.left}px`,      ...    };    ...    return html`      <div        class="code-inspector-container"        id="code-inspector-container"        style=${styleMap(containerPosition)}      >      ...        <div          id="element-info"          class="element-info ${this.infoClassName.vertical}${this            .infoClassName.horizon}"          style=${styleMap({ width: this.infoWidth })}        >          <div class="element-info-content">            <div class="name-line">              <div class="element-name">                <span class="element-title"><${this.element.name}></span>                <span class="element-tip">click to open IDE</span>              </div>            </div>            <div class="path-line">${this.element.path}</div>          </div>        </div>      </div>      <div        id="inspector-switch"        class="inspector-switch ${this.open          ? 'active-inspector-switch'          : ''}${this.moved ? 'move-inspector-switch' : ''}"        style=${styleMap({ display: this.showSwitch ? 'flex' : 'none' })}      >        ${this.open          ? html`              <svg                t="1677801709811"                class="icon"                viewBox="0 0 1024 1024"                version="1.1"                xmlns="http://www.w3.org/2000/svg"                p-id="1110"                xmlns:xlink="http://www.w3.org/1999/xlink"                width="1em"                height="1em"              >              ...            </svg>`}      </div>    `;  }  ...

后台服务处理请求

实现了一个 HTTP 服务器,通过接收到的请求打开指定的代码文件并跳转到某行某列。具体功能如下:

createServer:创建一个 HTTP 服务器,监听请求,解析请求中的 file(文件路径)、line(行号)、column(列号)参数,打开文件并跳转到指定位置。

startServer:检查是否已有端口信息(record.port)。如果没有,它会调用 createServer 来获取一个可用端口并启动服务器。

// 启动本地接口,访问时唤起vscodeimport http from 'http';import portFinder from 'portfinder';import launchEditor from './launch-editor';export function createServer(callback: (port: number) => any, options?: CodeOptions) {  const server = http.createServer((req: any, res: any) => {    // 收到请求唤醒vscode    const params = new URLSearchParams(req.url.slice(1));    const file = decodeURIComponent(params.get('file') as string);    const line = Number(params.get('line'));    const column = Number(params.get('column'));    res.writeHead(200, {      'Access-Control-Allow-Origin': '*',      'Access-Control-Allow-Methods': '*',      'Access-Control-Allow-Headers': '*',      'Access-Control-Allow-Private-Network': 'true',    });    res.end('ok');    // 调用 hooks    options?.hooks?.afterInspectRequest?.(options, { file, line, column });    // 打开 IDE    launchEditor(file, line, column, options?.editor, options?.openIn, options?.pathFormat);      });  // 寻找可用接口  portFinder.getPort({ port: DefaultPort }, (err: Error, port: number) => {    if (err) {      throw err;    }    server.listen(port, () => {      callback(port);    });  });}export async function startServer(options: CodeOptions, record: RecordInfo) {  if (!record.port) {    if (!record.findPort) {      record.findPort = new Promise((resolve) => {        createServer((port: number) => {          resolve(port);        }, options);      });    }    record.port = await record.findPort;  }}

自动打开 IDE 并定位源代码

这里源码就不展示了,感兴趣的可以自己去看看源码。

源码核心解决了 2 个问题:

  1. 如何打开用户的 IDE 并定位到源代码?

默认方式:通过安装 通过 node 的 spwan 或者 exec 启动一个子进程,执行 code -g 文件路径:行:列 打开 vscode 并定位到对应的文件路径、行、列位置。

自定义编辑器路径方式: 通过 {IDE路径} -g {path}:{line}:{column}打开并定位编辑器。

  1. 用户系统安装了多种 IDE,如何智能地启动适合的代码编辑器?

匹配用户当前设备上正在运行的进程,在 IDE 列表中匹配打开,同时针对 web 项目,设置了优先匹配 vscode、 webstorm。

使用中遇到的问题

1、按住快捷键点击页面发送请求了,但是 vscode 没被打开

要将 code 命令添加到 PATH 中,以便可以从终端启动 VS Code

解决:cmd+shfit+p 选择 shell 命令: 在 path 中安装 code 命令

2、开发环境 code-inspector-plugin 在安卓低端机上会报 globalThis is not defined, 这个文件报的 append-code-${port}.js

解决:找源码作者兼容处理了一下,升级最新包即可。

3、默认 enforcePre 为 true 导致 eslint-plugin 校验错误

解决:设置 enforcePre:false。

  1. 移动端项目使用开关方式,体验不佳

解决:使用最新包即可,已推源码作者 增加了移动端的交互体验,目前已支持开关位置、拖拽;在移动端开发环境,「长按」可以展示对应的组件,「点击」能打开对应的 vscode 组件。

参考

官网:https://inspector.fe-dev.cn/guide/start.html

掘金原文:https://juejin.cn/post/7326002010084311079

github 源码:https://github.com/zh-lx/code-inspector

想了解更多转转公司的业务实践,点击关注下方的公众号吧!