Skip to content

Commit

Permalink
feat: code prapper
Browse files Browse the repository at this point in the history
  • Loading branch information
jaskang committed Mar 29, 2024
1 parent 5d72f64 commit df23275
Show file tree
Hide file tree
Showing 9 changed files with 250 additions and 60 deletions.
4 changes: 4 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
"@tailwindcss/typography": "0.5.10",
"@tailwindcss/vite": "4.0.0-alpha.10",
"@types/jsdom": "^21.1.6",
"@types/mdast": "^4.0.3",
"@types/node": "^20.11.30",
"@typescript-eslint/eslint-plugin": "^7.3.1",
"@typescript-eslint/parser": "^7.3.1",
Expand All @@ -26,13 +27,16 @@
"eslint-plugin-vue": "^9.23.0",
"globals": "^14.0.0",
"happy-dom": "^14.3.6",
"mdast-util-from-markdown": "^2.0.0",
"mdast-util-to-markdown": "^2.1.0",
"mini-svg-data-uri": "^1.4.4",
"prettier": "^3.2.5",
"prettier-plugin-tailwindcss": "^0.5.12",
"remark-gfm": "^4.0.0",
"rimraf": "^5.0.5",
"tailwindcss": "4.0.0-alpha.10",
"typescript": "^5.4.3",
"unist-util-visit": "^5.0.0",
"vite": "^5.2.6",
"vite-plugin-dts": "^3.7.3",
"vite-plugin-inspect": "^0.8.3",
Expand Down
40 changes: 1 addition & 39 deletions packages/docs/.vitepress/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,44 +9,9 @@ import tailwindcss from '@tailwindcss/vite'
// import typography from '@tailwindcss/typography'
// import forms from '@tailwindcss/forms'
import { ThemeConfig } from './theme/theme'

function getHash(text: string): string {
return createHash('sha256').update(text).digest('hex').substring(0, 8)
}

const demoBlocks = new Map<string, string>()
import { demo } from './plugins/demo'
const __dirname = dirname(fileURLToPath(import.meta.url))
console.log('vitepress config', __dirname)
const demo = () => {
return {
name: 'baseCss',
resolveId(id: string) {
if (id.startsWith('virtual:') && demoBlocks.has(id)) {
return '\0' + id
}
},
load(id: string) {
if (id.startsWith('\0virtual:') && demoBlocks.has(id)) {
return demoBlocks.get(id.replace('\0', ''))
}
},
}
}

const fencePlugin = (md: MarkdownRenderer) => {
const fence = md.renderer.rules.fence!
md.renderer.rules.fence = (tokens, idx, ...args) => {
const setup = tokens[0]
console.log(setup)
const node = tokens[idx]
const rawCode = fence(tokens, idx, ...args)
const id = getHash(node.content)

setup.content.replace(`</script>\n`, `import Demo${id} from 'virtual:Demo${id}' </script>\n`)
demoBlocks.set(`Demo${id}`, node.content)
return `<demo-${id}/> ${rawCode}`
}
}

