Repository: marp-team/marp-vscode
Pull request #348 (2022-04-22 17:35:25)

Prevent accidental conflict between IDs by Markdown headings and internal IDs for preview
Marp preview had used `<div id="marp-vscode">` and `<style id="marp-vscode-style">` to show the preview.

The default Markdown preview by VS Code will automatically generate ID from the heading content, so this line (generates `<h1 id="marp-vscode">`) can break Markdown preview easily:

```markdown
# marp-vscode
```

I've updated to use `#__marp-vscode` and `#__marp-vscode-style` instead. `__` prefix is mostly safe in preview because it will not be generated as heading slug.



Patch:
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 2ce366d..c3c8efc 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -5,6 +5,7 @@
 ### Fixed
 
 - Prevent accidental conflict with globally polluted variables by VS Code ([#345](https://github.com/marp-team/marp-vscode/issues/345), [#347](https://github.com/marp-team/marp-vscode/pull/347))
+- Prevent accidental conflict between IDs by Markdown headings and internal IDs for preview ([#348](https://github.com/marp-team/marp-vscode/pull/348))
 
 ## v1.5.1 - 2022-04-17
 
diff --git a/marp-vscode.css b/marp-vscode.css
index d833d8c..a80296c 100644
--- a/marp-vscode.css
+++ b/marp-vscode.css
@@ -1,4 +1,4 @@
-#marp-vscode {
+#__marp-vscode {
   all: initial;
 }
 
@@ -29,11 +29,11 @@ body.marp-vscode blockquote {
     overflow-y: scroll;
   }
 
-  #marp-vscode [data-marp-vscode-slide-wrapper] {
+  #__marp-vscode [data-marp-vscode-slide-wrapper] {
     margin: 20px;
   }
 
-  #marp-vscode svg[data-marpit-svg] {
+  #__marp-vscode svg[data-marpit-svg] {
     box-shadow: 0 5px 10px rgb(0 0 0 / 25%);
     display: block;
     margin: 0;
diff --git a/package.json b/package.json
index 8421eba..c3b6050 100644
--- a/package.json
+++ b/package.json
@@ -221,6 +221,7 @@
       "stylelint-config-prettier"
     ],
     "rules": {
+      "selector-id-pattern": null,
       "selector-type-no-unknown": [
         true,
         {
diff --git a/src/extension.ts b/src/extension.ts
index f2d1945..79dac06 100644
--- a/src/extension.ts
+++ b/src/extension.ts
@@ -177,7 +177,7 @@ export function extendMarkdownIt(md: any) {
       const style = marp.renderStyle(marp.lastGlobalDirectives.theme)
       const html = markdown.renderer.render(tokens, markdown.options, env)
 
-      return `<style id="marp-vscode-style">${style}</style>${html}`
+      return `<style id="__marp-vscode-style">${style}</style>${html}`
     }
 
     return render.call(renderer, tokens, options, env)
diff --git a/src/option.ts b/src/option.ts
index 85a83f3..73c9648 100644
--- a/src/option.ts
+++ b/src/option.ts
@@ -43,7 +43,7 @@ export const marpCoreOptionForPreview = (
 ): MarpOptions => {
   if (!cachedPreviewOption) {
     cachedPreviewOption = {
-      container: { tag: 'div', id: 'marp-vscode' },
+      container: { tag: 'div', id: '__marp-vscode' },
       slideContainer: { tag: 'div', 'data-marp-vscode-slide-wrapper': '' },
       html: enableHtml() || undefined,
       inlineSVG: {
diff --git a/src/preview.ts b/src/preview.ts
index 93cc516..cd2c353 100644
--- a/src/preview.ts
+++ b/src/preview.ts
@@ -6,7 +6,7 @@ export default function preview() {
 
   // Detect update of DOM
   const updateCallback = () => {
-    const marpVscode = document.getElementById('marp-vscode')
+    const marpVscode = document.getElementById('__marp-vscode')
     const newMarpState = !!marpVscode
 
     if (marpState !== newMarpState) {
@@ -38,20 +38,20 @@ export default function preview() {
 
 const removeStyles = () => {
   const styles = document.querySelectorAll<HTMLStyleElement>(
-    'style:not(#marp-vscode-style):not(#_defaultStyles):not([data-marp-vscode-body])'
+    'style:not(#__marp-vscode-style):not(#_defaultStyles):not([data-marp-vscode-body])'
   )
   const links = document.querySelectorAll<HTMLLinkElement>(
     'link[rel="stylesheet"][href]:not([href*="marp-vscode"])'
   )
 
   styles.forEach((elm) => {
-    if (elm.closest('#marp-vscode')) return
+    if (elm.closest('#__marp-vscode')) return
     elm.dataset.marpVscodeBody = elm.textContent ?? ''
     elm.textContent = ''
   })
 
   links.forEach((elm) => {
-    if (elm.closest('#marp-vscode')) return
+    if (elm.closest('#__marp-vscode')) return
     const { href } = elm
     elm.dataset.marpVscodeHref = href
     elm.removeAttribute('href')


Test patch:
diff --git a/src/extension.test.ts b/src/extension.test.ts
index a647240..b77941e 100644
--- a/src/extension.test.ts
+++ b/src/extension.test.ts
@@ -79,8 +79,8 @@ describe('#extendMarkdownIt', () => {
           .extendMarkdownIt(new markdownIt())
           .render(markdown)
 
-        expect(html).not.toContain('<div id="marp-vscode">')
-        expect(html).not.toContain('<style id="marp-vscode-style">')
+        expect(html).not.toContain('<div id="__marp-vscode">')
+        expect(html).not.toContain('<style id="__marp-vscode-style">')
         expect(html).not.toContain('svg')
         expect(html).not.toContain('img')
       }
@@ -91,8 +91,8 @@ describe('#extendMarkdownIt', () => {
         .extendMarkdownIt(new markdownIt())
         .render(marpMd(baseMd))
 
-      expect(html).toContain('<div id="marp-vscode">')
-      expect(html).toContain('<style id="marp-vscode-style">')
+      expect(html).toContain('<div id="__marp-vscode">')
+      expect(html).toContain('<style id="__marp-vscode-style">')
       expect(html).toContain('svg')
       expect(html).toContain('img')
     })
diff --git a/src/preview.test.ts b/src/preview.test.ts
index 9217843..f012606 100644
--- a/src/preview.test.ts
+++ b/src/preview.test.ts
@@ -18,7 +18,7 @@ describe('Preview HTML', () => {
   })
 
   it('calls only browser context JS when HTML has Marp slide', () => {
-    document.body.innerHTML = '<div id="marp-vscode"></div>'
+    document.body.innerHTML = '<div id="__marp-vscode"></div>'
 
     preview()
     expect(document.body.classList.contains('marp-vscode')).toBe(true)
@@ -34,8 +34,8 @@ describe('Preview HTML', () => {
 
     document.body.innerHTML = `
       <style id="another-plugin">a {}</style>
-      <style id="marp-vscode-style">a {}</style>
-      <div id="marp-vscode">
+      <style id="__marp-vscode-style">a {}</style>
+      <div id="__marp-vscode">
         <style id="style-in-markdown-content">a {}</style>
         <link rel="stylesheet" href="vscode-resource:/style/in/markdown/content" />
       </div>
@@ -53,7 +53,7 @@ describe('Preview HTML', () => {
       document.querySelector<HTMLLinkElement>('link:not([href])')?.dataset
         .marpVscodeHref
     ).toContain('/other-extension/defined.css')
-    expect(document.getElementById('marp-vscode-style')).toBeTruthy()
+    expect(document.getElementById('__marp-vscode-style')).toBeTruthy()
     expect(document.getElementById('_defaultStyles')).toBeTruthy()
   })
 
@@ -70,7 +70,7 @@ describe('Preview HTML', () => {
         expect(document.body.classList.contains('marp-vscode')).toBe(false)
         expect(observer).not.toHaveBeenCalled()
 
-        document.body.innerHTML = '<div id="marp-vscode"></div>'
+        document.body.innerHTML = '<div id="__marp-vscode"></div>'
         emitUpdateEvent()
         expect(document.body.classList.contains('marp-vscode')).toBe(true)
         expect(observer).toHaveBeenCalled()
@@ -95,7 +95,7 @@ describe('Preview HTML', () => {
         expect(link.href).toBeTruthy()
         expect(style.textContent).toBeTruthy()
 
-        main.innerHTML = '<div id="marp-vscode"></div>'
+        main.innerHTML = '<div id="__marp-vscode"></div>'
         emitUpdateEvent()
         expect(link.href).toBeFalsy()
         expect(style.textContent).toBeFalsy()