Merge pull request #316329 from microsoft/dev/mjbvz/profitable-marmot

Let markdown preview scripts mark themselves as modules
This commit is contained in:
Matt Bierner
2026-05-13 16:09:58 -07:00
committed by GitHub
4 changed files with 88 additions and 10 deletions

View File

@@ -849,7 +849,10 @@
"./media/highlight.css"
],
"markdown.previewScripts": [
"./media/index.js"
{
"path": "./media/index.js",
"type": "module"
}
],
"customEditors": [
{

View File

@@ -17,8 +17,31 @@
"type": "array",
"description": "Contributed scripts that are executed in the Markdown preview",
"items": {
"type": "string",
"description": "Extension relative path to a JavaScript file"
"oneOf": [
{
"type": "string",
"description": "Extension relative path to a JavaScript file"
},
{
"type": "object",
"required": [
"path"
],
"properties": {
"path": {
"type": "string",
"description": "Extension relative path to a JavaScript file"
},
"type": {
"type": "string",
"enum": [
"module"
],
"description": "The script type to use when loading the contributed script"
}
}
}
]
}
},
"markdown.markdownItPlugins": {

View File

@@ -23,8 +23,13 @@ function* resolveExtensionResources(extension: vscode.Extension<any>, resourcePa
}
}
export interface MarkdownPreviewScript {
readonly resource: vscode.Uri;
readonly type?: 'module';
}
export interface MarkdownContributions {
readonly previewScripts: readonly vscode.Uri[];
readonly previewScripts: readonly MarkdownPreviewScript[];
readonly previewStyles: readonly vscode.Uri[];
readonly previewResourceRoots: readonly vscode.Uri[];
readonly markdownItPlugins: ReadonlyMap<string, Thenable<(md: any) => any>>;
@@ -51,8 +56,12 @@ export namespace MarkdownContributions {
return a.toString() === b.toString();
}
function previewScriptEqual(a: MarkdownPreviewScript, b: MarkdownPreviewScript): boolean {
return uriEqual(a.resource, b.resource) && a.type === b.type;
}
export function equal(a: MarkdownContributions, b: MarkdownContributions): boolean {
return arrays.equals(a.previewScripts, b.previewScripts, uriEqual)
return arrays.equals(a.previewScripts, b.previewScripts, previewScriptEqual)
&& arrays.equals(a.previewStyles, b.previewStyles, uriEqual)
&& arrays.equals(a.previewResourceRoots, b.previewResourceRoots, uriEqual)
&& arrays.equals(Array.from(a.markdownItPlugins.keys()), Array.from(b.markdownItPlugins.keys()));
@@ -96,8 +105,8 @@ export namespace MarkdownContributions {
function getContributedScripts(
contributes: any,
extension: vscode.Extension<any>
) {
return resolveExtensionResources(extension, contributes['markdown.previewScripts']);
): Iterable<MarkdownPreviewScript> {
return resolvePreviewScripts(extension, contributes['markdown.previewScripts']);
}
function getContributedStyles(
@@ -106,6 +115,48 @@ export namespace MarkdownContributions {
) {
return resolveExtensionResources(extension, contributes['markdown.previewStyles']);
}
function* resolvePreviewScripts(extension: vscode.Extension<any>, scripts: unknown): Iterable<MarkdownPreviewScript> {
if (!Array.isArray(scripts)) {
return;
}
for (const script of scripts) {
const contribution = getPreviewScriptContribution(script);
if (!contribution) {
continue;
}
try {
yield {
resource: resolveExtensionResource(extension, contribution.path),
type: contribution.type,
};
} catch {
// noop
}
}
}
function getPreviewScriptContribution(script: unknown): { path: string; type?: MarkdownPreviewScript['type'] } | undefined {
if (typeof script === 'string') {
return { path: script };
}
if (!script || typeof script !== 'object') {
return undefined;
}
const contribution = script as Record<string, unknown>;
if (typeof contribution.path !== 'string') {
return undefined;
}
return {
path: contribution.path,
type: contribution.type === 'module' ? contribution.type : undefined,
};
}
}
export interface MarkdownContributionProvider {

View File

@@ -244,9 +244,10 @@ export class MdDocumentRenderer {
#getScripts(resourceProvider: WebviewResourceProvider, nonce: string): string {
const out: string[] = [];
for (const resource of this.#contributionProvider.contributions.previewScripts) {
out.push(`<script async
src="${escapeAttribute(resourceProvider.asWebviewUri(resource))}"
for (const script of this.#contributionProvider.contributions.previewScripts) {
const type = script.type ? ` type="${escapeAttribute(script.type)}"` : '';
out.push(`<script async${type}
src="${escapeAttribute(resourceProvider.asWebviewUri(script.resource))}"
nonce="${nonce}"
charset="UTF-8"></script>`);
}