diff --git a/build/gulpfile.extensions.js b/build/gulpfile.extensions.js index 1505c185d6b..63e7a7f2280 100644 --- a/build/gulpfile.extensions.js +++ b/build/gulpfile.extensions.js @@ -8,6 +8,7 @@ require('events').EventEmitter.defaultMaxListeners = 100; const gulp = require('gulp'); const path = require('path'); +const child_process = require('child_process'); const nodeUtil = require('util'); const es = require('event-stream'); const filter = require('gulp-filter'); @@ -203,30 +204,38 @@ gulp.task(compileExtensionsBuildLegacyTask); // Additional projects to webpack. These typically build code for webviews const webpackMediaConfigFiles = [ 'markdown-language-features/webpack.config.js', - 'markdown-language-features/webpack.notebook.js', - 'notebook-markdown-extensions/webpack.notebook.js', 'simple-browser/webpack.config.js', ]; -const compileExtensionMediaTask = task.define('compile-extension-media', () => webpackExtensionMedia(false)); +// Additional projects to run esbuild on. These typically build code for webviews +const esbuildMediaScripts = [ + 'markdown-language-features/esbuild.js', + 'notebook-markdown-extensions/esbuild.js', +]; + +const compileExtensionMediaTask = task.define('compile-extension-media', () => buildExtensionMedia(false)); gulp.task(compileExtensionMediaTask); exports.compileExtensionMediaTask = compileExtensionMediaTask; -const watchExtensionMedia = task.define('watch-extension-media', () => webpackExtensionMedia(true)); +const watchExtensionMedia = task.define('watch-extension-media', () => buildExtensionMedia(true)); gulp.task(watchExtensionMedia); exports.watchExtensionMedia = watchExtensionMedia; -function webpackExtensionMedia(isWatch, outputRoot) { +const compileExtensionMediaBuildTask = task.define('compile-extension-media-build', () => buildExtensionMedia(false, '.build/extensions')); +gulp.task(compileExtensionMediaBuildTask); + +async function buildExtensionMedia(isWatch, outputRoot) { const webpackConfigLocations = webpackMediaConfigFiles.map(p => { return { configPath: path.join(extensionsPath, p), outputRoot: outputRoot ? path.join(root, outputRoot, path.dirname(p)) : undefined }; }); - return webpackExtensions('packaging extension media', isWatch, webpackConfigLocations); + return Promise.all([ + webpackExtensions('webpacking extension media', isWatch, webpackConfigLocations), + esbuildExtensions('esbuilding extension media', isWatch, outputRoot, esbuildMediaScripts.map(p => path.join(extensionsPath, p))), + ]); } -const compileExtensionMediaBuildTask = task.define('compile-extension-media-build', () => webpackExtensionMedia(false, '.build/extensions')); -gulp.task(compileExtensionMediaBuildTask); //#endregion @@ -336,4 +345,47 @@ async function webpackExtensions(taskName, isWatch, webpackConfigLocations) { }); } +/** + * @param {string} taskName + * @param {boolean} isWatch + * @param {string | undefined} outputRoot + * @param {string} esbuildScripts + */ +async function esbuildExtensions(taskName, isWatch, outputRoot, esbuildScripts) { + function reporter(/** @type {string} */ stdError, /** @type {string} */script) { + const matches = (stdError || '').match(/\> (.+): error: (.+)?/g); + fancyLog(`Finished ${ansiColors.green(taskName)} ${script} with ${matches ? matches.length : 0} errors.`); + for (const match of matches || []) { + fancyLog.error(match); + } + } + + const scripts = esbuildScripts.map(script => { + return new Promise((resolve, reject) => { + const args = [script]; + if (isWatch) { + args.push('--watch'); + } + if (outputRoot) { + args.push('--outputRoot', outputRoot); + } + const proc = child_process.execFile(process.argv[0], args, {}, (error, _stdout, stderr) => { + if (error) { + return reject(error); + } + reporter(stderr, script); + if (stderr) { + return reject(); + } + return resolve(); + }); + + proc.stdout.on('data', (data) => { + fancyLog(`${ansiColors.green(taskName)}: ${data.toString('utf8')}`); + }); + }); + }); + return Promise.all(scripts); +} + diff --git a/extensions/markdown-language-features/esbuild.js b/extensions/markdown-language-features/esbuild.js new file mode 100644 index 00000000000..3d1fca3b673 --- /dev/null +++ b/extensions/markdown-language-features/esbuild.js @@ -0,0 +1,31 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ +const path = require('path'); +const esbuild = require('esbuild'); + +const args = process.argv.slice(2); + +const isWatch = args.indexOf('--watch') >= 0; + +let outputRoot = __dirname; +const outputRootIndex = args.indexOf('--outputRoot'); +if (outputRootIndex >= 0) { + outputRoot = args[outputRootIndex + 1]; +} + +const outDir = path.join(outputRoot, 'notebook-out'); +esbuild.build({ + entryPoints: [ + path.join(__dirname, 'notebook', 'index.ts'), + ], + bundle: true, + minify: true, + sourcemap: false, + format: 'esm', + outdir: outDir, + platform: 'browser', + target: ['es2020'], + incremental: isWatch, +}).catch(() => process.exit(1)); diff --git a/extensions/markdown-language-features/notebook/index.ts b/extensions/markdown-language-features/notebook/index.ts index 99bec20cd55..1d7c6523344 100644 --- a/extensions/markdown-language-features/notebook/index.ts +++ b/extensions/markdown-language-features/notebook/index.ts @@ -3,31 +3,37 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import * as MarkdownIt from 'markdown-it'; +const MarkdownIt = require('markdown-it'); -declare const acquireNotebookRendererApi: any; -type extendMarkdownItFnType = ( - (f: (md: MarkdownIt.MarkdownIt) => void) => void -); - -(function () { - const markdownIt = new MarkdownIt({ +export async function activate(ctx: { + dependencies: ReadonlyArray<{ entrypoint: string }> +}) { + let markdownIt = new MarkdownIt({ html: true }); - (globalThis as any).extendMarkdownIt = ((f: (md: MarkdownIt.MarkdownIt) => void) => { - f(markdownIt); - }) as extendMarkdownItFnType; - - const notebook = acquireNotebookRendererApi('notebookCoreTestRenderer'); - - notebook.onDidCreateMarkdown(({ element, content }: any) => { - const rendered = markdownIt.render(content); - element.innerHTML = rendered; - - // Insert styles into markdown preview shadow dom so that they are applied - for (const markdownStyleNode of document.getElementsByClassName('markdown-style')) { - element.appendChild(markdownStyleNode.cloneNode(true)); + // Should we load the deps before this point? + // Also could we await inside `renderMarkup`? + await Promise.all(ctx.dependencies.map(async (dep) => { + try { + const api = await import(dep.entrypoint); + if (api?.extendMarkdownIt) { + markdownIt = api.extendMarkdownIt(markdownIt); + } + } catch (e) { + console.error('Could not load markdown entryPoint', e); } - }); -}()); + })); + + return { + renderMarkup: (context: { element: HTMLElement, content: string }) => { + const rendered = markdownIt.render(context.content); + context.element.innerHTML = rendered; + + // Insert styles into markdown preview shadow dom so that they are applied + for (const markdownStyleNode of document.getElementsByClassName('markdown-style')) { + context.element.appendChild(markdownStyleNode.cloneNode(true)); + } + } + }; +} diff --git a/extensions/markdown-language-features/notebook/tsconfig.json b/extensions/markdown-language-features/notebook/tsconfig.json index b1bede72c17..2d704f32ded 100644 --- a/extensions/markdown-language-features/notebook/tsconfig.json +++ b/extensions/markdown-language-features/notebook/tsconfig.json @@ -3,6 +3,7 @@ "compilerOptions": { "outDir": "./dist/", "jsx": "react", + "module": "es2020", "lib": [ "es2018", "DOM", diff --git a/extensions/markdown-language-features/package.json b/extensions/markdown-language-features/package.json index 6116bc2d064..19d1354c206 100644 --- a/extensions/markdown-language-features/package.json +++ b/extensions/markdown-language-features/package.json @@ -40,11 +40,14 @@ } }, "contributes": { - "notebookMarkdownRenderer": [ + "notebookMarkupRenderers": [ { "id": "markdownItRenderer", "displayName": "Markdown it renderer", - "entrypoint": "./notebook-out/index.js" + "entrypoint": "./notebook-out/index.js", + "mimeTypes": [ + "text/markdown" + ] } ], "commands": [ @@ -343,7 +346,7 @@ "vscode:prepublish": "npm run build-ext && npm run build-preview", "build-ext": "node ../../node_modules/gulp/bin/gulp.js --gulpfile ../../build/gulpfile.extensions.js compile-extension:markdown-language-features ./tsconfig.json", "build-preview": "npx webpack-cli --mode production", - "build-notebook": "npx webpack-cli --config webpack.notebook --mode production", + "build-notebook": "node ./esbuild", "compile-web": "npx webpack-cli --config extension-browser.webpack.config --mode none", "watch-web": "npx webpack-cli --config extension-browser.webpack.config --mode none --watch --info-verbosity verbose" }, diff --git a/extensions/markdown-language-features/webpack.notebook.js b/extensions/markdown-language-features/webpack.notebook.js deleted file mode 100644 index 3783b8a6c4d..00000000000 --- a/extensions/markdown-language-features/webpack.notebook.js +++ /dev/null @@ -1,28 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ -const path = require('path'); - -module.exports = { - mode: 'production', - entry: { - index: path.join(__dirname, 'notebook', 'index.ts') - }, - module: { - rules: [ - { - test: /\.tsx?$/, - use: 'ts-loader', - exclude: /node_modules/ - } - ] - }, - resolve: { - extensions: ['.tsx', '.ts', '.js'] - }, - output: { - filename: '[name].js', - path: path.resolve(__dirname, 'notebook-out') - } -}; diff --git a/extensions/notebook-markdown-extensions/esbuild.js b/extensions/notebook-markdown-extensions/esbuild.js new file mode 100644 index 00000000000..f1bb454f4f7 --- /dev/null +++ b/extensions/notebook-markdown-extensions/esbuild.js @@ -0,0 +1,41 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ +const path = require('path'); +const fse = require('fs-extra'); +const esbuild = require('esbuild'); + +const args = process.argv.slice(2); + +const isWatch = args.indexOf('--watch') >= 0; + +let outputRoot = __dirname; +const outputRootIndex = args.indexOf('--outputRoot'); +if (outputRootIndex >= 0) { + outputRoot = args[outputRootIndex + 1]; +} + +const outDir = path.join(outputRoot, 'notebook-out'); +esbuild.build({ + entryPoints: [ + path.join(__dirname, 'notebook', 'katex.ts'), + path.join(__dirname, 'notebook', 'emoji.ts') + ], + bundle: true, + minify: true, + sourcemap: false, + format: 'esm', + outdir: outDir, + platform: 'browser', + target: ['es2020'], + incremental: isWatch, +}).catch(() => process.exit(1)); + +fse.copySync( + path.join(__dirname, 'node_modules/katex/dist/katex.min.css'), + path.join(outDir, 'katex.min.css')); + +fse.copySync( + path.join(__dirname, 'node_modules/katex/dist/fonts'), + path.join(outDir, 'fonts/')); diff --git a/extensions/notebook-markdown-extensions/notebook/emoji.ts b/extensions/notebook-markdown-extensions/notebook/emoji.ts index 45c02d31e70..bf82f98ba0f 100644 --- a/extensions/notebook-markdown-extensions/notebook/emoji.ts +++ b/extensions/notebook-markdown-extensions/notebook/emoji.ts @@ -4,17 +4,8 @@ *--------------------------------------------------------------------------------------------*/ import type * as markdownIt from 'markdown-it'; -declare const extendMarkdownIt: undefined | ( - (f: (md: markdownIt.MarkdownIt) => void) => void -); - -(function () { - if (typeof extendMarkdownIt !== 'undefined') { - const emoji = require('markdown-it-emoji'); - - extendMarkdownIt((md: markdownIt.MarkdownIt) => { - md.use(emoji); - }); - } -}()); +const emoji = require('markdown-it-emoji'); +export function extendMarkdownIt(md: markdownIt.MarkdownIt) { + return md.use(emoji); +} diff --git a/extensions/notebook-markdown-extensions/notebook/katex.ts b/extensions/notebook-markdown-extensions/notebook/katex.ts index 7913497cef9..f862fd94940 100644 --- a/extensions/notebook-markdown-extensions/notebook/katex.ts +++ b/extensions/notebook-markdown-extensions/notebook/katex.ts @@ -4,11 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import type * as markdownIt from 'markdown-it'; -declare const extendMarkdownIt: undefined | ( - (f: (md: markdownIt.MarkdownIt) => void) => void -); - -const styleHref = (document.currentScript as any).src.replace(/katex.js$/, 'katex.min.css'); +const styleHref = import.meta.url.replace(/katex.js$/, 'katex.min.css'); const link = document.createElement('link'); link.rel = 'stylesheet'; @@ -17,12 +13,8 @@ link.href = styleHref; document.head.append(link); -(function () { - const katex = require('@iktakahiro/markdown-it-katex'); - if (typeof extendMarkdownIt !== 'undefined') { +const katex = require('@iktakahiro/markdown-it-katex'); - extendMarkdownIt((md: markdownIt.MarkdownIt) => { - md.use(katex); - }); - } -}()); +export function extendMarkdownIt(md: markdownIt.MarkdownIt) { + return md.use(katex); +} diff --git a/extensions/notebook-markdown-extensions/notebook/tsconfig.json b/extensions/notebook-markdown-extensions/notebook/tsconfig.json index b1bede72c17..2d704f32ded 100644 --- a/extensions/notebook-markdown-extensions/notebook/tsconfig.json +++ b/extensions/notebook-markdown-extensions/notebook/tsconfig.json @@ -3,6 +3,7 @@ "compilerOptions": { "outDir": "./dist/", "jsx": "react", + "module": "es2020", "lib": [ "es2018", "DOM", diff --git a/extensions/notebook-markdown-extensions/package.json b/extensions/notebook-markdown-extensions/package.json index 61bc0bd8871..1622659825f 100644 --- a/extensions/notebook-markdown-extensions/package.json +++ b/extensions/notebook-markdown-extensions/package.json @@ -18,23 +18,25 @@ "virtualWorkspaces": false }, "contributes": { - "notebookMarkdownRenderer": [ + "notebookMarkupRenderers": [ { "id": "markdownItRenderer-katex", "displayName": "Markdown it katex renderer", - "entrypoint": "./notebook-out/katex.js" + "entrypoint": "./notebook-out/katex.js", + "dependsOn": "markdownItRenderer" }, { "id": "markdownItRenderer-emoji", "displayName": "Markdown it emoji renderer", - "entrypoint": "./notebook-out/emoji.js" + "entrypoint": "./notebook-out/emoji.js", + "dependsOn": "markdownItRenderer" } ] }, "scripts": { "compile": "npm run build-notebook", "watch": "npm run build-notebook", - "build-notebook": "npx webpack-cli --config webpack.notebook.js --mode production" + "build-notebook": "node ./esbuild" }, "devDependencies": { "@iktakahiro/markdown-it-katex": "https://github.com/mjbvz/markdown-it-katex.git", diff --git a/extensions/notebook-markdown-extensions/webpack.notebook.js b/extensions/notebook-markdown-extensions/webpack.notebook.js deleted file mode 100644 index de8fe9af0fc..00000000000 --- a/extensions/notebook-markdown-extensions/webpack.notebook.js +++ /dev/null @@ -1,50 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ -const path = require('path'); -const CopyPlugin = require('copy-webpack-plugin'); - -module.exports = { - context: path.resolve(__dirname), - mode: 'production', - performance: { - hints: false, - maxEntrypointSize: 512000, - maxAssetSize: 512000 - }, - entry: { - katex: './notebook/katex.ts', - emoji: './notebook/emoji.ts', - }, - module: { - rules: [ - { - test: /\.tsx?$/, - use: 'ts-loader', - }, - ], - }, - resolve: { - extensions: ['.tsx', '.ts', '.js'] - }, - output: { - filename: '[name].js', - path: path.resolve(__dirname, 'notebook-out') - }, - plugins: [ - // @ts-ignore - new CopyPlugin({ - patterns: [ - { - from: './node_modules/katex/dist/katex.min.css', - to: 'katex.min.css' - }, - { - from: './node_modules/katex/dist/fonts', - to: 'fonts/' - }, - ], - }), - ] -}; diff --git a/extensions/package.json b/extensions/package.json index 6a7c49ce500..645ea5c23cb 100644 --- a/extensions/package.json +++ b/extensions/package.json @@ -10,6 +10,7 @@ "postinstall": "node ./postinstall" }, "devDependencies": { + "esbuild": "^0.11.12", "vscode-grammar-updater": "^1.0.3" } } diff --git a/extensions/yarn.lock b/extensions/yarn.lock index 9108c34e550..067a5ebba64 100644 --- a/extensions/yarn.lock +++ b/extensions/yarn.lock @@ -14,6 +14,11 @@ cson-parser@^1.3.3: dependencies: coffee-script "^1.10.0" +esbuild@^0.11.12: + version "0.11.12" + resolved "https://registry.yarnpkg.com/esbuild/-/esbuild-0.11.12.tgz#8cbe15bcb44212624c3e77c896a835f74dc71c3c" + integrity sha512-c8cso/1RwVj+fbDvLtUgSG4ZJQ0y9Zdrl6Ot/GAjyy4pdMCHaFnDMts5gqFnWRPLajWtEnI+3hlET4R9fVoZng== + fast-plist@0.1.2: version "0.1.2" resolved "https://registry.yarnpkg.com/fast-plist/-/fast-plist-0.1.2.tgz#a45aff345196006d406ca6cdcd05f69051ef35b8" diff --git a/src/vs/workbench/contrib/notebook/browser/extensionPoint.ts b/src/vs/workbench/contrib/notebook/browser/extensionPoint.ts index f2dbad383f2..1c7fe9d2009 100644 --- a/src/vs/workbench/contrib/notebook/browser/extensionPoint.ts +++ b/src/vs/workbench/contrib/notebook/browser/extensionPoint.ts @@ -42,16 +42,20 @@ export interface INotebookRendererContribution { readonly [NotebookRendererContribution.optionalDependencies]: readonly string[]; } -enum NotebookMarkdownRendererContribution { +enum NotebookMarkupRendererContribution { id = 'id', displayName = 'displayName', entrypoint = 'entrypoint', + dependsOn = 'dependsOn', + mimeTypes = 'mimeTypes', } -export interface INotebookMarkdownRendererContribution { - readonly [NotebookMarkdownRendererContribution.id]?: string; - readonly [NotebookMarkdownRendererContribution.displayName]: string; - readonly [NotebookMarkdownRendererContribution.entrypoint]: string; +export interface INotebookMarkupRendererContribution { + readonly [NotebookMarkupRendererContribution.id]?: string; + readonly [NotebookMarkupRendererContribution.displayName]: string; + readonly [NotebookMarkupRendererContribution.entrypoint]: string; + readonly [NotebookMarkupRendererContribution.dependsOn]: string | undefined; + readonly [NotebookMarkupRendererContribution.mimeTypes]: string[] | undefined; } const notebookProviderContribution: IJSONSchema = { @@ -160,30 +164,39 @@ const notebookRendererContribution: IJSONSchema = { } } }; -const notebookMarkdownRendererContribution: IJSONSchema = { +const notebookMarkupRendererContribution: IJSONSchema = { description: nls.localize('contributes.notebook.markdownRenderer', 'Contributes a renderer for markdown cells in notebooks.'), type: 'array', defaultSnippets: [{ body: [{ id: '', displayName: '', entrypoint: '' }] }], items: { type: 'object', required: [ - NotebookMarkdownRendererContribution.id, - NotebookMarkdownRendererContribution.displayName, - NotebookMarkdownRendererContribution.entrypoint, + NotebookMarkupRendererContribution.id, + NotebookMarkupRendererContribution.displayName, + NotebookMarkupRendererContribution.entrypoint, ], properties: { - [NotebookMarkdownRendererContribution.id]: { + [NotebookMarkupRendererContribution.id]: { type: 'string', description: nls.localize('contributes.notebook.markdownRenderer.id', 'Unique identifier of the notebook markdown renderer.'), }, - [NotebookMarkdownRendererContribution.displayName]: { + [NotebookMarkupRendererContribution.displayName]: { type: 'string', description: nls.localize('contributes.notebook.markdownRenderer.displayName', 'Human readable name of the notebook markdown renderer.'), }, - [NotebookMarkdownRendererContribution.entrypoint]: { + [NotebookMarkupRendererContribution.entrypoint]: { type: 'string', description: nls.localize('contributes.notebook.markdownRenderer.entrypoint', 'File to load in the webview to render the extension.'), }, + [NotebookMarkupRendererContribution.mimeTypes]: { + type: 'array', + items: { type: 'string' }, + description: nls.localize('contributes.notebook.markdownRenderer.mimeTypes', 'The mime type that the renderer handles.'), + }, + [NotebookMarkupRendererContribution.dependsOn]: { + type: 'string', + description: nls.localize('contributes.notebook.markdownRenderer.dependsOn', 'If specified, this renderer augments another renderer instead of providing full rendering.'), + }, } } }; @@ -200,8 +213,8 @@ export const notebookRendererExtensionPoint = ExtensionsRegistry.registerExtensi jsonSchema: notebookRendererContribution }); -export const notebookMarkdownRendererExtensionPoint = ExtensionsRegistry.registerExtensionPoint( +export const notebookMarkupRendererExtensionPoint = ExtensionsRegistry.registerExtensionPoint( { - extensionPoint: 'notebookMarkdownRenderer', - jsonSchema: notebookMarkdownRendererContribution + extensionPoint: 'notebookMarkupRenderers', + jsonSchema: notebookMarkupRendererContribution }); diff --git a/src/vs/workbench/contrib/notebook/browser/notebookServiceImpl.ts b/src/vs/workbench/contrib/notebook/browser/notebookServiceImpl.ts index 588560d592a..2e3511f19f6 100644 --- a/src/vs/workbench/contrib/notebook/browser/notebookServiceImpl.ts +++ b/src/vs/workbench/contrib/notebook/browser/notebookServiceImpl.ts @@ -20,12 +20,12 @@ import { IInstantiationService } from 'vs/platform/instantiation/common/instanti import { IStorageService, StorageScope, StorageTarget } from 'vs/platform/storage/common/storage'; import { NotebookExtensionDescription } from 'vs/workbench/api/common/extHost.protocol'; import { Memento } from 'vs/workbench/common/memento'; -import { INotebookEditorContribution, notebookMarkdownRendererExtensionPoint, notebookProviderExtensionPoint, notebookRendererExtensionPoint } from 'vs/workbench/contrib/notebook/browser/extensionPoint'; +import { INotebookEditorContribution, notebookMarkupRendererExtensionPoint, notebookProviderExtensionPoint, notebookRendererExtensionPoint } from 'vs/workbench/contrib/notebook/browser/extensionPoint'; import { NotebookEditorOptions, updateEditorTopPadding } from 'vs/workbench/contrib/notebook/browser/notebookBrowser'; import { NotebookCellTextModel } from 'vs/workbench/contrib/notebook/common/model/notebookCellTextModel'; import { NotebookTextModel } from 'vs/workbench/contrib/notebook/common/model/notebookTextModel'; -import { ACCESSIBLE_NOTEBOOK_DISPLAY_ORDER, BUILTIN_RENDERER_ID, CellUri, DisplayOrderKey, INotebookExclusiveDocumentFilter, INotebookMarkdownRendererInfo, INotebookRendererInfo, INotebookTextModel, IOrderedMimeType, IOutputDto, mimeTypeIsAlwaysSecure, mimeTypeSupportedByCore, NotebookDataDto, NotebookEditorPriority, NotebookRendererMatch, NotebookTextDiffEditorPreview, RENDERER_NOT_AVAILABLE, sortMimeTypes, TransientOptions } from 'vs/workbench/contrib/notebook/common/notebookCommon'; -import { NotebookMarkdownRendererInfo } from 'vs/workbench/contrib/notebook/common/notebookMarkdownRenderer'; +import { ACCESSIBLE_NOTEBOOK_DISPLAY_ORDER, BUILTIN_RENDERER_ID, CellUri, DisplayOrderKey, INotebookExclusiveDocumentFilter, INotebookMarkupRendererInfo, INotebookRendererInfo, INotebookTextModel, IOrderedMimeType, IOutputDto, mimeTypeIsAlwaysSecure, mimeTypeSupportedByCore, NotebookDataDto, NotebookEditorPriority, NotebookRendererMatch, NotebookTextDiffEditorPreview, RENDERER_NOT_AVAILABLE, sortMimeTypes, TransientOptions } from 'vs/workbench/contrib/notebook/common/notebookCommon'; +import { NotebookMarkupRendererInfo as NotebookMarkupRendererInfo } from 'vs/workbench/contrib/notebook/common/notebookMarkdownRenderer'; import { NotebookOutputRendererInfo } from 'vs/workbench/contrib/notebook/common/notebookOutputRenderer'; import { NotebookEditorDescriptor, NotebookProviderInfo } from 'vs/workbench/contrib/notebook/common/notebookProvider'; import { ComplexNotebookProviderInfo, INotebookContentProvider, INotebookSerializer, INotebookService, SimpleNotebookProviderInfo } from 'vs/workbench/contrib/notebook/common/notebookService'; @@ -272,7 +272,7 @@ export class NotebookService extends Disposable implements INotebookService, IEd private readonly _notebookProviders = new Map(); private readonly _notebookProviderInfoStore: NotebookProviderInfoStore; private readonly _notebookRenderersInfoStore = this._instantiationService.createInstance(NotebookOutputRendererInfoStore); - private readonly _markdownRenderersInfos = new Set(); + private readonly _markdownRenderersInfos = new Set(); private readonly _models = new ResourceMap(); private readonly _onDidCreateNotebookDocument = this._register(new Emitter()); @@ -333,8 +333,7 @@ export class NotebookService extends Disposable implements INotebookService, IEd } } }); - - notebookMarkdownRendererExtensionPoint.setHandler((renderers) => { + notebookMarkupRendererExtensionPoint.setHandler((renderers) => { this._markdownRenderersInfos.clear(); for (const extension of renderers) { @@ -355,11 +354,13 @@ export class NotebookService extends Disposable implements INotebookService, IEd continue; } - this._markdownRenderersInfos.add(new NotebookMarkdownRendererInfo({ + this._markdownRenderersInfos.add(new NotebookMarkupRendererInfo({ id, extension: extension.description, entrypoint: notebookContribution.entrypoint, displayName: notebookContribution.displayName, + mimeTypes: notebookContribution.mimeTypes, + dependsOn: notebookContribution.dependsOn, })); } } @@ -518,7 +519,7 @@ export class NotebookService extends Disposable implements INotebookService, IEd this._notebookRenderersInfoStore.setPreferred(mimeType, rendererId); } - getMarkdownRendererInfo(): INotebookMarkdownRendererInfo[] { + getMarkupRendererInfo(): INotebookMarkupRendererInfo[] { return Array.from(this._markdownRenderersInfos); } diff --git a/src/vs/workbench/contrib/notebook/browser/view/renderers/backLayerWebView.ts b/src/vs/workbench/contrib/notebook/browser/view/renderers/backLayerWebView.ts index a8884f718ad..c9dc0838849 100644 --- a/src/vs/workbench/contrib/notebook/browser/view/renderers/backLayerWebView.ts +++ b/src/vs/workbench/contrib/notebook/browser/view/renderers/backLayerWebView.ts @@ -449,7 +449,7 @@ export class BackLayerWebView extends Disposable { this.element.style.position = 'absolute'; } private generateContent(coreDependencies: string, baseUrl: string) { - const markdownRenderersSrc = this.getMarkdownRendererScripts(); + const markupRenderer = this.getMarkdownRenderer(); const outputWidth = `calc(100% - ${this.options.leftMargin + this.options.rightMargin + this.options.runGutter}px)`; const outputMarginLeft = `${this.options.leftMargin + this.options.runGutter}px`; return html` @@ -718,31 +718,42 @@ export class BackLayerWebView extends Disposable { ${coreDependencies}
- - ${markdownRenderersSrc} `; } - private getMarkdownRendererScripts() { - const markdownRenderers = this.notebookService.getMarkdownRendererInfo(); + private getMarkdownRenderer(): Array<{ entrypoint: string, dependencies: Array<{ entrypoint: string }> }> { + const allRenderers = this.notebookService.getMarkupRendererInfo(); - return markdownRenderers - .sort((a, b) => { - // prefer built-in extension - if (a.extensionIsBuiltin) { - return b.extensionIsBuiltin ? 0 : -1; + const topLevelMarkdownRenderers = allRenderers + .filter(renderer => !renderer.dependsOn) + .filter(renderer => renderer.mimeTypes?.includes('text/markdown')); + + const subRenderers = new Map>(); + for (const renderer of allRenderers) { + if (renderer.dependsOn) { + if (!subRenderers.has(renderer.dependsOn)) { + subRenderers.set(renderer.dependsOn, []); } - return b.extensionIsBuiltin ? 1 : -1; - }) - .map(renderer => { - return asWebviewUri(this.environmentService, this.id, renderer.entrypoint); - }) - .map(src => ``) - .join('\n'); + const entryPoint = asWebviewUri(this.environmentService, this.id, renderer.entrypoint); + subRenderers.get(renderer.dependsOn)!.push({ entrypoint: entryPoint.toString(true) }); + } + } + + return topLevelMarkdownRenderers.map((renderer) => { + const src = asWebviewUri(this.environmentService, this.id, renderer.entrypoint); + return { + entrypoint: src.toString(), + dependencies: subRenderers.get(renderer.id) || [], + }; + }); } postRendererMessage(rendererId: string, message: any) { @@ -1139,7 +1150,7 @@ var requirejs = (function() { this.localResourceRootsCache = [ ...this.notebookService.getNotebookProviderResourceRoots(), - ...this.notebookService.getMarkdownRendererInfo().map(x => dirname(x.entrypoint)), + ...this.notebookService.getMarkupRendererInfo().map(x => dirname(x.entrypoint)), ...workspaceFolders, rootPath, ]; diff --git a/src/vs/workbench/contrib/notebook/browser/view/renderers/webviewPreloads.ts b/src/vs/workbench/contrib/notebook/browser/view/renderers/webviewPreloads.ts index 6bbced710ce..193e9f6f84b 100644 --- a/src/vs/workbench/contrib/notebook/browser/view/renderers/webviewPreloads.ts +++ b/src/vs/workbench/contrib/notebook/browser/view/renderers/webviewPreloads.ts @@ -29,6 +29,10 @@ declare class ResizeObserver { declare const __outputNodePadding__: number; declare const __outputNodeLeftPadding__: number; +declare const markdownRenderer: { + renderMarkup: (context: { element: HTMLElement, content: string }) => void, +}; + type Listener = { fn: (evt: T) => void; thisArg: unknown; }; interface EmitterLike { @@ -409,18 +413,12 @@ function webviewPreloads() { metadata: unknown; } - interface ICreateMarkdownInfo { - readonly content: string; - readonly element: HTMLElement; - } - interface IDestroyCellInfo { outputId: string; } const onWillDestroyOutput = createEmitter<[string | undefined /* namespace */, IDestroyCellInfo | undefined /* cell uri */]>(); const onDidCreateOutput = createEmitter<[string | undefined /* namespace */, ICreateCellInfo]>(); - const onDidCreateMarkdown = createEmitter<[string | undefined /* namespace */, ICreateMarkdownInfo]>(); const onDidReceiveMessage = createEmitter<[string, unknown]>(); const matchesNs = (namespace: string, query: string | undefined) => namespace === '*' || query === namespace || query === 'undefined'; @@ -447,7 +445,6 @@ function webviewPreloads() { onDidReceiveMessage: mapEmitter(onDidReceiveMessage, ([ns, data]) => ns === namespace ? data : dontEmit), onWillDestroyOutput: mapEmitter(onWillDestroyOutput, ([ns, data]) => matchesNs(namespace, ns) ? data : dontEmit), onDidCreateOutput: mapEmitter(onDidCreateOutput, ([ns, data]) => matchesNs(namespace, ns) ? data : dontEmit), - onDidCreateMarkdown: mapEmitter(onDidCreateMarkdown, ([ns, data]) => data), }; }; @@ -805,6 +802,8 @@ function webviewPreloads() { } }); + void markdownRenderer; + vscode.postMessage({ __vscode_notebook_message: true, type: 'initialized' @@ -913,10 +912,10 @@ function webviewPreloads() { previewContainerNode.innerText = ''; } else { previewContainerNode.classList.remove('emptyMarkdownCell'); - onDidCreateMarkdown.fire([undefined /* data.apiNamespace */, { + markdownRenderer.renderMarkup({ element: previewNode, content: content - }]); + }); if (!hasPostedRenderedMathTelemetry) { const hasRenderedMath = previewNode.querySelector('.katex'); @@ -1015,11 +1014,24 @@ function webviewPreloads() { }(); } -export function preloadsScriptStr(values: { +export function preloadsScriptStr(styleValues: { outputNodePadding: number; outputNodeLeftPadding: number; +}, markdownRenderer: { + entrypoint: string, + dependencies: Array<{ entrypoint: string }>, }) { - return `(${webviewPreloads})()` - .replace(/__outputNodePadding__/g, `${values.outputNodePadding}`) - .replace(/__outputNodeLeftPadding__/g, `${values.outputNodeLeftPadding}`); + const markdownCtx = { + dependencies: markdownRenderer.dependencies, + }; + + return `${webviewPreloads}` + .slice(0, -1) + .replace(/function webviewPreloads\(\) \{/g, '') + .replace(/__outputNodePadding__/g, `${styleValues.outputNodePadding}`) + .replace(/__outputNodeLeftPadding__/g, `${styleValues.outputNodeLeftPadding}`) + .replace(/void markdownRenderer;/g, ` + import * as markdownRendererModule from "${markdownRenderer.entrypoint}"; + const markdownRenderer = await markdownRendererModule.activate(JSON.parse(decodeURIComponent("${encodeURIComponent(JSON.stringify(markdownCtx))}"))) + `); } diff --git a/src/vs/workbench/contrib/notebook/common/notebookCommon.ts b/src/vs/workbench/contrib/notebook/common/notebookCommon.ts index ae910076cd4..6c7fd3d08c8 100644 --- a/src/vs/workbench/contrib/notebook/common/notebookCommon.ts +++ b/src/vs/workbench/contrib/notebook/common/notebookCommon.ts @@ -141,11 +141,14 @@ export interface INotebookRendererInfo { matches(mimeType: string, kernelProvides: ReadonlyArray): NotebookRendererMatch; } -export interface INotebookMarkdownRendererInfo { +export interface INotebookMarkupRendererInfo { + readonly id: string; readonly entrypoint: URI; readonly extensionLocation: URI; readonly extensionId: ExtensionIdentifier; readonly extensionIsBuiltin: boolean; + readonly dependsOn: string | undefined; + readonly mimeTypes: readonly string[] | undefined; } export interface NotebookCellOutputMetadata { diff --git a/src/vs/workbench/contrib/notebook/common/notebookMarkdownRenderer.ts b/src/vs/workbench/contrib/notebook/common/notebookMarkdownRenderer.ts index 9c3313461ab..407fb7731cb 100644 --- a/src/vs/workbench/contrib/notebook/common/notebookMarkdownRenderer.ts +++ b/src/vs/workbench/contrib/notebook/common/notebookMarkdownRenderer.ts @@ -6,9 +6,9 @@ import { joinPath } from 'vs/base/common/resources'; import { URI } from 'vs/base/common/uri'; import { ExtensionIdentifier, IExtensionDescription } from 'vs/platform/extensions/common/extensions'; -import { INotebookMarkdownRendererInfo } from 'vs/workbench/contrib/notebook/common/notebookCommon'; +import { INotebookMarkupRendererInfo } from 'vs/workbench/contrib/notebook/common/notebookCommon'; -export class NotebookMarkdownRendererInfo implements INotebookMarkdownRendererInfo { +export class NotebookMarkupRendererInfo implements INotebookMarkupRendererInfo { readonly id: string; readonly entrypoint: URI; @@ -16,12 +16,16 @@ export class NotebookMarkdownRendererInfo implements INotebookMarkdownRendererIn readonly extensionLocation: URI; readonly extensionId: ExtensionIdentifier; readonly extensionIsBuiltin: boolean; + readonly dependsOn: string | undefined; + readonly mimeTypes: readonly string[] | undefined; constructor(descriptor: { readonly id: string; readonly displayName: string; readonly entrypoint: string; readonly extension: IExtensionDescription; + readonly mimeTypes: readonly string[] | undefined, + readonly dependsOn: string | undefined, }) { this.id = descriptor.id; this.extensionId = descriptor.extension.identifier; @@ -29,5 +33,7 @@ export class NotebookMarkdownRendererInfo implements INotebookMarkdownRendererIn this.entrypoint = joinPath(this.extensionLocation, descriptor.entrypoint); this.displayName = descriptor.displayName; this.extensionIsBuiltin = descriptor.extension.isBuiltin; + this.dependsOn = descriptor.dependsOn; + this.mimeTypes = descriptor.mimeTypes; } } diff --git a/src/vs/workbench/contrib/notebook/common/notebookService.ts b/src/vs/workbench/contrib/notebook/common/notebookService.ts index 29a6c6ea71b..1b09f73800d 100644 --- a/src/vs/workbench/contrib/notebook/common/notebookService.ts +++ b/src/vs/workbench/contrib/notebook/common/notebookService.ts @@ -8,7 +8,7 @@ import { URI } from 'vs/base/common/uri'; import { NotebookProviderInfo } from 'vs/workbench/contrib/notebook/common/notebookProvider'; import { NotebookExtensionDescription } from 'vs/workbench/api/common/extHost.protocol'; import { Event } from 'vs/base/common/event'; -import { INotebookRendererInfo, NotebookDataDto, TransientOptions, INotebookExclusiveDocumentFilter, IOrderedMimeType, IOutputDto, INotebookMarkdownRendererInfo } from 'vs/workbench/contrib/notebook/common/notebookCommon'; +import { INotebookRendererInfo, NotebookDataDto, TransientOptions, INotebookExclusiveDocumentFilter, IOrderedMimeType, IOutputDto, INotebookMarkupRendererInfo } from 'vs/workbench/contrib/notebook/common/notebookCommon'; import { NotebookTextModel } from 'vs/workbench/contrib/notebook/common/model/notebookTextModel'; import { CancellationToken } from 'vs/base/common/cancellation'; import { NotebookCellTextModel } from 'vs/workbench/contrib/notebook/common/model/notebookCellTextModel'; @@ -71,7 +71,7 @@ export interface INotebookService { getMimeTypeInfo(textModel: NotebookTextModel, kernelProvides: readonly string[] | undefined, output: IOutputDto): readonly IOrderedMimeType[]; getRendererInfo(id: string): INotebookRendererInfo | undefined; - getMarkdownRendererInfo(): INotebookMarkdownRendererInfo[]; + getMarkupRendererInfo(): INotebookMarkupRendererInfo[]; /** Updates the preferred renderer for the given mimetype in the workspace. */ updateMimePreferredRenderer(mimeType: string, rendererId: string): void;