@maizzle/framework 6.0.0-rc.25 → 6.0.0-rc.26

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.d.ts CHANGED
@@ -15,6 +15,24 @@ interface BuildResult {
15
15
  * from the working directory.
16
16
  */
17
17
  declare function build(configInput?: Partial<MaizzleConfig> | string): Promise<BuildResult>;
18
+ /**
19
+ * Decide whether to build in parallel and with how many workers.
20
+ *
21
+ * `config.parallel`:
22
+ * - omitted → parallel when `count > 50`, min(CPU count − 1, 8) workers
23
+ * - `true` → always parallel (ignores threshold), default workers
24
+ * - `false` → always sequential
25
+ * - `{ workers, threshold }` → parallel when `count > threshold` (default 50),
26
+ * using `workers` threads (default min(CPU count − 1, 8))
27
+ *
28
+ * Workers reload the config file to recover function hooks, so parallel only
29
+ * applies to file-based configs (a path or the default cwd config) — an inline
30
+ * config object has no file to reload and always builds sequentially.
31
+ */
32
+ declare function resolveParallel(config: MaizzleConfig, count: number, configInput: Partial<MaizzleConfig> | string | undefined): {
33
+ enabled: boolean;
34
+ workers: number;
35
+ };
18
36
  //#endregion
19
- export { BuildResult, build };
37
+ export { BuildResult, build, resolveParallel };
20
38
  //# sourceMappingURL=build.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"build.d.ts","names":[],"sources":["../src/build.ts"],"mappings":";;UAeiB,WAAA;EACf,KAAA;EACA,MAAA,EAAQ,aAAa;AAAA;;;;;;;;AAAA;AAavB;;iBAAsB,KAAA,CAAM,WAAA,GAAc,OAAA,CAAQ,aAAA,aAA0B,OAAA,CAAQ,WAAA"}
1
+ {"version":3,"file":"build.d.ts","names":[],"sources":["../src/build.ts"],"mappings":";;UAaiB,WAAA;EACf,KAAA;EACA,MAAA,EAAQ,aAAa;AAAA;;;;;;;;AAAA;AAavB;;iBAAsB,KAAA,CAAM,WAAA,GAAc,OAAA,CAAQ,aAAA,aAA0B,OAAA,CAAQ,WAAA;;;;;;;;;;;;;AAAW;AA6G/F;iBAAgB,eAAA,CACd,MAAA,EAAQ,aAAA,EACR,KAAA,UACA,WAAA,EAAa,OAAA,CAAQ,aAAA;EAClB,OAAA;EAAkB,OAAA;AAAA"}
package/dist/build.js CHANGED
@@ -1,16 +1,14 @@
1
1
  import { resolveConfig } from "./config/index.js";
2
2
  import { EventManager } from "./events/index.js";
3
- import { runTransformers } from "./transformers/index.js";
4
3
  import { normalizeComponentSources } from "./utils/componentSources.js";
5
4
  import { createRenderer } from "./render/createRenderer.js";
6
- import { createPlaintext } from "./plaintext.js";
7
- import { stripForHtml, stripForPlaintext } from "./utils/output-markers.js";
8
- import { _setCurrentTemplate } from "./composables/useCurrentTemplate.js";
9
- import { cpSync, existsSync, mkdirSync, readFileSync, rmSync, writeFileSync } from "node:fs";
10
- import { basename, dirname, join, parse, relative, resolve } from "node:path";
5
+ import { buildTemplate, computeContentBase } from "./render/buildTemplate.js";
6
+ import { cpSync, existsSync, mkdirSync, rmSync } from "node:fs";
7
+ import { dirname, join, relative, resolve } from "node:path";
8
+ import { fileURLToPath } from "node:url";
9
+ import { availableParallelism } from "node:os";
11
10
  import { glob } from "tinyglobby";
12
11
  import ora from "ora";
13
- import defu from "defu";
14
12
  //#region src/build.ts
15
13
  /**
16
14
  * Build all SFC email templates to HTML files.
@@ -48,97 +46,56 @@ async function build(configInput) {
48
46
  recursive: true,
49
47
  force: true
50
48
  });
51
- const renderer = await createRenderer({
52
- markdown: config.markdown,
53
- root: config.root,
54
- componentDirs: normalizeComponentSources(config.components?.source, process.cwd()),
55
- vite: config.vite
56
- });
57
49
  const outputFiles = [];
58
- try {
59
- for (const templatePath of templateFiles) {
60
- const absolutePath = resolve(templatePath);
61
- const parsedPath = parse(absolutePath);
62
- const template = {
63
- source: readFileSync(absolutePath, "utf-8"),
64
- path: parsedPath
65
- };
66
- _setCurrentTemplate(parsedPath);
67
- try {
68
- await events.fireBeforeRender({
69
- config,
70
- template
71
- });
72
- const rendered = await renderer.render(absolutePath, config);
73
- /**
74
- * Register SFC event handlers collected during render so they take
75
- * part in the post-render events (afterRender / afterTransform).
76
- * They're cleared at the end of the iteration so they don't
77
- * leak into the next template.
78
- */
79
- for (const { name, handler } of rendered.sfcEventHandlers) events.on(name, handler);
80
- let html = await events.fireAfterRender({
81
- config,
82
- template,
83
- html: rendered.html
84
- });
85
- /**
86
- * Use the per-template merged config (from defineConfig() in the SFC) so
87
- * that template-level overrides like css.safe: false are respected
88
- * by transformers.
89
- */
90
- const templateConfig = rendered.templateConfig;
91
- const doctype = rendered.doctype ?? templateConfig.doctype ?? "<!DOCTYPE html>";
92
- if (templateConfig.useTransformers !== false) html = await runTransformers(html, templateConfig, absolutePath, doctype, rendered.tailwindBlocks);
93
- html = await events.fireAfterTransform({
94
- config,
95
- template,
96
- html
97
- });
98
- if (doctype) html = `${doctype}\n${html}`;
99
- const htmlOut = stripForHtml(html);
100
- const sfcOutputPath = rendered.outputPath;
101
- let outputFilePath;
102
- if (sfcOutputPath) {
103
- const parsed = parse(resolve(sfcOutputPath));
104
- const ext = parsed.ext ? parsed.ext.slice(1) : outputExtension;
105
- outputFilePath = join(parsed.dir, `${parsed.name}.${ext}`);
106
- } else outputFilePath = resolveOutputPath(templatePath, outputPath, outputExtension, contentBase);
107
- mkdirSync(dirname(outputFilePath), { recursive: true });
108
- writeFileSync(outputFilePath, htmlOut);
109
- outputFiles.push(outputFilePath);
110
- const globalPlaintext = templateConfig.plaintext;
111
- const sfcPlaintext = rendered.plaintext;
112
- if (globalPlaintext || sfcPlaintext) {
113
- const globalCfg = typeof globalPlaintext === "object" ? globalPlaintext : {};
114
- const stripOptions = defu(sfcPlaintext?.options, globalCfg.options);
115
- const plaintext = createPlaintext(stripForPlaintext(html), stripOptions);
116
- const ptExtension = sfcPlaintext?.extension ?? globalCfg.extension ?? "txt";
117
- let ptOutputPath;
118
- if (sfcPlaintext?.destination) {
119
- const name = basename(templatePath).replace(/\.(vue|md)$/, "");
120
- ptOutputPath = join(resolve(sfcPlaintext.destination), `${name}.${ptExtension}`);
121
- } else if (sfcOutputPath) {
122
- const parsed = parse(outputFilePath);
123
- ptOutputPath = join(parsed.dir, `${parsed.name}.${ptExtension}`);
124
- } else if (globalCfg.destination) ptOutputPath = resolveOutputPath(templatePath, resolve(globalCfg.destination), ptExtension, contentBase);
125
- else ptOutputPath = resolveOutputPath(templatePath, outputPath, ptExtension, contentBase);
126
- mkdirSync(dirname(ptOutputPath), { recursive: true });
127
- writeFileSync(ptOutputPath, plaintext);
128
- }
129
- } finally {
130
- _setCurrentTemplate(void 0);
131
- events.clearSfcHandlers();
132
- }
133
- }
50
+ let droppedAfterBuild = 0;
51
+ const parallel = resolveParallel(config, templateFiles.length, configInput);
52
+ if (parallel.enabled) {
53
+ spinner.text = `Building ${templateFiles.length} templates across ${parallel.workers} workers...`;
54
+ const result = await runParallelBuild({
55
+ templateFiles,
56
+ workers: parallel.workers,
57
+ config,
58
+ configInput,
59
+ outputPath,
60
+ outputExtension,
61
+ contentBase
62
+ });
63
+ outputFiles.push(...result.files);
64
+ droppedAfterBuild = result.sfcAfterBuildCount;
134
65
  await copyStatic(config, outputPath);
135
66
  await events.fireAfterBuild({
136
67
  files: outputFiles,
137
68
  config
138
69
  });
139
- } finally {
140
- await renderer.close();
70
+ } else {
71
+ const renderer = await createRenderer({
72
+ markdown: config.markdown,
73
+ root: config.root,
74
+ componentDirs: normalizeComponentSources(config.components?.source, process.cwd()),
75
+ vite: config.vite
76
+ });
77
+ try {
78
+ for (const templatePath of templateFiles) {
79
+ const { files } = await buildTemplate(templatePath, {
80
+ config,
81
+ renderer,
82
+ events,
83
+ outputPath,
84
+ outputExtension,
85
+ contentBase
86
+ });
87
+ outputFiles.push(...files);
88
+ }
89
+ await copyStatic(config, outputPath);
90
+ await events.fireAfterBuild({
91
+ files: outputFiles,
92
+ config
93
+ });
94
+ } finally {
95
+ await renderer.close();
96
+ }
141
97
  }
