Reusability between extensions

Hi there

a probably dumb question from a npm/ts newbie:

I am trying to use in extension jlab_ext2 some code (say, function foo) from another extension jlab_ext1

(knowing that jlab_ext1 is packaged with pip)

exporting

in jlab_ext1, I think I found how to re-export foo, which is not defined in index.ts but in a separate file foo.ts by doing

export { foo } from './foo'

importing

the other way around though, when writing src/index.ts in jlab_ext2, I would like to mimic the imports like this one

import { Cell } from '@jupyterlab/cells'

but I do not know how to refer to jlab_ext1 because when writing this (with or without a @)

import { foo } from 'jlab_ext1'

I’m getting

src/index.ts:31:8 - error TS2307: Cannot find module 'jlab_ext1' or its corresponding type declarations.

I also tried

  • with from 'jlab-ext1' as it is the name displayed by jupyter labextension list
  • and also with a @ just in case, but none seemed to work

it feels like the naming is npm-dictated, but since I am packaging through pip I am a little confused

thanks for pointing me in the right direction

Webpack (and therefore jupyter labextension) only speaks npm-compatible package names, as declared in package.json#/name.

To import code from another, pip-installed prebuilt extension, the consumer must declare the provider in package.json#/jupyterlab/sharedPackages, which will ensure it is found at runtime, and not duplicated: this is extremely important for libraries which use, e.g. instanceof or otherwise have hidden singleton behavior, like react. Indeed, some packages like react, @lumino/*, @jupyterlab/* are already forced as singletons, due to how terrible the failure modes are.

Another option is to have a third package, which is not an extension, that provides the common functionality, in which case both extensions would add the common package name to sharedPackages.

As for pypi.org vs npmjs.org: publishing “real” packages on npmjs.org allows for other extension developers to depend on the features of an extension, even though users will (hopefully) not have to muck about with it. This is the pattern that enables, for example, Jupyter Widgets, which separates the lab-specific code which declares plugins (@jupyter-widgets/jupyterlab-manager) from the underlying models (@jupyter-widgets/base) and the UI implementations (@jupyter-widgets/controls). This already allows complete reuse of all UI in Lab and Classic, and would allow someone to entirely customize the relatively stock HTML5 widgets, but still be compatible with things like Box subclasses, which rely on their children being actual WidgetView instances.

2 Likes

thanks for the detailed explanation

I’m still confused though; in my use case, the provider extension is this one

So in the consumer extension, I tried this in package.json

  "jupyterlab": {
    "extension": true,
    "outputDir": "jupyterlab_courselevels/labextension",
    "sharedPackages": [
      "jupyterlab-celltagsclasses"
    ]
  }

and in my index.ts I wrote this

import { md_get } from 'jupyterlab-celltagsclasses'

but I still see this

pip install -e .
<snip>
      INFO:hatch_jupyter_builder.utils:> /private/var/folders/9n/sxs31qhj1gnd6gk2v0ns8848000fn2/T/pip-build-env-zirlx0n7/overlay/bin/jlpm run install:extension
      src/index.ts(31,8): error TS2307: Cannot find module 'jupyterlab-celltagsclasses' or its corresponding type declarations.
<snip>

also, I can’t seem to find any ts or js file in the site-packages area, which may or may be expected… I guess I’m still wondering how the js code is supposed to get installed in the node-modules area ?

So I guess there’s some additional step I should make at some point ?

PS.
in the consumer extension I have this

$ pip show jupyterlab-celltagsclasses
Name: jupyterlab_celltagsclasses
Version: 0.2.0
Summary: JLAB extension to add classes to cells based on their tags

This is rather unrelated to the sharedPackages story (and if you are just importing utility functions which are stateless you are not required to use sharedPackages). To build extensions with TypeScript you need to have types. To have types you either publish the provider package to npm and declare it in the (dev)dependencies of consumer extension, or if publication of types to npm is not an option (rarely the case) manually declare types for the package in declaration files.

Of note this is only required for development build, your users will not need to download anything from npm.

Thanks, that was helpful

So to summarize, what I had to do was to

  • run npm publish in the provider extension
  • add the provider extension in the dependencies area in the consumer extension’s package.json
  • and then run npm install in the provider extension

and this way I indeed have the code pulled in my node-modules folder

It’s good to know that the provider extension code will come with a pip install of the consumer extension :slight_smile:

thank you !

2 Likes