UNPKG

svem

Version:

Svelte in Markdown preprocessor

113 lines (112 loc) 3.92 kB
import { transform } from "./core/index.js"; import { parse } from "svelte/compiler"; import { remarkSvelteEscape, remarkSvelteParse } from "./plugins/index.js"; const LAYOUT_DATA_TOKEN = "__svemLayoutData"; const LAYOUT_WRAP_TOKEN = "SvemLayout"; const LAYOUT_META_TOKEN = "meta"; const svem = (options) => { const { extensions = [".svem", ".svx"] } = options ?? {}; return { name: "svem", async markup({ content, filename }) { if (extensions.some((ext) => filename.endsWith(ext))) { const layouts = await resolveLayouts(options); if (options?.annotateScript) { content = transformScripts(content, options?.annotateOpen, options?.annotateClose); } let { code, data } = await transform(content, filename, { ...options, decoder: [remarkSvelteParse, ...options?.decoder ?? []], encoder: [remarkSvelteEscape, ...options?.encoder ?? []] }); code = wrapLayout(code, layouts, data); if (code.match(/\$svem\(\)/g)) { code = code.replace(/globalThis\.\$svem\(\)/g, LAYOUT_DATA_TOKEN).replace(/\$svem\(\)/g, LAYOUT_DATA_TOKEN); } return { code }; } } }; }; function wrapLayout(code, layouts, data) { const outputData = { ...data, ...data[LAYOUT_META_TOKEN] }; delete outputData[LAYOUT_META_TOKEN]; const layout = layouts[data?.[LAYOUT_META_TOKEN]?.layout] ?? layouts.default; let output = code; const ast = parse(output); const template = output.slice(ast.html.start, ast.html.end); const { start = 0, end = 0 } = ast.module ?? {}; let outScript = '<script lang="ts" module>\n<\/script>'; let rawScript = ""; if (end > start) { rawScript = output.slice(start, end); outScript = rawScript; } outScript = outScript.replace(/<\/script>$/, (match) => { return `const ${LAYOUT_DATA_TOKEN} = ${JSON.stringify(outputData)}; ${match}`; }); if (layout) { const openingTag = `<${layout.tag} {...${LAYOUT_DATA_TOKEN}}>`; const closingTag = `</${layout.tag}>`; const wrapped = `${openingTag} ${template} ${closingTag}`; output = output.replace(template, wrapped); outScript = outScript.replace(/^<script(?:\s+lang="(?:ts|js)")?(?:\s+module)?\s*>/, (match) => { return `${match} ${layout.script}`; }); } if (rawScript) { output = output.replace(rawScript, outScript); } else { output = `${outScript} ${output}`; } return output; } async function resolveLayouts(options = {}) { const { layout: layoutInput, layouts: layoutInputs = {} } = options ?? {}; const layouts = {}; if (layoutInput) { layoutInputs.default = layoutInput; } for (const [name, path] of Object.entries(layoutInputs)) { if (typeof path === "string") { const tag = `${LAYOUT_WRAP_TOKEN}${toTitleCase(name)}`; layouts[name] = { tag, path, script: `import ${tag} from '${path}'; `, openingTag: `<${tag}>`, closingTag: `</${tag}>` }; } } return layouts; } function toTitleCase(text) { const words = text.match(/\w+/g) ?? []; return words.map((word) => word.replace(/^\w/, (c) => c.toUpperCase())).join(""); } function annotationOpenRegex(open = "--", close = "--") { return new RegExp(`${open}(script|style)(?:\\s+[^${close}]+)?\\s*${close}`, "i"); } function annotationCloseRegex(open = "--", close = "--") { return new RegExp(`${open}/(script|style)${close}`, "i"); } function transformScripts(content, open = "--", close = "--") { return content.replace(annotationOpenRegex(open, close), (match) => { return match.replace(new RegExp(`^${open}`), "<").replace(new RegExp(`${close}$`), ">"); }).replace(annotationCloseRegex(open, close), (match) => { return match.replace(new RegExp(`^${open}`), "<").replace(new RegExp(`${close}$`), ">"); }); } export { LAYOUT_DATA_TOKEN, LAYOUT_META_TOKEN, LAYOUT_WRAP_TOKEN, svem };