react-19-plugin-migration

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese

Migrate Grafana Plugin to React 19

将Grafana插件迁移至React 19

Grafana 13 (April 2026) moves from React 18 to React 19. Incompatible plugins will break. Do not upgrade React to 19 — only make forward-compatible changes.
All changes go in one PR. Execute steps in order. Never manually edit
yarn.lock
.

Grafana 13(2026年4月)将从React 18升级到React 19。不兼容的插件将会失效。 请勿直接将React升级到19 —— 仅需做向前兼容的修改。
所有修改需合并到一个PR中。按顺序执行步骤。禁止手动编辑
yarn.lock

Step 1: Detect plugin context

步骤1:检测插件上下文

bash
PLUGIN_JSON=$([ -f src/plugin.json ] && echo "src/plugin.json" \
  || ([ -f plugin/src/plugin.json ] && echo "plugin/src/plugin.json" || echo ""))
PKG_JSON=$([ -f package.json ] && echo "package.json" \
  || ([ -f plugin/package.json ] && echo "plugin/package.json" || echo ""))
PLUGIN_ID=$(jq -r '.id' $PLUGIN_JSON 2>/dev/null)
[ -f yarn.lock ] && PM="yarn" || ([ -f pnpm-lock.yaml ] && PM="pnpm" || PM="npm")
CP_VERSION=$(jq -r '.version' .config/.cprc.json 2>/dev/null)
echo "PLUGIN_ID=$PLUGIN_ID  PM=$PM  CP=$CP_VERSION"
If
PLUGIN_ID
is empty, ask the user for the plugin root path.

bash
PLUGIN_JSON=$([ -f src/plugin.json ] && echo "src/plugin.json" \
  || ([ -f plugin/src/plugin.json ] && echo "plugin/src/plugin.json" || echo ""))
PKG_JSON=$([ -f package.json ] && echo "package.json" \
  || ([ -f plugin/package.json ] && echo "plugin/package.json" || echo ""))
PLUGIN_ID=$(jq -r '.id' $PLUGIN_JSON 2>/dev/null)
[ -f yarn.lock ] && PM="yarn" || ([ -f pnpm-lock.yaml ] && PM="pnpm" || PM="npm")
CP_VERSION=$(jq -r '.version' .config/.cprc.json 2>/dev/null)
echo "PLUGIN_ID=$PLUGIN_ID  PM=$PM  CP=$CP_VERSION"
如果
PLUGIN_ID
为空,请询问用户插件的根路径。

Step 2: Scan for compatibility issues

步骤2:扫描兼容性问题

Build the plugin and run the React 19 compatibility scanner:
bash
npm run build 2>&1 | tail -5
npx -y @grafana/react-detect@latest 2>&1
Save the output. It flags:
  • jsxRuntimeImport
    /
    __SECRET_INTERNALS
    → Step 4 fixes this
  • defaultProps
    /
    propTypes
    /
    ReactDOM.render
    → Step 8 (source fixes)
  • findDOMNode
    → Step 6 (dependency bump) or Step 8 (source fix)