// https://vitepress.dev/reference/site-config
export default defineConfigWithTheme<ThemeConfig>({
Expand Down Expand Up @@ -117,9 +82,6 @@ export default defineConfigWithTheme<ThemeConfig>({
},
},
markdown: {
config(md) {
md.use(fencePlugin)
},
// codeTransformers: [
// {
// preprocess(code, options) {
Expand Down
Empty file removed packages/docs/.vitepress/plugin.ts
Empty file.
140 changes: 140 additions & 0 deletions packages/docs/.vitepress/plugins/demo.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,140 @@
import fs from 'node:fs'
import path, { dirname } from 'node:path'

import type { Plugin, ResolvedConfig } from 'vite'

import { createHash } from 'node:crypto'

import { fromMarkdown } from 'mdast-util-from-markdown'
import { toMarkdown } from 'mdast-util-to-markdown'
import { visit } from 'unist-util-visit'
import type { Html } from 'mdast'

const CODE_VUE_REGEXP = /.md.DemoI[a-zA-Z0-9]{8}\.vue$/
const DemoMap = new Map<string, string>()

function getHash(text: string): string {
return createHash('sha256').update(text).digest('hex').substring(0, 8)
}

function praseMeta(meta?: string | null) {
const metaArr = (meta || '').split(' ')
const ret: Record<string, string | boolean> = {}
for (const m of metaArr) {
const [key, val] = m.split('=', 2)
ret[key] = val || true
}
return ret
}
function remarkDemo(id: string, content: string) {
const root = fromMarkdown(content)

const blocks: Record<string, string> = {}

visit(root, 'code', (node, index, parent) => {
const lang = (node.lang || '').split(':')[0]
const meta = praseMeta(node.meta)
const demo = meta['demo']
const isVueDemo = demo && lang === 'vue'
if (isVueDemo) {
const hash = getHash(node.value)
const name = `DemoI${hash}`
blocks[name] = node.value
parent!.children.splice(index!, 1, {
type: 'html',
value: `
<DemoWrapper lang="${decodeURIComponent(node.lang || '')}" meta="${decodeURIComponent(node.meta || '')}" code="${encodeURIComponent(node.value)}">
<${name}/>
</DemoWrapper>`,
})
return index! + 1
}
})

const setup = root.children.find(n => n.type === 'html' && n.value.startsWith('<script setup>')) as Html
if (setup) {
setup.value = setup.value.replace(
/<script setup>/,
`<script setup>\n${Object.keys(blocks)
.map(k => `import ${k} from "${id}.${k}.vue";`)
.join('\n')}`
)
} else {
root.children.unshift({
type: 'html',
value: '<script setup>\n' + "import { ref } from 'vue'\n" + '\n' + 'const count = ref(0)\n' + '</script>',
})
}

const code = toMarkdown(root)
return { code, blocks }
}

export function demo(): Plugin {
let config: ResolvedConfig
let vuePlugin: any
return {
name: 'vite:markdown-demo',
enforce: 'pre',
async configResolved(cfg) {
config = cfg
vuePlugin = cfg.plugins.find(p => p.name === 'vite:vue')!
// const isVitepress = cfg.plugins.find(p => p.name === 'vitepress')
// envType = isVitepress ? 'vitepress' : 'vite'
},
resolveId(id) {
if (CODE_VUE_REGEXP.test(id)) {
return id
}
},
async load(id) {
if (CODE_VUE_REGEXP.test(id)) {
const blockId = '/' + path.relative(config.root, id)
const demoCode = DemoMap.get(id) || DemoMap.get(blockId)
return demoCode
}
if (id.endsWith('.md')) {
const { code, blocks } = remarkDemo(id, fs.readFileSync(id, 'utf8'))
console.log(code)
for (const k of Object.keys(blocks)) {
const blockKey = `${id}.${k}.vue`
const blockId = '/' + path.relative(config.root, blockKey)
DemoMap.set(blockId, blocks[k])
}
return code
}
},
async handleHotUpdate(ctx) {
const { file, server, timestamp } = ctx
const { moduleGraph } = server
if (file.endsWith('.md')) {
const { blocks } = remarkDemo(file, fs.readFileSync(file, 'utf8'))
const updates: any[] = []
for (const k of Object.keys(blocks)) {
const blockKey = `${file}.${k}.vue`
const blockId = '/' + path.relative(config.root, blockKey)
DemoMap.set(blockId, blocks[k])

const mod = moduleGraph.getModuleById(blockId)
if (mod) {
// console.log(mod)
const ret = await vuePlugin.handleHotUpdate({
file: blockId,
timestamp: timestamp,
modules: [mod],
read: () => blocks[k],
server: server,
})

updates.push(...ret)
}
}
if (updates.length > 0) {
return updates.filter(Boolean)
}
}
},
}
}

export default demo
18 changes: 18 additions & 0 deletions packages/docs/.vitepress/theme/components/DemoWrapper.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
<script setup lang="ts">
defineOptions({ name: 'DemoWrapper' })
const emit = defineEmits<{ click: [any] }>()
const slots = defineSlots<{ default?(_: {}): any }>()
const props = defineProps({
lang: { type: String, required: true },
meta: { type: String, required: true },
code: { type: String, required: true },
})
</script>
<template>
<div class="flex-col">
<div>
<slot />
</div>
<div>{{ lang }} {{ meta }} {{ code }}</div>
</div>
</template>
7 changes: 7 additions & 0 deletions packages/docs/.vitepress/theme/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,13 @@
import './theme.css'
import type { Theme } from 'vitepress'
import Layout from './Layout.vue'
import DemoWrapper from './components/DemoWrapper.vue'
import { plugin } from 'tailv'
export default {
Layout: Layout,
enhanceApp(ctx) {
ctx.app.component('DemoWrapper', DemoWrapper)
// @ts-ignore
ctx.app.use(plugin)
},
} satisfies Theme
6 changes: 3 additions & 3 deletions packages/docs/components/button.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
<script setup>
import { Button,SpaceCompact,Tooltip } from 'tailv'
</script>
---
hello: world
---

# Button

Expand Down
77 changes: 64 additions & 13 deletions packages/vue/src/index.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,64 @@
export { default as Button } from './Button/index.vue'
export { Checkbox, CheckboxGroup } from './Checkbox'
export { Radio, RadioGroup } from './Radio'
export { Anchor, type AnchorItem } from './Anchor'
export { ScrollArea } from './ScrollArea'
export { default as Input } from './Input/index.vue'
export { Select, type SelectOption } from './Select'
export { Switch } from './Switch'
export { Menu, type MenuItemRawType, type MenuItemType, type MenuItemTitleType, type MenuItemDividerType } from './Menu'
export { default as Popover } from './Popover/index.vue'
export { default as Tooltip } from './Tooltip/index.vue'
export { default as SpaceCompact } from './Space/SpaceCompact.vue'
export { useAnchor, type AnchorRange, type AnchorHeader } from './use/useAnchor'
import type { Plugin } from 'vue'

import { default as Button } from './Button/index.vue'
import { Checkbox, CheckboxGroup } from './Checkbox'
import { Radio, RadioGroup } from './Radio'
import { Anchor, type AnchorItem } from './Anchor'
import { ScrollArea } from './ScrollArea'
import { default as Input } from './Input/index.vue'
import { Select, type SelectOption } from './Select'
import { Switch } from './Switch'
import { Menu, type MenuItemRawType, type MenuItemType, type MenuItemTitleType, type MenuItemDividerType } from './Menu'
import { default as Popover } from './Popover/index.vue'
import { default as Tooltip } from './Tooltip/index.vue'
import { default as SpaceCompact } from './Space/SpaceCompact.vue'
import { useAnchor, type AnchorRange, type AnchorHeader } from './use/useAnchor'

export {
// components
Button,
Checkbox,
CheckboxGroup,
Radio,
RadioGroup,
Anchor,
ScrollArea,
Input,
Select,
Switch,
Menu,
Popover,
Tooltip,
SpaceCompact,
// use
useAnchor,
}

export type {
AnchorItem,
AnchorRange,
AnchorHeader,
MenuItemRawType,
MenuItemType,
MenuItemTitleType,
MenuItemDividerType,
SelectOption,
}
export const plugin: Plugin = {
install(app, ...options) {
app.component(Button.name!, Button)
app.component(Checkbox.name!, Checkbox)
app.component(CheckboxGroup.name!, CheckboxGroup)
app.component(Radio.name!, Radio)
app.component(RadioGroup.name!, RadioGroup)
app.component(Anchor.name!, Anchor)
app.component(ScrollArea.name!, ScrollArea)
app.component(Input.name!, Input)
app.component(Select.name!, Select)
app.component(Switch.name!, Switch)
app.component(Menu.name!, Menu)
app.component(Popover.name!, Popover)
app.component(Tooltip.name!, Tooltip)
app.component(SpaceCompact.name!, SpaceCompact)
},
}
18 changes: 13 additions & 5 deletions pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

0 comments on commit df23275

Please sign in to comment.