@ox-content/vite-plugin-react 0.0.1-alpha.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.
- package/dist/index.cjs +465 -0
- package/dist/index.d.cts +85 -0
- package/dist/index.d.ts +85 -0
- package/dist/index.js +429 -0
- package/package.json +59 -0
package/dist/index.cjs
ADDED
|
@@ -0,0 +1,465 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __create = Object.create;
|
|
3
|
+
var __defProp = Object.defineProperty;
|
|
4
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
5
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
6
|
+
var __getProtoOf = Object.getPrototypeOf;
|
|
7
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
8
|
+
var __export = (target, all) => {
|
|
9
|
+
for (var name in all)
|
|
10
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
11
|
+
};
|
|
12
|
+
var __copyProps = (to, from, except, desc) => {
|
|
13
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
14
|
+
for (let key of __getOwnPropNames(from))
|
|
15
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
16
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
17
|
+
}
|
|
18
|
+
return to;
|
|
19
|
+
};
|
|
20
|
+
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
|
|
21
|
+
// If the importer is in node compatibility mode or this is not an ESM
|
|
22
|
+
// file that has been converted to a CommonJS file using a Babel-
|
|
23
|
+
// compatible transform (i.e. "__esModule" has not been set), then set
|
|
24
|
+
// "default" to the CommonJS "module.exports" for node compatibility.
|
|
25
|
+
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
|
|
26
|
+
mod
|
|
27
|
+
));
|
|
28
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
29
|
+
|
|
30
|
+
// src/index.ts
|
|
31
|
+
var index_exports = {};
|
|
32
|
+
__export(index_exports, {
|
|
33
|
+
oxContent: () => import_vite_plugin_ox_content3.oxContent,
|
|
34
|
+
oxContentReact: () => oxContentReact
|
|
35
|
+
});
|
|
36
|
+
module.exports = __toCommonJS(index_exports);
|
|
37
|
+
var fs = __toESM(require("fs"), 1);
|
|
38
|
+
var path2 = __toESM(require("path"), 1);
|
|
39
|
+
var import_vite_plugin_ox_content2 = require("vite-plugin-ox-content");
|
|
40
|
+
|
|
41
|
+
// src/transform.ts
|
|
42
|
+
var path = __toESM(require("path"), 1);
|
|
43
|
+
var import_vite_plugin_ox_content = require("vite-plugin-ox-content");
|
|
44
|
+
var COMPONENT_REGEX = /<([A-Z][a-zA-Z0-9]*)\s*([^>]*?)\s*(?:\/>|>(?:[\s\S]*?)<\/\1>)/g;
|
|
45
|
+
var PROP_REGEX = /([a-zA-Z0-9-]+)(?:=(?:"([^"]*)"|'([^']*)'|{([^}]*)}))?/g;
|
|
46
|
+
async function transformMarkdownWithReact(code, id, options) {
|
|
47
|
+
const components = options.components;
|
|
48
|
+
const usedComponents = [];
|
|
49
|
+
const slots = [];
|
|
50
|
+
let slotIndex = 0;
|
|
51
|
+
const { content: markdownContent, frontmatter } = extractFrontmatter(code);
|
|
52
|
+
let processedContent = markdownContent;
|
|
53
|
+
let match;
|
|
54
|
+
while ((match = COMPONENT_REGEX.exec(markdownContent)) !== null) {
|
|
55
|
+
const [fullMatch, componentName, propsString] = match;
|
|
56
|
+
if (componentName in components) {
|
|
57
|
+
if (!usedComponents.includes(componentName)) {
|
|
58
|
+
usedComponents.push(componentName);
|
|
59
|
+
}
|
|
60
|
+
const props = parseProps(propsString);
|
|
61
|
+
const slotId = `__ox_slot_${slotIndex++}__`;
|
|
62
|
+
slots.push({
|
|
63
|
+
name: componentName,
|
|
64
|
+
props,
|
|
65
|
+
position: match.index,
|
|
66
|
+
id: slotId
|
|
67
|
+
});
|
|
68
|
+
processedContent = processedContent.replace(
|
|
69
|
+
fullMatch,
|
|
70
|
+
`<div data-ox-slot="${slotId}"></div>`
|
|
71
|
+
);
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
const transformed = await (0, import_vite_plugin_ox_content.transformMarkdown)(processedContent, id, {
|
|
75
|
+
srcDir: options.srcDir,
|
|
76
|
+
outDir: options.outDir,
|
|
77
|
+
base: options.base,
|
|
78
|
+
ssg: { enabled: false, extension: ".html", clean: false, bare: false, generateOgImage: false },
|
|
79
|
+
gfm: options.gfm,
|
|
80
|
+
frontmatter: false,
|
|
81
|
+
toc: options.toc,
|
|
82
|
+
tocMaxDepth: options.tocMaxDepth,
|
|
83
|
+
footnotes: true,
|
|
84
|
+
tables: true,
|
|
85
|
+
taskLists: true,
|
|
86
|
+
strikethrough: true,
|
|
87
|
+
highlight: false,
|
|
88
|
+
highlightTheme: "github-dark",
|
|
89
|
+
mermaid: false,
|
|
90
|
+
ogImage: false,
|
|
91
|
+
ogImageOptions: {},
|
|
92
|
+
transformers: [],
|
|
93
|
+
docs: false,
|
|
94
|
+
search: { enabled: false, limit: 10, prefix: true, placeholder: "Search...", hotkey: "k" }
|
|
95
|
+
});
|
|
96
|
+
const jsxCode = generateReactModule(
|
|
97
|
+
transformed.html,
|
|
98
|
+
usedComponents,
|
|
99
|
+
slots,
|
|
100
|
+
frontmatter,
|
|
101
|
+
options,
|
|
102
|
+
id
|
|
103
|
+
);
|
|
104
|
+
return {
|
|
105
|
+
code: jsxCode,
|
|
106
|
+
map: null,
|
|
107
|
+
usedComponents,
|
|
108
|
+
frontmatter
|
|
109
|
+
};
|
|
110
|
+
}
|
|
111
|
+
function extractFrontmatter(content) {
|
|
112
|
+
const frontmatterRegex = /^---\n([\s\S]*?)\n---\n/;
|
|
113
|
+
const match = frontmatterRegex.exec(content);
|
|
114
|
+
if (!match) {
|
|
115
|
+
return { content, frontmatter: {} };
|
|
116
|
+
}
|
|
117
|
+
const frontmatterStr = match[1];
|
|
118
|
+
const frontmatter = {};
|
|
119
|
+
for (const line of frontmatterStr.split("\n")) {
|
|
120
|
+
const colonIndex = line.indexOf(":");
|
|
121
|
+
if (colonIndex > 0) {
|
|
122
|
+
const key = line.slice(0, colonIndex).trim();
|
|
123
|
+
let value = line.slice(colonIndex + 1).trim();
|
|
124
|
+
try {
|
|
125
|
+
value = JSON.parse(value);
|
|
126
|
+
} catch {
|
|
127
|
+
if (typeof value === "string" && value.startsWith('"') && value.endsWith('"')) {
|
|
128
|
+
value = value.slice(1, -1);
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
frontmatter[key] = value;
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
return { content: content.slice(match[0].length), frontmatter };
|
|
135
|
+
}
|
|
136
|
+
function parseProps(propsString) {
|
|
137
|
+
const props = {};
|
|
138
|
+
if (!propsString) return props;
|
|
139
|
+
let match;
|
|
140
|
+
while ((match = PROP_REGEX.exec(propsString)) !== null) {
|
|
141
|
+
const [, name, doubleQuoted, singleQuoted, braceValue] = match;
|
|
142
|
+
if (name) {
|
|
143
|
+
if (doubleQuoted !== void 0) props[name] = doubleQuoted;
|
|
144
|
+
else if (singleQuoted !== void 0) props[name] = singleQuoted;
|
|
145
|
+
else if (braceValue !== void 0) {
|
|
146
|
+
try {
|
|
147
|
+
props[name] = JSON.parse(braceValue);
|
|
148
|
+
} catch {
|
|
149
|
+
props[name] = braceValue;
|
|
150
|
+
}
|
|
151
|
+
} else props[name] = true;
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
return props;
|
|
155
|
+
}
|
|
156
|
+
function generateReactModule(content, usedComponents, slots, frontmatter, options, id) {
|
|
157
|
+
const mdDir = path.dirname(id);
|
|
158
|
+
const root = options.root || process.cwd();
|
|
159
|
+
const imports = usedComponents.map((name) => {
|
|
160
|
+
const componentPath = options.components[name];
|
|
161
|
+
if (!componentPath) return "";
|
|
162
|
+
const absolutePath = path.resolve(root, componentPath.replace(/^\.\//, ""));
|
|
163
|
+
const relativePath = path.relative(mdDir, absolutePath).replace(/\\/g, "/");
|
|
164
|
+
const importPath = relativePath.startsWith(".") ? relativePath : "./" + relativePath;
|
|
165
|
+
return `import ${name} from '${importPath}';`;
|
|
166
|
+
}).filter(Boolean).join("\n");
|
|
167
|
+
return `
|
|
168
|
+
import React, { useState, useEffect, createElement } from 'react';
|
|
169
|
+
${imports}
|
|
170
|
+
|
|
171
|
+
const frontmatter = ${JSON.stringify(frontmatter)};
|
|
172
|
+
const rawHtml = ${JSON.stringify(content)};
|
|
173
|
+
const slots = ${JSON.stringify(slots)};
|
|
174
|
+
|
|
175
|
+
const components = { ${usedComponents.join(", ")} };
|
|
176
|
+
|
|
177
|
+
export default function MarkdownContent() {
|
|
178
|
+
const [mounted, setMounted] = useState(false);
|
|
179
|
+
|
|
180
|
+
useEffect(() => {
|
|
181
|
+
setMounted(true);
|
|
182
|
+
}, []);
|
|
183
|
+
|
|
184
|
+
if (!mounted) {
|
|
185
|
+
return createElement('div', {
|
|
186
|
+
className: 'ox-content',
|
|
187
|
+
dangerouslySetInnerHTML: { __html: rawHtml }
|
|
188
|
+
});
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
return createElement('div', { className: 'ox-content' },
|
|
192
|
+
slots.map((slot) => {
|
|
193
|
+
const Component = components[slot.name];
|
|
194
|
+
return Component ? createElement(Component, { key: slot.id, ...slot.props }) : null;
|
|
195
|
+
})
|
|
196
|
+
);
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
export { frontmatter };
|
|
200
|
+
`;
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
// src/environment.ts
|
|
204
|
+
function createReactMarkdownEnvironment(mode, options) {
|
|
205
|
+
const isSSR = mode === "ssr";
|
|
206
|
+
return {
|
|
207
|
+
build: {
|
|
208
|
+
outDir: isSSR ? `${options.outDir}/.ox-content/ssr` : `${options.outDir}/.ox-content/client`,
|
|
209
|
+
ssr: isSSR,
|
|
210
|
+
rollupOptions: {
|
|
211
|
+
output: {
|
|
212
|
+
format: "esm",
|
|
213
|
+
entryFileNames: isSSR ? "[name].js" : "[name].[hash].js"
|
|
214
|
+
}
|
|
215
|
+
},
|
|
216
|
+
...isSSR && { target: "node18", minify: false }
|
|
217
|
+
},
|
|
218
|
+
resolve: {
|
|
219
|
+
conditions: isSSR ? ["node", "import"] : ["browser", "import"]
|
|
220
|
+
},
|
|
221
|
+
optimizeDeps: {
|
|
222
|
+
include: isSSR ? [] : ["react", "react-dom"],
|
|
223
|
+
exclude: ["vite-plugin-ox-content", "vite-plugin-ox-content-react"]
|
|
224
|
+
}
|
|
225
|
+
};
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
// src/index.ts
|
|
229
|
+
var import_vite_plugin_ox_content3 = require("vite-plugin-ox-content");
|
|
230
|
+
function oxContentReact(options = {}) {
|
|
231
|
+
const resolved = resolveReactOptions(options);
|
|
232
|
+
let componentMap = /* @__PURE__ */ new Map();
|
|
233
|
+
let config;
|
|
234
|
+
if (typeof options.components === "object" && !Array.isArray(options.components)) {
|
|
235
|
+
componentMap = new Map(Object.entries(options.components));
|
|
236
|
+
}
|
|
237
|
+
const reactTransformPlugin = {
|
|
238
|
+
name: "ox-content:react-transform",
|
|
239
|
+
enforce: "pre",
|
|
240
|
+
async configResolved(resolvedConfig) {
|
|
241
|
+
config = resolvedConfig;
|
|
242
|
+
const componentsOption = options.components;
|
|
243
|
+
if (componentsOption) {
|
|
244
|
+
const resolvedComponents = await resolveComponentsGlob(
|
|
245
|
+
componentsOption,
|
|
246
|
+
config.root
|
|
247
|
+
);
|
|
248
|
+
componentMap = new Map(Object.entries(resolvedComponents));
|
|
249
|
+
}
|
|
250
|
+
},
|
|
251
|
+
async transform(code, id) {
|
|
252
|
+
if (!id.endsWith(".md")) {
|
|
253
|
+
return null;
|
|
254
|
+
}
|
|
255
|
+
const result = await transformMarkdownWithReact(code, id, {
|
|
256
|
+
...resolved,
|
|
257
|
+
components: Object.fromEntries(componentMap),
|
|
258
|
+
root: config.root
|
|
259
|
+
});
|
|
260
|
+
return {
|
|
261
|
+
code: result.code,
|
|
262
|
+
map: result.map
|
|
263
|
+
};
|
|
264
|
+
}
|
|
265
|
+
};
|
|
266
|
+
const reactEnvironmentPlugin = {
|
|
267
|
+
name: "ox-content:react-environment",
|
|
268
|
+
config() {
|
|
269
|
+
const envOptions = {
|
|
270
|
+
...resolved,
|
|
271
|
+
components: Object.fromEntries(componentMap)
|
|
272
|
+
};
|
|
273
|
+
return {
|
|
274
|
+
environments: {
|
|
275
|
+
oxcontent_ssr: createReactMarkdownEnvironment("ssr", envOptions),
|
|
276
|
+
oxcontent_client: createReactMarkdownEnvironment("client", envOptions)
|
|
277
|
+
}
|
|
278
|
+
};
|
|
279
|
+
},
|
|
280
|
+
resolveId(id) {
|
|
281
|
+
if (id === "virtual:ox-content-react/runtime") {
|
|
282
|
+
return "\0virtual:ox-content-react/runtime";
|
|
283
|
+
}
|
|
284
|
+
if (id === "virtual:ox-content-react/components") {
|
|
285
|
+
return "\0virtual:ox-content-react/components";
|
|
286
|
+
}
|
|
287
|
+
return null;
|
|
288
|
+
},
|
|
289
|
+
load(id) {
|
|
290
|
+
if (id === "\0virtual:ox-content-react/runtime") {
|
|
291
|
+
return generateRuntimeModule();
|
|
292
|
+
}
|
|
293
|
+
if (id === "\0virtual:ox-content-react/components") {
|
|
294
|
+
return generateComponentsModule(componentMap);
|
|
295
|
+
}
|
|
296
|
+
return null;
|
|
297
|
+
},
|
|
298
|
+
applyToEnvironment(environment) {
|
|
299
|
+
return ["oxcontent_ssr", "oxcontent_client", "client", "ssr"].includes(
|
|
300
|
+
environment.name
|
|
301
|
+
);
|
|
302
|
+
}
|
|
303
|
+
};
|
|
304
|
+
const reactHmrPlugin = {
|
|
305
|
+
name: "ox-content:react-hmr",
|
|
306
|
+
apply: "serve",
|
|
307
|
+
handleHotUpdate({ file, server, modules }) {
|
|
308
|
+
const isComponent = Array.from(componentMap.values()).some(
|
|
309
|
+
(path3) => file.endsWith(path3.replace(/^\.\//, ""))
|
|
310
|
+
);
|
|
311
|
+
if (isComponent) {
|
|
312
|
+
const mdModules = Array.from(
|
|
313
|
+
server.moduleGraph.idToModuleMap.values()
|
|
314
|
+
).filter((mod) => mod.file?.endsWith(".md"));
|
|
315
|
+
if (mdModules.length > 0) {
|
|
316
|
+
server.ws.send({
|
|
317
|
+
type: "custom",
|
|
318
|
+
event: "ox-content:react-update",
|
|
319
|
+
data: { file }
|
|
320
|
+
});
|
|
321
|
+
return [...modules, ...mdModules];
|
|
322
|
+
}
|
|
323
|
+
}
|
|
324
|
+
return modules;
|
|
325
|
+
}
|
|
326
|
+
};
|
|
327
|
+
const basePlugins = (0, import_vite_plugin_ox_content2.oxContent)(options);
|
|
328
|
+
const environmentPlugin = basePlugins.find((p) => p.name === "ox-content:environment");
|
|
329
|
+
return [
|
|
330
|
+
reactTransformPlugin,
|
|
331
|
+
reactEnvironmentPlugin,
|
|
332
|
+
reactHmrPlugin,
|
|
333
|
+
...environmentPlugin ? [environmentPlugin] : []
|
|
334
|
+
];
|
|
335
|
+
}
|
|
336
|
+
function resolveReactOptions(options) {
|
|
337
|
+
return {
|
|
338
|
+
srcDir: options.srcDir ?? "docs",
|
|
339
|
+
outDir: options.outDir ?? "dist",
|
|
340
|
+
base: options.base ?? "/",
|
|
341
|
+
gfm: options.gfm ?? true,
|
|
342
|
+
frontmatter: options.frontmatter ?? true,
|
|
343
|
+
toc: options.toc ?? true,
|
|
344
|
+
tocMaxDepth: options.tocMaxDepth ?? 3,
|
|
345
|
+
jsxRuntime: options.jsxRuntime ?? "automatic"
|
|
346
|
+
};
|
|
347
|
+
}
|
|
348
|
+
function generateRuntimeModule() {
|
|
349
|
+
return `
|
|
350
|
+
import React, { useState, useEffect } from 'react';
|
|
351
|
+
|
|
352
|
+
export function OxContentRenderer({ content, components = {} }) {
|
|
353
|
+
const [mounted, setMounted] = useState(false);
|
|
354
|
+
|
|
355
|
+
useEffect(() => {
|
|
356
|
+
setMounted(true);
|
|
357
|
+
}, []);
|
|
358
|
+
|
|
359
|
+
if (!content) return null;
|
|
360
|
+
|
|
361
|
+
const { html, frontmatter, slots } = content;
|
|
362
|
+
|
|
363
|
+
if (!mounted) {
|
|
364
|
+
return React.createElement('div', {
|
|
365
|
+
className: 'ox-content',
|
|
366
|
+
dangerouslySetInnerHTML: { __html: html },
|
|
367
|
+
});
|
|
368
|
+
}
|
|
369
|
+
|
|
370
|
+
return React.createElement('div', { className: 'ox-content' },
|
|
371
|
+
slots.map((slot) => {
|
|
372
|
+
const Component = components[slot.name];
|
|
373
|
+
return Component
|
|
374
|
+
? React.createElement(Component, { key: slot.id, ...slot.props })
|
|
375
|
+
: null;
|
|
376
|
+
})
|
|
377
|
+
);
|
|
378
|
+
}
|
|
379
|
+
|
|
380
|
+
export function useOxContent() {
|
|
381
|
+
return { OxContentRenderer };
|
|
382
|
+
}
|
|
383
|
+
`;
|
|
384
|
+
}
|
|
385
|
+
function generateComponentsModule(componentMap) {
|
|
386
|
+
const imports = [];
|
|
387
|
+
const exports2 = [];
|
|
388
|
+
componentMap.forEach((path3, name) => {
|
|
389
|
+
imports.push(`import ${name} from '${path3}';`);
|
|
390
|
+
exports2.push(` ${name},`);
|
|
391
|
+
});
|
|
392
|
+
return `
|
|
393
|
+
${imports.join("\n")}
|
|
394
|
+
|
|
395
|
+
export const components = {
|
|
396
|
+
${exports2.join("\n")}
|
|
397
|
+
};
|
|
398
|
+
|
|
399
|
+
export default components;
|
|
400
|
+
`;
|
|
401
|
+
}
|
|
402
|
+
async function resolveComponentsGlob(componentsOption, root) {
|
|
403
|
+
if (typeof componentsOption === "object" && !Array.isArray(componentsOption)) {
|
|
404
|
+
return componentsOption;
|
|
405
|
+
}
|
|
406
|
+
const patterns = Array.isArray(componentsOption) ? componentsOption : [componentsOption];
|
|
407
|
+
const result = {};
|
|
408
|
+
for (const pattern of patterns) {
|
|
409
|
+
const files = await globFiles(pattern, root);
|
|
410
|
+
for (const file of files) {
|
|
411
|
+
const baseName = path2.basename(file, path2.extname(file));
|
|
412
|
+
const componentName = toPascalCase(baseName);
|
|
413
|
+
const relativePath = "./" + path2.relative(root, file).replace(/\\/g, "/");
|
|
414
|
+
result[componentName] = relativePath;
|
|
415
|
+
}
|
|
416
|
+
}
|
|
417
|
+
return result;
|
|
418
|
+
}
|
|
419
|
+
async function globFiles(pattern, root) {
|
|
420
|
+
const files = [];
|
|
421
|
+
const isGlob = pattern.includes("*");
|
|
422
|
+
if (!isGlob) {
|
|
423
|
+
const fullPath = path2.resolve(root, pattern);
|
|
424
|
+
if (fs.existsSync(fullPath)) {
|
|
425
|
+
files.push(fullPath);
|
|
426
|
+
}
|
|
427
|
+
return files;
|
|
428
|
+
}
|
|
429
|
+
const parts = pattern.split("*");
|
|
430
|
+
const baseDir = path2.resolve(root, parts[0]);
|
|
431
|
+
const ext = parts[1] || "";
|
|
432
|
+
if (!fs.existsSync(baseDir)) {
|
|
433
|
+
return files;
|
|
434
|
+
}
|
|
435
|
+
if (pattern.includes("**")) {
|
|
436
|
+
await walkDir(baseDir, files, ext);
|
|
437
|
+
} else {
|
|
438
|
+
const entries = await fs.promises.readdir(baseDir, { withFileTypes: true });
|
|
439
|
+
for (const entry of entries) {
|
|
440
|
+
if (entry.isFile() && entry.name.endsWith(ext)) {
|
|
441
|
+
files.push(path2.join(baseDir, entry.name));
|
|
442
|
+
}
|
|
443
|
+
}
|
|
444
|
+
}
|
|
445
|
+
return files;
|
|
446
|
+
}
|
|
447
|
+
async function walkDir(dir, files, ext) {
|
|
448
|
+
const entries = await fs.promises.readdir(dir, { withFileTypes: true });
|
|
449
|
+
for (const entry of entries) {
|
|
450
|
+
const fullPath = path2.join(dir, entry.name);
|
|
451
|
+
if (entry.isDirectory()) {
|
|
452
|
+
await walkDir(fullPath, files, ext);
|
|
453
|
+
} else if (entry.isFile() && entry.name.endsWith(ext)) {
|
|
454
|
+
files.push(fullPath);
|
|
455
|
+
}
|
|
456
|
+
}
|
|
457
|
+
}
|
|
458
|
+
function toPascalCase(str) {
|
|
459
|
+
return str.replace(/[-_](\w)/g, (_, c) => c.toUpperCase()).replace(/^\w/, (c) => c.toUpperCase());
|
|
460
|
+
}
|
|
461
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
462
|
+
0 && (module.exports = {
|
|
463
|
+
oxContent,
|
|
464
|
+
oxContentReact
|
|
465
|
+
});
|
package/dist/index.d.cts
ADDED
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
import { PluginOption } from 'vite';
|
|
2
|
+
import { OxContentOptions } from 'vite-plugin-ox-content';
|
|
3
|
+
export { oxContent } from 'vite-plugin-ox-content';
|
|
4
|
+
|
|
5
|
+
type ComponentsMap = Record<string, string>;
|
|
6
|
+
/**
|
|
7
|
+
* Component registration options.
|
|
8
|
+
* Can be a map, a glob pattern, or an array of glob patterns.
|
|
9
|
+
*/
|
|
10
|
+
type ComponentsOption = ComponentsMap | string | string[];
|
|
11
|
+
interface ReactIntegrationOptions extends OxContentOptions {
|
|
12
|
+
/**
|
|
13
|
+
* Components to register for use in Markdown.
|
|
14
|
+
* Can be a map of names to paths, a glob pattern, or an array of globs.
|
|
15
|
+
* When using glob patterns, component names are derived from file names.
|
|
16
|
+
*
|
|
17
|
+
* @example
|
|
18
|
+
* ```ts
|
|
19
|
+
* // Glob pattern (recommended)
|
|
20
|
+
* components: './src/components/*.tsx'
|
|
21
|
+
*
|
|
22
|
+
* // Explicit map
|
|
23
|
+
* components: { Counter: './src/components/Counter.tsx' }
|
|
24
|
+
* ```
|
|
25
|
+
*/
|
|
26
|
+
components?: ComponentsOption;
|
|
27
|
+
jsxRuntime?: 'automatic' | 'classic';
|
|
28
|
+
}
|
|
29
|
+
interface ResolvedReactOptions {
|
|
30
|
+
srcDir: string;
|
|
31
|
+
outDir: string;
|
|
32
|
+
base: string;
|
|
33
|
+
gfm: boolean;
|
|
34
|
+
frontmatter: boolean;
|
|
35
|
+
toc: boolean;
|
|
36
|
+
tocMaxDepth: number;
|
|
37
|
+
components: ComponentsMap;
|
|
38
|
+
jsxRuntime: 'automatic' | 'classic';
|
|
39
|
+
root?: string;
|
|
40
|
+
}
|
|
41
|
+
interface ReactTransformResult {
|
|
42
|
+
code: string;
|
|
43
|
+
map: null;
|
|
44
|
+
usedComponents: string[];
|
|
45
|
+
frontmatter: Record<string, unknown>;
|
|
46
|
+
}
|
|
47
|
+
interface ComponentSlot {
|
|
48
|
+
name: string;
|
|
49
|
+
props: Record<string, unknown>;
|
|
50
|
+
position: number;
|
|
51
|
+
id: string;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* Vite Plugin for Ox Content React Integration
|
|
56
|
+
*
|
|
57
|
+
* Uses Vite's Environment API to enable embedding React components in Markdown.
|
|
58
|
+
*/
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* Creates the Ox Content React integration plugin.
|
|
62
|
+
*
|
|
63
|
+
* @example
|
|
64
|
+
* ```ts
|
|
65
|
+
* // vite.config.ts
|
|
66
|
+
* import { defineConfig } from 'vite';
|
|
67
|
+
* import react from '@vitejs/plugin-react';
|
|
68
|
+
* import { oxContentReact } from 'vite-plugin-ox-content-react';
|
|
69
|
+
*
|
|
70
|
+
* export default defineConfig({
|
|
71
|
+
* plugins: [
|
|
72
|
+
* react(),
|
|
73
|
+
* oxContentReact({
|
|
74
|
+
* srcDir: 'docs',
|
|
75
|
+
* components: {
|
|
76
|
+
* Counter: './src/components/Counter.tsx',
|
|
77
|
+
* },
|
|
78
|
+
* }),
|
|
79
|
+
* ],
|
|
80
|
+
* });
|
|
81
|
+
* ```
|
|
82
|
+
*/
|
|
83
|
+
declare function oxContentReact(options?: ReactIntegrationOptions): PluginOption[];
|
|
84
|
+
|
|
85
|
+
export { type ComponentSlot, type ComponentsMap, type ComponentsOption, type ReactIntegrationOptions, type ReactTransformResult, type ResolvedReactOptions, oxContentReact };
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
import { PluginOption } from 'vite';
|
|
2
|
+
import { OxContentOptions } from 'vite-plugin-ox-content';
|
|
3
|
+
export { oxContent } from 'vite-plugin-ox-content';
|
|
4
|
+
|
|
5
|
+
type ComponentsMap = Record<string, string>;
|
|
6
|
+
/**
|
|
7
|
+
* Component registration options.
|
|
8
|
+
* Can be a map, a glob pattern, or an array of glob patterns.
|
|
9
|
+
*/
|
|
10
|
+
type ComponentsOption = ComponentsMap | string | string[];
|
|
11
|
+
interface ReactIntegrationOptions extends OxContentOptions {
|
|
12
|
+
/**
|
|
13
|
+
* Components to register for use in Markdown.
|
|
14
|
+
* Can be a map of names to paths, a glob pattern, or an array of globs.
|
|
15
|
+
* When using glob patterns, component names are derived from file names.
|
|
16
|
+
*
|
|
17
|
+
* @example
|
|
18
|
+
* ```ts
|
|
19
|
+
* // Glob pattern (recommended)
|
|
20
|
+
* components: './src/components/*.tsx'
|
|
21
|
+
*
|
|
22
|
+
* // Explicit map
|
|
23
|
+
* components: { Counter: './src/components/Counter.tsx' }
|
|
24
|
+
* ```
|
|
25
|
+
*/
|
|
26
|
+
components?: ComponentsOption;
|
|
27
|
+
jsxRuntime?: 'automatic' | 'classic';
|
|
28
|
+
}
|
|
29
|
+
interface ResolvedReactOptions {
|
|
30
|
+
srcDir: string;
|
|
31
|
+
outDir: string;
|
|
32
|
+
base: string;
|
|
33
|
+
gfm: boolean;
|
|
34
|
+
frontmatter: boolean;
|
|
35
|
+
toc: boolean;
|
|
36
|
+
tocMaxDepth: number;
|
|
37
|
+
components: ComponentsMap;
|
|
38
|
+
jsxRuntime: 'automatic' | 'classic';
|
|
39
|
+
root?: string;
|
|
40
|
+
}
|
|
41
|
+
interface ReactTransformResult {
|
|
42
|
+
code: string;
|
|
43
|
+
map: null;
|
|
44
|
+
usedComponents: string[];
|
|
45
|
+
frontmatter: Record<string, unknown>;
|
|
46
|
+
}
|
|
47
|
+
interface ComponentSlot {
|
|
48
|
+
name: string;
|
|
49
|
+
props: Record<string, unknown>;
|
|
50
|
+
position: number;
|
|
51
|
+
id: string;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* Vite Plugin for Ox Content React Integration
|
|
56
|
+
*
|
|
57
|
+
* Uses Vite's Environment API to enable embedding React components in Markdown.
|
|
58
|
+
*/
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* Creates the Ox Content React integration plugin.
|
|
62
|
+
*
|
|
63
|
+
* @example
|
|
64
|
+
* ```ts
|
|
65
|
+
* // vite.config.ts
|
|
66
|
+
* import { defineConfig } from 'vite';
|
|
67
|
+
* import react from '@vitejs/plugin-react';
|
|
68
|
+
* import { oxContentReact } from 'vite-plugin-ox-content-react';
|
|
69
|
+
*
|
|
70
|
+
* export default defineConfig({
|
|
71
|
+
* plugins: [
|
|
72
|
+
* react(),
|
|
73
|
+
* oxContentReact({
|
|
74
|
+
* srcDir: 'docs',
|
|
75
|
+
* components: {
|
|
76
|
+
* Counter: './src/components/Counter.tsx',
|
|
77
|
+
* },
|
|
78
|
+
* }),
|
|
79
|
+
* ],
|
|
80
|
+
* });
|
|
81
|
+
* ```
|
|
82
|
+
*/
|
|
83
|
+
declare function oxContentReact(options?: ReactIntegrationOptions): PluginOption[];
|
|
84
|
+
|
|
85
|
+
export { type ComponentSlot, type ComponentsMap, type ComponentsOption, type ReactIntegrationOptions, type ReactTransformResult, type ResolvedReactOptions, oxContentReact };
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,429 @@
|
|
|
1
|
+
// src/index.ts
|
|
2
|
+
import * as fs from "fs";
|
|
3
|
+
import * as path2 from "path";
|
|
4
|
+
import { oxContent } from "vite-plugin-ox-content";
|
|
5
|
+
|
|
6
|
+
// src/transform.ts
|
|
7
|
+
import * as path from "path";
|
|
8
|
+
import { transformMarkdown as baseTransformMarkdown } from "vite-plugin-ox-content";
|
|
9
|
+
var COMPONENT_REGEX = /<([A-Z][a-zA-Z0-9]*)\s*([^>]*?)\s*(?:\/>|>(?:[\s\S]*?)<\/\1>)/g;
|
|
10
|
+
var PROP_REGEX = /([a-zA-Z0-9-]+)(?:=(?:"([^"]*)"|'([^']*)'|{([^}]*)}))?/g;
|
|
11
|
+
async function transformMarkdownWithReact(code, id, options) {
|
|
12
|
+
const components = options.components;
|
|
13
|
+
const usedComponents = [];
|
|
14
|
+
const slots = [];
|
|
15
|
+
let slotIndex = 0;
|
|
16
|
+
const { content: markdownContent, frontmatter } = extractFrontmatter(code);
|
|
17
|
+
let processedContent = markdownContent;
|
|
18
|
+
let match;
|
|
19
|
+
while ((match = COMPONENT_REGEX.exec(markdownContent)) !== null) {
|
|
20
|
+
const [fullMatch, componentName, propsString] = match;
|
|
21
|
+
if (componentName in components) {
|
|
22
|
+
if (!usedComponents.includes(componentName)) {
|
|
23
|
+
usedComponents.push(componentName);
|
|
24
|
+
}
|
|
25
|
+
const props = parseProps(propsString);
|
|
26
|
+
const slotId = `__ox_slot_${slotIndex++}__`;
|
|
27
|
+
slots.push({
|
|
28
|
+
name: componentName,
|
|
29
|
+
props,
|
|
30
|
+
position: match.index,
|
|
31
|
+
id: slotId
|
|
32
|
+
});
|
|
33
|
+
processedContent = processedContent.replace(
|
|
34
|
+
fullMatch,
|
|
35
|
+
`<div data-ox-slot="${slotId}"></div>`
|
|
36
|
+
);
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
const transformed = await baseTransformMarkdown(processedContent, id, {
|
|
40
|
+
srcDir: options.srcDir,
|
|
41
|
+
outDir: options.outDir,
|
|
42
|
+
base: options.base,
|
|
43
|
+
ssg: { enabled: false, extension: ".html", clean: false, bare: false, generateOgImage: false },
|
|
44
|
+
gfm: options.gfm,
|
|
45
|
+
frontmatter: false,
|
|
46
|
+
toc: options.toc,
|
|
47
|
+
tocMaxDepth: options.tocMaxDepth,
|
|
48
|
+
footnotes: true,
|
|
49
|
+
tables: true,
|
|
50
|
+
taskLists: true,
|
|
51
|
+
strikethrough: true,
|
|
52
|
+
highlight: false,
|
|
53
|
+
highlightTheme: "github-dark",
|
|
54
|
+
mermaid: false,
|
|
55
|
+
ogImage: false,
|
|
56
|
+
ogImageOptions: {},
|
|
57
|
+
transformers: [],
|
|
58
|
+
docs: false,
|
|
59
|
+
search: { enabled: false, limit: 10, prefix: true, placeholder: "Search...", hotkey: "k" }
|
|
60
|
+
});
|
|
61
|
+
const jsxCode = generateReactModule(
|
|
62
|
+
transformed.html,
|
|
63
|
+
usedComponents,
|
|
64
|
+
slots,
|
|
65
|
+
frontmatter,
|
|
66
|
+
options,
|
|
67
|
+
id
|
|
68
|
+
);
|
|
69
|
+
return {
|
|
70
|
+
code: jsxCode,
|
|
71
|
+
map: null,
|
|
72
|
+
usedComponents,
|
|
73
|
+
frontmatter
|
|
74
|
+
};
|
|
75
|
+
}
|
|
76
|
+
function extractFrontmatter(content) {
|
|
77
|
+
const frontmatterRegex = /^---\n([\s\S]*?)\n---\n/;
|
|
78
|
+
const match = frontmatterRegex.exec(content);
|
|
79
|
+
if (!match) {
|
|
80
|
+
return { content, frontmatter: {} };
|
|
81
|
+
}
|
|
82
|
+
const frontmatterStr = match[1];
|
|
83
|
+
const frontmatter = {};
|
|
84
|
+
for (const line of frontmatterStr.split("\n")) {
|
|
85
|
+
const colonIndex = line.indexOf(":");
|
|
86
|
+
if (colonIndex > 0) {
|
|
87
|
+
const key = line.slice(0, colonIndex).trim();
|
|
88
|
+
let value = line.slice(colonIndex + 1).trim();
|
|
89
|
+
try {
|
|
90
|
+
value = JSON.parse(value);
|
|
91
|
+
} catch {
|
|
92
|
+
if (typeof value === "string" && value.startsWith('"') && value.endsWith('"')) {
|
|
93
|
+
value = value.slice(1, -1);
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
frontmatter[key] = value;
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
return { content: content.slice(match[0].length), frontmatter };
|
|
100
|
+
}
|
|
101
|
+
function parseProps(propsString) {
|
|
102
|
+
const props = {};
|
|
103
|
+
if (!propsString) return props;
|
|
104
|
+
let match;
|
|
105
|
+
while ((match = PROP_REGEX.exec(propsString)) !== null) {
|
|
106
|
+
const [, name, doubleQuoted, singleQuoted, braceValue] = match;
|
|
107
|
+
if (name) {
|
|
108
|
+
if (doubleQuoted !== void 0) props[name] = doubleQuoted;
|
|
109
|
+
else if (singleQuoted !== void 0) props[name] = singleQuoted;
|
|
110
|
+
else if (braceValue !== void 0) {
|
|
111
|
+
try {
|
|
112
|
+
props[name] = JSON.parse(braceValue);
|
|
113
|
+
} catch {
|
|
114
|
+
props[name] = braceValue;
|
|
115
|
+
}
|
|
116
|
+
} else props[name] = true;
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
return props;
|
|
120
|
+
}
|
|
121
|
+
function generateReactModule(content, usedComponents, slots, frontmatter, options, id) {
|
|
122
|
+
const mdDir = path.dirname(id);
|
|
123
|
+
const root = options.root || process.cwd();
|
|
124
|
+
const imports = usedComponents.map((name) => {
|
|
125
|
+
const componentPath = options.components[name];
|
|
126
|
+
if (!componentPath) return "";
|
|
127
|
+
const absolutePath = path.resolve(root, componentPath.replace(/^\.\//, ""));
|
|
128
|
+
const relativePath = path.relative(mdDir, absolutePath).replace(/\\/g, "/");
|
|
129
|
+
const importPath = relativePath.startsWith(".") ? relativePath : "./" + relativePath;
|
|
130
|
+
return `import ${name} from '${importPath}';`;
|
|
131
|
+
}).filter(Boolean).join("\n");
|
|
132
|
+
return `
|
|
133
|
+
import React, { useState, useEffect, createElement } from 'react';
|
|
134
|
+
${imports}
|
|
135
|
+
|
|
136
|
+
const frontmatter = ${JSON.stringify(frontmatter)};
|
|
137
|
+
const rawHtml = ${JSON.stringify(content)};
|
|
138
|
+
const slots = ${JSON.stringify(slots)};
|
|
139
|
+
|
|
140
|
+
const components = { ${usedComponents.join(", ")} };
|
|
141
|
+
|
|
142
|
+
export default function MarkdownContent() {
|
|
143
|
+
const [mounted, setMounted] = useState(false);
|
|
144
|
+
|
|
145
|
+
useEffect(() => {
|
|
146
|
+
setMounted(true);
|
|
147
|
+
}, []);
|
|
148
|
+
|
|
149
|
+
if (!mounted) {
|
|
150
|
+
return createElement('div', {
|
|
151
|
+
className: 'ox-content',
|
|
152
|
+
dangerouslySetInnerHTML: { __html: rawHtml }
|
|
153
|
+
});
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
return createElement('div', { className: 'ox-content' },
|
|
157
|
+
slots.map((slot) => {
|
|
158
|
+
const Component = components[slot.name];
|
|
159
|
+
return Component ? createElement(Component, { key: slot.id, ...slot.props }) : null;
|
|
160
|
+
})
|
|
161
|
+
);
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
export { frontmatter };
|
|
165
|
+
`;
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
// src/environment.ts
|
|
169
|
+
function createReactMarkdownEnvironment(mode, options) {
|
|
170
|
+
const isSSR = mode === "ssr";
|
|
171
|
+
return {
|
|
172
|
+
build: {
|
|
173
|
+
outDir: isSSR ? `${options.outDir}/.ox-content/ssr` : `${options.outDir}/.ox-content/client`,
|
|
174
|
+
ssr: isSSR,
|
|
175
|
+
rollupOptions: {
|
|
176
|
+
output: {
|
|
177
|
+
format: "esm",
|
|
178
|
+
entryFileNames: isSSR ? "[name].js" : "[name].[hash].js"
|
|
179
|
+
}
|
|
180
|
+
},
|
|
181
|
+
...isSSR && { target: "node18", minify: false }
|
|
182
|
+
},
|
|
183
|
+
resolve: {
|
|
184
|
+
conditions: isSSR ? ["node", "import"] : ["browser", "import"]
|
|
185
|
+
},
|
|
186
|
+
optimizeDeps: {
|
|
187
|
+
include: isSSR ? [] : ["react", "react-dom"],
|
|
188
|
+
exclude: ["vite-plugin-ox-content", "vite-plugin-ox-content-react"]
|
|
189
|
+
}
|
|
190
|
+
};
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
// src/index.ts
|
|
194
|
+
import { oxContent as oxContent2 } from "vite-plugin-ox-content";
|
|
195
|
+
function oxContentReact(options = {}) {
|
|
196
|
+
const resolved = resolveReactOptions(options);
|
|
197
|
+
let componentMap = /* @__PURE__ */ new Map();
|
|
198
|
+
let config;
|
|
199
|
+
if (typeof options.components === "object" && !Array.isArray(options.components)) {
|
|
200
|
+
componentMap = new Map(Object.entries(options.components));
|
|
201
|
+
}
|
|
202
|
+
const reactTransformPlugin = {
|
|
203
|
+
name: "ox-content:react-transform",
|
|
204
|
+
enforce: "pre",
|
|
205
|
+
async configResolved(resolvedConfig) {
|
|
206
|
+
config = resolvedConfig;
|
|
207
|
+
const componentsOption = options.components;
|
|
208
|
+
if (componentsOption) {
|
|
209
|
+
const resolvedComponents = await resolveComponentsGlob(
|
|
210
|
+
componentsOption,
|
|
211
|
+
config.root
|
|
212
|
+
);
|
|
213
|
+
componentMap = new Map(Object.entries(resolvedComponents));
|
|
214
|
+
}
|
|
215
|
+
},
|
|
216
|
+
async transform(code, id) {
|
|
217
|
+
if (!id.endsWith(".md")) {
|
|
218
|
+
return null;
|
|
219
|
+
}
|
|
220
|
+
const result = await transformMarkdownWithReact(code, id, {
|
|
221
|
+
...resolved,
|
|
222
|
+
components: Object.fromEntries(componentMap),
|
|
223
|
+
root: config.root
|
|
224
|
+
});
|
|
225
|
+
return {
|
|
226
|
+
code: result.code,
|
|
227
|
+
map: result.map
|
|
228
|
+
};
|
|
229
|
+
}
|
|
230
|
+
};
|
|
231
|
+
const reactEnvironmentPlugin = {
|
|
232
|
+
name: "ox-content:react-environment",
|
|
233
|
+
config() {
|
|
234
|
+
const envOptions = {
|
|
235
|
+
...resolved,
|
|
236
|
+
components: Object.fromEntries(componentMap)
|
|
237
|
+
};
|
|
238
|
+
return {
|
|
239
|
+
environments: {
|
|
240
|
+
oxcontent_ssr: createReactMarkdownEnvironment("ssr", envOptions),
|
|
241
|
+
oxcontent_client: createReactMarkdownEnvironment("client", envOptions)
|
|
242
|
+
}
|
|
243
|
+
};
|
|
244
|
+
},
|
|
245
|
+
resolveId(id) {
|
|
246
|
+
if (id === "virtual:ox-content-react/runtime") {
|
|
247
|
+
return "\0virtual:ox-content-react/runtime";
|
|
248
|
+
}
|
|
249
|
+
if (id === "virtual:ox-content-react/components") {
|
|
250
|
+
return "\0virtual:ox-content-react/components";
|
|
251
|
+
}
|
|
252
|
+
return null;
|
|
253
|
+
},
|
|
254
|
+
load(id) {
|
|
255
|
+
if (id === "\0virtual:ox-content-react/runtime") {
|
|
256
|
+
return generateRuntimeModule();
|
|
257
|
+
}
|
|
258
|
+
if (id === "\0virtual:ox-content-react/components") {
|
|
259
|
+
return generateComponentsModule(componentMap);
|
|
260
|
+
}
|
|
261
|
+
return null;
|
|
262
|
+
},
|
|
263
|
+
applyToEnvironment(environment) {
|
|
264
|
+
return ["oxcontent_ssr", "oxcontent_client", "client", "ssr"].includes(
|
|
265
|
+
environment.name
|
|
266
|
+
);
|
|
267
|
+
}
|
|
268
|
+
};
|
|
269
|
+
const reactHmrPlugin = {
|
|
270
|
+
name: "ox-content:react-hmr",
|
|
271
|
+
apply: "serve",
|
|
272
|
+
handleHotUpdate({ file, server, modules }) {
|
|
273
|
+
const isComponent = Array.from(componentMap.values()).some(
|
|
274
|
+
(path3) => file.endsWith(path3.replace(/^\.\//, ""))
|
|
275
|
+
);
|
|
276
|
+
if (isComponent) {
|
|
277
|
+
const mdModules = Array.from(
|
|
278
|
+
server.moduleGraph.idToModuleMap.values()
|
|
279
|
+
).filter((mod) => mod.file?.endsWith(".md"));
|
|
280
|
+
if (mdModules.length > 0) {
|
|
281
|
+
server.ws.send({
|
|
282
|
+
type: "custom",
|
|
283
|
+
event: "ox-content:react-update",
|
|
284
|
+
data: { file }
|
|
285
|
+
});
|
|
286
|
+
return [...modules, ...mdModules];
|
|
287
|
+
}
|
|
288
|
+
}
|
|
289
|
+
return modules;
|
|
290
|
+
}
|
|
291
|
+
};
|
|
292
|
+
const basePlugins = oxContent(options);
|
|
293
|
+
const environmentPlugin = basePlugins.find((p) => p.name === "ox-content:environment");
|
|
294
|
+
return [
|
|
295
|
+
reactTransformPlugin,
|
|
296
|
+
reactEnvironmentPlugin,
|
|
297
|
+
reactHmrPlugin,
|
|
298
|
+
...environmentPlugin ? [environmentPlugin] : []
|
|
299
|
+
];
|
|
300
|
+
}
|
|
301
|
+
function resolveReactOptions(options) {
|
|
302
|
+
return {
|
|
303
|
+
srcDir: options.srcDir ?? "docs",
|
|
304
|
+
outDir: options.outDir ?? "dist",
|
|
305
|
+
base: options.base ?? "/",
|
|
306
|
+
gfm: options.gfm ?? true,
|
|
307
|
+
frontmatter: options.frontmatter ?? true,
|
|
308
|
+
toc: options.toc ?? true,
|
|
309
|
+
tocMaxDepth: options.tocMaxDepth ?? 3,
|
|
310
|
+
jsxRuntime: options.jsxRuntime ?? "automatic"
|
|
311
|
+
};
|
|
312
|
+
}
|
|
313
|
+
function generateRuntimeModule() {
|
|
314
|
+
return `
|
|
315
|
+
import React, { useState, useEffect } from 'react';
|
|
316
|
+
|
|
317
|
+
export function OxContentRenderer({ content, components = {} }) {
|
|
318
|
+
const [mounted, setMounted] = useState(false);
|
|
319
|
+
|
|
320
|
+
useEffect(() => {
|
|
321
|
+
setMounted(true);
|
|
322
|
+
}, []);
|
|
323
|
+
|
|
324
|
+
if (!content) return null;
|
|
325
|
+
|
|
326
|
+
const { html, frontmatter, slots } = content;
|
|
327
|
+
|
|
328
|
+
if (!mounted) {
|
|
329
|
+
return React.createElement('div', {
|
|
330
|
+
className: 'ox-content',
|
|
331
|
+
dangerouslySetInnerHTML: { __html: html },
|
|
332
|
+
});
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
return React.createElement('div', { className: 'ox-content' },
|
|
336
|
+
slots.map((slot) => {
|
|
337
|
+
const Component = components[slot.name];
|
|
338
|
+
return Component
|
|
339
|
+
? React.createElement(Component, { key: slot.id, ...slot.props })
|
|
340
|
+
: null;
|
|
341
|
+
})
|
|
342
|
+
);
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
export function useOxContent() {
|
|
346
|
+
return { OxContentRenderer };
|
|
347
|
+
}
|
|
348
|
+
`;
|
|
349
|
+
}
|
|
350
|
+
function generateComponentsModule(componentMap) {
|
|
351
|
+
const imports = [];
|
|
352
|
+
const exports = [];
|
|
353
|
+
componentMap.forEach((path3, name) => {
|
|
354
|
+
imports.push(`import ${name} from '${path3}';`);
|
|
355
|
+
exports.push(` ${name},`);
|
|
356
|
+
});
|
|
357
|
+
return `
|
|
358
|
+
${imports.join("\n")}
|
|
359
|
+
|
|
360
|
+
export const components = {
|
|
361
|
+
${exports.join("\n")}
|
|
362
|
+
};
|
|
363
|
+
|
|
364
|
+
export default components;
|
|
365
|
+
`;
|
|
366
|
+
}
|
|
367
|
+
async function resolveComponentsGlob(componentsOption, root) {
|
|
368
|
+
if (typeof componentsOption === "object" && !Array.isArray(componentsOption)) {
|
|
369
|
+
return componentsOption;
|
|
370
|
+
}
|
|
371
|
+
const patterns = Array.isArray(componentsOption) ? componentsOption : [componentsOption];
|
|
372
|
+
const result = {};
|
|
373
|
+
for (const pattern of patterns) {
|
|
374
|
+
const files = await globFiles(pattern, root);
|
|
375
|
+
for (const file of files) {
|
|
376
|
+
const baseName = path2.basename(file, path2.extname(file));
|
|
377
|
+
const componentName = toPascalCase(baseName);
|
|
378
|
+
const relativePath = "./" + path2.relative(root, file).replace(/\\/g, "/");
|
|
379
|
+
result[componentName] = relativePath;
|
|
380
|
+
}
|
|
381
|
+
}
|
|
382
|
+
return result;
|
|
383
|
+
}
|
|
384
|
+
async function globFiles(pattern, root) {
|
|
385
|
+
const files = [];
|
|
386
|
+
const isGlob = pattern.includes("*");
|
|
387
|
+
if (!isGlob) {
|
|
388
|
+
const fullPath = path2.resolve(root, pattern);
|
|
389
|
+
if (fs.existsSync(fullPath)) {
|
|
390
|
+
files.push(fullPath);
|
|
391
|
+
}
|
|
392
|
+
return files;
|
|
393
|
+
}
|
|
394
|
+
const parts = pattern.split("*");
|
|
395
|
+
const baseDir = path2.resolve(root, parts[0]);
|
|
396
|
+
const ext = parts[1] || "";
|
|
397
|
+
if (!fs.existsSync(baseDir)) {
|
|
398
|
+
return files;
|
|
399
|
+
}
|
|
400
|
+
if (pattern.includes("**")) {
|
|
401
|
+
await walkDir(baseDir, files, ext);
|
|
402
|
+
} else {
|
|
403
|
+
const entries = await fs.promises.readdir(baseDir, { withFileTypes: true });
|
|
404
|
+
for (const entry of entries) {
|
|
405
|
+
if (entry.isFile() && entry.name.endsWith(ext)) {
|
|
406
|
+
files.push(path2.join(baseDir, entry.name));
|
|
407
|
+
}
|
|
408
|
+
}
|
|
409
|
+
}
|
|
410
|
+
return files;
|
|
411
|
+
}
|
|
412
|
+
async function walkDir(dir, files, ext) {
|
|
413
|
+
const entries = await fs.promises.readdir(dir, { withFileTypes: true });
|
|
414
|
+
for (const entry of entries) {
|
|
415
|
+
const fullPath = path2.join(dir, entry.name);
|
|
416
|
+
if (entry.isDirectory()) {
|
|
417
|
+
await walkDir(fullPath, files, ext);
|
|
418
|
+
} else if (entry.isFile() && entry.name.endsWith(ext)) {
|
|
419
|
+
files.push(fullPath);
|
|
420
|
+
}
|
|
421
|
+
}
|
|
422
|
+
}
|
|
423
|
+
function toPascalCase(str) {
|
|
424
|
+
return str.replace(/[-_](\w)/g, (_, c) => c.toUpperCase()).replace(/^\w/, (c) => c.toUpperCase());
|
|
425
|
+
}
|
|
426
|
+
export {
|
|
427
|
+
oxContent2 as oxContent,
|
|
428
|
+
oxContentReact
|
|
429
|
+
};
|
package/package.json
ADDED
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@ox-content/vite-plugin-react",
|
|
3
|
+
"version": "0.0.1-alpha.0",
|
|
4
|
+
"description": "React integration for Ox Content - Embed React components in Markdown",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "./dist/index.js",
|
|
7
|
+
"types": "./dist/index.d.ts",
|
|
8
|
+
"exports": {
|
|
9
|
+
".": {
|
|
10
|
+
"import": "./dist/index.js",
|
|
11
|
+
"types": "./dist/index.d.ts"
|
|
12
|
+
}
|
|
13
|
+
},
|
|
14
|
+
"files": [
|
|
15
|
+
"dist"
|
|
16
|
+
],
|
|
17
|
+
"scripts": {
|
|
18
|
+
"build": "tsup",
|
|
19
|
+
"dev": "tsup --watch",
|
|
20
|
+
"typecheck": "tsc --noEmit"
|
|
21
|
+
},
|
|
22
|
+
"peerDependencies": {
|
|
23
|
+
"react": "catalog:",
|
|
24
|
+
"react-dom": "catalog:",
|
|
25
|
+
"vite": "catalog:"
|
|
26
|
+
},
|
|
27
|
+
"dependencies": {
|
|
28
|
+
"vite-plugin-ox-content": "workspace:*"
|
|
29
|
+
},
|
|
30
|
+
"devDependencies": {
|
|
31
|
+
"@types/node": "catalog:",
|
|
32
|
+
"@types/react": "catalog:",
|
|
33
|
+
"@types/react-dom": "catalog:",
|
|
34
|
+
"@vitejs/plugin-react": "catalog:",
|
|
35
|
+
"react": "catalog:",
|
|
36
|
+
"react-dom": "catalog:",
|
|
37
|
+
"tsup": "catalog:",
|
|
38
|
+
"typescript": "catalog:",
|
|
39
|
+
"vite": "catalog:"
|
|
40
|
+
},
|
|
41
|
+
"keywords": [
|
|
42
|
+
"vite",
|
|
43
|
+
"vite-plugin",
|
|
44
|
+
"react",
|
|
45
|
+
"markdown",
|
|
46
|
+
"ox-content",
|
|
47
|
+
"mdx"
|
|
48
|
+
],
|
|
49
|
+
"license": "MIT",
|
|
50
|
+
"author": "ubugeeei",
|
|
51
|
+
"repository": {
|
|
52
|
+
"type": "git",
|
|
53
|
+
"url": "https://github.com/ubugeeei/ox-content.git",
|
|
54
|
+
"directory": "npm/vite-plugin-ox-content-react"
|
|
55
|
+
},
|
|
56
|
+
"publishConfig": {
|
|
57
|
+
"access": "public"
|
|
58
|
+
}
|
|
59
|
+
}
|