Skip to content

Commit ff549c8

Browse files
authored
feat: Bundle notebook renderers (#272)
* feat: integrate notebook renderers and fix ipywidget configuration Replace external dependency on ms-toolsai.jupyter-renderers with integrated renderer functionality bundled directly in the extension. Build system changes: - Switch renderers build from webpack to esbuild - Add esbuild entries for notebook-renderer, ipywidgets-renderer, ipywidgets7, ipywidgets8, and ipywidgetsKernel - Copy renderer source from vscode-notebook-renderers into src/renderers/ Package.json renderer configuration: - Add deepnote-ipywidget-renderer with MIME type support - Add jupyter-notebook preloads for ipywidget scripts - Update renderer entrypoints to use new bundled paths IPyWidget fixes: - Change DefaultCommTarget from 'deepnote.widget' to 'jupyter.widget' - Change WIDGET_MIMETYPE from deepnote to standard jupyter MIME types - Update renderer messaging channel IDs to 'deepnote-ipywidget-renderer' - Fix hardcoded comm target in ipyWidgetMessageDispatcher Kernel interface updates: - Add registerCommTarget to IKernelConnection interface - Add kernelSession property to rawKernelConnection - Update rawSessionConnection to implement new interface methods Other improvements: - Add logging for widget kernel communication - Update renderer version checker for integrated renderers - Fix plotView renderer communication imports - Simplify fixJupyterLabRenderers() in postInstall.js * fix version * fix: address CodeRabbit review feedback - Fix async function returning JSX instead of component in transforms.tsx - Remove debug "Hello World" message in builtinRendererHooks.ts - Fix subshellId to delegate to realKernel instead of hardcoded null - Add error handling for vegaEmbed and fix memory leak in disposeOutputItem - Update preload.ts comment (remove outdated webpack reference) - Fix declaration order in type definition files - Fix typo "them" -> "theme" in vegaRenderer.ts - Simplify boolean return in isMimeTypeSupported - Reorder clipboard support check before creating ClipboardItem * fix: address remaining CodeRabbit review feedback Accessibility improvements in render.tsx: - Add alt attribute to img element - Add type="button" to all button elements - Remove redundant role="button" attributes - Add title elements to SVGs for screen readers - Add aria-hidden="true" to decorative SVGs - Add onFocus/onBlur keyboard handlers alongside mouse events Consolidate duplicated type definitions: - Create src/renderers/shared/types.ts with shared message types - Update client/constants.ts to re-export from shared types - Update extension/constants.ts to re-export from shared types Update dependencies: - Update path-browserify from ^0.0.1 to ^1.0.1 * fix(security): resolve npm audit vulnerabilities - Fix vega-functions XSS vulnerability (high severity) - Fix vega-selections XSS vulnerability (high severity) - Fix prismjs DOM Clobbering vulnerability (moderate severity) - Downgrade @nteract/transform-vega from 7.0.10 to 7.0.6 All 7 vulnerabilities resolved, npm audit now passes. * fix: remove elkjs dependency due to EPL-2.0 license incompatibility Add npm override to replace @mermaid-js/layout-elk with empty-pkg (MIT). This removes elkjs from the dependency tree while preserving Mermaid diagram functionality (uses default dagre layout instead of ELK). Dependency chain removed: @mermaid-js/layout-elk → elkjs (EPL-2.0) * fix: add stub for @nteract/presentational-components to fix esbuild error The @nteract/transform-vega package imports @nteract/presentational-components but doesn't declare it as a dependency. Instead of adding the full package (which has 4 moderate vulnerabilities via prismjs), create a minimal stub that only exports the Error component actually used. This resolves the CI build failure: "Could not resolve @nteract/presentational-components" * tilde * cr feedback * Remove RendererVersionChecker
1 parent 6a24088 commit ff549c8

41 files changed

Lines changed: 8187 additions & 542 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

build/ci/postInstall.js

Lines changed: 5 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -91,7 +91,8 @@ function makeVariableExplorerAlwaysSorted() {
9191
}
9292

9393
function fixJupyterLabRenderers() {
94-
const warnings = [];
94+
// This fix is no longer needed as modern JupyterLab versions don't use marked directly
95+
// Kept for backwards compatibility with older versions
9596
['node_modules/@jupyterlab/cells/lib/widget.js', 'node_modules/@jupyterlab/rendermime/lib/renderers.js'].forEach(
9697
(file) => {
9798
const filePath = path.join(__dirname, '..', '..', file);
@@ -101,15 +102,12 @@ function fixJupyterLabRenderers() {
101102
const textToReplace = `import marked from 'marked'`;
102103
const textToReplaceWith = `import { marked } from 'marked'`;
103104
const fileContents = fs.readFileSync(filePath, 'utf8').toString();
104-
if (fileContents.indexOf(textToReplace) === -1 && fileContents.indexOf(textToReplaceWith) === -1) {
105-
warnings.push('Unable to find Jupyter marked usage to replace!');
105+
if (fileContents.indexOf(textToReplace) !== -1) {
106+
fs.writeFileSync(filePath, fileContents.replace(textToReplace, textToReplaceWith));
107+
console.log(colors.green(file + ' file updated (marked import fix)'));
106108
}
107-
fs.writeFileSync(filePath, fileContents.replace(textToReplace, `import { marked } from 'marked'`));
108109
}
109110
);
110-
if (warnings.length === 2) {
111-
throw new Error(warnings[0] + '\n' + warnings[1]);
112-
}
113111
}
114112

115113
/**

build/esbuild/build.ts

Lines changed: 88 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -68,7 +68,12 @@ const commonExternals = [
6868
'ansi-regex' // Used by regexp utils
6969
];
7070
// Create separate copies to avoid shared-state mutations
71-
const webExternals = [...commonExternals];
71+
// For web, add Node.js native modules that can't run in browser
72+
const webExternals = [
73+
...commonExternals,
74+
'canvas', // Native module used by vega for server-side rendering, not needed in browser
75+
'mathjax-electron' // Uses Node.js path module, MathJax rendering handled differently in browser
76+
];
7277
const desktopExternals = [...commonExternals, ...deskTopNodeModulesToExternalize];
7378
const bundleConfig = getBundleConfiguration();
7479
const isDevbuild = !process.argv.includes('--production');
@@ -243,7 +248,16 @@ function createConfig(
243248
const releaseVersionScriptFile = isPreRelease ? 'release.pre-release.js' : 'release.stable.js';
244249
const alias: Record<string, string> = {
245250
moment: path.join(extensionFolder, 'build', 'webpack', 'moment.js'),
246-
'vscode-jupyter-release-version': path.join(__dirname, releaseVersionScriptFile)
251+
'vscode-jupyter-release-version': path.join(__dirname, releaseVersionScriptFile),
252+
// Stub for @nteract/presentational-components to avoid pulling in vulnerable dependencies
253+
'@nteract/presentational-components': path.join(
254+
extensionFolder,
255+
'src',
256+
'renderers',
257+
'client',
258+
'stubs',
259+
'nteract-presentational-components.tsx'
260+
)
247261
};
248262
// Use ESM entry for jsonc-parser to avoid UMD internal require() issues when bundling
249263
if (target === 'desktop') {
@@ -410,9 +424,33 @@ async function buildAll() {
410424
path.join(extensionFolder, 'src', 'webviews', 'webview-side', 'bigNumberComparisonSettings', 'index.tsx'),
411425
path.join(extensionFolder, 'dist', 'webviews', 'webview-side', 'bigNumberComparisonSettings', 'index.js'),
412426
{ target: 'web', watch: watchAll }
427+
),
428+
// Notebook renderers (integrated from jupyter-renderers)
429+
build(
430+
path.join(extensionFolder, 'src', 'renderers', 'client', 'index.tsx'),
431+
path.join(extensionFolder, 'dist', 'renderers', 'client', 'renderers.js'),
432+
{ target: 'web', watch: watchAll }
433+
),
434+
build(
435+
path.join(extensionFolder, 'src', 'renderers', 'client', 'builtinRendererHooks.ts'),
436+
path.join(extensionFolder, 'dist', 'renderers', 'client', 'builtinRendererHooks.js'),
437+
{ target: 'web', watch: watchAll }
438+
),
439+
build(
440+
path.join(extensionFolder, 'src', 'renderers', 'client', 'markdown.ts'),
441+
path.join(extensionFolder, 'dist', 'renderers', 'client', 'markdown.js'),
442+
{ target: 'web', watch: watchAll }
443+
),
444+
build(
445+
path.join(extensionFolder, 'src', 'renderers', 'client', 'preload.ts'),
446+
path.join(extensionFolder, 'dist', 'renderers', 'client', 'preload.js'),
447+
{ target: 'web', watch: watchAll }
413448
)
414449
);
415450

451+
// Copy ipywidgets from node_modules
452+
builders.push(copyIPyWidgets7(), copyIPyWidgets8());
453+
416454
if (isDevbuild) {
417455
builders.push(
418456
build(
@@ -528,6 +566,54 @@ async function copyNodeGypBuild() {
528566
await fs.copy(source, target, { recursive: true });
529567
}
530568

569+
/**
570+
* Helper to copy a bundle file if it exists, with clear error messaging
571+
*/
572+
async function copyBundleIfExists(source: string, target: string, description: string): Promise<void> {
573+
const exists = await fs.pathExists(source);
574+
if (!exists) {
575+
console.warn(
576+
colors.yellow(`Warning: ${description} not found at ${source}. `) +
577+
colors.yellow('Skipping copy. Install the package if this feature is needed.')
578+
);
579+
return;
580+
}
581+
await fs.ensureDir(path.dirname(target));
582+
await fs.copyFile(source, target);
583+
}
584+
585+
/**
586+
* Copy @vscode/jupyter-ipywidgets7 pre-built bundle for IPyWidget v7 support
587+
*/
588+
async function copyIPyWidgets7() {
589+
const source = path.join(
590+
extensionFolder,
591+
'node_modules',
592+
'@vscode',
593+
'jupyter-ipywidgets7',
594+
'dist',
595+
'ipywidgets.js'
596+
);
597+
const target = path.join(extensionFolder, 'dist', 'renderers', 'ipywidgets7', 'ipywidgets.js');
598+
await copyBundleIfExists(source, target, '@vscode/jupyter-ipywidgets7 bundle');
599+
}
600+
601+
/**
602+
* Copy @vscode/jupyter-ipywidgets8 pre-built bundle for IPyWidget v8 support
603+
*/
604+
async function copyIPyWidgets8() {
605+
const source = path.join(
606+
extensionFolder,
607+
'node_modules',
608+
'@vscode',
609+
'jupyter-ipywidgets8',
610+
'dist',
611+
'ipywidgets.js'
612+
);
613+
const target = path.join(extensionFolder, 'dist', 'renderers', 'ipywidgets8', 'ipywidgets.js');
614+
await copyBundleIfExists(source, target, '@vscode/jupyter-ipywidgets8 bundle');
615+
}
616+
531617
async function buildSqlLanguageServer() {
532618
// Bundle the sql-language-server with all its dependencies into a single file
533619
const entryPoint = path.join(

0 commit comments

Comments
 (0)