@maizzle/framework 6.0.0-rc.4 → 6.0.0-rc.6
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/build.mjs +3 -2
- package/dist/build.mjs.map +1 -1
- package/dist/components/Body.vue +42 -0
- package/dist/components/Button.vue +65 -14
- package/dist/components/CodeBlock.vue +75 -0
- package/dist/components/CodeInline.vue +44 -0
- package/dist/components/Column.vue +61 -0
- package/dist/components/Container.vue +40 -0
- package/dist/components/Head.vue +8 -0
- package/dist/components/Html.vue +53 -0
- package/dist/components/Image.vue +70 -0
- package/dist/components/Overlap.vue +60 -0
- package/dist/components/Preview.vue +20 -0
- package/dist/components/Row.vue +80 -0
- package/dist/components/Spacer.vue +50 -7
- package/dist/composables/renderContext.d.mts +5 -0
- package/dist/composables/renderContext.d.mts.map +1 -1
- package/dist/composables/renderContext.mjs.map +1 -1
- package/dist/composables/usePreviewText.d.mts +24 -0
- package/dist/composables/usePreviewText.d.mts.map +1 -0
- package/dist/composables/usePreviewText.mjs +29 -0
- package/dist/composables/usePreviewText.mjs.map +1 -0
- package/dist/index.d.mts +3 -2
- package/dist/index.mjs +2 -1
- package/dist/render/createRenderer.d.mts +1 -0
- package/dist/render/createRenderer.d.mts.map +1 -1
- package/dist/render/createRenderer.mjs +60 -1
- package/dist/render/createRenderer.mjs.map +1 -1
- package/dist/render/index.mjs +3 -2
- package/dist/render/index.mjs.map +1 -1
- package/dist/serve.d.mts.map +1 -1
- package/dist/serve.mjs +31 -20
- package/dist/serve.mjs.map +1 -1
- package/dist/server/ui/pages/Preview.vue +11 -5
- package/dist/transformers/entities.d.mts.map +1 -1
- package/dist/transformers/entities.mjs +3 -0
- package/dist/transformers/entities.mjs.map +1 -1
- package/dist/transformers/filters/defaults.d.mts +6 -0
- package/dist/transformers/filters/defaults.d.mts.map +1 -0
- package/dist/transformers/filters/defaults.mjs +78 -0
- package/dist/transformers/filters/defaults.mjs.map +1 -0
- package/dist/transformers/filters/index.d.mts +22 -0
- package/dist/transformers/filters/index.d.mts.map +1 -0
- package/dist/transformers/filters/index.mjs +67 -0
- package/dist/transformers/filters/index.mjs.map +1 -0
- package/dist/transformers/index.d.mts +9 -8
- package/dist/transformers/index.d.mts.map +1 -1
- package/dist/transformers/index.mjs +15 -10
- package/dist/transformers/index.mjs.map +1 -1
- package/dist/transformers/tailwindcss.d.mts +6 -2
- package/dist/transformers/tailwindcss.d.mts.map +1 -1
- package/dist/transformers/tailwindcss.mjs +49 -21
- package/dist/transformers/tailwindcss.mjs.map +1 -1
- package/dist/types/config.d.mts +15 -1
- package/dist/types/config.d.mts.map +1 -1
- package/dist/types/index.d.mts +2 -2
- package/dist/utils/ast/serializer.d.mts +3 -2
- package/dist/utils/ast/serializer.d.mts.map +1 -1
- package/dist/utils/ast/serializer.mjs +24 -0
- package/dist/utils/ast/serializer.mjs.map +1 -1
- package/package.json +1 -1
package/dist/serve.mjs
CHANGED
|
@@ -151,20 +151,27 @@ function maizzleDevPlugin(config, renderer, configInput) {
|
|
|
151
151
|
const userWatchPaths = config.server?.watch ?? [];
|
|
152
152
|
const watchPaths = [...defaultWatchPaths, ...userWatchPaths];
|
|
153
153
|
for (const watchPath of watchPaths) server.watcher.add(watchPath);
|
|
154
|
-
server.watcher.on("add", (file) => {
|
|
155
|
-
if (isTemplateFile(file))
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
154
|
+
server.watcher.on("add", async (file) => {
|
|
155
|
+
if (isTemplateFile(file)) {
|
|
156
|
+
await renderer.invalidateAll();
|
|
157
|
+
server.ws.send({
|
|
158
|
+
type: "custom",
|
|
159
|
+
event: "maizzle:templates-changed"
|
|
160
|
+
});
|
|
161
|
+
}
|
|
159
162
|
});
|
|
160
|
-
server.watcher.on("unlink", (file) => {
|
|
161
|
-
if (isTemplateFile(file))
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
163
|
+
server.watcher.on("unlink", async (file) => {
|
|
164
|
+
if (isTemplateFile(file)) {
|
|
165
|
+
await renderer.invalidateAll();
|
|
166
|
+
server.ws.send({
|
|
167
|
+
type: "custom",
|
|
168
|
+
event: "maizzle:templates-changed"
|
|
169
|
+
});
|
|
170
|
+
}
|
|
165
171
|
});
|
|
166
172
|
server.watcher.on("change", async (file) => {
|
|
167
173
|
if (watchPaths.some((p) => file.endsWith(p))) config = await resolveConfig(configInput);
|
|
174
|
+
await renderer.invalidateAll();
|
|
168
175
|
if (isTemplateFile(file) || watchPaths.some((p) => file.endsWith(p))) server.ws.send({
|
|
169
176
|
type: "custom",
|
|
170
177
|
event: "maizzle:template-updated",
|
|
@@ -229,12 +236,13 @@ async function serveRenderedTemplate(url, config, renderer, res) {
|
|
|
229
236
|
}
|
|
230
237
|
try {
|
|
231
238
|
const absolutePath = resolve(match);
|
|
232
|
-
await renderer.
|
|
239
|
+
await renderer.invalidateAll();
|
|
233
240
|
const rendered = await renderer.render(absolutePath, config);
|
|
234
241
|
let html = rendered.html;
|
|
235
242
|
const templateConfig = rendered.templateConfig;
|
|
236
|
-
|
|
237
|
-
html =
|
|
243
|
+
const doctype = rendered.doctype ?? templateConfig.doctype ?? "<!DOCTYPE html>";
|
|
244
|
+
html = await runTransformers(html, templateConfig, absolutePath, doctype);
|
|
245
|
+
html = `${doctype}\n${html}`;
|
|
238
246
|
res.setHeader("Content-Type", "text/html");
|
|
239
247
|
res.end(html);
|
|
240
248
|
} catch (error) {
|
|
@@ -260,12 +268,13 @@ async function serveHighlightedSource(url, config, renderer, res) {
|
|
|
260
268
|
}
|
|
261
269
|
try {
|
|
262
270
|
const absolutePath = resolve(match);
|
|
263
|
-
await renderer.
|
|
271
|
+
await renderer.invalidateAll();
|
|
264
272
|
const rendered = await renderer.render(absolutePath, config);
|
|
265
273
|
let html = rendered.html;
|
|
266
274
|
const templateConfig = rendered.templateConfig;
|
|
267
|
-
|
|
268
|
-
html =
|
|
275
|
+
const doctype = rendered.doctype ?? templateConfig.doctype ?? "<!DOCTYPE html>";
|
|
276
|
+
html = await runTransformers(html, templateConfig, absolutePath, doctype);
|
|
277
|
+
html = `${doctype}\n${html}`;
|
|
269
278
|
const highlighted = (await getHighlighter()).codeToHtml(html, {
|
|
270
279
|
lang: "html",
|
|
271
280
|
theme: "laserwave",
|
|
@@ -315,11 +324,12 @@ async function servePlaintext(url, config, renderer, res) {
|
|
|
315
324
|
}
|
|
316
325
|
try {
|
|
317
326
|
const absolutePath = resolve(match);
|
|
318
|
-
await renderer.
|
|
327
|
+
await renderer.invalidateAll();
|
|
319
328
|
const rendered = await renderer.render(absolutePath, config);
|
|
320
329
|
let html = rendered.html;
|
|
321
330
|
const templateConfig = rendered.templateConfig;
|
|
322
|
-
|
|
331
|
+
const doctype = rendered.doctype ?? templateConfig.doctype ?? "<!DOCTYPE html>";
|
|
332
|
+
html = await runTransformers(html, templateConfig, absolutePath, doctype);
|
|
323
333
|
const plaintext = createPlaintext(html);
|
|
324
334
|
res.setHeader("Content-Type", "text/plain");
|
|
325
335
|
res.end(plaintext);
|
|
@@ -359,11 +369,12 @@ async function serveStats(url, config, renderer, res) {
|
|
|
359
369
|
}
|
|
360
370
|
try {
|
|
361
371
|
const absolutePath = resolve(match);
|
|
362
|
-
await renderer.
|
|
372
|
+
await renderer.invalidateAll();
|
|
363
373
|
const rendered = await renderer.render(absolutePath, config);
|
|
364
374
|
let html = rendered.html;
|
|
365
375
|
const templateConfig = rendered.templateConfig;
|
|
366
|
-
|
|
376
|
+
const doctype = rendered.doctype ?? templateConfig.doctype ?? "<!DOCTYPE html>";
|
|
377
|
+
html = await runTransformers(html, templateConfig, absolutePath, doctype);
|
|
367
378
|
const sizeBytes = Buffer.byteLength(html, "utf-8");
|
|
368
379
|
const totalImages = (html.match(/<img\b[^>]*>/gi) || []).length + (html.match(/url\s*\([^)]+\)/gi) || []).length;
|
|
369
380
|
const links = (html.match(/<a\b[^>]*href\s*=/gi) || []).length;
|
package/dist/serve.mjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"serve.mjs","names":[],"sources":["../src/serve.ts"],"sourcesContent":["import { readFileSync } from 'node:fs'\nimport { dirname, resolve, basename } from 'node:path'\nimport { fileURLToPath } from 'node:url'\nimport { createRequire } from 'node:module'\nimport { createServer, createLogger, type ViteDevServer } from 'vite'\nimport vue from '@vitejs/plugin-vue'\nimport tailwindcss from '@tailwindcss/vite'\nimport { glob } from 'tinyglobby'\nimport { createHighlighter, type Highlighter } from 'shiki'\nimport { createPlaintext } from './plaintext.ts'\nimport { resolveConfig } from './config/index.ts'\nimport { runTransformers } from './transformers/index.ts'\nimport { createRenderer, type Renderer } from './render/createRenderer.ts'\nimport { serveCompatibility } from './server/compatibility.ts'\nimport { serveLint } from './server/linter.ts'\nimport type { MaizzleConfig } from './types/index.ts'\n\nconst __dirname = dirname(fileURLToPath(import.meta.url))\nconst devUIDir = resolve(__dirname, 'server/ui')\n\nconst require = createRequire(import.meta.url)\nconst frameworkNodeModules = resolve(dirname(require.resolve('vue/package.json')), '..')\nconst vuePkgDir = dirname(require.resolve('vue/package.json'))\n\nexport interface ServeOptions {\n config?: Partial<MaizzleConfig> | string\n /** Expose the server on the network (e.g. --host) */\n host?: boolean | string\n /** When true, suppresses the banner/URL output (used by the Vite plugin, which prints its own) */\n silent?: boolean\n}\n\n/**\n * Start the Maizzle dev server.\n *\n * Creates two things:\n * 1. A Vite dev server for the dev UI (sidebar + preview, with Vue + Tailwind for the UI itself)\n * 2. A Renderer instance for SSR rendering email templates\n *\n * Template rendering goes through the Renderer, not the Vite dev server.\n */\nexport async function serve(options: ServeOptions = {}) {\n const start = performance.now()\n\n let config = await resolveConfig(options.config)\n const port = config.server?.port ?? 3000\n\n // Create a renderer for SSR rendering email templates (with dts for dev)\n const renderer = await createRenderer({ dts: true, markdown: config.markdown, root: config.root, componentDirs: [config.components?.source ?? []].flat() })\n\n const server = await createServer({\n configFile: false,\n plugins: [\n // Vue and Tailwind are only for the dev UI SPA, not for email templates\n vue(),\n tailwindcss(),\n maizzleDevPlugin(config, renderer, options.config),\n ],\n resolve: {\n dedupe: ['vue'],\n alias: [\n { find: '@', replacement: devUIDir },\n { find: 'vue', replacement: resolve(vuePkgDir, 'dist/vue.runtime.esm-bundler.js') },\n { find: 'vue-router', replacement: resolve(frameworkNodeModules, 'vue-router') },\n { find: 'reka-ui', replacement: resolve(frameworkNodeModules, 'reka-ui') },\n { find: '@vueuse/core', replacement: resolve(frameworkNodeModules, '@vueuse/core') },\n { find: '@vueuse/shared', replacement: resolve(frameworkNodeModules, '@vueuse/shared') },\n { find: 'lucide-vue-next', replacement: resolve(frameworkNodeModules, 'lucide-vue-next') },\n { find: 'class-variance-authority', replacement: resolve(frameworkNodeModules, 'class-variance-authority') },\n { find: 'clsx', replacement: resolve(frameworkNodeModules, 'clsx') },\n { find: 'tailwind-merge', replacement: resolve(frameworkNodeModules, 'tailwind-merge') },\n ],\n },\n cacheDir: resolve(devUIDir, '.vite'),\n optimizeDeps: {\n noDiscovery: true,\n include: [\n 'vue',\n 'vue-router',\n 'lucide-vue-next',\n '@vueuse/core',\n '@vueuse/shared',\n 'reka-ui',\n 'class-variance-authority',\n 'clsx',\n 'tailwind-merge',\n ],\n },\n server: {\n port,\n host: options.host,\n fs: {\n allow: [process.cwd(), config.root ?? process.cwd(), devUIDir, frameworkNodeModules],\n },\n },\n customLogger: customLogger(),\n })\n\n // Store renderer ref on server for cleanup\n const originalClose = server.close.bind(server)\n server.close = async () => {\n await renderer.close()\n return originalClose()\n }\n\n await server.listen()\n\n const startupTime = Math.round(performance.now() - start)\n\n if (!options.silent) {\n printBanner(server, startupTime)\n }\n\n // Expose startup time so the plugin can print it later\n ; (server as any)._maizzleStartupTime = startupTime\n\n return server\n}\n\n/**\n * Internal Vite plugin that adds Maizzle middleware and file watching to the dev UI server.\n */\nfunction maizzleDevPlugin(\n config: MaizzleConfig,\n renderer: Renderer,\n configInput: Partial<MaizzleConfig> | string | undefined,\n) {\n return {\n name: 'maizzle:dev',\n enforce: 'pre' as const,\n\n hotUpdate: {\n order: 'pre' as const,\n handler({ file }: { file: string }) {\n // Prevent Tailwind/Vue from triggering a full reload for email template files.\n // Maizzle handles these via custom HMR events in the watcher below.\n if (isTemplateFile(file)) {\n return []\n }\n },\n },\n\n configureServer(server: ViteDevServer) {\n // File watching\n const defaultWatchPaths = [\n 'maizzle.config.js',\n 'maizzle.config.ts',\n 'tailwind.config.js',\n 'tailwind.config.ts',\n ]\n\n const userWatchPaths = config.server?.watch ?? []\n const watchPaths = [...defaultWatchPaths, ...userWatchPaths]\n\n for (const watchPath of watchPaths) {\n server.watcher.add(watchPath)\n }\n\n server.watcher.on('add', (file) => {\n if (isTemplateFile(file)) {\n server.ws.send({ type: 'custom', event: 'maizzle:templates-changed' })\n }\n })\n\n server.watcher.on('unlink', (file) => {\n if (isTemplateFile(file)) {\n server.ws.send({ type: 'custom', event: 'maizzle:templates-changed' })\n }\n })\n\n server.watcher.on('change', async (file) => {\n if (watchPaths.some(p => file.endsWith(p))) {\n config = await resolveConfig(configInput)\n }\n\n if (\n isTemplateFile(file)\n || watchPaths.some(p => file.endsWith(p))\n ) {\n server.ws.send({ type: 'custom', event: 'maizzle:template-updated', data: { file } })\n }\n })\n\n // API middleware (before Vite's middleware)\n server.middlewares.use(async (req: any, res: any, next: any) => {\n const url = req.url || '/'\n\n if (url === '/__maizzle/templates') {\n return serveTemplateList(config, res)\n }\n\n if (url.startsWith('/__maizzle/render/')) {\n return await serveRenderedTemplate(url, config, renderer, res)\n }\n\n if (url.startsWith('/__maizzle/source/')) {\n return await serveHighlightedSource(url, config, renderer, res)\n }\n\n if (url.startsWith('/__maizzle/compatibility/')) {\n return await serveCompatibility(url, config, res)\n }\n\n if (url.startsWith('/__maizzle/lint/')) {\n return await serveLint(url, config, res)\n }\n\n if (url.startsWith('/__maizzle/vue-source/')) {\n return await serveVueSource(url, config, res)\n }\n\n if (url.startsWith('/__maizzle/plaintext/')) {\n return await servePlaintext(url, config, renderer, res)\n }\n\n if (url.startsWith('/__maizzle/stats/')) {\n return await serveStats(url, config, renderer, res)\n }\n\n next()\n })\n\n // Dev UI fallback (after Vite's middleware)\n return () => {\n server.middlewares.use(async (req: any, res: any, next: any) => {\n if (isNavigationRequest(req)) {\n return await serveDevUI(server, res, req.url || '/')\n }\n\n next()\n })\n }\n },\n }\n}\n\nfunction isTemplateFile(file: string): boolean {\n return (file.endsWith('.vue') || file.endsWith('.md')) && !file.includes('server/ui')\n}\n\nfunction isNavigationRequest(req: any): boolean {\n const accept = req.headers?.accept || ''\n return req.method === 'GET' && accept.includes('text/html')\n}\n\nasync function serveDevUI(server: ViteDevServer, res: any, url: string) {\n let indexHtml = readFileSync(resolve(devUIDir, 'index.html'), 'utf-8')\n\n indexHtml = indexHtml.replace('./main.ts', `/@fs/${resolve(devUIDir, 'main.ts')}`)\n indexHtml = indexHtml.replace('./favicon.svg', `/@fs/${resolve(devUIDir, 'favicon.svg')}`)\n\n const transformed = await server.transformIndexHtml(url, indexHtml)\n\n res.setHeader('Content-Type', 'text/html')\n res.end(transformed)\n}\n\nasync function serveTemplateList(config: MaizzleConfig, res: any) {\n const contentPatterns = config.content ?? ['emails/**/*.vue']\n const templates = await glob(contentPatterns)\n\n const data = templates.map(t => ({\n name: basename(t).replace(/\\.(vue|md)$/, ''),\n path: t,\n href: '/' + t.replace(/\\.(vue|md)$/, ''),\n }))\n\n res.setHeader('Content-Type', 'application/json')\n res.end(JSON.stringify(data))\n}\n\n/**\n * SSR render a .vue template using the Renderer (not the dev UI server).\n */\nasync function serveRenderedTemplate(url: string, config: MaizzleConfig, renderer: Renderer, res: any) {\n const templateSlug = url.replace('/__maizzle/render/', '').replace(/\\?.*$/, '')\n\n const contentPatterns = config.content ?? ['emails/**/*.vue']\n const templates = await glob(contentPatterns)\n const match = templates.find(t => t.replace(/\\.(vue|md)$/, '') === templateSlug)\n\n if (!match) {\n res.statusCode = 404\n res.end('Template not found')\n return\n }\n\n try {\n const absolutePath = resolve(match)\n\n // Invalidate the module so changes are picked up\n await renderer.invalidate(absolutePath)\n\n const rendered = await renderer.render(absolutePath, config)\n let html = rendered.html\n\n const templateConfig = rendered.templateConfig\n\n html = await runTransformers(html, templateConfig, absolutePath)\n\n const doctype = rendered.doctype ?? templateConfig.doctype ?? '<!DOCTYPE html>'\n html = `${doctype}\\n${html}`\n\n res.setHeader('Content-Type', 'text/html')\n res.end(html)\n } catch (error: any) {\n res.statusCode = 500\n res.end(`<pre>${error.stack || error.message}</pre>`)\n }\n}\n\nlet highlighter: Highlighter | null = null\n\nasync function getHighlighter() {\n if (!highlighter) {\n highlighter = await createHighlighter({\n themes: ['laserwave'],\n langs: ['html', 'vue'],\n })\n }\n return highlighter\n}\n\nasync function serveHighlightedSource(url: string, config: MaizzleConfig, renderer: Renderer, res: any) {\n const templateSlug = url.replace('/__maizzle/source/', '').replace(/\\?.*$/, '')\n\n const contentPatterns = config.content ?? ['emails/**/*.vue']\n const templates = await glob(contentPatterns)\n const match = templates.find(t => t.replace(/\\.(vue|md)$/, '') === templateSlug)\n\n if (!match) {\n res.statusCode = 404\n res.end('Template not found')\n return\n }\n\n try {\n const absolutePath = resolve(match)\n\n await renderer.invalidate(absolutePath)\n\n const rendered = await renderer.render(absolutePath, config)\n let html = rendered.html\n\n const templateConfig = rendered.templateConfig\n html = await runTransformers(html, templateConfig, absolutePath)\n\n const doctype = rendered.doctype ?? templateConfig.doctype ?? '<!DOCTYPE html>'\n html = `${doctype}\\n${html}`\n\n const hl = await getHighlighter()\n const highlighted = hl.codeToHtml(html, {\n lang: 'html',\n theme: 'laserwave',\n transformers: [{\n line(node, line) {\n node.properties['data-line'] = line\n },\n }],\n })\n\n res.setHeader('Content-Type', 'text/html')\n res.end(highlighted)\n } catch (error: any) {\n res.statusCode = 500\n res.end(`<pre>${error.stack || error.message}</pre>`)\n }\n}\n\nasync function serveVueSource(url: string, config: MaizzleConfig, res: any) {\n const templateSlug = url.replace('/__maizzle/vue-source/', '').replace(/\\?.*$/, '')\n\n const contentPatterns = config.content ?? ['emails/**/*.vue']\n const templates = await glob(contentPatterns)\n const match = templates.find(t => t.replace(/\\.(vue|md)$/, '') === templateSlug)\n\n if (!match) {\n res.statusCode = 404\n res.end('Template not found')\n return\n }\n\n try {\n const source = readFileSync(resolve(match), 'utf-8')\n const lang = match.endsWith('.md') ? 'html' : 'vue'\n\n const hl = await getHighlighter()\n const highlighted = hl.codeToHtml(source, {\n lang,\n theme: 'laserwave',\n transformers: [{\n line(node, line) {\n node.properties['data-line'] = line\n },\n }],\n })\n\n res.setHeader('Content-Type', 'text/html')\n res.end(highlighted)\n } catch (error: any) {\n res.statusCode = 500\n res.end(`<pre>${error.stack || error.message}</pre>`)\n }\n}\n\nasync function servePlaintext(url: string, config: MaizzleConfig, renderer: Renderer, res: any) {\n const templateSlug = url.replace('/__maizzle/plaintext/', '').replace(/\\?.*$/, '')\n\n const contentPatterns = config.content ?? ['emails/**/*.vue']\n const templates = await glob(contentPatterns)\n const match = templates.find(t => t.replace(/\\.(vue|md)$/, '') === templateSlug)\n\n if (!match) {\n res.statusCode = 404\n res.end('Template not found')\n return\n }\n\n try {\n const absolutePath = resolve(match)\n await renderer.invalidate(absolutePath)\n\n const rendered = await renderer.render(absolutePath, config)\n let html = rendered.html\n const templateConfig = rendered.templateConfig\n html = await runTransformers(html, templateConfig, absolutePath)\n\n const plaintext = createPlaintext(html)\n\n res.setHeader('Content-Type', 'text/plain')\n res.end(plaintext)\n } catch (error: any) {\n res.statusCode = 500\n res.end(error.message)\n }\n}\n\nfunction humanFileSize(bytes: number, si = false, dp = 2) {\n const threshold = si ? 1000 : 1024\n\n if (Math.abs(bytes) < threshold) {\n return bytes + ' B'\n }\n\n const units = ['KB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB']\n let u = -1\n const r = 10 ** dp\n\n do {\n bytes /= threshold\n ++u\n } while (Math.round(Math.abs(bytes) * r) / r >= threshold && u < units.length - 1)\n\n return bytes.toFixed(dp) + ' ' + units[u]\n}\n\nasync function serveStats(url: string, config: MaizzleConfig, renderer: Renderer, res: any) {\n const templateSlug = url.replace('/__maizzle/stats/', '').replace(/\\?.*$/, '')\n\n const contentPatterns = config.content ?? ['emails/**/*.vue']\n const templates = await glob(contentPatterns)\n const match = templates.find(t => t.replace(/\\.(vue|md)$/, '') === templateSlug)\n\n if (!match) {\n res.statusCode = 404\n res.end(JSON.stringify({ error: 'Template not found' }))\n return\n }\n\n try {\n const absolutePath = resolve(match)\n await renderer.invalidate(absolutePath)\n\n const rendered = await renderer.render(absolutePath, config)\n let html = rendered.html\n const templateConfig = rendered.templateConfig\n html = await runTransformers(html, templateConfig, absolutePath)\n\n const sizeBytes = Buffer.byteLength(html, 'utf-8')\n\n // Count images: <img> tags and CSS background images\n const imgTags = (html.match(/<img\\b[^>]*>/gi) || []).length\n const bgImages = (html.match(/url\\s*\\([^)]+\\)/gi) || []).length\n const totalImages = imgTags + bgImages\n\n // Count links\n const links = (html.match(/<a\\b[^>]*href\\s*=/gi) || []).length\n\n res.setHeader('Content-Type', 'application/json')\n res.end(JSON.stringify({\n size: {\n bytes: sizeBytes,\n formatted: humanFileSize(sizeBytes),\n },\n images: totalImages,\n links,\n }))\n } catch (error: any) {\n res.statusCode = 500\n res.end(JSON.stringify({ error: error.message }))\n }\n}\n\nexport function printBanner(server: ViteDevServer, startupTime?: number) {\n const info = server.config.logger.info\n const time = startupTime ?? (server as any)._maizzleStartupTime\n\n info('')\n info(` \\x1b[32m\\x1b[1mMAIZZLE\\x1b[0m\\x1b[32m v6.0.0\\x1b[0m \\x1b[2mready in\\x1b[0m \\x1b[1m${time}\\x1b[0m ms`)\n info('')\n server.printUrls()\n info('')\n}\n\nfunction customLogger() {\n const logger = createLogger('info')\n const warn = logger.warn\n\n logger.warn = (message, options) => {\n if (typeof message === 'string' && message.includes('<tr> cannot be child of <table>')) {\n return\n }\n\n warn(message, options)\n }\n\n return logger\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"serve.mjs","names":[],"sources":["../src/serve.ts"],"sourcesContent":["import { readFileSync } from 'node:fs'\nimport { dirname, resolve, basename } from 'node:path'\nimport { fileURLToPath } from 'node:url'\nimport { createRequire } from 'node:module'\nimport { createServer, createLogger, type ViteDevServer } from 'vite'\nimport vue from '@vitejs/plugin-vue'\nimport tailwindcss from '@tailwindcss/vite'\nimport { glob } from 'tinyglobby'\nimport { createHighlighter, type Highlighter } from 'shiki'\nimport { createPlaintext } from './plaintext.ts'\nimport { resolveConfig } from './config/index.ts'\nimport { runTransformers } from './transformers/index.ts'\nimport { createRenderer, type Renderer } from './render/createRenderer.ts'\nimport { serveCompatibility } from './server/compatibility.ts'\nimport { serveLint } from './server/linter.ts'\nimport type { MaizzleConfig } from './types/index.ts'\n\nconst __dirname = dirname(fileURLToPath(import.meta.url))\nconst devUIDir = resolve(__dirname, 'server/ui')\n\nconst require = createRequire(import.meta.url)\nconst frameworkNodeModules = resolve(dirname(require.resolve('vue/package.json')), '..')\nconst vuePkgDir = dirname(require.resolve('vue/package.json'))\n\nexport interface ServeOptions {\n config?: Partial<MaizzleConfig> | string\n /** Expose the server on the network (e.g. --host) */\n host?: boolean | string\n /** When true, suppresses the banner/URL output (used by the Vite plugin, which prints its own) */\n silent?: boolean\n}\n\n/**\n * Start the Maizzle dev server.\n *\n * Creates two things:\n * 1. A Vite dev server for the dev UI (sidebar + preview, with Vue + Tailwind for the UI itself)\n * 2. A Renderer instance for SSR rendering email templates\n *\n * Template rendering goes through the Renderer, not the Vite dev server.\n */\nexport async function serve(options: ServeOptions = {}) {\n const start = performance.now()\n\n let config = await resolveConfig(options.config)\n const port = config.server?.port ?? 3000\n\n // Create a renderer for SSR rendering email templates (with dts for dev)\n const renderer = await createRenderer({ dts: true, markdown: config.markdown, root: config.root, componentDirs: [config.components?.source ?? []].flat() })\n\n const server = await createServer({\n configFile: false,\n plugins: [\n // Vue and Tailwind are only for the dev UI SPA, not for email templates\n vue(),\n tailwindcss(),\n maizzleDevPlugin(config, renderer, options.config),\n ],\n resolve: {\n dedupe: ['vue'],\n alias: [\n { find: '@', replacement: devUIDir },\n { find: 'vue', replacement: resolve(vuePkgDir, 'dist/vue.runtime.esm-bundler.js') },\n { find: 'vue-router', replacement: resolve(frameworkNodeModules, 'vue-router') },\n { find: 'reka-ui', replacement: resolve(frameworkNodeModules, 'reka-ui') },\n { find: '@vueuse/core', replacement: resolve(frameworkNodeModules, '@vueuse/core') },\n { find: '@vueuse/shared', replacement: resolve(frameworkNodeModules, '@vueuse/shared') },\n { find: 'lucide-vue-next', replacement: resolve(frameworkNodeModules, 'lucide-vue-next') },\n { find: 'class-variance-authority', replacement: resolve(frameworkNodeModules, 'class-variance-authority') },\n { find: 'clsx', replacement: resolve(frameworkNodeModules, 'clsx') },\n { find: 'tailwind-merge', replacement: resolve(frameworkNodeModules, 'tailwind-merge') },\n ],\n },\n cacheDir: resolve(devUIDir, '.vite'),\n optimizeDeps: {\n noDiscovery: true,\n include: [\n 'vue',\n 'vue-router',\n 'lucide-vue-next',\n '@vueuse/core',\n '@vueuse/shared',\n 'reka-ui',\n 'class-variance-authority',\n 'clsx',\n 'tailwind-merge',\n ],\n },\n server: {\n port,\n host: options.host,\n fs: {\n allow: [process.cwd(), config.root ?? process.cwd(), devUIDir, frameworkNodeModules],\n },\n },\n customLogger: customLogger(),\n })\n\n // Store renderer ref on server for cleanup\n const originalClose = server.close.bind(server)\n server.close = async () => {\n await renderer.close()\n return originalClose()\n }\n\n await server.listen()\n\n const startupTime = Math.round(performance.now() - start)\n\n if (!options.silent) {\n printBanner(server, startupTime)\n }\n\n // Expose startup time so the plugin can print it later\n ; (server as any)._maizzleStartupTime = startupTime\n\n return server\n}\n\n/**\n * Internal Vite plugin that adds Maizzle middleware and file watching to the dev UI server.\n */\nfunction maizzleDevPlugin(\n config: MaizzleConfig,\n renderer: Renderer,\n configInput: Partial<MaizzleConfig> | string | undefined,\n) {\n return {\n name: 'maizzle:dev',\n enforce: 'pre' as const,\n\n hotUpdate: {\n order: 'pre' as const,\n handler({ file }: { file: string }) {\n // Prevent Tailwind/Vue from triggering a full reload for email template files.\n // Maizzle handles these via custom HMR events in the watcher below.\n if (isTemplateFile(file)) {\n return []\n }\n },\n },\n\n configureServer(server: ViteDevServer) {\n // File watching\n const defaultWatchPaths = [\n 'maizzle.config.js',\n 'maizzle.config.ts',\n 'tailwind.config.js',\n 'tailwind.config.ts',\n ]\n\n const userWatchPaths = config.server?.watch ?? []\n const watchPaths = [...defaultWatchPaths, ...userWatchPaths]\n\n for (const watchPath of watchPaths) {\n server.watcher.add(watchPath)\n }\n\n server.watcher.on('add', async (file) => {\n if (isTemplateFile(file)) {\n await renderer.invalidateAll()\n server.ws.send({ type: 'custom', event: 'maizzle:templates-changed' })\n }\n })\n\n server.watcher.on('unlink', async (file) => {\n if (isTemplateFile(file)) {\n await renderer.invalidateAll()\n server.ws.send({ type: 'custom', event: 'maizzle:templates-changed' })\n }\n })\n\n server.watcher.on('change', async (file) => {\n if (watchPaths.some(p => file.endsWith(p))) {\n config = await resolveConfig(configInput)\n }\n\n // Invalidate all renderer modules so component and config changes\n // are picked up on the next render (Tailwind recompiles with fresh content)\n await renderer.invalidateAll()\n\n if (\n isTemplateFile(file)\n || watchPaths.some(p => file.endsWith(p))\n ) {\n server.ws.send({ type: 'custom', event: 'maizzle:template-updated', data: { file } })\n }\n })\n\n // API middleware (before Vite's middleware)\n server.middlewares.use(async (req: any, res: any, next: any) => {\n const url = req.url || '/'\n\n if (url === '/__maizzle/templates') {\n return serveTemplateList(config, res)\n }\n\n if (url.startsWith('/__maizzle/render/')) {\n return await serveRenderedTemplate(url, config, renderer, res)\n }\n\n if (url.startsWith('/__maizzle/source/')) {\n return await serveHighlightedSource(url, config, renderer, res)\n }\n\n if (url.startsWith('/__maizzle/compatibility/')) {\n return await serveCompatibility(url, config, res)\n }\n\n if (url.startsWith('/__maizzle/lint/')) {\n return await serveLint(url, config, res)\n }\n\n if (url.startsWith('/__maizzle/vue-source/')) {\n return await serveVueSource(url, config, res)\n }\n\n if (url.startsWith('/__maizzle/plaintext/')) {\n return await servePlaintext(url, config, renderer, res)\n }\n\n if (url.startsWith('/__maizzle/stats/')) {\n return await serveStats(url, config, renderer, res)\n }\n\n next()\n })\n\n // Dev UI fallback (after Vite's middleware)\n return () => {\n server.middlewares.use(async (req: any, res: any, next: any) => {\n if (isNavigationRequest(req)) {\n return await serveDevUI(server, res, req.url || '/')\n }\n\n next()\n })\n }\n },\n }\n}\n\nfunction isTemplateFile(file: string): boolean {\n return (file.endsWith('.vue') || file.endsWith('.md')) && !file.includes('server/ui')\n}\n\nfunction isNavigationRequest(req: any): boolean {\n const accept = req.headers?.accept || ''\n return req.method === 'GET' && accept.includes('text/html')\n}\n\nasync function serveDevUI(server: ViteDevServer, res: any, url: string) {\n let indexHtml = readFileSync(resolve(devUIDir, 'index.html'), 'utf-8')\n\n indexHtml = indexHtml.replace('./main.ts', `/@fs/${resolve(devUIDir, 'main.ts')}`)\n indexHtml = indexHtml.replace('./favicon.svg', `/@fs/${resolve(devUIDir, 'favicon.svg')}`)\n\n const transformed = await server.transformIndexHtml(url, indexHtml)\n\n res.setHeader('Content-Type', 'text/html')\n res.end(transformed)\n}\n\nasync function serveTemplateList(config: MaizzleConfig, res: any) {\n const contentPatterns = config.content ?? ['emails/**/*.vue']\n const templates = await glob(contentPatterns)\n\n const data = templates.map(t => ({\n name: basename(t).replace(/\\.(vue|md)$/, ''),\n path: t,\n href: '/' + t.replace(/\\.(vue|md)$/, ''),\n }))\n\n res.setHeader('Content-Type', 'application/json')\n res.end(JSON.stringify(data))\n}\n\n/**\n * SSR render a .vue template using the Renderer (not the dev UI server).\n */\nasync function serveRenderedTemplate(url: string, config: MaizzleConfig, renderer: Renderer, res: any) {\n const templateSlug = url.replace('/__maizzle/render/', '').replace(/\\?.*$/, '')\n\n const contentPatterns = config.content ?? ['emails/**/*.vue']\n const templates = await glob(contentPatterns)\n const match = templates.find(t => t.replace(/\\.(vue|md)$/, '') === templateSlug)\n\n if (!match) {\n res.statusCode = 404\n res.end('Template not found')\n return\n }\n\n try {\n const absolutePath = resolve(match)\n\n // Invalidate all modules so template + component changes are picked up\n await renderer.invalidateAll()\n\n const rendered = await renderer.render(absolutePath, config)\n let html = rendered.html\n\n const templateConfig = rendered.templateConfig\n const doctype = rendered.doctype ?? templateConfig.doctype ?? '<!DOCTYPE html>'\n\n html = await runTransformers(html, templateConfig, absolutePath, doctype)\n html = `${doctype}\\n${html}`\n\n res.setHeader('Content-Type', 'text/html')\n res.end(html)\n } catch (error: any) {\n res.statusCode = 500\n res.end(`<pre>${error.stack || error.message}</pre>`)\n }\n}\n\nlet highlighter: Highlighter | null = null\n\nasync function getHighlighter() {\n if (!highlighter) {\n highlighter = await createHighlighter({\n themes: ['laserwave'],\n langs: ['html', 'vue'],\n })\n }\n return highlighter\n}\n\nasync function serveHighlightedSource(url: string, config: MaizzleConfig, renderer: Renderer, res: any) {\n const templateSlug = url.replace('/__maizzle/source/', '').replace(/\\?.*$/, '')\n\n const contentPatterns = config.content ?? ['emails/**/*.vue']\n const templates = await glob(contentPatterns)\n const match = templates.find(t => t.replace(/\\.(vue|md)$/, '') === templateSlug)\n\n if (!match) {\n res.statusCode = 404\n res.end('Template not found')\n return\n }\n\n try {\n const absolutePath = resolve(match)\n\n await renderer.invalidateAll()\n\n const rendered = await renderer.render(absolutePath, config)\n let html = rendered.html\n\n const templateConfig = rendered.templateConfig\n const doctype = rendered.doctype ?? templateConfig.doctype ?? '<!DOCTYPE html>'\n html = await runTransformers(html, templateConfig, absolutePath, doctype)\n\n html = `${doctype}\\n${html}`\n\n const hl = await getHighlighter()\n const highlighted = hl.codeToHtml(html, {\n lang: 'html',\n theme: 'laserwave',\n transformers: [{\n line(node, line) {\n node.properties['data-line'] = line\n },\n }],\n })\n\n res.setHeader('Content-Type', 'text/html')\n res.end(highlighted)\n } catch (error: any) {\n res.statusCode = 500\n res.end(`<pre>${error.stack || error.message}</pre>`)\n }\n}\n\nasync function serveVueSource(url: string, config: MaizzleConfig, res: any) {\n const templateSlug = url.replace('/__maizzle/vue-source/', '').replace(/\\?.*$/, '')\n\n const contentPatterns = config.content ?? ['emails/**/*.vue']\n const templates = await glob(contentPatterns)\n const match = templates.find(t => t.replace(/\\.(vue|md)$/, '') === templateSlug)\n\n if (!match) {\n res.statusCode = 404\n res.end('Template not found')\n return\n }\n\n try {\n const source = readFileSync(resolve(match), 'utf-8')\n const lang = match.endsWith('.md') ? 'html' : 'vue'\n\n const hl = await getHighlighter()\n const highlighted = hl.codeToHtml(source, {\n lang,\n theme: 'laserwave',\n transformers: [{\n line(node, line) {\n node.properties['data-line'] = line\n },\n }],\n })\n\n res.setHeader('Content-Type', 'text/html')\n res.end(highlighted)\n } catch (error: any) {\n res.statusCode = 500\n res.end(`<pre>${error.stack || error.message}</pre>`)\n }\n}\n\nasync function servePlaintext(url: string, config: MaizzleConfig, renderer: Renderer, res: any) {\n const templateSlug = url.replace('/__maizzle/plaintext/', '').replace(/\\?.*$/, '')\n\n const contentPatterns = config.content ?? ['emails/**/*.vue']\n const templates = await glob(contentPatterns)\n const match = templates.find(t => t.replace(/\\.(vue|md)$/, '') === templateSlug)\n\n if (!match) {\n res.statusCode = 404\n res.end('Template not found')\n return\n }\n\n try {\n const absolutePath = resolve(match)\n await renderer.invalidateAll()\n\n const rendered = await renderer.render(absolutePath, config)\n let html = rendered.html\n const templateConfig = rendered.templateConfig\n const doctype = rendered.doctype ?? templateConfig.doctype ?? '<!DOCTYPE html>'\n html = await runTransformers(html, templateConfig, absolutePath, doctype)\n\n const plaintext = createPlaintext(html)\n\n res.setHeader('Content-Type', 'text/plain')\n res.end(plaintext)\n } catch (error: any) {\n res.statusCode = 500\n res.end(error.message)\n }\n}\n\nfunction humanFileSize(bytes: number, si = false, dp = 2) {\n const threshold = si ? 1000 : 1024\n\n if (Math.abs(bytes) < threshold) {\n return bytes + ' B'\n }\n\n const units = ['KB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB']\n let u = -1\n const r = 10 ** dp\n\n do {\n bytes /= threshold\n ++u\n } while (Math.round(Math.abs(bytes) * r) / r >= threshold && u < units.length - 1)\n\n return bytes.toFixed(dp) + ' ' + units[u]\n}\n\nasync function serveStats(url: string, config: MaizzleConfig, renderer: Renderer, res: any) {\n const templateSlug = url.replace('/__maizzle/stats/', '').replace(/\\?.*$/, '')\n\n const contentPatterns = config.content ?? ['emails/**/*.vue']\n const templates = await glob(contentPatterns)\n const match = templates.find(t => t.replace(/\\.(vue|md)$/, '') === templateSlug)\n\n if (!match) {\n res.statusCode = 404\n res.end(JSON.stringify({ error: 'Template not found' }))\n return\n }\n\n try {\n const absolutePath = resolve(match)\n await renderer.invalidateAll()\n\n const rendered = await renderer.render(absolutePath, config)\n let html = rendered.html\n const templateConfig = rendered.templateConfig\n const doctype = rendered.doctype ?? templateConfig.doctype ?? '<!DOCTYPE html>'\n html = await runTransformers(html, templateConfig, absolutePath, doctype)\n\n const sizeBytes = Buffer.byteLength(html, 'utf-8')\n\n // Count images: <img> tags and CSS background images\n const imgTags = (html.match(/<img\\b[^>]*>/gi) || []).length\n const bgImages = (html.match(/url\\s*\\([^)]+\\)/gi) || []).length\n const totalImages = imgTags + bgImages\n\n // Count links\n const links = (html.match(/<a\\b[^>]*href\\s*=/gi) || []).length\n\n res.setHeader('Content-Type', 'application/json')\n res.end(JSON.stringify({\n size: {\n bytes: sizeBytes,\n formatted: humanFileSize(sizeBytes),\n },\n images: totalImages,\n links,\n }))\n } catch (error: any) {\n res.statusCode = 500\n res.end(JSON.stringify({ error: error.message }))\n }\n}\n\nexport function printBanner(server: ViteDevServer, startupTime?: number) {\n const info = server.config.logger.info\n const time = startupTime ?? (server as any)._maizzleStartupTime\n\n info('')\n info(` \\x1b[32m\\x1b[1mMAIZZLE\\x1b[0m\\x1b[32m v6.0.0\\x1b[0m \\x1b[2mready in\\x1b[0m \\x1b[1m${time}\\x1b[0m ms`)\n info('')\n server.printUrls()\n info('')\n}\n\nfunction customLogger() {\n const logger = createLogger('info')\n const warn = logger.warn\n\n logger.warn = (message, options) => {\n if (typeof message === 'string' && message.includes('<tr> cannot be child of <table>')) {\n return\n }\n\n warn(message, options)\n }\n\n return logger\n}\n"],"mappings":"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"}
|
|
@@ -176,7 +176,11 @@ watch(() => route.params.template, () => {
|
|
|
176
176
|
}, { immediate: true })
|
|
177
177
|
|
|
178
178
|
watch(viewMode, (mode) => {
|
|
179
|
-
if (mode === 'source'
|
|
179
|
+
if (mode === 'source') {
|
|
180
|
+
if (sourceView.value === 'compiled' && !sourceHtml.value) fetchSource()
|
|
181
|
+
if (sourceView.value === 'vue' && !vueSourceHtml.value) fetchVueSource()
|
|
182
|
+
if (sourceView.value === 'plaintext' && !plaintextContent.value) fetchPlaintext()
|
|
183
|
+
}
|
|
180
184
|
})
|
|
181
185
|
|
|
182
186
|
watch(sourceView, (view) => {
|
|
@@ -191,11 +195,13 @@ if ((import.meta as any).hot) {
|
|
|
191
195
|
fetchCompatibility()
|
|
192
196
|
fetchLint()
|
|
193
197
|
fetchStats()
|
|
194
|
-
// Clear non-active source views so they re-fetch when switched to
|
|
195
|
-
if (sourceView.value !== 'compiled') sourceHtml.value = ''
|
|
196
|
-
if (sourceView.value !== 'vue') vueSourceHtml.value = ''
|
|
197
|
-
if (sourceView.value !== 'plaintext') plaintextContent.value = ''
|
|
198
198
|
|
|
199
|
+
// Always clear all source views so they re-fetch when switched to
|
|
200
|
+
sourceHtml.value = ''
|
|
201
|
+
vueSourceHtml.value = ''
|
|
202
|
+
plaintextContent.value = ''
|
|
203
|
+
|
|
204
|
+
// Re-fetch the active source view immediately if currently visible
|
|
199
205
|
if (viewMode.value === 'source') {
|
|
200
206
|
if (sourceView.value === 'compiled') fetchSource()
|
|
201
207
|
if (sourceView.value === 'vue') fetchVueSource()
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"entities.d.mts","names":[],"sources":["../../src/transformers/entities.ts"],"mappings":";;;;
|
|
1
|
+
{"version":3,"file":"entities.d.mts","names":[],"sources":["../../src/transformers/entities.ts"],"mappings":";;;;iBA8BgB,QAAA,CAAS,GAAA,EAAK,SAAA,IAAa,MAAA,GAAQ,cAAA,GAAwB,SAAA"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"entities.mjs","names":["merge"],"sources":["../../src/transformers/entities.ts"],"sourcesContent":["import { defu as merge } from 'defu'\nimport { walk } from '../utils/ast/index.ts'\nimport type { ChildNode } from 'domhandler'\nimport type { EntitiesConfig } from '../types/index.ts'\n\nconst DEFAULT_ENTITIES: Record<string, string> = {\n '\\u200D': '‍',\n '\\u200C': '‌',\n '\\u00A0': ' ',\n '\\u200B': '​',\n '\\u2003': ' ',\n '\\u2028': '&LineSeparator;',\n '\\u2029': '&ParagraphSeparator;',\n '\\u00B7': '·',\n '\\u2013': '–',\n '\\u2014': '—',\n '\\u2018': '‘',\n '\\u2019': '’',\n '\\u201C': '“',\n '\\u201D': '”',\n '\\u00AB': '«',\n '\\u00BB': '»',\n '\\u2022': '•',\n '\\u2039': '‹',\n '\\u203A': '›'\n}\n\nexport function entities(dom: ChildNode[], config: EntitiesConfig = true): ChildNode[] {\n if (!config) return dom\n\n const map = typeof config === 'object'\n ? merge(config as Record<string, string>, DEFAULT_ENTITIES)\n : DEFAULT_ENTITIES\n\n walk(dom, (node) => {\n if (node.type === 'text') {\n for (const [char, entity] of Object.entries(map)) {\n node.data = node.data.split(char).join(entity)\n }\n }\n })\n\n return dom\n}\n"],"mappings":";;;;;AAKA,MAAM,mBAA2C;CAC/C,KAAU;CACV,KAAU;CACV,QAAU;CACV,KAAU;CACV,KAAU;CACV,UAAU;CACV,UAAU;CACV,KAAU;CACV,KAAU;CACV,KAAU;CACV,KAAU;CACV,KAAU;CACV,KAAU;CACV,KAAU;CACV,KAAU;CACV,KAAU;CACV,KAAU;CACV,KAAU;CACV,KAAU;CACX;AAED,SAAgB,SAAS,KAAkB,SAAyB,MAAmB;AACrF,KAAI,CAAC,OAAQ,QAAO;CAEpB,MAAM,MAAM,OAAO,WAAW,WAC1BA,KAAM,QAAkC,iBAAiB,GACzD;AAEJ,MAAK,MAAM,SAAS;AAClB,MAAI,KAAK,SAAS,OAChB,MAAK,MAAM,CAAC,MAAM,WAAW,OAAO,QAAQ,IAAI,CAC9C,MAAK,OAAO,KAAK,KAAK,MAAM,KAAK,CAAC,KAAK,OAAO;GAGlD;AAEF,QAAO"}
|
|
1
|
+
{"version":3,"file":"entities.mjs","names":["merge"],"sources":["../../src/transformers/entities.ts"],"sourcesContent":["import { defu as merge } from 'defu'\nimport { walk } from '../utils/ast/index.ts'\nimport type { ChildNode } from 'domhandler'\nimport type { EntitiesConfig } from '../types/index.ts'\n\nconst DEFAULT_ENTITIES: Record<string, string> = {\n '\\u200D': '‍',\n '\\u200C': '‌',\n '\\u00A0': ' ',\n '\\u00AD': '­',\n '\\u200B': '​',\n '\\u2007': ' ',\n '\\u034F': '͏',\n '\\u2003': ' ',\n '\\u2028': '&LineSeparator;',\n '\\u2029': '&ParagraphSeparator;',\n '\\u00B7': '·',\n '\\u2013': '–',\n '\\u2014': '—',\n '\\u2018': '‘',\n '\\u2019': '’',\n '\\u201C': '“',\n '\\u201D': '”',\n '\\u00AB': '«',\n '\\u00BB': '»',\n '\\u2022': '•',\n '\\u2039': '‹',\n '\\u203A': '›'\n}\n\nexport function entities(dom: ChildNode[], config: EntitiesConfig = true): ChildNode[] {\n if (!config) return dom\n\n const map = typeof config === 'object'\n ? merge(config as Record<string, string>, DEFAULT_ENTITIES)\n : DEFAULT_ENTITIES\n\n walk(dom, (node) => {\n if (node.type === 'text') {\n for (const [char, entity] of Object.entries(map)) {\n node.data = node.data.split(char).join(entity)\n }\n }\n })\n\n return dom\n}\n"],"mappings":";;;;;AAKA,MAAM,mBAA2C;CAC/C,KAAU;CACV,KAAU;CACV,QAAU;CACV,KAAU;CACV,KAAU;CACV,KAAU;CACV,KAAU;CACV,KAAU;CACV,UAAU;CACV,UAAU;CACV,KAAU;CACV,KAAU;CACV,KAAU;CACV,KAAU;CACV,KAAU;CACV,KAAU;CACV,KAAU;CACV,KAAU;CACV,KAAU;CACV,KAAU;CACV,KAAU;CACV,KAAU;CACX;AAED,SAAgB,SAAS,KAAkB,SAAyB,MAAmB;AACrF,KAAI,CAAC,OAAQ,QAAO;CAEpB,MAAM,MAAM,OAAO,WAAW,WAC1BA,KAAM,QAAkC,iBAAiB,GACzD;AAEJ,MAAK,MAAM,SAAS;AAClB,MAAI,KAAK,SAAS,OAChB,MAAK,MAAM,CAAC,MAAM,WAAW,OAAO,QAAQ,IAAI,CAC9C,MAAK,OAAO,KAAK,KAAK,MAAM,KAAK,CAAC,KAAK,OAAO;GAGlD;AAEF,QAAO"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"defaults.d.mts","names":[],"sources":["../../../src/transformers/filters/defaults.ts"],"mappings":";KAAY,cAAA,IAAkB,GAAA,UAAa,KAAA;AAAA,cAgB9B,QAAA,EAAU,MAAA,SAAe,cAAA"}
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
//#region src/transformers/filters/defaults.ts
|
|
2
|
+
const escapeMap = {
|
|
3
|
+
"\"": """,
|
|
4
|
+
"&": "&",
|
|
5
|
+
"'": "'",
|
|
6
|
+
"<": "<",
|
|
7
|
+
">": ">"
|
|
8
|
+
};
|
|
9
|
+
const escapeRegex = /["&'<>]/g;
|
|
10
|
+
function escapeHtml(str) {
|
|
11
|
+
return str.replace(escapeRegex, (ch) => escapeMap[ch]);
|
|
12
|
+
}
|
|
13
|
+
const defaults = {
|
|
14
|
+
append: (str, value) => str + value,
|
|
15
|
+
prepend: (str, value) => value + str,
|
|
16
|
+
uppercase: (str) => str.toUpperCase(),
|
|
17
|
+
lowercase: (str) => str.toLowerCase(),
|
|
18
|
+
capitalize: (str) => str.charAt(0).toUpperCase() + str.slice(1),
|
|
19
|
+
ceil: (str) => String(Math.ceil(Number.parseFloat(str))),
|
|
20
|
+
floor: (str) => String(Math.floor(Number.parseFloat(str))),
|
|
21
|
+
round: (str) => String(Math.round(Number.parseFloat(str))),
|
|
22
|
+
escape: (str) => escapeHtml(str),
|
|
23
|
+
"escape-once": (str) => {
|
|
24
|
+
return escapeHtml(str.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, "\"").replace(/"/g, "\"").replace(/'/g, "'").replace(/'/g, "'"));
|
|
25
|
+
},
|
|
26
|
+
lstrip: (str) => str.trimStart(),
|
|
27
|
+
rstrip: (str) => str.trimEnd(),
|
|
28
|
+
trim: (str) => str.trim(),
|
|
29
|
+
minus: (str, value) => String(Number.parseFloat(str) - Number.parseFloat(value)),
|
|
30
|
+
plus: (str, value) => String(Number.parseFloat(str) + Number.parseFloat(value)),
|
|
31
|
+
multiply: (str, value) => String(Number.parseFloat(str) * Number.parseFloat(value)),
|
|
32
|
+
times: (str, value) => String(Number.parseFloat(str) * Number.parseFloat(value)),
|
|
33
|
+
"divide-by": (str, value) => String(Number.parseFloat(str) / Number.parseFloat(value)),
|
|
34
|
+
divide: (str, value) => String(Number.parseFloat(str) / Number.parseFloat(value)),
|
|
35
|
+
modulo: (str, value) => String(Number.parseFloat(str) % Number.parseFloat(value)),
|
|
36
|
+
"newline-to-br": (str) => str.replace(/\n/g, "<br>"),
|
|
37
|
+
"strip-newlines": (str) => str.replace(/\n/g, ""),
|
|
38
|
+
remove: (str, value) => str.split(value).join(""),
|
|
39
|
+
"remove-first": (str, value) => {
|
|
40
|
+
const i = str.indexOf(value);
|
|
41
|
+
return i === -1 ? str : str.slice(0, i) + str.slice(i + value.length);
|
|
42
|
+
},
|
|
43
|
+
replace: (str, value) => {
|
|
44
|
+
const [search = "", replacement = ""] = value.split("|");
|
|
45
|
+
return str.split(search).join(replacement);
|
|
46
|
+
},
|
|
47
|
+
"replace-first": (str, value) => {
|
|
48
|
+
const [search = "", replacement = ""] = value.split("|");
|
|
49
|
+
const i = str.indexOf(search);
|
|
50
|
+
return i === -1 ? str : str.slice(0, i) + replacement + str.slice(i + search.length);
|
|
51
|
+
},
|
|
52
|
+
size: (str) => String(str.length),
|
|
53
|
+
slice: (str, value) => {
|
|
54
|
+
const args = value.split(",").map((s) => Number.parseInt(s.trim(), 10));
|
|
55
|
+
return str.slice(args[0], args[1]);
|
|
56
|
+
},
|
|
57
|
+
truncate: (str, value) => {
|
|
58
|
+
const commaIndex = value.indexOf(",");
|
|
59
|
+
const length = Number.parseInt(commaIndex === -1 ? value : value.slice(0, commaIndex), 10);
|
|
60
|
+
const ellipsis = commaIndex === -1 ? "..." : value.slice(commaIndex + 1);
|
|
61
|
+
if (str.length <= length) return str;
|
|
62
|
+
return str.slice(0, length) + ellipsis;
|
|
63
|
+
},
|
|
64
|
+
"truncate-words": (str, value) => {
|
|
65
|
+
const commaIndex = value.indexOf(",");
|
|
66
|
+
const count = Number.parseInt(commaIndex === -1 ? value : value.slice(0, commaIndex), 10);
|
|
67
|
+
const ellipsis = commaIndex === -1 ? "..." : value.slice(commaIndex + 1);
|
|
68
|
+
const words = str.split(/\s+/).filter(Boolean);
|
|
69
|
+
if (words.length <= count) return str;
|
|
70
|
+
return words.slice(0, count).join(" ") + ellipsis;
|
|
71
|
+
},
|
|
72
|
+
"url-decode": (str) => decodeURIComponent(str.replace(/\+/g, " ")),
|
|
73
|
+
"url-encode": (str) => encodeURIComponent(str)
|
|
74
|
+
};
|
|
75
|
+
|
|
76
|
+
//#endregion
|
|
77
|
+
export { defaults };
|
|
78
|
+
//# sourceMappingURL=defaults.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"defaults.mjs","names":[],"sources":["../../../src/transformers/filters/defaults.ts"],"sourcesContent":["export type FilterFunction = (str: string, value: string) => string\n\nconst escapeMap: Record<string, string> = {\n '\"': '"',\n '&': '&',\n \"'\": ''',\n '<': '<',\n '>': '>',\n}\n\nconst escapeRegex = /[\"&'<>]/g\n\nfunction escapeHtml(str: string): string {\n return str.replace(escapeRegex, ch => escapeMap[ch])\n}\n\nexport const defaults: Record<string, FilterFunction> = {\n append: (str, value) => str + value,\n prepend: (str, value) => value + str,\n uppercase: str => str.toUpperCase(),\n lowercase: str => str.toLowerCase(),\n capitalize: str => str.charAt(0).toUpperCase() + str.slice(1),\n ceil: str => String(Math.ceil(Number.parseFloat(str))),\n floor: str => String(Math.floor(Number.parseFloat(str))),\n round: str => String(Math.round(Number.parseFloat(str))),\n escape: str => escapeHtml(str),\n 'escape-once': str => {\n const decoded = str\n .replace(/&/g, '&')\n .replace(/</g, '<')\n .replace(/>/g, '>')\n .replace(/"/g, '\"')\n .replace(/"/g, '\"')\n .replace(/'/g, \"'\")\n .replace(/'/g, \"'\")\n\n return escapeHtml(decoded)\n },\n lstrip: str => str.trimStart(),\n rstrip: str => str.trimEnd(),\n trim: str => str.trim(),\n minus: (str, value) => String(Number.parseFloat(str) - Number.parseFloat(value)),\n plus: (str, value) => String(Number.parseFloat(str) + Number.parseFloat(value)),\n multiply: (str, value) => String(Number.parseFloat(str) * Number.parseFloat(value)),\n times: (str, value) => String(Number.parseFloat(str) * Number.parseFloat(value)),\n 'divide-by': (str, value) => String(Number.parseFloat(str) / Number.parseFloat(value)),\n divide: (str, value) => String(Number.parseFloat(str) / Number.parseFloat(value)),\n modulo: (str, value) => String(Number.parseFloat(str) % Number.parseFloat(value)),\n 'newline-to-br': str => str.replace(/\\n/g, '<br>'),\n 'strip-newlines': str => str.replace(/\\n/g, ''),\n remove: (str, value) => str.split(value).join(''),\n 'remove-first': (str, value) => {\n const i = str.indexOf(value)\n return i === -1 ? str : str.slice(0, i) + str.slice(i + value.length)\n },\n replace: (str, value) => {\n const [search = '', replacement = ''] = value.split('|')\n return str.split(search).join(replacement)\n },\n 'replace-first': (str, value) => {\n const [search = '', replacement = ''] = value.split('|')\n const i = str.indexOf(search)\n return i === -1 ? str : str.slice(0, i) + replacement + str.slice(i + search.length)\n },\n size: str => String(str.length),\n slice: (str, value) => {\n const args = value.split(',').map(s => Number.parseInt(s.trim(), 10))\n return str.slice(args[0], args[1])\n },\n truncate: (str, value) => {\n const commaIndex = value.indexOf(',')\n const length = Number.parseInt(commaIndex === -1 ? value : value.slice(0, commaIndex), 10)\n const ellipsis = commaIndex === -1 ? '...' : value.slice(commaIndex + 1)\n\n if (str.length <= length) return str\n\n return str.slice(0, length) + ellipsis\n },\n 'truncate-words': (str, value) => {\n const commaIndex = value.indexOf(',')\n const count = Number.parseInt(commaIndex === -1 ? value : value.slice(0, commaIndex), 10)\n const ellipsis = commaIndex === -1 ? '...' : value.slice(commaIndex + 1)\n const words = str.split(/\\s+/).filter(Boolean)\n\n if (words.length <= count) return str\n\n return words.slice(0, count).join(' ') + ellipsis\n },\n 'url-decode': str => decodeURIComponent(str.replace(/\\+/g, ' ')),\n 'url-encode': str => encodeURIComponent(str),\n}\n"],"mappings":"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"}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { FilterFunction } from "./defaults.mjs";
|
|
2
|
+
import { ChildNode } from "domhandler";
|
|
3
|
+
|
|
4
|
+
//#region src/transformers/filters/index.d.ts
|
|
5
|
+
type FiltersConfig = false | Record<string, (str: string, value: string) => string>;
|
|
6
|
+
/**
|
|
7
|
+
* Filters transformer.
|
|
8
|
+
*
|
|
9
|
+
* Applies transformation functions to the content of elements that
|
|
10
|
+
* have matching filter attributes. Multiple filters on the same element
|
|
11
|
+
* are executed in the order the attributes are defined.
|
|
12
|
+
*
|
|
13
|
+
* Default filters include string manipulation (uppercase, lowercase, trim, etc.),
|
|
14
|
+
* math operations (plus, minus, multiply, etc.), and more.
|
|
15
|
+
*
|
|
16
|
+
* Custom filters can be added via config, and will be merged with defaults.
|
|
17
|
+
* Set config to `false` to disable all filters.
|
|
18
|
+
*/
|
|
19
|
+
declare function filters(dom: ChildNode[], config?: FiltersConfig): ChildNode[];
|
|
20
|
+
//#endregion
|
|
21
|
+
export { type FilterFunction, FiltersConfig, filters };
|
|
22
|
+
//# sourceMappingURL=index.d.mts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.mts","names":[],"sources":["../../../src/transformers/filters/index.ts"],"mappings":";;;;KAMY,aAAA,WAAwB,MAAA,UAAgB,GAAA,UAAa,KAAA;;AAAjE;;;;;;;;;AA4BA;;;iBAAgB,OAAA,CAAQ,GAAA,EAAK,SAAA,IAAa,MAAA,GAAQ,aAAA,GAAqB,SAAA"}
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
import { parse } from "../../utils/ast/parser.mjs";
|
|
2
|
+
import { serialize } from "../../utils/ast/serializer.mjs";
|
|
3
|
+
import "../../utils/ast/index.mjs";
|
|
4
|
+
import { defaults } from "./defaults.mjs";
|
|
5
|
+
import { Text } from "domhandler";
|
|
6
|
+
|
|
7
|
+
//#region src/transformers/filters/index.ts
|
|
8
|
+
/**
|
|
9
|
+
* Process children before parents so nested filter elements work correctly.
|
|
10
|
+
*/
|
|
11
|
+
function walkBottomUp(nodes, callback) {
|
|
12
|
+
for (const node of [...nodes]) {
|
|
13
|
+
if ("children" in node && node.children?.length) walkBottomUp(node.children, callback);
|
|
14
|
+
callback(node);
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
/**
|
|
18
|
+
* Filters transformer.
|
|
19
|
+
*
|
|
20
|
+
* Applies transformation functions to the content of elements that
|
|
21
|
+
* have matching filter attributes. Multiple filters on the same element
|
|
22
|
+
* are executed in the order the attributes are defined.
|
|
23
|
+
*
|
|
24
|
+
* Default filters include string manipulation (uppercase, lowercase, trim, etc.),
|
|
25
|
+
* math operations (plus, minus, multiply, etc.), and more.
|
|
26
|
+
*
|
|
27
|
+
* Custom filters can be added via config, and will be merged with defaults.
|
|
28
|
+
* Set config to `false` to disable all filters.
|
|
29
|
+
*/
|
|
30
|
+
function filters(dom, config = {}) {
|
|
31
|
+
if (config === false) return dom;
|
|
32
|
+
const allFilters = {
|
|
33
|
+
...defaults,
|
|
34
|
+
...config
|
|
35
|
+
};
|
|
36
|
+
const filterNames = new Set(Object.keys(allFilters));
|
|
37
|
+
walkBottomUp(dom, (node) => {
|
|
38
|
+
const el = node;
|
|
39
|
+
if (!el.attribs) return;
|
|
40
|
+
const matched = [];
|
|
41
|
+
for (const attr of Object.keys(el.attribs)) if (filterNames.has(attr)) matched.push({
|
|
42
|
+
name: attr,
|
|
43
|
+
value: el.attribs[attr]
|
|
44
|
+
});
|
|
45
|
+
if (matched.length === 0) return;
|
|
46
|
+
let content = serialize(el.children);
|
|
47
|
+
for (const { name, value } of matched) {
|
|
48
|
+
content = allFilters[name](content, value);
|
|
49
|
+
delete el.attribs[name];
|
|
50
|
+
}
|
|
51
|
+
if (content === "") el.children = [];
|
|
52
|
+
else if (/<[a-z/!]/i.test(content)) {
|
|
53
|
+
const newChildren = parse(content);
|
|
54
|
+
for (const child of newChildren) child.parent = el;
|
|
55
|
+
el.children = newChildren;
|
|
56
|
+
} else {
|
|
57
|
+
const textNode = new Text(content);
|
|
58
|
+
textNode.parent = el;
|
|
59
|
+
el.children = [textNode];
|
|
60
|
+
}
|
|
61
|
+
});
|
|
62
|
+
return dom;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
//#endregion
|
|
66
|
+
export { filters };
|
|
67
|
+
//# sourceMappingURL=index.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.mjs","names":[],"sources":["../../../src/transformers/filters/index.ts"],"sourcesContent":["import { Text } from 'domhandler'\nimport type { ChildNode, Element } from 'domhandler'\nimport { parse, serialize } from '../../utils/ast/index.ts'\nimport { defaults } from './defaults.ts'\n\nexport type { FilterFunction } from './defaults.ts'\nexport type FiltersConfig = false | Record<string, (str: string, value: string) => string>\n\n/**\n * Process children before parents so nested filter elements work correctly.\n */\nfunction walkBottomUp(nodes: ChildNode[], callback: (node: ChildNode) => void): void {\n for (const node of [...nodes]) {\n if ('children' in node && node.children?.length) {\n walkBottomUp(node.children as ChildNode[], callback)\n }\n\n callback(node)\n }\n}\n\n/**\n * Filters transformer.\n *\n * Applies transformation functions to the content of elements that\n * have matching filter attributes. Multiple filters on the same element\n * are executed in the order the attributes are defined.\n *\n * Default filters include string manipulation (uppercase, lowercase, trim, etc.),\n * math operations (plus, minus, multiply, etc.), and more.\n *\n * Custom filters can be added via config, and will be merged with defaults.\n * Set config to `false` to disable all filters.\n */\nexport function filters(dom: ChildNode[], config: FiltersConfig = {}): ChildNode[] {\n if (config === false) return dom\n\n const allFilters = { ...defaults, ...config }\n const filterNames = new Set(Object.keys(allFilters))\n\n walkBottomUp(dom, (node) => {\n const el = node as Element\n\n if (!el.attribs) return\n\n // Collect matching filter attributes in source order\n const matched: Array<{ name: string; value: string }> = []\n\n for (const attr of Object.keys(el.attribs)) {\n if (filterNames.has(attr)) {\n matched.push({ name: attr, value: el.attribs[attr] })\n }\n }\n\n if (matched.length === 0) return\n\n // Serialize children to get innerHTML\n let content = serialize(el.children as ChildNode[])\n\n // Apply each filter in attribute order\n for (const { name, value } of matched) {\n content = allFilters[name](content, value)\n delete el.attribs[name]\n }\n\n // Replace children with the filtered content\n if (content === '') {\n el.children = []\n } else if (/<[a-z/!]/i.test(content)) {\n // Result contains HTML elements — parse back to DOM\n const newChildren = parse(content)\n\n for (const child of newChildren) {\n child.parent = el as any\n }\n\n el.children = newChildren as ChildNode[]\n } else {\n // Text-only result — create a text node directly to preserve entity strings\n const textNode = new Text(content)\n textNode.parent = el as any\n el.children = [textNode]\n }\n })\n\n return dom\n}\n"],"mappings":";;;;;;;;;;AAWA,SAAS,aAAa,OAAoB,UAA2C;AACnF,MAAK,MAAM,QAAQ,CAAC,GAAG,MAAM,EAAE;AAC7B,MAAI,cAAc,QAAQ,KAAK,UAAU,OACvC,cAAa,KAAK,UAAyB,SAAS;AAGtD,WAAS,KAAK;;;;;;;;;;;;;;;;AAiBlB,SAAgB,QAAQ,KAAkB,SAAwB,EAAE,EAAe;AACjF,KAAI,WAAW,MAAO,QAAO;CAE7B,MAAM,aAAa;EAAE,GAAG;EAAU,GAAG;EAAQ;CAC7C,MAAM,cAAc,IAAI,IAAI,OAAO,KAAK,WAAW,CAAC;AAEpD,cAAa,MAAM,SAAS;EAC1B,MAAM,KAAK;AAEX,MAAI,CAAC,GAAG,QAAS;EAGjB,MAAM,UAAkD,EAAE;AAE1D,OAAK,MAAM,QAAQ,OAAO,KAAK,GAAG,QAAQ,CACxC,KAAI,YAAY,IAAI,KAAK,CACvB,SAAQ,KAAK;GAAE,MAAM;GAAM,OAAO,GAAG,QAAQ;GAAO,CAAC;AAIzD,MAAI,QAAQ,WAAW,EAAG;EAG1B,IAAI,UAAU,UAAU,GAAG,SAAwB;AAGnD,OAAK,MAAM,EAAE,MAAM,WAAW,SAAS;AACrC,aAAU,WAAW,MAAM,SAAS,MAAM;AAC1C,UAAO,GAAG,QAAQ;;AAIpB,MAAI,YAAY,GACd,IAAG,WAAW,EAAE;WACP,YAAY,KAAK,QAAQ,EAAE;GAEpC,MAAM,cAAc,MAAM,QAAQ;AAElC,QAAK,MAAM,SAAS,YAClB,OAAM,SAAS;AAGjB,MAAG,WAAW;SACT;GAEL,MAAM,WAAW,IAAI,KAAK,QAAQ;AAClC,YAAS,SAAS;AAClB,MAAG,WAAW,CAAC,SAAS;;GAE1B;AAEF,QAAO"}
|
|
@@ -20,16 +20,17 @@ import { MaizzleConfig } from "../types/config.mjs";
|
|
|
20
20
|
* 5. Remove attributes
|
|
21
21
|
* 6. Shorthand CSS
|
|
22
22
|
* 7. Add attributes
|
|
23
|
-
* 8.
|
|
24
|
-
* 9. URL
|
|
25
|
-
* 10.
|
|
26
|
-
* 11.
|
|
23
|
+
* 8. Filters
|
|
24
|
+
* 9. Base URL
|
|
25
|
+
* 10. URL query
|
|
26
|
+
* 11. Purge CSS (serializes/parses internally around email-comb)
|
|
27
|
+
* 12. Entities
|
|
27
28
|
* + Vue-generated comments stripped here (on serialized string)
|
|
28
|
-
*
|
|
29
|
-
*
|
|
30
|
-
*
|
|
29
|
+
* 13. Replace strings
|
|
30
|
+
* 14. Prettify
|
|
31
|
+
* 15. Minify
|
|
31
32
|
*/
|
|
32
|
-
declare function runTransformers(html: string, config: MaizzleConfig, filePath?: string): Promise<string>;
|
|
33
|
+
declare function runTransformers(html: string, config: MaizzleConfig, filePath?: string, doctype?: string): Promise<string>;
|
|
33
34
|
//#endregion
|
|
34
35
|
export { runTransformers };
|
|
35
36
|
//# sourceMappingURL=index.d.mts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.mts","names":[],"sources":["../../src/transformers/index.ts"],"mappings":";;;;;
|
|
1
|
+
{"version":3,"file":"index.d.mts","names":[],"sources":["../../src/transformers/index.ts"],"mappings":";;;;;AAgDA;;;;;;;;;;;;;;;;;;;;;;;;;;;iBAAsB,eAAA,CACpB,IAAA,UACA,MAAA,EAAQ,aAAA,EACR,QAAA,WACA,OAAA,YACC,OAAA"}
|
|
@@ -9,6 +9,7 @@ import { inlineCSS } from "./inlineCSS.mjs";
|
|
|
9
9
|
import { removeAttributes } from "./removeAttributes.mjs";
|
|
10
10
|
import { shorthandCSS } from "./shorthandCSS.mjs";
|
|
11
11
|
import { addAttributes } from "./addAttributes.mjs";
|
|
12
|
+
import { filters } from "./filters/index.mjs";
|
|
12
13
|
import { base } from "./base.mjs";
|
|
13
14
|
import { entities } from "./entities.mjs";
|
|
14
15
|
import { urlQuery } from "./urlQuery.mjs";
|
|
@@ -37,16 +38,17 @@ import { minify } from "./minify.mjs";
|
|
|
37
38
|
* 5. Remove attributes
|
|
38
39
|
* 6. Shorthand CSS
|
|
39
40
|
* 7. Add attributes
|
|
40
|
-
* 8.
|
|
41
|
-
* 9. URL
|
|
42
|
-
* 10.
|
|
43
|
-
* 11.
|
|
41
|
+
* 8. Filters
|
|
42
|
+
* 9. Base URL
|
|
43
|
+
* 10. URL query
|
|
44
|
+
* 11. Purge CSS (serializes/parses internally around email-comb)
|
|
45
|
+
* 12. Entities
|
|
44
46
|
* + Vue-generated comments stripped here (on serialized string)
|
|
45
|
-
*
|
|
46
|
-
*
|
|
47
|
-
*
|
|
47
|
+
* 13. Replace strings
|
|
48
|
+
* 14. Prettify
|
|
49
|
+
* 15. Minify
|
|
48
50
|
*/
|
|
49
|
-
async function runTransformers(html, config, filePath) {
|
|
51
|
+
async function runTransformers(html, config, filePath, doctype) {
|
|
50
52
|
let dom = parse(html);
|
|
51
53
|
dom = await inlineLink(dom, filePath);
|
|
52
54
|
dom = await tailwindcss(dom, config, filePath);
|
|
@@ -56,15 +58,18 @@ async function runTransformers(html, config, filePath) {
|
|
|
56
58
|
dom = removeAttributes(dom, config.html?.attributes);
|
|
57
59
|
dom = shorthandCSS(dom, config.css);
|
|
58
60
|
dom = addAttributes(dom, config.html?.attributes);
|
|
61
|
+
dom = filters(dom, config.filters);
|
|
59
62
|
dom = base(dom, config.url);
|
|
60
63
|
dom = urlQuery(dom, config.url);
|
|
61
64
|
dom = purgeCSS(dom, config.css);
|
|
62
65
|
dom = entities(dom, config.html?.decodeEntities);
|
|
63
|
-
|
|
64
|
-
result =
|
|
66
|
+
const isXhtml = doctype ? /xhtml/i.test(doctype) : false;
|
|
67
|
+
let result = serialize(dom, { selfClosingTags: isXhtml });
|
|
68
|
+
result = result.replaceAll("<!--[-->", "").replaceAll("<!--]-->", "").replaceAll("<!--teleport start anchor-->", "").replaceAll("<!--teleport anchor-->", "").replaceAll("<!--teleport start-->", "").replaceAll("<!--teleport end-->", "");
|
|
65
69
|
result = replaceStrings(result, config);
|
|
66
70
|
result = await format(result, config);
|
|
67
71
|
result = minify(result, config);
|
|
72
|
+
if (!isXhtml) result = result.replace(/ \/>/g, ">");
|
|
68
73
|
return result;
|
|
69
74
|
}
|
|
70
75
|
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.mjs","names":[],"sources":["../../src/transformers/index.ts"],"sourcesContent":["import { parse, serialize } from '../utils/ast/index.ts'\nimport { inlineLink } from './inlineLink.ts'\nimport { tailwindcss } from './tailwindcss.ts'\nimport { safeClassNames } from './safeClassNames.ts'\nimport { attributeToStyle } from './attributeToStyle.ts'\nimport { inlineCSS } from './inlineCSS.ts'\nimport { removeAttributes } from './removeAttributes.ts'\nimport { shorthandCSS } from './shorthandCSS.ts'\nimport { addAttributes } from './addAttributes.ts'\nimport { base } from './base.ts'\nimport { entities } from './entities.ts'\nimport { urlQuery } from './urlQuery.ts'\nimport { purgeCSS } from './purgeCSS.ts'\nimport { replaceStrings } from './replaceStrings.ts'\nimport { format } from './format.ts'\nimport { minify } from './minify.ts'\nimport type { MaizzleConfig } from '../types/config.ts'\n\n/**\n * Run all Maizzle transformers on the rendered HTML.\n *\n * The HTML is parsed into a DOM once at the start and passed through all\n * DOM-based transformers as a shared `ChildNode[]`. After all DOM transformers\n * complete, the DOM is serialized back to a string exactly once.\n *\n * String-only transformers (those that rely on external tools that require a\n * raw HTML string) then run on the serialized output.\n *\n * Transformers run in a specific order:\n * 0. Inline link stylesheets — replace `<link rel=\"stylesheet\">` with `<style>` tags\n * 1. Tailwind CSS — compile CSS, lower syntax, optimize (cleanup + merge media queries)\n * 2. Safe class names\n * 3. Attribute to style\n * 4. CSS inliner\n * 5. Remove attributes\n * 6. Shorthand CSS\n * 7. Add attributes\n * 8. Base URL\n *
|
|
1
|
+
{"version":3,"file":"index.mjs","names":[],"sources":["../../src/transformers/index.ts"],"sourcesContent":["import { parse, serialize } from '../utils/ast/index.ts'\nimport { inlineLink } from './inlineLink.ts'\nimport { tailwindcss } from './tailwindcss.ts'\nimport { safeClassNames } from './safeClassNames.ts'\nimport { attributeToStyle } from './attributeToStyle.ts'\nimport { inlineCSS } from './inlineCSS.ts'\nimport { removeAttributes } from './removeAttributes.ts'\nimport { shorthandCSS } from './shorthandCSS.ts'\nimport { addAttributes } from './addAttributes.ts'\nimport { filters } from './filters/index.ts'\nimport { base } from './base.ts'\nimport { entities } from './entities.ts'\nimport { urlQuery } from './urlQuery.ts'\nimport { purgeCSS } from './purgeCSS.ts'\nimport { replaceStrings } from './replaceStrings.ts'\nimport { format } from './format.ts'\nimport { minify } from './minify.ts'\nimport type { MaizzleConfig } from '../types/config.ts'\n\n/**\n * Run all Maizzle transformers on the rendered HTML.\n *\n * The HTML is parsed into a DOM once at the start and passed through all\n * DOM-based transformers as a shared `ChildNode[]`. After all DOM transformers\n * complete, the DOM is serialized back to a string exactly once.\n *\n * String-only transformers (those that rely on external tools that require a\n * raw HTML string) then run on the serialized output.\n *\n * Transformers run in a specific order:\n * 0. Inline link stylesheets — replace `<link rel=\"stylesheet\">` with `<style>` tags\n * 1. Tailwind CSS — compile CSS, lower syntax, optimize (cleanup + merge media queries)\n * 2. Safe class names\n * 3. Attribute to style\n * 4. CSS inliner\n * 5. Remove attributes\n * 6. Shorthand CSS\n * 7. Add attributes\n * 8. Filters\n * 9. Base URL\n * 10. URL query\n * 11. Purge CSS (serializes/parses internally around email-comb)\n * 12. Entities\n * + Vue-generated comments stripped here (on serialized string)\n * 13. Replace strings\n * 14. Prettify\n * 15. Minify\n */\nexport async function runTransformers(\n html: string,\n config: MaizzleConfig,\n filePath?: string,\n doctype?: string,\n): Promise<string> {\n // Parse once — all DOM transformers share this array\n let dom = parse(html)\n\n // 0. Inline <link> stylesheets\n dom = await inlineLink(dom, filePath)\n\n // 1. Tailwind CSS — always runs first\n dom = await tailwindcss(dom, config, filePath)\n\n // 2. Safe class names\n dom = safeClassNames(dom, config.css)\n\n // 3. Attribute to style\n dom = attributeToStyle(dom, config.css)\n\n // 4. CSS inliner (serializes/parses internally around juice)\n dom = inlineCSS(dom, config.css)\n\n // 5. Remove attributes\n dom = removeAttributes(dom, config.html?.attributes)\n\n // 6. Shorthand CSS\n dom = shorthandCSS(dom, config.css)\n\n // 7. Add attributes\n dom = addAttributes(dom, config.html?.attributes)\n\n // 8. Filters\n dom = filters(dom, config.filters)\n\n // 9. Base URL (serializes/parses internally for VML/MSO regex passes)\n dom = base(dom, config.url)\n\n // 10. URL query\n dom = urlQuery(dom, config.url)\n\n // 11. Purge CSS (serializes/parses internally around email-comb)\n dom = purgeCSS(dom, config.css)\n\n // 12. Entities\n dom = entities(dom, config.html?.decodeEntities)\n\n // Serialize once — remaining transformers operate on the HTML string\n const isXhtml = doctype ? /xhtml/i.test(doctype) : false\n let result = serialize(dom, { selfClosingTags: isXhtml })\n\n // Remove Vue-generated comments after serializing\n result = result\n .replaceAll('<!--[-->', '')\n .replaceAll('<!--]-->', '')\n .replaceAll('<!--teleport start anchor-->', '')\n .replaceAll('<!--teleport anchor-->', '')\n .replaceAll('<!--teleport start-->', '')\n .replaceAll('<!--teleport end-->', '')\n\n // 13. Replace strings\n result = replaceStrings(result, config)\n\n // 14. Format\n result = await format(result, config)\n\n // 15. Minify\n result = minify(result, config)\n\n // Strip self-closing slashes for HTML5 doctypes\n if (!isXhtml) {\n result = result.replace(/ \\/>/g, '>')\n }\n\n return result\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAgDA,eAAsB,gBACpB,MACA,QACA,UACA,SACiB;CAEjB,IAAI,MAAM,MAAM,KAAK;AAGrB,OAAM,MAAM,WAAW,KAAK,SAAS;AAGrC,OAAM,MAAM,YAAY,KAAK,QAAQ,SAAS;AAG9C,OAAM,eAAe,KAAK,OAAO,IAAI;AAGrC,OAAM,iBAAiB,KAAK,OAAO,IAAI;AAGvC,OAAM,UAAU,KAAK,OAAO,IAAI;AAGhC,OAAM,iBAAiB,KAAK,OAAO,MAAM,WAAW;AAGpD,OAAM,aAAa,KAAK,OAAO,IAAI;AAGnC,OAAM,cAAc,KAAK,OAAO,MAAM,WAAW;AAGjD,OAAM,QAAQ,KAAK,OAAO,QAAQ;AAGlC,OAAM,KAAK,KAAK,OAAO,IAAI;AAG3B,OAAM,SAAS,KAAK,OAAO,IAAI;AAG/B,OAAM,SAAS,KAAK,OAAO,IAAI;AAG/B,OAAM,SAAS,KAAK,OAAO,MAAM,eAAe;CAGhD,MAAM,UAAU,UAAU,SAAS,KAAK,QAAQ,GAAG;CACnD,IAAI,SAAS,UAAU,KAAK,EAAE,iBAAiB,SAAS,CAAC;AAGzD,UAAS,OACN,WAAW,YAAY,GAAG,CAC1B,WAAW,YAAY,GAAG,CAC1B,WAAW,gCAAgC,GAAG,CAC9C,WAAW,0BAA0B,GAAG,CACxC,WAAW,yBAAyB,GAAG,CACvC,WAAW,uBAAuB,GAAG;AAGxC,UAAS,eAAe,QAAQ,OAAO;AAGvC,UAAS,MAAM,OAAO,QAAQ,OAAO;AAGrC,UAAS,OAAO,QAAQ,OAAO;AAG/B,KAAI,CAAC,QACH,UAAS,OAAO,QAAQ,SAAS,IAAI;AAGvC,QAAO"}
|