Last active
March 27, 2025 02:42
-
-
Save Lightnet/8b2dad4aa831772fc1d486d813707ad1 to your computer and use it in GitHub Desktop.
vanjs markdown build test.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import van from "https://cdn.jsdelivr.net/gh/vanjs-org/van/public/van-1.5.3.min.js"; | |
const { div, p, h1, h2, h3, h4, h5, h6, pre, code, button, textarea, strong, em, a, ul, ol, li, blockquote, br, table, thead, tbody, tr, th, td, img, span, hr, del, sup, section } = van.tags; | |
// Editor State | |
const editorState = van.state({ | |
text: "# Sample Title\n\nHello, **bold** and *italic* world![^1]\n\n> This is a **blockquote** with *emphasis*\n> Another line\n\n| Header 1 | Header 2 |\n|----------|----------|\n| Cell 1 | **Cell 2** |\n| *Cell 3* | <span style=\"color: red;\">Cell 4</span> |\n\n\n\nHere's some <b>bold</b> and <i>italic</i> inline HTML.\nWith a break \nright here \nand more.[^2]\n\nasdddd\ndddddd\ndddddd\n\nddddd\nddddd\n\n[^1]: This is the first footnote.\n[^2]: This is the second footnote with **bold** text.\n\n---\n\n```javascript\nconsole.log('hi');\n```\n\n- Item 1\n- _Item 2_\n\n1. First\n2. ~~Second~~\n\n[Link](https://example.com)\n\n`inline code`" | |
}); | |
// Highlight.js State | |
const hljsState = van.state(null); | |
// Load highlight.js and languages | |
const loadHighlightJS = async () => { | |
const hljs = (await import("https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.11.1/es/highlight.min.js")).default; | |
const jsLang = (await import("https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.11.1/es/languages/javascript.min.js")).default; | |
const pyLang = (await import("https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.11.1/es/languages/python.min.js")).default; | |
hljs.registerLanguage("javascript", jsLang); | |
hljs.registerLanguage("python", pyLang); | |
hljsState.val = hljs; | |
return hljs; | |
}; | |
// Initialize highlight.js | |
loadHighlightJS(); | |
// Inline parsing function | |
const parseInline = (text, footnotes) => { | |
let parts = [text]; | |
// Footnotes: [^id] | |
parts = parts.flatMap(part => { | |
if (typeof part !== "string") return [part]; | |
const result = []; | |
let remaining = part; | |
const footnoteRegex = /\[\^([^\]]+)\]/g; | |
let lastIndex = 0; | |
let match; | |
while ((match = footnoteRegex.exec(remaining)) !== null) { | |
if (match.index > lastIndex) { | |
result.push(remaining.slice(lastIndex, match.index)); | |
} | |
const id = match[1]; | |
if (footnotes[id]) { | |
result.push(sup(a({ href: `#fn-${id}`, id: `fnref-${id}`, class: "footnote-ref" }, id))); | |
} else { | |
result.push(`[^${id}]`); // Unresolved footnote | |
} | |
lastIndex = footnoteRegex.lastIndex; | |
} | |
if (lastIndex < remaining.length) { | |
result.push(remaining.slice(lastIndex)); | |
} | |
return result.length ? result : [part]; | |
}); | |
// Inline HTML: <tag>...</tag> or <tag attr="value">...</tag> | |
parts = parts.flatMap(part => { | |
if (typeof part !== "string") return [part]; | |
const result = []; | |
let remaining = part; | |
const htmlRegex = /<([a-zA-Z][a-zA-Z0-9]*)([^>]*)>(.*?)<\/\1>|<([a-zA-Z][a-zA-Z0-9]*)([^>]*)\/>/g; | |
let lastIndex = 0; | |
let match; | |
while ((match = htmlRegex.exec(remaining)) !== null) { | |
if (match.index > lastIndex) { | |
result.push(remaining.slice(lastIndex, match.index)); | |
} | |
const tag = match[1] || match[4]; | |
const attributes = match[2] || match[5] || ""; | |
const content = match[3] || ""; | |
result.push(span({ innerHTML: `<${tag}${attributes}>${content}</${tag}>` })); | |
lastIndex = htmlRegex.lastIndex; | |
} | |
if (lastIndex < remaining.length) { | |
result.push(remaining.slice(lastIndex)); | |
} | |
return result.length ? result : [part]; | |
}); | |
// Inline code: `text` | |
parts = parts.flatMap(part => { | |
if (typeof part !== "string") return [part]; | |
const result = []; | |
let remaining = part; | |
const codeRegex = /`([^`]+)`/g; | |
let lastIndex = 0; | |
let match; | |
while ((match = codeRegex.exec(remaining)) !== null) { | |
if (match.index > lastIndex) { | |
result.push(remaining.slice(lastIndex, match.index)); | |
} | |
result.push(code(match[1])); | |
lastIndex = codeRegex.lastIndex; | |
} | |
if (lastIndex < remaining.length) { | |
result.push(remaining.slice(lastIndex)); | |
} | |
return result.length ? result : [part]; | |
}); | |
// Strikethrough: ~~text~~ | |
parts = parts.flatMap(part => { | |
if (typeof part !== "string") return [part]; | |
const result = []; | |
let remaining = part; | |
const strikeRegex = /~~([^~]+)~~/g; | |
let lastIndex = 0; | |
let match; | |
while ((match = strikeRegex.exec(remaining)) !== null) { | |
if (match.index > lastIndex) { | |
result.push(remaining.slice(lastIndex, match.index)); | |
} | |
result.push(del(match[1])); | |
lastIndex = strikeRegex.lastIndex; | |
} | |
if (lastIndex < remaining.length) { | |
result.push(remaining.slice(lastIndex)); | |
} | |
return result.length ? result : [part]; | |
}); | |
// Bold: **text** | |
parts = parts.flatMap(part => { | |
if (typeof part !== "string") return [part]; | |
const result = []; | |
let remaining = part; | |
const boldRegex = /\*\*([^*]+)\*\*/g; | |
let lastIndex = 0; | |
let match; | |
while ((match = boldRegex.exec(remaining)) !== null) { | |
if (match.index > lastIndex) { | |
result.push(remaining.slice(lastIndex, match.index)); | |
} | |
result.push(strong(match[1])); | |
lastIndex = boldRegex.lastIndex; | |
} | |
if (lastIndex < remaining.length) { | |
result.push(remaining.slice(lastIndex)); | |
} | |
return result.length ? result : [part]; | |
}); | |
// Italic: *text* or _text_ | |
parts = parts.flatMap(part => { | |
if (typeof part !== "string") return [part]; | |
const result = []; | |
let remaining = part; | |
const italicRegex = /(\*|_)([^*_]+)\1/g; | |
let lastIndex = 0; | |
let match; | |
while ((match = italicRegex.exec(remaining)) !== null) { | |
if (match.index > lastIndex) { | |
result.push(remaining.slice(lastIndex, match.index)); | |
} | |
result.push(em(match[2])); | |
lastIndex = italicRegex.lastIndex; | |
} | |
if (lastIndex < remaining.length) { | |
result.push(remaining.slice(lastIndex)); | |
} | |
return result.length ? result : [part]; | |
}); | |
// Images:  | |
parts = parts.flatMap(part => { | |
if (typeof part !== "string") return [part]; | |
const result = []; | |
let remaining = part; | |
const imageRegex = /!\[([^\]]*)\]\(([^)]+)\)/g; | |
let lastIndex = 0; | |
let match; | |
while ((match = imageRegex.exec(remaining)) !== null) { | |
if (match.index > lastIndex) { | |
result.push(remaining.slice(lastIndex, match.index)); | |
} | |
result.push(img({ src: match[2], alt: match[1], class: "markdown-body" })); | |
lastIndex = imageRegex.lastIndex; | |
} | |
if (lastIndex < remaining.length) { | |
result.push(remaining.slice(lastIndex)); | |
} | |
return result.length ? result : [part]; | |
}); | |
// Links: [text](url) | |
parts = parts.flatMap(part => { | |
if (typeof part !== "string") return [part]; | |
const result = []; | |
let remaining = part; | |
const linkRegex = /\[([^\]]+)\]\(([^)]+)\)/g; | |
let lastIndex = 0; | |
let match; | |
while ((match = linkRegex.exec(remaining)) !== null) { | |
if (match.index > lastIndex) { | |
result.push(remaining.slice(lastIndex, match.index)); | |
} | |
result.push(a({ href: match[2] }, match[1])); | |
lastIndex = linkRegex.lastIndex; | |
} | |
if (lastIndex < remaining.length) { | |
result.push(remaining.slice(lastIndex)); | |
} | |
return result.length ? result : [part]; | |
}); | |
return parts; | |
}; | |
// Parse Markdown text into blocks and footnotes | |
const parseMarkdown = (text) => { | |
const blocks = []; | |
const footnotes = {}; | |
const lines = text.split("\n"); | |
let currentCodeBlock = null; | |
let currentList = null; | |
let currentBlockquote = null; | |
let currentTable = null; | |
let currentParagraphLines = []; | |
for (let i = 0; i < lines.length; i++) { | |
const line = lines[i]; | |
const trimmedLine = line.trim(); | |
// Footnote definition: [^id]: text | |
if (trimmedLine.match(/^\[\^[^\]]+\]:/)) { | |
if (currentList) { | |
blocks.push(currentList); | |
currentList = null; | |
} | |
if (currentBlockquote) { | |
blocks.push(currentBlockquote); | |
currentBlockquote = null; | |
} | |
if (currentTable) { | |
blocks.push(currentTable); | |
currentTable = null; | |
} | |
if (currentParagraphLines.length) { | |
blocks.push({ type: "paragraph", text: currentParagraphLines.join("\n") }); | |
currentParagraphLines = []; | |
} | |
const [id, ...content] = trimmedLine.split(":"); | |
const footnoteId = id.replace(/^\[\^|\]$/g, "").trim(); | |
footnotes[footnoteId] = content.join(":").trim(); | |
continue; | |
} | |
if (trimmedLine.startsWith("#")) { | |
const level = trimmedLine.match(/^#+/)[0].length; | |
if (level <= 6) { | |
if (currentList) { | |
blocks.push(currentList); | |
currentList = null; | |
} | |
if (currentBlockquote) { | |
blocks.push(currentBlockquote); | |
currentBlockquote = null; | |
} | |
if (currentTable) { | |
blocks.push(currentTable); | |
currentTable = null; | |
} | |
if (currentParagraphLines.length) { | |
blocks.push({ type: "paragraph", text: currentParagraphLines.join("\n") }); | |
currentParagraphLines = []; | |
} | |
blocks.push({ type: `h${level}`, text: trimmedLine.replace(/^#+/, "").trim() }); | |
continue; | |
} | |
} | |
if (trimmedLine.startsWith("```")) { | |
if (currentList) { | |
blocks.push(currentList); | |
currentList = null; | |
} | |
if (currentBlockquote) { | |
blocks.push(currentBlockquote); | |
currentBlockquote = null; | |
} | |
if (currentTable) { | |
blocks.push(currentTable); | |
currentTable = null; | |
} | |
if (currentParagraphLines.length) { | |
blocks.push({ type: "paragraph", text: currentParagraphLines.join("\n") }); | |
currentParagraphLines = []; | |
} | |
if (currentCodeBlock) { | |
blocks.push(currentCodeBlock); | |
currentCodeBlock = null; | |
} else { | |
const language = trimmedLine.replace("```", "").trim() || "text"; | |
currentCodeBlock = { type: "code", language, text: "" }; | |
} | |
continue; | |
} | |
if (currentCodeBlock) { | |
currentCodeBlock.text += (currentCodeBlock.text ? "\n" : "") + line; | |
continue; | |
} | |
if (trimmedLine.startsWith("> ")) { | |
if (currentList) { | |
blocks.push(currentList); | |
currentList = null; | |
} | |
if (currentTable) { | |
blocks.push(currentTable); | |
currentTable = null; | |
} | |
if (currentParagraphLines.length) { | |
blocks.push({ type: "paragraph", text: currentParagraphLines.join("\n") }); | |
currentParagraphLines = []; | |
} | |
if (!currentBlockquote) { | |
currentBlockquote = { type: "blockquote", text: "" }; | |
} | |
currentBlockquote.text += (currentBlockquote.text ? "\n" : "") + trimmedLine.replace("> ", "").trim(); | |
continue; | |
} | |
if (trimmedLine.startsWith("|")) { | |
if (currentList) { | |
blocks.push(currentList); | |
currentList = null; | |
} | |
if (currentBlockquote) { | |
blocks.push(currentBlockquote); | |
currentBlockquote = null; | |
} | |
if (currentParagraphLines.length) { | |
blocks.push({ type: "paragraph", text: currentParagraphLines.join("\n") }); | |
currentParagraphLines = []; | |
} | |
if (!currentTable) { | |
currentTable = { type: "table", rows: [] }; | |
} | |
const cells = trimmedLine.split("|").map(cell => cell.trim()).filter(cell => cell !== ""); | |
currentTable.rows.push(cells); | |
if (i + 1 < lines.length && lines[i + 1].trim().match(/^\|?-+\|?-+\|?$/)) { | |
i++; // Skip the separator line | |
} | |
continue; | |
} | |
if (trimmedLine.match(/^(?:[-*_]){3,}$/)) { | |
if (currentList) { | |
blocks.push(currentList); | |
currentList = null; | |
} | |
if (currentBlockquote) { | |
blocks.push(currentBlockquote); | |
currentBlockquote = null; | |
} | |
if (currentTable) { | |
blocks.push(currentTable); | |
currentTable = null; | |
} | |
if (currentParagraphLines.length) { | |
blocks.push({ type: "paragraph", text: currentParagraphLines.join("\n") }); | |
currentParagraphLines = []; | |
} | |
blocks.push({ type: "hr" }); | |
continue; | |
} | |
if (trimmedLine.startsWith("- ")) { | |
if (currentBlockquote) { | |
blocks.push(currentBlockquote); | |
currentBlockquote = null; | |
} | |
if (currentTable) { | |
blocks.push(currentTable); | |
currentTable = null; | |
} | |
if (currentParagraphLines.length) { | |
blocks.push({ type: "paragraph", text: currentParagraphLines.join("\n") }); | |
currentParagraphLines = []; | |
} | |
if (!currentList || currentList.type !== "ul") { | |
if (currentList) blocks.push(currentList); | |
currentList = { type: "ul", items: [] }; | |
} | |
currentList.items.push(trimmedLine.replace("- ", "").trim()); | |
continue; | |
} | |
if (trimmedLine.match(/^\d+\.\s/)) { | |
if (currentBlockquote) { | |
blocks.push(currentBlockquote); | |
currentBlockquote = null; | |
} | |
if (currentTable) { | |
blocks.push(currentTable); | |
currentTable = null; | |
} | |
if (currentParagraphLines.length) { | |
blocks.push({ type: "paragraph", text: currentParagraphLines.join("\n") }); | |
currentParagraphLines = []; | |
} | |
if (!currentList || currentList.type !== "ol") { | |
if (currentList) blocks.push(currentList); | |
currentList = { type: "ol", items: [] }; | |
} | |
currentList.items.push(trimmedLine.replace(/^\d+\.\s/, "").trim()); | |
continue; | |
} | |
if (trimmedLine === "") { | |
if (currentList) { | |
blocks.push(currentList); | |
currentList = null; | |
} | |
if (currentBlockquote) { | |
blocks.push(currentBlockquote); | |
currentBlockquote = null; | |
} | |
if (currentTable) { | |
blocks.push(currentTable); | |
currentTable = null; | |
} | |
if (currentParagraphLines.length) { | |
blocks.push({ type: "paragraph", text: currentParagraphLines.join("\n") }); | |
currentParagraphLines = []; | |
} | |
continue; | |
} | |
if (line) { | |
if (currentList) { | |
blocks.push(currentList); | |
currentList = null; | |
} | |
if (currentBlockquote) { | |
blocks.push(currentBlockquote); | |
currentBlockquote = null; | |
} | |
if (currentTable) { | |
blocks.push(currentTable); | |
currentTable = null; | |
} | |
currentParagraphLines.push(line); | |
} | |
} | |
if (currentCodeBlock) blocks.push(currentCodeBlock); | |
if (currentList) blocks.push(currentList); | |
if (currentBlockquote) blocks.push(currentBlockquote); | |
if (currentTable) blocks.push(currentTable); | |
if (currentParagraphLines.length) { | |
blocks.push({ type: "paragraph", text: currentParagraphLines.join("\n") }); | |
} | |
return { blocks, footnotes }; | |
}; | |
// Preview Renderer | |
const PreviewBlock = ({ type, text, language, items, rows, footnotes }) => { | |
const hljs = hljsState.val; | |
if (type === "paragraph") { | |
const lines = text.split("\n").flatMap(line => { | |
const hasBreak = line.match(/\s{2}$/); | |
const parsedLine = parseInline(line.replace(/\s{2}$/, ""), footnotes); | |
return hasBreak ? [...parsedLine, br()] : parsedLine; | |
}); | |
return p({ class: "markdown-body" }, lines); | |
} | |
if (type === "h1") return h1({ class: "markdown-body" }, ...parseInline(text, footnotes)); | |
if (type === "h2") return h2({ class: "markdown-body" }, ...parseInline(text, footnotes)); | |
if (type === "h3") return h3({ class: "markdown-body" }, ...parseInline(text, footnotes)); | |
if (type === "h4") return h4({ class: "markdown-body" }, ...parseInline(text, footnotes)); | |
if (type === "h5") return h5({ class: "markdown-body" }, ...parseInline(text, footnotes)); | |
if (type === "h6") return h6({ class: "markdown-body" }, ...parseInline(text, footnotes)); | |
if (type === "code") { | |
if (hljs) { | |
const highlighted = hljs.highlight(text, { language: language === "text" ? "plaintext" : language }).value; | |
return pre({ class: "markdown-body code-block" }, | |
code({ class: `language-${language}`, innerHTML: highlighted }) | |
); | |
} | |
return pre({ class: "markdown-body code-block" }, code({ class: `language-${language}` }, text)); | |
} | |
if (type === "blockquote") { | |
const lines = text.split("\n").map(line => p(...parseInline(line, footnotes))); | |
return blockquote({ class: "markdown-body blockquote" }, lines); | |
} | |
if (type === "table") { | |
const [header, ...body] = rows; | |
return table({ class: "markdown-body" }, | |
thead(tr(header.map(cell => th(...parseInline(cell, footnotes))))), | |
tbody(body.map(row => tr(row.map(cell => td(...parseInline(cell, footnotes)))))) | |
); | |
} | |
if (type === "hr") return hr({ class: "markdown-body" }); | |
if (type === "ul") return ul({ class: "markdown-body" }, items.map(item => li({ class: "markdown-body" }, ...parseInline(item, footnotes)))); | |
if (type === "ol") return ol({ class: "markdown-body" }, items.map(item => li({ class: "markdown-body" }, ...parseInline(item, footnotes)))); | |
}; | |
// Editor Component with Preview | |
const Editor = () => { | |
const editorTextarea = textarea({ | |
value: editorState.val.text, | |
style: "width: 100%; height: 100%; resize: none; padding: 10px; font-family: monospace; box-sizing: border-box;", | |
oninput: (e) => { | |
editorState.val = { ...editorState.val, text: e.target.value }; | |
} | |
}); | |
const editorPane = div({ class: "editor-pane" }, editorTextarea); | |
const previewPane = div({ class: "preview-pane" }, | |
h1("Preview"), | |
van.derive(() => { | |
const { blocks, footnotes } = parseMarkdown(editorState.val.text); | |
const content = blocks.map(block => PreviewBlock({ ...block, footnotes })); | |
if (Object.keys(footnotes).length > 0) { | |
const footnoteList = ol({ class: "markdown-body footnotes" }, | |
Object.entries(footnotes).map(([id, text]) => | |
li({ id: `fn-${id}`, class: "footnote-item" }, | |
...parseInline(text, footnotes), | |
a({ href: `#fnref-${id}`, class: "footnote-backref" }, "↩") | |
) | |
) | |
); | |
content.push(hr({ class: "markdown-body" }), section({ class: "markdown-body" }, h2("Footnotes"), footnoteList)); | |
} | |
return div({ class: "preview-content" }, content); | |
}) | |
); | |
return div({ class: "editor-container" }, editorPane, previewPane); | |
}; | |
// Mount to DOM | |
van.add(document.getElementById("app"), Editor()); |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<!DOCTYPE html> | |
<html lang="en"> | |
<head> | |
<meta charset="UTF-8"> | |
<title>Markdown Editor with Preview</title> | |
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.11.1/styles/github.min.css"> | |
<style> | |
.editor-container { | |
display: flex; | |
width: 100%; | |
height: 100vh; | |
overflow: hidden; | |
} | |
.editor-pane, .preview-pane { | |
width: 50%; | |
padding: 10px; | |
overflow-y: auto; | |
box-sizing: border-box; | |
} | |
.editor-pane { border-right: 1px solid #ccc; } | |
.preview-pane { background: #f9f9f9; } | |
.markdown-body p { font-size: 16px; line-height: 1.6; margin: 0 0 10px; } | |
.markdown-body h1, .markdown-body h2, .markdown-body h3, | |
.markdown-body h4, .markdown-body h5, .markdown-body h6 { | |
font-weight: 600; | |
margin: 16px 0; | |
} | |
.markdown-body h1 { font-size: 24px; } | |
.markdown-body h2 { font-size: 20px; } | |
.markdown-body h3 { font-size: 18px; } | |
.markdown-body h4 { font-size: 16px; } | |
.markdown-body h5 { font-size: 14px; } | |
.markdown-body h6 { font-size: 12px; } | |
.markdown-body.code-block { | |
background: #f6f8fa; | |
padding: 16px; | |
border: 1px solid #e1e4e8; | |
border-radius: 6px; | |
margin: 10px 0; | |
width: 100%; | |
box-sizing: border-box; | |
white-space: pre-wrap; | |
overflow-x: auto; | |
} | |
.markdown-body code { | |
font-family: monospace; | |
font-size: 14px; | |
background: #f6f8fa; | |
padding: 2px 4px; | |
border-radius: 3px; | |
} | |
.markdown-body strong { font-weight: 700; } | |
.markdown-body em { font-style: italic; } | |
.markdown-body del { text-decoration: line-through; } | |
.markdown-body a { color: #0366d6; text-decoration: none; } | |
.markdown-body a:hover { text-decoration: underline; } | |
.markdown-body ul, .markdown-body ol { | |
margin: 10px 0; | |
padding-left: 20px; | |
} | |
.markdown-body li { | |
margin: 5px 0; | |
line-height: 1.6; | |
} | |
.markdown-body ul { list-style-type: disc; } | |
.markdown-body ol { list-style-type: decimal; } | |
.markdown-body.blockquote { | |
border-left: 4px solid #dfe2e5; | |
padding: 0 15px; | |
color: #6a737d; | |
margin: 10px 0; | |
background: #f6f8fa !important; | |
} | |
.markdown-body table { | |
border-collapse: collapse; | |
margin: 10px 0; | |
width: 100%; | |
} | |
.markdown-body th, .markdown-body td { | |
border: 1px solid #dfe2e5; | |
padding: 6px 13px; | |
text-align: left; | |
} | |
.markdown-body th { | |
background: #f6f8fa; | |
font-weight: 600; | |
} | |
.markdown-body tr:nth-child(even) { | |
background: #fafbfc; | |
} | |
.markdown-body img { | |
max-width: 100%; | |
height: auto; | |
margin: 10px 0; | |
display: block; | |
} | |
.markdown-body hr { | |
border: 0; | |
border-top: 1px solid #dfe2e5; | |
margin: 20px 0; | |
} | |
.markdown-body sup { font-size: 0.8em; vertical-align: super; } | |
.markdown-body .footnote-ref { color: #0366d6; } | |
.markdown-body .footnotes { | |
font-size: 14px; | |
color: #6a737d; | |
margin-top: 20px; | |
} | |
.markdown-body .footnote-item { | |
margin: 5px 0; | |
} | |
.markdown-body .footnote-backref { | |
margin-left: 5px; | |
color: #0366d6; | |
text-decoration: none; | |
} | |
.markdown-body .footnote-backref:hover { | |
text-decoration: underline; | |
} | |
</style> | |
</head> | |
<body> | |
<div id="app"></div> | |
<script type="module" src="./client.js"></script> | |
</body> | |
</html> |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import van from "vanjs-core"; | |
import http from "http"; | |
import handler from "serve-handler"; | |
const server = http.createServer((req, res) => { | |
handler(req, res, { | |
public: "./", | |
cleanUrls: false // Ensure .js files are served correctly | |
}); | |
}); | |
server.listen(3000, () => { | |
console.log("Server running at http://localhost:3000"); | |
}); | |
// Export van for use in client.js | |
globalThis.van = van; |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
{ | |
"name": "vanjs_markdown", | |
"version": "1.0.0", | |
"description": "", | |
"main": "index.js", | |
"type": "module", | |
"scripts": { | |
"start": "node index.js" | |
}, | |
"dependencies": { | |
"vanjs-core": "^1.5.0", | |
"serve-handler": "^6.1.5", | |
"http": "^0.0.1-security" | |
}, | |
"keywords": [], | |
"author": "", | |
"license": "ISC" | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment