Skip to content

Commit

Permalink
Merge branch 'main' into lazy_import
Browse files Browse the repository at this point in the history
  • Loading branch information
hoxbro committed Sep 2, 2024
2 parents a943fda + d49dafc commit a2e2541
Show file tree
Hide file tree
Showing 61 changed files with 1,565 additions and 276 deletions.
4 changes: 2 additions & 2 deletions doc/explanation/develop_seamlessly.md
Original file line number Diff line number Diff line change
Expand Up @@ -146,10 +146,10 @@ template.servable()
From the terminal run the following command.

```bash
panel serve app.py --show --autoreload
panel serve app.py --show --dev
```

The `--show` flag will open a browser tab with the live app and the `--autoreload` flag ensures that the app reloads whenever you make a change to the Python source. `--autoreload` is key to your developer experience, you will see the app being updated live when you save your app file! In the image below the windows have been re-arranged the way web developers like, on one side the code and on the other side a live view of the app, just like the *Preview* functionality in Jupyterlab.
The `--show` flag will open a browser tab with the live app and the `--dev` flag ensures that the app reloads whenever you make a change to the Python source. `--dev` is key to your developer experience, you will see the app being updated live when you save your app file! In the image below the windows have been re-arranged the way web developers like, on one side the code and on the other side a live view of the app, just like the *Preview* functionality in Jupyterlab.

![VSCode Preview](../_static/images/vscode_preview.png)

Expand Down
4 changes: 2 additions & 2 deletions doc/getting_started/build_app.md
Original file line number Diff line number Diff line change
Expand Up @@ -124,7 +124,7 @@ Save the notebook with the name `app.ipynb`.
Finally, we'll serve the app with:

```bash
panel serve app.ipynb --autoreload
panel serve app.ipynb --dev
```

