This is an automated email from the ASF dual-hosted git repository.
vatsrahul1001 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 3a861426cae UI: Use local Monaco editor module instead of CDN (#66647)
3a861426cae is described below
commit 3a861426caeb33bf6c9feb755850d952a194ce9d
Author: hojeong park <[email protected]>
AuthorDate: Wed May 20 01:39:58 2026 +0900
UI: Use local Monaco editor module instead of CDN (#66647)
* UI: Bundle Monaco editor instead of loading it from CDN
* UI: Configure Monaco worker loading for bundled editor
* UI: Lazy load Monaco editor configuration
* UI: Bundle Monaco editor without changing runtime version
* UI: Fix Dag code tab line number selector
* UI: Load bundled Monaco workers with Vite worker imports
* UI: Handle Monaco editor load failures
* UI: Add Monaco Editor license notice
* UI: Add Monaco Editor license notice
---
.../3rd-party-licenses/LICENSE-monaco-editor.txt | 21 +++++++
airflow-core/LICENSE | 1 +
airflow-core/NOTICE | 8 +++
airflow-core/src/airflow/ui/package.json | 1 +
airflow-core/src/airflow/ui/pnpm-lock.yaml | 22 +++----
.../src/airflow/ui/src/components/JsonEditor.tsx | 2 +-
.../src/components/MonacoEditor/configureMonaco.ts | 71 ++++++++++++++++++++++
.../ui/src/components/MonacoEditor/index.tsx | 51 ++++++++++++++++
.../src/components/MonacoEditor/useMonacoReady.ts | 43 +++++++++++++
.../ui/src/components/RenderedJsonField.tsx | 2 +-
.../src/airflow/ui/src/pages/Dag/Code/Code.tsx | 2 +-
.../ui/src/pages/Dag/Code/CodeDiffViewer.tsx | 2 +-
.../src/airflow/ui/tests/e2e/pages/DagCodePage.ts | 2 +-
13 files changed, 210 insertions(+), 18 deletions(-)
diff --git a/airflow-core/3rd-party-licenses/LICENSE-monaco-editor.txt
b/airflow-core/3rd-party-licenses/LICENSE-monaco-editor.txt
new file mode 100644
index 00000000000..76fdc58a0d7
--- /dev/null
+++ b/airflow-core/3rd-party-licenses/LICENSE-monaco-editor.txt
@@ -0,0 +1,21 @@
+The MIT License (MIT)
+
+Copyright (c) 2016 - present Microsoft Corporation
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
diff --git a/airflow-core/LICENSE b/airflow-core/LICENSE
index 4df0bdc15cc..1d91f60c980 100644
--- a/airflow-core/LICENSE
+++ b/airflow-core/LICENSE
@@ -236,6 +236,7 @@ The text of each license is also included at
3rd-party-licenses/LICENSE-[project
(MIT License) normalize.css v3.0.2
(http://necolas.github.io/normalize.css/)
(MIT License) ElasticMock v1.3.2 (https://github.com/vrcmarcos/elasticmock)
(MIT License) MomentJS v2.24.0 (http://momentjs.com/)
+ (MIT License) Monaco Editor v0.52.2
(https://github.com/microsoft/monaco-editor)
(MIT License) eonasdan-bootstrap-datetimepicker v4.17.49
(https://github.com/eonasdan/bootstrap-datetimepicker/)
(MIT License) Chakra UI v3.35.0 (https://github.com/chakra-ui/chakra-ui)
diff --git a/airflow-core/NOTICE b/airflow-core/NOTICE
index b103c97d19a..9a1b9cddf6d 100644
--- a/airflow-core/NOTICE
+++ b/airflow-core/NOTICE
@@ -20,3 +20,11 @@ This product contains a modified portion of 'Chakra UI'
developed by Segun Adeba
(https://github.com/chakra-ui/chakra-ui).
* Copyright 2019, Segun Adebayo
+
+
+Monaco Editor:
+-----
+This product contains 'Monaco Editor' developed by Microsoft Corporation.
+(https://github.com/microsoft/monaco-editor).
+
+* Copyright (c) 2016 - present Microsoft Corporation
diff --git a/airflow-core/src/airflow/ui/package.json
b/airflow-core/src/airflow/ui/package.json
index 9096187a570..5684c8356b0 100644
--- a/airflow-core/src/airflow/ui/package.json
+++ b/airflow-core/src/airflow/ui/package.json
@@ -50,6 +50,7 @@
"i18next": "^25.8.16",
"i18next-browser-languagedetector": "^8.2.1",
"i18next-http-backend": "^3.0.5",
+ "monaco-editor": "^0.52.2",
"next-themes": "^0.4.6",
"react": "^19.2.5",
"react-chartjs-2": "^5.3.1",
diff --git a/airflow-core/src/airflow/ui/pnpm-lock.yaml
b/airflow-core/src/airflow/ui/pnpm-lock.yaml
index 35a6b300521..deacc4a6867 100644
--- a/airflow-core/src/airflow/ui/pnpm-lock.yaml
+++ b/airflow-core/src/airflow/ui/pnpm-lock.yaml
@@ -47,7 +47,7 @@ importers:
version: 1.2.3
'@monaco-editor/react':
specifier: ^4.7.0
- version:
4.7.0([email protected])([email protected]([email protected]))([email protected])
+ version:
4.7.0([email protected])([email protected]([email protected]))([email protected])
'@tanstack/react-query':
specifier: ^5.90.21
version: 5.90.21([email protected])
@@ -105,6 +105,9 @@ importers:
i18next-http-backend:
specifier: ^3.0.5
version: 3.0.5
+ monaco-editor:
+ specifier: ^0.52.2
+ version: 0.52.2
next-themes:
specifier: ^0.4.6
version: 0.4.6([email protected]([email protected]))([email protected])
@@ -1322,9 +1325,6 @@ packages:
'@types/[email protected]':
resolution: {integrity:
sha512-xMAgYwceFhRA2zY+XbEA7mxYbA093wdiW8Vu6gZPGWy9cmOyU9XesH1tNcEWsKFd5Vzrqx5T3D38PWx1FIIXkA==}
- '@types/[email protected]':
- resolution: {integrity:
sha512-230RC8sFeHoT6sSUlRO6a8cAnclO06eeiq1QDfiv2FGCLWFvvERWgwIQD4FWqD9A69BN7Lzee4OXwoMVnnsWDw==}
-
'@types/[email protected]':
resolution: {integrity:
sha512-CmBKiL6NNo/OqgmMn95Fk9Whlp2mtvIv+KNpQKN2F4SjvrEesubTRWGYSg+BnWZOnlCaSTU1sMpsBOzgbYhnsA==}
@@ -3299,8 +3299,8 @@ packages:
[email protected]:
resolution: {integrity:
sha512-l8D9ODSRWLe2KHJSifWGwBqpTZXIXTeo8mlKjY+E2HAakaTeNpqAyBZ8GSqLzHgw4XmHmC8whvpjJNMbFZN7/g==}
- [email protected]:
- resolution: {integrity:
sha512-0WNThgC6CMWNXXBxTbaYYcunj08iB5rnx4/G56UOPeL9UVIUGGHA1GR0EWIh9Ebabj7NpCRawQ5b0hfN1jQmYQ==}
+ [email protected]:
+ resolution: {integrity:
sha512-GEQWEZmfkOGLdd3XK8ryrfWz3AIP8YymVXiPHEdewrUq7mh0qrKrfHLNCXcbB6sTnMLnOZ3ztSiKcciFUkIJwQ==}
[email protected]:
resolution: {integrity:
sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==}
@@ -4997,10 +4997,10 @@ snapshots:
dependencies:
state-local: 1.0.7
-
'@monaco-editor/[email protected]([email protected])([email protected]([email protected]))([email protected])':
+
'@monaco-editor/[email protected]([email protected])([email protected]([email protected]))([email protected])':
dependencies:
'@monaco-editor/loader': 1.5.0
- monaco-editor: 0.53.0
+ monaco-editor: 0.52.2
react: 19.2.5
react-dom: 19.2.5([email protected])
@@ -5378,8 +5378,6 @@ snapshots:
'@types/[email protected]': {}
- '@types/[email protected]': {}
-
'@types/[email protected]': {}
'@types/[email protected]': {}
@@ -8144,9 +8142,7 @@ snapshots:
pkg-types: 1.3.1
ufo: 1.6.1
- [email protected]:
- dependencies:
- '@types/trusted-types': 1.0.6
+ [email protected]: {}
[email protected]: {}
diff --git a/airflow-core/src/airflow/ui/src/components/JsonEditor.tsx
b/airflow-core/src/airflow/ui/src/components/JsonEditor.tsx
index 806b73b7d26..060df05ebce 100644
--- a/airflow-core/src/airflow/ui/src/components/JsonEditor.tsx
+++ b/airflow-core/src/airflow/ui/src/components/JsonEditor.tsx
@@ -16,9 +16,9 @@
* specific language governing permissions and limitations
* under the License.
*/
-import Editor, { type EditorProps } from "@monaco-editor/react";
import { useRef } from "react";
+import Editor, { type EditorProps } from "src/components/MonacoEditor";
import { useMonacoTheme } from "src/context/colorMode";
type JsonEditorProps = {
diff --git
a/airflow-core/src/airflow/ui/src/components/MonacoEditor/configureMonaco.ts
b/airflow-core/src/airflow/ui/src/components/MonacoEditor/configureMonaco.ts
new file mode 100644
index 00000000000..629b0cc46b6
--- /dev/null
+++ b/airflow-core/src/airflow/ui/src/components/MonacoEditor/configureMonaco.ts
@@ -0,0 +1,71 @@
+/*!
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+import { loader } from "@monaco-editor/react";
+
+type MonacoEnvironment = {
+ readonly getWorker: (_moduleId: string, label: string) => Worker;
+};
+
+let configurationPromise: Promise<void> | undefined;
+
+const loadMonacoModules = async () => {
+ const monacoApi = import("monaco-editor/esm/vs/editor/editor.api");
+
+ const workerConstructors = Promise.all([
+ import("monaco-editor/esm/vs/editor/editor.worker?worker").then((module)
=> module.default),
+
import("monaco-editor/esm/vs/language/json/json.worker?worker").then((module)
=> module.default),
+ ]);
+
+ const languageContributions = Promise.all([
+ import("monaco-editor/esm/vs/basic-languages/python/python.contribution"),
+ import("monaco-editor/esm/vs/language/json/monaco.contribution"),
+ ]);
+
+ const [monaco, [editorWorker, jsonWorker]] = await Promise.all([
+ monacoApi,
+ workerConstructors,
+ languageContributions,
+ ]);
+
+ return { editorWorker, jsonWorker, monaco };
+};
+
+export const configureMonaco = () => {
+ if (configurationPromise !== undefined) {
+ return configurationPromise;
+ }
+
+ configurationPromise = loadMonacoModules()
+ .then(({ editorWorker, jsonWorker, monaco }) => {
+ Reflect.set(globalThis, "MonacoEnvironment", {
+ getWorker: (_moduleId: string, label: string) =>
+ label === "json" ? new jsonWorker() : new editorWorker(),
+ } satisfies MonacoEnvironment);
+
+ loader.config({ monaco });
+ })
+ .catch((error: unknown) => {
+ configurationPromise = undefined;
+ // eslint-disable-next-line no-console
+ console.error("Failed to configure Monaco editor", error);
+ throw error;
+ });
+
+ return configurationPromise;
+};
diff --git a/airflow-core/src/airflow/ui/src/components/MonacoEditor/index.tsx
b/airflow-core/src/airflow/ui/src/components/MonacoEditor/index.tsx
new file mode 100644
index 00000000000..949146d6524
--- /dev/null
+++ b/airflow-core/src/airflow/ui/src/components/MonacoEditor/index.tsx
@@ -0,0 +1,51 @@
+/*!
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+import EditorComponent, {
+ DiffEditor as DiffEditorComponent,
+ type DiffEditorProps,
+ type EditorProps,
+} from "@monaco-editor/react";
+
+import { useMonacoReady } from "./useMonacoReady";
+
+export const MonacoEditor = (props: EditorProps) => {
+ const isMonacoReady = useMonacoReady();
+
+ if (!isMonacoReady) {
+ return null;
+ }
+
+ return <EditorComponent {...props} />;
+};
+
+export const MonacoDiffEditor = (props: DiffEditorProps) => {
+ const isMonacoReady = useMonacoReady();
+
+ if (!isMonacoReady) {
+ return null;
+ }
+
+ return <DiffEditorComponent {...props} />;
+};
+
+export const DiffEditor = MonacoDiffEditor;
+
+export default MonacoEditor;
+
+export type { DiffEditorProps, EditorProps, Monaco, OnMount } from
"@monaco-editor/react";
diff --git
a/airflow-core/src/airflow/ui/src/components/MonacoEditor/useMonacoReady.ts
b/airflow-core/src/airflow/ui/src/components/MonacoEditor/useMonacoReady.ts
new file mode 100644
index 00000000000..b8a6821a0c7
--- /dev/null
+++ b/airflow-core/src/airflow/ui/src/components/MonacoEditor/useMonacoReady.ts
@@ -0,0 +1,43 @@
+/*!
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+import { useEffect, useState } from "react";
+
+import { configureMonaco } from "./configureMonaco";
+
+export const useMonacoReady = () => {
+ const [isReady, setIsReady] = useState(false);
+
+ useEffect(() => {
+ let isMounted = true;
+
+ void configureMonaco()
+ .then(() => {
+ if (isMounted) {
+ setIsReady(true);
+ }
+ })
+ .catch(() => undefined);
+
+ return () => {
+ isMounted = false;
+ };
+ }, []);
+
+ return isReady;
+};
diff --git a/airflow-core/src/airflow/ui/src/components/RenderedJsonField.tsx
b/airflow-core/src/airflow/ui/src/components/RenderedJsonField.tsx
index 87308e6fdd7..ff629eaa1fb 100644
--- a/airflow-core/src/airflow/ui/src/components/RenderedJsonField.tsx
+++ b/airflow-core/src/airflow/ui/src/components/RenderedJsonField.tsx
@@ -17,9 +17,9 @@
* under the License.
*/
import { Flex, type FlexProps } from "@chakra-ui/react";
-import Editor, { type OnMount } from "@monaco-editor/react";
import { useCallback, useState } from "react";
+import Editor, { type OnMount } from "src/components/MonacoEditor";
import { ClipboardRoot, ClipboardIconButton } from "src/components/ui";
import { useMonacoTheme } from "src/context/colorMode";
diff --git a/airflow-core/src/airflow/ui/src/pages/Dag/Code/Code.tsx
b/airflow-core/src/airflow/ui/src/pages/Dag/Code/Code.tsx
index 53dc2ba55e4..0196f947ca9 100644
--- a/airflow-core/src/airflow/ui/src/pages/Dag/Code/Code.tsx
+++ b/airflow-core/src/airflow/ui/src/pages/Dag/Code/Code.tsx
@@ -17,7 +17,6 @@
* under the License.
*/
import { Box, Button, Heading, HStack, Link, VStack } from "@chakra-ui/react";
-import Editor, { type EditorProps } from "@monaco-editor/react";
import { useState } from "react";
import { useHotkeys } from "react-hotkeys-hook";
import { useTranslation } from "react-i18next";
@@ -33,6 +32,7 @@ import type { ApiError } from
"openapi/requests/core/ApiError";
import type { DAGSourceResponse } from "openapi/requests/types.gen";
import { DagVersionSelect } from "src/components/DagVersionSelect";
import { ErrorAlert } from "src/components/ErrorAlert";
+import Editor, { type EditorProps } from "src/components/MonacoEditor";
import Time from "src/components/Time";
import { ClipboardRoot, ClipboardButton, Tooltip } from "src/components/ui";
import { ProgressBar } from "src/components/ui";
diff --git a/airflow-core/src/airflow/ui/src/pages/Dag/Code/CodeDiffViewer.tsx
b/airflow-core/src/airflow/ui/src/pages/Dag/Code/CodeDiffViewer.tsx
index 08acae1b42a..8f6b89bc608 100644
--- a/airflow-core/src/airflow/ui/src/pages/Dag/Code/CodeDiffViewer.tsx
+++ b/airflow-core/src/airflow/ui/src/pages/Dag/Code/CodeDiffViewer.tsx
@@ -17,8 +17,8 @@
* under the License.
*/
import { Box } from "@chakra-ui/react";
-import { DiffEditor, type DiffEditorProps } from "@monaco-editor/react";
+import { DiffEditor, type DiffEditorProps } from "src/components/MonacoEditor";
import { useMonacoTheme } from "src/context/colorMode";
type CodeDiffViewerProps = {
diff --git a/airflow-core/src/airflow/ui/tests/e2e/pages/DagCodePage.ts
b/airflow-core/src/airflow/ui/tests/e2e/pages/DagCodePage.ts
index fa686d48944..3320d7651d4 100644
--- a/airflow-core/src/airflow/ui/tests/e2e/pages/DagCodePage.ts
+++ b/airflow-core/src/airflow/ui/tests/e2e/pages/DagCodePage.ts
@@ -29,7 +29,7 @@ export class DagCodePage extends BasePage {
public constructor(page: Page) {
super(page);
this.editorContainer = page.locator('[role="code"]');
- this.lineNumbers = page.locator(".monaco-editor .line-numbers");
+ this.lineNumbers = page.locator(".monaco-editor .margin-view-overlays
.line-numbers");
this.editorScrollable = page.locator(".monaco-scrollable-element");
this.syntaxTokens = page.locator(".monaco-editor .view-line span span");
this.viewLines = page.locator(".monaco-editor .view-line");