@ox-content/vite-plugin-react 0.0.1-alpha.0 → 0.3.0-alpha.13
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 +471 -378
- package/dist/index.d.cts +45 -47
- package/dist/index.d.cts.map +1 -0
- package/dist/index.d.ts +45 -47
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +445 -350
- package/dist/index.js.map +1 -0
- package/package.json +9 -6
package/dist/index.cjs
CHANGED
|
@@ -1,352 +1,458 @@
|
|
|
1
|
-
|
|
1
|
+
//#region rolldown:runtime
|
|
2
2
|
var __create = Object.create;
|
|
3
3
|
var __defProp = Object.defineProperty;
|
|
4
4
|
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
5
5
|
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
6
6
|
var __getProtoOf = Object.getPrototypeOf;
|
|
7
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
8
|
var __copyProps = (to, from, except, desc) => {
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
9
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
10
|
+
for (var keys = __getOwnPropNames(from), i = 0, n = keys.length, key; i < n; i++) {
|
|
11
|
+
key = keys[i];
|
|
12
|
+
if (!__hasOwnProp.call(to, key) && key !== except) {
|
|
13
|
+
__defProp(to, key, {
|
|
14
|
+
get: ((k) => from[k]).bind(null, key),
|
|
15
|
+
enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable
|
|
16
|
+
});
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
return to;
|
|
19
21
|
};
|
|
20
|
-
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
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);
|
|
22
|
+
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", {
|
|
23
|
+
value: mod,
|
|
24
|
+
enumerable: true
|
|
25
|
+
}) : target, mod));
|
|
29
26
|
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
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");
|
|
27
|
+
//#endregion
|
|
28
|
+
let fs = require("fs");
|
|
29
|
+
fs = __toESM(fs);
|
|
30
|
+
let path$1 = require("path");
|
|
31
|
+
path$1 = __toESM(path$1);
|
|
32
|
+
let _ox_content_vite_plugin = require("@ox-content/vite-plugin");
|
|
40
33
|
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
34
|
+
//#region src/transform.ts
|
|
35
|
+
const COMPONENT_REGEX = /<([A-Z][a-zA-Z0-9]*)\s*([^>]*?)\s*(?:\/>|>([\s\S]*?)<\/\1>)/g;
|
|
36
|
+
const PROP_REGEX = /([a-zA-Z0-9-]+)(?:=(?:"([^"]*)"|'([^']*)'|{([^}]*)}|\[([^\]]*)\]))?/g;
|
|
37
|
+
const ISLAND_MARKER_PREFIX = "OXCONTENT-ISLAND-";
|
|
38
|
+
const ISLAND_MARKER_SUFFIX = "-PLACEHOLDER";
|
|
46
39
|
async function transformMarkdownWithReact(code, id, options) {
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
40
|
+
const components = options.components;
|
|
41
|
+
const usedComponents = [];
|
|
42
|
+
const islands = [];
|
|
43
|
+
let islandIndex = 0;
|
|
44
|
+
const { content: markdownContent, frontmatter } = extractFrontmatter(code);
|
|
45
|
+
const fenceRanges = collectFenceRanges(markdownContent);
|
|
46
|
+
let processedContent = "";
|
|
47
|
+
let lastIndex = 0;
|
|
48
|
+
let match;
|
|
49
|
+
COMPONENT_REGEX.lastIndex = 0;
|
|
50
|
+
while ((match = COMPONENT_REGEX.exec(markdownContent)) !== null) {
|
|
51
|
+
const [fullMatch, componentName, propsString, rawIslandContent] = match;
|
|
52
|
+
const matchStart = match.index;
|
|
53
|
+
const matchEnd = matchStart + fullMatch.length;
|
|
54
|
+
if (!Object.prototype.hasOwnProperty.call(components, componentName) || isInRanges(matchStart, matchEnd, fenceRanges)) {
|
|
55
|
+
processedContent += markdownContent.slice(lastIndex, matchEnd);
|
|
56
|
+
lastIndex = matchEnd;
|
|
57
|
+
continue;
|
|
58
|
+
}
|
|
59
|
+
if (!usedComponents.includes(componentName)) usedComponents.push(componentName);
|
|
60
|
+
const props = parseProps(propsString);
|
|
61
|
+
const islandId = `ox-island-${islandIndex++}`;
|
|
62
|
+
const islandContent = typeof rawIslandContent === "string" ? rawIslandContent.trim() : void 0;
|
|
63
|
+
islands.push({
|
|
64
|
+
name: componentName,
|
|
65
|
+
props,
|
|
66
|
+
position: matchStart,
|
|
67
|
+
id: islandId,
|
|
68
|
+
content: islandContent
|
|
69
|
+
});
|
|
70
|
+
processedContent += markdownContent.slice(lastIndex, matchStart) + createIslandMarker(islandId);
|
|
71
|
+
lastIndex = matchEnd;
|
|
72
|
+
}
|
|
73
|
+
processedContent += markdownContent.slice(lastIndex);
|
|
74
|
+
return {
|
|
75
|
+
code: generateReactModule(injectIslandMarkers((await (0, _ox_content_vite_plugin.transformMarkdown)(processedContent, id, {
|
|
76
|
+
srcDir: options.srcDir,
|
|
77
|
+
outDir: options.outDir,
|
|
78
|
+
base: options.base,
|
|
79
|
+
ssg: {
|
|
80
|
+
enabled: false,
|
|
81
|
+
extension: ".html",
|
|
82
|
+
clean: false,
|
|
83
|
+
bare: false,
|
|
84
|
+
generateOgImage: false
|
|
85
|
+
},
|
|
86
|
+
gfm: options.gfm,
|
|
87
|
+
frontmatter: false,
|
|
88
|
+
toc: options.toc,
|
|
89
|
+
tocMaxDepth: options.tocMaxDepth,
|
|
90
|
+
footnotes: true,
|
|
91
|
+
tables: true,
|
|
92
|
+
taskLists: true,
|
|
93
|
+
strikethrough: true,
|
|
94
|
+
highlight: false,
|
|
95
|
+
highlightTheme: "github-dark",
|
|
96
|
+
mermaid: false,
|
|
97
|
+
ogImage: false,
|
|
98
|
+
ogImageOptions: {},
|
|
99
|
+
transformers: [],
|
|
100
|
+
docs: false,
|
|
101
|
+
search: {
|
|
102
|
+
enabled: false,
|
|
103
|
+
limit: 10,
|
|
104
|
+
prefix: true,
|
|
105
|
+
placeholder: "Search...",
|
|
106
|
+
hotkey: "k"
|
|
107
|
+
}
|
|
108
|
+
})).html, islands), usedComponents, islands, frontmatter, options, id),
|
|
109
|
+
map: null,
|
|
110
|
+
usedComponents,
|
|
111
|
+
frontmatter
|
|
112
|
+
};
|
|
113
|
+
}
|
|
114
|
+
function createIslandMarker(islandId) {
|
|
115
|
+
return `${ISLAND_MARKER_PREFIX}${islandId}${ISLAND_MARKER_SUFFIX}`;
|
|
116
|
+
}
|
|
117
|
+
function collectFenceRanges(content) {
|
|
118
|
+
const ranges = [];
|
|
119
|
+
let inFence = false;
|
|
120
|
+
let fenceChar = "";
|
|
121
|
+
let fenceLength = 0;
|
|
122
|
+
let fenceStart = 0;
|
|
123
|
+
let pos = 0;
|
|
124
|
+
while (pos < content.length) {
|
|
125
|
+
const lineEnd = content.indexOf("\n", pos);
|
|
126
|
+
const next = lineEnd === -1 ? content.length : lineEnd + 1;
|
|
127
|
+
const fenceMatch = content.slice(pos, lineEnd === -1 ? content.length : lineEnd).match(/^\s{0,3}([`~]{3,})/);
|
|
128
|
+
if (fenceMatch) {
|
|
129
|
+
const marker = fenceMatch[1];
|
|
130
|
+
if (!inFence) {
|
|
131
|
+
inFence = true;
|
|
132
|
+
fenceChar = marker[0];
|
|
133
|
+
fenceLength = marker.length;
|
|
134
|
+
fenceStart = pos;
|
|
135
|
+
} else if (marker[0] === fenceChar && marker.length >= fenceLength) {
|
|
136
|
+
inFence = false;
|
|
137
|
+
ranges.push({
|
|
138
|
+
start: fenceStart,
|
|
139
|
+
end: next
|
|
140
|
+
});
|
|
141
|
+
fenceChar = "";
|
|
142
|
+
fenceLength = 0;
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
pos = next;
|
|
146
|
+
}
|
|
147
|
+
if (inFence) ranges.push({
|
|
148
|
+
start: fenceStart,
|
|
149
|
+
end: content.length
|
|
150
|
+
});
|
|
151
|
+
return ranges;
|
|
152
|
+
}
|
|
153
|
+
function isInRanges(start, end, ranges) {
|
|
154
|
+
for (const range of ranges) if (start < range.end && end > range.start) return true;
|
|
155
|
+
return false;
|
|
156
|
+
}
|
|
157
|
+
function injectIslandMarkers(html, islands) {
|
|
158
|
+
let output = html;
|
|
159
|
+
for (const island of islands) {
|
|
160
|
+
const marker = createIslandMarker(island.id);
|
|
161
|
+
const propsAttr = Object.keys(island.props).length > 0 ? ` data-ox-props='${JSON.stringify(island.props).replace(/'/g, "'")}'` : "";
|
|
162
|
+
const contentAttr = island.content ? ` data-ox-content='${island.content.replace(/'/g, "'")}'` : "";
|
|
163
|
+
const attrs = `data-ox-island="${island.name}"${propsAttr}${contentAttr}`;
|
|
164
|
+
output = output.replaceAll(`<p>${marker}</p>`, `<div ${attrs}></div>`);
|
|
165
|
+
output = output.replaceAll(marker, `<span ${attrs}></span>`);
|
|
166
|
+
}
|
|
167
|
+
return output;
|
|
110
168
|
}
|
|
111
169
|
function extractFrontmatter(content) {
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
170
|
+
const match = /^---\n([\s\S]*?)\n---\n/.exec(content);
|
|
171
|
+
if (!match) return {
|
|
172
|
+
content,
|
|
173
|
+
frontmatter: {}
|
|
174
|
+
};
|
|
175
|
+
const frontmatterStr = match[1];
|
|
176
|
+
const frontmatter = {};
|
|
177
|
+
for (const line of frontmatterStr.split("\n")) {
|
|
178
|
+
const colonIndex = line.indexOf(":");
|
|
179
|
+
if (colonIndex > 0) {
|
|
180
|
+
const key = line.slice(0, colonIndex).trim();
|
|
181
|
+
let value = line.slice(colonIndex + 1).trim();
|
|
182
|
+
try {
|
|
183
|
+
value = JSON.parse(value);
|
|
184
|
+
} catch {
|
|
185
|
+
if (typeof value === "string" && (value.startsWith("\"") && value.endsWith("\"") || value.startsWith("'") && value.endsWith("'"))) value = value.slice(1, -1);
|
|
186
|
+
}
|
|
187
|
+
frontmatter[key] = value;
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
return {
|
|
191
|
+
content: content.slice(match[0].length),
|
|
192
|
+
frontmatter
|
|
193
|
+
};
|
|
135
194
|
}
|
|
136
195
|
function parseProps(propsString) {
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
196
|
+
const props = {};
|
|
197
|
+
if (!propsString) return props;
|
|
198
|
+
PROP_REGEX.lastIndex = 0;
|
|
199
|
+
let match;
|
|
200
|
+
while ((match = PROP_REGEX.exec(propsString)) !== null) {
|
|
201
|
+
const [, name, doubleQuoted, singleQuoted, braceValue, bracketValue] = match;
|
|
202
|
+
if (name) if (doubleQuoted !== void 0) props[name] = doubleQuoted;
|
|
203
|
+
else if (singleQuoted !== void 0) props[name] = singleQuoted;
|
|
204
|
+
else if (braceValue !== void 0) try {
|
|
205
|
+
props[name] = JSON.parse(braceValue);
|
|
206
|
+
} catch {
|
|
207
|
+
props[name] = braceValue;
|
|
208
|
+
}
|
|
209
|
+
else if (bracketValue !== void 0) try {
|
|
210
|
+
props[name] = JSON.parse(`[${bracketValue}]`);
|
|
211
|
+
} catch {
|
|
212
|
+
props[name] = bracketValue;
|
|
213
|
+
}
|
|
214
|
+
else props[name] = true;
|
|
215
|
+
}
|
|
216
|
+
return props;
|
|
155
217
|
}
|
|
156
|
-
function generateReactModule(content, usedComponents,
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
}).
|
|
167
|
-
|
|
168
|
-
import React, {
|
|
218
|
+
function generateReactModule(content, usedComponents, islands, frontmatter, options, id) {
|
|
219
|
+
const mdDir = path$1.dirname(id);
|
|
220
|
+
const root = options.root || process.cwd();
|
|
221
|
+
const imports = usedComponents.map((name) => {
|
|
222
|
+
const componentPath = options.components[name];
|
|
223
|
+
if (!componentPath) return "";
|
|
224
|
+
const absolutePath = path$1.resolve(root, componentPath.replace(/^\.\//, ""));
|
|
225
|
+
const relativePath = path$1.relative(mdDir, absolutePath).replace(/\\/g, "/");
|
|
226
|
+
return `import ${name} from '${relativePath.startsWith(".") ? relativePath : "./" + relativePath}';`;
|
|
227
|
+
}).filter(Boolean).join("\n");
|
|
228
|
+
const componentMap = usedComponents.map((name) => ` ${name},`).join("\n");
|
|
229
|
+
if (islands.length === 0) return `
|
|
230
|
+
import React, { createElement } from 'react';
|
|
231
|
+
|
|
232
|
+
export const frontmatter = ${JSON.stringify(frontmatter)};
|
|
233
|
+
|
|
234
|
+
const rawHtml = ${JSON.stringify(content)};
|
|
235
|
+
|
|
236
|
+
export default function MarkdownContent() {
|
|
237
|
+
return createElement('div', {
|
|
238
|
+
className: 'ox-content',
|
|
239
|
+
dangerouslySetInnerHTML: { __html: rawHtml },
|
|
240
|
+
});
|
|
241
|
+
}
|
|
242
|
+
`;
|
|
243
|
+
return `
|
|
244
|
+
import React, { useEffect, useRef, createElement } from 'react';
|
|
245
|
+
import { createRoot } from 'react-dom/client';
|
|
246
|
+
import { initIslands } from '@ox-content/islands';
|
|
169
247
|
${imports}
|
|
170
248
|
|
|
171
|
-
const frontmatter = ${JSON.stringify(frontmatter)};
|
|
249
|
+
export const frontmatter = ${JSON.stringify(frontmatter)};
|
|
250
|
+
|
|
172
251
|
const rawHtml = ${JSON.stringify(content)};
|
|
173
|
-
const
|
|
252
|
+
const components = {
|
|
253
|
+
${componentMap}
|
|
254
|
+
};
|
|
255
|
+
|
|
256
|
+
function createReactHydrate() {
|
|
257
|
+
const mountedRoots = [];
|
|
258
|
+
|
|
259
|
+
return (element, props) => {
|
|
260
|
+
const componentName = element.dataset.oxIsland;
|
|
261
|
+
const Component = components[componentName];
|
|
262
|
+
if (!Component) return;
|
|
174
263
|
|
|
175
|
-
const
|
|
264
|
+
const islandContent = element.dataset.oxContent || element.innerHTML;
|
|
265
|
+
const vnode = islandContent
|
|
266
|
+
? createElement(
|
|
267
|
+
Component,
|
|
268
|
+
props,
|
|
269
|
+
createElement('div', { dangerouslySetInnerHTML: { __html: islandContent } })
|
|
270
|
+
)
|
|
271
|
+
: createElement(Component, props);
|
|
272
|
+
|
|
273
|
+
const root = createRoot(element);
|
|
274
|
+
root.render(vnode);
|
|
275
|
+
mountedRoots.push(root);
|
|
276
|
+
|
|
277
|
+
return () => root.unmount();
|
|
278
|
+
};
|
|
279
|
+
}
|
|
176
280
|
|
|
177
281
|
export default function MarkdownContent() {
|
|
178
|
-
const
|
|
282
|
+
const containerRef = useRef(null);
|
|
179
283
|
|
|
180
284
|
useEffect(() => {
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
if (!mounted) {
|
|
185
|
-
return createElement('div', {
|
|
186
|
-
className: 'ox-content',
|
|
187
|
-
dangerouslySetInnerHTML: { __html: rawHtml }
|
|
285
|
+
if (!containerRef.current) return;
|
|
286
|
+
const controller = initIslands(createReactHydrate(), {
|
|
287
|
+
selector: '.ox-content [data-ox-island]',
|
|
188
288
|
});
|
|
189
|
-
|
|
289
|
+
return () => controller.destroy();
|
|
290
|
+
}, []);
|
|
190
291
|
|
|
191
|
-
return createElement('div', {
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
);
|
|
292
|
+
return createElement('div', {
|
|
293
|
+
className: 'ox-content',
|
|
294
|
+
ref: containerRef,
|
|
295
|
+
dangerouslySetInnerHTML: { __html: rawHtml },
|
|
296
|
+
});
|
|
197
297
|
}
|
|
198
|
-
|
|
199
|
-
export { frontmatter };
|
|
200
298
|
`;
|
|
201
299
|
}
|
|
202
300
|
|
|
203
|
-
|
|
301
|
+
//#endregion
|
|
302
|
+
//#region src/environment.ts
|
|
204
303
|
function createReactMarkdownEnvironment(mode, options) {
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
};
|
|
304
|
+
const isSSR = mode === "ssr";
|
|
305
|
+
return {
|
|
306
|
+
build: {
|
|
307
|
+
outDir: isSSR ? `${options.outDir}/.ox-content/ssr` : `${options.outDir}/.ox-content/client`,
|
|
308
|
+
ssr: isSSR,
|
|
309
|
+
rollupOptions: { output: {
|
|
310
|
+
format: "esm",
|
|
311
|
+
entryFileNames: isSSR ? "[name].js" : "[name].[hash].js"
|
|
312
|
+
} },
|
|
313
|
+
...isSSR && {
|
|
314
|
+
target: "node18",
|
|
315
|
+
minify: false
|
|
316
|
+
}
|
|
317
|
+
},
|
|
318
|
+
resolve: { conditions: isSSR ? ["node", "import"] : ["browser", "import"] },
|
|
319
|
+
optimizeDeps: {
|
|
320
|
+
include: isSSR ? [] : ["react", "react-dom"],
|
|
321
|
+
exclude: ["@ox-content/vite-plugin", "@ox-content/vite-plugin-react"]
|
|
322
|
+
}
|
|
323
|
+
};
|
|
226
324
|
}
|
|
227
325
|
|
|
228
|
-
|
|
229
|
-
|
|
326
|
+
//#endregion
|
|
327
|
+
//#region src/index.ts
|
|
328
|
+
/**
|
|
329
|
+
* Vite Plugin for Ox Content React Integration
|
|
330
|
+
*
|
|
331
|
+
* Uses Vite's Environment API to enable embedding React components in Markdown.
|
|
332
|
+
*/
|
|
333
|
+
/**
|
|
334
|
+
* Creates the Ox Content React integration plugin.
|
|
335
|
+
*
|
|
336
|
+
* @example
|
|
337
|
+
* ```ts
|
|
338
|
+
* // vite.config.ts
|
|
339
|
+
* import { defineConfig } from 'vite';
|
|
340
|
+
* import react from '@vitejs/plugin-react';
|
|
341
|
+
* import { oxContentReact } from 'vite-plugin-ox-content-react';
|
|
342
|
+
*
|
|
343
|
+
* export default defineConfig({
|
|
344
|
+
* plugins: [
|
|
345
|
+
* react(),
|
|
346
|
+
* oxContentReact({
|
|
347
|
+
* srcDir: 'docs',
|
|
348
|
+
* components: {
|
|
349
|
+
* Counter: './src/components/Counter.tsx',
|
|
350
|
+
* },
|
|
351
|
+
* }),
|
|
352
|
+
* ],
|
|
353
|
+
* });
|
|
354
|
+
* ```
|
|
355
|
+
*/
|
|
230
356
|
function oxContentReact(options = {}) {
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
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
|
-
];
|
|
357
|
+
const resolved = resolveReactOptions(options);
|
|
358
|
+
let componentMap = /* @__PURE__ */ new Map();
|
|
359
|
+
let config;
|
|
360
|
+
if (typeof options.components === "object" && !Array.isArray(options.components)) componentMap = new Map(Object.entries(options.components));
|
|
361
|
+
const reactTransformPlugin = {
|
|
362
|
+
name: "ox-content:react-transform",
|
|
363
|
+
enforce: "pre",
|
|
364
|
+
async configResolved(resolvedConfig) {
|
|
365
|
+
config = resolvedConfig;
|
|
366
|
+
const componentsOption = options.components;
|
|
367
|
+
if (componentsOption) {
|
|
368
|
+
const resolvedComponents = await resolveComponentsGlob(componentsOption, config.root);
|
|
369
|
+
componentMap = new Map(Object.entries(resolvedComponents));
|
|
370
|
+
}
|
|
371
|
+
},
|
|
372
|
+
async transform(code, id) {
|
|
373
|
+
if (!id.endsWith(".md")) return null;
|
|
374
|
+
const result = await transformMarkdownWithReact(code, id, {
|
|
375
|
+
...resolved,
|
|
376
|
+
components: Object.fromEntries(componentMap),
|
|
377
|
+
root: config.root
|
|
378
|
+
});
|
|
379
|
+
return {
|
|
380
|
+
code: result.code,
|
|
381
|
+
map: result.map
|
|
382
|
+
};
|
|
383
|
+
}
|
|
384
|
+
};
|
|
385
|
+
const reactEnvironmentPlugin = {
|
|
386
|
+
name: "ox-content:react-environment",
|
|
387
|
+
config() {
|
|
388
|
+
const envOptions = {
|
|
389
|
+
...resolved,
|
|
390
|
+
components: Object.fromEntries(componentMap)
|
|
391
|
+
};
|
|
392
|
+
return { environments: {
|
|
393
|
+
oxcontent_ssr: createReactMarkdownEnvironment("ssr", envOptions),
|
|
394
|
+
oxcontent_client: createReactMarkdownEnvironment("client", envOptions)
|
|
395
|
+
} };
|
|
396
|
+
},
|
|
397
|
+
resolveId(id) {
|
|
398
|
+
if (id === "virtual:ox-content-react/runtime") return "\0virtual:ox-content-react/runtime";
|
|
399
|
+
if (id === "virtual:ox-content-react/components") return "\0virtual:ox-content-react/components";
|
|
400
|
+
return null;
|
|
401
|
+
},
|
|
402
|
+
load(id) {
|
|
403
|
+
if (id === "\0virtual:ox-content-react/runtime") return generateRuntimeModule();
|
|
404
|
+
if (id === "\0virtual:ox-content-react/components") return generateComponentsModule(componentMap);
|
|
405
|
+
return null;
|
|
406
|
+
},
|
|
407
|
+
applyToEnvironment(environment) {
|
|
408
|
+
return [
|
|
409
|
+
"oxcontent_ssr",
|
|
410
|
+
"oxcontent_client",
|
|
411
|
+
"client",
|
|
412
|
+
"ssr"
|
|
413
|
+
].includes(environment.name);
|
|
414
|
+
}
|
|
415
|
+
};
|
|
416
|
+
const reactHmrPlugin = {
|
|
417
|
+
name: "ox-content:react-hmr",
|
|
418
|
+
apply: "serve",
|
|
419
|
+
handleHotUpdate({ file, server, modules }) {
|
|
420
|
+
if (Array.from(componentMap.values()).some((path) => file.endsWith(path.replace(/^\.\//, "")))) {
|
|
421
|
+
const mdModules = Array.from(server.moduleGraph.idToModuleMap.values()).filter((mod) => mod.file?.endsWith(".md"));
|
|
422
|
+
if (mdModules.length > 0) {
|
|
423
|
+
server.ws.send({
|
|
424
|
+
type: "custom",
|
|
425
|
+
event: "ox-content:react-update",
|
|
426
|
+
data: { file }
|
|
427
|
+
});
|
|
428
|
+
return [...modules, ...mdModules];
|
|
429
|
+
}
|
|
430
|
+
}
|
|
431
|
+
return modules;
|
|
432
|
+
}
|
|
433
|
+
};
|
|
434
|
+
const environmentPlugin = (0, _ox_content_vite_plugin.oxContent)(options).find((p) => p.name === "ox-content:environment");
|
|
435
|
+
return [
|
|
436
|
+
reactTransformPlugin,
|
|
437
|
+
reactEnvironmentPlugin,
|
|
438
|
+
reactHmrPlugin,
|
|
439
|
+
...environmentPlugin ? [environmentPlugin] : []
|
|
440
|
+
];
|
|
335
441
|
}
|
|
336
442
|
function resolveReactOptions(options) {
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
443
|
+
return {
|
|
444
|
+
srcDir: options.srcDir ?? "docs",
|
|
445
|
+
outDir: options.outDir ?? "dist",
|
|
446
|
+
base: options.base ?? "/",
|
|
447
|
+
gfm: options.gfm ?? true,
|
|
448
|
+
frontmatter: options.frontmatter ?? true,
|
|
449
|
+
toc: options.toc ?? true,
|
|
450
|
+
tocMaxDepth: options.tocMaxDepth ?? 3,
|
|
451
|
+
jsxRuntime: options.jsxRuntime ?? "automatic"
|
|
452
|
+
};
|
|
347
453
|
}
|
|
348
454
|
function generateRuntimeModule() {
|
|
349
|
-
|
|
455
|
+
return `
|
|
350
456
|
import React, { useState, useEffect } from 'react';
|
|
351
457
|
|
|
352
458
|
export function OxContentRenderer({ content, components = {} }) {
|
|
@@ -358,7 +464,7 @@ export function OxContentRenderer({ content, components = {} }) {
|
|
|
358
464
|
|
|
359
465
|
if (!content) return null;
|
|
360
466
|
|
|
361
|
-
const { html, frontmatter,
|
|
467
|
+
const { html, frontmatter, islands } = content;
|
|
362
468
|
|
|
363
469
|
if (!mounted) {
|
|
364
470
|
return React.createElement('div', {
|
|
@@ -368,10 +474,10 @@ export function OxContentRenderer({ content, components = {} }) {
|
|
|
368
474
|
}
|
|
369
475
|
|
|
370
476
|
return React.createElement('div', { className: 'ox-content' },
|
|
371
|
-
|
|
372
|
-
const Component = components[
|
|
477
|
+
islands.map((island) => {
|
|
478
|
+
const Component = components[island.name];
|
|
373
479
|
return Component
|
|
374
|
-
? React.createElement(Component, { key:
|
|
480
|
+
? React.createElement(Component, { key: island.id, ...island.props })
|
|
375
481
|
: null;
|
|
376
482
|
})
|
|
377
483
|
);
|
|
@@ -383,83 +489,70 @@ export function useOxContent() {
|
|
|
383
489
|
`;
|
|
384
490
|
}
|
|
385
491
|
function generateComponentsModule(componentMap) {
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
492
|
+
const imports = [];
|
|
493
|
+
const exports = [];
|
|
494
|
+
componentMap.forEach((path, name) => {
|
|
495
|
+
imports.push(`import ${name} from '${path}';`);
|
|
496
|
+
exports.push(` ${name},`);
|
|
497
|
+
});
|
|
498
|
+
return `
|
|
393
499
|
${imports.join("\n")}
|
|
394
500
|
|
|
395
501
|
export const components = {
|
|
396
|
-
${
|
|
502
|
+
${exports.join("\n")}
|
|
397
503
|
};
|
|
398
504
|
|
|
399
505
|
export default components;
|
|
400
506
|
`;
|
|
401
507
|
}
|
|
402
508
|
async function resolveComponentsGlob(componentsOption, root) {
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
result[componentName] = relativePath;
|
|
415
|
-
}
|
|
416
|
-
}
|
|
417
|
-
return result;
|
|
509
|
+
if (typeof componentsOption === "object" && !Array.isArray(componentsOption)) return componentsOption;
|
|
510
|
+
const patterns = Array.isArray(componentsOption) ? componentsOption : [componentsOption];
|
|
511
|
+
const result = {};
|
|
512
|
+
for (const pattern of patterns) {
|
|
513
|
+
const files = await globFiles(pattern, root);
|
|
514
|
+
for (const file of files) {
|
|
515
|
+
const componentName = toPascalCase(path$1.basename(file, path$1.extname(file)));
|
|
516
|
+
result[componentName] = "./" + path$1.relative(root, file).replace(/\\/g, "/");
|
|
517
|
+
}
|
|
518
|
+
}
|
|
519
|
+
return result;
|
|
418
520
|
}
|
|
419
521
|
async function globFiles(pattern, root) {
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
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;
|
|
522
|
+
const files = [];
|
|
523
|
+
if (!pattern.includes("*")) {
|
|
524
|
+
const fullPath = path$1.resolve(root, pattern);
|
|
525
|
+
if (fs.existsSync(fullPath)) files.push(fullPath);
|
|
526
|
+
return files;
|
|
527
|
+
}
|
|
528
|
+
const parts = pattern.split("*");
|
|
529
|
+
const baseDir = path$1.resolve(root, parts[0]);
|
|
530
|
+
const ext = parts[1] || "";
|
|
531
|
+
if (!fs.existsSync(baseDir)) return files;
|
|
532
|
+
if (pattern.includes("**")) await walkDir(baseDir, files, ext);
|
|
533
|
+
else {
|
|
534
|
+
const entries = await fs.promises.readdir(baseDir, { withFileTypes: true });
|
|
535
|
+
for (const entry of entries) if (entry.isFile() && entry.name.endsWith(ext)) files.push(path$1.join(baseDir, entry.name));
|
|
536
|
+
}
|
|
537
|
+
return files;
|
|
446
538
|
}
|
|
447
539
|
async function walkDir(dir, files, ext) {
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
files.push(fullPath);
|
|
455
|
-
}
|
|
456
|
-
}
|
|
540
|
+
const entries = await fs.promises.readdir(dir, { withFileTypes: true });
|
|
541
|
+
for (const entry of entries) {
|
|
542
|
+
const fullPath = path$1.join(dir, entry.name);
|
|
543
|
+
if (entry.isDirectory()) await walkDir(fullPath, files, ext);
|
|
544
|
+
else if (entry.isFile() && entry.name.endsWith(ext)) files.push(fullPath);
|
|
545
|
+
}
|
|
457
546
|
}
|
|
458
547
|
function toPascalCase(str) {
|
|
459
|
-
|
|
548
|
+
return str.replace(/[-_](\w)/g, (_, c) => c.toUpperCase()).replace(/^\w/, (c) => c.toUpperCase());
|
|
460
549
|
}
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
550
|
+
|
|
551
|
+
//#endregion
|
|
552
|
+
Object.defineProperty(exports, 'oxContent', {
|
|
553
|
+
enumerable: true,
|
|
554
|
+
get: function () {
|
|
555
|
+
return _ox_content_vite_plugin.oxContent;
|
|
556
|
+
}
|
|
465
557
|
});
|
|
558
|
+
exports.oxContentReact = oxContentReact;
|