Skip to content

Commit 31c9588

Browse files
authored
Continue improving clarity of Anatomy module (#57)
2 parents 566fc6d + ed622d6 commit 31c9588

File tree

1 file changed

+103
-29
lines changed

1 file changed

+103
-29
lines changed

04-materials/02-anatomy-of-extensions.md

Lines changed: 103 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -49,13 +49,21 @@ This tutorial is inspired by many prior works.
4949

5050
## 🛠️ Setup
5151

52+
Before we get started, we need to set up:
53+
54+
* Development dependencies on our local machine
55+
* Git configuration on our local machine
56+
* A repository on GitHub
57+
58+
5259
### Dependency environment
5360

61+
Create an environment named `jupytercon2025`.
5462
We'll use this environment for the rest of this workshop:
5563

5664
```bash
5765
# Create an environment named "jupytercon2025"
58-
micromamba create -n jupytercon2025
66+
micromamba create --name jupytercon2025
5967

6068
# Activate it
6169
# IMPORTANT: Run this every time you open a new terminal!
@@ -74,15 +82,17 @@ micromamba install python pip nodejs gh "copier~=9.2" jinja2-time
7482
### Important Git settings
7583

7684
1. Git needs to know who you are.
85+
7786
Configure identity information Git will use when we commit:
7887

7988
```bash
8089
git config --global user.email "[email protected]"
8190
git config --global user.name "Your Name Here"
8291
```
8392

84-
2. The modern conventional branch name is "main", and this tutorial will assume that's
85-
your default branch.
93+
2. The modern conventional branch name is `main`, and this tutorial will assume
94+
you're working on the `main` branch.
95+
8696
Ensure your default branch is set to `main`:
8797

8898
```bash
@@ -222,7 +232,7 @@ Our extension will:
222232

223233
## 🏋️ Exercise A (15 minutes): Extension creation and development loop
224234

225-
### Create a new extension from the [official template](https://github.com/jupyterlab/extension-template)
235+
### 🔧 Create a new extension from the [official template](https://github.com/jupyterlab/extension-template)
226236

227237
1. Instantiate the template to get started on our new extension!
228238

@@ -312,7 +322,7 @@ Our extension will:
312322
:::
313323

314324

315-
#### 🧪 Test
325+
### 🧪 Test
316326

317327
1. Start JupyterLab in a **separate terminal**.
318328

@@ -376,7 +386,7 @@ git push -u origin main
376386
:::
377387
378388
379-
### Do a complete development loop
389+
### 🔧 Do a complete development loop
380390
381391
0. Close the JupyterLab server with `CTRL+C`.
382392
@@ -389,7 +399,7 @@ git push -u origin main
389399
2. Rebuild the extension with `jlpm build`.
390400

391401

392-
#### 🧪 Test
402+
### 🧪 Test
393403

394404
Follow the same testing steps as last time.
395405

@@ -417,7 +427,7 @@ git push -u origin main
417427
:::
418428

419429

420-
### What just happened?
430+
### 🧠 What do we know now?
421431

422432
We know how to get started: we learned how to instantiate a new extension from the
423433
official template and set it up for development.
@@ -433,12 +443,12 @@ Now we have all the knowledge we need to keep iterating on our extension!
433443
🎓 Well done!
434444

435445

436-
## Creating a widget
446+
## 😖 "Hello, world" is boring!
437447

438448
Our working extension is a basic "hello, world" application.
439449
All it does is log a string to the console, then make a request to the back-end
440450
for another string, which is also logged to the console.
441-
This all happens once, when the extension is activated when the user opens JupyterLab.
451+
This all happens **once** when the user opens JupyterLab and the extension is activated.
442452

443453
Our goal is to display a viewer for a random photo and caption, with a refresh button to
444454
instantly display a new image.
@@ -448,7 +458,7 @@ will eventually house that content.
448458
449459
## 🏋️ Exercise B (20 minutes): Launching a "hello, world" {term}`widget <widget>`
450460
451-
### Create a "hello, world" widget
461+
### 🔧 Create a "hello, world" widget
452462
453463
To display this widget in the {term}`main area <main area>`, we need to
454464
implement a {term}`widget <widget>` which displays our content (for now, just
@@ -518,7 +528,7 @@ widget in JupyterLab yet.
518528
Let's fix that now.
519529

520530

521-
### Create a {term}`command <command>` to display the {term}`widget <widget>` in the {term}`main area <main area>`
531+
### 🔧 Create a {term}`command <command>` to display the {term}`widget <widget>` in the {term}`main area <main area>`
522532

523533
In `src/index.ts`, we need to update our plugin to define a command in our
524534
{term}`plugin's <plugin>` `activate` method:
@@ -573,7 +583,7 @@ But right now, this command is not being used by anything!
573583
Next, we'll add it to the {term}`command palette <command palette>`.
574584

575585

576-
### Register our {term}`command <command>` with the {term}`command palette <command palette>`
586+
### 🔧 Register our {term}`command <command>` with the {term}`command palette <command palette>`
577587

578588
First, import the command palette interface at the top of `src/index.ts`:
579589

@@ -668,7 +678,7 @@ git push -u origin main
668678
:::
669679

670680

671-
### Optional: Register with the {term}`launcher <launcher>`
681+
### 🔧 Optional: Register with the {term}`launcher <launcher>`
672682

673683
Unlike the command palette, this functionality needs to be installed as a dependency.
674684
First, install `@jupyterlab/launcher` with `jlpm add @jupyterlab/launcher` to make
@@ -718,7 +728,7 @@ git push -u origin main
718728
:::
719729

720730

721-
#### My launcher button works, but it has no icon!
731+
#### Troubleshooting: My launcher button works, but it has no icon!
722732

723733
Adding an icon is one extra step.
724734
We can import the icon in `src/index.ts` like so:
@@ -760,7 +770,19 @@ git push -u origin main
760770
:::
761771

762772

763-
## What's next?
773+
### 🧠 What do we know now?
774+
775+
We know that a {term}`widget <widget>` by itself isn't very useful, and we need some way to display it.
776+
777+
We decided we want to display our widget in the {term}`main area <main area>`, and we
778+
know that we need a {term}`command <command>` to achieve that.
779+
780+
Finally, we know that we can register our command with the
781+
{term}`command palette <command palette>` and/or the {term}`launcher <launcher>`
782+
to give the user an easy way to execute the command to open the widget.
783+
784+
785+
## 😖 It's still just "hello, world"!
764786

765787
We've graduated from "Hello, world" in the console to "Hello, world" in a
766788
{term}`main area widget <main area widget>`.
@@ -774,7 +796,7 @@ Now we need to implement the logic and glue the pieces together.
774796

775797
## 🏋️ Exercise C (20 minutes): Serve images and captions from the server extension
776798

777-
### Set up images and captions
799+
### 🔧 Define images and captions
778800

779801
Create a new directory at `jupytercon2025_extension_workshop/images`:
780802

@@ -813,7 +835,7 @@ IMAGES_AND_CAPTIONS = [
813835
```
814836

815837

816-
### Update the server to serve images and captions
838+
### 🔧 Update the server to serve images and captions
817839

818840
Our server behaviors are defined in
819841
`jupytercon2025_extension_workshop/routes.py`, so that module will need to know
@@ -897,7 +919,7 @@ def setup_route_handlers(web_app):
897919
```
898920

899921

900-
#### 🧪 Test
922+
### 🧪 Test
901923

902924
Now's the best time for us to stop and test before moving on to consuming this
903925
data with our widget.
@@ -933,7 +955,7 @@ git push -u origin main
933955
:::
934956

935957

936-
### Connect the {term}`widget` to the {term}`server extension`
958+
### 🔧 Connect the {term}`widget` to the {term}`server extension`
937959

938960
Now that our backend is working, we need to glue our widget to it.
939961

@@ -962,7 +984,7 @@ that nothing is calling that method yet:
962984
```{code} typescript
963985
:linenos:
964986
:emphasize-lines: 7-19, 21-23
965-
:file: src/widget.ts
987+
:filename: src/widget.ts
966988
967989
class ImageCaptionWidget extends Widget {
968990
// Initialization
@@ -996,7 +1018,7 @@ Now, we're calling `load_image()` when we initialize the widget:
9961018
```{code} typescript
9971019
:linenos:
9981020
:emphasize-lines: 12-25
999-
:file: src/widget.ts
1021+
:filename: src/widget.ts
10001022
10011023
class ImageCaptionWidget extends Widget {
10021024
// Initialization
@@ -1037,7 +1059,7 @@ class ImageCaptionWidget extends Widget {
10371059
}
10381060
```
10391061

1040-
#### 🧪 Test
1062+
### 🧪 Test
10411063

10421064
Now that we have our widget user interface hooked up to the data coming from the server, let's test again.
10431065
Because we changed the JavaScript, we need to use `jlpm run build`, but we _don't_ need to restart the JupyterLab server.
@@ -1077,14 +1099,32 @@ git push -u origin main
10771099
:::
10781100

10791101

1080-
## 🏋️ Exercise D (15 minutes): Add user interactivity to the widget
1102+
### 🧠 What do we know now?
1103+
1104+
We know that a {term}`server extension <server extension>` can access the hardware
1105+
resources of the JupyterLab server, for example to read data from disk.
1106+
1107+
We know that server extensions provide HTTP endpoints that can be consumed by
1108+
{term}`frontend extensions <frontend extension>`.
1109+
1110+
We know how to provide JSON data from the server and consume it in a {term}`widget
1111+
<widget>`.
1112+
1113+
We know how to dynamically update {term}`widget` HTML elements.
1114+
1115+
1116+
## 😖 The widget isn't interactive!
10811117

10821118
Right now, you only get a random image when you first open the widget.
10831119
It's much more interesting if the widget can respond to user actions!
1120+
1121+
1122+
## 🏋️ Exercise D (10 minutes): Add interactivity to the widget
1123+
10841124
Let's add a toolbar and refresh button which triggers the image to change immediately.
10851125

10861126

1087-
### Import a toolbar UI component and icon
1127+
### 🔧 Import a toolbar UI component and icon
10881128

10891129
For all of this to work, we need the `ToolbarButton` to use in our
10901130
widget.
@@ -1108,7 +1148,7 @@ import {
11081148
```
11091149

11101150

1111-
### Add the button to the widget and connect the logic
1151+
### 🔧 Add the button to the widget and connect the logic
11121152

11131153
Now we can use the `ToolbarButton` class to instantiate a new button with an icon,
11141154
tooltip, and behavior (`onClick`).
@@ -1169,7 +1209,16 @@ git push -u origin main
11691209
:::
11701210

11711211

1172-
## Problem: The widget disappears when we refresh the page
1212+
### 🧠 What do we know now?
1213+
1214+
We know that {term}`main area widgets <main area widget>` can offer a toolbar for
1215+
interacting with the widget.
1216+
1217+
We know how to add a button to the toolbar with an icon, tooltip, and `onClick`
1218+
behavior.
1219+
1220+
1221+
## 😖 The widget disappears when we refresh the page
11731222

11741223
:::{important} 👀 You should notice...
11751224
:class: simple
@@ -1183,9 +1232,12 @@ disappears.
11831232
## 🏋️ Exercise E (15 minutes): Preserve layout
11841233

11851234
JupyterLab can save and restore layouts, but we need to define how our widget restores
1186-
its state.
1235+
its state for that to work.
1236+
1237+
The layout restorer **loads** layouts, and the widget tracker **saves** layouts.
1238+
11871239

1188-
First, let's import the layout restorer:
1240+
### 🔧 Import the layout restorer plugin and widget tracker
11891241

11901242
```{code} typescript
11911243
:linenos:
@@ -1204,6 +1256,9 @@ import {
12041256
12051257
```
12061258

1259+
1260+
### 🔧 Use the layout restorer plugin as a dependency
1261+
12071262
Now, we'll define the layout restorer {term}`token <token>` as an _optional_
12081263
dependency, as it may not be available in all JupyterLab deployments.
12091264
When we pass an optional dependency to the `activate` function, we follow two
@@ -1234,6 +1289,9 @@ const plugin: JupyterFrontEndPlugin<void> = {
12341289
) => {
12351290
```
12361291

1292+
1293+
### 🔧 Track our widget and conditionally restore it
1294+
12371295
Now that we have the dependency, we need to define _how_ the widget's layout
12381296
will be saved and restored.
12391297
First, we need to define a tracker object:
@@ -1314,5 +1372,21 @@ widget is initialized.
13141372
:::
13151373

13161374

1375+
### 🧠 What do we know now?
1376+
1377+
We know that by default, JupyterLab doesn't track our open widget or restore its state
1378+
when we refresh the page.
1379+
1380+
We know that we can use the `WidgetTracker` to remember the state of our widget and the
1381+
`ILayoutRestorer` plugin to restore that state.
1382+
1383+
1384+
## 🎉 Great job!
1385+
1386+
The extension we built together today is just a small example of what can be done with
1387+
the Jupyter extension system.
1388+
What other things can you imagine building?
1389+
1390+
13171391
[^rebuild-not-always-required]: We don't actually _always_ need to rebuild -- only when
13181392
we change the JavaScript. If we only changed Python, we only need to restart JupyterLab.

0 commit comments

Comments
 (0)