Skip to content

Commit e02d0b8

Browse files
committed
feat: abilities to compile, render html blocks as vue inline components
Signed-off-by: Neko Ayaka <[email protected]>
1 parent 7cf337c commit e02d0b8

File tree

5 files changed

+106
-5
lines changed

5 files changed

+106
-5
lines changed

cspell.config.yaml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ words:
66
- antfu
77
- codemirror
88
- collab
9+
- importmap
910
- lezer
1011
- nolebase
1112
- Nólëbase

package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@
3535
"eslint": "^8.57.0",
3636
"hast-util-to-html": "^9.0.1",
3737
"obsidian": "^1.5.7",
38+
"ofetch": "^1.3.4",
3839
"rehype-raw": "^7.0.0",
3940
"rehype-stringify": "^10.0.0",
4041
"remark-parse": "^11.0.0",

pnpm-lock.yaml

Lines changed: 22 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/import.ts

Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
import { $fetch } from 'ofetch'
2+
3+
// https://github.com/unocss/unocss/blob/6d94efc56b0c966f25f46d8988b3fd30ebc189aa/packages/shared-docs/src/config.ts#L5
4+
const AsyncFunction = Object.getPrototypeOf(async () => { }).constructor
5+
6+
// https://github.com/unocss/unocss/blob/6d94efc56b0c966f25f46d8988b3fd30ebc189aa/packages/shared-docs/src/config.ts#L7-L9
7+
const CDN_BASE = 'https://esm.sh/'
8+
const modulesCache = new Map<string, Promise<unknown> | unknown>()
9+
10+
// https://github.com/unocss/unocss/blob/6d94efc56b0c966f25f46d8988b3fd30ebc189aa/packages/shared-docs/src/config.ts#L26
11+
// eslint-disable-next-line no-new-func
12+
const nativeImport = new Function('a', 'return import(a);')
13+
14+
// https://github.com/unocss/unocss/blob/6d94efc56b0c966f25f46d8988b3fd30ebc189aa/packages/shared-docs/src/config.ts#L31-L33
15+
async function fetchAndImportAnyModuleWithCDNCapabilities(name: string) {
16+
if (name.endsWith('.json')) {
17+
const response = await $fetch(CDN_BASE + name, { responseType: 'json' })
18+
return { default: response }
19+
}
20+
21+
return nativeImport(CDN_BASE + name)
22+
}
23+
24+
// https://github.com/unocss/unocss/blob/6d94efc56b0c966f25f46d8988b3fd30ebc189aa/packages/shared-docs/src/config.ts#L27-L37
25+
// bypass vite interop
26+
async function dynamicImportAnyModule(name: string): Promise<any> {
27+
if (modulesCache.has(name))
28+
return modulesCache.get(name)
29+
30+
try {
31+
const module = await fetchAndImportAnyModuleWithCDNCapabilities(name)
32+
modulesCache.set(name, module)
33+
}
34+
catch (error) {
35+
console.error(`Failed to import module ${name} from CDN`, error)
36+
}
37+
}
38+
39+
// https://github.com/unocss/unocss/blob/main/packages/shared-docs/src/config.ts
40+
const importUnocssRegex = /import\s(.*?)\sfrom\s*(['"])unocss\2/g
41+
const importObjectRegex = /import\s*\{([\s\S]*?)\}\s*from\s*(['"])([\w@/-]+)\2/g
42+
const importDefaultRegex = /import\s(.*?)\sfrom\s*(['"])([\w@/-]+)\2/g
43+
const exportDefaultRegex = /export default /
44+
const importRegex = /\bimport\s*\(/g
45+
46+
// New regex to handle `as` to `:` transformation
47+
const importAsRegex = /(\w+)\s+as\s+(\w+)/g
48+
49+
// https://github.com/unocss/unocss/blob/main/packages/shared-docs/src/config.ts
50+
export async function evaluateAnyModule<T = any>(configCode: string): Promise<T | undefined> {
51+
const transformedCode = configCode
52+
.replace(importUnocssRegex, 'const $1 = await __import("unocss");')
53+
.replace(importObjectRegex, (match, p1, p2, p3) => {
54+
// Replace `as` with `:` within the destructuring assignment
55+
const transformedP1 = p1.replace(importAsRegex, '$1: $2')
56+
return `const {${transformedP1}} = await __import("${p3}");`
57+
})
58+
.replace(importDefaultRegex, 'const $1 = (await __import("$3")).default;')
59+
.replace(exportDefaultRegex, 'return ')
60+
.replace(importRegex, '__import(')
61+
62+
const wrappedDynamicImport = new AsyncFunction('__import', transformedCode)
63+
return await wrappedDynamicImport(dynamicImportAnyModule)
64+
}

src/main.ts

Lines changed: 18 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import { Plugin } from 'obsidian'
22
import type { EditorView, PluginValue, ViewUpdate } from '@codemirror/view'
33
import { ViewPlugin } from '@codemirror/view'
44
import { type App, createApp, defineComponent, h } from 'vue'
5+
import * as Vue from 'vue'
56
import { compileTemplate } from '@vue/compiler-sfc'
67

78
import { unified } from 'unified'
@@ -11,6 +12,8 @@ import RehypeRaw from 'rehype-raw'
1112
import { remove } from 'unist-util-remove'
1213
import { toHtml } from 'hast-util-to-html'
1314

15+
import { evaluateAnyModule } from './import'
16+
1417
function sleep(ms: number) {
1518
return new Promise(resolve => setTimeout(resolve, ms))
1619
}
@@ -40,6 +43,8 @@ class VueViewPlugin implements PluginValue {
4043

4144
async init() {
4245
await this.waitForViewDOM()
46+
const anyGlobalThis = globalThis as any
47+
anyGlobalThis.Vue = Vue
4348

4449
// eslint-disable-next-line no-console
4550
console.log('view ready', this.view.dom)
@@ -50,6 +55,8 @@ class VueViewPlugin implements PluginValue {
5055
vueDom.classList.add('.obsidian-plugin-vue')
5156
}
5257

58+
this.vueInstance?.unmount()
59+
5360
const parsedMarkdownAst = await unified()
5461
.use(RemarkParse)
5562
.use(() => tree => remove(tree, 'heading'))
@@ -63,6 +70,8 @@ class VueViewPlugin implements PluginValue {
6370
.run(parsedMarkdownAst)
6471

6572
let index = 0
73+
const renderFunctions: Array<() => void> = []
74+
6675
for (const node of transformedHast.children) {
6776
index++
6877

@@ -73,22 +82,26 @@ class VueViewPlugin implements PluginValue {
7382
source: componentTemplateStr,
7483
filename: `some-${index}`,
7584
id: index.toString(),
85+
compilerOptions: {
86+
mode: 'function',
87+
},
7688
})
7789
if (errors.length) {
7890
console.error(errors)
7991
throw new Error('Failed to compile template')
8092
}
8193

82-
// eslint-disable-next-line no-console
83-
console.log(code)
84-
}
94+
const res = await evaluateAnyModule<() => void>(code)
95+
if (!res)
96+
continue
8597

86-
this.vueInstance?.unmount()
98+
renderFunctions.push(res)
99+
}
87100

88101
const comp = defineComponent({
89102
setup() {
90103
return () => [
91-
h('div', 'Hello World'),
104+
h('div', { class: 'obsidian-plugin-vue' }, renderFunctions.map(fn => fn())),
92105
]
93106
},
94107
})

0 commit comments

Comments
 (0)