Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Transpile (optional) TypeScript, support imports (package and relative) and schema #28

Merged
merged 43 commits into from
Mar 24, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
43 commits
Select commit Hold shift + click to select a range
5ae0222
Implement transpilation with import transformation
krassowski Jan 2, 2022
cad665e
Update the list of packages
krassowski Jan 2, 2022
4fc3bad
Fix typos
krassowski Jan 2, 2022
2d53c90
Catch and display activation errors, handle unknown tokens and jupyte…
krassowski Jan 2, 2022
dce0d5f
Host imports to main module, handle import errors
krassowski Jan 2, 2022
5f519e2
Improve export transformer and error handling
krassowski Jan 2, 2022
3f1b003
Prepare for aliased import (import-as) handling
krassowski Jan 2, 2022
fc507fc
Split up transpiler, resolver and loader
krassowski Jan 2, 2022
32a40d2
Check for existence of module properties
krassowski Jan 2, 2022
93cc1fa
Warn, but do not raise because types will be undefined.
krassowski Jan 2, 2022
8f5be22
Implement visit import aliasing and add missing transpiler
krassowski Jan 2, 2022
1131f84
Support default imports, recognise type imports
krassowski Jan 2, 2022
81d4211
Clean up comments
krassowski Jan 3, 2022
ef7c8a5
Use local copy of require.js, do not expose it in global `window`
krassowski Jan 4, 2022
b07d1eb
Add webpack config with workaround for https://github.com/microsoft/T…
krassowski Jan 4, 2022
9788f27
Update MANIFEST.in
krassowski Jan 4, 2022
aa85c07
Update the fallback warning text
krassowski Jan 4, 2022
81f0921
Pass requirejs to the object-based plugins; document deprecation and …
krassowski Jan 5, 2022
abc6335
Correct default imports
krassowski Jan 5, 2022
370fd89
Allow to hide the icon in launcher
krassowski Jan 5, 2022
dd7b6fd
Use more expressive setting name
krassowski Jan 5, 2022
8aa1628
Implement CDN policy, including SRI (does NOT work)
krassowski Jan 8, 2022
4cf596b
Revert the SRI part, keep only allow/never choices for now
krassowski Jan 8, 2022
b0cface
Add missing `await` to avoid double dialog
krassowski Jan 8, 2022
b9d7a82
Add missing `types.ts`
krassowski Jan 8, 2022
07abee5
Clone extension examples on binder
krassowski Jan 8, 2022
6d147c7
Merge branch 'jupyterlab:master' into transpile-tokens
krassowski Jan 8, 2022
d4deb62
Silence LSP plugin warnings, not important here
krassowski Jan 8, 2022
529466c
Import between files (handle arbitrary export)
krassowski Jan 9, 2022
bbd00cb
Expose `@jupyterlab/outputarea`
krassowski Jan 9, 2022
fbc5b39
Fix require.js loading (requires the iframe to stay in DOM!)
krassowski Jan 9, 2022
23ea3cf
Implement schema loading
krassowski Jan 9, 2022
0599fbc
Use CommonJS instead of custom transformers;
krassowski Jan 9, 2022
c1b011b
Implement proper `after` transformers
krassowski Jan 9, 2022
e3baee9
Update README re support for relative imports/schema/styles/svg
krassowski Jan 9, 2022
6527789
Update setting name in README, remove irrelevant line
krassowski Jan 9, 2022
8a2ea13
Add React and .tsx support
krassowski Jan 9, 2022
ef20b38
Support SVG imports as a special case
krassowski Jan 9, 2022
580c723
Implement `default` proxy for known modules without default export
krassowski Jan 9, 2022
040d344
Update src/dialogs.tsx
jtpio Feb 16, 2022
62d2308
Apply suggestions from code review
krassowski Mar 23, 2022
0eb3c53
Merge branch 'master' into transpile-tokens
krassowski Mar 23, 2022
ad012b1
Lint with prettier
krassowski Mar 23, 2022
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 6 additions & 6 deletions .eslintrc.js
Original file line number Diff line number Diff line change
Expand Up @@ -15,11 +15,11 @@ module.exports = {
'@typescript-eslint/naming-convention': [
'error',
{
'selector': 'interface',
'format': ['PascalCase'],
'custom': {
'regex': '^I[A-Z]',
'match': true
selector: 'interface',
format: ['PascalCase'],
custom: {
regex: '^I[A-Z]',
match: true
}
}
],
Expand All @@ -33,7 +33,7 @@ module.exports = {
{ avoidEscape: true, allowTemplateLiterals: false }
],
curly: ['error', 'all'],
eqeqeq: 'error',
eqeqeq: ['error', 'smart'],
'prefer-arrow-callback': 'error'
}
};
1 change: 1 addition & 0 deletions MANIFEST.in
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ include package.json
include install.json
include ts*.json
include yarn.lock
include *.config.js

