safe-mdx 1.7.0 → 1.9.0

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.
Files changed (43) hide show
  1. package/README.md +56 -0
  2. package/dist/esm-parser.test.js +5 -0
  3. package/dist/esm-parser.test.js.map +1 -1
  4. package/dist/html/html-and-md.test.js +14 -41
  5. package/dist/html/html-and-md.test.js.map +1 -1
  6. package/dist/html/html-to-mdx-ast.d.ts +26 -1
  7. package/dist/html/html-to-mdx-ast.d.ts.map +1 -1
  8. package/dist/html/html-to-mdx-ast.js +40 -0
  9. package/dist/html/html-to-mdx-ast.js.map +1 -1
  10. package/dist/incremental-parse.d.ts +41 -0
  11. package/dist/incremental-parse.d.ts.map +1 -0
  12. package/dist/incremental-parse.js +139 -0
  13. package/dist/incremental-parse.js.map +1 -0
  14. package/dist/incremental-parse.test.d.ts +2 -0
  15. package/dist/incremental-parse.test.d.ts.map +1 -0
  16. package/dist/incremental-parse.test.js +299 -0
  17. package/dist/incremental-parse.test.js.map +1 -0
  18. package/dist/markdown-html.test.d.ts +2 -0
  19. package/dist/markdown-html.test.d.ts.map +1 -0
  20. package/dist/markdown-html.test.js +129 -0
  21. package/dist/markdown-html.test.js.map +1 -0
  22. package/dist/markdown.d.ts +3 -0
  23. package/dist/markdown.d.ts.map +1 -0
  24. package/dist/markdown.js +4 -0
  25. package/dist/markdown.js.map +1 -0
  26. package/dist/parse.d.ts +9 -2
  27. package/dist/parse.d.ts.map +1 -1
  28. package/dist/parse.js +34 -20
  29. package/dist/parse.js.map +1 -1
  30. package/dist/safe-mdx.d.ts.map +1 -1
  31. package/dist/safe-mdx.js +6 -24
  32. package/dist/safe-mdx.js.map +1 -1
  33. package/package.json +9 -1
  34. package/src/esm-parser.test.ts +7 -1
  35. package/src/html/html-and-md.test.ts +15 -47
  36. package/src/html/html-to-mdx-ast.ts +53 -1
  37. package/src/incremental-parse.test.ts +315 -0
  38. package/src/incremental-parse.ts +219 -0
  39. package/src/markdown-html.test.tsx +144 -0
  40. package/src/markdown.ts +4 -0
  41. package/src/parse.ts +46 -20
  42. package/src/safe-mdx.test.tsx +2 -0
  43. package/src/safe-mdx.tsx +6 -26
package/src/parse.ts CHANGED
@@ -5,9 +5,9 @@ import { Root, RootContent } from 'mdast'
5
5
  import { remark } from 'remark'
6
6
  import remarkGfm from 'remark-gfm'
7
7
  import remarkMdx from 'remark-mdx'
8
- import { parseHtmlToMdxAst, remarkMdxJsxNormalize } from './html/html-to-mdx-ast.ts'
8
+ import { remarkMdxJsxNormalize } from './html/remark-mdx-jsx-normalize.ts'
9
9
 
10
- export { parseHtmlToMdxAst, remarkMdxJsxNormalize }
10
+ export { remarkMdxJsxNormalize }
11
11
 
12
12
  /* ── Import extraction ──────────────────────────────────────────────── */
13
13
 
