This is an automated email from the ASF dual-hosted git repository.
kaxil 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 518f8d2e7be Lazy-load the Code tab grammar from the Dag's stored
language (#74643)
518f8d2e7be is described below
commit 518f8d2e7be6bc448b8e641aef4e8eff30d2b560
Author: Pierre Jeambrun <[email protected]>
AuthorDate: Tue Oct 13 22:56:28 2026 +0200
Lazy-load the Code tab grammar from the Dag's stored language (#74643)
Dag importers are pluggable, so core cannot list every language an importer
may
store in dag_code.language. Registering monaco's lazy grammar loaders lets
the
Code tab highlight any language monaco ships, fetching a grammar only when a
Dag source first uses it.
---
.../MonacoEditor/configureMonaco.test.ts | 35 +++---
.../src/components/MonacoEditor/configureMonaco.ts | 128 ++++++---------------
airflow-core/src/airflow/ui/src/vite-env.d.ts | 30 +----
3 files changed, 54 insertions(+), 139 deletions(-)
diff --git
a/airflow-core/src/airflow/ui/src/components/MonacoEditor/configureMonaco.test.ts
b/airflow-core/src/airflow/ui/src/components/MonacoEditor/configureMonaco.test.ts
index a9816858ae0..382a45a893a 100644
---
a/airflow-core/src/airflow/ui/src/components/MonacoEditor/configureMonaco.test.ts
+++
b/airflow-core/src/airflow/ui/src/components/MonacoEditor/configureMonaco.test.ts
@@ -18,28 +18,33 @@
*/
import { describe, expect, it } from "vitest";
+import { configureMonaco } from "./configureMonaco";
+
// A Monaco upgrade that moved a module or dropped an export
// would slip through CI and only surface as silently-unhighlighted code in
production.
// Importing the modules here fails the build loudly instead: a missing module
breaks resolution,
// a dropped export fails the assertion.
-const grammars = [
- { id: "go", load: () => import("monaco-editor/languages/definitions/go/go")
},
- { id: "java", load: () =>
import("monaco-editor/languages/definitions/java/java") },
- { id: "python", load: () =>
import("monaco-editor/languages/definitions/python/python") },
- { id: "typescript", load: () =>
import("monaco-editor/languages/definitions/typescript/typescript") },
- { id: "yaml", load: () =>
import("monaco-editor/languages/definitions/yaml/yaml") },
-] as const;
-
describe("Monaco Dag-code grammars", () => {
- it.each(grammars)(
- "$id exposes the { conf, language } Monarch shape configureMonaco
registers",
- async ({ load }) => {
- const { conf, language } = await load();
+ // Any language id an importer stores must be highlightable, not only the
ones core ships.
+ it.each(["go", "java", "python", "rust", "typescript", "yaml"])(
+ "registers %s for lazy loading",
+ async (languageId) => {
+ await configureMonaco();
+ const { languages } = await import("monaco-editor/editor");
- expect(conf).toBeDefined();
- expect(language).toBeDefined();
- expect(language.tokenizer).toBeDefined();
+ expect(languages.getLanguages().map(({ id }) =>
id)).toContain(languageId);
},
30_000,
);
+
+ it("tokenizes Python with the patched f-string grammar", async () => {
+ await configureMonaco();
+ const { editor } = await import("monaco-editor/editor");
+
+ // Resolves the lazy tokenizer, which `tokenize` alone does not trigger.
+ await editor.colorize("", "python", {});
+ const [, escapedBracesLine] = editor.tokenize('x = f"""\n{{a}}\n"""',
"python");
+
+ expect(escapedBracesLine).toEqual([{ language: "python", offset: 0, type:
"string.python" }]);
+ }, 30_000);
});
diff --git
a/airflow-core/src/airflow/ui/src/components/MonacoEditor/configureMonaco.ts
b/airflow-core/src/airflow/ui/src/components/MonacoEditor/configureMonaco.ts
index bfc28915c69..7929499b065 100644
--- a/airflow-core/src/airflow/ui/src/components/MonacoEditor/configureMonaco.ts
+++ b/airflow-core/src/airflow/ui/src/components/MonacoEditor/configureMonaco.ts
@@ -58,53 +58,21 @@ const loadMonacoModules = async () => {
),
]);
- // The JSON feature registers its language as a side effect. The
Dag-authoring languages
- // are registered manually below from their grammar modules instead: Python
needs its
- // patched grammar (triple-quoted f-string support) applied before
registering, which
- // importing its register module instead would not allow, and the others are
registered
- // the same way for consistency.
- // The runtime guard below fails loudly if a grammar export shape changes.
+ // The JSON feature registers its language as a side effect. Every Monarch
grammar monaco
+ // ships is registered with a lazy loader, so a grammar chunk is only
fetched the first time
+ // an editor uses its language: any language id an importer stores for its
Dag source gets
+ // highlighted without the UI knowing the list up front.
const jsonContribution =
import("monaco-editor/languages/features/json/register");
- const pythonGrammar =
import("monaco-editor/languages/definitions/python/python");
- const typescriptGrammar =
import("monaco-editor/languages/definitions/typescript/typescript");
- const javaGrammar = import("monaco-editor/languages/definitions/java/java");
- const goGrammar = import("monaco-editor/languages/definitions/go/go");
- const yamlGrammar = import("monaco-editor/languages/definitions/yaml/yaml");
-
- const [
- monaco,
- [editorWorkerUrl, jsonWorkerUrl],
- { conf: pythonConf, language: pythonLanguage },
- { conf: typescriptConf, language: typescriptLanguage },
- { conf: javaConf, language: javaLanguage },
- { conf: goConf, language: goLanguage },
- { conf: yamlConf, language: yamlLanguage },
- ] = await Promise.all([
+ const grammarContributions =
import("monaco-editor/languages/definitions/register.all");
+
+ const [monaco, [editorWorkerUrl, jsonWorkerUrl]] = await Promise.all([
monacoApi,
workerUrls,
- pythonGrammar,
- typescriptGrammar,
- javaGrammar,
- goGrammar,
- yamlGrammar,
jsonContribution,
+ grammarContributions,
]);
- return {
- editorWorkerUrl,
- goConf,
- goLanguage,
- javaConf,
- javaLanguage,
- jsonWorkerUrl,
- monaco,
- pythonConf,
- pythonLanguage,
- typescriptConf,
- typescriptLanguage,
- yamlConf,
- yamlLanguage,
- };
+ return { editorWorkerUrl, jsonWorkerUrl, monaco };
};
const createWorkerFromUrl = (workerUrl: string): Worker => {
@@ -121,62 +89,32 @@ export const configureMonaco = () => {
}
configurationPromise = loadMonacoModules()
- .then(
- ({
- editorWorkerUrl,
- goConf,
- goLanguage,
- javaConf,
- javaLanguage,
- jsonWorkerUrl,
- monaco,
- pythonConf,
- pythonLanguage,
- typescriptConf,
- typescriptLanguage,
- yamlConf,
- yamlLanguage,
- }) => {
- Reflect.set(globalThis, "MonacoEnvironment", {
- getWorker: (_moduleId: string, label: string) =>
- createWorkerFromUrl(label === "json" ? jsonWorkerUrl :
editorWorkerUrl),
- } satisfies MonacoEnvironment);
-
- // The editor always sets the Dag's own language explicitly, so no
extensions/firstLine
- // auto-detection metadata is needed for any of these. Guard the
internal grammar
- // export shape: if a monaco upgrade drops these, fail loudly here
rather than
- // silently disabling highlighting (`setMonarchTokensProvider(id,
undefined)`).
- // The `conf`/`language` types come from hand-written ambient
declarations, so
- // TypeScript believes they are always defined; this guard checks the
real runtime
- // shape the types cannot vouch for.
- const grammars = [
- { conf: pythonConf, id: "python", language: pythonLanguage },
- { conf: typescriptConf, id: "typescript", language:
typescriptLanguage },
- { conf: javaConf, id: "java", language: javaLanguage },
- { conf: goConf, id: "go", language: goLanguage },
- { conf: yamlConf, id: "yaml", language: yamlLanguage },
- ] as const;
-
- for (const grammar of grammars) {
+ .then(({ editorWorkerUrl, jsonWorkerUrl, monaco }) => {
+ Reflect.set(globalThis, "MonacoEnvironment", {
+ getWorker: (_moduleId: string, label: string) =>
+ createWorkerFromUrl(label === "json" ? jsonWorkerUrl :
editorWorkerUrl),
+ } satisfies MonacoEnvironment);
+
+ // Replaces the lazy Python tokenizer `register.all` set up with the
patched grammar
+ // (triple-quoted f-string support), still loaded on first use.
+ monaco.languages.registerTokensProviderFactory("python", {
+ create: async () => {
+ const { language } = await
import("monaco-editor/languages/definitions/python/python");
+
+ // The `language` type comes from a hand-written ambient
declaration, so TypeScript
+ // believes it is always defined; fail loudly if a monaco upgrade
drops the export
+ // rather than silently disabling Python highlighting.
// eslint-disable-next-line
@typescript-eslint/no-unnecessary-condition
- if (grammar.conf === undefined || grammar.language === undefined) {
- throw new Error(
- `monaco ${grammar.id} grammar module changed shape: missing
\`conf\`/\`language\` export`,
- );
+ if (language === undefined) {
+ throw new Error("monaco python grammar module changed shape:
missing `language` export");
}
- monaco.languages.register({ id: grammar.id });
- monaco.languages.setLanguageConfiguration(grammar.id, grammar.conf);
- // Python needs the patched grammar (triple-quoted f-string
support); the rest
- // register as monaco ships them.
- monaco.languages.setMonarchTokensProvider(
- grammar.id,
- grammar.id === "python" ? patchPythonFStrings(grammar.language) :
grammar.language,
- );
- }
-
- loader.config({ monaco });
- },
- )
+
+ return patchPythonFStrings(language);
+ },
+ });
+
+ loader.config({ monaco });
+ })
.catch((error: unknown) => {
configurationPromise = undefined;
// eslint-disable-next-line no-console
diff --git a/airflow-core/src/airflow/ui/src/vite-env.d.ts
b/airflow-core/src/airflow/ui/src/vite-env.d.ts
index 5b70be41e1c..0efa1e9c8b0 100644
--- a/airflow-core/src/airflow/ui/src/vite-env.d.ts
+++ b/airflow-core/src/airflow/ui/src/vite-env.d.ts
@@ -25,7 +25,7 @@ interface ImportMeta {
readonly env: ImportMetaEnv;
}
-// Each of these language definitions exports its Monarch grammar (`conf` /
`language`)
+// The Python language definition exports its Monarch grammar (`conf` /
`language`)
// but ships no `.d.ts` of its own.
declare module "monaco-editor/languages/definitions/python/python" {
// `import(...)` type syntax is required here: a top-level `import type`
would turn this
@@ -35,31 +35,3 @@ declare module
"monaco-editor/languages/definitions/python/python" {
export const language: import("monaco-editor").languages.IMonarchLanguage;
/* eslint-enable @typescript-eslint/consistent-type-imports */
}
-
-declare module "monaco-editor/languages/definitions/typescript/typescript" {
- /* eslint-disable @typescript-eslint/consistent-type-imports */
- export const conf: import("monaco-editor").languages.LanguageConfiguration;
- export const language: import("monaco-editor").languages.IMonarchLanguage;
- /* eslint-enable @typescript-eslint/consistent-type-imports */
-}
-
-declare module "monaco-editor/languages/definitions/java/java" {
- /* eslint-disable @typescript-eslint/consistent-type-imports */
- export const conf: import("monaco-editor").languages.LanguageConfiguration;
- export const language: import("monaco-editor").languages.IMonarchLanguage;
- /* eslint-enable @typescript-eslint/consistent-type-imports */
-}
-
-declare module "monaco-editor/languages/definitions/go/go" {
- /* eslint-disable @typescript-eslint/consistent-type-imports */
- export const conf: import("monaco-editor").languages.LanguageConfiguration;
- export const language: import("monaco-editor").languages.IMonarchLanguage;
- /* eslint-enable @typescript-eslint/consistent-type-imports */
-}
-
-declare module "monaco-editor/languages/definitions/yaml/yaml" {
- /* eslint-disable @typescript-eslint/consistent-type-imports */
- export const conf: import("monaco-editor").languages.LanguageConfiguration;
- export const language: import("monaco-editor").languages.IMonarchLanguage;
- /* eslint-enable @typescript-eslint/consistent-type-imports */
-}