diff --git a/docs/ssr.md b/docs/ssr.md new file mode 100644 index 000000000..3a760d1f3 --- /dev/null +++ b/docs/ssr.md @@ -0,0 +1,5 @@ +# Server-side rendering + +```js echo server +`
hello ${process.env.USER}
` +``` diff --git a/src/markdown.ts b/src/markdown.ts index 72fc03ab6..f9548d580 100644 --- a/src/markdown.ts +++ b/src/markdown.ts @@ -6,7 +6,7 @@ import equal from "fast-deep-equal"; import matter from "gray-matter"; import hljs from "highlight.js"; import {parseHTML} from "linkedom"; -import MarkdownIt from "markdown-it"; +import MarkdownIt, {type Token} from "markdown-it"; import {type RuleCore} from "markdown-it/lib/parser_core.js"; import {type RuleInline} from "markdown-it/lib/parser_inline.js"; import {type RenderRule, type default as Renderer} from "markdown-it/lib/renderer.js"; @@ -15,7 +15,7 @@ import {isEnoent} from "./error.js"; import {fileReference, getLocalPath} from "./files.js"; import {computeHash} from "./hash.js"; import {parseInfo} from "./info.js"; -import type {FileReference, ImportReference, PendingTranspile, Transpile} from "./javascript.js"; +import type {FileReference, ImportReference, ParseOptions, PendingTranspile, Transpile} from "./javascript.js"; import {transpileJavaScript} from "./javascript.js"; import {transpileTag} from "./tag.js"; import {resolvePath} from "./url.js"; @@ -101,6 +101,7 @@ function getLiveSource(content: string, tag: string): string | undefined { } function makeFenceRenderer(root: string, baseRenderer: RenderRule, sourcePath: string): RenderRule { + const transform = makeJavaScriptTransformer(root, sourcePath); return (tokens, idx, options, context: ParseContext, self) => { const token = tokens[idx]; const {tag, attributes} = parseInfo(token.info); @@ -109,27 +110,20 @@ function makeFenceRenderer(root: string, baseRenderer: RenderRule, sourcePath: s let count = 0; const source = isFalse(attributes.run) ? undefined : getLiveSource(token.content, tag); if (source != null) { - const id = uniqueCodeId(context, token.content); - const sourceLine = context.startLine + context.currentLine; - const transpile = transpileJavaScript(source, { - id, - root, - sourcePath, - sourceLine - }); - extendPiece(context, {code: [transpile]}); - if (transpile.files) context.files.push(...transpile.files); - if (transpile.imports) context.imports.push(...transpile.imports); - result += `
\n`; - count++; + if (tag === "js" && (attributes.server === "" || attributes.server?.toLowerCase() === "true")) { + result += `
${eval(source)}
`; + } else { + result += `
\n`; + } + ++count; } // TODO we could hide non-live code here with echo=false? if (source == null || (attributes.echo != null && !isFalse(attributes.echo))) { result += baseRenderer(tokens, idx, options, context, self); - count++; + ++count; } // Tokens should always be rendered as a single block element. - if (count > 1) result = "
" + result + "
"; + if (count > 1) result = `
${result}
`; return result; }; } @@ -263,21 +257,22 @@ const transformPlaceholderCore: RuleCore = (state) => { state.tokens = output; }; -function makePlaceholderRenderer(root: string, sourcePath: string): RenderRule { - return (tokens, idx, options, context: ParseContext) => { - const id = uniqueCodeId(context, tokens[idx].content); - const token = tokens[idx]; - const transpile = transpileJavaScript(token.content, { - id, - root, - sourcePath, - inline: true, - sourceLine: context.startLine + context.currentLine - }); +function makeJavaScriptTransformer(root: string, sourcePath: string, options: Partial = {}) { + return (token: Token, context: ParseContext): string => { + const id = uniqueCodeId(context, token.content); + const sourceLine = context.startLine + context.currentLine; + const transpile = transpileJavaScript(token.content, {id, root, sourcePath, sourceLine, ...options}); extendPiece(context, {code: [transpile]}); if (transpile.files) context.files.push(...transpile.files); if (transpile.imports) context.imports.push(...transpile.imports); - return ``; + return id; + }; +} + +function makePlaceholderRenderer(root: string, sourcePath: string): RenderRule { + const transform = makeJavaScriptTransformer(root, sourcePath, {inline: true}); + return (tokens, idx, options, context: ParseContext) => { + return ``; }; }