@@ -34,7 +34,7 @@ export function extractImports(ast: Root): MdxImport[] {
34
34
 
35
35
  for (const node of ast.children) {
36
36
  if (node.type !== 'mdxjsEsm') continue
37
- const estree = (node as any).data?.estree
37
+ const estree = node.data?.estree
38
38
  if (!estree) continue
39
39
 
40
40
  for (const statement of estree.body) {
@@ -70,6 +70,38 @@ export function mdxParse(code: string) {
70
70
  return file.data.ast as Root
71
71
  }
72
72
 
73
+ export type MdxProcessorOptions = {
74
+ /** Extra remark plugins appended after safe-mdx's default MDX, frontmatter, and GFM parsers. */
75
+ remarkPlugins?: any[]
76
+ }
77
+
78
+ export function createMdxProcessor({
79
+ remarkPlugins = [],
80
+ }: MdxProcessorOptions = {}) {
81
+ const processor = remark()
82
+ .use(remarkMdx)
83
+ .use(remarkFrontmatter, ['yaml', 'toml'])
84
+ .use(remarkGfm)
85
+
86
+ for (const plugin of remarkPlugins) {
87
+ if (Array.isArray(plugin)) {
88
+ processor.use(plugin[0], ...plugin.slice(1))
89
+ } else {
90
+ processor.use(plugin)
91
+ }
92
+ }
93
+
94
+ return processor
95
+ .use(remarkMarkAndUnravel)
96
+ .use(() => {
97
+ return (tree, file) => {
98
+ file.data.ast = tree
99
+ }
100
+ })
101
+ }
102
+
103
+ export type MdxProcessor = ReturnType<typeof createMdxProcessor>
104
+
73
105
  /**
74
106
  * https://github.com/mdx-js/mdx/blob/b3351fadcb6f78833a72757b7135dcfb8ab646fe/packages/mdx/lib/plugin/remark-mark-and-unravel.js
75
107
  * A tiny plugin that unravels `<p><h1>x</h1></p>` but also
@@ -181,7 +213,6 @@ export function resolveModulePath(
181
213
  } else if (source.startsWith('./') || source.startsWith('../')) {
182
214
  // Relative import: resolve from baseUrl
183
215
  const joined = joinPaths(baseUrl, source)
184
- if (!joined) return undefined // .. escaped above root
185
216
  normalized = joined
186
217
  } else {
187
218
  // Bare specifier (npm package etc.) — not resolvable from glob
@@ -200,23 +231,27 @@ export function resolveModulePath(
200
231
  }
201
232
 
202
233
  /** Simple path join that normalizes `./a/b/../c` segments.
203
- * Both inputs and output use `./` prefix (matching Vite glob key format).
204
- * Returns `undefined` if `..` escapes above the project root. */
205
- function joinPaths(base: string, relative: string): string | undefined {
234
+ * Outputs inside-root paths with `./` prefix and outside-root paths with
235
+ * leading `../` segments, matching Vite glob keys from explicit imports. */
236
+ function joinPaths(base: string, relative: string): string {
206
237
  // Strip ./ prefix and trailing /
207
238
  const baseParts = base.replace(/^\.\//, '').replace(/\/$/, '').split('/').filter(Boolean)
208
239
  const relParts = relative.replace(/^\.\//, '').split('/').filter(Boolean)
209
240
 
210
241
  for (const part of relParts) {
211
242
  if (part === '..') {
212
- if (baseParts.length === 0) return undefined // escaped above root
213
- baseParts.pop()
243
+ if (baseParts.length > 0 && baseParts[baseParts.length - 1] !== '..') {
244
+ baseParts.pop()
245
+ } else {
246
+ baseParts.push('..')
247
+ }
214
248
  } else if (part !== '.') {
215
249
  baseParts.push(part)
216
250
  }
217
251
  }
218
252
 
219
- return './' + baseParts.join('/')
253
+ const joined = baseParts.join('/')
254
+ return joined.startsWith('../') ? joined : './' + joined
220
255
  }
221
256
 
222
257
  export type LazyGlob = Record<string, () => Promise<Record<string, any>>>
@@ -262,13 +297,4 @@ export async function resolveModules({
262
297
  return result
263
298
  }
264
299
 
265
- const mdxProcessor = remark()
266
- .use(remarkMdx)
267
- .use(remarkFrontmatter, ['yaml', 'toml'])
268
- .use(remarkGfm)
269
- .use(remarkMarkAndUnravel)
270
- .use(() => {
271
- return (tree, file) => {
272
- file.data.ast = tree
273
- }
274
- })
300
+ export const mdxProcessor = createMdxProcessor()
@@ -4197,3 +4197,5 @@ test('scope with tagged template literal without generate', () => {
4197
4197
  expect(errors).toMatchInlineSnapshot(`[]`)
4198
4198
  expect(html).toMatchInlineSnapshot(`"hello WORLD"`)
4199
4199
  })
4200
+
4201
+
package/src/safe-mdx.tsx CHANGED
@@ -10,8 +10,7 @@ import { Fragment, ReactNode } from 'react'
10
10
  import { DynamicEsmComponent } from 'safe-mdx/client'
11
11
  import { extractComponentInfo, parseEsmImports } from './esm-parser.ts'
12
12
  import { resolveModulePath, type EagerModules } from './parse.ts'
13
- import { htmlToMdxAst } from './html/html-to-mdx-ast.ts'
14
- import { validHtmlElements, nativeTags } from './html/valid-html-elements.ts'
13
+ import { nativeTags } from './html/valid-html-elements.ts'
15
14
 
16
15
  export type MyRootContent = RootContent | Root
17
16
 
@@ -1081,30 +1080,11 @@ export class MdastToJsx {
1081
1080
  return []
1082
1081
  }
1083
1082
  case 'html': {
1084
- const start = node.position?.start?.offset
1085
- const end = node.position?.end?.offset
1086
- const text = this.str.slice(start, end)
1087
- if (!text) {
1088
- return []
1089
- }
1090
-
1091
- // Parse HTML to MDX AST using the new approach - always returns an array
1092
- const mdxAst = htmlToMdxAst({
1093
- html: text,
1094
- parentType: parentType || 'root',
1095
- convertTagName: ({ tagName }) => {
1096
- const lowerTag = tagName.toLowerCase()
1097
- // Only keep valid HTML elements
1098
- if (validHtmlElements.has(lowerTag)) {
1099
- return lowerTag
1100
- }
1101
- // Return empty string for non-HTML elements
1102
- return ''
1103
- }
1104
- })
1105
-
1106
- // Process the MDX AST nodes
1107
- return mdxAst.map(child => this.mdastTransformer(child, 'html'))
1083
+ // html nodes appear when rendering plain markdown (not MDX) without
1084
+ // the remarkHtmlToMdx pre-processing plugin. They are intentionally
1085
+ // ignored here use remarkHtmlToMdx from 'safe-mdx/markdown' to convert
1086
+ // them to mdxJsx nodes before passing the AST to MdastToJsx.
1087
+ return []
1108
1088
  }
1109
1089
  case 'imageReference': {
1110
1090
  return []