diff --git a/.github/renovate.json b/.github/renovate.json
index 9b85094..26d9ddf 100644
--- a/.github/renovate.json
+++ b/.github/renovate.json
@@ -7,6 +7,7 @@
":pinVersions"
],
"enabledManagers": [
- "github-actions"
+ "pip_requirements",
+ "github-actions"
]
}
diff --git a/.github/scripts/build.py b/.github/scripts/build.py
deleted file mode 100755
index b936554..0000000
--- a/.github/scripts/build.py
+++ /dev/null
@@ -1,196 +0,0 @@
-#!/usr/bin/env python3
-"""
-Build script for marimo notebooks.
-
-This script exports marimo notebooks to HTML/WebAssembly format and generates
-an index.html file that lists all the notebooks. It handles both regular notebooks
-(from the notebooks/ directory) and apps (from the apps/ directory).
-
-The script can be run from the command line with optional arguments:
- python .github/scripts/build.py [--output-dir OUTPUT_DIR]
-
-The exported files will be placed in the specified output directory (default: _site).
-"""
-
-import os
-import subprocess
-import argparse
-from typing import List, Optional, Dict, Any, Union
-from pathlib import Path
-
-
-def export_html_wasm(notebook_path: str, output_dir: str, as_app: bool = False) -> bool:
- """Export a single marimo notebook to HTML/WebAssembly format.
-
- This function takes a marimo notebook (.py file) and exports it to HTML/WebAssembly format.
- If as_app is True, the notebook is exported in "run" mode with code hidden, suitable for
- applications. Otherwise, it's exported in "edit" mode, suitable for interactive notebooks.
-
- Args:
- notebook_path (str): Path to the marimo notebook (.py file) to export
- output_dir (str): Directory where the exported HTML file will be saved
- as_app (bool, optional): Whether to export as an app (run mode) or notebook (edit mode).
- Defaults to False.
-
- Returns:
- bool: True if export succeeded, False otherwise
- """
- # Convert .py extension to .html for the output file
- output_path: str = notebook_path.replace(".py", ".html")
-
- # Base command for marimo export
- cmd: List[str] = ["marimo", "export", "html-wasm"]
-
- # Configure export mode based on whether it's an app or a notebook
- if as_app:
- print(f"Exporting {notebook_path} to {output_path} as app")
- cmd.extend(["--mode", "run", "--no-show-code"]) # Apps run in "run" mode with hidden code
- else:
- print(f"Exporting {notebook_path} to {output_path} as notebook")
- cmd.extend(["--mode", "edit"]) # Notebooks run in "edit" mode
-
- try:
- # Create full output path and ensure directory exists
- output_file: str = os.path.join(output_dir, output_path)
- os.makedirs(os.path.dirname(output_file), exist_ok=True)
-
- # Add notebook path and output file to command
- cmd.extend([notebook_path, "-o", output_file])
-
- # Run marimo export command
- print(cmd)
- subprocess.run(cmd, capture_output=True, text=True, check=True)
- return True
- except subprocess.CalledProcessError as e:
- # Handle marimo export errors
- print(f"Error exporting {notebook_path}:")
- print(e.stderr)
- return False
- except Exception as e:
- # Handle unexpected errors
- print(f"Unexpected error exporting {notebook_path}: {e}")
- return False
-
-
-def generate_index(all_notebooks: List[str], output_dir: str) -> None:
- """Generate an index.html file that lists all the notebooks.
-
- This function creates an HTML index page that displays links to all the exported
- notebooks. The index page includes the marimo logo and displays each notebook
- with a formatted title and a link to open it.
-
- Args:
- all_notebooks (List[str]): List of paths to all the notebooks
- output_dir (str): Directory where the index.html file will be saved
-
- Returns:
- None
- """
- print("Generating index.html")
-
- # Create the full path for the index.html file
- index_path: str = os.path.join(output_dir, "index.html")
-
- # Ensure the output directory exists
- os.makedirs(output_dir, exist_ok=True)
-
- try:
- # Open the index.html file for writing
- with open(index_path, "w") as f:
- # Write the HTML header and page structure
- f.write(
- """
-
-
-
-
- marimo
-
-
-
-
-
-
-
-"""
- )
- # Process each notebook and create a card for it
- for notebook in all_notebooks:
- # Extract the notebook name from the path and remove the .py extension
- notebook_name: str = notebook.split("/")[-1].replace(".py", "")
-
- # Format the display name by replacing underscores with spaces and capitalizing
- display_name: str = notebook_name.replace("_", " ").title()
-
- # Write the HTML for the notebook card
- f.write(
- f'
\n"
- )
- # Write the HTML footer
- f.write(
- """
-
-"""
- )
- except IOError as e:
- # Handle file I/O errors
- print(f"Error generating index.html: {e}")
-
-
-def main() -> None:
- """Main function to build marimo notebooks.
-
- This function:
- 1. Parses command line arguments
- 2. Finds all marimo notebooks in the 'notebooks' and 'apps' directories
- 3. Exports each notebook to HTML/WebAssembly format
- 4. Generates an index.html file that lists all the notebooks
-
- Command line arguments:
- --output-dir: Directory where the exported files will be saved (default: _site)
-
- Returns:
- None
- """
- # Set up command line argument parsing
- parser: argparse.ArgumentParser = argparse.ArgumentParser(description="Build marimo notebooks")
- parser.add_argument(
- "--output-dir", default="_site", help="Output directory for built files"
- )
- args: argparse.Namespace = parser.parse_args()
-
- # Initialize empty list to store all notebook paths
- all_notebooks: List[str] = []
-
- # Look for notebooks in both the notebooks/ and apps/ directories
- for directory in ["notebooks", "apps"]:
- dir_path: Path = Path(directory)
- if not dir_path.exists():
- print(f"Warning: Directory not found: {dir_path}")
- continue
-
- # Find all Python files recursively in the directory
- all_notebooks.extend(str(path) for path in dir_path.rglob("*.py"))
-
- # Exit if no notebooks were found
- if not all_notebooks:
- print("No notebooks found!")
- return
-
- # Export each notebook to HTML/WebAssembly format
- # Files in the apps/ directory are exported as apps (run mode)
- # Files in the notebooks/ directory are exported as notebooks (edit mode)
- for nb in all_notebooks:
- export_html_wasm(nb, args.output_dir, as_app=nb.startswith("apps/"))
-
- # Generate the index.html file that lists all notebooks
- generate_index(all_notebooks, args.output_dir)
-
-
-if __name__ == "__main__":
- main()
diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml
index 73dca23..adeeb7b 100644
--- a/.github/workflows/deploy.yml
+++ b/.github/workflows/deploy.yml
@@ -26,25 +26,9 @@ jobs:
# Check out the repository code
- uses: actions/checkout@v4
- # Install uv package manager for faster Python package installation
- - name: ๐ Install uv
- uses: astral-sh/setup-uv@v6
-
- # Set up Python environment
- - name: ๐ Set up Python
- uses: actions/setup-python@v5
- with:
- python-version: 3.12 # Use Python 3.12 for the build
-
- # Install marimo and other required dependencies
- - name: ๐ฆ Install dependencies
- run: |
- uv pip install marimo
-
- # Run the build script to export notebooks to WebAssembly
- - name: ๐ ๏ธ Export notebooks
+ - name: Run the dryrun command
run: |
- python .github/scripts/build.py # This script exports all notebooks to the _site directory
+ make dryrun
# Upload the generated site as an artifact for the deploy job
- name: ๐ค Upload artifact
@@ -52,6 +36,8 @@ jobs:
with:
path: _site # Directory containing the built site
+
+
# The deploy job publishes the built site to GitHub Pages
deploy:
needs: build # This job depends on the build job completing successfully
@@ -72,3 +58,7 @@ jobs:
- name: ๐ Deploy to GitHub Pages
id: deployment # ID used to reference this step's outputs
uses: actions/deploy-pages@v4 # GitHub's official Pages deployment action
+
+ - name: Display address
+ run: |
+ echo ${{ steps.deployment.outputs.page_url }}
diff --git a/.github/workflows/index_template.html b/.github/workflows/index_template.html
new file mode 100644
index 0000000..ad7e91a
--- /dev/null
+++ b/.github/workflows/index_template.html
@@ -0,0 +1,242 @@
+
+
+
+
+
+ marimo WebAssembly + GitHub Pages Template
+
+
+
+
+
+
+
+
๐ marimo WebAssembly + GitHub Pages Template
+
Interactive Python notebooks exported to WebAssembly and deployed to GitHub Pages
+
+
+
+
+
+
+
+
+
+
+
Notebooks
+
Interactive notebooks in edit mode - you can modify and experiment with the code
+
+
+
+
+
fibonacci
+
+
Interactive Fibonacci sequence calculator with a slider to adjust the number of sequence elements.
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.gitignore b/.gitignore
index 5d79d07..c4ab7a1 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,4 +1,6 @@
_site/
+__marimo__
+tmp
# Byte-compiled / optimized / DLL files
__pycache__/
diff --git a/Makefile b/Makefile
new file mode 100644
index 0000000..4f1cb06
--- /dev/null
+++ b/Makefile
@@ -0,0 +1,64 @@
+# This file contains commands for setting up the environment, formatting code,
+# building the book, and other maintenance tasks.
+
+.DEFAULT_GOAL := help
+
+# Install uv and uvx
+uv:
+uv:
+ @which uv > /dev/null || (curl -LsSf https://astral.sh/uv/install.sh | sh > /dev/null 2>&1)
+
+# Display help information about available make targets
+.PHONY: help
+help: ## Display this help screen
+ @echo -e "\033[1mAvailable commands:\033[0m"
+ @grep -E '^[a-z.A-Z_-]+:.*?## .*$$' $(MAKEFILE_LIST) | awk 'BEGIN {FS = ":.*?## "}; {printf " \033[36m%-18s\033[0m %s\n", $$1, $$2}' | sort
+
+# Install and run Marimo for interactive notebooks
+.PHONY: notebook
+notebook: uv ## Export one notebook with NOTEBOOK=...
+ @mkdir -p _site/notebooks/$(NOTEBOOK)
+ @uvx marimo export html-wasm --sandbox notebooks/$(NOTEBOOK).py --mode edit -o _site/notebooks/$(NOTEBOOK)
+
+# Export all notebooks to HTML-WASM
+.PHONY: all-notebooks
+all-notebooks: ## Export all notebooks to HTML-WASM
+ @for notebook in notebooks/*.py; do \
+ filename=$$(basename "$$notebook" .py); \
+ echo "Exporting $$notebook to HTML-WASM..."; \
+ echo $$filename; \
+ $(MAKE) notebook NOTEBOOK=$$filename; \
+ done
+
+# Install and run Marimo for interactive notebooks
+.PHONY: app
+app: uv ## Export one app with APP=...
+ @mkdir -p _site/apps/$(APP)
+ @uvx marimo export html-wasm --sandbox apps/$(APP).py --mode run --no-show-code -o _site/apps/$(APP)
+
+# Export all apps to HTML-WASM
+.PHONY: all-apps
+all-apps: ## Export all apps to HTML-WASM
+ @for app in apps/*.py; do \
+ filename=$$(basename "$$app" .py); \
+ echo "Exporting $$app to HTML-WASM..."; \
+ echo $$filename; \
+ $(MAKE) app APP=$$filename; \
+ done
+
+# Build the website locally (dry run) without deploying to GitHub Pages
+.PHONY: dryrun
+dryrun: ## Build the website locally using the same process as CI/CD but publish to a local folder
+ @echo "Building website locally (dry run)..."
+ @mkdir -p _site/assets
+
+ $(MAKE) all-notebooks
+ $(MAKE) all-apps
+
+ @echo "Copying assets..."
+ @cp -r apps/public/logo.png _site/assets/logo.png
+
+ @echo "Generating index.html..."
+ @cp .github/workflows/index_template.html _site/index.html
+
+ @echo "Website built successfully! Open _site/index.html in your browser to view it."
diff --git a/README.md b/README.md
index dc2630d..7c2b5a6 100644
--- a/README.md
+++ b/README.md
@@ -1,52 +1,103 @@
-# marimo WebAssembly + GitHub Pages Template
+# ๐ marimo WebAssembly + GitHub Pages Template
-This template repository demonstrates how to export [marimo](https://marimo.io) notebooks to WebAssembly and deploy them to GitHub Pages.
+[](https://www.python.org/downloads/)
+[](LICENSE)
+[](https://pages.github.com/)
+[](https://marimo.io)
+[](https://github.com/features/actions)
+[](https://github.com/astral-sh/uv)
+
+This template repository demonstrates how to export [marimo](https://marimo.io) notebooks to WebAssembly and
+deploy them to GitHub Pages. Marimo is an interactive Python notebook that combines the flexibility
+of a notebook with the reproducibility of a script.
+
+## ๐งฐ Prerequisites
+
+- Python 3.12 or higher
+- GitHub account (for deployment)
## ๐ Included Examples
-- `apps/charts.py`: Interactive data visualization with Altair
-- `notebooks/fibonacci.py`: Interactive Fibonacci sequence calculator
-- `notebooks/penguins.py`: Interactive data analysis with Polars and marimo
+- **`apps/charts.py`**: Interactive data visualization dashboard with Altair charts. This app is exported in "run" mode, which hides the code and presents a clean user interface.
+- **`notebooks/fibonacci.py`**: Interactive Fibonacci sequence calculator with a slider to adjust the number of sequence elements. This notebook is exported in "edit" mode, allowing users to modify the code.
+- **`notebooks/penguins.py`**: Interactive data analysis of the Palmer Penguins dataset using Polars and marimo. Demonstrates data loading, filtering, and visualization.
+
+## ๐ Deployment
-## ๐ Usage
+### Automatic Deployment with GitHub Actions
1. Fork this repository
2. Add your marimo files to the `notebooks/` or `apps/` directory
- 1. `notebooks/` notebooks are exported with `--mode edit`
- 2. `apps/` notebooks are exported with `--mode run`
-3. Push to main branch
+3. Push to the main branch
4. Go to repository **Settings > Pages** and change the "Source" dropdown to "GitHub Actions"
5. GitHub Actions will automatically build and deploy to Pages
-## Including data or assets
+### How the Deployment Works
-To include data or assets in your notebooks, add them to the `public/` directory.
+- Notebooks in the `notebooks/` directory are exported with `--mode edit` (users can modify the code)
+- Notebooks in the `apps/` directory are exported with `--mode run --no-show-code` (code is hidden, only UI is shown)
+- The GitHub Actions workflow creates an index page and deploys everything to GitHub Pages
-For example, the `apps/charts.py` notebook loads an image asset from the `public/` directory.
+## ๐ป Local Development
-```markdown
-
+### Setting Up the Environment
+
+This project uses [uv](https://github.com/astral-sh/uv) for package management. The Makefile includes commands to help you get started:
+
+```bash
+# Install uv package manager
+make uv
+
+# Run a specific notebook in edit mode
+make notebook NOTEBOOK=fibonacci
+
+# Run a specific app in edit mode
+make app APP=charts
+
+# Build the entire website locally (dry run)
+make dryrun
```
-And the `notebooks/penguins.py` notebook loads a CSV dataset from the `public/` directory.
+The `dryrun` command builds the entire website locally using the same process as the CI/CD pipeline but publishes it to a local `_site` folder instead of deploying to GitHub Pages. This is useful for previewing the complete website before pushing changes.
+
+### Creating New Notebooks
+
+1. Add your Python file to either the `notebooks/` or `apps/` directory
+2. Include the necessary dependencies at the top of your file:
```python
-import polars as pl
-df = pl.read_csv(mo.notebook_location() / "public" / "penguins.csv")
+# /// script
+# requires-python = ">=3.12"
+# dependencies = [
+# "marimo>=0.13.15",
+# # Add other dependencies here
+# ]
+# ///
```
-## ๐งช Testing
+## ๐ Including Data or Assets
-To test the export process, run `.github/scripts/build.py` from the root directory.
+To include data or assets in your notebooks, add them to the `public/` directory within either the `notebooks/` or `apps/` directory.
-```bash
-python .github/scripts/build.py
+### Examples:
+
+#### Loading an image:
+
+```markdown
+
```
-This will export all notebooks in a folder called `_site/` in the root directory. Then to serve the site, run:
+#### Loading a CSV dataset:
-```bash
-python -m http.server -d _site
+```python
+import polars as pl
+df = pl.read_csv(mo.notebook_location() / "public" / "penguins.csv")
```
-This will serve the site at `http://localhost:8000`.
+## ๐ค Contributing
+
+Contributions are welcome! Please feel free to submit a Pull Request.
+
+## ๐ License
+
+This project is licensed under the terms included in the [LICENSE](LICENSE) file.
diff --git a/apps/charts.py b/apps/charts.py
index e9d6a88..5b8f022 100644
--- a/apps/charts.py
+++ b/apps/charts.py
@@ -1,20 +1,26 @@
+# /// script
+# requires-python = ">=3.12"
+# dependencies = [
+# "marimo==0.13.15",
+# "altair==4.2.0",
+# "pandas==2.3.0",
+# "numpy==2.3.0"
+# ]
+# ///
import marimo
__generated_with = "0.10.9"
app = marimo.App(width="medium")
-
-@app.cell
-def _():
+with app.setup:
import numpy as np
import altair as alt
import pandas as pd
import marimo as mo
- return alt, mo, np, pd
@app.cell
-def _(mo):
+def _():
mo.md(
"""
# Interactive Data Visualization
@@ -29,7 +35,7 @@ def _(mo):
@app.cell
-def _(alt, mo, np, pd):
+def _():
# Create sample data
data = pd.DataFrame({"x": np.arange(100), "y": np.random.normal(0, 1, 100)})
@@ -43,7 +49,7 @@ def _(alt, mo, np, pd):
)
)
chart
- return chart, data
+ return chart
@app.cell
diff --git a/notebooks/fibonacci.py b/notebooks/fibonacci.py
index 5997136..c4c8d1d 100644
--- a/notebooks/fibonacci.py
+++ b/notebooks/fibonacci.py
@@ -1,11 +1,19 @@
+# /// script
+# requires-python = ">=3.12"
+# dependencies = [
+# "marimo==0.13.15",
+# ]
+# ///
import marimo
__generated_with = "0.10.6"
app = marimo.App()
-
+with app.setup:
+ import marimo as mo
+
@app.cell
-def _(mo):
+def _():
mo.md(
r"""
# Fibonacci Calculator
@@ -17,37 +25,25 @@ def _(mo):
@app.cell
-def _(mo):
+def _():
# Create an interactive slider
n = mo.ui.slider(1, 100, value=50, label="Number of Fibonacci numbers")
n
- return (n,)
+ return n
@app.cell
-def _(fibonacci, mo, n):
+def _(n):
fib = fibonacci(n.value)
mo.md(", ".join([str(f) for f in fib]))
- return (fib,)
-
-
-@app.cell
-def _():
- # Generate Fibonacci sequence
- def fibonacci(n):
- sequence = [0, 1]
- for i in range(2, n):
- sequence.append(sequence[i - 1] + sequence[i - 2])
- return sequence
- return (fibonacci,)
-
-
-@app.cell
-def _():
- import numpy as np
- import marimo as mo
- return mo, np
-
+
+@app.function
+def fibonacci(n):
+ sequence = [0, 1]
+ for i in range(2, n):
+ sequence.append(sequence[i - 1] + sequence[i - 2])
+ return sequence
+
if __name__ == "__main__":
app.run()
diff --git a/notebooks/penguins.py b/notebooks/penguins.py
index 064a562..dd01ba6 100644
--- a/notebooks/penguins.py
+++ b/notebooks/penguins.py
@@ -1,19 +1,27 @@
+# /// script
+# requires-python = ">=3.12"
+# dependencies = [
+# "marimo==0.13.15",
+# "polars==1.30.0",
+# "altair==4.2.0",
+# "pandas==2.3.0",
+# ]
+# ///
import marimo
-__generated_with = "0.10.9"
+__generated_with = "0.13.5"
app = marimo.App(width="medium")
-
-@app.cell
-def _():
- import polars as pl
+with app.setup:
import marimo as mo
+ import polars as pl
import altair as alt
- return alt, mo, pl
+ import pandas as pd
+ file = mo.notebook_location() / "public" / "penguins.csv"
@app.cell(hide_code=True)
-def _(mo):
+def _():
mo.md(
"""
# Palmer Penguins Analysis
@@ -25,15 +33,20 @@ def _(mo):
@app.cell
-def _(mo, pl):
+def _():
# Read the penguins dataset
- df = pl.read_csv(str(mo.notebook_location() / "public" / "penguins.csv"))
+ df = pl.read_csv(str(file))
df.head()
return (df,)
+@app.cell
+def _():
+ # Try to avoid reading the file with pandas
+ _df = pd.read_csv(str(file))
+ return
@app.cell
-def _(df, mo):
+def _(df):
# Basic statistics
mo.md(f"""
### Dataset Overview
@@ -49,13 +62,13 @@ def _(df, mo):
@app.cell(hide_code=True)
-def _(mo):
+def _():
mo.md(r"""### Species Distribution""")
return
@app.cell
-def _(alt, df, mo):
+def _(df):
# Create species distribution chart
species_chart = mo.ui.altair_chart(
alt.Chart(df)
@@ -70,13 +83,13 @@ def _(alt, df, mo):
@app.cell(hide_code=True)
-def _(mo):
+def _():
mo.md(r"""### Bill Dimensions Analysis""")
return
@app.cell
-def _(alt, df, mo):
+def _(df):
# Scatter plot of bill dimensions
scatter = mo.ui.altair_chart(
alt.Chart(df)