mjml-rb 0.5.2 → 0.6.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/CHANGELOG.md +29 -0
- data/README.md +60 -22
- data/THIRD_PARTY_LICENSES.md +25 -0
- data/docs/ARCHITECTURE.md +363 -0
- data/docs/MIGRATING_TO_V5.md +112 -0
- data/docs/MJML_V5_SUPPORT_PLAN.md +325 -0
- data/docs/PARITY_AUDIT.md +124 -0
- data/docs/RELEASING.md +35 -0
- data/docs/USAGE.md +1258 -0
- data/docs/releases/v0.6.0.md +63 -0
- data/lib/mjml-rb/cli.rb +43 -31
- data/lib/mjml-rb/column_gutter.rb +81 -0
- data/lib/mjml-rb/compiler.rb +32 -122
- data/lib/mjml-rb/component_registry.rb +11 -1
- data/lib/mjml-rb/components/accordion.rb +11 -9
- data/lib/mjml-rb/components/base.rb +1 -1
- data/lib/mjml-rb/components/body.rb +6 -3
- data/lib/mjml-rb/components/carousel.rb +2 -2
- data/lib/mjml-rb/components/carousel_image.rb +2 -2
- data/lib/mjml-rb/components/column.rb +16 -11
- data/lib/mjml-rb/components/group.rb +13 -4
- data/lib/mjml-rb/components/hero.rb +7 -2
- data/lib/mjml-rb/components/image.rb +1 -3
- data/lib/mjml-rb/components/raw.rb +1 -1
- data/lib/mjml-rb/components/section.rb +23 -8
- data/lib/mjml-rb/components/social.rb +18 -27
- data/lib/mjml-rb/components/table.rb +1 -1
- data/lib/mjml-rb/config_file.rb +25 -15
- data/lib/mjml-rb/dependencies.rb +1 -0
- data/lib/mjml-rb/formatter.rb +113 -0
- data/lib/mjml-rb/node_compat.rb +4 -0
- data/lib/mjml-rb/options.rb +81 -0
- data/lib/mjml-rb/parser.rb +138 -94
- data/lib/mjml-rb/renderer.rb +50 -76
- data/lib/mjml-rb/result.rb +3 -2
- data/lib/mjml-rb/validator.rb +14 -33
- data/lib/mjml-rb/version.rb +2 -1
- data/lib/mjml-rb.rb +2 -2
- data/mjml-rb.gemspec +2 -2
- metadata +19 -1
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
# Migrating to MJML v5
|
|
2
|
+
|
|
3
|
+
`mjml-rb` 0.6.0 targets **MJML 5.4.1**. The 0.5.x line retains v4 behavior.
|
|
4
|
+
There is no v4/v5 runtime switch. The library remains synchronous and requires
|
|
5
|
+
no Node.js packages at runtime.
|
|
6
|
+
|
|
7
|
+
Requires Ruby >= 3.3, `css_parser` >= 1.17 and < 4, and Nokogiri >= 1.13.
|
|
8
|
+
The CI matrix covers `css_parser` 1.21, 2.x and 3.x on Ruby 3.3, 3.4 and 4.0.
|
|
9
|
+
|
|
10
|
+
## Includes require opt-in
|
|
11
|
+
|
|
12
|
+
`mj-include` is now silently omitted by default, including in strict validation.
|
|
13
|
+
Enable it only for templates that need local files:
|
|
14
|
+
|
|
15
|
+
```ruby
|
|
16
|
+
MjmlRb.mjml2html(source,
|
|
17
|
+
ignore_includes: false,
|
|
18
|
+
file_path: File.dirname(template_path),
|
|
19
|
+
actual_path: template_path,
|
|
20
|
+
include_path: ["../shared"]
|
|
21
|
+
)
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
```sh
|
|
25
|
+
mjml templates/email.mjml --config.allowIncludes -o email.html
|
|
26
|
+
mjml --stdin --filePath templates --config.allowIncludes < templates/email.mjml
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
Paths resolve from `file_path`; CLI file inputs supply their own directory.
|
|
30
|
+
Nested includes resolve from the included file's directory. The original
|
|
31
|
+
canonical root and explicit `include_path` directories define the allowlist;
|
|
32
|
+
nested includes do not expand it. `include_path` accepts a string or an array,
|
|
33
|
+
and relative roots resolve against the original `file_path`.
|
|
34
|
+
|
|
35
|
+
Absolute paths, Windows drive paths, UNC paths, null bytes, and traversal or
|
|
36
|
+
symlink escapes are denied. Encoded paths are decoded before checking. Denied
|
|
37
|
+
includes produce `<!-- mj-include denied -->` and a structured `include-denied`
|
|
38
|
+
diagnostic: a warning in soft/skip mode, an error in strict mode. This is more
|
|
39
|
+
explicit than upstream 5.4.1, which omits a validation error for denied includes.
|
|
40
|
+
Missing allowed files still produce include diagnostics. Watch mode tracks
|
|
41
|
+
recursive enabled dependencies, including deletion, recreation and graph changes.
|
|
42
|
+
|
|
43
|
+
## HTML and layout changes
|
|
44
|
+
|
|
45
|
+
- `mj-body` owns the HTML `<body>`. Its `id` and `css-class` are on that element;
|
|
46
|
+
update selectors that previously targeted the inner article div. The preview
|
|
47
|
+
is the first body child. ARIA, language and direction remain on the article.
|
|
48
|
+
- `mj-section gutter="20px"` or `gutter="4%"` adds responsive column spacing,
|
|
49
|
+
including edge-aware Outlook padding, RTL and non-stacking groups.
|
|
50
|
+
- An unspecified column gets `100 / sibling_count` percent. An explicit `30%`
|
|
51
|
+
column next to an unspecified column now produces `30%` and `50%`.
|
|
52
|
+
- `mj-social` and `mj-social-element` accept `border`; icons default to `0`.
|
|
53
|
+
- Border radii accept arbitrary CSS strings, including mixed units and `/`.
|
|
54
|
+
- `mj-hero` no longer accepts `container-background-color`. Its Outlook inner
|
|
55
|
+
table and child width account for outer horizontal padding; inner padding
|
|
56
|
+
appears in both Outlook and normal markup.
|
|
57
|
+
- Empty `mj-image` and `mj-font` no longer trigger Ruby-only required checks.
|
|
58
|
+
|
|
59
|
+
### Backgrounds in browser previews
|
|
60
|
+
|
|
61
|
+
CSS applied through `mj-body`'s `css-class` now targets the actual HTML `<body>`.
|
|
62
|
+
Its background can paint the browser canvas below the content of a short email;
|
|
63
|
+
the inner article also receives the background colour set on `mj-body`.
|
|
64
|
+
Review both the body and any wrapper selectors when comparing v4 and v5 output.
|
|
65
|
+
|
|
66
|
+
In the maintainer's downstream Chrome comparisons, all 11 background-only
|
|
67
|
+
differences disappeared exactly when the browser harness set a white HTML-root
|
|
68
|
+
background. This was a diagnostic control applied only in the browser harness.
|
|
69
|
+
No footer markup change was needed. See the [validation record](PARITY_AUDIT.md#downstream-browser-validation)
|
|
70
|
+
for the full scope and results.
|
|
71
|
+
|
|
72
|
+
## Formatting and source preservation
|
|
73
|
+
|
|
74
|
+
`keep_comments: false` removes source HTML comments while preserving Outlook
|
|
75
|
+
conditionals. Ending-tag content stays raw: bare ampersands, HTML void-tag
|
|
76
|
+
spelling and script operators are no longer rewritten for XML parsing.
|
|
77
|
+
|
|
78
|
+
`minify` takes precedence over `beautify`. The Ruby formatter conservatively
|
|
79
|
+
compacts markup and CSS while preserving raw content, preformatted elements,
|
|
80
|
+
script text, template tokens, VML and paired `htmlmin:ignore` regions. Ignore
|
|
81
|
+
markers are removed from output. Whitespace and CSS optimizations are not
|
|
82
|
+
byte-identical to upstream htmlnano/cssnano.
|
|
83
|
+
|
|
84
|
+
`minify_options: {minify_css: false}` disables CSS compaction. `minifyCss` and the
|
|
85
|
+
legacy `minifyCSS` spelling are accepted. Other htmlnano options are rejected.
|
|
86
|
+
Use `sanitize_styles: true` to validate template tokens in CSS. Default token
|
|
87
|
+
pairs are `{{ ... }}` and `[[ ... ]]`; `template_syntax` accepts an array of
|
|
88
|
+
`{prefix:, suffix:}` objects. Unbalanced tokens and mixed block/property/value
|
|
89
|
+
syntax report errors; `allow_mixed_syntax: true` enables mixed syntax.
|
|
90
|
+
|
|
91
|
+
## Options, configuration and extensions
|
|
92
|
+
|
|
93
|
+
Snake_case, camelCase and dashed option keys are accepted. Conflicting aliases
|
|
94
|
+
in the same option layer raise `ArgumentError`; CLI errors return exit status 1.
|
|
95
|
+
Boolean options require Ruby booleans. JSON `.mjmlconfig` is preferred;
|
|
96
|
+
`.mjmlrc` remains a legacy alias. Config options apply only with
|
|
97
|
+
`use_mjml_config_options: true` (CLI `--config.useMjmlConfigOptions`). Explicit
|
|
98
|
+
constructor/call/CLI options win. `minify_options` merges by key.
|
|
99
|
+
|
|
100
|
+
`Compiler#validate` and CLI `--validate` parse and validate without rendering.
|
|
101
|
+
Valid CLI validation emits no HTML and does not write output files.
|
|
102
|
+
`Result#dependencies` exposes enabled include dependencies without changing the
|
|
103
|
+
existing `{html:, errors:, warnings:}` hash API.
|
|
104
|
+
|
|
105
|
+
Custom components use `MjmlRb.register_component`. Ending-tag metadata is shared
|
|
106
|
+
by parsing and validation, and later registrations are visible to existing
|
|
107
|
+
compilers. Registering a different class for an existing tag raises an error.
|
|
108
|
+
|
|
109
|
+
Non-empty `juice_options` and `juice_preserve_tags` are explicitly unsupported.
|
|
110
|
+
JavaScript `.mjmlconfig.js` and component packages, browser bundles, Node version
|
|
111
|
+
policy and removed `mjml-migrate` tooling are outside this Ruby port's scope.
|
|
112
|
+
See [the compatibility audit](PARITY_AUDIT.md) for other known differences.
|
|
@@ -0,0 +1,325 @@
|
|
|
1
|
+
# MJML v5.4 Support Plan
|
|
2
|
+
|
|
3
|
+
- Status: implemented for the 0.6.0 release; see bounded compatibility notes below
|
|
4
|
+
- Implementation reference: [`mjml@5.4.1`](https://github.com/mjmlio/mjml/releases/tag/v5.4.1)
|
|
5
|
+
- Original planning reference: `mjml@5.4.0`
|
|
6
|
+
- Original planning baseline: `mjml-rb` 0.5.2, documented against `mjml@4.18.0`
|
|
7
|
+
- Last reviewed: 2026-10-16
|
|
8
|
+
|
|
9
|
+
## Implementation notes
|
|
10
|
+
|
|
11
|
+
The implementation advances the original 5.4.0 target to patch release 5.4.1.
|
|
12
|
+
The baseline and delivery sections below retain the original audit context;
|
|
13
|
+
for current behavior and explicit exceptions, use [the compatibility audit](PARITY_AUDIT.md)
|
|
14
|
+
and [migration guide](MIGRATING_TO_V5.md). CLI input-relative paths were already
|
|
15
|
+
fixed in 0.5.3 before this migration.
|
|
16
|
+
|
|
17
|
+
The development-only oracle is locked to 5.4.1. Ruby fixtures and focused live
|
|
18
|
+
reference contracts cover the v5 changes; they do not claim exhaustive email
|
|
19
|
+
client parity. Full htmlnano/cssnano behavior and Juice settings are deliberately
|
|
20
|
+
outside the implemented subset. Unsupported options raise explicitly.
|
|
21
|
+
|
|
22
|
+
Release preparation and the downstream validation summary are recorded in the
|
|
23
|
+
[0.6.0 release notes](releases/v0.6.0.md). The 5.4.0 targets and baseline defects
|
|
24
|
+
below are retained as historical planning material, not current release status.
|
|
25
|
+
|
|
26
|
+
## Goal
|
|
27
|
+
|
|
28
|
+
Make the pure-Ruby compiler behaviorally compatible with the tagged MJML
|
|
29
|
+
v5.4.0 release without adding a Node.js runtime dependency.
|
|
30
|
+
|
|
31
|
+
Compatibility means:
|
|
32
|
+
|
|
33
|
+
- equivalent public compiler and CLI semantics;
|
|
34
|
+
- equivalent validation and include diagnostics;
|
|
35
|
+
- equivalent HTML structure and email-client behavior for critical markup;
|
|
36
|
+
- v5 security defaults, especially for `mj-include`; and
|
|
37
|
+
- a repeatable differential test suite against exactly `mjml@5.4.0`.
|
|
38
|
+
|
|
39
|
+
Exact byte-for-byte HTML is not a general requirement. Attribute order and
|
|
40
|
+
non-functional whitespace may differ, but Outlook conditionals, VML, CSS,
|
|
41
|
+
accessibility markup, widths, and option behavior must be equivalent.
|
|
42
|
+
|
|
43
|
+
## Recommended release policy
|
|
44
|
+
|
|
45
|
+
- Keep `0.5.x` as the MJML v4 compatibility line.
|
|
46
|
+
- Ship v5 semantics in `0.6.0`, with the breaking defaults called out in the
|
|
47
|
+
changelog and migration guide.
|
|
48
|
+
- Do not add a long-lived v4/v5 runtime switch. It would duplicate parser,
|
|
49
|
+
validation, and rendering branches and make parity harder to prove.
|
|
50
|
+
- Pin `mjml@5.4.0` only in development/CI as the reference oracle. The gem
|
|
51
|
+
remains pure Ruby at runtime.
|
|
52
|
+
|
|
53
|
+
There are no new core component tags between v4.18.0 and v5.4.0. The migration
|
|
54
|
+
is primarily about security defaults, document structure, component attributes
|
|
55
|
+
and layout, configuration, and post-processing. The authoritative scope is the
|
|
56
|
+
[v4.18.0...v5.4.0 comparison](https://github.com/mjmlio/mjml/compare/v4.18.0...v5.4.0).
|
|
57
|
+
|
|
58
|
+
## Baseline audit: fix before claiming v5 support
|
|
59
|
+
|
|
60
|
+
The current test suite passes, but differential probes against the official
|
|
61
|
+
compiler found the following correctness and consistency defects. Fix these
|
|
62
|
+
before changing golden fixtures to v5; otherwise the migration can hide
|
|
63
|
+
pre-existing regressions.
|
|
64
|
+
|
|
65
|
+
Baseline verification: `bundle exec rake test` completed with 589 runs, 2,284
|
|
66
|
+
assertions, 0 failures, 0 errors, and 1 skip.
|
|
67
|
+
|
|
68
|
+
### Release blockers
|
|
69
|
+
|
|
70
|
+
| Priority | Location | Confirmed behavior | Required change |
|
|
71
|
+
|---|---|---|---|
|
|
72
|
+
| P1 | `lib/mjml-rb/compiler.rb:104-113` | `keep_comments: false` deletes every generated MSO/IE conditional and `OfficeDocumentSettings`, breaking Outlook layout. | Remove global comment stripping. Drop source comment nodes during parsing while always preserving generated conditionals. |
|
|
73
|
+
| P1 | `lib/mjml-rb/compiler.rb:8-17`, `lib/mjml-rb/parser.rb:391-403` | Includes are enabled by default and an absolute or traversing path can read any process-readable file. | Adopt the v5 disabled-by-default include policy and canonical allowlisted roots described below. |
|
|
74
|
+
| P1 | `lib/mjml-rb/cli.rb:189-197` | File compilation does not pass the input filename as `actual_path`/`file_path`, so a same-directory relative include is resolved against the process working directory and omitted. | Merge per-input path context into compiler options, including watch mode. |
|
|
75
|
+
| P1 | `lib/mjml-rb/parser.rb:84-168` | CSS includes are visited in reverse, collected, and appended to `mj-head`; this reverses include order and moves styles away from their source position. A later blue rule can incorrectly lose to an earlier red rule. | Replace each CSS include in place with its corresponding `mj-style` node and preserve source order. |
|
|
76
|
+
| P1 | `lib/mjml-rb/compiler.rb:115-123` | Regex minification collapses significant whitespace inside `<pre>` and raw content. | Replace it with token/HTML-aware processing that protects raw blocks, preformatted elements, template tokens, conditionals, and `htmlmin:ignore` regions. |
|
|
77
|
+
| P1 | `lib/mjml-rb/renderer.rb:223-248` | When one column has an explicit width, remaining width is divided among unspecified columns. Upstream gives every unspecified sibling `100 / sibling_count`; `[30%, unset]` must be `[30%, 50%]`, not `[30%, 70%]`. | Base every unspecified width on the total non-raw sibling count. Add percentage and pixel mixed-width fixtures. |
|
|
78
|
+
| P1 | `lib/mjml-rb/components/section.rb:511-548`, `lib/mjml-rb/components/section.rb:691-715`, `lib/mjml-rb/components/social.rb:183-233` | `mj-raw` siblings are silently dropped from sections, wrappers, and social blocks whenever a normal child exists. | Iterate original children. Apply layout wrappers only to non-raw children and render raw children directly. |
|
|
79
|
+
| P1 | `lib/mjml-rb/parser.rb:13-19`, `lib/mjml-rb/components/social.rb:328-354` | `mj-social-element` is not registered as an ending tag; nested HTML is validated as MJML and then escaped as text. | Centralize ending-tag metadata and render the element's raw inner HTML without escaping. |
|
|
80
|
+
|
|
81
|
+
### Additional correctness debt
|
|
82
|
+
|
|
83
|
+
| Priority | Location | Confirmed behavior | Required change |
|
|
84
|
+
|---|---|---|---|
|
|
85
|
+
| P2 | `lib/mjml-rb/parser.rb:48-60` | With `ignore_includes: true`, the unexpanded `mj-include` remains in the AST and strict validation rejects it. Official v5 silently omits ignored includes. | Remove ignored include nodes during parsing before validation. |
|
|
86
|
+
| P2 | `lib/mjml-rb/components/image.rb:170-175` | An image with both explicit width and non-auto height emits `width="auto"`; upstream emits the numeric content width. | Remove the height-dependent special case and test Outlook-oriented width output. |
|
|
87
|
+
| P2 | `lib/mjml-rb/components/table.rb:115-129` | Comments inside `mj-table`, including MSO conditionals, are always discarded even when comments are kept. | Preserve serialized comments; source-comment policy belongs in the parser. |
|
|
88
|
+
| P2 | `lib/mjml-rb/components/accordion.rb:219-220`, `lib/mjml-rb/components/accordion.rb:253-254` | Accordion checkboxes and interactive icons are emitted unconditionally, while upstream hides them from Outlook with negated-MSO comments. | Restore the `<!--[if !mso | IE]><!-->` wrappers around unsupported interactive controls. |
|
|
89
|
+
| P2 | `lib/mjml-rb/components/accordion.rb:223-257` | `css-class` on `mj-accordion-title` is accepted but not rendered on the title cell. | Apply the class to the same element as the reference compiler. |
|
|
90
|
+
| P2 | `lib/mjml-rb/components/section.rb:79-83` | Padding width math recognizes only pixels even though section/wrapper schemas accept percentages. | Parse the numeric magnitude of every allowed unit and add percentage-padding width fixtures. |
|
|
91
|
+
| P2 | `lib/mjml-rb/validator.rb:107-110` | A known component with an empty allowed-attribute schema skips unknown-attribute validation, so strict mode accepts values such as `mj-title typo="x"`. | Distinguish an empty schema from an explicit allow-any schema. |
|
|
92
|
+
| P2 | `lib/mjml-rb/parser.rb:13-19`, `lib/mjml-rb/component_registry.rb:13-20` | Custom ending tags affect validation but not the parser's hard-coded CDATA wrapping. Raw text such as `A < B` then fails XML parsing. | Derive parser ending-tag handling from central registry metadata, retaining explicit structural exceptions such as `mj-table`. |
|
|
93
|
+
| P2 | `lib/mjml-rb/component_registry.rb:57-70`, `lib/mjml-rb/renderer.rb:628-659` | A warmed compiler does not see later component registrations, and validator/renderer precedence differs when a custom tag collides with a built-in. | Add a registry revision, invalidate renderer caches, and either reject duplicate tags or apply one documented precedence everywhere. |
|
|
94
|
+
| P2 | `lib/mjml-rb/parser.rb:296-303`, `lib/mjml-rb/parser.rb:355-359` | Bare-ampersand sanitization mutates ending-tag content; for example, raw script `a && b` becomes `a && b`. | Sanitize only XML text/attributes, never bytes already protected as raw/CDATA content. |
|
|
95
|
+
| P2 | `lib/mjml-rb/compiler.rb:130-133`, `lib/mjml-rb/config_file.rb:31-34`, `lib/mjml-rb/cli.rb:51-61` | Official camelCase keys such as `keepComments` are rejected, including keys loaded from config, and the CLI does not consistently turn these failures into a controlled exit. | Add one option canonicalizer shared by library, CLI, and config loading; normalize errors to `ConfigError`/exit 1. |
|
|
96
|
+
| P2 | `lib/mjml-rb/cli.rb:189-220` | `mjml --validate` compiles and prints/writes HTML. The reference CLI only parses/validates and is silent on valid input. | Add a validate-only path and prohibit output generation in that mode. |
|
|
97
|
+
| P2 | `lib/mjml-rb/validator.rb:18-30`, `lib/mjml-rb/validator.rb:45-53` | Calling `Validator` directly with a missing include loses `Parser#include_errors` and reports a clean strict result. | Return a parse result containing AST and diagnostics, then make both `Compiler` and `Validator` consume it. |
|
|
98
|
+
| P2 | `lib/mjml-rb/parser.rb:111-115`, `lib/mjml-rb/compiler.rb:136-159` | Include diagnostics collect `file` and `tag_name`, then discard them while building the public result. Nokogiri parse line data is also lost. | Carry structured location fields through a parse result instead of mutable parser state. |
|
|
99
|
+
|
|
100
|
+
The claims in `docs/PARITY_AUDIT.md` for column width, social ending-tag
|
|
101
|
+
behavior, CSS include order, and `.mjmlrc` parity should be corrected as each
|
|
102
|
+
issue is resolved. The README's “all components are implemented and tested”
|
|
103
|
+
claim should also be reconciled with the load-time experimental warning.
|
|
104
|
+
|
|
105
|
+
## V5 implementation scope
|
|
106
|
+
|
|
107
|
+
### 1. Secure include model
|
|
108
|
+
|
|
109
|
+
This is the first implementation phase because it changes the default and
|
|
110
|
+
closes a local-file disclosure risk when untrusted MJML is compiled. The
|
|
111
|
+
[v5 parser](https://github.com/mjmlio/mjml/blob/v5.4.0/packages/mjml-parser-xml/src/index.js)
|
|
112
|
+
is the behavioral reference.
|
|
113
|
+
|
|
114
|
+
- Change `ignore_includes`/`ignoreIncludes` default from `false` to `true`.
|
|
115
|
+
- Add `include_path`/`includePath`, accepting a string or array of roots.
|
|
116
|
+
- Add CLI `allowIncludes`; it maps to `ignore_includes: false`.
|
|
117
|
+
- When includes are enabled, make the template's canonical `file_path` root
|
|
118
|
+
and explicit canonical `include_path` roots the only readable locations.
|
|
119
|
+
- Resolve and compare `realpath` values so `..` and symlink escapes cannot
|
|
120
|
+
leave an allowed root.
|
|
121
|
+
- Reject absolute, UNC, drive-letter, null-byte, and repeatedly URL-encoded
|
|
122
|
+
traversal attempts when they escape the allowlist.
|
|
123
|
+
- Preserve relative resolution for nested includes by updating `actual_path`
|
|
124
|
+
to the included file and `file_path` to its directory.
|
|
125
|
+
- Preserve MJML, HTML, and CSS include types, inline CSS behavior, source
|
|
126
|
+
order, missing-file diagnostics, and circular-include detection.
|
|
127
|
+
- Omit ignored includes silently. For an enabled but denied include, emit
|
|
128
|
+
`<!-- mj-include denied -->` and a structured `include-denied` diagnostic.
|
|
129
|
+
- Watch enabled recursive include dependencies, not only the root input file.
|
|
130
|
+
|
|
131
|
+
### 2. Move HTML `<body>` ownership to `mj-body`
|
|
132
|
+
|
|
133
|
+
Follow the tagged
|
|
134
|
+
[`mj-body`](https://github.com/mjmlio/mjml/blob/v5.4.0/packages/mjml-body/src/index.js)
|
|
135
|
+
and [skeleton](https://github.com/mjmlio/mjml/blob/v5.4.0/packages/mjml-core/src/helpers/skeleton.js)
|
|
136
|
+
implementations rather than the initial v5 release prose where they differ.
|
|
137
|
+
|
|
138
|
+
- Add `id: string` to `mj-body`.
|
|
139
|
+
- Render the actual `<body>` from `Components::Body`.
|
|
140
|
+
- Put `id`, `css-class`, word spacing, and body background on `<body>`.
|
|
141
|
+
- Render preview as the first body child.
|
|
142
|
+
- Keep the accessibility wrapper inside the body with `aria-label` when a
|
|
143
|
+
title exists, `aria-roledescription="email"`, `role="article"`, `lang`,
|
|
144
|
+
`dir`, word spacing, and background styling.
|
|
145
|
+
- Remove body and preview generation from the global skeleton.
|
|
146
|
+
- Ensure `mj-html-attributes` selectors can target the real body without
|
|
147
|
+
creating a second body element.
|
|
148
|
+
|
|
149
|
+
### 3. Component and validation changes
|
|
150
|
+
|
|
151
|
+
| Component/area | V5.4 behavior to implement |
|
|
152
|
+
|---|---|
|
|
153
|
+
| `mj-section` | Add `gutter: unit(px,%)`. Propagate gutter/direction and group context; reduce desktop column widths; apply horizontal edge-aware gutter padding in normal and Outlook output; convert regular mobile columns to vertical gaps; preserve horizontal `mj-group` layout; reverse edges for RTL; and normalize mixed-unit/odd-pixel output deterministically. Use the tagged [`mj-section`](https://github.com/mjmlio/mjml/blob/v5.4.0/packages/mjml-section/src/index.js) and [`mj-column`](https://github.com/mjmlio/mjml/blob/v5.4.0/packages/mjml-column/src/index.js) behavior. |
|
|
154
|
+
| `mj-social`, `mj-social-element` | Add `border: string`, default element border to `0`, cascade the parent value, allow a child override, and render it on the icon image. See [`mj-social`](https://github.com/mjmlio/mjml/blob/v5.4.0/packages/mjml-social/src/Social.js) and [`mj-social-element`](https://github.com/mjmlio/mjml/blob/v5.4.0/packages/mjml-social/src/SocialElement.js). |
|
|
155
|
+
| Border radius | Change every border-radius schema to `string`, including column and inner-column, image, carousel and thumbnail, social and social element, section, hero, and button. Accept mixed units and slash syntax. |
|
|
156
|
+
| `mj-hero` | Remove `container-background-color`; apply `inner-padding*` to Outlook and non-Outlook content; pass the padding-reduced width to children and the Outlook inner table. Match the tagged [`mj-hero`](https://github.com/mjmlio/mjml/blob/v5.4.0/packages/mjml-hero/src/index.js). |
|
|
157
|
+
| `mj-accordion` | Replace legacy `input.mj-accordion-checkbox` selectors with `.mj-accordion-checkbox[type="checkbox"]` where v5 does so. |
|
|
158
|
+
| Required attributes | Remove Ruby-only required checks for `mj-image`, `mj-font`, and `mj-include` unless the tagged v5 validator produces the same diagnostic. V5.4 strict compilation accepts an empty `mj-image` and `mj-font`. |
|
|
159
|
+
| Component metadata | Use one registry/manifest for tag class, allowed attributes, ending-tag status, dependencies, head styles, and cache revision. Parser, validator, and renderer must not carry divergent copies. |
|
|
160
|
+
|
|
161
|
+
The media-query registry currently records only responsive width. Extend it to
|
|
162
|
+
record gutter padding and grouped/RTL variants without making every component
|
|
163
|
+
mutate a shared ad hoc hash.
|
|
164
|
+
|
|
165
|
+
### 4. Post-processing and template safety
|
|
166
|
+
|
|
167
|
+
MJML v5 replaced its old minifier with htmlnano/cssnano and added template
|
|
168
|
+
syntax protection. Ruby does not need to use the same libraries, but must match
|
|
169
|
+
their observable contract.
|
|
170
|
+
|
|
171
|
+
- Make minify win when both `minify` and `beautify` are true.
|
|
172
|
+
- Preserve MSO/IE conditional comments regardless of `keep_comments`.
|
|
173
|
+
- Remove only source comments when `keep_comments` is false.
|
|
174
|
+
- Protect `<pre>`, `<textarea>`, raw/ending-tag content, script/style content,
|
|
175
|
+
VML, file-start raw content, and significant whitespace.
|
|
176
|
+
- Support paired `<!-- htmlmin:ignore -->` regions; preserve their content
|
|
177
|
+
verbatim during minification and remove only the markers during beautifying.
|
|
178
|
+
- Add `minify_options`/`minifyOptions`, including CSS-minification control and
|
|
179
|
+
the legacy `minifyCSS` alias expected by v5.
|
|
180
|
+
- Add `sanitize_styles`/`sanitizeStyles`,
|
|
181
|
+
`template_syntax`/`templateSyntax`, and
|
|
182
|
+
`allow_mixed_syntax`/`allowMixedSyntax`.
|
|
183
|
+
- Protect and restore template variables in CSS values, property names, and
|
|
184
|
+
blocks. Default delimiters are `{{ ... }}` and `[[ ... ]]`.
|
|
185
|
+
- Return an actionable error for unbalanced or disallowed mixed syntax; do not
|
|
186
|
+
silently mutate a template token.
|
|
187
|
+
|
|
188
|
+
Keep the Ruby API synchronous. The JavaScript reference becoming asynchronous
|
|
189
|
+
because of its minifier is an implementation detail, not a compatibility
|
|
190
|
+
requirement.
|
|
191
|
+
|
|
192
|
+
### 5. Public API, config, and CLI
|
|
193
|
+
|
|
194
|
+
Create a single option schema and canonicalization function. Ruby snake_case is
|
|
195
|
+
the documented native form, while upstream camelCase is accepted as an alias.
|
|
196
|
+
If both spellings are supplied with different values, raise a clear conflict
|
|
197
|
+
error instead of depending on hash order.
|
|
198
|
+
|
|
199
|
+
| Ruby key | Upstream alias | V5.4 default |
|
|
200
|
+
|---|---|---|
|
|
201
|
+
| `keep_comments` | `keepComments` | `true` |
|
|
202
|
+
| `ignore_includes` | `ignoreIncludes` | `true` |
|
|
203
|
+
| `include_path` | `includePath` | none |
|
|
204
|
+
| `validation_level` | `validationLevel` | `soft` |
|
|
205
|
+
| `file_path` | `filePath` | `.` |
|
|
206
|
+
| `actual_path` | `actualPath` | `.` |
|
|
207
|
+
| `printer_support` | `printerSupport` | `false` |
|
|
208
|
+
| `minify_options` | `minifyOptions` | implementation defaults |
|
|
209
|
+
| `sanitize_styles` | `sanitizeStyles` | `false` |
|
|
210
|
+
| `template_syntax` | `templateSyntax` | `{{ }}` and `[[ ]]` |
|
|
211
|
+
| `allow_mixed_syntax` | `allowMixedSyntax` | `false` |
|
|
212
|
+
| `mjml_config_path` | `mjmlConfigPath` | none |
|
|
213
|
+
| `use_mjml_config_options` | `useMjmlConfigOptions` | `false` |
|
|
214
|
+
| `juice_options` | `juiceOptions` | `{}` |
|
|
215
|
+
| `juice_preserve_tags` | `juicePreserveTags` | none |
|
|
216
|
+
|
|
217
|
+
For Juice-specific options, either implement equivalent behavior in the custom
|
|
218
|
+
CSS inliner or return an explicit unsupported-option error. Silently accepting
|
|
219
|
+
and ignoring them would not be compatible.
|
|
220
|
+
|
|
221
|
+
Configuration and CLI work:
|
|
222
|
+
|
|
223
|
+
- Discover JSON `.mjmlconfig` and keep `.mjmlrc` as a documented legacy alias.
|
|
224
|
+
- Do not execute `.mjmlconfig.js` in Ruby. Return a clear unsupported-format
|
|
225
|
+
error and document Ruby custom-component registration as the alternative.
|
|
226
|
+
- Apply config options only when requested by `use_mjml_config_options`, while
|
|
227
|
+
explicit call/CLI options take precedence. Deep-merge `minify_options`.
|
|
228
|
+
- Preserve preprocessors ordering and reject non-object config roots cleanly.
|
|
229
|
+
- Give every file input its own `file_path` and `actual_path` context.
|
|
230
|
+
- Add stdin file-path support for include resolution.
|
|
231
|
+
- Make `--validate` parse and validate only; valid input produces no HTML.
|
|
232
|
+
- Make watch mode observe enabled recursive include dependencies and update
|
|
233
|
+
dependency watches when the include graph changes.
|
|
234
|
+
|
|
235
|
+
### 6. Documentation and compatibility metadata
|
|
236
|
+
|
|
237
|
+
- Add a constant such as `MJML_COMPATIBILITY_VERSION = "5.4.0"` and expose it
|
|
238
|
+
in the library/CLI version output.
|
|
239
|
+
- Update `README.md`, `docs/USAGE.md`, `docs/ARCHITECTURE.md`, and
|
|
240
|
+
`docs/PARITY_AUDIT.md` from v4.18 to v5.4 semantics.
|
|
241
|
+
- Add a migration section covering the include default, `include_path`, body
|
|
242
|
+
structure/class placement, formatter changes, and looser border radii.
|
|
243
|
+
- State that `.mjmlconfig.js`, JavaScript component packages, the browser
|
|
244
|
+
bundle, Node-version policy, and removed `mjml-migrate` tooling are outside
|
|
245
|
+
the scope of this pure-Ruby port.
|
|
246
|
+
- Replace broad parity claims with the exact reference tag and the known
|
|
247
|
+
unsupported-option list.
|
|
248
|
+
|
|
249
|
+
## Delivery phases
|
|
250
|
+
|
|
251
|
+
Each phase should land with its own differential fixtures. Do not update all
|
|
252
|
+
snapshots in one commit.
|
|
253
|
+
|
|
254
|
+
1. **Lock the oracle and baseline**
|
|
255
|
+
- Pin `mjml@5.4.0` in a development-only differential harness.
|
|
256
|
+
- Convert the baseline defects above into failing tests.
|
|
257
|
+
- Record normalized v4 and v5 outputs for high-risk templates.
|
|
258
|
+
2. **Fix current correctness defects**
|
|
259
|
+
- Fix Outlook comments, include file context/order, column widths, raw-child
|
|
260
|
+
loss, ending-tag metadata, raw preservation, and registry invalidation.
|
|
261
|
+
3. **Implement v5 include security and option aliases**
|
|
262
|
+
- Change the default, add allowlisted roots, denial diagnostics, CLI
|
|
263
|
+
`allowIncludes`, and traversal/symlink coverage.
|
|
264
|
+
4. **Move body ownership**
|
|
265
|
+
- Refactor body/skeleton responsibilities and add outer-HTML fixtures.
|
|
266
|
+
5. **Port component deltas**
|
|
267
|
+
- Border-radius schemas, hero, accordion, social border, then section gutter.
|
|
268
|
+
6. **Replace post-processing**
|
|
269
|
+
- Safe comment handling, formatter/minifier, template sanitization, and
|
|
270
|
+
`htmlmin:ignore`.
|
|
271
|
+
7. **Align config and CLI**
|
|
272
|
+
- `.mjmlconfig`, per-file context, validate-only behavior, and include-aware
|
|
273
|
+
watch mode.
|
|
274
|
+
8. **Document, benchmark, and release**
|
|
275
|
+
- Update compatibility docs, run the full oracle matrix, benchmark common
|
|
276
|
+
templates, and publish the v4-to-v5 migration notes.
|
|
277
|
+
|
|
278
|
+
## Acceptance matrix
|
|
279
|
+
|
|
280
|
+
| Area | Required cases |
|
|
281
|
+
|---|---|
|
|
282
|
+
| Includes | Default ignored; explicitly enabled local include; string/array allowlist; MJML/HTML/CSS; nested relative includes; inline CSS order; missing and circular includes; absolute/UNC/drive/null/encoded traversal denied; symlink escape denied; structured diagnostics. |
|
|
283
|
+
| Body | Exactly one body; id/class on body; preview first; ARIA/lang/dir on the inner div; title omission; background placement; body-targeted HTML attributes. |
|
|
284
|
+
| Gutter | Two/three/four columns; px/% gutter; implicit and explicit px/% widths; mixed units; odd pixels; mobile stacking; grouped columns; RTL; borders/padding/full width; Outlook markup. |
|
|
285
|
+
| Social | Raw sibling; raw inner HTML; default border; parent cascade; child override; horizontal and vertical modes. |
|
|
286
|
+
| Radius | Multiple values, mixed units, and slash syntax across every radius-bearing component. |
|
|
287
|
+
| Hero | Shorthand/directional outer padding; inner padding in all clients; padding-reduced child/Outlook width; removed attribute validation. |
|
|
288
|
+
| Post-processing | Source comments versus generated conditionals; raw/pre whitespace; multiple ignore pairs; file-start content; template variables in CSS values/properties/blocks; multiline, mixed, and unbalanced delimiters; CSS minification disabled. |
|
|
289
|
+
| API/config/CLI | camelCase/snake_case equivalence and conflicts; config precedence; input-relative include; stdin context; validate emits no HTML; watch updates after include-graph changes. |
|
|
290
|
+
| Registry | Register before/after first compile; custom ending tags; duplicate-tag policy; repeated and concurrent compilation isolation. |
|
|
291
|
+
| Regression | Existing component fixtures; strict/soft/skip validation; Outlook/VML snapshots; malformed input; performance and memory bounds. |
|
|
292
|
+
|
|
293
|
+
Differential comparisons should use:
|
|
294
|
+
|
|
295
|
+
- exact fragments for critical Outlook, VML, body, and diagnostic contracts;
|
|
296
|
+
- normalized DOM comparisons for general markup;
|
|
297
|
+
- parsed CSS/media-query comparisons for responsive behavior; and
|
|
298
|
+
- exact result classification and CLI exit codes for validation/configuration.
|
|
299
|
+
|
|
300
|
+
## Upstream tests to port
|
|
301
|
+
|
|
302
|
+
- [Include defaults](https://github.com/mjmlio/mjml/blob/v5.4.0/packages/mjml/test/ignore-includes.test.js)
|
|
303
|
+
- [Include roots and types](https://github.com/mjmlio/mjml/blob/v5.4.0/packages/mjml/test/include-path.test.js)
|
|
304
|
+
- [Include security](https://github.com/mjmlio/mjml/blob/v5.4.0/packages/mjml/test/include-path-security.test.js)
|
|
305
|
+
- [Traversal handling](https://github.com/mjmlio/mjml/blob/v5.4.0/packages/mjml/test/include-path-traversal.test.js)
|
|
306
|
+
- [Section gutter](https://github.com/mjmlio/mjml/blob/v5.4.0/packages/mjml/test/section-gutter.test.js)
|
|
307
|
+
- [Social border](https://github.com/mjmlio/mjml/blob/v5.4.0/packages/mjml/test/social-border.test.js)
|
|
308
|
+
- [Border-radius strings](https://github.com/mjmlio/mjml/blob/v5.4.0/packages/mjml/test/border-radius-string.test.js)
|
|
309
|
+
- [Hero padding width](https://github.com/mjmlio/mjml/blob/v5.4.0/packages/mjml/test/hero-padding-inner-width.test.js)
|
|
310
|
+
- [Template syntax sanitization](https://github.com/mjmlio/mjml/blob/v5.4.0/packages/mjml/test/template-syntax-sanitization.test.js)
|
|
311
|
+
- [`htmlmin:ignore`](https://github.com/mjmlio/mjml/blob/v5.4.0/packages/mjml/test/htmlmin-ignore.test.js)
|
|
312
|
+
- [Beautifier output](https://github.com/mjmlio/mjml/blob/v5.4.0/packages/mjml/test/beautify-output.test.js)
|
|
313
|
+
|
|
314
|
+
## Definition of done
|
|
315
|
+
|
|
316
|
+
V5 support is complete only when all of the following are true:
|
|
317
|
+
|
|
318
|
+
- the baseline audit issues are fixed or explicitly documented as unsupported;
|
|
319
|
+
- every v5 option is implemented or rejected explicitly rather than ignored;
|
|
320
|
+
- the security suite proves includes cannot escape canonical allowed roots;
|
|
321
|
+
- the component and CLI acceptance matrices pass against `mjml@5.4.0`;
|
|
322
|
+
- generated Outlook conditionals and VML survive every option combination;
|
|
323
|
+
- the existing Ruby suite remains green;
|
|
324
|
+
- documentation names the exact compatibility tag and migration breakages; and
|
|
325
|
+
- no Node.js package is required at gem runtime.
|
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
# MJML v5 compatibility audit
|
|
2
|
+
|
|
3
|
+
Reference: [`mjml@5.4.1`](https://github.com/mjmlio/mjml/releases/tag/v5.4.1),
|
|
4
|
+
commit `1c434fcbfd042ddfcb9fa4e0496f7eedf1d90bc7`. Updated 2026-10-16.
|
|
5
|
+
|
|
6
|
+
The 0.6.0 implementation targets v5 semantics. This document replaces the old
|
|
7
|
+
v4.18 audit's broad “Match” claims with bounded, reproducible coverage. Passing
|
|
8
|
+
these checks does not establish identical rendering in every email client.
|
|
9
|
+
|
|
10
|
+
## Verified contracts
|
|
11
|
+
|
|
12
|
+
| Area | Coverage |
|
|
13
|
+
|---|---|
|
|
14
|
+
| Includes | Disabled by default; explicit opt-in; canonical string/array allowlists; nested relative MJML; HTML and CSS; CSS cascade order; missing/circular files; encoded traversal, absolute/UNC/drive/null paths and symlink escapes; structured diagnostics |
|
|
15
|
+
| Body | One body; body id/class; preview first; article ARIA/lang/dir/background; body-targeted HTML attributes |
|
|
16
|
+
| Gutter | Pinned upstream fixtures for px/% widths and gutters, two/three/four columns, mixed units, fractional/odd pixels, mobile stacking, groups, RTL, borders/padding and full width |
|
|
17
|
+
| Components | Social borders, raw siblings and inner HTML; arbitrary border radii; hero padding and inner widths; accordion selectors/Outlook controls; numeric image width |
|
|
18
|
+
| Source/formatting | Raw operators and whitespace, source comments versus Outlook conditionals, minify precedence, paired ignore regions, CSS template values/properties/blocks, mixed/unbalanced syntax, CSS compaction switch |
|
|
19
|
+
| API and CLI | Shared option aliases and conflicts; opt-in config and explicit precedence; file/stdin context; validation without rendering; recursive include watching |
|
|
20
|
+
| Registry | Shared class/dependency/ending metadata; later registrations; duplicate-tag rejection; per-compilation parser and renderer state |
|
|
21
|
+
|
|
22
|
+
`test/reference/v5_cases.json` contains focused reference fragments and semantic
|
|
23
|
+
DOM projections. `test/test_v5_support.rb` and `test/test_v5_cli_config.rb` cover
|
|
24
|
+
security and Ruby-specific error/API contracts. The existing component golden
|
|
25
|
+
fragments continue to cover Outlook/VML and the broader rendering behavior.
|
|
26
|
+
|
|
27
|
+
## Reproducing the reference checks
|
|
28
|
+
|
|
29
|
+
```sh
|
|
30
|
+
npm ci --prefix test/reference --ignore-scripts
|
|
31
|
+
MJML_REFERENCE=1 bundle exec rake test
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
`test/reference/package-lock.json` pins the development oracle and its dependency
|
|
35
|
+
graph. The gem includes Ruby runtime files, licenses and documentation; it does
|
|
36
|
+
not include the npm reference package or load Node/npm. Run these development
|
|
37
|
+
commands from a repository checkout; the gem does not ship the test suite.
|
|
38
|
+
Ordinary `bundle exec rake test` checks the committed reference expectations and
|
|
39
|
+
skips the live oracle. To deliberately refresh semantic projections from the
|
|
40
|
+
pinned reference, run `bundle exec ruby test/reference/update.rb`, then review
|
|
41
|
+
its diff. This command never generates expectations from the Ruby compiler.
|
|
42
|
+
|
|
43
|
+
The CI matrix covers `css_parser` 1.21, 2.x and 3.x on Ruby 3.3, 3.4 and 4.0,
|
|
44
|
+
plus a separate pinned MJML reference job. The supported dependency range is
|
|
45
|
+
`css_parser >= 1.17, < 4`; the CI floor for the 1.x line is 1.21.
|
|
46
|
+
|
|
47
|
+
## Downstream browser validation
|
|
48
|
+
|
|
49
|
+
Maintainer-reported Halalbooking validation, supplied on 2026-10-16, tested
|
|
50
|
+
`mjml-rb` commit `90f646e35968fdca3afda58c1f2891a7f7a290d6` (gem 0.6.0)
|
|
51
|
+
with `css_parser` 3.3.0. Coverage comprised all eight booking-mailer actions
|
|
52
|
+
through eleven representative scenarios, all nine configured locales, and
|
|
53
|
+
desktop/mobile widths: 198 exact pixel comparisons, without tolerance or masks.
|
|
54
|
+
|
|
55
|
+
| Comparison | Result |
|
|
56
|
+
|---|---|
|
|
57
|
+
| Validated commit versus `313ad6a` (gem 0.6.0 / `css_parser` 2.2.0) | All 198 screenshots pixel-identical |
|
|
58
|
+
| Master controls versus the previous control run | All 198 screenshots pixel-identical |
|
|
59
|
+
| Validated commit versus downstream master's `mjml-rb` 0.5.2 | 185 pixel-identical; 11 background-only differences below short desktop emails; 2 comparisons restoring a missing space in the Turkish KYC email |
|
|
60
|
+
|
|
61
|
+
There were no rendering errors, missing images or unstable captures. All 198
|
|
62
|
+
generated HTML documents had balanced tags, and all 32 focused downstream
|
|
63
|
+
MJML/mailer specs passed.
|
|
64
|
+
|
|
65
|
+
The 11 background-only differences are explained by v5 body styling/class
|
|
66
|
+
ownership. A white HTML-root background applied only in the browser harness
|
|
67
|
+
eliminated all eleven differences exactly. No footer markup change was needed.
|
|
68
|
+
The two Turkish spacing changes were already present in the earlier v5 run;
|
|
69
|
+
switching to `css_parser` 3.3.0 introduced no additional visual changes.
|
|
70
|
+
|
|
71
|
+
This evidence covers representative Chrome renders, not certification across
|
|
72
|
+
email clients. The customer screenshots and source templates remain private;
|
|
73
|
+
public examples use synthetic content. These results are a maintainer-supplied
|
|
74
|
+
validation record, separate from the repository's reproducible reference suite.
|
|
75
|
+
|
|
76
|
+
## Local performance check
|
|
77
|
+
|
|
78
|
+
`ruby -Ilib benchmark/compile.rb` measures warmed compilation and allocated Ruby
|
|
79
|
+
objects for one and thirty sections containing text and an image. On Ruby 4.0.1
|
|
80
|
+
on the development machine, comparing 0.5.3 with this change gave:
|
|
81
|
+
|
|
82
|
+
| Template | 0.5.3 ms/compile | 0.6.0 ms/compile | 0.5.3 objects | 0.6.0 objects |
|
|
83
|
+
|---|---:|---:|---:|---:|
|
|
84
|
+
| One section, 300 iterations | 0.47 | 0.65 | 1,554 | 2,007 |
|
|
85
|
+
| Thirty sections, 30 iterations | 8.35 | 8.53 | 32,124 | 33,157 |
|
|
86
|
+
|
|
87
|
+
These are local samples, not performance guarantees or peak-memory measurements.
|
|
88
|
+
Per-call parser/renderer state and option validation add overhead most visible
|
|
89
|
+
on small inputs; larger-template cost remained close to the baseline in this run.
|
|
90
|
+
|
|
91
|
+
## Intentional differences and remaining limits
|
|
92
|
+
|
|
93
|
+
- The API is synchronous and returns structured `Result` errors instead of
|
|
94
|
+
JavaScript promises/exceptions. Soft issues are exposed as `warnings`.
|
|
95
|
+
- Denied includes produce explicit Ruby diagnostics. Upstream 5.4.1 renders the
|
|
96
|
+
denial comment but suppresses the validator error for that type. Ruby freezes
|
|
97
|
+
the initial include allowlist across recursion rather than widening it to
|
|
98
|
+
each included file's directory.
|
|
99
|
+
- CSS includes in `mj-head` retain their source position, preserving cascade
|
|
100
|
+
order. Upstream collects CSS includes; this port does not copy that reordering.
|
|
101
|
+
- The parser uses strict XML outside ending-tag content. Malformed structural
|
|
102
|
+
MJML accepted by upstream's HTML parser can fail in Ruby. `mj-table` retains
|
|
103
|
+
structural parsing for normalization rather than fully raw ending content.
|
|
104
|
+
- The formatter preserves raw regions conservatively and implements CSS
|
|
105
|
+
whitespace compaction, not the full htmlnano/cssnano optimization pipeline.
|
|
106
|
+
Only `minifyCss`/`minifyCSS`/`minify_css` is supported in `minify_options`;
|
|
107
|
+
other minifier settings are rejected explicitly.
|
|
108
|
+
- The custom CSS inliner implements common cascade and HTML-attribute syncing,
|
|
109
|
+
but is not Juice. Non-empty `juice_options` and `juice_preserve_tags` raise an
|
|
110
|
+
unsupported-option error. Template CSS intended for later evaluation should
|
|
111
|
+
use non-inline `mj-style` or raw styles; template-driven selector/block
|
|
112
|
+
evaluation in the CSS inliner is unsupported.
|
|
113
|
+
- Ruby components replace JavaScript package execution. `.mjmlconfig.js`, npm
|
|
114
|
+
component packages and the browser bundle are unsupported.
|
|
115
|
+
- Negative hero widths caused by padding wider than the container are clamped
|
|
116
|
+
to zero in Ruby; upstream currently emits negative widths.
|
|
117
|
+
- Watch mode polls known root files and enabled include dependencies; it does
|
|
118
|
+
not discover additional root inputs that later match a startup glob.
|
|
119
|
+
- Formatting, quote/entity spelling, attribute order and generated carousel
|
|
120
|
+
identifiers can differ. No automated email-client screenshot certification
|
|
121
|
+
is claimed.
|
|
122
|
+
|
|
123
|
+
Migration details are in [MIGRATING_TO_V5.md](MIGRATING_TO_V5.md). The original
|
|
124
|
+
baseline findings and acceptance goals remain in [the support plan](MJML_V5_SUPPORT_PLAN.md).
|
data/docs/RELEASING.md
ADDED
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
# Publishing mjml-rb 0.6.0
|
|
2
|
+
|
|
3
|
+
The maintainer merges and publishes this release. Preparation does not create a
|
|
4
|
+
tag, publish to RubyGems, or create a GitHub release.
|
|
5
|
+
|
|
6
|
+
Before merging PR #24, confirm all ten CI jobs pass for its final head, inspect
|
|
7
|
+
the packaged gem and smoke-test its API and CLI from outside the checkout.
|
|
8
|
+
Runtime source under `lib/` and `bin/` must match the downstream-validated
|
|
9
|
+
commit `90f646e35968fdca3afda58c1f2891a7f7a290d6` unless a confirmed blocker is
|
|
10
|
+
fixed and revalidated. Release documentation and package contents may change.
|
|
11
|
+
|
|
12
|
+
After merging PR #24, run the following from a clean repository checkout.
|
|
13
|
+
The detached checkout selects the PR's exact merge commit even if `main` has
|
|
14
|
+
subsequently advanced. The release date in the changelog and prepared
|
|
15
|
+
[release notes](releases/v0.6.0.md) is 2026-10-16.
|
|
16
|
+
|
|
17
|
+
```sh
|
|
18
|
+
set -e
|
|
19
|
+
test -z "$(git status --porcelain)"
|
|
20
|
+
test "$(gh pr view 24 --repo faraquet/mjml-rb --json state --jq .state)" = "MERGED"
|
|
21
|
+
git fetch origin main --tags
|
|
22
|
+
release_commit=$(gh pr view 24 --repo faraquet/mjml-rb --json mergeCommit --jq .mergeCommit.oid)
|
|
23
|
+
git switch --detach "$release_commit"
|
|
24
|
+
test "$(ruby -Ilib -rmjml-rb/version -e 'print MjmlRb::VERSION')" = "0.6.0"
|
|
25
|
+
gem build mjml-rb.gemspec
|
|
26
|
+
git tag -a v0.6.0 -m "mjml-rb 0.6.0"
|
|
27
|
+
git push origin refs/tags/v0.6.0
|
|
28
|
+
gem push mjml-rb-0.6.0.gem
|
|
29
|
+
gh release create v0.6.0 --repo faraquet/mjml-rb --verify-tag --title "mjml-rb 0.6.0" --notes-file docs/releases/v0.6.0.md
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
`gem push` publishes the built artifact to RubyGems using the maintainer's
|
|
33
|
+
credentials, as described in the [RubyGems publishing guide](https://guides.rubygems.org/publishing/).
|
|
34
|
+
No version bump is needed: the gem version is already 0.6.0, and
|
|
35
|
+
`MjmlRb::MJML_COMPATIBILITY_VERSION` remains 5.4.1.
|