If the build fails (plugin hasn't been built before), skip this step and run react-detect after Step 9 instead. If output says "No breaking changes detected", still proceed — jsx-runtime externalization and grafanaDependency bump are always required.
Re-run react-detect after Step 9 to confirm all issues are resolved.

构建插件并运行React 19兼容性扫描工具:
bash
npm run build 2>&1 | tail -5
npx -y @grafana/react-detect@latest 2>&1
保存输出结果。它会标记以下问题:
  • jsxRuntimeImport
    /
    __SECRET_INTERNALS
    → 步骤4可修复此问题
  • defaultProps
    /
    propTypes
    /
    ReactDOM.render
    → 步骤8(源码修复)
  • findDOMNode
    → 步骤6(依赖版本提升)或步骤8(源码修复)
如果构建失败(插件从未构建过),跳过此步骤,在步骤9之后再运行react-detect。如果输出显示“No breaking changes detected”,仍需继续执行——jsx-runtime外部化和grafanaDependency版本提升是必须完成的操作。
在步骤9之后重新运行react-detect,确认所有问题已解决。

Step 3: Update
@grafana/create-plugin

步骤3:更新
@grafana/create-plugin

The scaffolding update brings in externals extraction, jest mocks, Docker fixes, and webpack improvements needed for React 19. Always do this before
add externalize-jsx-runtime
.
Requires a clean git working tree. Create a feature branch first if not already on one.
脚手架更新会引入React 19所需的外部依赖提取、jest模拟、Docker修复和webpack改进。必须在执行
add externalize-jsx-runtime
之前完成此步骤。
需要干净的git工作区。如果尚未创建特性分支,请先创建。

Run the update

运行更新命令

bash
npx @grafana/create-plugin@latest update 2>&1
bash
npx @grafana/create-plugin@latest update 2>&1

If
yarn install
fails with "engine is incompatible"

如果
yarn install
因“engine is incompatible”失败

The update runs an intermediate
yarn install
without
--ignore-engines
. Complete it manually:
bash
yarn install --ignore-scripts --ignore-engines 2>&1 | tail -10
Commit the intermediate state and re-run:
bash
git add -A && git commit -m "chore: intermediate create-plugin update" --no-verify
npx @grafana/create-plugin@latest update 2>&1
更新过程会在未添加
--ignore-engines
参数的情况下执行中间步骤
yarn install
。请手动完成:
bash
yarn install --ignore-scripts --ignore-engines 2>&1 | tail -10
提交中间状态后重新运行:
bash
git add -A && git commit -m "chore: intermediate create-plugin update" --no-verify
npx @grafana/create-plugin@latest update 2>&1

If ESLint 9 migration (004) fails with a parser error

如果ESLint 9迁移(004)因解析器错误失败

The auto-migration can generate invalid JS on plugins with complex ESLint configs. Do not skip — commit what succeeded, then complete the ESLint 9 migration manually:
bash
git add -A && git commit -m "chore: update create-plugin (ESLint 9 migration manual)" --no-verify
Then follow the "Complete ESLint 9 migration" section below to finish.
自动迁移在配置复杂的插件上可能生成无效JS代码。请勿跳过——提交已成功的部分,然后手动完成ESLint 9迁移:
bash
git add -A && git commit -m "chore: update create-plugin (ESLint 9 migration manual)" --no-verify
然后按照下方“完成ESLint 9迁移”部分的说明完成操作。

After the update

更新完成后

Always run install and verify:
bash
yarn install --ignore-scripts --ignore-engines 2>&1 | tail -10
cat .config/.cprc.json
Commit if there are changes:
bash
git add -A && git diff --cached --quiet || git commit -m "chore: update create-plugin scaffolding" --no-verify

务必执行安装并验证:
bash
yarn install --ignore-scripts --ignore-engines 2>&1 | tail -10
cat .config/.cprc.json
如有变更则提交:
bash
git add -A && git diff --cached --quiet || git commit -m "chore: update create-plugin scaffolding" --no-verify

Step 3b: Complete ESLint 9 migration

步骤3b:完成ESLint 9迁移

The
create-plugin update
bumps ESLint to v9, which requires flat config (
eslint.config.js
) instead of
.eslintrc
. Whether the auto-migration (004) succeeded, partially succeeded, or failed, you must ensure ESLint works before proceeding.
create-plugin update
会将ESLint升级到v9,该版本要求使用扁平配置(
eslint.config.js
)而非
.eslintrc
。无论自动迁移(004)成功、部分成功还是失败,必须确保ESLint可正常工作后再继续

Check the current state

检查当前状态

bash
ls eslint.config.js .eslintrc* .config/.eslintrc* 2>/dev/null
npx eslint --version 2>&1
Three scenarios:
A)
eslint.config.js
exists and
yarn lint
passes
— auto-migration succeeded. Proceed.
B)
eslint.config.js
exists but
yarn lint
fails
— partial migration. Fix the issues:
bash
yarn lint 2>&1 | head -30
Common fixes:
  • Invalid option '--ignore-path'
    or
    Invalid option '--ext'
    → remove those flags from the
    lint
    script in
    package.json
    . In ESLint v9 flat config, ignores and file matching are configured inside
    eslint.config.js
    , not via CLI flags. Update to:
    eslint --cache .
  • Cannot find module 'eslint-plugin-deprecation'
    → remove the import/reference from
    eslint.config.js
    (replaced by
    @typescript-eslint/no-deprecated
    )
  • Other dead plugin imports → remove them from the config if the package was removed
