This is an automated email from the ASF dual-hosted git repository.
pierrejeambrun pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/airflow.git
The following commit(s) were added to refs/heads/main by this push:
new fa8f2337eef Prevent lazy loading for cached plugins (#61207)
fa8f2337eef is described below
commit fa8f2337eef7b24392e0e9e47db0f8ba74e94bcc
Author: Pierre Jeambrun <[email protected]>
AuthorDate: Thu Jan 29 17:51:23 2026 +0100
Prevent lazy loading for cached plugins (#61207)
---
.../src/airflow/ui/src/pages/ExternalView.tsx | 6 ++++--
.../src/airflow/ui/src/pages/ReactPlugin.tsx | 20 +++++++++++++++-----
2 files changed, 19 insertions(+), 7 deletions(-)
diff --git a/airflow-core/src/airflow/ui/src/pages/ExternalView.tsx
b/airflow-core/src/airflow/ui/src/pages/ExternalView.tsx
index a1c80cdf2f3..c74d9b25a4b 100644
--- a/airflow-core/src/airflow/ui/src/pages/ExternalView.tsx
+++ b/airflow-core/src/airflow/ui/src/pages/ExternalView.tsx
@@ -18,7 +18,7 @@
*/
import { Box } from "@chakra-ui/react";
import { useTranslation } from "react-i18next";
-import { useParams } from "react-router-dom";
+import { useLocation, useParams } from "react-router-dom";
import { usePluginServiceGetPlugins } from "openapi/queries";
import { ProgressBar } from "src/components/ui";
@@ -32,6 +32,8 @@ export const ExternalView = () => {
const { page } = useParams();
const { data: pluginData, isLoading } = usePluginServiceGetPlugins();
+ const { pathname } = useLocation();
+
const externalView =
page === "legacy-fab-views"
? {
@@ -82,7 +84,7 @@ export const ExternalView = () => {
m={-2} // Compensate for parent padding
minHeight={0}
>
- <ReactPlugin reactApp={reactApp} />
+ <ReactPlugin key={pathname} reactApp={reactApp} />
</Box>
);
}
diff --git a/airflow-core/src/airflow/ui/src/pages/ReactPlugin.tsx
b/airflow-core/src/airflow/ui/src/pages/ReactPlugin.tsx
index 9f43f72d90d..c530409a0e5 100644
--- a/airflow-core/src/airflow/ui/src/pages/ReactPlugin.tsx
+++ b/airflow-core/src/airflow/ui/src/pages/ReactPlugin.tsx
@@ -59,19 +59,29 @@ const loadPlugin = (reactApp: ReactAppResponse): Promise<{
default: PluginCompon
// eslint-disable-next-line no-console
console.error("Component failed to load:", error);
- return {
- default: ErrorPage,
- };
+ return { default: ErrorPage };
});
export const ReactPlugin = ({ reactApp }: { readonly reactApp:
ReactAppResponse }) => {
const { dagId, mapIndex, runId, taskId } = useParams();
- const Plugin = lazy(() => loadPlugin(reactApp));
+ // If the plugin component was already registered on the global object by a
previous load,
+ // render it directly without going through Suspense/lazy (avoids flashing
the spinner).
+ const existing = (globalThis as Record<string, unknown>)[reactApp.name];
+
+ if (typeof existing === "function") {
+ const Plugin = existing as PluginComponentType;
+
+ return <Plugin dagId={dagId} mapIndex={mapIndex} runId={runId}
taskId={taskId} />;
+ }
+
+ // Otherwise, lazy-load the bundle once. When it resolves, it must set a
function component
+ // under globalThis[reactApp.name], which we then use as the default export.
+ const LazyPlugin = lazy(() => loadPlugin(reactApp));
return (
<Suspense fallback={<Spinner />}>
- <Plugin dagId={dagId} mapIndex={mapIndex} runId={runId} taskId={taskId}
/>
+ <LazyPlugin dagId={dagId} mapIndex={mapIndex} runId={runId}
taskId={taskId} />
</Suspense>
);
};