graft jupyterlab_plugin_playground/labextension

Expand Down
87 changes: 54 additions & 33 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
# JupyterLab Plugin Playground

[![Github Actions Status](https://github.com/jupyterlab/jupyterlab-plugin-playground/workflows/Build/badge.svg)](https://github.com/jupyterlab/jupyterlab-plugin-playground/actions/workflows/build.yml)[![Binder](https://mybinder.org/badge_logo.svg)](https://mybinder.org/v2/gh/jupyterlab/jupyterlab-plugin-playground/master?urlpath=lab)
[![Github Actions Status](https://github.com/jupyterlab/jupyterlab-plugin-playground/workflows/Build/badge.svg)](https://github.com/jupyterlab/jupyterlab-plugin-playground/actions/workflows/build.yml)
[![Binder](https://mybinder.org/badge_logo.svg)](https://mybinder.org/v2/gh/jupyterlab/jupyterlab-plugin-playground/master?urlpath=lab)

A JupyterLab extension to write and load simple JupyterLab plugins inside JupyterLab.


## Install

This extension requires JupyterLab 3. Install this extension with pip:
Expand All @@ -19,20 +19,22 @@ This extension provides a new command, `Load Current File As Extension`, availab

As an example, open the text editor by creating a new text file and paste this small JupyterLab plugin into it. This plugin will create a simple command `My Super Cool Toggle` in the command palette that can be toggled on and off.

```js
{
id: 'MySuperCoolTogglePlugin',
```typescript
import { ICommandPalette } from '@jupyterlab/apputils';

const plugin = {
id: 'my-super-cool-toggle:plugin',
autoStart: true, // Activate this plugin immediately
requires: ["@jupyterlab/apputils:ICommandPalette"],
activate: function(app, palette) {
let commandID = "MySuperCoolToggle";
requires: [ICommandPalette],
activate: function (app, palette) {
let commandID = 'my-super-cool-toggle:toggle';
let toggle = true; // The current toggle state
app.commands.addCommand(commandID, {
label: 'My Super Cool Toggle',
isToggled: function() {
isToggled: function () {
return toggle;
},
execute: function() {
execute: function () {
// Toggle the state
toggle = !toggle;
}
Expand All @@ -41,50 +43,69 @@ As an example, open the text editor by creating a new text file and paste this s
palette.addItem({
command: commandID,
// Sort to the top for convenience
category: "AAA"
category: 'AAA'
});
}
}
};

export default plugin;
```

While in the text editor, load this plugin in JupyterLab by invoking the Command Palette and executing `Load Current File As Extension`. Invoke the Command Palette again and you will see a new command "My Super Cool Toggle". Executing this new command will toggle the checkbox next to the command.

As another more advanced example, we load the [bqplot](https://bqplot.readthedocs.io) Jupyter Widget library from the cloud using RequireJS. This assumes you have the [ipywidgets JupyterLab extension](https://ipywidgets.readthedocs.io/en/stable/user_install.html#installing-in-jupyterlab-3-0) installed.

```js
{
```typescript
// IJupyterWidgetRegistry token is provided with Plugin Playground
import { IJupyterWidgetRegistry } from '@jupyter-widgets/base';
// Use RequireJS to load the AMD module. '@*' selects the latest version
// and `/dist/index.js` loads the corresponding module containing bqplot
// from the CDN configured in Settings (`requirejsCDN`).
import bqplot from "bqplot@*/dist/index";

const plugin = {
id: 'mydynamicwidget',
autoStart: true,
requires: ["jupyter.extensions.jupyterWidgetRegistry"],
activate: function(app, widgets) {
// Set up RequireJS to pull packages from the jsdelivr CDN.
require.config({
baseUrl: "https://cdn.jsdelivr.net/npm"
});
// Use RequireJS to load the AMD module. '@*' selects the latest version
// and `/dist/index.js` loads the corresponding module containing bqplot.
require(["bqplot@*/dist/index"], function(plugin) {
widgets.registerWidget({
name: 'bqplot',
version: plugin.version,
exports: plugin
});
requires: [IJupyterWidgetRegistry],
activate: function(app, widgets: IJupyterWidgetRegistry) {
widgets.registerWidget({
name: 'bqplot',
version: bqplot.version,
exports: bqplot
});
}
}
export default plugin;
```

There are a few differences in how to write plugins in the Plugin Playground compared to writing plugins in a JupyterLab extension:

* Plugins cannot import tokens from other packages, so we use the token names as strings in the `requires` and `optional` plugin fields rather than imported tokens. For example, we used the [ICommandPalette](https://github.com/jupyterlab/jupyterlab/blob/4169b7b684f6160b5a9ab093391ec531399dfa82/packages/apputils/src/tokens.ts#L16-L18) token name in the `requires` field above. The Plugin Playground will automatically change the token names to the corresponding tokens registered with the current JupyterLab when it loads the plugin. This means you can use token names for any extension currently loaded in JupyterLab, including non-core extensions.
* To load code from a separate package, you can use RequireJS as in the example above to load bqplot. RequireJS comes with the Plugin Playground and can be used to load any AMD module.
* You can only load a plugin with a given id once. If you make changes to your plugin, save it and refresh the JupyterLab page to be able to load it again.
- The playground is more understanding: you can use JavaScript-like code rather than fully typed TypeScript and it will still compile.
- You can only load a plugin with a given id more than once, but the previous version will not be unloaded. If you make changes to your plugin, save it and refresh the JupyterLab page to be able to load it afresh again.
- To load code from an external package, RequireJS is used (it is hidden behind ES6-compatible import syntax) which means that the import statements need to be slightly modified to point to appropriate version or file in the package.
- In addition to JupyterLab and Lumino packages, only AMD modules can be imported; ES6 modules and modules compiled for consumption by Webpack/Node will not work in the current version and an attempt to load such modules will result in `Uncaught SyntaxError: Unexpected token 'export'` error.
- While the playground will attempt to import relative files (with `.ts` suffix), SVG (as strings), and to load `plugin.json` schema, these are experimental features for rapid prototyping and details are subject to change; other resources like CSS styles are not yet supported (but the support is planned)

### Migrating from version 0.3.0

Version 0.3.0 supported only object-based plugins and `require.js` based imports.
While the object-based syntax for defining plugins remains supported, using `require` global reference is now deprecated.

A future version will remove `require` object to prevent confusion between `require` from `require.js`, and native `require` syntax;
please use `requirejs` (an alias function with the same signature) instead, or migrate to ES6-syntax plugins.
Require.js is not available in the ES6-syntax based plugins.

To migrate to the ES6-compatible syntax:
- assign the plugin object to a variable, e.g. `const plugin = { /* plugin code without changes */ };`,
- add `export default plugin;` line,
- convert `require()` calls to ES6 default imports.

## Advanced Settings

The Advanced Settings for the Plugin Playground enable you to configure plugins to load every time JupyterLab starts up. Automatically loaded plugins can be configured in two ways:

* `urls` is a list of URLs that will be fetched and loaded as plugins automatically when JupyterLab starts up. For example, you can point to a GitHub gist or a file you host on a local server that serves text files like the above examples.
* `plugins` is a list of strings of plugin text, like the examples above, that are loaded automatically when JupyterLab starts up. Since JSON strings cannot have multiple lines, you will need to encode any newlines in your plugin text directly as `\n\` (the second backslash is to allow the string to continue on the next line). For example, here is a user setting to encode a small plugin to run at startup:
- `urls` is a list of URLs that will be fetched and loaded as plugins automatically when JupyterLab starts up. For example, you can point to a GitHub gist or a file you host on a local server that serves text files like the above examples.
- `plugins` is a list of strings of plugin text, like the examples above, that are loaded automatically when JupyterLab starts up. Since JSON strings cannot have multiple lines, you will need to encode any newlines in your plugin text directly as `\n\` (the second backslash is to allow the string to continue on the next line). For example, here is a user setting to encode a small plugin to run at startup:
```json5
{
plugins: [
Expand Down
2 changes: 2 additions & 0 deletions RELEASE.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@

The extension can be published to `PyPI` and `npm` manually or using the [Jupyter Releaser](https://github.com/jupyter-server/jupyter_releaser).

To refresh the packages list run `python scripts/modules.py` with the target version of JupyterLab installed.

## Manual release

### Python package
Expand Down
3 changes: 3 additions & 0 deletions binder/overrides.json
Original file line number Diff line number Diff line change
@@ -1,4 +1,7 @@
{
"@krassowski/jupyterlab-lsp:plugin": {
"loggingLevel": "error"
},
"@krassowski/jupyterlab-lsp:completion": {
"layout": "detail-below"
}
Expand Down
3 changes: 3 additions & 0 deletions binder/postBuild
Original file line number Diff line number Diff line change
Expand Up @@ -52,5 +52,8 @@ SETTINGS = Path(sys.prefix) / "share/jupyter/lab/settings"
SETTINGS.mkdir(parents=True, exist_ok=True)
shutil.copy2("binder/overrides.json", SETTINGS / "overrides.json")

# download examples
_("git", "clone", "https://github.com/jupyterlab/extension-examples.git")

print("JupyterLab with @jupyterlab/plugin-playground is ready to run with:\n")
print("\tjupyter lab\n")
37 changes: 33 additions & 4 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"name": "@jupyterlab/plugin-playground",
"version": "0.3.0",
"description": "A JupyterLab extension.",
"description": "A JupyterLab Plugin Playground.",
"keywords": [
"jupyter",
"jupyterlab",
Expand Down Expand Up @@ -37,6 +37,8 @@
"clean:lib": "rimraf lib tsconfig.tsbuildinfo",
"clean:labextension": "rimraf jupyterlab_plugin_playground/labextension",
"clean:all": "jlpm run clean:lib && jlpm run clean:labextension",
"prettier": "prettier --list-different --write \"**/*{.ts,.tsx,.css,.json}\"",
"lint": "jlpm prettier && jlpm eslint",
"eslint": "eslint . --ext .ts,.tsx --fix",
"eslint:check": "eslint . --ext .ts,.tsx",
"install:extension": "jlpm run build",
Expand All @@ -45,19 +47,45 @@
"watch:labextension": "jupyter labextension watch ."
},
"dependencies": {
"@jupyter-widgets/base": "^4.0.0",
"@jupyterlab/application": "^3.0.0",
"@jupyterlab/apputils": "^3.0.0",
"@jupyterlab/fileeditor": "^3.0.0",
"@jupyterlab/settingregistry": "^3.0.0",
"@jupyter-widgets/base": "^4.0.0"
"requirejs": "^2.3.6",
"raw-loader": "^4.0.2",
"typescript": "~4.1.3"
},
"devDependencies": {
"@jupyterlab/builder": "^3.0.0",
"@jupyterlab/completer": "^3.2.5",
"@jupyterlab/console": "^3.2.5",
"@jupyterlab/debugger": "^3.2.5",
"@jupyterlab/docmanager": "^3.2.5",
"@jupyterlab/documentsearch": "^3.2.5",
"@jupyterlab/extensionmanager": "^3.2.5",
"@jupyterlab/filebrowser": "^3.2.5",
"@jupyterlab/imageviewer": "^3.2.5",
"@jupyterlab/inspector": "^3.2.5",
"@jupyterlab/launcher": "^3.2.5",
"@jupyterlab/logconsole": "^3.2.5",
"@jupyterlab/mainmenu": "^3.2.5",
"@jupyterlab/markdownviewer": "^3.2.5",
"@jupyterlab/notebook": "^3.2.5",
"@jupyterlab/settingeditor": "^3.2.5",
"@jupyterlab/terminal": "^3.2.5",
"@jupyterlab/toc": "^5.2.5",
"@jupyterlab/tooltip": "^3.2.5",
"@lumino/datagrid": "^0.34.1",
"@types/codemirror": "^5.6.20",
"@types/react": "^17.0.0",
"@types/react-dom": "^17.0.0",
"@types/requirejs": "^2.1.34",
"@typescript-eslint/eslint-plugin": "^4.8.1",
"@typescript-eslint/parser": "^4.8.1",
"eslint": "^7.14.0",
"eslint-config-prettier": "^6.15.0",
"eslint-plugin-prettier": "^3.1.4",
"eslint-plugin-prettier": "^3.1.4",
"npm-run-all": "^4.1.5",
"prettier": "^2.1.1",
"rimraf": "^3.0.2",
Expand All @@ -68,7 +96,8 @@
"style/index.js"
],
"styleModule": "style/index.js",
"jupyterlab": {
"jupyterlab": {
"webpackConfig": "./webpack.config.js",
"extension": true,
"outputDir": "jupyterlab_plugin_playground/labextension",
"schemaDir": "schema"
Expand Down
100 changes: 97 additions & 3 deletions schema/plugin.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,21 @@
{
"title": "Plugin Playground",
"jupyter.lab.setting-icon": "ui-components:extension",
"jupyter.lab.setting-icon-label": "Plugin Playground",
"properties": {
"allowCDN": {
"title": "Allow execution from CDN?",
"description": "Whether to allow execution of modules directly from CDN. The accepted values are `never` and `always-insecure`.",
"type": "string",
"enum": ["awaiting-decision", "always-insecure", "never"],
"default": "awaiting-decision"
},
"requirejsCDN": {
"title": "Base URL of a CDN for requirejs",
"description": "URL from which the unknown packages should be loaded, e.g. https://unpkg.com/",
"default": "https://cdn.jsdelivr.net/npm/",
"type": "string"
},
"urls": {
"title": "Plugin URLs",
"description": "List of URL strings that will be fetched and loaded as plugins automatically on startup.",
Expand All @@ -10,6 +25,12 @@
"type": "string"
}
},
"showIconInLauncher": {
"title": "Show icon in launcher",
"description": "Whether to show the Plugin Playground icon in the lanucher.",
"default": true,
"type": "boolean"
},
"plugins": {
"title": "Plugins",
"description": "List of strings of plugin text to load automatically. Line breaks are encoded as '\\n'",
Expand All @@ -18,8 +39,81 @@
"items": {
"type": "string"
}
},
"toolbar": {
"title": "Toolbar items (for JupyterLab 3.3+)",
"items": {
"$ref": "#/definitions/toolbarItem"
},
"type": "array",
"default": []
}
},
"additionalProperties": false,
"type": "object"
}
"jupyter.lab.transform": false,
"jupyter.lab.toolbars": {
"Editor": [
{
"name": "insert",
"command": "plugin-playground:load-as-extension",
"rank": 20
}
]
},
"jupyter.lab.shortcuts": [
{
"command": "plugin-playground:load-as-extension",
"keys": ["Ctrl Alt C"],
"selector": ".jp-Editor"
}
],
"additionalProperties": true,
"type": "object",
"definitions": {
"toolbarItem": {
"properties": {
"name": {
"title": "Unique name",
"type": "string"
},
"args": {
"title": "Command arguments",
"type": "object"
},
"command": {
"title": "Command id",
"type": "string",
"default": ""
},
"disabled": {
"title": "Whether the item is ignored or not",
"type": "boolean",
"default": false
},
"icon": {
"title": "Item icon id",
"description": "If defined, it will override the command icon",
"type": "string"
},
"label": {
"title": "Item label",
"description": "If defined, it will override the command label",
"type": "string"
},
"type": {
"title": "Item type",
"type": "string",
"enum": ["command", "spacer"]
},
"rank": {
"title": "Item rank",
"type": "number",
"minimum": 0,
"default": 50
}
},
"required": ["name"],
"additionalProperties": false,
"type": "object"
}
}
}
Loading