C) No
eslint.config.js
exists
— auto-migration failed. Create one manually:
bash
ls node_modules/@grafana/eslint-config/flat.js 2>/dev/null
If
flat.js
exists, create
eslint.config.js
using it as the base:
js
import grafanaConfig from '@grafana/eslint-config/flat';

export default [
  ...grafanaConfig,
  {
    ignores: ['**/dist/', '**/node_modules/', '**/.config/', '**/coverage/'],
  },
];
Then migrate any custom rules from the old
.eslintrc
into additional config objects in the array. After creating the flat config:
  1. Update the
    lint
    script:
    "lint": "eslint --cache ."
  2. Delete the root
    .eslintrc
    (leave
    .config/.eslintrc
    — it's scaffolded and harmless)
bash
ls eslint.config.js .eslintrc* .config/.eslintrc* 2>/dev/null
npx eslint --version 2>&1
三种场景:
A)
eslint.config.js
存在且
yarn lint
执行成功
—— 自动迁移完成。继续执行后续步骤。
B)
eslint.config.js
存在但
yarn lint
执行失败
—— 迁移不完整。修复问题:
bash
yarn lint 2>&1 | head -30
常见修复方案:
  • Invalid option '--ignore-path'
    Invalid option '--ext'
    → 从
    package.json
    lint
    脚本中移除这些参数。在ESLint v9扁平配置中,忽略规则和文件匹配需在
    eslint.config.js
    内配置,而非通过CLI参数。更新为:
    eslint --cache .
  • Cannot find module 'eslint-plugin-deprecation'
    → 从
    eslint.config.js
    中移除相关导入/引用(已被
    @typescript-eslint/no-deprecated
    替代)
  • 其他失效插件导入 → 如果对应包已被移除,从配置中删除相关内容
C) 不存在
eslint.config.js
—— 自动迁移失败。手动创建:
bash
ls node_modules/@grafana/eslint-config/flat.js 2>/dev/null
如果
flat.js
存在,以此为基础创建
eslint.config.js
js
import grafanaConfig from '@grafana/eslint-config/flat';

export default [
  ...grafanaConfig,
  {
    ignores: ['**/dist/', '**/node_modules/', '**/.config/', '**/coverage/'],
  },
];
然后将旧
.eslintrc
中的自定义规则迁移到数组中的额外配置对象中。创建扁平配置后:
  1. 更新
    lint
    脚本:
    "lint": "eslint --cache ."
  2. 删除根目录下的
    .eslintrc
    (保留
    .config/.eslintrc
    ——这是脚手架生成的,无影响)

Verify lint works

验证lint功能正常

bash
yarn lint 2>&1 | tail -20
Fix auto-fixable issues with
yarn lint --fix
. Commit:
bash
git add -A && git diff --cached --quiet || git commit -m "chore: complete ESLint 9 flat config migration" --no-verify

bash
yarn lint 2>&1 | tail -20
使用
yarn lint --fix
修复可自动修复的问题。提交变更:
bash
git add -A && git diff --cached --quiet || git commit -m "chore: complete ESLint 9 flat config migration" --no-verify

Step 4: Externalize jsx-runtime

步骤4:外部化jsx-runtime

Always use the
create-plugin add
command.
Requires a clean git working tree.
bash
npx @grafana/create-plugin@latest add externalize-jsx-runtime 2>&1
Verify:
bash
grep "jsx-runtime" .config/bundler/externals.ts 2>/dev/null
  • Found → commit and proceed.
  • Not found → command failed. Only then add externals manually to the root
    webpack.config.ts
    :
