chore: use react-grab to replace code-inspector-plugin (#33078)

This commit is contained in:
Stephen Zhou
2026-03-06 14:54:24 +08:00
committed by GitHub
parent e74cda6535
commit f05f0be55f
18 changed files with 250 additions and 427 deletions

View File

@ -0,0 +1,80 @@
import type { Plugin } from 'vite'
import fs from 'node:fs'
import { injectClientSnippet, normalizeViteModuleId } from './utils'
type CustomI18nHmrPluginOptions = {
injectTarget: string
}
export const customI18nHmrPlugin = ({ injectTarget }: CustomI18nHmrPluginOptions): Plugin => {
const i18nHmrClientMarker = 'custom-i18n-hmr-client'
const i18nHmrClientSnippet = `/* ${i18nHmrClientMarker} */
if (import.meta.hot) {
const getI18nUpdateTarget = (file) => {
const match = file.match(/[/\\\\]i18n[/\\\\]([^/\\\\]+)[/\\\\]([^/\\\\]+)\\.json$/)
if (!match)
return null
const [, locale, namespaceFile] = match
return { locale, namespaceFile }
}
import.meta.hot.on('i18n-update', async ({ file, content }) => {
const target = getI18nUpdateTarget(file)
if (!target)
return
const [{ getI18n }, { camelCase }] = await Promise.all([
import('react-i18next'),
import('es-toolkit/string'),
])
const i18n = getI18n()
if (!i18n)
return
if (target.locale !== i18n.language)
return
let resources
try {
resources = JSON.parse(content)
}
catch {
return
}
const namespace = camelCase(target.namespaceFile)
i18n.addResourceBundle(target.locale, namespace, resources, true, true)
i18n.emit('languageChanged', i18n.language)
})
}
`
return {
name: 'custom-i18n-hmr',
apply: 'serve',
handleHotUpdate({ file, server }) {
if (file.endsWith('.json') && file.includes('/i18n/')) {
server.ws.send({
type: 'custom',
event: 'i18n-update',
data: {
file,
content: fs.readFileSync(file, 'utf-8'),
},
})
return []
}
},
transform(code, id) {
const cleanId = normalizeViteModuleId(id)
if (cleanId !== injectTarget)
return null
const nextCode = injectClientSnippet(code, i18nHmrClientMarker, i18nHmrClientSnippet)
if (nextCode === code)
return null
return { code: nextCode, map: null }
},
}
}

View File

@ -0,0 +1,92 @@
import type { Plugin } from 'vite'
import { injectClientSnippet, normalizeViteModuleId } from './utils'
type ReactGrabOpenFilePluginOptions = {
injectTarget: string
projectRoot: string
}
export const reactGrabOpenFilePlugin = ({
injectTarget,
projectRoot,
}: ReactGrabOpenFilePluginOptions): Plugin => {
const reactGrabOpenFileClientMarker = 'react-grab-open-file-client'
const reactGrabOpenFileClientSnippet = `/* ${reactGrabOpenFileClientMarker} */
if (typeof window !== 'undefined') {
const projectRoot = ${JSON.stringify(projectRoot)};
const pluginName = 'dify-vite-open-file';
const rootRelativeSourcePathPattern = /^\\/(?!@|node_modules)(?:.+)\\.(?:[cm]?[jt]sx?|mdx?)$/;
const normalizeProjectRoot = (input) => {
return input.endsWith('/') ? input.slice(0, -1) : input;
};
const resolveFilePath = (filePath) => {
if (filePath.startsWith('/@fs/')) {
return filePath.slice('/@fs'.length);
}
if (!rootRelativeSourcePathPattern.test(filePath)) {
return filePath;
}
const normalizedProjectRoot = normalizeProjectRoot(projectRoot);
if (filePath.startsWith(normalizedProjectRoot)) {
return filePath;
}
return \`\${normalizedProjectRoot}\${filePath}\`;
};
const registerPlugin = () => {
if (window.__DIFY_REACT_GRAB_OPEN_FILE_PLUGIN_REGISTERED__) {
return;
}
const reactGrab = window.__REACT_GRAB__;
if (!reactGrab) {
return;
}
reactGrab.registerPlugin({
name: pluginName,
hooks: {
onOpenFile(filePath, lineNumber) {
const params = new URLSearchParams({
file: resolveFilePath(filePath),
column: '1',
});
if (lineNumber) {
params.set('line', String(lineNumber));
}
void fetch(\`/__open-in-editor?\${params.toString()}\`);
return true;
},
},
});
window.__DIFY_REACT_GRAB_OPEN_FILE_PLUGIN_REGISTERED__ = true;
};
registerPlugin();
window.addEventListener('react-grab:init', registerPlugin);
}
`
return {
name: 'react-grab-open-file',
apply: 'serve',
transform(code, id) {
const cleanId = normalizeViteModuleId(id)
if (cleanId !== injectTarget)
return null
const nextCode = injectClientSnippet(code, reactGrabOpenFileClientMarker, reactGrabOpenFileClientSnippet)
if (nextCode === code)
return null
return { code: nextCode, map: null }
},
}
}

20
web/plugins/vite/utils.ts Normal file
View File

@ -0,0 +1,20 @@
export const normalizeViteModuleId = (id: string): string => {
const withoutQuery = id.split('?', 1)[0]
if (withoutQuery.startsWith('/@fs/'))
return withoutQuery.slice('/@fs'.length)
return withoutQuery
}
export const injectClientSnippet = (code: string, marker: string, snippet: string): string => {
if (code.includes(marker))
return code
const useClientMatch = code.match(/(['"])use client\1;?\s*\n/)
if (!useClientMatch)
return `${snippet}\n${code}`
const insertAt = (useClientMatch.index ?? 0) + useClientMatch[0].length
return `${code.slice(0, insertAt)}\n${snippet}\n${code.slice(insertAt)}`
}