98
+ if (droppedAfterBuild > 0) console.warn(`[maizzle] Skipped ${droppedAfterBuild} SFC-registered afterBuild handler(s): afterBuild can't run inside a parallel build worker. Move build-completion logic to the config's afterBuild hook.`);
142
99
  const duration = ((Date.now() - start) / 1e3).toFixed(2);
143
100
  const count = outputFiles.length;
144
101
  spinner.stopAndPersist({
@@ -151,20 +108,100 @@ async function build(configInput) {
151
108
  };
152
109
  }
153
110
  /**
154
- * Extract the static (non-glob) prefix from content patterns.
111
+ * Default template count above which parallel build turns on. Benchmarked
112
+ * crossover (with the worker cap below) is ~25 templates; 50 leaves margin so
113
+ * auto-parallel only kicks in where it's a reliable win across hardware.
114
+ * Override per project with `parallel: { threshold }`.
115
+ */
116
+ const DEFAULT_PARALLEL_THRESHOLD = 50;
117
+ /**
118
+ * Default worker cap. Each worker runs a full Vite SSR renderer, so startup +
119
+ * contention outweighs added parallelism past ~8 — benchmarks showed 8 beating
120
+ * 12/16/23 at every size. Override with `parallel: { workers }`.
121
+ */
122
+ const DEFAULT_MAX_WORKERS = 8;
123
+ /**
124
+ * Decide whether to build in parallel and with how many workers.
155
125
  *
156
- * For example, `['/abs/path/emails/**\/*.vue']` → `'/abs/path/emails'`
126
+ * `config.parallel`:
127
+ * - omitted → parallel when `count > 50`, min(CPU count − 1, 8) workers
128
+ * - `true` → always parallel (ignores threshold), default workers
129
+ * - `false` → always sequential
130
+ * - `{ workers, threshold }` → parallel when `count > threshold` (default 50),
131
+ * using `workers` threads (default min(CPU count − 1, 8))
157
132
  *
158
- * This is used to strip the content base from template paths
159
- * so the output preserves only the subdirectory structure.
133
+ * Workers reload the config file to recover function hooks, so parallel only
134
+ * applies to file-based configs (a path or the default cwd config) — an inline
135
+ * config object has no file to reload and always builds sequentially.
136
+ */
137
+ function resolveParallel(config, count, configInput) {
138
+ const setting = config.parallel;
139
+ if (setting === false) return {
140
+ enabled: false,
141
+ workers: 0
142
+ };
143
+ if (!(typeof configInput === "string" || configInput == null)) return {
144
+ enabled: false,
145
+ workers: 0
146
+ };
147
+ const cpus = availableParallelism();
148
+ let maxWorkers = Math.min(Math.max(1, cpus - 1), DEFAULT_MAX_WORKERS);
149
+ let threshold = DEFAULT_PARALLEL_THRESHOLD;
150
+ const ignoreThreshold = setting === true;
151
+ if (typeof setting === "object" && setting !== null) {
152
+ if (typeof setting.workers === "number" && setting.workers > 0) maxWorkers = Math.floor(setting.workers);
153
+ if (typeof setting.threshold === "number" && setting.threshold >= 0) threshold = Math.floor(setting.threshold);
154
+ }
155
+ if (!ignoreThreshold && count <= threshold) return {
156
+ enabled: false,
157
+ workers: 0
158
+ };
159
+ const workers = Math.min(maxWorkers, count);
160
+ return {
161
+ enabled: workers >= 2 && count >= 2,
162
+ workers
163
+ };
164
+ }
165
+ /**
166
+ * Run the build across worker threads. Each worker reloads the config (for its
167
+ * function hooks), builds its batch via the same `buildTemplate` as the
168
+ * sequential path, and returns the files it wrote. beforeCreate/afterBuild stay
169
+ * on the main thread (handled by the caller).
160
170
  */
161
- function computeContentBase(patterns) {
162
- const staticPart = (patterns.find((p) => !p.startsWith("!")) ?? patterns[0]).split(/[*{?[]/)[0];
163
- return resolve(staticPart.endsWith("/") ? staticPart : dirname(staticPart));
171
+ async function runParallelBuild(opts) {
172
+ const { templateFiles, workers, config, configInput, outputPath, outputExtension, contentBase } = opts;
173
+ const { default: Tinypool } = await import("tinypool");
174
+ const workerPath = resolve(dirname(fileURLToPath(import.meta.url)), "render/parallel/worker.mjs");
175
+ const configPath = typeof configInput === "string" ? configInput : void 0;
176
+ const configData = JSON.parse(JSON.stringify(config));
177
+ const batches = shardEvenly(templateFiles, workers);
178
+ const pool = new Tinypool({
179
+ filename: workerPath,
180
+ minThreads: batches.length,
181
+ maxThreads: batches.length
182
+ });
183
+ try {
184
+ const results = await Promise.all(batches.map((templatePaths) => pool.run({
185
+ templatePaths,
186
+ configPath,
187
+ configData,
188
+ outputPath,
189
+ outputExtension,
190
+ contentBase
191
+ })));
192
+ return {
193
+ files: results.flatMap((r) => r.files),
194
+ sfcAfterBuildCount: results.reduce((n, r) => n + r.sfcAfterBuildCount, 0)
195
+ };
196
+ } finally {
197
+ await pool.destroy();
198
+ }
164
199
  }
165
- function resolveOutputPath(templatePath, outputDir, extension, contentBase) {
166
- const name = basename(templatePath).replace(/\.(vue|md)$/, "");
167
- return join(outputDir, relative(contentBase, dirname(resolve(templatePath))), `${name}.${extension}`);
200
+ /** Round-robin items into up to `buckets` non-empty groups for even balance. */
201
+ function shardEvenly(items, buckets) {
202
+ const out = Array.from({ length: buckets }, () => []);
203
+ items.forEach((item, i) => out[i % buckets].push(item));
204
+ return out.filter((b) => b.length > 0);
168
205
  }
169
206
  async function copyStatic(config, outputPath) {
170
207
  const sources = config.static?.source ?? ["public/**/*.*"];
@@ -178,6 +215,6 @@ async function copyStatic(config, outputPath) {
178
215
  }
179
216
  }
180
217
  //#endregion
181
- export { build };
218
+ export { build, resolveParallel };
182
219
 
183
220
  //# sourceMappingURL=build.js.map
package/dist/build.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"build.js","names":["parsePath"],"sources":["../src/build.ts"],"sourcesContent":["import { readFileSync, writeFileSync, mkdirSync, cpSync, existsSync, rmSync } from 'node:fs'\nimport { resolve, dirname, basename, relative, join, parse as parsePath } from 'node:path'\nimport { glob } from 'tinyglobby'\nimport ora from 'ora'\nimport { resolveConfig } from './config/index.ts'\nimport { EventManager } from './events/index.ts'\nimport { runTransformers } from './transformers/index.ts'\nimport { createRenderer } from './render/createRenderer.ts'\nimport { createPlaintext } from './plaintext.ts'\nimport { stripForHtml, stripForPlaintext } from './utils/output-markers.ts'\nimport { normalizeComponentSources } from './utils/componentSources.ts'\nimport { _setCurrentTemplate } from './composables/useCurrentTemplate.ts'\nimport defu from 'defu'\nimport type { MaizzleConfig } from './types/index.ts'\n\nexport interface BuildResult {\n files: string[]\n config: MaizzleConfig\n}\n\n/**\n * Build all SFC email templates to HTML files.\n *\n * Creates a single Renderer instance, then loops through each template\n * calling render → transformers → write to disk.\n *\n * Pass a `Partial<MaizzleConfig>` to override config inline, or a string\n * to load config from a specific file path. Omit to load `maizzle.config`\n * from the working directory.\n */\nexport async function build(configInput?: Partial<MaizzleConfig> | string): Promise<BuildResult> {\n const start = Date.now()\n const spinner = ora({ text: 'Building templates...', spinner: 'circleHalves' }).start()\n\n const config = await resolveConfig(configInput)\n\n const events = new EventManager()\n events.registerConfig(config)\n await events.fireBeforeCreate({ config })\n\n const outputPath = resolve(config.output?.path ?? 'dist')\n const outputExtension = config.output?.extension ?? 'html'\n\n const contentPatterns = config.content ?? ['emails/**/*.vue']\n const contentBase = computeContentBase(contentPatterns)\n const templateFiles = await glob(contentPatterns)\n\n if (templateFiles.length === 0) {\n spinner.succeed('No templates found')\n return { files: [], config }\n }\n\n // Clear the output directory before writing fresh output\n if (existsSync(outputPath)) {\n rmSync(outputPath, { recursive: true, force: true })\n }\n\n const renderer = await createRenderer({ markdown: config.markdown, root: config.root, componentDirs: normalizeComponentSources(config.components?.source, process.cwd()), vite: config.vite })\n const outputFiles: string[] = []\n\n try {\n for (const templatePath of templateFiles) {\n const absolutePath = resolve(templatePath)\n const parsedPath = parsePath(absolutePath)\n const template = { source: readFileSync(absolutePath, 'utf-8'), path: parsedPath }\n\n _setCurrentTemplate(parsedPath)\n\n try {\n await events.fireBeforeRender({ config, template })\n\n const rendered = await renderer.render(absolutePath, config)\n\n /**\n * Register SFC event handlers collected during render so they take\n * part in the post-render events (afterRender / afterTransform).\n * They're cleared at the end of the iteration so they don't\n * leak into the next template.\n */\n for (const { name, handler } of rendered.sfcEventHandlers) {\n events.on(name, handler)\n }\n\n let html = await events.fireAfterRender({ config, template, html: rendered.html })\n\n /**\n * Use the per-template merged config (from defineConfig() in the SFC) so\n * that template-level overrides like css.safe: false are respected\n * by transformers.\n */\n const templateConfig = rendered.templateConfig\n\n const doctype = rendered.doctype ?? templateConfig.doctype ?? '<!DOCTYPE html>'\n\n if (templateConfig.useTransformers !== false) {\n html = await runTransformers(html, templateConfig, absolutePath, doctype, rendered.tailwindBlocks)\n }\n\n html = await events.fireAfterTransform({ config, template, html })\n if (doctype) html = `${doctype}\\n${html}`\n\n const htmlOut = stripForHtml(html)\n const sfcOutputPath = rendered.outputPath\n let outputFilePath: string\n\n if (sfcOutputPath) {\n const parsed = parsePath(resolve(sfcOutputPath))\n const ext = parsed.ext ? parsed.ext.slice(1) : outputExtension\n outputFilePath = join(parsed.dir, `${parsed.name}.${ext}`)\n } else {\n outputFilePath = resolveOutputPath(templatePath, outputPath, outputExtension, contentBase)\n }\n\n mkdirSync(dirname(outputFilePath), { recursive: true })\n writeFileSync(outputFilePath, htmlOut)\n outputFiles.push(outputFilePath)\n\n // Generate plaintext version if configured\n const globalPlaintext = templateConfig.plaintext\n const sfcPlaintext = rendered.plaintext\n\n if (globalPlaintext || sfcPlaintext) {\n const globalCfg = typeof globalPlaintext === 'object' ? globalPlaintext : {}\n const stripOptions = defu(sfcPlaintext?.options, globalCfg.options)\n const plaintext = createPlaintext(stripForPlaintext(html), stripOptions)\n const ptExtension = sfcPlaintext?.extension ?? globalCfg.extension ?? 'txt'\n\n let ptOutputPath: string\n\n if (sfcPlaintext?.destination) {\n const name = basename(templatePath).replace(/\\.(vue|md)$/, '')\n ptOutputPath = join(resolve(sfcPlaintext.destination), `${name}.${ptExtension}`)\n } else if (sfcOutputPath) {\n const parsed = parsePath(outputFilePath)\n ptOutputPath = join(parsed.dir, `${parsed.name}.${ptExtension}`)\n } else if (globalCfg.destination) {\n ptOutputPath = resolveOutputPath(templatePath, resolve(globalCfg.destination), ptExtension, contentBase)\n } else {\n ptOutputPath = resolveOutputPath(templatePath, outputPath, ptExtension, contentBase)\n }\n\n mkdirSync(dirname(ptOutputPath), { recursive: true })\n writeFileSync(ptOutputPath, plaintext)\n }\n } finally {\n _setCurrentTemplate(undefined)\n events.clearSfcHandlers()\n }\n }\n\n await copyStatic(config, outputPath)\n await events.fireAfterBuild({ files: outputFiles, config })\n } finally {\n await renderer.close()\n }\n\n const duration = ((Date.now() - start) / 1000).toFixed(2)\n const count = outputFiles.length\n spinner.stopAndPersist({\n symbol: '✅',\n text: `Built ${count} template${count !== 1 ? 's' : ''} in ${duration}s`,\n })\n\n return { files: outputFiles, config }\n}\n\n/**\n * Extract the static (non-glob) prefix from content patterns.\n *\n * For example, `['/abs/path/emails/**\\/*.vue']` → `'/abs/path/emails'`\n *\n * This is used to strip the content base from template paths\n * so the output preserves only the subdirectory structure.\n */\nfunction computeContentBase(patterns: string[]): string {\n // Use the first non-negated pattern\n const pattern = patterns.find(p => !p.startsWith('!')) ?? patterns[0]\n\n // Split on first glob character (* { ? [) and take the directory part\n const staticPart = pattern.split(/[*{?[]/)[0]\n\n // Ensure we have a clean directory path (not a partial segment)\n return resolve(staticPart.endsWith('/') ? staticPart : dirname(staticPart))\n}\n\nfunction resolveOutputPath(templatePath: string, outputDir: string, extension: string, contentBase: string): string {\n const name = basename(templatePath).replace(/\\.(vue|md)$/, '')\n const absTemplate = resolve(templatePath)\n const rel = relative(contentBase, dirname(absTemplate))\n\n return join(outputDir, rel, `${name}.${extension}`)\n}\n\nasync function copyStatic(config: MaizzleConfig, outputPath: string): Promise<void> {\n const sources = config.static?.source ?? ['public/**/*.*']\n const destination = config.static?.destination ?? 'public'\n\n const files = await glob(sources)\n\n for (const file of files) {\n const destPath = join(outputPath, destination, relative(dirname(sources[0]).replace(/\\*.*$/, ''), file))\n const destDir = dirname(destPath)\n\n if (!existsSync(destDir)) {\n mkdirSync(destDir, { recursive: true })\n }\n\n cpSync(file, destPath)\n }\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"build.js","names":[],"sources":["../src/build.ts"],"sourcesContent":["import { mkdirSync, cpSync, existsSync, rmSync } from 'node:fs'\nimport { resolve, dirname, relative, join } from 'node:path'\nimport { fileURLToPath } from 'node:url'\nimport { availableParallelism } from 'node:os'\nimport { glob } from 'tinyglobby'\nimport ora from 'ora'\nimport { resolveConfig } from './config/index.ts'\nimport { EventManager } from './events/index.ts'\nimport { createRenderer } from './render/createRenderer.ts'\nimport { normalizeComponentSources } from './utils/componentSources.ts'\nimport { buildTemplate, computeContentBase } from './render/buildTemplate.ts'\nimport type { MaizzleConfig } from './types/index.ts'\n\nexport interface BuildResult {\n files: string[]\n config: MaizzleConfig\n}\n\n/**\n * Build all SFC email templates to HTML files.\n *\n * Creates a single Renderer instance, then loops through each template\n * calling render → transformers → write to disk.\n *\n * Pass a `Partial<MaizzleConfig>` to override config inline, or a string\n * to load config from a specific file path. Omit to load `maizzle.config`\n * from the working directory.\n */\nexport async function build(configInput?: Partial<MaizzleConfig> | string): Promise<BuildResult> {\n const start = Date.now()\n const spinner = ora({ text: 'Building templates...', spinner: 'circleHalves' }).start()\n\n const config = await resolveConfig(configInput)\n\n const events = new EventManager()\n events.registerConfig(config)\n await events.fireBeforeCreate({ config })\n\n const outputPath = resolve(config.output?.path ?? 'dist')\n const outputExtension = config.output?.extension ?? 'html'\n\n const contentPatterns = config.content ?? ['emails/**/*.vue']\n const contentBase = computeContentBase(contentPatterns)\n const templateFiles = await glob(contentPatterns)\n\n if (templateFiles.length === 0) {\n spinner.succeed('No templates found')\n return { files: [], config }\n }\n\n // Clear the output directory before writing fresh output\n if (existsSync(outputPath)) {\n rmSync(outputPath, { recursive: true, force: true })\n }\n\n const outputFiles: string[] = []\n let droppedAfterBuild = 0\n\n const parallel = resolveParallel(config, templateFiles.length, configInput)\n\n if (parallel.enabled) {\n spinner.text = `Building ${templateFiles.length} templates across ${parallel.workers} workers...`\n\n const result = await runParallelBuild({\n templateFiles,\n workers: parallel.workers,\n config,\n configInput,\n outputPath,\n outputExtension,\n contentBase,\n })\n\n outputFiles.push(...result.files)\n droppedAfterBuild = result.sfcAfterBuildCount\n\n await copyStatic(config, outputPath)\n await events.fireAfterBuild({ files: outputFiles, config })\n } else {\n const renderer = await createRenderer({ markdown: config.markdown, root: config.root, componentDirs: normalizeComponentSources(config.components?.source, process.cwd()), vite: config.vite })\n\n try {\n for (const templatePath of templateFiles) {\n const { files } = await buildTemplate(templatePath, { config, renderer, events, outputPath, outputExtension, contentBase })\n outputFiles.push(...files)\n }\n\n await copyStatic(config, outputPath)\n await events.fireAfterBuild({ files: outputFiles, config })\n } finally {\n await renderer.close()\n }\n }\n\n if (droppedAfterBuild > 0) {\n console.warn(`[maizzle] Skipped ${droppedAfterBuild} SFC-registered afterBuild handler(s): afterBuild can't run inside a parallel build worker. Move build-completion logic to the config's afterBuild hook.`)\n }\n\n const duration = ((Date.now() - start) / 1000).toFixed(2)\n const count = outputFiles.length\n spinner.stopAndPersist({\n symbol: '✅',\n text: `Built ${count} template${count !== 1 ? 's' : ''} in ${duration}s`,\n })\n\n return { files: outputFiles, config }\n}\n\n/**\n * Default template count above which parallel build turns on. Benchmarked\n * crossover (with the worker cap below) is ~25 templates; 50 leaves margin so\n * auto-parallel only kicks in where it's a reliable win across hardware.\n * Override per project with `parallel: { threshold }`.\n */\nconst DEFAULT_PARALLEL_THRESHOLD = 50\n\n/**\n * Default worker cap. Each worker runs a full Vite SSR renderer, so startup +\n * contention outweighs added parallelism past ~8 — benchmarks showed 8 beating\n * 12/16/23 at every size. Override with `parallel: { workers }`.\n */\nconst DEFAULT_MAX_WORKERS = 8\n\n/**\n * Decide whether to build in parallel and with how many workers.\n *\n * `config.parallel`:\n * - omitted → parallel when `count > 50`, min(CPU count − 1, 8) workers\n * - `true` → always parallel (ignores threshold), default workers\n * - `false` → always sequential\n * - `{ workers, threshold }` → parallel when `count > threshold` (default 50),\n * using `workers` threads (default min(CPU count − 1, 8))\n *\n * Workers reload the config file to recover function hooks, so parallel only\n * applies to file-based configs (a path or the default cwd config) — an inline\n * config object has no file to reload and always builds sequentially.\n */\nexport function resolveParallel(\n config: MaizzleConfig,\n count: number,\n configInput: Partial<MaizzleConfig> | string | undefined,\n): { enabled: boolean; workers: number } {\n const setting = config.parallel\n if (setting === false) return { enabled: false, workers: 0 }\n\n const fileBased = typeof configInput === 'string' || configInput == null\n if (!fileBased) return { enabled: false, workers: 0 }\n\n const cpus = availableParallelism()\n const defaultWorkers = Math.min(Math.max(1, cpus - 1), DEFAULT_MAX_WORKERS)\n\n let maxWorkers = defaultWorkers\n let threshold = DEFAULT_PARALLEL_THRESHOLD\n // `true` opts in regardless of count; object/omitted stay threshold-gated.\n const ignoreThreshold = setting === true\n\n if (typeof setting === 'object' && setting !== null) {\n if (typeof setting.workers === 'number' && setting.workers > 0) maxWorkers = Math.floor(setting.workers)\n if (typeof setting.threshold === 'number' && setting.threshold >= 0) threshold = Math.floor(setting.threshold)\n }\n\n if (!ignoreThreshold && count <= threshold) return { enabled: false, workers: 0 }\n\n const workers = Math.min(maxWorkers, count)\n return { enabled: workers >= 2 && count >= 2, workers }\n}\n\n/**\n * Run the build across worker threads. Each worker reloads the config (for its\n * function hooks), builds its batch via the same `buildTemplate` as the\n * sequential path, and returns the files it wrote. beforeCreate/afterBuild stay\n * on the main thread (handled by the caller).\n */\nasync function runParallelBuild(opts: {\n templateFiles: string[]\n workers: number\n config: MaizzleConfig\n configInput: Partial<MaizzleConfig> | string | undefined\n outputPath: string\n outputExtension: string\n contentBase: string\n}): Promise<{ files: string[]; sfcAfterBuildCount: number }> {\n const { templateFiles, workers, config, configInput, outputPath, outputExtension, contentBase } = opts\n\n const { default: Tinypool } = await import('tinypool')\n const workerPath = resolve(dirname(fileURLToPath(import.meta.url)), 'render/parallel/worker.mjs')\n\n const configPath = typeof configInput === 'string' ? configInput : undefined\n // Serializable snapshot of the post-beforeCreate config (functions dropped).\n const configData = JSON.parse(JSON.stringify(config)) as Partial<MaizzleConfig>\n\n const batches = shardEvenly(templateFiles, workers)\n\n const pool = new Tinypool({ filename: workerPath, minThreads: batches.length, maxThreads: batches.length })\n\n try {\n const results = await Promise.all(\n batches.map(templatePaths => pool.run({\n templatePaths,\n configPath,\n configData,\n outputPath,\n outputExtension,\n contentBase,\n })),\n )\n\n return {\n files: results.flatMap(r => r.files),\n sfcAfterBuildCount: results.reduce((n, r) => n + r.sfcAfterBuildCount, 0),\n }\n } finally {\n await pool.destroy()\n }\n}\n\n/** Round-robin items into up to `buckets` non-empty groups for even balance. */\nfunction shardEvenly<T>(items: T[], buckets: number): T[][] {\n const out: T[][] = Array.from({ length: buckets }, () => [])\n items.forEach((item, i) => out[i % buckets].push(item))\n return out.filter(b => b.length > 0)\n}\n\nasync function copyStatic(config: MaizzleConfig, outputPath: string): Promise<void> {\n const sources = config.static?.source ?? ['public/**/*.*']\n const destination = config.static?.destination ?? 'public'\n\n const files = await glob(sources)\n\n for (const file of files) {\n const destPath = join(outputPath, destination, relative(dirname(sources[0]).replace(/\\*.*$/, ''), file))\n const destDir = dirname(destPath)\n\n if (!existsSync(destDir)) {\n mkdirSync(destDir, { recursive: true })\n }\n\n cpSync(file, destPath)\n }\n}\n"],"mappings":"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"}
@@ -2,8 +2,8 @@ import { defaults } from "./defaults.js";
2
2
  import { defineConfig } from "../composables/defineConfig.js";
3
3
  import { existsSync } from "node:fs";
4
4
  import { resolve } from "node:path";
5
- import { createJiti } from "jiti";
6
5
  import { fileURLToPath } from "node:url";
6
+ import { createJiti } from "jiti";
7
7
  import { createDefu } from "defu";
8
8
  //#region src/config/index.ts
9
9
  const merge = createDefu((obj, key, value) => {
package/dist/index.js CHANGED
@@ -1,6 +1,8 @@
1
1
  import { useConfig } from "./composables/useConfig.js";
2
2
  import { defineConfig } from "./composables/defineConfig.js";
3
3
  import { resolveConfig } from "./config/index.js";
4
+ import { normalizeComponentSources } from "./utils/componentSources.js";
5
+ import { createRenderer } from "./render/createRenderer.js";
4
6
  import { inlineLink } from "./transformers/inlineLink.js";
5
7
  import { safeSelectors } from "./transformers/safeSelectors.js";
6
8
  import { attributeToStyle } from "./transformers/attributeToStyle.js";
@@ -17,8 +19,6 @@ import { purgeCss } from "./transformers/purgeCss.js";
17
19
  import { replaceStrings } from "./transformers/replaceStrings.js";
18
20
  import { format } from "./transformers/format.js";
19
21
  import { minify } from "./transformers/minify.js";
20
- import { normalizeComponentSources } from "./utils/componentSources.js";
21
- import { createRenderer } from "./render/createRenderer.js";
22
22
  import { createPlaintext } from "./plaintext.js";
23
23
  import { useCurrentTemplate } from "./composables/useCurrentTemplate.js";
24
24
  import { build } from "./build.js";
@@ -0,0 +1,49 @@
1
+ import { EventManager } from "../events/index.js";
2
+ import { MaizzleConfig } from "../types/config.js";
3
+ import { Renderer } from "./createRenderer.js";
4
+
5
+ //#region src/render/buildTemplate.d.ts
6
+ interface BuildTemplateContext {
7
+ config: MaizzleConfig;
8
+ renderer: Renderer;
9
+ events: EventManager;
10
+ outputPath: string;
11
+ outputExtension: string;
12
+ contentBase: string;
13
+ }
14
+ interface BuildTemplateResult {
15
+ /** Output files written for this template (html + optional plaintext). */
16
+ files: string[];
17
+ /**
18
+ * Number of SFC-registered `afterBuild` handlers seen while rendering. They
19
+ * only fire once at end of build on the main thread, so a worker can't run
20
+ * them — the count lets the orchestrator warn instead of silently dropping.
21
+ */
22
+ sfcAfterBuildCount: number;
23
+ }
24
+ /**
25
+ * Render a single template through the full pipeline and write its output.
26
+ *
27
+ * Shared by the sequential build loop and the parallel build worker so both
28
+ * paths produce byte-identical output. `events` is the manager the per-template
29
+ * events fire on (config handlers registered via `registerConfig`, SFC handlers
30
+ * registered here from the render). The caller owns build-scoped events
31
+ * (`beforeCreate`/`afterBuild`).
32
+ */
33
+ declare function buildTemplate(templatePath: string, ctx: BuildTemplateContext): Promise<BuildTemplateResult>;
34
+ /**
35
+ * Extract the static (non-glob) prefix from content patterns.
36
+ *
37
+ * For example, `['/abs/path/emails/**\/*.vue']` → `'/abs/path/emails'`
38
+ *
39
+ * Used to strip the content base from template paths so the output preserves
40
+ * only the subdirectory structure.
41
+ *
42
+ * With multiple positive patterns (multi-root setups), returns their common
43
+ * ancestor directory so templates from every root keep a clean relative path.
44
+ */
45
+ declare function computeContentBase(patterns: string[]): string;
46
+ declare function resolveOutputPath(templatePath: string, outputDir: string, extension: string, contentBase: string): string;
47
+ //#endregion
48
+ export { BuildTemplateContext, BuildTemplateResult, buildTemplate, computeContentBase, resolveOutputPath };
49
+ //# sourceMappingURL=buildTemplate.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"buildTemplate.d.ts","names":[],"sources":["../../src/render/buildTemplate.ts"],"mappings":";;;;;UAWiB,oBAAA;EACf,MAAA,EAAQ,aAAA;EACR,QAAA,EAAU,QAAA;EACV,MAAA,EAAQ,YAAA;EACR,UAAA;EACA,eAAA;EACA,WAAA;AAAA;AAAA,UAGe,mBAAA;EANK;EAQpB,KAAA;EAVA;;;;;EAgBA,kBAAkB;AAAA;;;;AAXP;AAGb;;;;AAQoB;iBAYE,aAAA,CACpB,YAAA,UACA,GAAA,EAAK,oBAAA,GACJ,OAAA,CAAQ,mBAAA;;;;;;;;;;;;iBAgHK,kBAAA,CAAmB,QAAkB;AAAA,iBA4BrC,iBAAA,CAAkB,YAAA,UAAsB,SAAA,UAAmB,SAAA,UAAmB,WAAA"}
@@ -0,0 +1,139 @@
1
+ import { runTransformers } from "../transformers/index.js";
2
+ import { createPlaintext } from "../plaintext.js";
3
+ import { stripForHtml, stripForPlaintext } from "../utils/output-markers.js";
4
+ import { _setCurrentTemplate } from "../composables/useCurrentTemplate.js";
5
+ import { mkdirSync, readFileSync, writeFileSync } from "node:fs";
6
+ import { basename, dirname, join, parse, relative, resolve, sep } from "node:path";
7
+ import defu from "defu";
8
+ //#region src/render/buildTemplate.ts
9
+ /**
10
+ * Render a single template through the full pipeline and write its output.
11
+ *
12
+ * Shared by the sequential build loop and the parallel build worker so both
13
+ * paths produce byte-identical output. `events` is the manager the per-template
14
+ * events fire on (config handlers registered via `registerConfig`, SFC handlers
15
+ * registered here from the render). The caller owns build-scoped events
16
+ * (`beforeCreate`/`afterBuild`).
17
+ */
18
+ async function buildTemplate(templatePath, ctx) {
19
+ const { config, renderer, events, outputPath, outputExtension, contentBase } = ctx;
20
+ const absolutePath = resolve(templatePath);
21
+ const parsedPath = parse(absolutePath);
22
+ const template = {
23
+ source: readFileSync(absolutePath, "utf-8"),
24
+ path: parsedPath
25
+ };
26
+ const files = [];
27
+ let sfcAfterBuildCount = 0;
28
+ _setCurrentTemplate(parsedPath);
29
+ try {
30
+ /**
31
+ * Clone config per template so beforeRender mutations (setting a
32
+ * preheader, injecting fetched data, etc.) stay scoped to this template
33
+ * instead of leaking into later ones through the shared config object.
34
+ */
35
+ const renderConfig = defu({}, config);
36
+ const originalSource = template.source;
37
+ await events.fireBeforeRender({
38
+ config: renderConfig,
39
+ template
40
+ });
41
+ const rendered = await renderer.render(absolutePath, renderConfig, template.source !== originalSource ? { source: template.source } : void 0);
42
+ /**
43
+ * Register SFC event handlers collected during render so they take part in
44
+ * the post-render events. Cleared at the end of this call so they don't
45
+ * leak into the next template (afterBuild is the exception — it's never
46
+ * cleared by clearSfcHandlers; see the count above).
47
+ */
48
+ for (const { name, handler } of rendered.sfcEventHandlers) {
49
+ if (name === "afterBuild") sfcAfterBuildCount++;
50
+ events.on(name, handler);
51
+ }
52
+ const templateConfig = rendered.templateConfig;
53
+ let html = await events.fireAfterRender({
54
+ config: templateConfig,
55
+ template,
56
+ html: rendered.html
57
+ });
58
+ const doctype = rendered.doctype ?? templateConfig.doctype ?? "<!DOCTYPE html>";
59
+ if (templateConfig.useTransformers !== false) html = await runTransformers(html, templateConfig, absolutePath, doctype, rendered.tailwindBlocks);
60
+ html = await events.fireAfterTransform({
61
+ config: templateConfig,
62
+ template,
63
+ html
64
+ });
65
+ if (doctype) html = `${doctype}\n${html}`;
66
+ const htmlOut = stripForHtml(html);
67
+ const sfcOutputPath = rendered.outputPath;
68
+ let outputFilePath;
69
+ if (sfcOutputPath) {
70
+ const parsed = parse(resolve(sfcOutputPath));
71
+ const ext = parsed.ext ? parsed.ext.slice(1) : outputExtension;
72
+ outputFilePath = join(parsed.dir, `${parsed.name}.${ext}`);
73
+ } else outputFilePath = resolveOutputPath(templatePath, outputPath, outputExtension, contentBase);
74
+ mkdirSync(dirname(outputFilePath), { recursive: true });
75
+ writeFileSync(outputFilePath, htmlOut);
76
+ files.push(outputFilePath);
77
+ const globalPlaintext = templateConfig.plaintext;
78
+ const sfcPlaintext = rendered.plaintext;
79
+ if (globalPlaintext || sfcPlaintext) {
80
+ const globalCfg = typeof globalPlaintext === "object" ? globalPlaintext : {};
81
+ const stripOptions = defu(sfcPlaintext?.options, globalCfg.options);
82
+ const plaintext = createPlaintext(stripForPlaintext(html), stripOptions);
83
+ const ptExtension = sfcPlaintext?.extension ?? globalCfg.extension ?? "txt";
84
+ let ptOutputPath;
85
+ if (sfcPlaintext?.destination) ptOutputPath = resolveOutputPath(templatePath, resolve(sfcPlaintext.destination), ptExtension, contentBase);
86
+ else if (sfcOutputPath) {
87
+ const parsed = parse(outputFilePath);
88
+ ptOutputPath = join(parsed.dir, `${parsed.name}.${ptExtension}`);
89
+ } else if (globalCfg.destination) ptOutputPath = resolveOutputPath(templatePath, resolve(globalCfg.destination), ptExtension, contentBase);
90
+ else ptOutputPath = resolveOutputPath(templatePath, outputPath, ptExtension, contentBase);
91
+ mkdirSync(dirname(ptOutputPath), { recursive: true });
92
+ writeFileSync(ptOutputPath, plaintext);
93
+ }
94
+ } finally {
95
+ _setCurrentTemplate(void 0);
96
+ events.clearSfcHandlers();
97
+ }
98
+ return {
99
+ files,
100
+ sfcAfterBuildCount
101
+ };
102
+ }
103
+ /**
104
+ * Extract the static (non-glob) prefix from content patterns.
105
+ *
106
+ * For example, `['/abs/path/emails/**\/*.vue']` → `'/abs/path/emails'`
107
+ *
108
+ * Used to strip the content base from template paths so the output preserves
109
+ * only the subdirectory structure.
110
+ *
111
+ * With multiple positive patterns (multi-root setups), returns their common
112
+ * ancestor directory so templates from every root keep a clean relative path.
113
+ */
114
+ function computeContentBase(patterns) {
115
+ const positives = patterns.filter((p) => !p.startsWith("!"));
116
+ return (positives.length > 0 ? positives : patterns).map((pattern) => {
117
+ const staticPart = pattern.split(/[*{?[]/)[0];
118
+ return resolve(staticPart.endsWith("/") ? staticPart : dirname(staticPart));
119
+ }).reduce(commonPath);
120
+ }
121
+ /** Longest common directory path shared by two absolute paths. */
122
+ function commonPath(a, b) {
123
+ const aSegments = a.split(sep);
124
+ const bSegments = b.split(sep);
125
+ const shared = [];
126
+ for (let i = 0; i < Math.min(aSegments.length, bSegments.length); i++) {
127
+ if (aSegments[i] !== bSegments[i]) break;
128
+ shared.push(aSegments[i]);
129
+ }
130
+ return shared.join(sep) || sep;
131
+ }
132
+ function resolveOutputPath(templatePath, outputDir, extension, contentBase) {
133
+ const name = basename(templatePath).replace(/\.(vue|md)$/, "");
134
+ return join(outputDir, relative(contentBase, dirname(resolve(templatePath))), `${name}.${extension}`);
135
+ }
136
+ //#endregion
137
+ export { buildTemplate, computeContentBase, resolveOutputPath };
138
+
139
+ //# sourceMappingURL=buildTemplate.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"buildTemplate.js","names":["parsePath"],"sources":["../../src/render/buildTemplate.ts"],"sourcesContent":["import { readFileSync, writeFileSync, mkdirSync } from 'node:fs'\nimport { resolve, dirname, basename, relative, join, sep, parse as parsePath } from 'node:path'\nimport defu from 'defu'\nimport { runTransformers } from '../transformers/index.ts'\nimport { createPlaintext } from '../plaintext.ts'\nimport { stripForHtml, stripForPlaintext } from '../utils/output-markers.ts'\nimport { _setCurrentTemplate } from '../composables/useCurrentTemplate.ts'\nimport type { EventManager } from '../events/index.ts'\nimport type { Renderer } from './createRenderer.ts'\nimport type { MaizzleConfig } from '../types/index.ts'\n\nexport interface BuildTemplateContext {\n config: MaizzleConfig\n renderer: Renderer\n events: EventManager\n outputPath: string\n outputExtension: string\n contentBase: string\n}\n\nexport interface BuildTemplateResult {\n /** Output files written for this template (html + optional plaintext). */\n files: string[]\n /**\n * Number of SFC-registered `afterBuild` handlers seen while rendering. They\n * only fire once at end of build on the main thread, so a worker can't run\n * them — the count lets the orchestrator warn instead of silently dropping.\n */\n sfcAfterBuildCount: number\n}\n\n/**\n * Render a single template through the full pipeline and write its output.\n *\n * Shared by the sequential build loop and the parallel build worker so both\n * paths produce byte-identical output. `events` is the manager the per-template\n * events fire on (config handlers registered via `registerConfig`, SFC handlers\n * registered here from the render). The caller owns build-scoped events\n * (`beforeCreate`/`afterBuild`).\n */\nexport async function buildTemplate(\n templatePath: string,\n ctx: BuildTemplateContext,\n): Promise<BuildTemplateResult> {\n const { config, renderer, events, outputPath, outputExtension, contentBase } = ctx\n const absolutePath = resolve(templatePath)\n const parsedPath = parsePath(absolutePath)\n const template = { source: readFileSync(absolutePath, 'utf-8'), path: parsedPath }\n const files: string[] = []\n let sfcAfterBuildCount = 0\n\n _setCurrentTemplate(parsedPath)\n\n try {\n /**\n * Clone config per template so beforeRender mutations (setting a\n * preheader, injecting fetched data, etc.) stay scoped to this template\n * instead of leaking into later ones through the shared config object.\n */\n const renderConfig = defu({}, config) as MaizzleConfig\n const originalSource = template.source\n\n await events.fireBeforeRender({ config: renderConfig, template })\n\n const rendered = await renderer.render(\n absolutePath,\n renderConfig,\n template.source !== originalSource ? { source: template.source } : undefined,\n )\n\n /**\n * Register SFC event handlers collected during render so they take part in\n * the post-render events. Cleared at the end of this call so they don't\n * leak into the next template (afterBuild is the exception — it's never\n * cleared by clearSfcHandlers; see the count above).\n */\n for (const { name, handler } of rendered.sfcEventHandlers) {\n if (name === 'afterBuild') sfcAfterBuildCount++\n events.on(name, handler)\n }\n\n const templateConfig = rendered.templateConfig\n\n let html = await events.fireAfterRender({ config: templateConfig, template, html: rendered.html })\n\n const doctype = rendered.doctype ?? templateConfig.doctype ?? '<!DOCTYPE html>'\n\n if (templateConfig.useTransformers !== false) {\n html = await runTransformers(html, templateConfig, absolutePath, doctype, rendered.tailwindBlocks)\n }\n\n html = await events.fireAfterTransform({ config: templateConfig, template, html })\n if (doctype) html = `${doctype}\\n${html}`\n\n const htmlOut = stripForHtml(html)\n const sfcOutputPath = rendered.outputPath\n let outputFilePath: string\n\n if (sfcOutputPath) {\n const parsed = parsePath(resolve(sfcOutputPath))\n const ext = parsed.ext ? parsed.ext.slice(1) : outputExtension\n outputFilePath = join(parsed.dir, `${parsed.name}.${ext}`)\n } else {\n outputFilePath = resolveOutputPath(templatePath, outputPath, outputExtension, contentBase)\n }\n\n mkdirSync(dirname(outputFilePath), { recursive: true })\n writeFileSync(outputFilePath, htmlOut)\n files.push(outputFilePath)\n\n // Generate plaintext version if configured\n const globalPlaintext = templateConfig.plaintext\n const sfcPlaintext = rendered.plaintext\n\n if (globalPlaintext || sfcPlaintext) {\n const globalCfg = typeof globalPlaintext === 'object' ? globalPlaintext : {}\n const stripOptions = defu(sfcPlaintext?.options, globalCfg.options)\n const plaintext = createPlaintext(stripForPlaintext(html), stripOptions)\n const ptExtension = sfcPlaintext?.extension ?? globalCfg.extension ?? 'txt'\n\n let ptOutputPath: string\n\n if (sfcPlaintext?.destination) {\n ptOutputPath = resolveOutputPath(templatePath, resolve(sfcPlaintext.destination), ptExtension, contentBase)\n } else if (sfcOutputPath) {\n const parsed = parsePath(outputFilePath)\n ptOutputPath = join(parsed.dir, `${parsed.name}.${ptExtension}`)\n } else if (globalCfg.destination) {\n ptOutputPath = resolveOutputPath(templatePath, resolve(globalCfg.destination), ptExtension, contentBase)\n } else {\n ptOutputPath = resolveOutputPath(templatePath, outputPath, ptExtension, contentBase)\n }\n\n mkdirSync(dirname(ptOutputPath), { recursive: true })\n writeFileSync(ptOutputPath, plaintext)\n }\n } finally {\n _setCurrentTemplate(undefined)\n events.clearSfcHandlers()\n }\n\n return { files, sfcAfterBuildCount }\n}\n\n/**\n * Extract the static (non-glob) prefix from content patterns.\n *\n * For example, `['/abs/path/emails/**\\/*.vue']` → `'/abs/path/emails'`\n *\n * Used to strip the content base from template paths so the output preserves\n * only the subdirectory structure.\n *\n * With multiple positive patterns (multi-root setups), returns their common\n * ancestor directory so templates from every root keep a clean relative path.\n */\nexport function computeContentBase(patterns: string[]): string {\n const positives = patterns.filter(p => !p.startsWith('!'))\n const sources = positives.length > 0 ? positives : patterns\n\n const bases = sources.map((pattern) => {\n // Split on first glob character (* { ? [) and take the directory part\n const staticPart = pattern.split(/[*{?[]/)[0]\n // Ensure we have a clean directory path (not a partial segment)\n return resolve(staticPart.endsWith('/') ? staticPart : dirname(staticPart))\n })\n\n return bases.reduce(commonPath)\n}\n\n/** Longest common directory path shared by two absolute paths. */\nfunction commonPath(a: string, b: string): string {\n const aSegments = a.split(sep)\n const bSegments = b.split(sep)\n const shared: string[] = []\n\n for (let i = 0; i < Math.min(aSegments.length, bSegments.length); i++) {\n if (aSegments[i] !== bSegments[i]) break\n shared.push(aSegments[i])\n }\n\n return shared.join(sep) || sep\n}\n\nexport function resolveOutputPath(templatePath: string, outputDir: string, extension: string, contentBase: string): string {\n const name = basename(templatePath).replace(/\\.(vue|md)$/, '')\n const absTemplate = resolve(templatePath)\n const rel = relative(contentBase, dirname(absTemplate))\n\n return join(outputDir, rel, `${name}.${extension}`)\n}\n"],"mappings":"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"}
@@ -15,7 +15,9 @@ interface RenderedTemplate {
15
15
  tailwindBlocks?: RenderContext['tailwindBlocks'];
16
16
  }
17
17
  interface Renderer {
18
- render(input: string | Component, config: MaizzleConfig): Promise<RenderedTemplate>;
18
+ render(input: string | Component, config: MaizzleConfig, opts?: {
19
+ source?: string;
20
+ }): Promise<RenderedTemplate>;
19
21
  invalidate(filePath: string): Promise<void>;
20
22
  invalidateAll(): Promise<void>;
21
23
  close(): Promise<void>;
@@ -1 +1 @@
1
- {"version":3,"file":"createRenderer.d.ts","names":[],"sources":["../../src/render/createRenderer.ts"],"mappings":";;;;;;;UAmCiB,gBAAA;EACf,IAAA;EACA,OAAA;EACA,cAAA,EAAgB,aAAA;EAChB,gBAAA,EAAkB,aAAA;EAClB,SAAA,GAAY,aAAA;EACZ,UAAA,GAAa,aAAA;EACb,cAAA,GAAiB,aAAA;AAAA;AAAA,UAGF,QAAA;EACf,MAAA,CAAO,KAAA,WAAgB,SAAA,EAAW,MAAA,EAAQ,aAAA,GAAgB,OAAA,CAAQ,gBAAA;EAClE,UAAA,CAAW,QAAA,WAAmB,OAAA;EAC9B,aAAA,IAAiB,OAAA;EACjB,KAAA,IAAS,OAAA;AAAA;AAAA,UAGM,qBAAA;EAdC;EAgBhB,GAAA;EAfkB;EAiBlB,QAAA,GAAW,cAAA;EAhBC;EAkBZ,IAAA;EAjBa;;;;EAsBb,aAAA,GAAgB,yBAAA;EAlBD;EAoBf,IAAA,GAAO,YAAA;AAAA;;;;;;;iBASa,cAAA,CACpB,OAAA,GAAS,qBAAA,GACR,OAAA,CAAQ,QAAA"}
1
+ {"version":3,"file":"createRenderer.d.ts","names":[],"sources":["../../src/render/createRenderer.ts"],"mappings":";;;;;;;UAmCiB,gBAAA;EACf,IAAA;EACA,OAAA;EACA,cAAA,EAAgB,aAAA;EAChB,gBAAA,EAAkB,aAAA;EAClB,SAAA,GAAY,aAAA;EACZ,UAAA,GAAa,aAAA;EACb,cAAA,GAAiB,aAAA;AAAA;AAAA,UAGF,QAAA;EACf,MAAA,CAAO,KAAA,WAAgB,SAAA,EAAW,MAAA,EAAQ,aAAA,EAAe,IAAA;IAAS,MAAA;EAAA,IAAoB,OAAA,CAAQ,gBAAA;EAC9F,UAAA,CAAW,QAAA,WAAmB,OAAA;EAC9B,aAAA,IAAiB,OAAA;EACjB,KAAA,IAAS,OAAA;AAAA;AAAA,UAGM,qBAAA;EAbG;EAelB,GAAA;EAdY;EAgBZ,QAAA,GAAW,cAAA;EAfE;EAiBb,IAAA;EAhBiB;;AAAa;AAGhC;EAkBE,aAAA,GAAgB,yBAAA;;EAEhB,IAAA,GAAO,YAAA;AAAA;;;;;;;iBASa,cAAA,CACpB,OAAA,GAAS,qBAAA,GACR,OAAA,CAAQ,QAAA"}