@mdream/vite 0.7.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/LICENSE.md ADDED
@@ -0,0 +1,9 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2025 Harlan Wilton
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
6
+
7
+ The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
8
+
9
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,207 @@
1
+ # @mdream/vite
2
+
3
+ Vite plugin for HTML to Markdown conversion with on-demand generation support.
4
+
5
+ ## Features
6
+
7
+ - **🚀 On-Demand Generation**: Access any HTML page as `.md` for instant markdown conversion
8
+ - **âš¡ Multi-Environment**: Works in development, preview, and production
9
+ - **📦 Build Integration**: Generate static markdown files during build
10
+ - **💾 Smart Caching**: Intelligent caching for optimal performance
11
+ - **🎯 URL Pattern Matching**: Simple `.md` suffix for any HTML route
12
+ - **🔧 Configurable**: Full control over processing and output
13
+
14
+ ## Installation
15
+
16
+ ```bash
17
+ npm install @mdream/vite mdream
18
+ # or
19
+ pnpm add @mdream/vite mdream
20
+ # or
21
+ yarn add @mdream/vite mdream
22
+ ```
23
+
24
+ ## Usage
25
+
26
+ Add the plugin to your `vite.config.js`:
27
+
28
+ ```javascript
29
+ import { viteHtmlToMarkdownPlugin } from '@mdream/vite'
30
+ import { defineConfig } from 'vite'
31
+
32
+ export default defineConfig({
33
+ plugins: [
34
+ viteHtmlToMarkdownPlugin({
35
+ // Optional configuration
36
+ include: ['**/*.html'],
37
+ exclude: ['**/node_modules/**'],
38
+ outputDir: 'markdown',
39
+ cacheEnabled: true,
40
+ mdreamOptions: {
41
+ // mdream configuration options
42
+ plugins: []
43
+ }
44
+ })
45
+ ]
46
+ })
47
+ ```
48
+
49
+ ## URL Pattern
50
+
51
+ The plugin enables accessing any HTML path with a `.md` extension:
52
+
53
+ - `/about` → `/about.md` (converts on-demand)
54
+ - `/docs/guide.html` → `/docs/guide.md`
55
+ - `/blog/post` → `/blog/post.md`
56
+
57
+ ## Configuration Options
58
+
59
+ ```typescript
60
+ interface ViteHtmlToMarkdownOptions {
61
+ /**
62
+ * Glob patterns to include HTML files for processing
63
+ * @default ['**/*.html']
64
+ */
65
+ include?: string[]
66
+
67
+ /**
68
+ * Glob patterns to exclude from processing
69
+ * @default ['**/node_modules/**']
70
+ */
71
+ exclude?: string[]
72
+
73
+ /**
74
+ * Output directory for generated markdown files
75
+ * @default 'markdown'
76
+ */
77
+ outputDir?: string
78
+
79
+ /**
80
+ * Enable in-memory caching for development
81
+ * @default true
82
+ */
83
+ cacheEnabled?: boolean
84
+
85
+ /**
86
+ * Options to pass to mdream's htmlToMarkdown function
87
+ */
88
+ mdreamOptions?: HtmlToMarkdownOptions
89
+
90
+ /**
91
+ * Whether to preserve directory structure in output
92
+ * @default true
93
+ */
94
+ preserveStructure?: boolean
95
+
96
+ /**
97
+ * Custom cache TTL in milliseconds for production
98
+ * @default 3600000 (1 hour)
99
+ */
100
+ cacheTTL?: number
101
+
102
+ /**
103
+ * Whether to log conversion activities
104
+ * @default false
105
+ */
106
+ verbose?: boolean
107
+ }
108
+ ```
109
+
110
+ ## How It Works
111
+
112
+ ### Development (`vite dev`)
113
+ - **Middleware**: Intercepts `.md` requests via `configureServer`
114
+ - **Transform Pipeline**: Uses Vite's transform system for HTML content
115
+ - **Fallbacks**: Tries multiple path variations including SPA fallback
116
+ - **Caching**: Memory cache with no-cache headers
117
+
118
+ ### Build Time (`vite build`)
119
+ - **Bundle Processing**: Processes HTML files via `generateBundle` hook
120
+ - **Static Generation**: Creates `.md` files alongside HTML output
121
+ - **Pattern Matching**: Respects include/exclude patterns
122
+
123
+ ### Preview (`vite preview`)
124
+ - **File System**: Reads from build output directory
125
+ - **Caching**: Aggressive caching with TTL
126
+ - **Multiple Paths**: Tries various file locations
127
+
128
+ ## Examples
129
+
130
+ ### Basic Setup
131
+
132
+ ```javascript
133
+ import { viteHtmlToMarkdownPlugin } from '@mdream/vite'
134
+
135
+ export default defineConfig({
136
+ plugins: [
137
+ viteHtmlToMarkdownPlugin()
138
+ ]
139
+ })
140
+ ```
141
+
142
+ ### Advanced Configuration
143
+
144
+ ```javascript
145
+ import { viteHtmlToMarkdownPlugin } from '@mdream/vite'
146
+
147
+ export default defineConfig({
148
+ plugins: [
149
+ viteHtmlToMarkdownPlugin({
150
+ include: ['pages/**/*.html', 'docs/**/*.html'],
151
+ exclude: ['**/admin/**', '**/private/**'],
152
+ outputDir: 'public/markdown',
153
+ verbose: true,
154
+ mdreamOptions: {
155
+ plugins: [
156
+ // Custom mdream plugins
157
+ ]
158
+ }
159
+ })
160
+ ]
161
+ })
162
+ ```
163
+
164
+ ### With Custom mdream Options
165
+
166
+ ```javascript
167
+ import { viteHtmlToMarkdownPlugin } from '@mdream/vite'
168
+ import { readabilityPlugin } from 'mdream/plugins'
169
+
170
+ export default defineConfig({
171
+ plugins: [
172
+ viteHtmlToMarkdownPlugin({
173
+ mdreamOptions: {
174
+ plugins: [
175
+ readabilityPlugin({
176
+ minScore: 0.7
177
+ })
178
+ ]
179
+ }
180
+ })
181
+ ]
182
+ })
183
+ ```
184
+
185
+ ## Integration with SSR
186
+
187
+ For production SSR applications, you can extend your Express server:
188
+
189
+ ```javascript
190
+ import express from 'express'
191
+ import { createServer as createViteServer } from 'vite'
192
+
193
+ const app = express()
194
+
195
+ // In production, handle .md requests
196
+ app.use(async (req, res, next) => {
197
+ if (req.path.endsWith('.md')) {
198
+ // Your custom SSR markdown handling
199
+ // The plugin provides the foundation
200
+ }
201
+ next()
202
+ })
203
+ ```
204
+
205
+ ## License
206
+
207
+ MIT
@@ -0,0 +1,61 @@
1
+ import { HTMLToMarkdownOptions } from "mdream";
2
+ import { Plugin } from "vite";
3
+
4
+ //#region src/types.d.ts
5
+ interface ViteHtmlToMarkdownOptions {
6
+ /**
7
+ * Glob patterns to include HTML files for processing
8
+ * @default ['**\/*.html']
9
+ */
10
+ include?: string[];
11
+ /**
12
+ * Glob patterns to exclude from processing
13
+ * @default ['**\/node_modules\/**']
14
+ */
15
+ exclude?: string[];
16
+ /**
17
+ * Output directory for generated markdown files
18
+ * @default '' (same directory as HTML files)
19
+ */
20
+ outputDir?: string;
21
+ /**
22
+ * Enable in-memory caching for development
23
+ * @default true
24
+ */
25
+ cacheEnabled?: boolean;
26
+ /**
27
+ * Options to pass to mdream's htmlToMarkdown function
28
+ */
29
+ mdreamOptions?: HTMLToMarkdownOptions;
30
+ /**
31
+ * Whether to preserve directory structure in output
32
+ * @default true
33
+ */
34
+ preserveStructure?: boolean;
35
+ /**
36
+ * Custom cache TTL in milliseconds for production
37
+ * @default 3600000 (1 hour)
38
+ */
39
+ cacheTTL?: number;
40
+ /**
41
+ * Whether to log conversion activities
42
+ * @default false
43
+ */
44
+ verbose?: boolean;
45
+ }
46
+ interface CacheEntry {
47
+ content: string;
48
+ timestamp: number;
49
+ ttl: number;
50
+ }
51
+ interface MarkdownConversionResult {
52
+ content: string;
53
+ cached: boolean;
54
+ source: 'dev' | 'preview' | 'build';
55
+ }
56
+ type ViteHtmlToMarkdownPlugin = (options?: ViteHtmlToMarkdownOptions) => Plugin;
57
+ //#endregion
58
+ //#region src/plugin.d.ts
59
+ declare function viteHtmlToMarkdownPlugin(userOptions?: ViteHtmlToMarkdownOptions): Plugin;
60
+ //#endregion
61
+ export { CacheEntry, MarkdownConversionResult, ViteHtmlToMarkdownOptions, ViteHtmlToMarkdownPlugin, viteHtmlToMarkdownPlugin as default, viteHtmlToMarkdownPlugin };
package/dist/index.mjs ADDED
@@ -0,0 +1,182 @@
1
+ import fs from "node:fs";
2
+ import path from "node:path";
3
+ import { htmlToMarkdown } from "mdream";
4
+
5
+ //#region src/plugin.ts
6
+ const DEFAULT_OPTIONS = {
7
+ include: ["**/*.html"],
8
+ exclude: ["**/node_modules/**"],
9
+ outputDir: "",
10
+ cacheEnabled: true,
11
+ mdreamOptions: {},
12
+ preserveStructure: true,
13
+ cacheTTL: 36e5,
14
+ verbose: true
15
+ };
16
+ function viteHtmlToMarkdownPlugin(userOptions = {}) {
17
+ const options = {
18
+ ...DEFAULT_OPTIONS,
19
+ ...userOptions
20
+ };
21
+ const markdownCache = new Map();
22
+ function log(message) {
23
+ if (options.verbose) console.log(`[vite-html-to-markdown] ${message}`);
24
+ }
25
+ function isValidCache(entry) {
26
+ return Date.now() - entry.timestamp < entry.ttl;
27
+ }
28
+ function getCachedMarkdown(key) {
29
+ if (!options.cacheEnabled) return null;
30
+ const entry = markdownCache.get(key);
31
+ if (entry && isValidCache(entry)) return entry.content;
32
+ if (entry) markdownCache.delete(key);
33
+ return null;
34
+ }
35
+ function setCachedMarkdown(key, content, ttl = options.cacheTTL) {
36
+ if (!options.cacheEnabled) return;
37
+ markdownCache.set(key, {
38
+ content,
39
+ timestamp: Date.now(),
40
+ ttl
41
+ });
42
+ }
43
+ async function convertHtmlToMarkdown(htmlContent, source) {
44
+ try {
45
+ const markdownContent = htmlToMarkdown(htmlContent, options.mdreamOptions);
46
+ log(`Converted ${source} to markdown (${markdownContent.length} chars)`);
47
+ return markdownContent;
48
+ } catch (error) {
49
+ throw new Error(`Failed to convert HTML to markdown: ${error instanceof Error ? error.message : String(error)}`);
50
+ }
51
+ }
52
+ async function handleMarkdownRequest(url, server = null, outDir) {
53
+ let basePath = url.slice(0, -3);
54
+ if (basePath === "/index") basePath = "/";
55
+ const source = server ? "dev" : outDir ? "preview" : "build";
56
+ const cacheKey = `${source}:${basePath}`;
57
+ const cached = getCachedMarkdown(cacheKey);
58
+ if (cached) {
59
+ log(`Cache hit for ${url}`);
60
+ return {
61
+ content: cached,
62
+ cached: true,
63
+ source
64
+ };
65
+ }
66
+ let htmlContent = null;
67
+ if (server) {
68
+ const possiblePaths = [
69
+ basePath.endsWith(".html") ? basePath : `${basePath}.html`,
70
+ basePath,
71
+ "/index.html"
72
+ ];
73
+ for (const htmlPath of possiblePaths) try {
74
+ const result = await server.transformRequest(htmlPath);
75
+ if (result?.code) {
76
+ htmlContent = result.code;
77
+ log(`Found HTML content for ${htmlPath}`);
78
+ break;
79
+ }
80
+ } catch {
81
+ continue;
82
+ }
83
+ } else if (outDir) {
84
+ const possiblePaths = [
85
+ path.join(outDir, `${basePath}.html`),
86
+ path.join(outDir, basePath, "index.html"),
87
+ path.join(outDir, "index.html")
88
+ ];
89
+ for (const htmlPath of possiblePaths) if (fs.existsSync(htmlPath)) {
90
+ htmlContent = fs.readFileSync(htmlPath, "utf-8");
91
+ log(`Read HTML file from ${htmlPath}`);
92
+ break;
93
+ }
94
+ }
95
+ if (!htmlContent) throw new Error(`No HTML content found for ${url}`);
96
+ const markdownContent = await convertHtmlToMarkdown(htmlContent, url);
97
+ setCachedMarkdown(cacheKey, markdownContent);
98
+ return {
99
+ content: markdownContent,
100
+ cached: false,
101
+ source
102
+ };
103
+ }
104
+ function matchesPattern(fileName, patterns) {
105
+ return patterns.some((pattern) => {
106
+ const regexPattern = pattern.replace(/\./g, "\\.").replace(/\*\*/g, ".*").replace(/\*/g, "[^/]*").replace(/\?/g, ".");
107
+ const regex = new RegExp(`^${regexPattern}$`);
108
+ return regex.test(fileName);
109
+ });
110
+ }
111
+ return {
112
+ name: "vite-html-to-markdown",
113
+ configureServer(server) {
114
+ server.middlewares.use(async (req, res, next) => {
115
+ if (!req.url?.endsWith(".md")) return next();
116
+ try {
117
+ const result = await handleMarkdownRequest(req.url, server);
118
+ res.setHeader("Content-Type", "text/markdown; charset=utf-8");
119
+ res.setHeader("Cache-Control", "no-cache");
120
+ res.setHeader("X-Markdown-Source", result.source);
121
+ res.setHeader("X-Markdown-Cached", result.cached.toString());
122
+ res.end(result.content);
123
+ log(`Served ${req.url} from ${result.source} (cached: ${result.cached})`);
124
+ } catch (error) {
125
+ const message = error instanceof Error ? error.message : String(error);
126
+ log(`Error serving ${req.url}: ${message}`);
127
+ res.statusCode = 404;
128
+ res.end(`HTML content not found for ${req.url}`);
129
+ }
130
+ });
131
+ },
132
+ generateBundle(outputOptions, bundle) {
133
+ const htmlFiles = Object.entries(bundle).filter(([fileName, file]) => {
134
+ return fileName.endsWith(".html") && file.type === "asset" && matchesPattern(fileName, options.include) && !matchesPattern(fileName, options.exclude);
135
+ });
136
+ log(`Processing ${htmlFiles.length} HTML files for markdown generation`);
137
+ for (const [fileName, htmlFile] of htmlFiles) try {
138
+ if (htmlFile.type !== "asset" || !("source" in htmlFile)) continue;
139
+ const htmlContent = htmlFile.source;
140
+ const markdownContent = htmlToMarkdown(htmlContent, options.mdreamOptions);
141
+ const markdownFileName = fileName.replace(".html", ".md");
142
+ const outputPath = options.preserveStructure ? `${options.outputDir}/${markdownFileName}` : `${options.outputDir}/${path.basename(markdownFileName)}`;
143
+ this.emitFile({
144
+ type: "asset",
145
+ fileName: outputPath,
146
+ source: markdownContent
147
+ });
148
+ log(`Generated markdown: ${outputPath}`);
149
+ } catch (error) {
150
+ const message = error instanceof Error ? error.message : String(error);
151
+ console.error(`[vite-html-to-markdown] Failed to convert ${fileName}: ${message}`);
152
+ }
153
+ },
154
+ configurePreviewServer(server) {
155
+ server.middlewares.use(async (req, res, next) => {
156
+ if (!req.url?.endsWith(".md")) return next();
157
+ try {
158
+ const outDir = server.config.build?.outDir || "dist";
159
+ const result = await handleMarkdownRequest(req.url, null, outDir);
160
+ res.setHeader("Content-Type", "text/markdown; charset=utf-8");
161
+ res.setHeader("Cache-Control", "public, max-age=3600");
162
+ res.setHeader("X-Markdown-Source", result.source);
163
+ res.setHeader("X-Markdown-Cached", result.cached.toString());
164
+ res.end(result.content);
165
+ log(`Served ${req.url} from ${result.source} (cached: ${result.cached})`);
166
+ } catch (error) {
167
+ const message = error instanceof Error ? error.message : String(error);
168
+ log(`Error in preview server for ${req.url}: ${message}`);
169
+ res.statusCode = 404;
170
+ res.end(`HTML content not found for ${req.url}`);
171
+ }
172
+ });
173
+ }
174
+ };
175
+ }
176
+
177
+ //#endregion
178
+ //#region src/index.ts
179
+ var src_default = viteHtmlToMarkdownPlugin;
180
+
181
+ //#endregion
182
+ export { src_default as default, viteHtmlToMarkdownPlugin };
package/package.json ADDED
@@ -0,0 +1,54 @@
1
+ {
2
+ "name": "@mdream/vite",
3
+ "type": "module",
4
+ "version": "0.7.0",
5
+ "description": "Vite plugin for HTML to Markdown conversion with on-demand generation",
6
+ "author": {
7
+ "name": "Harlan Wilton",
8
+ "email": "harlan@harlanzw.com",
9
+ "url": "https://harlanzw.com/"
10
+ },
11
+ "license": "MIT",
12
+ "keywords": [
13
+ "vite",
14
+ "plugin",
15
+ "html",
16
+ "markdown",
17
+ "converter",
18
+ "ssr",
19
+ "on-demand",
20
+ "llm"
21
+ ],
22
+ "exports": {
23
+ ".": {
24
+ "types": "./dist/index.d.mts",
25
+ "import": {
26
+ "types": "./dist/index.d.mts",
27
+ "default": "./dist/index.mjs"
28
+ },
29
+ "default": "./dist/index.mjs"
30
+ }
31
+ },
32
+ "main": "./dist/index.mjs",
33
+ "types": "./dist/index.d.mts",
34
+ "files": [
35
+ "dist"
36
+ ],
37
+ "peerDependencies": {
38
+ "vite": "^4.0.0 || ^5.0.0 || ^6.0.0"
39
+ },
40
+ "dependencies": {
41
+ "mdream": "0.7.0"
42
+ },
43
+ "devDependencies": {
44
+ "@types/node": "^22.0.0",
45
+ "vite": "^6.0.0"
46
+ },
47
+ "scripts": {
48
+ "build": "obuild",
49
+ "typecheck": "tsc --noEmit",
50
+ "dev:prepare": "obuild --stub",
51
+ "test": "vitest test",
52
+ "test:attw": "attw --pack"
53
+ }
54
+ }