Now, open the app in your browser at [http://localhost:5006/app](http://localhost:5006/app).
Expand All @@ -138,7 +138,7 @@ It should look like this:
If you prefer developing in a Python Script using an editor, you can copy the code into a file `app.py` and serve it.

```bash
panel serve app.py --autoreload
panel serve app.py --dev
```

:::
Expand Down
6 changes: 3 additions & 3 deletions doc/getting_started/core_concepts.md
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ By placing a Panel component at the end of a notebook cell, it renders as part o
To add Panel components to your app, mark them as `.servable()` and serve the app with:

```bash
panel serve app.ipynb --autoreload
panel serve app.ipynb --dev
```

You've already experimented with this while [building a simple app](build_app.md).
Expand All @@ -47,10 +47,10 @@ You've already experimented with this while [building a simple app](build_app.md
If you're working in an editor, declare the Panel components you want to display as `.servable()`, then serve the script with:

```bash
panel serve app.py --autoreload --show
panel serve app.py --dev --show
```

Upon running that command, Panel launches a server that serves your app, opens a tab in your default browser (`--show`), and updates the application whenever you modify the code (`--autoreload`).
Upon running that command, Panel launches a server that serves your app, opens a tab in your default browser (`--show`), and updates the application whenever you modify the code (`--dev`).

<img src="https://assets.holoviz.org/panel/gifs/vscode_autoreload.gif" style="margin-left: auto; margin-right: auto; display: block;"></img>

Expand Down
2 changes: 1 addition & 1 deletion doc/getting_started/installation.md
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,7 @@ conda install panel watchfiles
:::::

:::{tip}
We recommend also installing [`watchfiles`](https://watchfiles.helpmanual.io) while developing. This will provide a significantly better experience when using Panel's `--autoreload` feature. It's not needed for production.
We recommend also installing [`watchfiles`](https://watchfiles.helpmanual.io) while developing. This will provide a significantly better experience when using Panel's autoreload features when activating `--dev` mode. It's not needed for production.
:::

:::{tip}
Expand Down
197 changes: 178 additions & 19 deletions doc/how_to/custom_components/esm/build.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# Handling of external resources
# Compile and Bundle ESM Components

The ESM components make it possible to load external libraries from NPM or GitHub easily using one of two approaches:
The ESM components make it possible to load external libraries from a CDN, NPM or GitHub using one of two approaches:

1. Directly importing from `esm.sh` or another CDN or by defining a so called importmap.
2. Bundling the resources using `npm` and `esbuild`.
Expand Down Expand Up @@ -84,7 +84,7 @@ Let's say for instance you want to import libraries `A`, `B` and `C`. Both `B` a

In order to avoid this we can ask `esm.sh` not to rewrite the imports using the `external` query parameter. This tells esm.sh that `A` will be provided externally (i.e. by us), ensuring that libraries `B` and `C` both import the version of `A` we declare:

```
```python
{
"imports": {
"A": "https://esm.sh/[email protected]",
Expand Down Expand Up @@ -117,19 +117,186 @@ Import maps supports trailing slash that can not work with URL search params fri
```
:::

## Bundling
## Compile & Bundling

Importing libraries directly from a CDN allows for extremely quick iteration but also means that the users of your components will have to have access to the internet to fetch the required modules. By bundling the component resources you can ship a self-contained module that includes all the dependencies, while also ensuring that you only fetch the parts of the libraries that are actually needed.
Importing libraries directly from a CDN allows for quick experimentation and iteration but also means that the users of your components will have to have access to the internet to fetch the required modules. By compiling and bundling the component and external resources you can ship a self-contained and optimized ESM module that includes all the dependencies, while also ensuring that you only fetch the parts of the libraries that are actually needed. The `panel compile` command provides a simple entrypoint to compile one or more components into a single component.

### Tooling
### Setup

The tooling we recommend to bundle your component resources include `esbuild` and `npm`, both can conveniently be installed with `conda`:
The compilation and bundling workflow depends on two JavaScript tools: `node.js` (or more specifically the node package manager `npm`) and `esbuild`. The most convenient way to install them is `conda` but you can also set up your own node environment using something like [`asdf`](https://asdf-vm.com/guide/getting-started.html), [`nvm`](https://github.com/nvm-sh/nvm?tab=readme-ov-file#installing-and-updating) or [`volta`](https://volta.sh/).

::::{tab-set}

:::{tab-item} `conda`
```bash
conda install esbuild npm
```
:::

:::{tab-item} Custom Node.js installation

Once you have set up `node.js` you can install `esbuild` globally with:

```bash
npm install -g esbuild
```

and confirm the installation with:

```bash
esbuild --version
```
:::

::::

### Panel Compile Command

Panel provides the `panel compile` command to automate the compilation of ESM components from the command line and bundle their resources. This functionality requires `npm` and `esbuild` to be installed globally on your system.

#### Example Usage

Let's consider a confetti.py module containing a custom JavaScript component:

```python
# confetti.py
import panel as pn

from panel.custom import JSComponent

class ConfettiButton(JSComponent):

_esm = """
import confetti from "https://esm.sh/[email protected]";
export function render() {
const button = document.createElement('button')
button.addEventListener('click', () => confetti())
button.append('Click me!')
return button
}"""
```

To compile this component, you can use the following command:

```bash
panel compile confetti
```

:::{hint}
`panel compile` accepts file paths, e.g. `my_components/custom.py`, and dotted module name, e.g. `my_package.custom`. If you provide a module name it must be importable.
:::

This will automatically discover the `ConfettiButton` but you can also explicitly request a single component by adding the class name:

```bash
panel compile confetti:ConfettiButton
```

After running the command you should output that looks a like this, indicating the build succeeded:

```bash
Running command: npm install

npm output:

added 1 package, and audited 2 packages in 649ms

1 package is looking for funding
run `npm fund` for details

### Configuration
found 0 vulnerabilities

Running command: esbuild /var/folders/7c/ww31pmxj2j18w_mn_qy52gdh0000gq/T/tmp9yhyqo55/index.js --bundle --format=esm --outfile=<module-path>/ConfettiButton.bundle.js --minify

esbuild output:

.....<module-path>/ConfettiButton.bundle.js 10.5kb

⚡ Done in 9ms
```

The compiled JavaScript file will be automatically loaded if it remains alongside the component. If you rename the component or modify its code or `_importmap`, you must recompile the component. For ongoing development, consider using the `--autoreload` option to ignore the compiled file and automatically reload the development version when it changes.

#### Compilation Steps

The `panel compile` command performs the compilation and bundling in several steps:

1. **Identify Components**: The first step is to discover the components in the provided module(s).
2. **Extract External Dependencies**: The command identifies external dependencies from the `_importmap` (if defined) or directly from the ESM code. These dependencies are written to a `package.json` file in a temporary build directory. The `.js(x)` files corresponding to each component are also placed in this directory.
3. **Install Dependencies**: The command runs `npm install` within the build directory to fetch all external dependencies specified in `package.json`.
4. **Bundle and Minify**: The command executes `esbuild index.js --bundle --format=esm --minify --outfile=<module-path>ConfettiButton.bundle.js` to bundle the ESM code into a single minified JavaScript file.
5. **Output the Compiled Bundle(s)**: The final output is one or more compiled JavaScript bundle (`ConfettiButton.bundle.js`).

#### Compiling Multiple Components

If you intend to ship multiple components with shared dependencies, `panel compile` can generate a combined bundle, which ensures that the dependencies are only loaded once. By default it will generate one bundle per module or per component, but if you declare a `_bundle` attribute on the class, declared either as a string defining a relative path or a `pathlib.Path`, you can generate shared bundles across modules. These bundles can include as many components as needed and will be automatically loaded when you use the component.

As an example, imagine you have a components declared across your package containing two distinct components. By declaring a path that resolves to the same location we can bundle them together:

```python
# my_package/my_module.py
class ComponentA(JSComponent):
_bundle = './dist/custom.bundle.js'

# my_package/subpackage/other_module.py
class ComponentB(JSComponent):
_bundle = '../dist/custom.bundle.js'
```

when you compile it with:

```bash
panel compile my_package.my_module my_package.subpackage.other_module
```

you will end up with a single `custom.bundle.js` file placed in the `my_package/dist` directory.

#### Using the `--build-dir` Option

The `--build-dir` option allows you to specify a custom directory where the `package.json` and raw JavaScript/JSX modules will be written. This is useful if you need to manually modify the dependencies before the bundling process and/or debug issues while bundling. To use this feature, follow these steps:

1. Run the compile command with the `--build-dir` option to generate the directory:

```bash
panel compile confetti.py --build-dir ./custom_build_dir
```

2. Navigate to the specified build directory and manually edit the `package.json` file to adjust dependencies as needed.

3. Once you've made your changes, you can manually run the `esbuild` command:

```bash
esbuild custom_build_dir/index.js --format=esm --bundle --minify
```

Here is a typical structure of the build_dir directory:

```
custom_build_dir/
├── index.js
├── package.json
├── <Component>.js
└── <OtherComponent>.js
```

The compiled JS file will now be loaded automatically as long as it remains alongside the component. If you rename the component you will have to delete and recompile the JS file. If you make changes to the code or `_importmap` you also have to recompile. During development we recommend using `--autoreload`, which ignores the compiled file.

```{caution}
The `panel compile` CLI tool is still very new and experimental. In our testing it was able to compile and bundle most components but there are bound to be corner cases.
We will continue to improve the tool and eventually allow you to bundle multiple components into a single bundle to allow sharing of resources.
```

### React Components

React components automatically include `react` and `react-dom` in their bundles. The version of `React` that is loaded can be specified the `_react_version` attribute on the component class. We strongly suggest you pin a specific version on your component to ensure your component does not break should the version be bumped in Panel.

### Manual Compilation

If you have more complex requirements or the automatic compilation fails for whatever reason you can also manually compile the output. We generally strongly recommend that you start by generating the initial bundle structure by providing a `--build-dir` and then tweaking the resulting output.

#### Configuration

To run the bundling we will need one additional file, the `package.json`, which, just like the import maps, determines the required packages and their versions. The `package.json` is a complex file with tons of configuration options but all we will need are the [dependencies](https://docs.npmjs.com/cli/v10/configuring-npm/package-json#dependencies).

Expand Down Expand Up @@ -160,7 +327,7 @@ pn.extension()

class ConfettiButton(JSComponent):

_esm = 'confetti.bundled.js'
_esm = 'confetti.js'

ConfettiButton().servable()
```
Expand Down Expand Up @@ -190,15 +357,7 @@ npm install
This will fetch the packages and install them into the local `node_modules` directory. Once that is complete we can run the bundling:

```bash
esbuild confetti.js --bundle --format=esm --minify --outfile=confetti.bundled.js
esbuild confetti.js --bundle --format=esm --minify --outfile=ConfettiButton.bundle.js
```

This will create a new file called `confetti.bundled.js`, which includes all the dependencies (even CSS, image files and other static assets if you have imported them).

The only thing left to do now is to update the `_esm` declaration to point to the new bundled file:

```python
class ConfettiButton(JSComponent):
_esm = 'confetti.bundled.js'
```
This will create a new file called `ConfettiButton.bundle.js`, which includes all the dependencies (even CSS, image files and other static assets if you have imported them).
4 changes: 2 additions & 2 deletions doc/how_to/custom_components/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -59,11 +59,11 @@ Build custom components in Javascript using so called ESM components, which allo
:gutter: 1 1 1 2


:::{grid-item-card} {octicon}`tools;2.5em;sd-mr-1 sd-animate-grow50` Building and Bundling ESM components
:::{grid-item-card} {octicon}`tools;2.5em;sd-mr-1 sd-animate-grow50` Compile and Bundle ESM Components
:link: esm/build
:link-type: doc

How to specify and bundle external dependencies for ESM components.
How to specify external dependencies for ESM components and compile them into JS bundles.
:::

:::{grid-item-card} {octicon}`pencil;2.5em;sd-mr-1 sd-animate-grow50` Add callbacks to ESM components
Expand Down
8 changes: 4 additions & 4 deletions doc/how_to/server/commandline.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,13 +12,13 @@ or even serve a number of apps at once:

panel serve apps/*.py

For development it can be particularly helpful to use the ``--autoreload`` option to `panel serve` as that will automatically reload the page whenever the application code or any of its imports change.
For development it can be particularly helpful to use the `--dev` option to `panel serve` as that will automatically reload the page whenever the application code or any of its imports change.

```{note}
We recommend installing `watchfiles`, which will provide a significantly better user experience when using `--autoreload`.
We recommend installing `watchfiles`, which will provide a significantly better user experience when using `--dev`.
```

The ``panel serve`` command has the following options:
The `panel serve` command has the following options:

```bash
positional arguments:
Expand Down Expand Up @@ -142,6 +142,6 @@ options:
Whether to add a global loading spinner to the application(s).
```
To turn a notebook into a deployable app simply append ``.servable()`` to one or more Panel objects, which will add the app to Bokeh's ``curdoc``, ensuring it can be discovered by Bokeh server on deployment. In this way it is trivial to build dashboards that can be used interactively in a notebook and then seamlessly deployed on Bokeh server.
To turn a notebook into a deployable app simply append `.servable()` to one or more Panel objects, which will add the app to Bokeh's `curdoc`, ensuring it can be discovered by Bokeh server on deployment. In this way it is trivial to build dashboards that can be used interactively in a notebook and then seamlessly deployed on Bokeh server.
When called on a notebook, `panel serve` first converts it to a python script using [`nbconvert.PythonExporter()`](https://nbconvert.readthedocs.io/en/stable/api/exporters.html), albeit with [IPython magics](https://ipython.readthedocs.io/en/stable/interactive/magics.html) stripped out. This means that non-code cells, such as raw cells, are entirely handled by `nbconvert` and [may modify the served app](https://nbsphinx.readthedocs.io/en/latest/raw-cells.html).
2 changes: 1 addition & 1 deletion doc/how_to/streamlit_migration/get_started.md
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@ pn.panel("Hello World").servable()
You *serve* and *show* (i.e. open) the app in your browser with *autoreload* via

```bash
panel serve app.py --autoreload --show
panel serve app.py --dev --show
```

![Panel Hello World Example](../../_static/images/panel_hello_world.png)
Loading

0 comments on commit a2e2541

Please sign in to comment.