Fix studio frontend build producing empty Tailwind CSS
Two issues caused the studio frontend to render without any styling when installed via `pip install` (non-editable): 1. `pyproject.toml` package-data only included `frontend/dist/**/*`. The `include-package-data = true` setting relies on `git ls-files`, which fails in isolated builds (pip/uv copy source to a temp dir without `.git`). This meant `frontend/src/`, `package.json`, `vite.config.ts`, and other build files were missing from the installed package. Tailwind had no source files to scan. 2. Python venvs auto-create a `.gitignore` with a bare `*` pattern. Tailwind v4's oxide scanner walks parent directories and respects `.gitignore` -- so even when source files are present, the venv's `*` pattern causes the scanner to skip all `.tsx` files. The result is a 34KB CSS skeleton with zero utility classes instead of the expected 265KB. Additionally, Vite adds `crossorigin` to script/link tags by default. This forces CORS mode on font subresource loads, which Firefox HTTPS-Only Mode does not exempt -- causing all @font-face downloads to fail silently when Studio is served over HTTP. Changes: - pyproject.toml: Expand package-data to include frontend source, config files, setup scripts, and backend requirements using glob patterns (no node_modules) - studio/setup.sh: Temporarily hide parent .gitignore files containing a bare `*` during `npm run build`, with trap-based restoration - studio/backend/main.py: Strip `crossorigin` attributes from HTML at serve time so fonts load correctly on any protocol
This commit is contained in:
parent
15e7d0dd5c
commit
a8f02c9f3f
3 changed files with 61 additions and 1 deletions
|
|
@ -41,7 +41,24 @@ version = {attr = "unsloth.models._utils.__version__"}
|
|||
include-package-data = true
|
||||
|
||||
[tool.setuptools.package-data]
|
||||
studio = ["frontend/dist/**/*"]
|
||||
studio = [
|
||||
"*.sh",
|
||||
"*.ps1",
|
||||
"*.bat",
|
||||
"frontend/dist/**/*",
|
||||
"frontend/public/**/*",
|
||||
"frontend/src/**/*",
|
||||
"frontend/*.json",
|
||||
"frontend/*.ts",
|
||||
"frontend/*.js",
|
||||
"frontend/*.lock",
|
||||
"frontend/*.html",
|
||||
"frontend/*.yaml",
|
||||
"frontend/.git*",
|
||||
"backend/requirements/**/*",
|
||||
"backend/core/data_recipe/oxc-validator/*.json",
|
||||
"backend/core/data_recipe/oxc-validator/*.mjs",
|
||||
]
|
||||
|
||||
[tool.setuptools.packages.find]
|
||||
exclude = ["images*", "tests*", "kernels/moe*"]
|
||||
|
|
|
|||
|
|
@ -255,6 +255,22 @@ async def get_hardware_info():
|
|||
# ============ Serve Frontend (Optional) ============
|
||||
|
||||
|
||||
def _strip_crossorigin(html_bytes: bytes) -> bytes:
|
||||
"""Remove ``crossorigin`` attributes from script/link tags.
|
||||
|
||||
Vite adds ``crossorigin`` by default which forces CORS mode on font
|
||||
subresource loads. When Studio is served over plain HTTP, Firefox
|
||||
HTTPS-Only Mode does not exempt CORS font requests -- causing all
|
||||
@font-face downloads to fail silently. Stripping the attribute
|
||||
makes them regular same-origin fetches that work on any protocol.
|
||||
"""
|
||||
import re as _re
|
||||
|
||||
html = html_bytes.decode("utf-8")
|
||||
html = _re.sub(r'\s+crossorigin(?:="[^"]*")?', "", html)
|
||||
return html.encode("utf-8")
|
||||
|
||||
|
||||
def _inject_bootstrap(html_bytes: bytes, app: FastAPI) -> bytes:
|
||||
"""Inject bootstrap credentials into HTML when password change is required.
|
||||
|
||||
|
|
@ -296,6 +312,7 @@ def setup_frontend(app: FastAPI, build_path: Path):
|
|||
@app.get("/")
|
||||
async def serve_root():
|
||||
content = (build_path / "index.html").read_bytes()
|
||||
content = _strip_crossorigin(content)
|
||||
content = _inject_bootstrap(content, app)
|
||||
return Response(
|
||||
content = content,
|
||||
|
|
@ -319,6 +336,7 @@ def setup_frontend(app: FastAPI, build_path: Path):
|
|||
|
||||
# Serve index.html as bytes — avoids Content-Length mismatch
|
||||
content = (build_path / "index.html").read_bytes()
|
||||
content = _strip_crossorigin(content)
|
||||
content = _inject_bootstrap(content, app)
|
||||
return Response(
|
||||
content = content,
|
||||
|
|
|
|||
|
|
@ -121,8 +121,33 @@ echo "✅ Node $(node -v) | npm $(npm -v)"
|
|||
echo ""
|
||||
echo "Building frontend..."
|
||||
cd "$SCRIPT_DIR/frontend"
|
||||
|
||||
# Tailwind v4's oxide scanner respects .gitignore in parent directories.
|
||||
# Python venvs create a .gitignore with "*" (ignore everything), which
|
||||
# prevents Tailwind from scanning .tsx source files for class names.
|
||||
# Temporarily hide any such .gitignore during the build, then restore it.
|
||||
_HIDDEN_GITIGNORES=()
|
||||
_dir="$(pwd)"
|
||||
while [ "$_dir" != "/" ]; do
|
||||
_dir="$(dirname "$_dir")"
|
||||
if [ -f "$_dir/.gitignore" ] && grep -qx '\*' "$_dir/.gitignore" 2>/dev/null; then
|
||||
mv "$_dir/.gitignore" "$_dir/.gitignore._twbuild"
|
||||
_HIDDEN_GITIGNORES+=("$_dir/.gitignore")
|
||||
fi
|
||||
done
|
||||
|
||||
_restore_gitignores() {
|
||||
for _gi in "${_HIDDEN_GITIGNORES[@]}"; do
|
||||
mv "${_gi}._twbuild" "$_gi" 2>/dev/null || true
|
||||
done
|
||||
}
|
||||
trap _restore_gitignores EXIT
|
||||
|
||||
run_quiet "npm install" npm install
|
||||
run_quiet "npm run build" npm run build
|
||||
|
||||
_restore_gitignores
|
||||
trap - EXIT
|
||||
cd "$SCRIPT_DIR/backend/core/data_recipe/oxc-validator"
|
||||
run_quiet "npm install (oxc validator runtime)" npm install
|
||||
cd "$SCRIPT_DIR"
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue