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