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 eff567c7e60 UI: Rewrite modulepreload hrefs to the api-server static
path (#67548)
eff567c7e60 is described below
commit eff567c7e607ecd744d4ec4dc5e7a6fedb33663c
Author: Pierre Jeambrun <[email protected]>
AuthorDate: Tue May 26 15:56:44 2026 +0200
UI: Rewrite modulepreload hrefs to the api-server static path (#67548)
* UI: Fix Monaco workers crashing in production mode
`?url` does not run the Vite worker pipeline. For `editor.worker.js` it
inlines the raw source as a base64 data URL (the source is below the
default `assetsInlineLimit`), and the inlined module still has bare
specifier imports it cannot resolve in a Worker context. For
`json.worker.js` it copies a partly-bundled file whose top-level
`import '../../editor/editor.worker.js'` resolves to a path that does
not exist in the build output. Both cases manifest at runtime as
"Could not create web worker(s). Falling back to loading web worker
code in main thread" followed by a stream of fallback fetch errors.
Switch to `?worker&url`: Vite runs the file through the worker pipeline
(bundling all dependencies into a self-contained IIFE) and the URL
query suffix returns the resulting URL as a string instead of a Worker
constructor. The Blob shim from #67352 then imports a properly bundled
worker, restoring real worker-thread execution.
* UI: Rewrite modulepreload hrefs to the api-server static path
Vite emits both `<script src="./assets/...">` and
`<link rel="modulepreload" href="./assets/...">` for chunks in the
built `index.html`. The `transform-url-src` plugin only rewrote
`src="./assets/"` to `src="./static/assets/"`, leaving the
modulepreload `href`s untouched. Behind the api-server's `/static/`
mount those preload requests hit the SPA HTML fallback and the
browser fails them with `Expected a JavaScript-or-Wasm module script
but the server responded with a MIME type of "text/html"`. The chunks
themselves still load via `<script src>` so the app works, but every
page load wastes three round-trips on the failed preloads and floods
the console.
Apply the same `static/` rewrite to `href="./assets/"` and switch all
three rewrites to `replaceAll` so multiple matches (one per chunk) are
handled — `replace` with a string only touches the first occurrence.
---------
Co-authored-by: Rahul Vats <[email protected]>
---
.../src/components/MonacoEditor/configureMonaco.ts | 20 ++++++++++++--------
airflow-core/src/airflow/ui/vite.config.ts | 5 ++++-
2 files changed, 16 insertions(+), 9 deletions(-)
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 2ad53eb09a6..911cc489df1 100644
--- a/airflow-core/src/airflow/ui/src/components/MonacoEditor/configureMonaco.ts
+++ b/airflow-core/src/airflow/ui/src/components/MonacoEditor/configureMonaco.ts
@@ -35,15 +35,19 @@ const loadMonacoModules = async () => {
import("monaco-editor/esm/vs/base/browser/ui/codicons/codiconStyles"),
]).then(([api]) => api);
- // Resolve the workers as plain URLs (not Vite `?worker` constructors). In
dev mode
- // the SPA shell is served by the airflow api-server while Vite serves
assets on a
- // different origin, and `new Worker(crossOriginUrl, { type: "module" })` is
rejected
- // by the browser. Wrapping the cross-origin URL in a same-origin Blob shim
that just
- // re-imports it sidesteps the restriction (CORS still permits the inner
import). In
- // production the worker is same-origin and the shim is harmless.
+ // Resolve the bundled worker URLs (`?worker&url` runs the worker through
Vite's worker
+ // pipeline — bundling all dependencies — and returns the resulting URL as a
string,
+ // unlike `?url` which would treat the file as a raw asset and either inline
its source
+ // as a data URL (assetsInlineLimit) or copy it without bundling its
imports, leaving
+ // unresolved bare specifiers at runtime). In dev mode the SPA shell is
served by the
+ // airflow api-server while Vite serves assets on a different origin, and
+ // `new Worker(crossOriginUrl, { type: "module" })` is rejected by the
browser.
+ // Wrapping the cross-origin URL in a same-origin Blob shim that just
re-imports it
+ // sidesteps the restriction (CORS still permits the inner import). In
production the
+ // worker is same-origin and the shim is harmless.
const workerUrls = Promise.all([
- import("monaco-editor/esm/vs/editor/editor.worker.js?url").then((module)
=> module.default),
-
import("monaco-editor/esm/vs/language/json/json.worker.js?url").then((module)
=> module.default),
+
import("monaco-editor/esm/vs/editor/editor.worker.js?worker&url").then((module)
=> module.default),
+
import("monaco-editor/esm/vs/language/json/json.worker.js?worker&url").then((module)
=> module.default),
]);
const languageContributions = Promise.all([
diff --git a/airflow-core/src/airflow/ui/vite.config.ts
b/airflow-core/src/airflow/ui/vite.config.ts
index 4d1c8dc2932..546e5cc1f02 100644
--- a/airflow-core/src/airflow/ui/vite.config.ts
+++ b/airflow-core/src/airflow/ui/vite.config.ts
@@ -37,7 +37,10 @@ export default defineConfig({
{
name: "transform-url-src",
transformIndexHtml: (html) =>
- html.replace(`src="./assets/`,
`src="./static/assets/`).replace(`href="/`, `href="./`),
+ html
+ .replaceAll(`src="./assets/`, `src="./static/assets/`)
+ .replaceAll(`href="./assets/`, `href="./static/assets/`)
+ .replaceAll(`href="/`, `href="./`),
},
// Keep Monaco's codicon CSS as a real CSS file (rather than inlined into
JS).
// The codicon stylesheet references `codicon.ttf` with a CSS-relative URL
— when