UNPKG

@maizzle/framework

Version:

Maizzle is a framework that helps you quickly build HTML emails with Tailwind CSS.

768 lines (767 loc) 23 kB
import { RemoveValue } from "../plugins/postcss/removeDeclarations.js"; import { TemplateInfo } from "../events/index.js"; import { Directive, Plugin } from "vue"; import { InlineConfig } from "vite"; import juice from "juice"; import { Options } from "unplugin-vue-markdown/types"; //#region src/types/config.d.ts type JuiceOptions = NonNullable<Parameters<typeof juice>[1]>; interface UrlQueryOptions { /** * CSS selectors for elements to process. * * @default ['a'] */ tags?: string[]; /** * HTML attributes containing URLs to append query params to. * * @default ['src', 'href', 'poster', 'srcset', 'background'] */ attributes?: string[]; /** * When `true`, only appends query params to absolute URLs. * * @default true */ strict?: boolean; /** * Options forwarded to the `query-string` library for controlling serialization. * * @default { encode: false } */ qs?: Record<string, unknown>; } type UrlQuery = Record<string, unknown> & { _options?: UrlQueryOptions; }; interface UrlConfig { /** * Append query parameters to URLs in your HTML. * * @example * url: { * query: { * utm_source: 'maizzle', * utm_medium: 'email', * } * } */ query?: UrlQuery; /** * Prepend a base URL to relative paths. * * Pass a string to prepend to all tags, or an object for fine-grained control. * * @example * url: { * base: 'https://cdn.example.com/emails/', * } */ base?: string | { /** The base URL to prepend. */url?: string; /** Tags or tag-attribute map to process. */ tags?: string[] | Record<string, Record<string, string | boolean>>; /** Attributes to process. */ attributes?: Record<string, string>; /** Also apply to URLs in `<style>` tags. */ styleTag?: boolean; /** Also apply to URLs in inline `style` attributes. */ inlineCss?: boolean; }; } interface CssConfig { /** * Base directory for Tailwind CSS `@source` resolution. * * Automatically set to `root` when `root` is configured. */ base?: string; /** * Remove unused CSS. * * Set to `true` to enable with defaults, or pass an options object. * * @default false */ purge?: boolean | Record<string, unknown>; /** * Inline CSS from `<style>` tags into matching HTML elements. * * Set to `true` to enable with defaults, or pass an options object for fine-grained control. * * @example * css: { * inline: { * removeStyleTags: true, * applyWidthAttributes: true, * } * } */ inline?: boolean | JuiceOptions & { /** * Convert HTML attributes like `width`, `height`, `bgcolor`, and `valign` * to inline CSS styles. Set to `true` for all, or pass an array of attribute names. * * @default false */ attributeToStyle?: boolean | string[]; /** * Convert `0px`, `0em` etc. to `0` in inline styles. * * @default true */ preferUnitlessValues?: boolean; /** * CSS selectors to preserve in `<style>` tags, even after inlining. * Mapped to Juice's `preservedSelectors` option. * * @default [] */ safelist?: string[]; /** * Duplicate CSS properties to HTML attributes. * Mapped to Juice's static `styleToAttribute` property. * * @default {} * * @example * styleToAttribute: { * 'background-color': 'bgcolor', * } */ styleToAttribute?: Record<string, string>; /** * Elements that can receive `width` HTML attributes. * Mapped to Juice's static `widthElements` property. * * @default ['img', 'video'] */ widthElements?: string[]; /** * Elements that can receive `height` HTML attributes. * Mapped to Juice's static `heightElements` property. * * @default ['img', 'video'] */ heightElements?: string[]; /** * CSS properties to exclude from inlining. * Mapped to Juice's static `excludedProperties` property. * * @default [] */ excludedProperties?: string[]; /** * Template language code blocks to preserve during inlining. * Mapped to Juice's static `codeBlocks` property. * * @default { EJS: { start: '<%', end: '%>' }, HBS: { start: '\{\{', end: '}}' } } */ codeBlocks?: Record<string, { start: string; end: string; }>; /** * Additional CSS string to inline alongside `<style>` tag contents. * Mapped to Juice's `extraCss` option. */ customCSS?: string; }; /** * Merge duplicate `@media` queries and sort them. * * Enabled by default. Set to `false` to disable, or pass an object to control sort order. * * @default true * * @example * css: { * media: { sort: 'desktop-first' }, * } */ media?: boolean | { /** * Sort order for media queries. * * @default 'mobile-first' */ sort?: 'mobile-first' | 'desktop-first' | ((a: string, b: string) => number); }; /** * Strip units from zero values in inlined styles. * * For example, `padding: 0px 16px` becomes `padding: 0 16px`. * * @default true */ preferUnitless?: boolean; /** * Replace unsafe CSS class names with email-safe equivalents. * * @default true */ safe?: boolean | Record<string, string>; /** * Rewrite longhand CSS to shorthand where possible. * * For example, `padding: 10px 20px 10px 20px` becomes `padding: 10px 20px`. * * @default false */ shorthand?: boolean | { tags?: string[]; }; /** * Convert 3-digit HEX colors to 6-digit in `bgcolor` and `color` attributes. * * Some email clients don't support shorthand HEX like `#fff`. * * @default true */ sixHex?: boolean; /** * Remove specific CSS declarations by selector. * * @example * css: { * removeDeclarations: { * ':root': '*', * } * } */ removeDeclarations?: Record<string, RemoveValue>; /** * Glob patterns or paths excluded from Tailwind's `@source` scanner. * * Tailwind won't generate utilities for classes used in these files. * Useful for ignoring AMP variants or any templates whose classes * shouldn't end up in the output CSS. * * @example * css: { * exclude: ['emails/amp/**'], * } */ exclude?: string[]; } interface AttributesConfig { /** * Add attributes to HTML elements. * * Set the whole map to `false` to disable auto-add. Set a selector * entry to `false` to skip that selector. Set an individual attribute * to `false` to skip just that attribute while keeping the rest. * * @example * html: { * attributes: { * add: { * table: { cellpadding: 0, cellspacing: 0, role: 'none' }, * img: { alt: '' }, * } * } * } */ add?: false | Record<string, false | Record<string, false | string | boolean | number>>; /** * Remove attributes from HTML elements. * * Empty `style` and `class` attributes are always stripped, regardless * of config. Entries added here are appended to those defaults. * * - String — remove when the attribute's value is empty. * - `{ name, value: 'literal' }` — remove when the value matches exactly. * - `{ name, value: /regex/ }` — remove when the value matches the regex. * * @example * html: { * attributes: { * remove: [ * 'data-foo', * { name: 'role', value: 'none' }, * { name: 'class', value: /^js-/ }, * ], * } * } */ remove?: Array<string | { name: string; value?: string | RegExp; }>; } type EntitiesConfig = boolean | Record<string, string>; /** * caniemail.com client family slugs. Maintained manually from * https://www.caniemail.com/api/data.json — update this list when caniemail * adds new clients. */ type CaniemailClient = 'gmail' | 'outlook' | 'yahoo' | 'apple-mail' | 'aol' | 'thunderbird' | 'microsoft' | 'samsung-email' | 'sfr' | 'orange' | 'protonmail' | 'hey' | 'mail-ru' | 'fastmail' | 'laposte' | 't-online-de' | 'free-fr' | 'gmx' | 'web-de' | 'ionos-1and1' | 'rainloop' | 'wp-pl'; interface ChecksConfig { /** * Client families to check against. Defaults to the four majors: * Gmail, Apple Mail, Outlook, Yahoo. Pass `'all'` to check every client * in the caniemail dataset. */ clients?: CaniemailClient[] | 'all'; /** * Filter which severities are reported. Omit to show everything. * * - `'error'` — only errors (unsupported features, hard lint errors) * - `'warning'` — only warnings (partial / unknown support, lint warnings) * - `'lint'` — only lint items (both severities, no compat items) */ level?: 'error' | 'warning' | 'lint'; } interface PostcssConfig { /** * Selector prefixes to strip from compiled CSS. * * @default [':host', ':lang'] * * @example * postcss: { * removeSelectors: [':host', ':lang', ':root'], * } */ removeSelectors?: string[]; /** * At-rule names to strip from compiled CSS. * * @default ['layer', 'property'] * * @example * postcss: { * removeAtRules: ['layer', 'property', 'charset'], * } */ removeAtRules?: string[]; } interface HtmlConfig { /** Configure HTML attribute transformations. */ attributes?: AttributesConfig; /** * Decode HTML entities. * * Set to `true` to decode all, or pass a map of entities to decode. * * @default true */ decodeEntities?: EntitiesConfig; /** * Pretty-print the HTML output. * * Set to `true` to enable with defaults, or pass options. */ format?: boolean | import('oxfmt').FormatConfig; /** * Minify the HTML output. * * Set to `true` to enable with defaults, or pass `html-crush` * options to customize. * * @see https://codsen.com/os/html-crush */ minify?: boolean | Partial<import('html-crush').Opts>; } type FilterFunction = (str: string, value: string) => string; type FiltersConfig = false | Record<string, FilterFunction>; interface MarkdownConfig extends Options { /** * The shiki theme to use for syntax highlighting in Markdown fenced code blocks. * * @default 'github-light' */ shikiTheme?: import('shiki').BundledTheme; } interface VueConfig { /** * Vue plugins to register on the app instance before rendering. * * Pass a factory (`() => Plugin[]`) for stateful plugins like vue-i18n * or Pinia to get a fresh instance per render — otherwise state leaks * across templates (e.g. one template setting `locale.value = 'fr'` * affects the next render). */ plugins?: Plugin[] | (() => Plugin[]); /** Custom Vue directives to register globally. */ directives?: Record<string, Directive>; /** Properties added to `app.config.globalProperties`, available in all templates. */ globalProperties?: Record<string, unknown>; } /** * Per-transformer toggle map for `useTransformers`. * * - `false` skips the listed transformer. * - `true` force-enables it for this run (only meaningful for boolean-driven * transformers: inlineCss, purgeCss, prettify, minify, shorthandCss, * sixHex, safeSelectors, entities). Layers on the matching * `css.*` / `html.*` config slice. * - missing keys keep their default behavior. * * Data-driven transformers (filters, baseURL, urlQuery, addAttributes, * removeAttributes, replaceStrings, attributeToStyle) require actual * config values — a bare `true` is a no-op for them. * * Transformers without a toggle (Tailwind, MSO placeholder resolution, * column width math, link inlining) always run; they're driven by markup * or framework state, not user opt-in. */ interface TransformerToggles { safeSelectors?: boolean; attributeToStyle?: boolean; inlineCss?: boolean; removeAttributes?: boolean; shorthandCss?: boolean; sixHex?: boolean; addAttributes?: boolean; filters?: boolean; baseURL?: boolean; urlQuery?: boolean; purgeCss?: boolean; entities?: boolean; replaceStrings?: boolean; prettify?: boolean; minify?: boolean; } /** * Plaintext generation options. * * Control where plaintext files are written, what extension they use, * and the options forwarded to `string-strip-html`. */ interface PlaintextConfig { /** * Output directory for plaintext files. When omitted, files are written * next to their HTML counterpart in `build.output.path`. */ destination?: string; /** * File extension for plaintext files (without the leading dot). * * @default 'txt' */ extension?: string; /** * Options forwarded to `string-strip-html`. * * @see https://codsen.com/os/string-strip-html */ options?: Partial<import('string-strip-html').Opts>; } /** * Source directory entry for component auto-import. * * String → folder name becomes a namespace prefix automatically * (e.g. `widgets/Button.vue` → `<WidgetsButton />`). * * Object → custom prefix overrides the folder-derived name. With * `pathPrefix: false`, intermediate subfolders are dropped from the * resolved name (useful for icon sets organized by category). */ type ComponentSource = string | { /** Directory to scan, resolved relative to `cwd`. */path: string; /** * Custom prefix prepended to every resolved component name. * Empty string disables the auto folder-name prefix. */ prefix?: string; /** * Include intermediate subfolder names in the resolved component * name. Defaults to `true`. * * @example * // pathPrefix: true → icons/social/twitter.vue → <IconSocialTwitter /> * // pathPrefix: false → icons/social/twitter.vue → <IconTwitter /> */ pathPrefix?: boolean; }; interface MaizzleConfig { /** * Root directory for the Maizzle email project. * * When set, relative paths for `content`, `static.source`, * and `css.base` are all resolved relative to this directory. * * Defaults to `process.cwd()`. * * @example * maizzle({ * root: 'resources/js/emails', * content: ['./**\/*.vue'], * }) */ root?: string; /** Options for Markdown template support, extending `unplugin-vue-markdown`. */ markdown?: MarkdownConfig; /** * Glob patterns for email template files to process. * * Resolved relative to `root`. * * @default ['emails/**\/*.{vue,md}'] */ content?: string[]; /** Output configuration for built email templates. */ output?: { /** * Directory to write compiled HTML files to. * * @default 'dist' */ path?: string; /** * File extension for compiled templates. * * @default 'html' * * @example * output: { * extension: 'blade.php', * } */ extension?: string; }; /** * Build templates in parallel across worker threads. * * - omitted (default): parallel when there are more than 50 templates * - `true`: always parallel, using min(CPU count − 1, 8) workers * - `false`: always sequential * - `{ workers, threshold }`: parallel when the template count exceeds * `threshold` (default 50), using `workers` threads (default * min(CPU count − 1, 8)). Set `threshold: 0` to always parallelize. * * Note: more workers is not faster — each runs a full renderer, so ~8 is the * sweet spot and over-provisioning slows builds down. * * Only applies to file-based configs (the CLI / a config path); an inline * config object always builds sequentially. SFC-registered `afterBuild` * handlers can't run in a worker — use the config `afterBuild` hook instead. */ parallel?: boolean | { workers?: number; threshold?: number; }; /** Static file copying configuration. */ static?: { /** * Glob patterns for static files to copy to the output directory. * * @default ['public/**\/*.*'] */ source?: string[]; /** * Subdirectory in the output folder where static files are placed. * * @default 'public' */ destination?: string; }; /** Component auto-import configuration. */ components?: { /** * Additional directories to scan for auto-imported Vue components. * * Resolved relative to `cwd` (not `root`), so paths outside the * email root directory work as expected. * * String entries use the folder name as a namespace prefix * automatically (e.g. `widgets/Button.vue` → `<WidgetsButton />`). * Object entries override that with a custom prefix. * * @example * components: { * source: [ * 'resources/js/components/email', * { path: 'src/widgets', prefix: 'W' }, * { path: 'src/icons', prefix: 'Icon', pathPrefix: false }, * ], * } */ source?: ComponentSource | ComponentSource[]; }; /** Dev server configuration. */ server?: { /** * Port for the dev server. * * @default 3000 */ port?: number; /** * Additional file paths to watch for changes. * * @default [] * * @example * server: { * watch: ['./tailwind.config.ts'], * } */ watch?: string[]; /** * Email sending configuration for the "Send test" feature in the dev UI. * * When not configured, falls back to Ethereal (free fake SMTP — emails * are captured and viewable via a URL, never actually delivered). * * @example * server: { * email: { * to: ['test@example.com'], * from: 'dev@maizzle.test', * transport: { * host: 'smtp.mailtrap.io', * port: 587, * auth: { user: '...', pass: '...' }, * }, * }, * } */ email?: { /** Default recipient(s). */to?: string | string[]; /** Sender address. @default 'Maizzle <maizzle@ethereal.email>' */ from?: string; /** Default subject line. */ subject?: string; /** Nodemailer transport options (SMTP, SES, etc.). Omit to use Ethereal. */ transport?: Record<string, unknown>; }; /** * Configure or disable the Checks tab in the dev UI. * * Set to `false` to disable checks entirely (the tab is hidden). * * @example * server: { * checks: { * clients: ['gmail', 'outlook', 'apple-mail'], * level: 'error', * } * } */ checks?: false | ChecksConfig; }; /** Tailwind CSS and email CSS optimization settings. */ css?: CssConfig; /** * Generate a plaintext version of the email. * * Set to `true` to enable with defaults, or pass an object to configure * destination directory, file extension, and `string-strip-html` options. * * @default false * @example * plaintext: { * destination: 'build_production/plaintext', * extension: 'txt', * options: { ignoreTags: ['br'] }, * } */ plaintext?: boolean | PlaintextConfig; /** PostCSS processing options. */ postcss?: PostcssConfig; /** * Enable the transformer pipeline (CSS inlining, purging, shorthand, etc). * * Pass `false` to skip the entire pipeline. Pass an object to opt out of * specific transformers while keeping the rest active — only keys set * to `false` are skipped. * * @default true * @example * useTransformers: { inlineCss: false, minify: false } */ useTransformers?: boolean | TransformerToggles; /** * Replace strings in the final HTML output. * * @example * replaceStrings: { * '{{ year }}': new Date().getFullYear().toString(), * } */ replaceStrings?: Record<string, string>; /** * Content filters that transform text inside HTML elements using custom attributes. * * Set to `false` to disable all filters. Pass an object to add custom filters * (merged with built-in defaults). * * @example * filters: { * uppercase: str => str.toUpperCase(), * } */ filters?: FiltersConfig; /** URL transformation settings (base URL, query string appending). */ url?: UrlConfig; /** HTML post-processing settings (attributes, formatting, minification). */ html?: HtmlConfig; /** * Vite configuration options passed to the internal Vite SSR server. * * Use this to add custom Vite plugins or other Vite options. The internal * SSR server never loads a project `vite.config.{ts,js}` (it runs with * `configFile: false`), so pass anything it needs here. These options are * merged underneath Maizzle's required settings, which take precedence. * * @example * vite: { * plugins: [myPlugin()], * } */ vite?: InlineConfig; /** * Vue app customization options. * * Register plugins, directives, or global properties on the * internal Vue app instance used for SSR rendering. * * @example * vue: { * // Use a factory for stateful plugins (vue-i18n, Pinia, vue-router) * // so each render gets a fresh instance. * plugins: () => [createI18n({ locale: 'en', messages })], * directives: { focus: vFocus }, * globalProperties: { $format: dateFormat }, * } */ vue?: VueConfig; /** * Props passed to the template's root component when rendering * programmatically. Map 1:1 to the template's `defineProps`. Not * merged into `useConfig()`. * * Props not declared via `defineProps` fall through as HTML * attributes on the root element, so declare every prop you pass. * * @example * render('./welcome.vue', { props: { name: 'Ava', plan: 'Pro' } }) */ props?: Record<string, any>; /** Called before any templates are processed. */ beforeCreate?: (params: { config: MaizzleConfig; }) => void | Promise<void>; /** Called before each template is rendered. Return a string to replace `template.source`. */ beforeRender?: (params: { config: MaizzleConfig; template: TemplateInfo; }) => string | void | Promise<string | void>; /** Called after each template is rendered but before transformers run. Return a string to replace the output HTML. */ afterRender?: (params: { config: MaizzleConfig; template: TemplateInfo; html: string; }) => string | void | Promise<string | void>; /** Called after transformers have run on each template. Return a string to replace the output HTML. */ afterTransform?: (params: { config: MaizzleConfig; template: TemplateInfo; html: string; }) => string | void | Promise<string | void>; /** Called after all templates have been built. */ afterBuild?: (params: { files: string[]; config: MaizzleConfig; }) => void | Promise<void>; [key: string]: any; } //#endregion export { AttributesConfig, CaniemailClient, ChecksConfig, ComponentSource, CssConfig, EntitiesConfig, FilterFunction, FiltersConfig, HtmlConfig, MaizzleConfig, MarkdownConfig, PlaintextConfig, PostcssConfig, TransformerToggles, UrlConfig, UrlQuery, UrlQueryOptions, VueConfig }; //# sourceMappingURL=config.d.ts.map