react-email-rails 0.7.0 → 0.8.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 +6 -0
- data/README.md +4 -12
- data/SECURITY.md +0 -4
- data/lib/react_email_rails/render_modes/persistent/server.rb +0 -2
- data/lib/react_email_rails/render_modes/subprocess.rb +4 -25
- data/lib/react_email_rails/render_protocol.rb +1 -1
- data/lib/react_email_rails/rendered_email.rb +1 -5
- data/lib/react_email_rails/version.rb +1 -1
- data/lib/react_email_rails.rb +9 -51
- metadata +1 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: cf2e6b94aa19f4ccec30037d5eefb38b1b98ba2f070ede1a89c99818e47b2eec
|
|
4
|
+
data.tar.gz: 26710f44d94a84d1df7a73a0759973c37fc9b3ec031c143ae902aed890ebb2d2
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 911446dcf37e32e420bef4e0f02cdf8c30c6076854dba593737ae552138e139473f355d1ef14328bf50cf77943e5b376e82300185b8d23ea34fb41a1a3deaaaf
|
|
7
|
+
data.tar.gz: 708ac8a7339f5133be74caccce59a1f30dc890f6da95adeb5385a5117b894295fd8e3e5748a50e8a46d0cdd6bcc1abc6d3943abf9bf146dd2b57163c3d0c94a5
|
data/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,11 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.8.0
|
|
4
|
+
|
|
5
|
+
- **Breaking:** Remove `@react-email/editor` document rendering to focus the library on component-based Action Mailer emails. `ReactEmailRails.compose`, `ReactEmailRails.parse`, the `documents` Vite plugin option, and the `react-email-rails/document` module are gone, along with the `@react-email/editor`, `@tiptap/core`, `@tiptap/html`, `happy-dom`, and `marked` optional peer dependencies.
|
|
6
|
+
- **Breaking:** `on_render_error` callbacks and the `render.react-email-rails` instrumentation payload no longer include `kind:` (every render is a component email again); `RenderedEmail` no longer has `warnings`.
|
|
7
|
+
- Bump the render protocol to 4 (the renderer no longer accepts document or parse requests). The Ruby gem and npm package must be upgraded together, as before.
|
|
8
|
+
|
|
3
9
|
## 0.7.0
|
|
4
10
|
|
|
5
11
|
- Add development live-reloading for Action Mailer previews. A development-only preview interceptor injects Vite's `@vite/client` into previews, and the `reactEmailRails()` plugin triggers a full reload when an email component changes, so the open preview refreshes on save. Configure the dev-server URL with `live_reload_url`, or set it to a falsy value to disable.
|
data/README.md
CHANGED
|
@@ -18,7 +18,6 @@ You get:
|
|
|
18
18
|
- Vite-powered development rendering
|
|
19
19
|
- A production renderer bundle built during `assets:precompile`
|
|
20
20
|
- Optional persistent rendering for high-volume workers
|
|
21
|
-
- Optional server-side rendering for `@react-email/editor` documents
|
|
22
21
|
|
|
23
22
|
## Status
|
|
24
23
|
|
|
@@ -471,12 +470,6 @@ export default function Welcome() {
|
|
|
471
470
|
}
|
|
472
471
|
```
|
|
473
472
|
|
|
474
|
-
### Editor Documents
|
|
475
|
-
|
|
476
|
-
If your app uses [@react-email/editor](https://react.email/docs/editor) to let users compose emails visually, `ReactEmailRails.compose` can render stored editor documents on the server.
|
|
477
|
-
|
|
478
|
-
See [Editor rendering](docs/editor.md) for setup and usage.
|
|
479
|
-
|
|
480
473
|
## Configuration
|
|
481
474
|
|
|
482
475
|
Configuration lives in two places:
|
|
@@ -580,11 +573,11 @@ ReactEmailRails.configure do |config|
|
|
|
580
573
|
end
|
|
581
574
|
```
|
|
582
575
|
|
|
583
|
-
The callback receives the error plus render context such as `
|
|
576
|
+
The callback receives the error plus render context such as `component:`.
|
|
584
577
|
|
|
585
578
|
### Instrumentation
|
|
586
579
|
|
|
587
|
-
Every render emits `render.react-email-rails` through [ActiveSupport::Notifications](https://guides.rubyonrails.org/active_support_instrumentation.html).
|
|
580
|
+
Every render emits `render.react-email-rails` through [ActiveSupport::Notifications](https://guides.rubyonrails.org/active_support_instrumentation.html). Payloads include `component` and successful HTML size in `html_bytes`.
|
|
588
581
|
|
|
589
582
|
```ruby
|
|
590
583
|
ActiveSupport::Notifications.subscribe("render.react-email-rails") do |event|
|
|
@@ -619,7 +612,6 @@ Server, preview, dependency optimization, and build output settings stay owned b
|
|
|
619
612
|
| `emails.path` | `"app/javascript/emails"` | Directory containing email components |
|
|
620
613
|
| `emails.extension` | `[".tsx", ".jsx"]` | Component extension, or an array of extensions |
|
|
621
614
|
| `emails.ignore` | `["**/_*", "**/_*/**"]` | Glob patterns ignored under `emails.path` |
|
|
622
|
-
| `documents` | `false` | Optional `@react-email/editor` document renderer discovery; see [Editor rendering](docs/editor.md) |
|
|
623
615
|
| `standalone` | `true` | Inline production renderer bundle dependencies |
|
|
624
616
|
| `vite` | `{}` | Extra email-only Vite config for compilation and resolution |
|
|
625
617
|
|
|
@@ -687,7 +679,7 @@ Production deploys should run the normal Rails asset task:
|
|
|
687
679
|
bin/rails assets:precompile
|
|
688
680
|
```
|
|
689
681
|
|
|
690
|
-
react-email-rails hooks `react_email_rails:build` into `assets:precompile`. The build task loads `reactEmailRails()` options from your Vite config and writes `tmp/react-email-rails/emails.js` with the email component registry.
|
|
682
|
+
react-email-rails hooks `react_email_rails:build` into `assets:precompile`. The build task loads `reactEmailRails()` options from your Vite config and writes `tmp/react-email-rails/emails.js` with the email component registry.
|
|
691
683
|
|
|
692
684
|
You can run the renderer build directly:
|
|
693
685
|
|
|
@@ -699,7 +691,7 @@ Production rendering requires that bundle. If it is missing, rendering raises `R
|
|
|
699
691
|
|
|
700
692
|
Set `SKIP_REACT_EMAIL_RAILS_BUILD=1` to skip the automatic asset hook. Directly running `bin/rails react_email_rails:build` always attempts the build.
|
|
701
693
|
|
|
702
|
-
The npm package, Vite, React, and `@react-email/render` must be available when Rails runs `assets:precompile`.
|
|
694
|
+
The npm package, Vite, React, and `@react-email/render` must be available when Rails runs `assets:precompile`.
|
|
703
695
|
|
|
704
696
|
The Ruby gem and npm package must stay on the same version. A protocol/version handshake catches mismatched installs and raises an actionable `ReactEmailRails::RenderError`.
|
|
705
697
|
|
data/SECURITY.md
CHANGED
|
@@ -3,7 +3,3 @@
|
|
|
3
3
|
Please report security issues privately by emailing hi@supertape.com.
|
|
4
4
|
|
|
5
5
|
Do not open a public GitHub issue for suspected vulnerabilities. Include the affected version, a minimal reproduction when possible, and any relevant deployment details.
|
|
6
|
-
|
|
7
|
-
## Rendering editor documents
|
|
8
|
-
|
|
9
|
-
`ReactEmailRails.compose` renders an `@react-email/editor` document (Tiptap/ProseMirror JSON) server-side. Treat that document as untrusted input: it is typically authored in a visual editor and stored, then rendered later. The renderer only dispatches to the extensions you register for that document type, and React Email escapes rendered output, but URL and asset sanitization inside your custom extensions (links, image sources, button hrefs) remains your application's responsibility. Validate or sanitize those values where you build extensions or transform the document.
|
|
@@ -75,8 +75,6 @@ class ReactEmailRails::RenderModes::Persistent::Server
|
|
|
75
75
|
}.tap do |body|
|
|
76
76
|
body[:html] = response["html"] if response.key?("html")
|
|
77
77
|
body[:text] = response["text"] if response.key?("text")
|
|
78
|
-
body[:warnings] = response["warnings"] if response.key?("warnings")
|
|
79
|
-
body[:document] = response["document"] if response.key?("document")
|
|
80
78
|
end,
|
|
81
79
|
))
|
|
82
80
|
rescue JSON::ParserError => e
|
|
@@ -8,10 +8,9 @@ class ReactEmailRails::RenderModes::Subprocess
|
|
|
8
8
|
end
|
|
9
9
|
end
|
|
10
10
|
|
|
11
|
-
def initialize(payload:, label
|
|
11
|
+
def initialize(payload:, label:)
|
|
12
12
|
@payload = payload
|
|
13
13
|
@label = label
|
|
14
|
-
@response = response
|
|
15
14
|
end
|
|
16
15
|
|
|
17
16
|
def render
|
|
@@ -20,7 +19,7 @@ class ReactEmailRails::RenderModes::Subprocess
|
|
|
20
19
|
|
|
21
20
|
private
|
|
22
21
|
|
|
23
|
-
attr_reader(:payload, :label
|
|
22
|
+
attr_reader(:payload, :label)
|
|
24
23
|
|
|
25
24
|
def run
|
|
26
25
|
result = capture(payload_json)
|
|
@@ -28,7 +27,7 @@ class ReactEmailRails::RenderModes::Subprocess
|
|
|
28
27
|
|
|
29
28
|
body = JSON.parse(result.stdout)
|
|
30
29
|
validate_metadata!(body)
|
|
31
|
-
|
|
30
|
+
build_rendered_email(body)
|
|
32
31
|
rescue JSON::ParserError => e
|
|
33
32
|
raise(render_error("render process returned invalid JSON: #{e.message}"))
|
|
34
33
|
rescue KeyError => e
|
|
@@ -86,32 +85,12 @@ class ReactEmailRails::RenderModes::Subprocess
|
|
|
86
85
|
raise(render_error(ReactEmailRails::RenderProtocol.mismatch_message(body)))
|
|
87
86
|
end
|
|
88
87
|
|
|
89
|
-
def build_result(body)
|
|
90
|
-
response == :document ? build_document(body) : build_rendered_email(body)
|
|
91
|
-
end
|
|
92
|
-
|
|
93
88
|
def build_rendered_email(body)
|
|
94
89
|
raise(KeyError.new(key: "html")) unless body.key?("html")
|
|
95
90
|
raise(render_error("render process returned an invalid response: html must be a string")) unless body["html"].is_a?(String)
|
|
96
91
|
raise(render_error("render process returned an invalid response: text must be a string")) if body.key?("text") && !body["text"].is_a?(String)
|
|
97
92
|
|
|
98
|
-
ReactEmailRails::RenderedEmail.new(html: body.fetch("html"), text: body["text"].to_s
|
|
99
|
-
end
|
|
100
|
-
|
|
101
|
-
def build_document(body)
|
|
102
|
-
raise(KeyError.new(key: "document")) unless body.key?("document")
|
|
103
|
-
|
|
104
|
-
document = body.fetch("document")
|
|
105
|
-
raise(render_error("parse process returned an invalid response: document must be an object")) unless document.is_a?(Hash)
|
|
106
|
-
|
|
107
|
-
document
|
|
108
|
-
end
|
|
109
|
-
|
|
110
|
-
def warnings_from(body)
|
|
111
|
-
warnings = body["warnings"]
|
|
112
|
-
return [] unless warnings.is_a?(Array)
|
|
113
|
-
|
|
114
|
-
warnings.filter_map { |warning| warning.transform_keys(&:to_sym) if warning.is_a?(Hash) }
|
|
93
|
+
ReactEmailRails::RenderedEmail.new(html: body.fetch("html"), text: body["text"].to_s)
|
|
115
94
|
end
|
|
116
95
|
|
|
117
96
|
def render_error(message)
|
data/lib/react_email_rails.rb
CHANGED
|
@@ -44,29 +44,12 @@ module ReactEmailRails
|
|
|
44
44
|
payload = { component:, props: serialized_props(props) }
|
|
45
45
|
payload[:renderOptions] = render_options if render_options.present?
|
|
46
46
|
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
type:,
|
|
54
|
-
document: document.as_json,
|
|
55
|
-
context: serialized_props(context),
|
|
56
|
-
preview:,
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
perform(payload:, label: type, kind: "document", type:)
|
|
60
|
-
end
|
|
61
|
-
|
|
62
|
-
def parse(type:, html: nil, markdown: nil, context: {})
|
|
63
|
-
payload = {
|
|
64
|
-
kind: "parse",
|
|
65
|
-
type:,
|
|
66
|
-
context: serialized_props(context),
|
|
67
|
-
}.merge(parse_source(html:, markdown:))
|
|
68
|
-
|
|
69
|
-
perform(payload:, label: type, response: :document, kind: "parse", type:)
|
|
47
|
+
instrument(component:) do
|
|
48
|
+
configuration.resolved_render_mode.new(payload:, label: component).render
|
|
49
|
+
end
|
|
50
|
+
rescue ReactEmailRails::RenderError => e
|
|
51
|
+
configuration.on_render_error&.call(e, component:)
|
|
52
|
+
raise
|
|
70
53
|
end
|
|
71
54
|
|
|
72
55
|
def healthy?
|
|
@@ -80,38 +63,13 @@ module ReactEmailRails
|
|
|
80
63
|
|
|
81
64
|
private
|
|
82
65
|
|
|
83
|
-
def perform(payload:, label:, response: :email, **metadata)
|
|
84
|
-
instrument(**metadata) do
|
|
85
|
-
configuration.resolved_render_mode.new(payload:, label:, response:).render
|
|
86
|
-
end
|
|
87
|
-
rescue ReactEmailRails::RenderError => e
|
|
88
|
-
configuration.on_render_error&.call(e, **metadata)
|
|
89
|
-
raise
|
|
90
|
-
end
|
|
91
|
-
|
|
92
|
-
def parse_source(html:, markdown:)
|
|
93
|
-
if !html.nil? && !markdown.nil?
|
|
94
|
-
raise(ArgumentError, "ReactEmailRails.parse accepts only one of html: or markdown:")
|
|
95
|
-
end
|
|
96
|
-
|
|
97
|
-
return { html: html.to_s } unless html.nil?
|
|
98
|
-
return { markdown: markdown.to_s } unless markdown.nil?
|
|
99
|
-
|
|
100
|
-
raise(ArgumentError, "ReactEmailRails.parse requires html: or markdown:")
|
|
101
|
-
end
|
|
102
|
-
|
|
103
66
|
def serialized_props(value)
|
|
104
67
|
configuration.send(:serialize_props, value)
|
|
105
68
|
end
|
|
106
69
|
|
|
107
|
-
def instrument(
|
|
108
|
-
ActiveSupport::Notifications.instrument("render.react-email-rails",
|
|
109
|
-
yield.tap
|
|
110
|
-
next unless result.respond_to?(:html)
|
|
111
|
-
|
|
112
|
-
payload[:html_bytes] = result.html.bytesize
|
|
113
|
-
payload[:warnings] = result.warnings if result.warnings.present?
|
|
114
|
-
end
|
|
70
|
+
def instrument(component:)
|
|
71
|
+
ActiveSupport::Notifications.instrument("render.react-email-rails", component:) do |payload|
|
|
72
|
+
yield.tap { |result| payload[:html_bytes] = result.html.bytesize }
|
|
115
73
|
end
|
|
116
74
|
end
|
|
117
75
|
end
|