diff --git a/docs/guides/exporting.md b/docs/guides/exporting.md index 5da57d3b89f..e6a9c8da590 100644 --- a/docs/guides/exporting.md +++ b/docs/guides/exporting.md @@ -163,6 +163,10 @@ python -m http.server ### Deploying to GitHub Pages +::: tip "Template repository" + + You can fork our [template repository](https://github.com/marimo-team/marimo-gh-pages-template) for deploying multiple notebooks to GitHub Pages. Once you have forked the repository, add your notebooks to the `notebooks`/`apps` directory. + You can deploy your WebAssembly marimo notebook to GitHub Pages using the following GitHub Actions workflow: ```yaml @@ -201,6 +205,31 @@ jobs: artifact_name: github-pages ``` +### Exporting multiple notebooks + +In order to export multiple notebooks under the same folder, you can use the following snippet: + +```bash +files=("batch_and_form.py" "data_explorer.py") + +for file in "${files[@]}"; do + without_extension="${file%.*}" + marimo export html-wasm "$file" -o public/"$without_extension".html --mode run +done +``` + +Optionally, create an `index.html` file in the public directory: + +```bash +# Optionally, create an index.html file in the public directory +echo "" >> public/index.html +``` + ## 🏝️ Embed marimo outputs in HTML using Islands !!! note "Preview" diff --git a/marimo/_cli/export/commands.py b/marimo/_cli/export/commands.py index 4a0acc49971..0b44f015f1b 100644 --- a/marimo/_cli/export/commands.py +++ b/marimo/_cli/export/commands.py @@ -393,30 +393,32 @@ def html_wasm( show_code: bool, ) -> None: """Export a notebook as a WASM-powered standalone HTML file.""" + out_dir = output + filename = "index.html" + ignore_index_html = False + # If ends with .html, get the directory + if output.endswith(".html"): + out_dir = os.path.dirname(output) + filename = os.path.basename(output) + ignore_index_html = True def export_callback(file_path: MarimoPath) -> ExportResult: return export_as_wasm(file_path, mode, show_code=show_code) - # Validate output is not a file - if os.path.isfile(output): - raise click.UsageError( - f"Output {output} is a file, but must be a directory." - ) - # Export assets first - Exporter().export_assets(output) + Exporter().export_assets(out_dir, ignore_index_html=ignore_index_html) echo( - f"Assets copied to {green(output)}. These assets are required for the " + f"Assets copied to {green(out_dir)}. These assets are required for the " "notebook to run in the browser." ) echo( "To run the exported notebook, use:\n" - f" python -m http.server --directory {output}\n" + f" python -m http.server --directory {out_dir}\n" "Then open the URL that is printed to your terminal." ) - outfile = os.path.join(output, "index.html") + outfile = os.path.join(out_dir, filename) return watch_and_export(MarimoPath(name), outfile, False, export_callback) diff --git a/marimo/_server/export/exporter.py b/marimo/_server/export/exporter.py index 8e24952d1b3..2d8dfdf834a 100644 --- a/marimo/_server/export/exporter.py +++ b/marimo/_server/export/exporter.py @@ -356,7 +356,9 @@ def export_as_wasm( return html, download_filename - def export_assets(self, directory: str) -> None: + def export_assets( + self, directory: str, ignore_index_html: bool = False + ) -> None: # Copy assets to the same directory as the notebook dirpath = Path(directory) LOGGER.debug(f"Copying assets to {dirpath}") @@ -365,7 +367,16 @@ def export_assets(self, directory: str) -> None: import shutil - shutil.copytree(root, dirpath, dirs_exist_ok=True) + shutil.copytree( + root, + dirpath, + dirs_exist_ok=True, + ignore=( + shutil.ignore_patterns("index.html") + if ignore_index_html + else None + ), + ) class AutoExporter: diff --git a/tests/_cli/test_cli_export.py b/tests/_cli/test_cli_export.py index eac12ad5171..b5434992e7e 100644 --- a/tests/_cli/test_cli_export.py +++ b/tests/_cli/test_cli_export.py @@ -84,6 +84,28 @@ def test_cli_export_html_wasm(temp_marimo_file: str) -> None: ) assert " None: + out_dir = Path(temp_marimo_file).parent / "out_file" + p = subprocess.run( + [ + "marimo", + "export", + "html-wasm", + temp_marimo_file, + "--output", + str(out_dir / "foo.html"), + ], + capture_output=True, + ) + assert p.returncode == 0, p.stderr.decode() + assert Path(out_dir / "foo.html").exists() + assert not Path(out_dir / "index.html").exists() + html = Path(out_dir / "foo.html").read_text() + assert " None: out_dir = Path(temp_marimo_file).parent / "out"