ts
externals: ['react/jsx-runtime', 'react/jsx-dev-runtime'],
Commit:
bash
git add -A && git diff --cached --quiet || git commit -m "feat: externalize jsx-runtime" --no-verify

务必使用
create-plugin add
命令
。需要干净的git工作区。
bash
npx @grafana/create-plugin@latest add externalize-jsx-runtime 2>&1
验证:
bash
grep "jsx-runtime" .config/bundler/externals.ts 2>/dev/null
  • 找到内容 → 提交并继续。
  • 未找到 → 命令执行失败。仅在此情况下手动将外部依赖添加到根目录的
    webpack.config.ts
    中:
ts
externals: ['react/jsx-runtime', 'react/jsx-dev-runtime'],
提交变更:
bash
git add -A && git diff --cached --quiet || git commit -m "feat: externalize jsx-runtime" --no-verify

Step 5: Bump
grafanaDependency

步骤5:提升
grafanaDependency
版本

bash
jq -r '.dependencies.grafanaDependency' $PLUGIN_JSON
If not already
>=12.3.0
, update it. The
create-plugin add
in Step 3 may have already done this.

bash
jq -r '.dependencies.grafanaDependency' $PLUGIN_JSON
如果当前版本未达到
>=12.3.0
,则进行更新。步骤3中的
create-plugin add
可能已完成此操作。

Step 6: Bump dependencies

步骤6:提升依赖版本

Faro (if present)

Faro(如果存在)

bash
grep '"@grafana/faro' $PKG_JSON
PackageTarget
@grafana/faro-react
^2.2.3
@grafana/faro-web-sdk
^2.2.3
@grafana/faro-web-tracing
^2.0.0
bash
grep '"@grafana/faro' $PKG_JSON
包名目标版本
@grafana/faro-react
^2.2.3
@grafana/faro-web-sdk
^2.2.3
@grafana/faro-web-tracing
^2.0.0

Grafana packages

Grafana相关包

bash
grep '"@grafana/' $PKG_JSON | grep -v faro | grep -v create-plugin
Bump
@grafana/data
,
@grafana/runtime
,
@grafana/schema
,
@grafana/ui
to
^12.2.0
or later. Add
@grafana/i18n@^12.2.0
if the plugin uses translations or
@grafana/scenes
requires it.
bash
grep '"@grafana/' $PKG_JSON | grep -v faro | grep -v create-plugin
@grafana/data
@grafana/runtime
@grafana/schema
@grafana/ui
提升到
^12.2.0
或更高版本。如果插件使用翻译功能或
@grafana/scenes
需要,添加
@grafana/i18n@^12.2.0

React types

React类型定义

Bump
react
and
react-dom
to
^18.3.0
(surfaces React 19 issues early). Add
@types/react@^18.3.0
and
@types/react-dom@^18.3.0
to devDependencies if missing.
react
react-dom
提升到
^18.3.0
(可提前发现React 19相关问题)。如果缺失,在devDependencies中添加
@types/react@^18.3.0
@types/react-dom@^18.3.0

Remove deprecated packages

移除废弃包

Remove from devDependencies if present:
  • eslint-plugin-deprecation
    (replaced by
    @typescript-eslint/no-deprecated
    )
  • @types/testing-library__jest-dom
    (replaced by
    setupTests.d.ts
    )
如果devDependencies中存在以下包,将其移除:
  • eslint-plugin-deprecation
    (已被
    @typescript-eslint/no-deprecated
    替代)
  • @types/testing-library__jest-dom
    (已被
    setupTests.d.ts
    替代)

Broken transitive dependencies

损坏的传递依赖

If
yarn install
fails with a stale git reference, do not edit yarn.lock. Add a
resolutions
entry:
json
"resolutions": {
  "<package-name>": "<working-version-or-git-ref>"
}
Then delete
yarn.lock
and
node_modules
and reinstall:
bash
rm -rf node_modules yarn.lock
yarn install --ignore-engines 2>&1 | tail -10

