Documentation
¶
Overview ¶
Package mjmlgo renders MJML documents to HTML.
The implementation targets MJML 4.18.0 exactly: for the supported feature set it produces the same document as the reference JavaScript implementation. Output is compact (never pretty-printed) and validation is fail-fast: unknown tags, invalid attributes or nesting, and missing required children are reported as errors instead of being warned about and ignored. Invalid input never produces partial output.
Every validation and MJML parse rejection is a structured *Error: inspect its Kind, Path, Attribute and Value instead of matching the message text. I/O failures that are not about the document's content - an unreadable top-level file, a nil argument, a cancelled context - keep their own plain errors. errors.Is still finds the package's sentinels (ErrValidation, ErrParsingFailed, ErrIncludeCycle, ...) and errors.As unwraps through wrapping, so both inspection styles keep working. The kinds are a closed set; see the ErrorKind constants.
Render writes the HTML for an in-memory document to a writer, and NewRenderer configures a renderer first, for example to drop author comments:
var out bytes.Buffer err := mjmlgo.Render(&out, src) renderer := mjmlgo.NewRenderer(mjmlgo.WithComments(false)) err = renderer.Render(ctx, &out, src)
The output goes to the writer the caller supplies, so a bytes.Buffer that is Reset between renders keeps its capacity and a server allocates the output buffer once.
A document that uses mj-include needs a filesystem: Renderer.RenderFS reads the top-level document through an fs.FS and resolves includes against it, so callers decide where documents come from and the renderer performs no implicit filesystem or network access. A Renderer is safe for concurrent use.
WithComponents adds custom components to a renderer. A host implements component.Component - tag, allowed attributes with validators, defaults and render - and registers it; a registered tag is then a first-class element, with the same validation and default cascade as a built-in one. Registration is per renderer, never global. Implement component.ChildComponent to declare child elements. example/customcomponent is the worked example, and package component documents the contract.
mj-include is resolved through the same fs.FS: paths are relative to the directory of the file that contains the include, and type="html", type="css" and the default type="mjml" are supported. Include cycles, paths that would leave the filesystem and unknown include types are errors.
The tree the renderer walks and the components it dispatches to live in the public node and component packages, so that custom components can receive the same values. Their exported shape is frozen; their package documentation lists the exact set, and changing any of it is a breaking change.
Renderer.RenderFile is the shorthand for a document on disk: the file's own directory becomes the filesystem root, so relative includes stay scoped to it.
Index ¶
Constants ¶
const ( KindUnknownElement = component.KindUnknownElement KindUnknownAttribute = component.KindUnknownAttribute KindInvalidValue = component.KindInvalidValue KindDisallowedChild = component.KindDisallowedChild KindUnknownIncludeType = component.KindUnknownIncludeType KindUndefinedMJClass = component.KindUndefinedMJClass KindParseFailure = component.KindParseFailure )
The closed set of structured-error kinds, re-exported from the component package. Each kind's meaning is documented on the component constant of the same name; the block is the whole set: KindUnknownElement, KindUnknownAttribute, KindInvalidValue, KindDisallowedChild, KindUnknownIncludeType, KindUndefinedMJClass and KindParseFailure.
Variables ¶
var ( ErrParsingFailed = errors.New("parsing MJML structure failed") // ErrIncludeNoFS is returned when a document contains mj-include but was // rendered through an entry point without a filesystem (Renderer.Render). ErrIncludeNoFS = errors.New("mjml: mj-include requires an fs.FS") // ErrIncludePath is returned for include paths that are absolute, escape the // filesystem root or are otherwise not valid fs.FS names. ErrIncludePath = errors.New("mjml: invalid mj-include path") // ErrIncludeType is returned for mj-include type values other than mjml, // html and css. MJML silently treats unknown types as mjml includes. ErrIncludeType = errors.New("mjml: unknown mj-include type") // ErrIncludeCycle is returned when a file includes itself, directly or // through other files. ErrIncludeCycle = errors.New("mjml: mj-include cycle") // ErrIncludeDepth is returned when includes nest deeper than maxIncludeDepth. ErrIncludeDepth = errors.New("mjml: mj-include depth limit exceeded") )
var ErrUnknownStartingTag = errors.New("mjml: unknown starting tag")
ErrUnknownStartingTag reports a document whose root element is not <mjml>.
var ErrValidation = component.ErrValidation
ErrValidation reports a document that failed validation. A structured error of a validation kind matches it with errors.Is.
Functions ¶
Types ¶
type Error ¶
Error is the structured failure every validation and parse site returns. It is the same type as component.Error, aliased here so callers of this package can use errors.As to a *mjmlgo.Error without importing the component package.
Inspect Kind, Path, Attribute and Value instead of matching the message text: the text is for people and may change. errors.Is works for the package's error sentinels (ErrValidation, ErrParsingFailed, ErrIncludeCycle, ...) and errors.As unwraps through wrapping.
type ErrorKind ¶
ErrorKind classifies a structured Error. See the component package constants for the closed set of kinds.
type Font ¶
type Font struct {
// Family is the name documents refer to, e.g. "Inter". It is matched
// case-sensitively against the families a document mentions. An entry with
// an empty Family is ignored.
Family string
// Href is the stylesheet URL, imported the way MJML imports its defaults.
Href string
}
Font is one entry of the web-font registry: the family a document names in a font-family attribute, and the URL of the stylesheet that provides it.
func FontsFromMap ¶
FontsFromMap converts a family-to-URL map into a registry ordered by family name. It is the bridge for callers holding a map: a Go map has no order of its own, so this states the order instead of leaving it to chance. FontsFromMap(nil) keeps the default registry, like WithFonts(nil).
type Option ¶
type Option func(*rendererConfig)
Option configures a Renderer.
func WithComments ¶
WithComments controls whether author comments in the document are preserved. The default is true, matching MJML: comments are emitted where MJML emits them, byte for byte. WithComments(false) drops them while parsing, exactly like MJML's keepComments option; comments inside raw content elements (mj-text, mj-raw, mj-table, ...) are part of that content and are never affected, and neither are the Outlook conditional comments the renderer generates itself.
func WithComponents ¶
WithComponents registers custom components on the Renderer. A registered tag becomes a first-class MJML element: it is validated like a built-in one, receives the defaults declared in <mj-attributes> and <mj-class>, and renders at its document position. Registration is per renderer - there is no global registry - so two renderers in one process may know different tags.
A custom component is accepted where content goes - as a direct child of mj-body, mj-column or mj-hero - and is a leaf unless it implements component.ChildComponent. A leaf rejects every child under its own tag. A container declares the children it accepts and renders them with component.RenderChildren, so it controls the markup around them; its declared children must be built-in content components or other components registered on the same renderer.
A component's tag must not be empty, must not be registered twice and must not shadow a built-in element; such a renderer is rejected with a *component.Error of kind component.KindInvalidComponent, whose Path names the offending tag (and whose Value names the declared child, for a child nothing can render), when it renders. Calling WithComponents more than once registers the union, so a duplicate tag across calls is rejected like a duplicate within one call.
func WithFonts ¶
WithFonts replaces the web-font registry MJML imports only for the families a document actually uses. WithFonts(nil) keeps the five defaults (Open Sans, Droid Sans, Lato, Roboto, Ubuntu); a non-nil slice replaces them completely, so a default family is no longer imported unless the slice lists it. An <mj-font> element in the document still wins for its family, like it does in MJML.
Fonts are emitted in the order given, which is the order the reference implementation emits a registry it received as a JavaScript object. A family listed twice keeps its first position and its last URL, like assigning to an object property does. FontsFromMap builds the slice from a map for callers whose registry has no order worth preserving.
func WithIDGenerator ¶
WithIDGenerator overrides the source of the opaque ids MJML assigns to navbar hamburger labels and carousels. The default is 16 lowercase hex characters from crypto/rand, like the reference implementation.
A constant generator makes rendering deterministic. The generator is called while the renderer holds its lock, so it does not need to be safe for concurrent use itself.
func WithIgnoreIncludes ¶
WithIgnoreIncludes leaves mj-include elements unexpanded, like MJML's ignoreIncludes option: the element is dropped from the output and the files it names are never read.
func WithPrinterSupport ¶
WithPrinterSupport adds MJML's printer style block to the head: the same media queries the document already generates, repeated inside an `@media only print` rule. It has no effect on a document without media queries, matching the reference implementation.
type Renderer ¶
type Renderer struct {
// contains filtered or unexported fields
}
Renderer renders MJML documents with the options it was created with. It is safe for concurrent use by multiple goroutines. All per-render state lives in the individual call.
func NewRenderer ¶
NewRenderer returns a Renderer configured with the given options.
func (*Renderer) Render ¶
Render renders the MJML document in src and writes the resulting HTML to w. Cancellation is honoured before any work starts and between stages.
src is the whole document, so the renderer never touches the filesystem on its own and documents with mj-include are rejected with ErrIncludeNoFS: use RenderFS to resolve includes against a filesystem.
A bytes.Buffer can be reused across renders: its Reset keeps the capacity, so a server can grow the output buffer once and hand the same one to every call. strings.Builder cannot be reused for that - its Reset drops the buffer.
func (*Renderer) RenderFS ¶
RenderFS reads the MJML document name from fsys, renders it and writes the resulting HTML to w. Cancellation is honoured before any work starts.
The document is read through fsys, so rendering never touches the filesystem on its own. mj-include is resolved through the same fsys: include paths are relative to the directory of the file that contains them, and include cycles, paths escaping the filesystem root and unknown include types are hard errors.
Source Files
¶
Directories
¶
| Path | Synopsis |
|---|---|
|
cmd
|
|
|
mjmlgo
command
Command mjmlgo compiles MJML documents to HTML.
|
Command mjmlgo compiles MJML documents to HTML. |
|
Package component renders the MJML component tree: every body and head component of MJML 4.18, the per-render state the renderer accumulates from the document head, the validation applied to a parsed document before it is rendered, and the contract a host implements to add a component of its own.
|
Package component renders the MJML component tree: every body and head component of MJML 4.18, the per-render state the renderer accumulates from the document head, the validation applied to a parsed document before it is rendered, and the contract a host implements to add a component of its own. |
|
example
|
|
|
customcomponent
Package customcomponent is the in-repo example of the custom-component contract in package component.
|
Package customcomponent is the in-repo example of the custom-component contract in package component. |
|
internal
|
|
|
mjmltest
Package mjmltest contains the test harness that renders MJML input with the reference JavaScript implementation (the `mjml` npm package) so that the Go implementation in this repository can be diffed against it.
|
Package mjmltest contains the test harness that renders MJML input with the reference JavaScript implementation (the `mjml` npm package) so that the Go implementation in this repository can be diffed against it. |
|
Package node holds the element tree the parser produces and the renderer walks: one Node per MJML element, with its attributes, its children and - for a raw-content element such as mj-text or mj-raw - its unparsed content.
|
Package node holds the element tree the parser produces and the renderer walks: one Node per MJML element, with its attributes, its children and - for a raw-content element such as mj-text or mj-raw - its unparsed content. |