Metadata-Version: 2.4
Name: nicegui-legacy-browser
Version: 0.1.1
Summary: Legacy browser support for NiceGUI 3.8: classic-script + es-module-shims loading paths for old browsers (Chrome 56+, Firefox 52+, iOS 12 Safari)
Author: NiceGUI Legacy Browser Contributors
License: MIT
Keywords: nicegui,legacy,polyfill,chromecast,ios12,old-browser
Requires-Python: >=3.8
Description-Content-Type: text/markdown
Requires-Dist: nicegui<3.9,>=3.7
Provides-Extra: build
Requires-Dist: nodeenv>=1.8; extra == "build"
Provides-Extra: test
Requires-Dist: pytest>=7; extra == "test"
Requires-Dist: pytest-asyncio>=0.21; extra == "test"
Requires-Dist: httpx>=0.24; extra == "test"
Requires-Dist: selenium>=4; extra == "test"

# nicegui-legacy-browser

Legacy browser support for [NiceGUI](https://nicegui.io/) 3.8.x — an ASGI middleware (polyfills, JS transpilation/bundling, CSS transforms) that makes NiceGUI run on old browsers via two auto-selected loading paths. Verified floor: **Chrome 56+, Firefox 52+, iOS 12 Safari** (see the status note below for what's tested vs. aspirational).

> **Status (2026-07):** two loading paths, auto-selected by the browser's User-Agent.
> - **Native-ES-module browsers** (Safari 12+, Chrome 61+, modern) → the **es-module-shims** path (validated incl. real iOS 12 Safari).
> - **No-native-module browsers** (Chrome 55–60, Firefox <60, Chromecast) → a **classic-script bundle** — verified mounting + interactive on **Chromium 56, Firefox 52, and real iOS 12 Safari**.
>
> **The floor is Chrome ~55/56, not 49** (the bundler can't lower syntax further without an added Babel pass), so **Chrome 49 / Chromecast-1st-gen is not verified** — treat those rows below as aspirational. **Lazy-loaded components** now load on the classic path via prebuilt per-component bundles (`static/_classic/components/`): **leaflet** is verified mounting on Chromium 56 (both eager and on-demand). aggrid/echart/plotly/mermaid build clean the same way but are deferred (browser-unverified, 1.5–7 MB) — uncomment a line in `build_classic.sh` to ship one. See [`FINDINGS.md`](FINDINGS.md) and issue #2 for the per-component table.

## Why?

NiceGUI 3.8.0 uses modern web features that break in old browsers:

| Feature | Required by | Minimum Browser |
|---------|------------|-----------------|
| `<script type="importmap">` | Vue loading | Chrome 89, Safari 16.4 |
| `<script type="module">` | Vue/component loading | Chrome 61, Safari 11 |
| CSS `@layer` | Style ordering | Chrome 99, Safari 15.4 |
| Optional chaining `?.` | nicegui.js, Vue 3, components | Chrome 80, Safari 13.1 |
| Nullish coalescing `??` | nicegui.js | Chrome 80, Safari 13.1 |
| `globalThis` | Vue setup | Chrome 71, Safari 12.1 |
| `async`/`await` | Event handling | Chrome 55, Safari 10.1 |
| `Object.entries()` | nicegui.js | Chrome 54 |
| `String.replaceAll()` | nicegui.js | Chrome 85, Safari 13.1 |

This package bridges the gap by:

1. **Transpiling** all JS (Vue 3, Quasar, nicegui.js, components) via Babel to remove modern syntax
2. **Polyfilling** missing APIs (`globalThis`, `Object.entries`, `replaceAll`, etc.)
3. **Shimming** ES modules and import maps via [es-module-shims](https://github.com/nicegui-shims/es-module-shims)
4. **Transforming** CSS to remove `@layer`, logical properties, `revert-layer`, etc.

## Hard Requirements

Vue 3's reactivity system **requires** native `Proxy` support. This cannot be polyfilled. Therefore:

- **Chrome 49+** ✅ (Proxy added in Chrome 49)
- **Safari 10+** / **iOS 10+** ✅
- **IE 11** ❌ (No Proxy — fundamentally impossible)

## Quick Start

```bash
pip install nicegui-legacy-browser     # pulls in a compatible nicegui
```

```python
from nicegui import ui
from nicegui_legacy_browser import enable

enable()  # call BEFORE ui.run()

@ui.page('/')
def index():
    ui.label('Works in old browsers!')

ui.run()
```

That's it — no build step. The transpiled/bundled JS ships **prebuilt** inside the
package, so there is **no Node.js requirement** at install or runtime. `enable()`
picks the right loading path per browser automatically (see below). You only need
the `nicegui-legacy-build` command (and Node) to *regenerate* the bundles after
bumping NiceGUI — see [Regenerating the bundles](#regenerating-the-bundles).

## How It Works

### Architecture

```
Browser Request
    │
    ▼
┌─────────────────────────────┐
│  LegacyBrowserMiddleware    │  ASGI middleware wrapping NiceGUI
│  (intercepts all responses) │
├─────────────────────────────┤
│  HTML Transform:            │
│  • Inject polyfills.js      │  ← globalThis, Object.entries, replaceAll, ...
│  • Inject es-module-shims   │  ← Polyfills import maps for old browsers
│  • importmap → importmap-shim
│  • module → module-shim     │
│  • Strip CSS @layer         │
│  • Remove modulepreload     │
│  • Remove Tailwind v4 syntax│
├─────────────────────────────┤
│  JS Transform:              │
│  • Serve Babel-transpiled   │  ← ?. → null checks, ?? → ternaries,
│    versions of all .js      │     async/await → generators
├─────────────────────────────┤
│  CSS Transform:             │
│  • Strip @layer             │
│  • revert-layer → revert    │
│  • Logical props → physical │  ← margin-block-start → margin-top
│  • Remove :dir()            │
└─────────────────────────────┘
    │
    ▼
  NiceGUI App (unmodified)
```

### Prebuilt bundles (no build needed to use)

The lowered JS ships **committed inside the package** — both the `static/_transpiled/`
files (Babel-transpiled originals, for the es-module-shims path) and the
`static/_classic/` bundles (esbuild classic-script bundles, for no-native-module
browsers). Installing the package is enough; `enable()` serves them directly. Node.js
is **not** a dependency of normal use — it lives behind the optional `[build]` extra
and is only needed to *regenerate* the bundles (below).

### No Hooks Required in NiceGUI

This package uses **standard ASGI middleware** via `app.add_middleware()`. No modifications to NiceGUI's source code are needed. The middleware:

1. Intercepts HTML responses to inject polyfills and transform inline CSS
2. Intercepts JS responses to serve transpiled versions
3. Intercepts CSS responses to strip `@layer` and transform logical properties
4. Serves its own static files (polyfills.js, es-module-shims.js)

## Browser Compatibility Matrix

| Browser | Version | Status | Notes |
|---------|---------|--------|-------|
| Chrome | 49-54 | ⚠️ Unverified | Below the classic-bundle floor (~55/56); needs a Babel pass — see issue #3 |
| Chrome | 55-60 | ✅ Works | Classic-script bundle (verified on Chromium 56) |
| Chrome | 61-79 | ✅ Works | Import maps shimmed |
| Chrome | 80-88 | ✅ Works | Import maps shimmed |
| Chrome | 89+ | ✅ Works | Native import maps |
| Safari | 12 (iOS 12) | ✅ Works | Import maps shimmed |
| Safari | 13-13.1 | ✅ Works | Import maps shimmed |
| Safari | 14-16.3 | ✅ Works | Import maps shimmed |
| Safari | 16.4+ | ✅ Works | Native import maps |
| Chromecast 1st gen | ~Chrome 49-55 | ⚠️ Unverified | Aspirational; ~Chrome 49 is below the classic floor — see issue #3 |
| IE 11 | — | ❌ Impossible | No Proxy support |

## Configuration

```python
enable(
    transpiled_dir=None,           # Path to pre-transpiled JS (auto-detected)
    legacy_static_prefix='/_nicegui_legacy',  # URL prefix for polyfill files
    auto_build=True,               # Auto-run build if transpiled files missing
    target='default',              # Browser target: chrome49, chrome55, safari12, default
)
```

### Build Targets

| Target | Transforms | Use Case |
|--------|-----------|----------|
| `default` | Everything (chrome 49 + safari 12) | Maximum compatibility |
| `chrome49` | async/await + all ES2020+ | Chromecast 1st gen |
| `chrome55` | ES2020+ only (keeps async/await) | Chromecast 2nd gen |
| `chrome61` | ES2020+ only (keeps modules) | Older desktops |
| `safari12` | ES2020+ only | iOS 12 devices |

## Known Limitations

1. **Tailwind CSS v4**: Not supported in old browsers. The JIT compiler requires Chrome 111+. Use Tailwind v3 or UnoCSS with a compatible preset instead.

2. **Mermaid diagrams**: Some mermaid dist files (3 out of ~40) fail Babel transpilation due to complex generator patterns. The originals are used as fallback — they may not work in Chrome < 55.

3. **WebGL (3D scenes)**: `ui.scene()` requires WebGL which has varying support on old hardware. Chromecast 1st gen does not support WebGL.

4. **CSS `gap` in flexbox**: Not supported in Chrome < 84. Some spacing may be slightly off. This is a cosmetic issue only.

5. **Performance**: The middleware adds ~1ms of overhead per HTML response for the transform. JS/CSS transforms add negligible overhead due to caching.

6. **File size**: Polyfills add ~64KB (es-module-shims) + ~12KB (polyfills.js) to the initial page load.

## Testing with Old Browsers

### On x86_64 Linux (with Docker)

```bash
# Download old Chromium snapshot for testing
# Chrome 55 (last without async/await native support)
docker run -d -p 4444:4444 selenium/standalone-chrome:3.141.59

# Or download a Chromium binary directly:
# 1. Find revision: https://omahaproxy.appspot.com (version 55.0.2883.87 → revision ~433059)
# 2. Download: https://commondatastorage.googleapis.com/chromium-browser-snapshots/Linux_x64/433059/chrome-linux.zip
```

### On macOS

Use Safari's Responsive Design Mode or install old Safari versions via Xcode.

### On actual devices

For Chromecast testing:
```bash
pip install catt
catt cast_site http://your-server:8080/
```

## Development

```bash
git clone <repo>
cd nicegui-legacy-browser
pip install -e ".[test]"    # bundles are committed; no build needed to run tests
pytest tests/ -v
```

To regenerate the bundles after a NiceGUI bump, see
[Regenerating the bundles](#regenerating-the-bundles) (adds the `[build]` extra + Node).

## Version Compatibility

Pinned to `nicegui>=3.7,<3.9`. The shipped bundles (`static/_transpiled/`,
`static/_classic/`) are lowered copies of a **specific** NiceGUI release's static
assets, so they are coupled to that release's JS. A NiceGUI bump can reshape that
markup — the classic transform will **fail loudly** if it no longer recognises the
page (rather than silently serving a broken page), signalling a regenerate is due.

## Regenerating the bundles

Only needed after bumping NiceGUI (or to add a lazy component). Requires Node.js,
pulled in via the optional `[build]` extra:

```bash
pip install -e ".[build]"
nicegui-legacy-build --target default                 # es-module-shims path (Babel)
bash nicegui_legacy_browser/build/build_classic.sh    # classic-bundle path (esbuild)
```

Commit the regenerated `static/_transpiled/` + `static/_classic/` so users still
get a build-free install.

## License

MIT