如果
yarn install
因陈旧的git引用失败,请勿编辑yarn.lock。添加
resolutions
配置项:
json
"resolutions": {
  "<package-name>": "<working-version-or-git-ref>"
}
然后删除
yarn.lock
node_modules
并重新安装:
bash
rm -rf node_modules yarn.lock
yarn install --ignore-engines 2>&1 | tail -10

Step 7: Fix unmet
@openfeature/web-sdk
peer dependency

步骤7:修复未满足的
@openfeature/web-sdk
peer依赖

@grafana/runtime
depends on
@openfeature/react-sdk
which has
@openfeature/web-sdk
as a peer dependency. Yarn v1 (classic) does not auto-install peer deps.
Check if the plugin uses yarn classic:
bash
yarn --version 2>&1 | head -1
If version starts with
1.
, check for warnings:
bash
yarn install --ignore-engines 2>&1 | grep "unmet peer dependency.*openfeature/web-sdk"
If warnings are found:
bash
yarn add -D @openfeature/web-sdk @openfeature/core --ignore-engines
Skip condition: Yarn v2+ or npm v7+ (peer deps are auto-installed).

@grafana/runtime
依赖
@openfeature/react-sdk
,而后者将
@openfeature/web-sdk
作为peer依赖。Yarn v1(经典版)不会自动安装peer依赖。
检查插件是否使用Yarn经典版:
bash
yarn --version 2>&1 | head -1
如果版本以
1.
开头,检查警告信息:
bash
yarn install --ignore-engines 2>&1 | grep "unmet peer dependency.*openfeature/web-sdk"
如果发现警告:
bash
yarn add -D @openfeature/web-sdk @openfeature/core --ignore-engines
跳过条件:使用Yarn v2+或npm v7+(会自动安装peer依赖)。

Step 8: Fix source code issues

步骤8:修复源码问题

bash
grep -rn "ReactDOM\.render\|ReactDOM\.unmountComponentAtNode\|ReactDOM\.findDOMNode" src/ --include="*.tsx" --include="*.ts"
grep -rn "\.defaultProps\s*=" src/ --include="*.tsx" --include="*.ts"
grep -rn "\.propTypes\s*=" src/ --include="*.tsx" --include="*.ts"
grep -rn "contextTypes\|getChildContext" src/ --include="*.tsx" --include="*.ts"
grep -rn "createFactory" src/ --include="*.tsx" --include="*.ts"
grep -rn "ChangeEvent<HTMLInputElement>" src/ --include="*.tsx" --include="*.ts"
PatternFix
ReactDOM.render()
createRoot(container).render(element)
defaultProps
on function components
Move to destructured parameter defaults
defaultProps
on class components
Leave — still works
propTypes
Remove
contextTypes
/
getChildContext
Use
React.createContext()
+
useContext()
createFactory
Use JSX or
createElement()
ChangeEvent<HTMLInputElement>
on checkbox
Change to
FormEvent<HTMLInputElement>

bash
grep -rn "ReactDOM\.render\|ReactDOM\.unmountComponentAtNode\|ReactDOM\.findDOMNode" src/ --include="*.tsx" --include="*.ts"
grep -rn "\.defaultProps\s*=" src/ --include="*.tsx" --include="*.ts"
grep -rn "\.propTypes\s*=" src/ --include="*.tsx" --include="*.ts"
grep -rn "contextTypes\|getChildContext" src/ --include="*.tsx" --include="*.ts"
grep -rn "createFactory" src/ --include="*.tsx" --include="*.ts"
grep -rn "ChangeEvent<HTMLInputElement>" src/ --include="*.tsx" --include="*.ts"
匹配模式修复方案
ReactDOM.render()
替换为
createRoot(container).render(element)
函数组件上的
defaultProps
迁移到解构参数默认值中
类组件上的
defaultProps
保留——仍可正常使用
propTypes
删除
contextTypes
/
getChildContext
使用
React.createContext()
+
useContext()
替代
createFactory
使用JSX或
createElement()
替代
复选框上的
ChangeEvent<HTMLInputElement>
改为
FormEvent<HTMLInputElement>

