diff --git a/docs/md/how_to/python/jupyterlab.md b/docs/md/how_to/python/jupyterlab.md index 33f6aea33b..78afd20173 100644 --- a/docs/md/how_to/python/jupyterlab.md +++ b/docs/md/how_to/python/jupyterlab.md @@ -59,3 +59,27 @@ Perspective also exposes a JS-only `mimerender-extension`. This lets you view this by right clicking one of these files and `Open With->CSVPerspective` (or `JSONPerspective` or `ArrowPerspective`). Perspective will also install itself as the default handler for opening `.arrow` files. --> + +## Depending on Perspective in your own JupyterLab Widget + +Perspective provides a [token for integration with JupyterLab's federated dependency model](https://jupyterlab.readthedocs.io/en/stable/extension/extension_dev.html#plugins-interacting-with-each-other). +This allows your plugin to simply depend on Perspective for initialization. + +In your plugin code: + +```javascript +import {IPerspective} from "@finos/perspective-jupyterlab"; + +export const MyCoolPlugin = { + activate, + id: "my-cool-plugin", + requires: [IPerspective], + autoStart: true, +}; + +// to use perspective, simply import. No initialization required +import perspective from "@finos/perspective"; +``` + +And remember to add `perspective-python` as a python dependency, to ensure +the Perspective JupyterLab extension is installed. diff --git a/packages/perspective-jupyterlab/package.json b/packages/perspective-jupyterlab/package.json index ee98757ad1..68be824d31 100644 --- a/packages/perspective-jupyterlab/package.json +++ b/packages/perspective-jupyterlab/package.json @@ -20,17 +20,19 @@ "build": "node ./build.mjs", "clean": "node ./clean.mjs", "test:jupyter": "__JUPYTERLAB_PORT__=6538 npx playwright test --config ../../tools/perspective-test/playwright.config.ts -- --jupyter", - "test:jupyter:build": "node ./build.mjs --test" + "test:jupyter:build": "node ./build.mjs --test", + "test:jupyter:extension": "jupyter labextension build test/extension" }, "dependencies": { + "@finos/perspective": "workspace:", + "@finos/perspective-viewer": "workspace:", "@finos/perspective-viewer-d3fc": "workspace:", "@finos/perspective-viewer-datagrid": "workspace:", "@finos/perspective-viewer-openlayers": "workspace:", - "@finos/perspective-viewer": "workspace:", - "@finos/perspective": "workspace:", "@jupyter-widgets/base": ">2 <5", "@jupyterlab/application": ">2 <5", "@lumino/application": "<3", + "@lumino/coreutils": "<3", "@lumino/widgets": "<3" }, "devDependencies": { @@ -39,6 +41,7 @@ "@jupyterlab/builder": "^4", "@prospective.co/procss": "^0.1.16", "copy-webpack-plugin": "~12", + "webpack": "catalog:", "zx": "^8.1.8" }, "jupyterlab": { @@ -49,6 +52,26 @@ "@jupyter-widgets/base": { "bundled": false, "singleton": true + }, + "@finos/perspective": { + "bundled": true, + "singleton": true + }, + "@finos/perspective-viewer": { + "bundled": true, + "singleton": true + }, + "@finos/perspective-viewer-d3fc": { + "bundled": true, + "singleton": true + }, + "@finos/perspective-viewer-datagrid": { + "bundled": true, + "singleton": true + }, + "@finos/perspective-viewer-openlayers": { + "bundled": true, + "singleton": true } }, "discovery": { diff --git a/packages/perspective-jupyterlab/src/js/index.js b/packages/perspective-jupyterlab/src/js/index.js index 9586031a8c..b814aa9a26 100644 --- a/packages/perspective-jupyterlab/src/js/index.js +++ b/packages/perspective-jupyterlab/src/js/index.js @@ -22,6 +22,7 @@ await Promise.all([ ]); export * from "./model"; +export * from "./tokens"; export * from "./version"; export * from "./view"; export * from "./widget"; diff --git a/packages/perspective-jupyterlab/src/js/plugin.js b/packages/perspective-jupyterlab/src/js/plugin.js index 0945fe273e..eb73d07021 100644 --- a/packages/perspective-jupyterlab/src/js/plugin.js +++ b/packages/perspective-jupyterlab/src/js/plugin.js @@ -12,6 +12,14 @@ import { IJupyterWidgetRegistry } from "@jupyter-widgets/base"; import { PerspectiveModel } from "./model"; +import { + IPerspective, + IPerspectiveJupyterlab, + IPerspectiveViewer, + IPerspectiveViewerD3fc, + IPerspectiveViewerDatagrid, + IPerspectiveViewerOpenlayers, +} from "./tokens"; import { PerspectiveView } from "./view"; import { PERSPECTIVE_VERSION } from "./version"; const EXTENSION_ID = "@finos/perspective-jupyterlab"; @@ -24,6 +32,14 @@ export const PerspectiveJupyterPlugin = { id: EXTENSION_ID, // @ts-ignore requires: [IJupyterWidgetRegistry], + provides: [ + IPerspective, + IPerspectiveJupyterlab, + IPerspectiveViewer, + IPerspectiveViewerD3fc, + IPerspectiveViewerDatagrid, + IPerspectiveViewerOpenlayers, + ], activate: (app, registry) => { registry.registerWidget({ name: EXTENSION_ID, diff --git a/packages/perspective-jupyterlab/src/js/tokens.js b/packages/perspective-jupyterlab/src/js/tokens.js new file mode 100644 index 0000000000..66c21fb47a --- /dev/null +++ b/packages/perspective-jupyterlab/src/js/tokens.js @@ -0,0 +1,28 @@ +// ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓ +// ┃ ██████ ██████ ██████ █ █ █ █ █ █▄ ▀███ █ ┃ +// ┃ ▄▄▄▄▄█ █▄▄▄▄▄ ▄▄▄▄▄█ ▀▀▀▀▀█▀▀▀▀▀ █ ▀▀▀▀▀█ ████████▌▐███ ███▄ ▀█ █ ▀▀▀▀▀ ┃ +// ┃ █▀▀▀▀▀ █▀▀▀▀▀ █▀██▀▀ ▄▄▄▄▄ █ ▄▄▄▄▄█ ▄▄▄▄▄█ ████████▌▐███ █████▄ █ ▄▄▄▄▄ ┃ +// ┃ █ ██████ █ ▀█▄ █ ██████ █ ███▌▐███ ███████▄ █ ┃ +// ┣━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┫ +// ┃ Copyright (c) 2017, the Perspective Authors. ┃ +// ┃ ╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌ ┃ +// ┃ This file is part of the Perspective library, distributed under the terms ┃ +// ┃ of the [Apache License 2.0](https://www.apache.org/licenses/LICENSE-2.0). ┃ +// ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛ + +import { Token } from "@lumino/coreutils"; + +export const IPerspective = new Token("@finos/perspective"); +export const IPerspectiveJupyterlab = new Token( + "@finos/perspective-jupyterlab", +); +export const IPerspectiveViewer = new Token("@finos/perspective-viewer"); +export const IPerspectiveViewerD3fc = new Token( + "@finos/perspective-viewer-d3fc", +); +export const IPerspectiveViewerDatagrid = new Token( + "@finos/perspective-viewer-datagrid", +); +export const IPerspectiveViewerOpenlayers = new Token( + "@finos/perspective-viewer-openlayers", +); diff --git a/packages/perspective-jupyterlab/test/jupyter/extension/extension.js b/packages/perspective-jupyterlab/test/jupyter/extension/extension.js new file mode 100644 index 0000000000..c9db7d7cbc --- /dev/null +++ b/packages/perspective-jupyterlab/test/jupyter/extension/extension.js @@ -0,0 +1,22 @@ +// ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓ +// ┃ ██████ ██████ ██████ █ █ █ █ █ █▄ ▀███ █ ┃ +// ┃ ▄▄▄▄▄█ █▄▄▄▄▄ ▄▄▄▄▄█ ▀▀▀▀▀█▀▀▀▀▀ █ ▀▀▀▀▀█ ████████▌▐███ ███▄ ▀█ █ ▀▀▀▀▀ ┃ +// ┃ █▀▀▀▀▀ █▀▀▀▀▀ █▀██▀▀ ▄▄▄▄▄ █ ▄▄▄▄▄█ ▄▄▄▄▄█ ████████▌▐███ █████▄ █ ▄▄▄▄▄ ┃ +// ┃ █ ██████ █ ▀█▄ █ ██████ █ ███▌▐███ ███████▄ █ ┃ +// ┣━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┫ +// ┃ Copyright (c) 2017, the Perspective Authors. ┃ +// ┃ ╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌ ┃ +// ┃ This file is part of the Perspective library, distributed under the terms ┃ +// ┃ of the [Apache License 2.0](https://www.apache.org/licenses/LICENSE-2.0). ┃ +// ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛ + +import { IPerspective } from "@finos/perspective-jupyterlab"; + +module.exports = [ + { + id: "@finos/perspective-test-federated", + autoStart: true, + requires: [IPerspective], + activate: () => "hello", + }, +]; diff --git a/packages/perspective-jupyterlab/test/jupyter/extension/package.json b/packages/perspective-jupyterlab/test/jupyter/extension/package.json new file mode 100644 index 0000000000..b661eaef1c --- /dev/null +++ b/packages/perspective-jupyterlab/test/jupyter/extension/package.json @@ -0,0 +1,41 @@ +{ + "name": "@finos/perspective-test-federated", + "version": "0.0.0", + "private": true, + "scripts": {}, + "dependencies": { + "@finos/perspective-jupyterlab": "file:../../.." + }, + "devDependencies": { + "@jupyterlab/builder": "^4" + }, + "publishConfig": { + "access": "public" + }, + "jupyterlab": { + "extension": true, + "outputDir": "./dist", + "sharedPackages": { + "@finos/perspective": { + "bundled": false, + "singleton": true + }, + "@finos/perspective-viewer": { + "bundled": false, + "singleton": true + }, + "@finos/perspective-viewer-d3fc": { + "bundled": false, + "singleton": true + }, + "@finos/perspective-viewer-datagrid": { + "bundled": false, + "singleton": true + }, + "@finos/perspective-viewer-openlayers": { + "bundled": false, + "singleton": true + } + } + } +} diff --git a/packages/perspective-jupyterlab/test/jupyter/extension/yarn.lock b/packages/perspective-jupyterlab/test/jupyter/extension/yarn.lock new file mode 100644 index 0000000000..e69de29bb2 diff --git a/packages/perspective-jupyterlab/webpack.config.js b/packages/perspective-jupyterlab/webpack.config.js index 8cb8152ac7..501e25c9ed 100644 --- a/packages/perspective-jupyterlab/webpack.config.js +++ b/packages/perspective-jupyterlab/webpack.config.js @@ -11,6 +11,20 @@ // ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛ const CopyPlugin = require("copy-webpack-plugin"); +const webpack = require("webpack"); +const { ModuleFederationPlugin } = webpack.container; +const packageData = require("./package.json"); + +const shared = Object.keys(packageData.jupyterlab.sharedPackages) + .filter((pkg) => pkg.startsWith("@finos/perspective")) + .reduce((obj, pkg) => { + obj[pkg] = { + singleton: true, + packageName: pkg, + requiredVersion: require(`${pkg}/package.json`).version, + }; + return obj; + }, {}); module.exports = { experiments: { @@ -20,5 +34,13 @@ module.exports = { new CopyPlugin({ patterns: [{ from: "./install.json", to: "../install.json" }], }), + new ModuleFederationPlugin({ + library: { + type: "var", + name: ["PERSPECTIVE"], + }, + name: "PERSPECTIVE", + shared, + }), ], }; diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index d304013228..b56bfc4b1d 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -175,7 +175,7 @@ catalogs: specifier: '>=6 <7' version: 6.3.6 webpack: - specifier: '>=5 <6' + specifier: 5.102.0 version: 5.102.0 webpack-cli: specifier: '>=5 <6' @@ -614,6 +614,9 @@ importers: '@lumino/application': specifier: <3 version: 2.4.4 + '@lumino/coreutils': + specifier: <3 + version: 2.2.1 '@lumino/widgets': specifier: <3 version: 2.7.1 @@ -632,7 +635,10 @@ importers: version: 0.1.17 copy-webpack-plugin: specifier: ~12 - version: 12.0.2(webpack@5.102.0(webpack-cli@5.1.4)) + version: 12.0.2(webpack@5.102.0) + webpack: + specifier: 'catalog:' + version: 5.102.0(webpack-cli@5.1.4) zx: specifier: ^8.1.8 version: 8.8.4 @@ -12995,17 +13001,17 @@ snapshots: '@webassemblyjs/ast': 1.14.1 '@xtuc/long': 4.2.2 - '@webpack-cli/configtest@2.1.1(webpack-cli@5.1.4(webpack@5.102.0))(webpack@5.102.0)': + '@webpack-cli/configtest@2.1.1(webpack-cli@5.1.4)(webpack@5.102.0)': dependencies: webpack: 5.102.0(webpack-cli@5.1.4) webpack-cli: 5.1.4(webpack@5.102.0) - '@webpack-cli/info@2.0.2(webpack-cli@5.1.4(webpack@5.102.0))(webpack@5.102.0)': + '@webpack-cli/info@2.0.2(webpack-cli@5.1.4)(webpack@5.102.0)': dependencies: webpack: 5.102.0(webpack-cli@5.1.4) webpack-cli: 5.1.4(webpack@5.102.0) - '@webpack-cli/serve@2.0.5(webpack-cli@5.1.4(webpack@5.102.0))(webpack@5.102.0)': + '@webpack-cli/serve@2.0.5(webpack-cli@5.1.4)(webpack@5.102.0)': dependencies: webpack: 5.102.0(webpack-cli@5.1.4) webpack-cli: 5.1.4(webpack@5.102.0) @@ -13720,7 +13726,7 @@ snapshots: serialize-javascript: 6.0.2 webpack: 5.102.0(webpack-cli@5.1.4) - copy-webpack-plugin@12.0.2(webpack@5.102.0(webpack-cli@5.1.4)): + copy-webpack-plugin@12.0.2(webpack@5.102.0): dependencies: fast-glob: 3.3.3 glob-parent: 6.0.2 @@ -18680,9 +18686,9 @@ snapshots: webpack-cli@5.1.4(webpack@5.102.0): dependencies: '@discoveryjs/json-ext': 0.5.7 - '@webpack-cli/configtest': 2.1.1(webpack-cli@5.1.4(webpack@5.102.0))(webpack@5.102.0) - '@webpack-cli/info': 2.0.2(webpack-cli@5.1.4(webpack@5.102.0))(webpack@5.102.0) - '@webpack-cli/serve': 2.0.5(webpack-cli@5.1.4(webpack@5.102.0))(webpack@5.102.0) + '@webpack-cli/configtest': 2.1.1(webpack-cli@5.1.4)(webpack@5.102.0) + '@webpack-cli/info': 2.0.2(webpack-cli@5.1.4)(webpack@5.102.0) + '@webpack-cli/serve': 2.0.5(webpack-cli@5.1.4)(webpack@5.102.0) colorette: 2.0.20 commander: 10.0.1 cross-spawn: 7.0.6 diff --git a/pnpm-workspace.yaml b/pnpm-workspace.yaml index 7601830097..62d21f414f 100644 --- a/pnpm-workspace.yaml +++ b/pnpm-workspace.yaml @@ -1,97 +1,94 @@ packages: - - "tools/perspective-test" - - "tools/perspective-scripts" - - "tools/perspective-bench" - - "packages/perspective-client" - - "tools/perspective-esbuild-plugin" - - "packages/perspective-viewer-datagrid" - - "packages/perspective-viewer-d3fc" - - "packages/perspective-viewer-openlayers" - - "packages/perspective-workspace" - - "packages/perspective-jupyterlab" - - "packages/perspective-react" - - "packages/perspective-cli" - - "rust/generate-metadata" - - "rust/perspective" - - "rust/perspective-js" - - "rust/perspective-viewer" - - "rust/perspective-python" - - "rust/perspective-server" - - "examples/*" - - "docs" + - tools/perspective-test + - tools/perspective-scripts + - tools/perspective-bench + - packages/perspective-client + - tools/perspective-esbuild-plugin + - packages/perspective-viewer-datagrid + - packages/perspective-viewer-d3fc + - packages/perspective-viewer-openlayers + - packages/perspective-workspace + - packages/perspective-jupyterlab + - packages/perspective-react + - packages/perspective-cli + - rust/generate-metadata + - rust/perspective + - rust/perspective-js + - rust/perspective-viewer + - rust/perspective-python + - rust/perspective-server + - examples/* + - docs catalog: - # Dependencies - "@d3fc/d3fc-chart": "5.1.9" - "@d3fc/d3fc-element": "6.2.0" - "@jupyter-widgets/base": ">2 <5" - "@jupyterlab/application": ">2 <5" - "@lumino/application": "<3" - "@lumino/widgets": "<3" - "chroma-js": ">=3 <4" - "d3-array": ">=3" - "d3-color": ">=3.1" - "d3-selection": ">=3" - "d3-svg-legend": ">=2" - "d3": "^7.9.0" - "d3fc": "^15.2.13" - "ol": "^5.3.2" - "pro_self_extracting_wasm": "0.0.9" - "react-dom": "^18" - "react": "^18" - "regular-table": "=0.6.8" - "stoppable": "=1.1.0" - "ws": "^8.17.0" - - # Dev Dependencies - "@fontsource/roboto-mono": "4.5.10" - "@iarna/toml": "3.0.0" - "@jupyterlab/builder": "^4" - "@playwright/experimental-ct-react": "=1.52.0" - "@playwright/test": "=1.52.0" - "@prospective.co/procss": "0.1.17" - "@types/d3": "^7.4.3" - "@types/lodash": "^4.17.20" - "@types/node": ">=22" - "@types/react-dom": "^18" - "@types/react": "^18" - "@types/stoppable": ">=1" - "@types/ws": ">=8" - "@zip.js/zip.js": "^2.7.54" - "apache-arrow": "18.1.0" - "arraybuffer-loader": ">=1.0.8 <2" - "auto-changelog": "^2.5.0" - "chalk": ">=5" - "commander": ">=14 <15" - "copy-webpack-plugin": "~12" - "css-loader": ">=7 <8" - "dotenv": ">=17" - "esbuild": "^0.25.5" - "express-ws": ">=5 <6" - "express": ">=5 <6" - "fs-extra": ">=11 <12" - "glob-gitignore": "^1.0.15" - "gradient-parser": ">=1 <2" - "html-webpack-plugin": ">=5 <6" - "http-server": "^14.1.1" - "husky": ">=9" - "inquirer": ">=12 <13" - "less": "^4.1.0" - "lodash": "^4.17.20" - "microtime": ">=3 <4" - "mkdirp": "^0.5.6" - "moment": "^2.30.1" - "npm-run-all": "^4.1.5" - "octokit": "^1.8.1" - "prettier": ">=3 <4" - "puppeteer": ">=24" - "style-loader": ">=4 < 5" - "superstore-arrow": "3.2.0" - "tar": "^7.4.3" - "tsx": "^4.20.3" - "typedoc": "^0.28.7" - "typescript": ">=5 <6" - "vite": ">=6 <7" - "webpack-cli": ">=5 <6" - "webpack": ">=5 <6" - "zx": ">=8 <9" + '@d3fc/d3fc-chart': 5.1.9 + '@d3fc/d3fc-element': 6.2.0 + '@fontsource/roboto-mono': 4.5.10 + '@iarna/toml': 3.0.0 + '@jupyter-widgets/base': '>2 <5' + '@jupyterlab/application': '>2 <5' + '@jupyterlab/builder': ^4 + '@lumino/application': <3 + '@lumino/widgets': <3 + '@playwright/experimental-ct-react': '=1.52.0' + '@playwright/test': '=1.52.0' + '@prospective.co/procss': 0.1.17 + '@types/d3': ^7.4.3 + '@types/lodash': ^4.17.20 + '@types/node': '>=22' + '@types/react': ^18 + '@types/react-dom': ^18 + '@types/stoppable': '>=1' + '@types/ws': '>=8' + '@zip.js/zip.js': ^2.7.54 + apache-arrow: 18.1.0 + arraybuffer-loader: '>=1.0.8 <2' + auto-changelog: ^2.5.0 + chalk: '>=5' + chroma-js: '>=3 <4' + commander: '>=14 <15' + copy-webpack-plugin: ~12 + css-loader: '>=7 <8' + d3: ^7.9.0 + d3-array: '>=3' + d3-color: '>=3.1' + d3-selection: '>=3' + d3-svg-legend: '>=2' + d3fc: ^15.2.13 + dotenv: '>=17' + esbuild: ^0.25.5 + express: '>=5 <6' + express-ws: '>=5 <6' + fs-extra: '>=11 <12' + glob-gitignore: ^1.0.15 + gradient-parser: '>=1 <2' + html-webpack-plugin: '>=5 <6' + http-server: ^14.1.1 + husky: '>=9' + inquirer: '>=12 <13' + less: ^4.1.0 + lodash: ^4.17.20 + microtime: '>=3 <4' + mkdirp: ^0.5.6 + moment: ^2.30.1 + npm-run-all: ^4.1.5 + octokit: ^1.8.1 + ol: ^5.3.2 + prettier: '>=3 <4' + pro_self_extracting_wasm: 0.0.9 + puppeteer: '>=24' + react: ^18 + react-dom: ^18 + regular-table: '=0.6.8' + stoppable: '=1.1.0' + style-loader: '>=4 < 5' + superstore-arrow: 3.2.0 + tar: ^7.4.3 + tsx: ^4.20.3 + typedoc: ^0.28.7 + typescript: '>=5 <6' + vite: '>=6 <7' + webpack: 5.102.0 + webpack-cli: '>=5 <6' + ws: ^8.17.0 + zx: '>=8 <9'