@stacksjs/stx 0.2.148 → 0.2.150

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -8,6 +8,7 @@ import { partialsCache } from "../includes.js";
8
8
  import { stateDir } from "../state-dir.js";
9
9
  import { plugin as stxPlugin } from "../plugin.js";
10
10
  import { clearComponentCache } from "../utils.js";
11
+ import { bracketPathToRegex } from "stx-router";
11
12
  import {
12
13
  colors,
13
14
  findAvailablePort,
@@ -275,9 +276,9 @@ export async function serveMultipleStxFiles(filePaths, options = {}) {
275
276
  if (routePath.endsWith("/index"))
276
277
  routePath = routePath.slice(0, -6) || "/";
277
278
  if (routePath.includes("[")) {
278
- const paramNames = [...routePath.matchAll(/\[([^\]]+)\]/g)].map((m) => m[1]), regexStr = routePath.replace(/\[([^\]]+)\]/g, "([^/]+)");
279
+ const { regex, params: paramNames } = bracketPathToRegex(routePath);
279
280
  dynamicRoutes.push({
280
- pattern: new RegExp(`^${regexStr}$`),
281
+ pattern: regex,
281
282
  paramNames,
282
283
  filePath: absolutePath,
283
284
  routeTemplate: routePath,
package/dist/index.d.ts CHANGED
@@ -228,5 +228,6 @@ export {
228
228
  validated,
229
229
  withDefaults,
230
230
  } from './props';
231
+ export { isMarkdownPath, type MarkdownRenderer, type MarkdownView, protectCodeBlocks, renderMarkdownView } from './markdown-view';
231
232
  // Default export for the stx module
232
233
  export default {};
package/dist/index.js CHANGED
@@ -144,3 +144,4 @@ export {
144
144
  withDefaults
145
145
  } from "./props.js";
146
146
  export default {};
147
+ export { isMarkdownPath, protectCodeBlocks, renderMarkdownView } from "./markdown-view.js";
@@ -0,0 +1,25 @@
1
+ /** Whether a path should be treated as a markdown view. */
2
+ export declare function isMarkdownPath(filePath: string): boolean;
3
+ /**
4
+ * Wrap code blocks so the stx pass leaves their contents alone.
5
+ *
6
+ * Applies to `<pre>` (fenced blocks) and to any `<code>` not already inside
7
+ * one (inline spans). Without this, a page documenting a directive executes it.
8
+ */
9
+ export declare function protectCodeBlocks(html: string): string;
10
+ /**
11
+ * Turn markdown source into HTML the stx pipeline can process.
12
+ *
13
+ * Frontmatter is returned rather than injected, so the caller decides how it
14
+ * merges with the rest of the render context.
15
+ */
16
+ export declare function renderMarkdownView(source: string, options?: MarkdownViewOptions): Promise<MarkdownView>;
17
+ export declare interface MarkdownViewOptions {
18
+ renderer?: MarkdownRenderer
19
+ }
20
+ export declare interface MarkdownView {
21
+ html: string
22
+ frontmatter: Record<string, any>
23
+ }
24
+ /** Converts markdown source to HTML. */
25
+ export type MarkdownRenderer = (source: string) => string | Promise<string>;
@@ -0,0 +1,39 @@
1
+ import { parseFrontmatter, parseMarkdown } from "./internal-markdown.js";
2
+ export function isMarkdownPath(filePath) {
3
+ return filePath.endsWith(".md") || filePath.endsWith(".markdown");
4
+ }
5
+ export function protectCodeBlocks(html) {
6
+ const protectedRanges = [];
7
+ collect(/<pre\b[^>]*>[\s\S]*?<\/pre>/gi);
8
+ const preRanges = [...protectedRanges];
9
+ collect(/<code\b[^>]*>[\s\S]*?<\/code>/gi, preRanges);
10
+ function collect(re, skipWithin) {
11
+ let m;
12
+ while ((m = re.exec(html)) !== null) {
13
+ const start = m.index, end = start + m[0].length;
14
+ if (skipWithin?.some((r) => start >= r.start && end <= r.end))
15
+ continue;
16
+ protectedRanges.push({ start, end });
17
+ }
18
+ }
19
+ if (protectedRanges.length === 0)
20
+ return html;
21
+ protectedRanges.sort((a, b) => a.start - b.start);
22
+ let out = "", cursor = 0;
23
+ for (const range of protectedRanges) {
24
+ if (range.start < cursor)
25
+ continue;
26
+ out += html.slice(cursor, range.start);
27
+ out += `@raw${html.slice(range.start, range.end)}@endraw`;
28
+ cursor = range.end;
29
+ }
30
+ out += html.slice(cursor);
31
+ return out;
32
+ }
33
+ export async function renderMarkdownView(source, options = {}) {
34
+ const { data, content } = parseFrontmatter(source), html = await (options.renderer ?? ((md) => parseMarkdown(md)))(content);
35
+ return {
36
+ html: protectCodeBlocks(html),
37
+ frontmatter: data ?? {}
38
+ };
39
+ }
@@ -4,6 +4,7 @@ import { serve } from "bun";
4
4
  import { loadManifest } from "./manifest.js";
5
5
  import { hydrateTemplateStream, hydrateFragment } from "./template-hydrator.js";
6
6
  import { extractLayoutMetadata } from "./app-shell.js";
7
+ import { patternToRegex } from "stx-router";
7
8
  const MIME_TYPES = {
8
9
  ".js": "application/javascript",
9
10
  ".mjs": "application/javascript",
@@ -55,11 +56,8 @@ export async function startProductionServer(options = {}) {
55
56
  const exactRoutes = new Map, paramRoutes = [];
56
57
  for (const route of manifest.routes)
57
58
  if (route.hasParams) {
58
- const paramNames = [], regexStr = route.pattern.replace(/:([^/]+)/g, (_, name) => {
59
- paramNames.push(name);
60
- return "([^/]+)";
61
- });
62
- paramRoutes.push({ regex: new RegExp(`^${regexStr}$`), route, paramNames });
59
+ const { regex, params: paramNames } = patternToRegex(route.pattern);
60
+ paramRoutes.push({ regex, route, paramNames });
63
61
  } else
64
62
  exactRoutes.set(route.pattern, route);
65
63
  const server = serve({
package/dist/render.d.ts CHANGED
@@ -42,6 +42,7 @@ export declare function renderTemplateString(content: string, filePath: string,
42
42
  // Types
43
43
  // ============================================================================
44
44
  export declare interface RenderOptions {
45
+ markdownRenderer?: (source: string) => string | Promise<string>
45
46
  context?: Record<string, unknown>
46
47
  options?: Partial<StxOptions>
47
48
  processClientScripts?: boolean
package/dist/render.js CHANGED
@@ -4,6 +4,7 @@ import { extractVariables } from "./variable-extractor.js";
4
4
  import { injectRouterScript, processDirectives } from "./process.js";
5
5
  import { extractBridgeData, processClientScript } from "./client-script.js";
6
6
  import { findSfcTemplateBlock } from "./sfc-template.js";
7
+ import { isMarkdownPath, renderMarkdownView } from "./markdown-view.js";
7
8
  function parseTemplate(content) {
8
9
  let workingContent = content;
9
10
  const templateBlock = findSfcTemplateBlock(content);
@@ -69,7 +70,17 @@ export async function renderTemplate(filePath, renderOptions = {}) {
69
70
  const resolvedPath = resolve(filePath), file = Bun.file(resolvedPath);
70
71
  if (!await file.exists())
71
72
  throw Error(`Template file not found: ${resolvedPath}`);
72
- const content = await file.text();
73
+ let content = await file.text(), markdownFrontmatter = null;
74
+ if (isMarkdownPath(resolvedPath)) {
75
+ const view = await renderMarkdownView(content, { renderer: renderOptions.markdownRenderer });
76
+ content = view.html;
77
+ markdownFrontmatter = view.frontmatter;
78
+ }
79
+ if (markdownFrontmatter)
80
+ renderOptions = {
81
+ ...renderOptions,
82
+ context: { ...markdownFrontmatter, ...renderOptions.context || {} }
83
+ };
73
84
  if (renderOptions.layout) {
74
85
  const layoutPath = resolve(renderOptions.layout), layoutFile = Bun.file(layoutPath);
75
86
  if (!await layoutFile.exists())
package/dist/ssr.js CHANGED
@@ -3,6 +3,7 @@ import path from "node:path";
3
3
  import { errorLogger } from "./error-handling.js";
4
4
  import { processDirectives } from "./process.js";
5
5
  import { extractVariables } from "./utils.js";
6
+ import { patternToRegex } from "stx-router";
6
7
 
7
8
  export class MemorySessionStore {
8
9
  store = new Map;
@@ -180,10 +181,7 @@ export function createApp(config = {}) {
180
181
  for (const route of routes) {
181
182
  if (route.method !== method && route.method !== "*")
182
183
  continue;
183
- const paramNames = [], regexPattern = route.pattern.replace(/\/:(\w+)/g, (_, name) => {
184
- paramNames.push(name);
185
- return "/([^/]+)";
186
- }).replace(/\//g, "\\/"), regex = new RegExp(`^${regexPattern}$`), match = pathname.match(regex);
184
+ const { regex, params: paramNames } = patternToRegex(route.pattern), match = pathname.match(regex);
187
185
  if (match) {
188
186
  const params = {};
189
187
  paramNames.forEach((name, i) => {
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@stacksjs/stx",
3
3
  "type": "module",
4
- "version": "0.2.148",
4
+ "version": "0.2.150",
5
5
  "description": "A performant UI Framework. Powered by Bun.",
6
6
  "author": "Chris Breuer <chris@stacksjs.org>",
7
7
  "license": "MIT",
@@ -117,12 +117,12 @@
117
117
  "dependencies": {
118
118
  "@cwcss/crosswind": "^0.2.14",
119
119
  "@stacksjs/clapp": "^0.2.0",
120
- "@stacksjs/desktop": "0.2.148",
121
- "@stacksjs/sanitizer": "0.2.148",
120
+ "@stacksjs/desktop": "0.2.150",
121
+ "@stacksjs/sanitizer": "0.2.150",
122
122
  "@stacksjs/ts-i18n": "^0.1.11",
123
- "bun-plugin-stx": "0.2.148",
123
+ "bun-plugin-stx": "0.2.150",
124
124
  "bunfig": "^0.15.17",
125
- "stx-router": "0.2.148",
125
+ "stx-router": "0.2.150",
126
126
  "ts-broadcasting": "^0.0.9",
127
127
  "ts-images": "^0.1.9",
128
128
  "ts-syntax-highlighter": "^0.2.1"