Step 9: Build, typecheck, test

步骤9:构建、类型检查、测试

bash
rm -rf node_modules dist
yarn install --ignore-engines 2>&1 | tail -10
yarn build 2>&1 | tail -10
yarn typecheck 2>&1 | tail -10
yarn test --watchAll=false 2>&1 | tail -10
ErrorFix
Cannot find module 'react/jsx-runtime'
Step 4 not applied — re-run
create-plugin add
Cannot find module '@openfeature/web-sdk'
Step 7 —
yarn add -D @openfeature/web-sdk @openfeature/core
Can't resolve '@grafana/i18n'
yarn add @grafana/i18n@^12.2.0
Cannot read properties of undefined (reading 'ReactCurrentOwner')
Bump
@grafana-cloud/*
packages — see Step 6
aria-label is missing
on icon-only
Button
Add
aria-label
prop (newer
@grafana/ui
requires it)
Stale git hash in
yarn.lock
Add
resolutions
in
package.json
, delete lockfile, reinstall
For detailed known issues (i18n crash,
@grafana/schema
type breaks, publicPath mismatch), see references/known-issues.md.

bash
rm -rf node_modules dist
yarn install --ignore-engines 2>&1 | tail -10
yarn build 2>&1 | tail -10
yarn typecheck 2>&1 | tail -10
yarn test --watchAll=false 2>&1 | tail -10
错误信息修复方案
Cannot find module 'react/jsx-runtime'
未执行步骤4——重新运行
create-plugin add
Cannot find module '@openfeature/web-sdk'
执行步骤7——
yarn add -D @openfeature/web-sdk @openfeature/core
Can't resolve '@grafana/i18n'
执行
yarn add @grafana/i18n@^12.2.0
Cannot read properties of undefined (reading 'ReactCurrentOwner')
提升
@grafana/cloud/*
包版本——参考步骤6
仅含图标的
Button
缺少
aria-label
添加
aria-label
属性(新版
@grafana/ui
要求)
yarn.lock
中存在陈旧的git哈希
package.json
中添加
resolutions
,删除锁文件后重新安装
关于详细的已知问题(i18n崩溃、
@grafana/schema
类型错误、publicPath不匹配),请查看references/known-issues.md

Step 10: Update CI (if applicable)

步骤10:更新CI(如适用)

bash
grep -rn "plugin-ci-workflows\|e2e-version" .github/workflows/ 2>/dev/null
  • plugin-ci-workflows@main
    or >= 6.0.0 → already tests React 19. No changes needed.
  • plugin-actions/e2e-version
    → add
    skip-grafana-react-19-preview-image: false
    .
  • Neither found → test manually with
    GRAFANA_VERSION=dev-preview-react19 docker compose up --build
    .

bash
grep -rn "plugin-ci-workflows\|e2e-version" .github/workflows/ 2>/dev/null
  • plugin-ci-workflows@main
    或版本>=6.0.0 → 已包含React 19测试。无需修改。
  • plugin-actions/e2e-version
    → 添加
    skip-grafana-react-19-preview-image: false
  • 均未找到 → 使用
    GRAFANA_VERSION=dev-preview-react19 docker compose up --build
    手动测试。

Step 11: Squash and push

步骤11:合并提交并推送

bash
git reset --soft origin/main
git add -A
git commit -m "fix: Prepare plugin for React 19 compatibility"
Commit message body should list: create-plugin version change, ESLint 9 migration, key dependency bumps, and any source code fixes.

bash
git reset --soft origin/main
git add -A
git commit -m "fix: Prepare plugin for React 19 compatibility"
提交信息正文应列出:create-plugin版本变更、ESLint 9迁移、关键依赖版本提升以及所有源码修复内容。

References

参考资料